about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.circleci/config.yml2
-rw-r--r--.codeclimate.yml10
-rw-r--r--.devcontainer/devcontainer.json20
-rw-r--r--.devcontainer/docker-compose.yml5
-rw-r--r--.prettierignore78
-rw-r--r--.prettierrc.js3
-rw-r--r--.rubocop.yml22
-rw-r--r--CHANGELOG.md19
-rw-r--r--Dockerfile2
-rw-r--r--Gemfile10
-rw-r--r--Gemfile.lock44
-rw-r--r--app.json5
-rw-r--r--app/chewy/statuses_index.rb4
-rw-r--r--app/controllers/activitypub/base_controller.rb1
-rw-r--r--app/controllers/api/base_controller.rb2
-rw-r--r--app/controllers/api/v1/admin/accounts_controller.rb16
-rw-r--r--app/controllers/api/v1/trends/links_controller.rb26
-rw-r--r--app/controllers/api/v1/trends/statuses_controller.rb24
-rw-r--r--app/controllers/api/v1/trends/tags_controller.rb26
-rw-r--r--app/controllers/api/v2/admin/accounts_controller.rb31
-rw-r--r--app/controllers/api/web/embeds_controller.rb2
-rw-r--r--app/helpers/accounts_helper.rb6
-rw-r--r--app/helpers/admin/trends/statuses_helper.rb5
-rw-r--r--app/helpers/application_helper.rb4
-rw-r--r--app/helpers/formatting_helper.rb27
-rw-r--r--app/helpers/routing_helper.rb3
-rw-r--r--app/helpers/statuses_helper.rb14
-rw-r--r--app/javascript/flavours/glitch/components/admin/Counter.js5
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload.js9
-rw-r--r--app/javascript/flavours/glitch/styles/admin.scss2
-rw-r--r--app/javascript/flavours/glitch/styles/components/composer.scss63
-rw-r--r--app/javascript/flavours/glitch/styles/components/modal.scss9
-rw-r--r--app/javascript/flavours/glitch/styles/components/status.scss10
-rw-r--r--app/javascript/flavours/glitch/styles/mastodon-light/diff.scss8
-rw-r--r--app/javascript/mastodon/components/admin/Counter.js5
-rw-r--r--app/javascript/mastodon/features/compose/components/upload.js9
-rw-r--r--app/javascript/mastodon/features/explore/results.js6
-rw-r--r--app/javascript/mastodon/locales/ca.json24
-rw-r--r--app/javascript/mastodon/locales/cs.json46
-rw-r--r--app/javascript/mastodon/locales/fa.json190
-rw-r--r--app/javascript/mastodon/locales/fr.json4
-rw-r--r--app/javascript/mastodon/locales/gd.json8
-rw-r--r--app/javascript/mastodon/locales/ja.json22
-rw-r--r--app/javascript/mastodon/locales/ku.json4
-rw-r--r--app/javascript/mastodon/locales/nl.json212
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json40
-rw-r--r--app/javascript/mastodon/locales/ru.json32
-rw-r--r--app/javascript/mastodon/locales/sk.json8
-rw-r--r--app/javascript/mastodon/locales/sq.json8
-rw-r--r--app/javascript/mastodon/locales/th.json4
-rw-r--r--app/javascript/mastodon/locales/uk.json20
-rw-r--r--app/javascript/mastodon/locales/vi.json20
-rw-r--r--app/javascript/styles/mastodon/admin.scss2
-rw-r--r--app/javascript/styles/mastodon/components.scss80
-rw-r--r--app/lib/activitypub/activity/create.rb4
-rw-r--r--app/lib/activitypub/parser/media_attachment_parser.rb4
-rw-r--r--app/lib/admin/system_check.rb1
-rw-r--r--app/lib/admin/system_check/elasticsearch_check.rb39
-rw-r--r--app/lib/advanced_text_formatter.rb131
-rw-r--r--app/lib/emoji_formatter.rb98
-rw-r--r--app/lib/extractor.rb88
-rw-r--r--app/lib/feed_manager.rb3
-rw-r--r--app/lib/formatter.rb382
-rw-r--r--app/lib/html_aware_formatter.rb42
-rw-r--r--app/lib/link_details_extractor.rb2
-rw-r--r--app/lib/plain_text_formatter.rb30
-rw-r--r--app/lib/rss/serializer.rb23
-rw-r--r--app/lib/text_formatter.rb158
-rw-r--r--app/mailers/application_mailer.rb1
-rw-r--r--app/models/account.rb2
-rw-r--r--app/models/account_filter.rb4
-rw-r--r--app/models/concerns/status_snapshot_concern.rb36
-rw-r--r--app/models/media_attachment.rb7
-rw-r--r--app/models/status.rb22
-rw-r--r--app/models/trends/query.rb4
-rw-r--r--app/models/user.rb10
-rw-r--r--app/serializers/activitypub/actor_serializer.rb7
-rw-r--r--app/serializers/activitypub/note_serializer.rb6
-rw-r--r--app/serializers/rest/account_serializer.rb7
-rw-r--r--app/serializers/rest/announcement_serializer.rb4
-rw-r--r--app/serializers/rest/status_edit_serializer.rb4
-rw-r--r--app/serializers/rest/status_serializer.rb4
-rw-r--r--app/services/activitypub/process_status_update_service.rb22
-rw-r--r--app/services/fetch_link_card_service.rb2
-rw-r--r--app/services/notify_service.rb40
-rw-r--r--app/services/update_status_service.rb28
-rw-r--r--app/validators/status_length_validator.rb50
-rw-r--r--app/views/accounts/_bio.html.haml6
-rw-r--r--app/views/accounts/_moved.html.haml4
-rw-r--r--app/views/admin/account_actions/new.html.haml4
-rw-r--r--app/views/admin/account_warnings/_account_warning.html.haml2
-rw-r--r--app/views/admin/accounts/_account.html.haml2
-rw-r--r--app/views/admin/accounts/index.html.haml2
-rw-r--r--app/views/admin/accounts/show.html.haml8
-rw-r--r--app/views/admin/change_emails/show.html.haml2
-rw-r--r--app/views/admin/disputes/appeals/_appeal.html.haml2
-rw-r--r--app/views/admin/instances/_instance.html.haml2
-rw-r--r--app/views/admin/instances/show.html.haml3
-rw-r--r--app/views/admin/relationships/index.html.haml2
-rw-r--r--app/views/admin/reports/_status.html.haml6
-rw-r--r--app/views/admin/reports/show.html.haml2
-rw-r--r--app/views/admin/settings/edit.html.haml2
-rw-r--r--app/views/admin/statuses/index.html.haml2
-rw-r--r--app/views/admin/tags/show.html.haml2
-rw-r--r--app/views/admin_mailer/new_report.text.erb2
-rw-r--r--app/views/auth/registrations/_status.html.haml2
-rw-r--r--app/views/authorize_interactions/show.html.haml2
-rw-r--r--app/views/authorize_interactions/success.html.haml2
-rw-r--r--app/views/directories/index.html.haml2
-rw-r--r--app/views/disputes/strikes/show.html.haml2
-rw-r--r--app/views/notification_mailer/_status.html.haml6
-rw-r--r--app/views/notification_mailer/_status.text.erb2
-rw-r--r--app/views/notification_mailer/digest.text.erb4
-rw-r--r--app/views/notification_mailer/favourite.html.haml2
-rw-r--r--app/views/notification_mailer/favourite.text.erb2
-rw-r--r--app/views/notification_mailer/follow.html.haml2
-rw-r--r--app/views/notification_mailer/follow.text.erb2
-rw-r--r--app/views/notification_mailer/follow_request.html.haml2
-rw-r--r--app/views/notification_mailer/follow_request.text.erb2
-rw-r--r--app/views/notification_mailer/mention.html.haml2
-rw-r--r--app/views/notification_mailer/mention.text.erb2
-rw-r--r--app/views/notification_mailer/reblog.html.haml2
-rw-r--r--app/views/notification_mailer/reblog.text.erb2
-rw-r--r--app/views/settings/aliases/index.html.haml2
-rw-r--r--app/views/settings/migrations/show.html.haml4
-rw-r--r--app/views/statuses/_detailed_status.html.haml5
-rw-r--r--app/views/statuses/_poll.html.haml4
-rw-r--r--app/views/statuses/_simple_status.html.haml5
-rw-r--r--app/views/user_mailer/warning.html.haml2
-rw-r--r--app/workers/scheduler/user_cleanup_scheduler.rb2
-rw-r--r--boxfile.yml32
-rw-r--r--config/brakeman.ignore80
-rw-r--r--config/database.yml1
-rw-r--r--config/deploy.rb2
-rw-r--r--config/environments/production.rb6
-rw-r--r--config/i18n-tasks.yml4
-rw-r--r--config/initializers/twitter_regex.rb26
-rw-r--r--config/locales/activerecord.ku.yml2
-rw-r--r--config/locales/activerecord.nl.yml2
-rw-r--r--config/locales/ar.yml3
-rw-r--r--config/locales/ca.yml40
-rw-r--r--config/locales/cs.yml35
-rw-r--r--config/locales/da.yml20
-rw-r--r--config/locales/de.yml19
-rw-r--r--config/locales/devise.ca.yml32
-rw-r--r--config/locales/devise.da.yml34
-rw-r--r--config/locales/devise.ku.yml26
-rw-r--r--config/locales/devise.th.yml2
-rw-r--r--config/locales/doorkeeper.ca.yml22
-rw-r--r--config/locales/doorkeeper.da.yml68
-rw-r--r--config/locales/doorkeeper.fa.yml31
-rw-r--r--config/locales/doorkeeper.gd.yml37
-rw-r--r--config/locales/doorkeeper.id.yml33
-rw-r--r--config/locales/doorkeeper.ku.yml8
-rw-r--r--config/locales/doorkeeper.nl.yml46
-rw-r--r--config/locales/doorkeeper.pt-BR.yml1
-rw-r--r--config/locales/doorkeeper.sk.yml1
-rw-r--r--config/locales/doorkeeper.uk.yml19
-rw-r--r--config/locales/doorkeeper.vi.yml8
-rw-r--r--config/locales/en.yml10
-rw-r--r--config/locales/es-AR.yml18
-rw-r--r--config/locales/es-MX.yml22
-rw-r--r--config/locales/es.yml12
-rw-r--r--config/locales/eu.yml8
-rw-r--r--config/locales/fa.yml123
-rw-r--r--config/locales/fi.yml16
-rw-r--r--config/locales/fr.yml24
-rw-r--r--config/locales/gd.yml117
-rw-r--r--config/locales/gl.yml22
-rw-r--r--config/locales/he.yml11
-rw-r--r--config/locales/hu.yml14
-rw-r--r--config/locales/id.yml22
-rw-r--r--config/locales/is.yml10
-rw-r--r--config/locales/it.yml10
-rw-r--r--config/locales/ja.yml14
-rw-r--r--config/locales/ko.yml14
-rw-r--r--config/locales/ku.yml72
-rw-r--r--config/locales/lv.yml22
-rw-r--r--config/locales/nl.yml96
-rw-r--r--config/locales/pl.yml9
-rw-r--r--config/locales/pt-BR.yml1
-rw-r--r--config/locales/pt-PT.yml22
-rw-r--r--config/locales/ru.yml33
-rw-r--r--config/locales/simple_form.cs.yml1
-rw-r--r--config/locales/simple_form.fa.yml39
-rw-r--r--config/locales/simple_form.gd.yml9
-rw-r--r--config/locales/simple_form.ja.yml7
-rw-r--r--config/locales/simple_form.ku.yml38
-rw-r--r--config/locales/simple_form.nl.yml48
-rw-r--r--config/locales/simple_form.sk.yml3
-rw-r--r--config/locales/simple_form.th.yml6
-rw-r--r--config/locales/sk.yml2
-rw-r--r--config/locales/sq.yml24
-rw-r--r--config/locales/sv.yml6
-rw-r--r--config/locales/th.yml40
-rw-r--r--config/locales/tr.yml17
-rw-r--r--config/locales/uk.yml36
-rw-r--r--config/locales/vi.yml122
-rw-r--r--config/locales/zh-CN.yml17
-rw-r--r--config/locales/zh-TW.yml16
-rw-r--r--config/routes.rb4
-rw-r--r--config/sidekiq.yml2
-rw-r--r--docker-compose.yml89
-rw-r--r--lib/mastodon/email_domain_blocks_cli.rb33
-rw-r--r--lib/mastodon/version.rb2
-rw-r--r--lib/sanitize_ext/sanitize_config.rb57
-rw-r--r--package.json19
-rw-r--r--scalingo.json5
-rw-r--r--spec/controllers/admin/accounts_controller_spec.rb10
-rw-r--r--spec/controllers/api/v1/admin/accounts_controller_spec.rb35
-rw-r--r--spec/controllers/api/v2/admin/accounts_controller_spec.rb73
-rw-r--r--spec/controllers/settings/exports/bookmarks_controller_spec.rb (renamed from spec/controllers/settings/exports/bookmarks_controller_specs.rb)11
-rw-r--r--spec/lib/advanced_text_formatter_spec.rb282
-rw-r--r--spec/lib/emoji_formatter_spec.rb55
-rw-r--r--spec/lib/formatter_spec.rb638
-rw-r--r--spec/lib/html_aware_formatter_spec.rb44
-rw-r--r--spec/lib/link_details_extractor_spec.rb8
-rw-r--r--spec/lib/plain_text_formatter_spec.rb24
-rw-r--r--spec/lib/rss/serializer_spec.rb7
-rw-r--r--spec/lib/sanitize_config_spec.rb18
-rw-r--r--spec/lib/text_formatter_spec.rb313
-rw-r--r--spec/models/media_attachment_spec.rb8
-rw-r--r--spec/services/activitypub/process_status_update_service_spec.rb31
-rw-r--r--spec/services/after_block_service_spec.rb8
-rw-r--r--spec/services/delete_account_service_spec.rb14
-rw-r--r--spec/services/mute_service_spec.rb22
-rw-r--r--spec/services/notify_service_spec.rb46
-rw-r--r--spec/services/suspend_account_service_spec.rb12
-rw-r--r--spec/services/unsuspend_account_service_spec.rb26
-rw-r--r--spec/services/update_status_service_spec.rb17
-rw-r--r--spec/validators/status_length_validator_spec.rb15
-rw-r--r--streaming/index.js43
-rw-r--r--yarn.lock95
233 files changed, 3758 insertions, 2748 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 4fcc8c618..b9228f996 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,7 +1,7 @@
 version: 2.1
 
 orbs:
-  ruby: circleci/ruby@1.4.0
+  ruby: circleci/ruby@1.4.1
   node: circleci/node@5.0.1
 
 executors:
diff --git a/.codeclimate.yml b/.codeclimate.yml
index c253bd95a..ee9022cda 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -1,4 +1,4 @@
-version: "2"
+version: '2'
 checks:
   argument-count:
     enabled: false
@@ -34,8 +34,8 @@ plugins:
   sass-lint:
     enabled: true
 exclude_patterns:
-- spec/
-- vendor/asset/
+  - spec/
+  - vendor/asset/
 
-- app/javascript/mastodon/locales/**/*.json
-- config/locales/**/*.yml
+  - app/javascript/mastodon/locales/**/*.json
+  - config/locales/**/*.yml
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 78e940763..628efc8ec 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -5,22 +5,22 @@
   "workspaceFolder": "/workspaces/mastodon",
 
   // Set *default* container specific settings.json values on container create.
-	"settings": {},
+  "settings": {},
 
   // Add the IDs of extensions you want installed when the container is created.
-	"extensions": [
+  "extensions": [
     "EditorConfig.EditorConfig",
     "dbaeumer.vscode-eslint",
-		"rebornix.Ruby"
-	],
+    "rebornix.Ruby"
+  ],
 
   // Use 'forwardPorts' to make a list of ports inside the container available locally.
-	// This can be used to network with other containers or the host.
-	"forwardPorts": [3000, 4000],
+  // This can be used to network with other containers or the host.
+  "forwardPorts": [3000, 4000],
 
-	// Use 'postCreateCommand' to run commands after the container is created.
-	"postCreateCommand": "bundle install --path vendor/bundle && yarn install && ./bin/rails db:setup",
+  // Use 'postCreateCommand' to run commands after the container is created.
+  "postCreateCommand": "bundle install --path vendor/bundle && yarn install && ./bin/rails db:setup",
 
-	// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
-	"remoteUser": "vscode"
+  // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
+  "remoteUser": "vscode"
 }
diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml
index 906fce430..538f6cccd 100644
--- a/.devcontainer/docker-compose.yml
+++ b/.devcontainer/docker-compose.yml
@@ -9,9 +9,9 @@ services:
         # Update 'VARIANT' to pick a version of Ruby: 3, 3.1, 3.0, 2, 2.7, 2.6
         # Append -bullseye or -buster to pin to an OS version.
         # Use -bullseye variants on local arm64/Apple Silicon.
-        VARIANT: "3.0-bullseye"
+        VARIANT: '3.0-bullseye'
         # Optional Node.js version to install
-        NODE_VERSION: "14"
+        NODE_VERSION: '14'
     volumes:
       - ..:/workspaces/mastodon:cached
     environment:
@@ -34,7 +34,6 @@ services:
       - internal_network
     user: vscode
 
-
   db:
     image: postgres:14-alpine
     restart: unless-stopped
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 000000000..de7673eb6
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,78 @@
+# See https://help.github.com/articles/ignoring-files for more about ignoring files.
+#
+# If you find yourself ignoring temporary files generated by your text editor
+# or operating system, you probably want to add a global ignore instead:
+#   git config --global core.excludesfile '~/.gitignore_global'
+
+# Ignore bundler config and downloaded libraries.
+/.bundle
+/vendor/bundle
+
+# Ignore the default SQLite database.
+/db/*.sqlite3
+/db/*.sqlite3-journal
+
+# Ignore all logfiles and tempfiles.
+.eslintcache
+/log/*
+!/log/.keep
+/tmp
+/coverage
+/public/system
+/public/assets
+/public/packs
+/public/packs-test
+.env
+.env.production
+.env.development
+/node_modules/
+/build/
+
+# Ignore Vagrant files
+.vagrant/
+
+# Ignore Capistrano customizations
+/config/deploy/*
+
+# Ignore IDE files
+.vscode/
+.idea/
+
+# Ignore postgres + redis + elasticsearch volume optionally created by docker-compose
+/postgres
+/postgres14
+/redis
+/elasticsearch
+
+# ignore Helm dependency charts
+/chart/charts/*.tgz
+
+# Ignore Apple files
+.DS_Store
+
+# Ignore vim files
+*~
+*.swp
+
+# Ignore npm debug log
+npm-debug.log
+
+# Ignore yarn log files
+yarn-error.log
+yarn-debug.log
+
+# Ignore vagrant log files
+*-cloudimg-console.log
+
+# Ignore Docker option files
+docker-compose.override.yml
+
+# Ignore Helm files
+/chart
+
+# Ignore emoji map file
+/app/javascript/mastodon/features/emoji/emoji_map.json
+
+# Ignore locale files
+/app/javascript/mastodon/locales
+/config/locales
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 000000000..1d70813d5
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+  singleQuote: true
+}
diff --git a/.rubocop.yml b/.rubocop.yml
index 4948aea5a..a76937426 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -5,17 +5,17 @@ AllCops:
   TargetRubyVersion: 2.5
   NewCops: disable
   Exclude:
-  - 'spec/**/*'
-  - 'db/**/*'
-  - 'app/views/**/*'
-  - 'config/**/*'
-  - 'bin/*'
-  - 'Rakefile'
-  - 'node_modules/**/*'
-  - 'Vagrantfile'
-  - 'vendor/**/*'
-  - 'lib/json_ld/*'
-  - 'lib/templates/**/*'
+    - 'spec/**/*'
+    - 'db/**/*'
+    - 'app/views/**/*'
+    - 'config/**/*'
+    - 'bin/*'
+    - 'Rakefile'
+    - 'node_modules/**/*'
+    - 'Vagrantfile'
+    - 'vendor/**/*'
+    - 'lib/json_ld/*'
+    - 'lib/templates/**/*'
 
 Bundler/OrderedGems:
   Enabled: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 519c561a6..dd0ccc5f7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,10 +3,10 @@ Changelog
 
 All notable changes to this project will be documented in this file.
 
-## Unreleased
+## [3.5.0] - 2022-03-30
 ### Added
 
-- **Add support for post editing** ([Gargron](https://github.com/mastodon/mastodon/pull/16697), [Gargron](https://github.com/mastodon/mastodon/pull/17727), [Gargron](https://github.com/mastodon/mastodon/pull/17728), [Gargron](https://github.com/mastodon/mastodon/pull/17320), [Gargron](https://github.com/mastodon/mastodon/pull/17404), [Gargron](https://github.com/mastodon/mastodon/pull/17390), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17335), [Gargron](https://github.com/mastodon/mastodon/pull/17696), [Gargron](https://github.com/mastodon/mastodon/pull/17745), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17740), [Gargron](https://github.com/mastodon/mastodon/pull/17697), [Gargron](https://github.com/mastodon/mastodon/pull/17648), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17531), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17499), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17498), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17380), [Gargron](https://github.com/mastodon/mastodon/pull/17373), [Gargron](https://github.com/mastodon/mastodon/pull/17334), [Gargron](https://github.com/mastodon/mastodon/pull/17333), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17699), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17748))
+- **Add support for incoming edited posts** ([Gargron](https://github.com/mastodon/mastodon/pull/16697), [Gargron](https://github.com/mastodon/mastodon/pull/17727), [Gargron](https://github.com/mastodon/mastodon/pull/17728), [Gargron](https://github.com/mastodon/mastodon/pull/17320), [Gargron](https://github.com/mastodon/mastodon/pull/17404), [Gargron](https://github.com/mastodon/mastodon/pull/17390), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17335), [Gargron](https://github.com/mastodon/mastodon/pull/17696), [Gargron](https://github.com/mastodon/mastodon/pull/17745), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17740), [Gargron](https://github.com/mastodon/mastodon/pull/17697), [Gargron](https://github.com/mastodon/mastodon/pull/17648), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17531), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17499), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17498), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17380), [Gargron](https://github.com/mastodon/mastodon/pull/17373), [Gargron](https://github.com/mastodon/mastodon/pull/17334), [Gargron](https://github.com/mastodon/mastodon/pull/17333), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17699), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17748))
   - Previous versions remain available for perusal and comparison
   - People who reblogged a post are notified when it's edited
   - New REST APIs:
@@ -14,7 +14,7 @@ All notable changes to this project will be documented in this file.
     - `GET /api/v1/statuses/:id/history`
     - `GET /api/v1/statuses/:id/source`
   - New streaming API event:
-    - `update`
+    - `status.update`
 - **Add appeals for moderator decisions** ([Gargron](https://github.com/mastodon/mastodon/pull/17364), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17725), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17566), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17652), [Gargron](https://github.com/mastodon/mastodon/pull/17616), [Gargron](https://github.com/mastodon/mastodon/pull/17615), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17554), [Gargron](https://github.com/mastodon/mastodon/pull/17523))
   - All default moderator decisions now notify the affected user by e-mail
   - They now link to an appeal page instead of suggesting replying to the e-mail
@@ -63,7 +63,7 @@ All notable changes to this project will be documented in this file.
 - Add `types` param to `GET /api/v1/notifications` in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/17767))
 - **Add notifications for moderators about new sign-ups** ([Gargron](https://github.com/mastodon/mastodon/pull/16953), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17629))
   - When a new user confirms e-mail, moderators receive a notification
-  - New streaming API event:
+  - New notification type:
     - `admin.sign_up`
 - Add authentication history ([Gargron](https://github.com/mastodon/mastodon/pull/16408), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16428), [baby-gnu](https://github.com/mastodon/mastodon/pull/16654))
 - Add ability to automatically delete old posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16529), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17691), [tribela](https://github.com/mastodon/mastodon/pull/16653))
@@ -81,6 +81,7 @@ All notable changes to this project will be documented in this file.
 - Add lazy loading for emoji picker in web UI ([mashirozx](https://github.com/mastodon/mastodon/pull/16907), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17011))
 - Add single option votes tooltip in polls in web UI ([Brawaru](https://github.com/mastodon/mastodon/pull/16849))
 - Add confirmation modal when closing media edit modal with unsaved changes in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16518))
+- Add hint about missing media attachment description in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/17845))
 - Add support for fetching Create and Announce activities by URI in ActivityPub ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16383))
 - Add `S3_FORCE_SINGLE_REQUEST` environment variable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16866))
 - Add `OMNIAUTH_ONLY` environment variable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17288), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17345))
@@ -130,6 +131,11 @@ All notable changes to this project will be documented in this file.
 
 ### Fixed
 
+- Fix IDN domains not being rendered correctly in a few left-over places ([Gargron](https://github.com/mastodon/mastodon/pull/17848))
+- Fix Sanskrit translation not being used in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17820))
+- Fix Kurdish languages having the wrong language codes ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17812))
+- Fix pghero making database schema suggestions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17807))
+- Fix encoding glitch in the OpenGraph description of a profile page ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17821))
 - Fix web manifest not permitting PWA usage from alternate domains ([HolgerHuo](https://github.com/mastodon/mastodon/pull/16714))
 - Fix not being able to edit media attachments for scheduled posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17690))
 - Fix subscribed relay activities being recorded as boosts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17571))
@@ -191,6 +197,11 @@ All notable changes to this project will be documented in this file.
 - Fix hashtag autocomplete overriding user-typed case ([weex](https://github.com/mastodon/mastodon/pull/16460))
 - Fix WebAuthn authentication setup to not prompt for PIN ([truongnmt](https://github.com/mastodon/mastodon/pull/16545))
 
+### Security
+
+- Fix being able to post URLs longer than 4096 characters ([Gargron](https://github.com/mastodon/mastodon/pull/17908))
+- Fix being able to bypass e-mail restrictions ([Gargron](https://github.com/mastodon/mastodon/pull/17909))
+
 ## [3.4.6] - 2022-02-03
 ### Fixed
 
diff --git a/Dockerfile b/Dockerfile
index 0185ebfe9..da4fb964c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -5,7 +5,7 @@ SHELL ["/bin/bash", "-c"]
 RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
 
 # Install Node v16 (LTS)
-ENV NODE_VER="16.13.2"
+ENV NODE_VER="16.14.2"
 RUN ARCH= && \
     dpkgArch="$(dpkg --print-architecture)" && \
   case "${dpkgArch##*-}" in \
diff --git a/Gemfile b/Gemfile
index 2621b77a5..4c8cd2f5a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -89,7 +89,7 @@ gem 'stoplight', '~> 2.2.1'
 gem 'strong_migrations', '~> 0.7'
 gem 'tty-prompt', '~> 0.23', require: false
 gem 'twitter-text', '~> 3.1.0'
-gem 'tzinfo-data', '~> 1.2021'
+gem 'tzinfo-data', '~> 1.2022'
 gem 'webpacker', '~> 5.4'
 gem 'webpush', '~> 0.3'
 gem 'webauthn', '~> 3.0.0.alpha1'
@@ -131,15 +131,15 @@ group :development do
   gem 'better_errors', '~> 2.9'
   gem 'binding_of_caller', '~> 1.0'
   gem 'bullet', '~> 7.0'
-  gem 'letter_opener', '~> 1.7'
+  gem 'letter_opener', '~> 1.8'
   gem 'letter_opener_web', '~> 2.0'
   gem 'memory_profiler'
-  gem 'rubocop', '~> 1.25', require: false
-  gem 'rubocop-rails', '~> 2.13', require: false
+  gem 'rubocop', '~> 1.26', require: false
+  gem 'rubocop-rails', '~> 2.14', require: false
   gem 'brakeman', '~> 5.2', require: false
   gem 'bundler-audit', '~> 0.9', require: false
 
-  gem 'capistrano', '~> 3.16'
+  gem 'capistrano', '~> 3.17'
   gem 'capistrano-rails', '~> 1.6'
   gem 'capistrano-rbenv', '~> 2.2'
   gem 'capistrano-yarn', '~> 2.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 2275d9453..e784b81cf 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -96,7 +96,7 @@ GEM
       aws-sigv4 (~> 1.4)
     aws-sigv4 (1.4.0)
       aws-eventstream (~> 1, >= 1.0.2)
-    bcrypt (3.1.16)
+    bcrypt (3.1.17)
     better_errors (2.9.1)
       coderay (>= 1.0.0)
       erubi (>= 1.0.0)
@@ -121,7 +121,7 @@ GEM
       bundler (>= 1.2.0, < 3)
       thor (~> 1.0)
     byebug (11.1.3)
-    capistrano (3.16.0)
+    capistrano (3.17.0)
       airbrussh (>= 1.0.0)
       i18n
       rake (>= 10.0.0)
@@ -157,7 +157,7 @@ GEM
     climate_control (0.2.0)
     coderay (1.1.3)
     color_diff (0.1)
-    concurrent-ruby (1.1.9)
+    concurrent-ruby (1.1.10)
     connection_pool (2.2.5)
     cose (1.0.0)
       cbor (~> 0.5.9)
@@ -174,11 +174,11 @@ GEM
       railties (>= 4.1.0)
       responders
       warden (~> 1.2.3)
-    devise-two-factor (4.0.1)
-      activesupport (< 6.2)
+    devise-two-factor (4.0.2)
+      activesupport (< 7.1)
       attr_encrypted (>= 1.3, < 4, != 2)
       devise (~> 4.0)
-      railties (< 6.2)
+      railties (< 7.1)
       rotp (~> 6.0)
     devise_pam_authenticatable2 (9.2.0)
       devise (>= 4.0.0)
@@ -351,8 +351,8 @@ GEM
       terrapin (~> 0.6.0)
     launchy (2.5.0)
       addressable (~> 2.7)
-    letter_opener (1.7.0)
-      launchy (~> 2.2)
+    letter_opener (1.8.1)
+      launchy (>= 2.2, < 3)
     letter_opener_web (2.0.0)
       actionmailer (>= 5.2)
       letter_opener (~> 1.7)
@@ -433,8 +433,8 @@ GEM
     openssl-signature_algorithm (0.4.0)
     orm_adapter (0.5.0)
     ox (2.14.10)
-    parallel (1.21.0)
-    parser (3.1.0.0)
+    parallel (1.22.1)
+    parser (3.1.1.0)
       ast (~> 2.4.1)
     parslet (2.0.0)
     pastel (0.8.0)
@@ -527,7 +527,7 @@ GEM
     redis (4.5.1)
     redis-namespace (1.8.2)
       redis (>= 3.0.4)
-    regexp_parser (2.2.0)
+    regexp_parser (2.2.1)
     request_store (1.5.0)
       rack (>= 1.4)
     responders (3.0.1)
@@ -562,18 +562,18 @@ GEM
     rspec-support (3.11.0)
     rspec_junit_formatter (0.5.1)
       rspec-core (>= 2, < 4, != 2.12.0)
-    rubocop (1.25.1)
+    rubocop (1.26.1)
       parallel (~> 1.10)
       parser (>= 3.1.0.0)
       rainbow (>= 2.2.2, < 4.0)
       regexp_parser (>= 1.8, < 3.0)
       rexml
-      rubocop-ast (>= 1.15.1, < 2.0)
+      rubocop-ast (>= 1.16.0, < 2.0)
       ruby-progressbar (~> 1.7)
       unicode-display_width (>= 1.4.0, < 3.0)
-    rubocop-ast (1.15.1)
-      parser (>= 3.0.1.1)
-    rubocop-rails (2.13.2)
+    rubocop-ast (1.16.0)
+      parser (>= 3.1.1.0)
+    rubocop-rails (2.14.2)
       activesupport (>= 4.2.0)
       rack (>= 1.1)
       rubocop (>= 1.7.0, < 2.0)
@@ -669,7 +669,7 @@ GEM
       unf (~> 0.1.0)
     tzinfo (2.0.4)
       concurrent-ruby (~> 1.0)
-    tzinfo-data (1.2021.5)
+    tzinfo-data (1.2022.1)
       tzinfo (>= 1.0.0)
     unf (0.1.4)
       unf_ext
@@ -735,7 +735,7 @@ DEPENDENCIES
   browser
   bullet (~> 7.0)
   bundler-audit (~> 0.9)
-  capistrano (~> 3.16)
+  capistrano (~> 3.17)
   capistrano-rails (~> 1.6)
   capistrano-rbenv (~> 2.2)
   capistrano-yarn (~> 2.0)
@@ -774,7 +774,7 @@ DEPENDENCIES
   json-ld-preloaded (~> 3.2)
   kaminari (~> 1.2)
   kt-paperclip (~> 7.1)
-  letter_opener (~> 1.7)
+  letter_opener (~> 1.8)
   letter_opener_web (~> 2.0)
   link_header (~> 0.0)
   lograge (~> 0.11)
@@ -819,8 +819,8 @@ DEPENDENCIES
   rspec-rails (~> 5.1)
   rspec-sidekiq (~> 3.1)
   rspec_junit_formatter (~> 0.5)
-  rubocop (~> 1.25)
-  rubocop-rails (~> 2.13)
+  rubocop (~> 1.26)
+  rubocop-rails (~> 2.14)
   ruby-progressbar (~> 1.11)
   sanitize (~> 6.0)
   scenic (~> 1.6)
@@ -839,7 +839,7 @@ DEPENDENCIES
   thor (~> 1.2)
   tty-prompt (~> 0.23)
   twitter-text (~> 3.1.0)
-  tzinfo-data (~> 1.2021)
+  tzinfo-data (~> 1.2022)
   webauthn (~> 3.0.0.alpha1)
   webmock (~> 3.14)
   webpacker (~> 5.4)
diff --git a/app.json b/app.json
index 6b4365383..c694908c5 100644
--- a/app.json
+++ b/app.json
@@ -95,8 +95,5 @@
   "scripts": {
     "postdeploy": "bundle exec rails db:migrate && bundle exec rails db:seed"
   },
-  "addons": [
-    "heroku-postgresql",
-    "heroku-redis"
-  ]
+  "addons": ["heroku-postgresql", "heroku-redis"]
 }
diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb
index 65cbb6fcd..bfd61a048 100644
--- a/app/chewy/statuses_index.rb
+++ b/app/chewy/statuses_index.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class StatusesIndex < Chewy::Index
+  include FormattingHelper
+
   settings index: { refresh_interval: '15m' }, analysis: {
     filter: {
       english_stop: {
@@ -57,7 +59,7 @@ class StatusesIndex < Chewy::Index
     field :id, type: 'long'
     field :account_id, type: 'long'
 
-    field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.ordered_media_attachments.map(&:description)).concat(status.preloadable_poll ? status.preloadable_poll.options : []).join("\n\n") } do
+    field :text, type: 'text', value: ->(status) { [status.spoiler_text, extract_status_plain_text(status)].concat(status.ordered_media_attachments.map(&:description)).concat(status.preloadable_poll ? status.preloadable_poll.options : []).join("\n\n") } do
       field :stemmed, type: 'text', analyzer: 'content'
     end
 
diff --git a/app/controllers/activitypub/base_controller.rb b/app/controllers/activitypub/base_controller.rb
index 4cbc3ab8f..196d85a32 100644
--- a/app/controllers/activitypub/base_controller.rb
+++ b/app/controllers/activitypub/base_controller.rb
@@ -2,6 +2,7 @@
 
 class ActivityPub::BaseController < Api::BaseController
   skip_before_action :require_authenticated_user!
+  skip_around_action :set_locale
 
   private
 
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index 72c30dec7..d96285b44 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -15,8 +15,6 @@ class Api::BaseController < ApplicationController
 
   protect_from_forgery with: :null_session
 
-  skip_around_action :set_locale
-
   rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
     render json: { error: e.to_s }, status: 422
   end
diff --git a/app/controllers/api/v1/admin/accounts_controller.rb b/app/controllers/api/v1/admin/accounts_controller.rb
index 65330b8c8..4b6dab208 100644
--- a/app/controllers/api/v1/admin/accounts_controller.rb
+++ b/app/controllers/api/v1/admin/accounts_controller.rb
@@ -104,13 +104,27 @@ class Api::V1::Admin::AccountsController < Api::BaseController
   end
 
   def filtered_accounts
-    AccountFilter.new(filter_params).results
+    AccountFilter.new(translated_filter_params).results
   end
 
   def filter_params
     params.permit(*FILTER_PARAMS)
   end
 
+  def translated_filter_params
+    translated_params = { origin: 'local', status: 'active' }.merge(filter_params.slice(*AccountFilter::KEYS))
+
+    translated_params[:origin] = 'remote' if params[:remote].present?
+
+    %i(active pending disabled silenced suspended).each do |status|
+      translated_params[:status] = status.to_s if params[status].present?
+    end
+
+    translated_params[:permissions] = 'staff' if params[:staff].present?
+
+    translated_params
+  end
+
   def insert_pagination_headers
     set_pagination_headers(next_path, prev_path)
   end
diff --git a/app/controllers/api/v1/trends/links_controller.rb b/app/controllers/api/v1/trends/links_controller.rb
index ad20e7f8b..b1cde5a4b 100644
--- a/app/controllers/api/v1/trends/links_controller.rb
+++ b/app/controllers/api/v1/trends/links_controller.rb
@@ -3,6 +3,10 @@
 class Api::V1::Trends::LinksController < Api::BaseController
   before_action :set_links
 
+  after_action :insert_pagination_headers
+
+  DEFAULT_LINKS_LIMIT = 10
+
   def index
     render json: @links, each_serializer: REST::Trends::LinkSerializer
   end
@@ -20,6 +24,26 @@ class Api::V1::Trends::LinksController < Api::BaseController
   end
 
   def links_from_trends
-    Trends.links.query.allowed.in_locale(content_locale).limit(limit_param(10))
+    Trends.links.query.allowed.in_locale(content_locale).offset(offset_param).limit(limit_param(DEFAULT_LINKS_LIMIT))
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def pagination_params(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
+  end
+
+  def next_path
+    api_v1_trends_links_url pagination_params(offset: offset_param + limit_param(DEFAULT_LINKS_LIMIT))
+  end
+
+  def prev_path
+    api_v1_trends_links_url pagination_params(offset: offset_param - limit_param(DEFAULT_LINKS_LIMIT)) if offset_param > limit_param(DEFAULT_LINKS_LIMIT)
+  end
+
+  def offset_param
+    params[:offset].to_i
   end
 end
diff --git a/app/controllers/api/v1/trends/statuses_controller.rb b/app/controllers/api/v1/trends/statuses_controller.rb
index d4ec97ae5..4977803fb 100644
--- a/app/controllers/api/v1/trends/statuses_controller.rb
+++ b/app/controllers/api/v1/trends/statuses_controller.rb
@@ -3,6 +3,8 @@
 class Api::V1::Trends::StatusesController < Api::BaseController
   before_action :set_statuses
 
+  after_action :insert_pagination_headers
+
   def index
     render json: @statuses, each_serializer: REST::StatusSerializer
   end
@@ -22,6 +24,26 @@ class Api::V1::Trends::StatusesController < Api::BaseController
   def statuses_from_trends
     scope = Trends.statuses.query.allowed.in_locale(content_locale)
     scope = scope.filtered_for(current_account) if user_signed_in?
-    scope.limit(limit_param(DEFAULT_STATUSES_LIMIT))
+    scope.offset(offset_param).limit(limit_param(DEFAULT_STATUSES_LIMIT))
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def pagination_params(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
+  end
+
+  def next_path
+    api_v1_trends_statuses_url pagination_params(offset: offset_param + limit_param(DEFAULT_STATUSES_LIMIT))
+  end
+
+  def prev_path
+    api_v1_trends_statuses_url pagination_params(offset: offset_param - limit_param(DEFAULT_STATUSES_LIMIT)) if offset_param > limit_param(DEFAULT_STATUSES_LIMIT)
+  end
+
+  def offset_param
+    params[:offset].to_i
   end
 end
diff --git a/app/controllers/api/v1/trends/tags_controller.rb b/app/controllers/api/v1/trends/tags_controller.rb
index 1334b72d2..d77857871 100644
--- a/app/controllers/api/v1/trends/tags_controller.rb
+++ b/app/controllers/api/v1/trends/tags_controller.rb
@@ -3,6 +3,10 @@
 class Api::V1::Trends::TagsController < Api::BaseController
   before_action :set_tags
 
+  after_action :insert_pagination_headers
+
+  DEFAULT_TAGS_LIMIT = 10
+
   def index
     render json: @tags, each_serializer: REST::TagSerializer
   end
@@ -12,10 +16,30 @@ class Api::V1::Trends::TagsController < Api::BaseController
   def set_tags
     @tags = begin
       if Setting.trends
-        Trends.tags.query.allowed.limit(limit_param(10))
+        Trends.tags.query.allowed.limit(limit_param(DEFAULT_TAGS_LIMIT))
       else
         []
       end
     end
   end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def pagination_params(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
+  end
+
+  def next_path
+    api_v1_trends_tags_url pagination_params(offset: offset_param + limit_param(DEFAULT_TAGS_LIMIT))
+  end
+
+  def prev_path
+    api_v1_trends_tags_url pagination_params(offset: offset_param - limit_param(DEFAULT_TAGS_LIMIT)) if offset_param > limit_param(DEFAULT_TAGS_LIMIT)
+  end
+
+  def offset_param
+    params[:offset].to_i
+  end
 end
diff --git a/app/controllers/api/v2/admin/accounts_controller.rb b/app/controllers/api/v2/admin/accounts_controller.rb
new file mode 100644
index 000000000..a89e6835e
--- /dev/null
+++ b/app/controllers/api/v2/admin/accounts_controller.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class Api::V2::Admin::AccountsController < Api::V1::Admin::AccountsController
+  FILTER_PARAMS = %i(
+    origin
+    status
+    permissions
+    username
+    by_domain
+    display_name
+    email
+    ip
+    invited_by
+  ).freeze
+
+  PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze
+
+  private
+
+  def filtered_accounts
+    AccountFilter.new(filter_params).results
+  end
+
+  def filter_params
+    params.permit(*FILTER_PARAMS)
+  end
+
+  def pagination_params(core_params)
+    params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
+  end
+end
diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb
index 741ba910f..58f6345e6 100644
--- a/app/controllers/api/web/embeds_controller.rb
+++ b/app/controllers/api/web/embeds_controller.rb
@@ -15,7 +15,7 @@ class Api::Web::EmbedsController < Api::Web::BaseController
     return not_found if oembed.nil?
 
     begin
-      oembed[:html] = Formatter.instance.sanitize(oembed[:html], Sanitize::Config::MASTODON_OEMBED)
+      oembed[:html] = Sanitize.fragment(oembed[:html], Sanitize::Config::MASTODON_OEMBED)
     rescue ArgumentError
       return not_found
     end
diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb
index bb2374c0e..f0becf8bd 100644
--- a/app/helpers/accounts_helper.rb
+++ b/app/helpers/accounts_helper.rb
@@ -2,10 +2,12 @@
 
 module AccountsHelper
   def display_name(account, **options)
+    str = account.display_name.presence || account.username
+
     if options[:custom_emojify]
-      Formatter.instance.format_display_name(account, **options)
+      prerender_custom_emojis(h(str), account.emojis)
     else
-      account.display_name.presence || account.username
+      str
     end
   end
 
diff --git a/app/helpers/admin/trends/statuses_helper.rb b/app/helpers/admin/trends/statuses_helper.rb
index d16e3dd12..214c1e2a6 100644
--- a/app/helpers/admin/trends/statuses_helper.rb
+++ b/app/helpers/admin/trends/statuses_helper.rb
@@ -12,9 +12,6 @@ module Admin::Trends::StatusesHelper
 
     return '' if text.blank?
 
-    html = Formatter.instance.send(:encode, text)
-    html = Formatter.instance.send(:encode_custom_emojis, html, status.emojis, prefers_autoplay?)
-
-    html.html_safe # rubocop:disable Rails/OutputSafety
+    prerender_custom_emojis(h(text), status.emojis)
   end
 end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index eace78af6..d482ad1a2 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -240,4 +240,8 @@ module ApplicationHelper
       end
     end.values
   end
+
+  def prerender_custom_emojis(html, custom_emojis)
+    EmojiFormatter.new(html, custom_emojis, animate: prefers_autoplay?).to_s
+  end
 end
diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb
new file mode 100644
index 000000000..2a622ae0b
--- /dev/null
+++ b/app/helpers/formatting_helper.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module FormattingHelper
+  def html_aware_format(text, local, options = {})
+    HtmlAwareFormatter.new(text, local, options).to_s
+  end
+
+  def linkify(text, options = {})
+    TextFormatter.new(text, options).to_s
+  end
+
+  def extract_status_plain_text(status)
+    PlainTextFormatter.new(status.text, status.local?).to_s
+  end
+
+  def status_content_format(status)
+    html_aware_format(status.text, status.local?, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []), content_type: status.content_type)
+  end
+
+  def account_bio_format(account)
+    html_aware_format(account.note, account.local?)
+  end
+
+  def account_field_value_format(field, with_rel_me: true)
+    html_aware_format(field.value, field.account.local?, with_rel_me: with_rel_me, with_domains: true, multiline: false)
+  end
+end
diff --git a/app/helpers/routing_helper.rb b/app/helpers/routing_helper.rb
index fb24a1b28..f95f46a56 100644
--- a/app/helpers/routing_helper.rb
+++ b/app/helpers/routing_helper.rb
@@ -2,6 +2,7 @@
 
 module RoutingHelper
   extend ActiveSupport::Concern
+
   include Rails.application.routes.url_helpers
   include ActionView::Helpers::AssetTagHelper
   include Webpacker::Helper
@@ -22,8 +23,6 @@ module RoutingHelper
     full_asset_url(asset_pack_path(source, **options))
   end
 
-  private
-
   def use_storage?
     Rails.configuration.x.use_s3 || Rails.configuration.x.use_swift
   end
diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb
index d328f89b7..e92b4c839 100644
--- a/app/helpers/statuses_helper.rb
+++ b/app/helpers/statuses_helper.rb
@@ -113,20 +113,6 @@ module StatusesHelper
     end
   end
 
-  private
-
-  def simplified_text(text)
-    text.dup.tap do |new_text|
-      URI.extract(new_text).each do |url|
-        new_text.gsub!(url, '')
-      end
-
-      new_text.gsub!(Account::MENTION_RE, '')
-      new_text.gsub!(Tag::HASHTAG_RE, '')
-      new_text.gsub!(/\s+/, '')
-    end
-  end
-
   def embedded_view?
     params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION
   end
diff --git a/app/javascript/flavours/glitch/components/admin/Counter.js b/app/javascript/flavours/glitch/components/admin/Counter.js
index ecb242950..a4d6cef41 100644
--- a/app/javascript/flavours/glitch/components/admin/Counter.js
+++ b/app/javascript/flavours/glitch/components/admin/Counter.js
@@ -33,6 +33,7 @@ export default class Counter extends React.PureComponent {
     label: PropTypes.string.isRequired,
     href: PropTypes.string,
     params: PropTypes.object,
+    target: PropTypes.string,
   };
 
   state = {
@@ -54,7 +55,7 @@ export default class Counter extends React.PureComponent {
   }
 
   render () {
-    const { label, href } = this.props;
+    const { label, href, target } = this.props;
     const { loading, data } = this.state;
 
     let content;
@@ -100,7 +101,7 @@ export default class Counter extends React.PureComponent {
 
     if (href) {
       return (
-        <a href={href} className='sparkline'>
+        <a href={href} className='sparkline' target={target}>
           {inner}
         </a>
       );
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload.js b/app/javascript/flavours/glitch/features/compose/components/upload.js
index 338bfca37..963b95c87 100644
--- a/app/javascript/flavours/glitch/features/compose/components/upload.js
+++ b/app/javascript/flavours/glitch/features/compose/components/upload.js
@@ -5,7 +5,6 @@ import Motion from 'flavours/glitch/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { FormattedMessage } from 'react-intl';
-import classNames from 'classnames';
 import Icon from 'flavours/glitch/components/icon';
 import { isUserTouching } from 'flavours/glitch/util/is_mobile';
 
@@ -44,10 +43,16 @@ export default class Upload extends ImmutablePureComponent {
         <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12, }) }}>
           {({ scale }) => (
             <div style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
-              <div className={classNames('composer--upload_form--actions', { active: true })}>
+              <div className='composer--upload_form--actions'>
                 <button className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
                 {!isEditingStatus && (<button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>)}
               </div>
+
+              {(media.get('description') || '').length === 0 && (
+                <div className='composer--upload_form--item__warning'>
+                  <button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button>
+                </div>
+              )}
             </div>
           )}
         </Motion>
diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss
index 40cd899b3..27be22f1b 100644
--- a/app/javascript/flavours/glitch/styles/admin.scss
+++ b/app/javascript/flavours/glitch/styles/admin.scss
@@ -1322,7 +1322,7 @@ a.sparkline {
       width: 50px;
       height: 21px;
       position: absolute;
-      bottom: 8px;
+      bottom: 0;
       right: 15px;
       background: linear-gradient(to left, $ui-base-color, transparent);
       pointer-events: none;
diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss
index 937751d00..96ea096e1 100644
--- a/app/javascript/flavours/glitch/styles/components/composer.scss
+++ b/app/javascript/flavours/glitch/styles/components/composer.scss
@@ -425,54 +425,12 @@
     background-repeat: no-repeat;
     overflow: hidden;
 
-    textarea {
-      display: block;
-      position: absolute;
-      box-sizing: border-box;
-      bottom: 0;
-      left: 0;
-      margin: 0;
-      border: 0;
-      padding: 10px;
-      width: 100%;
-      color: $secondary-text-color;
-      background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
-      font-size: 14px;
-      font-family: inherit;
-      font-weight: 500;
-      opacity: 0;
-      z-index: 2;
-      transition: opacity .1s ease;
-
-      &:focus { color: $white }
-
-      &::placeholder {
-        opacity: 0.54;
-        color: $secondary-text-color;
-      }
-    }
-
     & > .close { mix-blend-mode: difference }
   }
 
-  &.active {
-    & > div {
-      textarea { opacity: 1 }
-    }
-  }
-}
-
-.composer--upload_form--actions {
-  background: linear-gradient(180deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
-  display: flex;
-  align-items: flex-start;
-  justify-content: space-between;
-  opacity: 0;
-  transition: opacity .1s ease;
-
   .icon-button {
     flex: 0 1 auto;
-    color: $ui-secondary-color;
+    color: $secondary-text-color;
     font-size: 14px;
     font-weight: 500;
     padding: 10px;
@@ -481,15 +439,28 @@
     &:hover,
     &:focus,
     &:active {
-      color: lighten($ui-secondary-color, 4%);
+      color: lighten($secondary-text-color, 7%);
     }
   }
 
-  &.active {
-    opacity: 1;
+  &__warning {
+    position: absolute;
+    z-index: 2;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    box-sizing: border-box;
+    background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
   }
 }
 
+.composer--upload_form--actions {
+  background: linear-gradient(180deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+}
+
 .composer--upload_form--progress {
   display: flex;
   padding: 10px;
diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss
index 7e6918356..61c292b19 100644
--- a/app/javascript/flavours/glitch/styles/components/modal.scss
+++ b/app/javascript/flavours/glitch/styles/components/modal.scss
@@ -609,6 +609,15 @@
     color: $inverted-text-color;
   }
 
+  .status__content__spoiler-link {
+    color: $primary-text-color;
+    background: $ui-primary-color;
+
+    &:hover {
+      background: lighten($ui-primary-color, 8%);
+    }
+  }
+
   .dialog-option .poll__input {
     border-color: $inverted-text-color;
     color: $ui-secondary-color;
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index b9dd3107b..d39069410 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -198,7 +198,8 @@
   .status__content__spoiler-link {
     background: lighten($ui-base-color, 30%);
 
-    &:hover {
+    &:hover,
+    &:focus {
       background: lighten($ui-base-color, 33%);
       text-decoration: none;
     }
@@ -222,13 +223,13 @@
   background: lighten($ui-base-color, 30%);
   border: 0;
   color: $inverted-text-color;
-  font-weight: 500;
+  font-weight: 700;
   font-size: 11px;
   padding: 0 5px;
   text-transform: uppercase;
   line-height: inherit;
   cursor: pointer;
-  vertical-align: bottom;
+  vertical-align: top;
 
   &:hover {
     background: lighten($ui-base-color, 33%);
@@ -768,7 +769,8 @@ a.status__display-name,
     background: $ui-base-lighter-color;
     color: $inverted-text-color;
 
-    &:hover {
+    &:hover,
+    &:focus {
       background: lighten($ui-base-color, 29%);
       text-decoration: none;
     }
diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
index 020d39aff..bb91abdac 100644
--- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
+++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
@@ -165,14 +165,6 @@
   }
 }
 
-.composer--upload_form--item > div input {
-  color: lighten($white, 7%);
-
-  &::placeholder {
-    color: lighten($white, 10%);
-  }
-}
-
 .dropdown-menu__separator,
 .dropdown-menu__item.edited-timestamp__history__item,
 .dropdown-menu__container__header,
diff --git a/app/javascript/mastodon/components/admin/Counter.js b/app/javascript/mastodon/components/admin/Counter.js
index 6edb7bcfc..5a5b2b869 100644
--- a/app/javascript/mastodon/components/admin/Counter.js
+++ b/app/javascript/mastodon/components/admin/Counter.js
@@ -33,6 +33,7 @@ export default class Counter extends React.PureComponent {
     label: PropTypes.string.isRequired,
     href: PropTypes.string,
     params: PropTypes.object,
+    target: PropTypes.string,
   };
 
   state = {
@@ -54,7 +55,7 @@ export default class Counter extends React.PureComponent {
   }
 
   render () {
-    const { label, href } = this.props;
+    const { label, href, target } = this.props;
     const { loading, data } = this.state;
 
     let content;
@@ -100,7 +101,7 @@ export default class Counter extends React.PureComponent {
 
     if (href) {
       return (
-        <a href={href} className='sparkline'>
+        <a href={href} className='sparkline' target={target}>
           {inner}
         </a>
       );
diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js
index 1289d6b94..706824dc7 100644
--- a/app/javascript/mastodon/features/compose/components/upload.js
+++ b/app/javascript/mastodon/features/compose/components/upload.js
@@ -5,7 +5,6 @@ import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { FormattedMessage } from 'react-intl';
-import classNames from 'classnames';
 import Icon from 'mastodon/components/icon';
 
 export default class Upload extends ImmutablePureComponent {
@@ -43,10 +42,16 @@ export default class Upload extends ImmutablePureComponent {
         <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
           {({ scale }) => (
             <div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
-              <div className={classNames('compose-form__upload__actions', { active: true })}>
+              <div className='compose-form__upload__actions'>
                 <button className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
                 {!isEditingStatus && (<button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>)}
               </div>
+
+              {(media.get('description') || '').length === 0 && (
+                <div className='compose-form__upload__warning'>
+                  <button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button>
+                </div>
+              )}
             </div>
           )}
         </Motion>
diff --git a/app/javascript/mastodon/features/explore/results.js b/app/javascript/mastodon/features/explore/results.js
index ff900de08..339f883c5 100644
--- a/app/javascript/mastodon/features/explore/results.js
+++ b/app/javascript/mastodon/features/explore/results.js
@@ -24,15 +24,15 @@ const appendLoadMore = (id, list, onLoadMore) => {
   }
 };
 
-const renderAccounts = (results, onLoadMore) => appendLoadMore('accounts', results.get('accounts').map(item => (
+const renderAccounts = (results, onLoadMore) => appendLoadMore('accounts', results.get('accounts', ImmutableList()).map(item => (
   <Account key={`account-${item}`} id={item} />
 )), onLoadMore);
 
-const renderHashtags = (results, onLoadMore) => appendLoadMore('hashtags', results.get('hashtags').map(item => (
+const renderHashtags = (results, onLoadMore) => appendLoadMore('hashtags', results.get('hashtags', ImmutableList()).map(item => (
   <Hashtag key={`tag-${item.get('name')}`} hashtag={item} />
 )), onLoadMore);
 
-const renderStatuses = (results, onLoadMore) => appendLoadMore('statuses', results.get('statuses').map(item => (
+const renderStatuses = (results, onLoadMore) => appendLoadMore('statuses', results.get('statuses', ImmutableList()).map(item => (
   <Status key={`status-${item}`} id={item} />
 )), onLoadMore);
 
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index 287fd0771..055732cfd 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -328,7 +328,7 @@
   "notifications.column_settings.filter_bar.category": "Barra ràpida de filtres",
   "notifications.column_settings.filter_bar.show_bar": "Mostra la barra de filtres",
   "notifications.column_settings.follow": "Nous seguidors:",
-  "notifications.column_settings.follow_request": "Nova sol·licitud de seguiment:",
+  "notifications.column_settings.follow_request": "Noves sol·licituts de seguiment:",
   "notifications.column_settings.mention": "Mencions:",
   "notifications.column_settings.poll": "Resultats de l’enquesta:",
   "notifications.column_settings.push": "Notificacions push",
@@ -353,7 +353,7 @@
   "notifications.permission_denied_alert": "No es poden activar les notificacions del escriptori perquè el permís del navegador ha estat denegat abans",
   "notifications.permission_required": "Les notificacions d'escriptori no estan disponibles perquè el permís requerit no ha estat concedit.",
   "notifications_permission_banner.enable": "Activar les notificacions d’escriptori",
-  "notifications_permission_banner.how_to_control": "Per a rebre notificacions quan Mastodon no està obert cal activar les notificacions d’escriptori. Pots controlar amb precisió quins tipus d’interaccions generen notificacions d’escriptori després d’activar el botó {icon} de dalt.",
+  "notifications_permission_banner.how_to_control": "Per a rebre notificacions quan Mastodon no és obert cal activar les notificacions d’escriptori. Pots controlar amb precisió quins tipus d’interaccions generen notificacions d’escriptori després d’activar el botó {icon} de dalt.",
   "notifications_permission_banner.title": "Mai et perdis res",
   "picture_in_picture.restore": "Retorna’l",
   "poll.closed": "Finalitzada",
@@ -376,7 +376,7 @@
   "privacy.unlisted.short": "No llistat",
   "refresh": "Actualitza",
   "regeneration_indicator.label": "Carregant…",
-  "regeneration_indicator.sublabel": "S'està preparant la línia de temps Inici!",
+  "regeneration_indicator.sublabel": "S'està preparant la teva línia de temps Inici!",
   "relative_time.days": "fa {number} dies",
   "relative_time.full.days": "fa {number, plural, one {# dia} other {# dies}}",
   "relative_time.full.hours": "fa {number, plural, one {# hora} other {# hores}}",
@@ -394,7 +394,7 @@
   "report.categories.other": "Altres",
   "report.categories.spam": "Contingut brossa",
   "report.categories.violation": "El contingut viola una o més regles del servidor",
-  "report.category.subtitle": "Tria la millor combinació",
+  "report.category.subtitle": "Tria la millor coincidència",
   "report.category.title": "Digue'ns què està passant amb aquest {type}",
   "report.category.title_account": "perfil",
   "report.category.title_status": "publicació",
@@ -410,7 +410,7 @@
   "report.reasons.dislike_description": "Això no és quelcom que vulguis veure",
   "report.reasons.other": "Això és una altre cosa",
   "report.reasons.other_description": "El problema no encaixa en altres categories",
-  "report.reasons.spam": "Això és brossa",
+  "report.reasons.spam": "Això és contingut brossa",
   "report.reasons.spam_description": "Enllaços maliciosos, compromís falç o respostes repetitives",
   "report.reasons.violation": "Viola les regles del servidor",
   "report.reasons.violation_description": "Ets conscient que trenca regles especifiques",
@@ -447,7 +447,7 @@
   "status.cancel_reblog_private": "Desfer l'impuls",
   "status.cannot_reblog": "Aquesta publicació no pot ser impulsada",
   "status.copy": "Copia l'enllaç a l'estat",
-  "status.delete": "Esborrar",
+  "status.delete": "Esborra",
   "status.detailed_status": "Visualització detallada de la conversa",
   "status.direct": "Missatge directe @{name}",
   "status.edit": "Edita",
@@ -465,17 +465,17 @@
   "status.mute": "Silenciar @{name}",
   "status.mute_conversation": "Silenciar conversació",
   "status.open": "Ampliar aquest estat",
-  "status.pin": "Fixat en el perfil",
+  "status.pin": "Fixa en el perfil",
   "status.pinned": "Publicació fixada",
   "status.read_more": "Llegir més",
   "status.reblog": "Impuls",
-  "status.reblog_private": "Impulsar a l'audiència original",
+  "status.reblog_private": "Impulsar amb la visibilitat original",
   "status.reblogged_by": "{name} ha impulsat",
   "status.reblogs.empty": "Encara ningú no ha impulsat aquesta publicació. Quan algú ho faci, apareixeran aquí.",
   "status.redraft": "Esborrar i reescriure",
   "status.remove_bookmark": "Suprimeix el marcador",
   "status.reply": "Respondre",
-  "status.replyAll": "Respondre al tema",
+  "status.replyAll": "Respondre al fil",
   "status.report": "Informar sobre @{name}",
   "status.sensitive_warning": "Contingut sensible",
   "status.share": "Compartir",
@@ -510,11 +510,11 @@
   "units.short.million": "{count}M",
   "units.short.thousand": "{count}K",
   "upload_area.title": "Arrossega i deixa anar per a carregar",
-  "upload_button.label": "Afegir multimèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",
+  "upload_button.label": "Afegir mèdia, un vídeo o un fitxer d'audio",
   "upload_error.limit": "S'ha superat el límit de càrrega d'arxius.",
   "upload_error.poll": "No es permet l'enviament de fitxers en les enquestes.",
   "upload_form.audio_description": "Descriviu per a les persones amb pèrdua auditiva",
-  "upload_form.description": "Descriure els problemes visuals",
+  "upload_form.description": "Descriure per els que tenen problemes visuals",
   "upload_form.edit": "Edita",
   "upload_form.thumbnail": "Canvia la miniatura",
   "upload_form.undo": "Esborra",
@@ -523,7 +523,7 @@
   "upload_modal.apply": "Aplica",
   "upload_modal.applying": "Aplicant…",
   "upload_modal.choose_image": "Tria imatge",
-  "upload_modal.description_placeholder": "Jove xef, porti whisky amb quinze glaçons d’hidrogen, coi!",
+  "upload_modal.description_placeholder": "Una ràpida guineu marró salta sobre el gos mandrós",
   "upload_modal.detect_text": "Detecta el text de l'imatge",
   "upload_modal.edit_media": "Editar multimèdia",
   "upload_modal.hint": "Fes clic o arrossega el cercle en la previsualització per escollir el punt focal que sempre serà visible de totes les miniatures.",
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index d48780042..c6ffaa6f2 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -75,7 +75,7 @@
   "column.domain_blocks": "Blokované domény",
   "column.favourites": "Oblíbené",
   "column.follow_requests": "Žádosti o sledování",
-  "column.home": "Domů",
+  "column.home": "Domovská časová osa",
   "column.lists": "Seznamy",
   "column.mutes": "Skrytí uživatelé",
   "column.notifications": "Oznámení",
@@ -294,7 +294,7 @@
   "navigation_bar.discover": "Objevujte",
   "navigation_bar.domain_blocks": "Blokované domény",
   "navigation_bar.edit_profile": "Upravit profil",
-  "navigation_bar.explore": "Explore",
+  "navigation_bar.explore": "Objevování",
   "navigation_bar.favourites": "Oblíbené",
   "navigation_bar.filters": "Skrytá slova",
   "navigation_bar.follow_requests": "Žádosti o sledování",
@@ -318,7 +318,7 @@
   "notification.poll": "Anketa, ve které jste hlasovali, skončila",
   "notification.reblog": "Uživatel {name} boostnul váš příspěvek",
   "notification.status": "Nový příspěvek od {name}",
-  "notification.update": "{name} edited a post",
+  "notification.update": "uživatel {name} upravil příspěvek",
   "notifications.clear": "Smazat oznámení",
   "notifications.clear_confirmation": "Opravdu chcete trvale smazat všechna vaše oznámení?",
   "notifications.column_settings.admin.sign_up": "New sign-ups:",
@@ -338,7 +338,7 @@
   "notifications.column_settings.status": "Nové příspěvky:",
   "notifications.column_settings.unread_notifications.category": "Nepřečtená oznámení",
   "notifications.column_settings.unread_notifications.highlight": "Zvýraznit nepřečtená oznámení",
-  "notifications.column_settings.update": "Edits:",
+  "notifications.column_settings.update": "Úpravy:",
   "notifications.filter.all": "Vše",
   "notifications.filter.boosts": "Boosty",
   "notifications.filter.favourites": "Oblíbení",
@@ -380,7 +380,7 @@
   "relative_time.days": "{number} d",
   "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
   "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
+  "relative_time.full.just_now": "právě teď",
   "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
   "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
   "relative_time.hours": "{number} h",
@@ -391,11 +391,11 @@
   "reply_indicator.cancel": "Zrušit",
   "report.block": "Block",
   "report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.",
-  "report.categories.other": "Other",
+  "report.categories.other": "Ostatní",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
-  "report.category.subtitle": "Choose the best match",
-  "report.category.title": "Tell us what's going on with this {type}",
+  "report.categories.violation": "Obsah porušuje jedno nebo více pravidel serveru",
+  "report.category.subtitle": "Vyberte nejbližší možnost",
+  "report.category.title": "Povězte nám, proč chcete {type} nahlásit",
   "report.category.title_account": "profile",
   "report.category.title_status": "post",
   "report.close": "Done",
@@ -404,20 +404,20 @@
   "report.forward_hint": "Tento účet je z jiného serveru. Chcete na něj také poslat anonymizovanou kopii hlášení?",
   "report.mute": "Mute",
   "report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
-  "report.next": "Next",
+  "report.next": "Dále",
   "report.placeholder": "Dodatečné komentáře",
-  "report.reasons.dislike": "I don't like it",
-  "report.reasons.dislike_description": "It is not something you want to see",
-  "report.reasons.other": "It's something else",
-  "report.reasons.other_description": "The issue does not fit into other categories",
-  "report.reasons.spam": "It's spam",
-  "report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies",
-  "report.reasons.violation": "It violates server rules",
-  "report.reasons.violation_description": "You are aware that it breaks specific rules",
-  "report.rules.subtitle": "Select all that apply",
-  "report.rules.title": "Which rules are being violated?",
-  "report.statuses.subtitle": "Select all that apply",
-  "report.statuses.title": "Are there any posts that back up this report?",
+  "report.reasons.dislike": "Nelíbí se mi",
+  "report.reasons.dislike_description": "Není to něco, co chcete vidět",
+  "report.reasons.other": "Jde o něco jiného",
+  "report.reasons.other_description": "Problém neodpovídá ostatním kategoriím",
+  "report.reasons.spam": "Je to spam",
+  "report.reasons.spam_description": "Škodlivé odkazy, falešné interakce nebo opakované odpovědi",
+  "report.reasons.violation": "Porušuje pravidla serveru",
+  "report.reasons.violation_description": "Máte za to, že porušuje konkrétní pravidla",
+  "report.rules.subtitle": "Vyberte všechna relevantní",
+  "report.rules.title": "Která pravidla porušuje?",
+  "report.statuses.subtitle": "Vyberte všechny relevantní",
+  "report.statuses.title": "Existují příspěvky dokládající toto hlášení?",
   "report.submit": "Odeslat",
   "report.target": "Nahlášení uživatele {target}",
   "report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:",
@@ -490,7 +490,7 @@
   "suggestions.dismiss": "Odmítnout návrh",
   "suggestions.header": "Mohlo by vás zajímat…",
   "tabs_bar.federated_timeline": "Federovaná",
-  "tabs_bar.home": "Domů",
+  "tabs_bar.home": "Domovská",
   "tabs_bar.local_timeline": "Místní",
   "tabs_bar.notifications": "Oznámení",
   "tabs_bar.search": "Hledat",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index 659336393..73a65ed78 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -3,53 +3,53 @@
   "account.add_or_remove_from_list": "افزودن یا برداشتن از سیاهه‌ها",
   "account.badges.bot": "روبات",
   "account.badges.group": "گروه",
-  "account.block": "مسدود کردن @{name}",
+  "account.block": "مسدود کردن ‎@{name}",
   "account.block_domain": "مسدود کردن دامنهٔ {domain}",
   "account.blocked": "مسدود",
   "account.browse_more_on_origin_server": "مرور بیش‌تر روی نمایهٔ اصلی",
   "account.cancel_follow_request": "لغو درخواست پی‌گیری",
-  "account.direct": "پیام مستقیم به @{name}",
-  "account.disable_notifications": "آگاهی به من هنگام فرستادن‌های @{name} پایان یابد",
+  "account.direct": "پیام مستقیم به ‎@{name}",
+  "account.disable_notifications": "آگاه کردن من هنگام فرسته‌های ‎@{name} را متوقّف کن",
   "account.domain_blocked": "دامنه مسدود شد",
   "account.edit_profile": "ویرایش نمایه",
-  "account.enable_notifications": "هنگام فرسته‌های @{name} مرا آگاه کن",
+  "account.enable_notifications": "هنگام فرسته‌های ‎@{name} مرا آگاه کن",
   "account.endorse": "معرّفی در نمایه",
   "account.follow": "پی‌گیری",
   "account.followers": "پی‌گیرندگان",
   "account.followers.empty": "هنوز کسی این کاربر را پی‌گیری نمی‌کند.",
   "account.followers_counter": "{count, plural, one {{counter} پی‌گیرنده} other {{counter} پی‌گیرنده}}",
-  "account.following": "Following",
+  "account.following": "پی می‌گیرید",
   "account.following_counter": "{count, plural, one {{counter} پی‌گرفته} other {{counter} پی‌گرفته}}",
   "account.follows.empty": "این کاربر هنوز پی‌گیر کسی نیست.",
   "account.follows_you": "پی می‌گیردتان",
-  "account.hide_reblogs": "نهفتن تقویت‌های @{name}",
+  "account.hide_reblogs": "نهفتن تقویت‌های ‎@{name}",
   "account.joined": "پیوسته از {date}",
   "account.link_verified_on": "مالکیت این پیوند در {date} بررسی شد",
   "account.locked_info": "این حساب خصوصی است. صاحبش تصمیم می‌گیرد که چه کسی پی‌گیرش باشد.",
   "account.media": "رسانه",
-  "account.mention": "نام‌بردن از @{name}",
+  "account.mention": "نام‌بردن از ‎@{name}",
   "account.moved_to": "{name} منتقل شده به:",
-  "account.mute": "خموشاندن @{name}",
-  "account.mute_notifications": "خموشاندن آگاهی‌ها از @{name}",
+  "account.mute": "خموشاندن ‎@{name}",
+  "account.mute_notifications": "خموشاندن آگاهی‌های ‎@{name}",
   "account.muted": "خموش",
   "account.posts": "فرسته",
   "account.posts_with_replies": "فرسته‌ها و پاسخ‌ها",
-  "account.report": "گزارش @{name}",
+  "account.report": "گزارش ‎@{name}",
   "account.requested": "منتظر پذیرش است. برای لغو درخواست پی‌گیری کلیک کنید",
-  "account.share": "هم‌رسانی نمایهٔ @{name}",
-  "account.show_reblogs": "نمایش تقویت‌های @{name}",
+  "account.share": "هم‌رسانی نمایهٔ ‎@{name}",
+  "account.show_reblogs": "نمایش تقویت‌های ‎@{name}",
   "account.statuses_counter": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}",
-  "account.unblock": "رفع مسدودیت @{name}",
+  "account.unblock": "رفع مسدودیت ‎@{name}",
   "account.unblock_domain": "رفع مسدودیت دامنهٔ {domain}",
-  "account.unblock_short": "Unblock",
+  "account.unblock_short": "رفع مسدودیت",
   "account.unendorse": "معرّفی نکردن در نمایه",
   "account.unfollow": "ناپی‌گیری",
-  "account.unmute": "ناخموشی @{name}",
-  "account.unmute_notifications": "ناخموشی آگاهی‌ها از @{name}",
-  "account.unmute_short": "Unmute",
+  "account.unmute": "ناخموشی ‎@{name}",
+  "account.unmute_notifications": "ناخموشی آگاهی‌های ‎@{name}",
+  "account.unmute_short": "ناخموشی",
   "account_note.placeholder": "برای افزودن یادداشت کلیک کنید",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
-  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.daily_retention": "نرخ حفظ کاربر در روز پس از ثبت نام",
+  "admin.dashboard.monthly_retention": "نرخ حفظ کاربر در ماه پس از ثبت نام",
   "admin.dashboard.retention.average": "میانگین",
   "admin.dashboard.retention.cohort": "ماه ثبت‌نام",
   "admin.dashboard.retention.cohort_size": "کاربران جدید",
@@ -79,13 +79,13 @@
   "column.lists": "سیاهه‌ها",
   "column.mutes": "کاربران خموش",
   "column.notifications": "آگاهی‌ها",
-  "column.pins": "فرسته‌های سنجاق‌شده",
+  "column.pins": "فرسته‌های سنجاق شده",
   "column.public": "خط زمانی همگانی",
   "column_back_button.label": "بازگشت",
   "column_header.hide_settings": "نهفتن تنظیمات",
   "column_header.moveLeft_settings": "جابه‌جایی ستون به چپ",
   "column_header.moveRight_settings": "جابه‌جایی ستون به راست",
-  "column_header.pin": "سنجاق‌کردن",
+  "column_header.pin": "سنجاق کردن",
   "column_header.show_settings": "نمایش تنظیمات",
   "column_header.unpin": "برداشتن سنجاق",
   "column_subheading.settings": "تنظیمات",
@@ -94,7 +94,7 @@
   "community.column_settings.remote_only": "تنها دوردست",
   "compose_form.direct_message_warning": "این فرسته تنها به کاربرانی که از آن‌ها نام برده شده فرستاده خواهد شد.",
   "compose_form.direct_message_warning_learn_more": "بیشتر بدانید",
-  "compose_form.hashtag_warning": "از آن‌جا که این فرسته فهرست‌نشده است، در نتایج جست‌وجوی برچسب‌ها پیدا نخواهد شد. تنها فرسته‌های عمومی را می‌توان با جست‌وجوی برچسب یافت.",
+  "compose_form.hashtag_warning": "از آن‌جا که این فرسته فهرست نشده است، در نتایج جست‌وجوی هشتگ‌ها پیدا نخواهد شد. تنها فرسته‌های عمومی را می‌توان با جست‌وجوی هشتگ یافت.",
   "compose_form.lock_disclaimer": "حسابتان {locked} نیست. هر کسی می‌تواند پی‌گیرتان شده و فرسته‌های ویژهٔ پی‌گیرانتان را ببیند.",
   "compose_form.lock_disclaimer.lock": "قفل‌شده",
   "compose_form.placeholder": "تازه چه خبر؟",
@@ -106,7 +106,7 @@
   "compose_form.poll.switch_to_single": "تبدیل به نظرسنجی تک‌گزینه‌ای",
   "compose_form.publish": "بوق",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "ذخیرهٔ تغییرات",
   "compose_form.sensitive.hide": "{count, plural, one {علامت‌گذاری رسانه به عنوان حساس} other {علامت‌گذاری رسانه‌ها به عنوان حساس}}",
   "compose_form.sensitive.marked": "{count, plural, one {رسانه به عنوان حساس علامت‌گذاری شد} other {رسانه‌ها به عنوان حساس علامت‌گذاری شدند}}",
   "compose_form.sensitive.unmarked": "{count, plural, one {رسانه به عنوان حساس علامت‌گذاری نشد} other {رسانه‌ها به عنوان حساس علامت‌گذاری نشدند}}",
@@ -144,7 +144,7 @@
   "directory.local": "تنها از {domain}",
   "directory.new_arrivals": "تازه‌واردان",
   "directory.recently_active": "کاربران فعال اخیر",
-  "embed.instructions": "برای جاگذاری این فرسته در سایت خودتان، کد زیر را کپی کنید.",
+  "embed.instructions": "برای جاسازی این فرسته در سایت خودتان، کد زیر را رونوشت کنید.",
   "embed.preview": "این گونه دیده خواهد شد:",
   "emoji_button.activity": "فعالیت",
   "emoji_button.custom": "سفارشی",
@@ -164,11 +164,11 @@
   "empty_column.account_timeline": "هیچ فرسته‌ای این‌جا نیست!",
   "empty_column.account_unavailable": "نمایهٔ موجود نیست",
   "empty_column.blocks": "هنوز کسی را مسدود نکرده‌اید.",
-  "empty_column.bookmarked_statuses": "هنوز هیچ فرستهٔ نشان‌شده‌ای ندارید. هنگامی که فرسته‌ای را نشان‌کنید، این‌جا نشان داده خواهد شد.",
+  "empty_column.bookmarked_statuses": "هنوز هیچ فرستهٔ نشانه‌گذاری شده‌ای ندارید. هنگامی که فرسته‌ای را نشانه‌گذاری کنید، این‌جا نشان داده خواهد شد.",
   "empty_column.community": "خط زمانی محلّی خالی است. چیزی بنویسید تا چرخش بچرخد!",
   "empty_column.direct": "هنوز هیچ پیام مستقیمی ندارید. هنگامی که چنین پیامی بگیرید یا بفرستید این‌جا نشان داده خواهد شد.",
   "empty_column.domain_blocks": "هنوز هیچ دامنه‌ای مسدود نشده است.",
-  "empty_column.explore_statuses": "Nothing is trending right now. Check back later!",
+  "empty_column.explore_statuses": "الآن چیزی پرطرفدار نیست. بعداً دوباره بررسی کنید!",
   "empty_column.favourited_statuses": "شما هنوز هیچ فرسته‌ای را نپسندیده‌اید. هنگامی که فرسته‌ای را بپسندید، این‌جا نشان داده خواهد شد.",
   "empty_column.favourites": "هنوز هیچ کسی این فرسته را نپسندیده است. هنگامی که کسی آن را بپسندد، این‌جا نشان داده خواهد شد.",
   "empty_column.follow_recommendations": "ظاهرا هیچ پیشنهادی برای شما نمی‌توانیم تولید کنیم. می‌توانید از امکان جست‌وجو برای یافتن افرادی که ممکن است بشناسید و یا کاوش میان برچسب‌های داغ استفاده کنید.",
@@ -187,12 +187,12 @@
   "error.unexpected_crash.next_steps_addons": "لطفاً از کارشان انداخته و صفحه را نوسازی کنید. اگر کمکی نکرد، شاید همچنان بتوانید با مرورگری دیگر یا با کاره‌ای بومی از ماستودون استفاده کنید.",
   "errors.unexpected_crash.copy_stacktrace": "رونوشت از جزئیات اشکال",
   "errors.unexpected_crash.report_issue": "گزارش مشکل",
-  "explore.search_results": "Search results",
-  "explore.suggested_follows": "For you",
-  "explore.title": "Explore",
-  "explore.trending_links": "News",
-  "explore.trending_statuses": "Posts",
-  "explore.trending_tags": "Hashtags",
+  "explore.search_results": "نتایج جست‌وجو",
+  "explore.suggested_follows": "برای شما",
+  "explore.title": "کاوش",
+  "explore.trending_links": "اخبار",
+  "explore.trending_statuses": "فرسته‌ها",
+  "explore.trending_tags": "هشتگ‌ها",
   "follow_recommendations.done": "انجام شد",
   "follow_recommendations.heading": "افرادی را که می‌خواهید فرسته‌هایشان را ببینید پی‌گیری کنید! این‌ها تعدادی پیشنهاد هستند.",
   "follow_recommendations.lead": "فرسته‌های افرادی که دنبال می‌کنید به ترتیب زمانی در خوراک خانه‌تان نشان داده خواهد شد. از اشتباه کردن نترسید. می‌توانید به همین سادگی در هر زمانی از دنبال کردن افراد دست بکشید!",
@@ -247,7 +247,7 @@
   "keyboard_shortcuts.my_profile": "گشودن نمایه‌تان",
   "keyboard_shortcuts.notifications": "گشودن ستون آگاهی‌ها",
   "keyboard_shortcuts.open_media": "گشودن رسانه",
-  "keyboard_shortcuts.pinned": "گشودن سیاههٔ فرسته‌های سنجاق شده",
+  "keyboard_shortcuts.pinned": "گشودن فهرست فرسته‌های سنجاق شده",
   "keyboard_shortcuts.profile": "گشودن نمایهٔ نویسنده",
   "keyboard_shortcuts.reply": "پاسخ به فرسته",
   "keyboard_shortcuts.requests": "گشودن سیاههٔ درخواست‌های پی‌گیری",
@@ -294,7 +294,7 @@
   "navigation_bar.discover": "گشت و گذار",
   "navigation_bar.domain_blocks": "دامنه‌های مسدود شده",
   "navigation_bar.edit_profile": "ویرایش نمایه",
-  "navigation_bar.explore": "Explore",
+  "navigation_bar.explore": "کاوش",
   "navigation_bar.favourites": "پسندیده‌ها",
   "navigation_bar.filters": "واژه‌های خموش",
   "navigation_bar.follow_requests": "درخواست‌های پی‌گیری",
@@ -305,11 +305,11 @@
   "navigation_bar.logout": "خروج",
   "navigation_bar.mutes": "کاربران خموشانده",
   "navigation_bar.personal": "شخصی",
-  "navigation_bar.pins": "فرسته‌های سنجاق‌شده",
+  "navigation_bar.pins": "فرسته‌های سنجاق شده",
   "navigation_bar.preferences": "ترجیحات",
   "navigation_bar.public_timeline": "خط زمانی همگانی",
   "navigation_bar.security": "امنیت",
-  "notification.admin.sign_up": "{name} signed up",
+  "notification.admin.sign_up": "{name} ثبت نام کرد",
   "notification.favourite": "‫{name}‬ فرسته‌تان را پسندید",
   "notification.follow": "‫{name}‬ پی‌گیرتان شد",
   "notification.follow_request": "{name} می‌خواهد پی‌گیر شما باشد",
@@ -318,10 +318,10 @@
   "notification.poll": "نظرسنجی‌ای که در آن رأی دادید به پایان رسیده است",
   "notification.reblog": "‫{name}‬ فرسته‌تان را تقویت کرد",
   "notification.status": "{name} چیزی فرستاد",
-  "notification.update": "{name} edited a post",
+  "notification.update": "{name} فرسته‌ای را ویرایش کرد",
   "notifications.clear": "پاک‌سازی آگاهی‌ها",
   "notifications.clear_confirmation": "مطمئنید می‌خواهید همهٔ آگاهی‌هایتان را برای همیشه پاک کنید؟",
-  "notifications.column_settings.admin.sign_up": "New sign-ups:",
+  "notifications.column_settings.admin.sign_up": "ثبت نام‌های جدید:",
   "notifications.column_settings.alert": "آگاهی‌های میزکار",
   "notifications.column_settings.favourite": "پسندیده‌ها:",
   "notifications.column_settings.filter_bar.advanced": "نمایش همۀ دسته‌ها",
@@ -338,7 +338,7 @@
   "notifications.column_settings.status": "فرسته‌های جدید:",
   "notifications.column_settings.unread_notifications.category": "آگاهی‌های خوانده نشده",
   "notifications.column_settings.unread_notifications.highlight": "پررنگ کردن آگاهی‌های خوانده نشده",
-  "notifications.column_settings.update": "Edits:",
+  "notifications.column_settings.update": "ویرایش‌ها:",
   "notifications.filter.all": "همه",
   "notifications.filter.boosts": "تقویت‌ها",
   "notifications.filter.favourites": "پسندها",
@@ -378,54 +378,54 @@
   "regeneration_indicator.label": "در حال بار شدن…",
   "regeneration_indicator.sublabel": "خوراک خانگیان دارد آماده می‌شود!",
   "relative_time.days": "{number} روز",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# روز} other {# روز}} پیش",
+  "relative_time.full.hours": "{number, plural, one {# ساعت} other {# ساعت}} پیش",
+  "relative_time.full.just_now": "همين آلان",
+  "relative_time.full.minutes": "{number, plural, one {# دقیقه} other {# دقیقه}} پیش",
+  "relative_time.full.seconds": "{number, plural, one {# ثانیه} other {# ثانیه}} پیش",
   "relative_time.hours": "{number} ساعت",
   "relative_time.just_now": "حالا",
   "relative_time.minutes": "{number} دقیقه",
   "relative_time.seconds": "{number} ثانیه",
   "relative_time.today": "امروز",
   "reply_indicator.cancel": "لغو",
-  "report.block": "Block",
-  "report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.",
-  "report.categories.other": "Other",
-  "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
-  "report.category.subtitle": "Choose the best match",
-  "report.category.title": "Tell us what's going on with this {type}",
-  "report.category.title_account": "profile",
-  "report.category.title_status": "post",
-  "report.close": "Done",
-  "report.comment.title": "Is there anything else you think we should know?",
+  "report.block": "مسدود کردن",
+  "report.block_explanation": "شما فرسته‌هایشان را نخواهید دید. آن‌ها نمی‌توانند فرسته‌هایتان را ببینند یا شما را پی‌بگیرند. آنها می‌توانند بگویند که مسدود شده‌اند.",
+  "report.categories.other": "غیره",
+  "report.categories.spam": "هرزنامه",
+  "report.categories.violation": "محتوا یک یا چند قانون کارساز را نقض می‌کند",
+  "report.category.subtitle": "منطبق‌ترین را انتخاب کنید",
+  "report.category.title": "به ما بگویید با این {type} چه مشکلی دارید",
+  "report.category.title_account": "نمایه",
+  "report.category.title_status": "فرسته",
+  "report.close": "انجام شد",
+  "report.comment.title": "آیا چیز دیگری هست که فکر می‌کنید باید بدانیم؟",
   "report.forward": "فرستادن به {target}",
   "report.forward_hint": "این حساب در کارساز دیگری ثبت شده. آیا می‌خواهید رونوشتی ناشناس از این گزارش به آن‌جا هم فرستاده شود؟",
-  "report.mute": "Mute",
-  "report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
-  "report.next": "Next",
+  "report.mute": "خموش",
+  "report.mute_explanation": "شما فرسته‌های آن‌ها را نخواهید دید. آن‌ها همچنان می‌توانند شما را پی‌بگیرند و فرسته‌هایتان را ببینند و نمی‌دانند که خموش شده‌اند.",
+  "report.next": "بعدی",
   "report.placeholder": "توضیحات اضافه",
-  "report.reasons.dislike": "I don't like it",
-  "report.reasons.dislike_description": "It is not something you want to see",
-  "report.reasons.other": "It's something else",
-  "report.reasons.other_description": "The issue does not fit into other categories",
-  "report.reasons.spam": "It's spam",
-  "report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies",
-  "report.reasons.violation": "It violates server rules",
-  "report.reasons.violation_description": "You are aware that it breaks specific rules",
-  "report.rules.subtitle": "Select all that apply",
-  "report.rules.title": "Which rules are being violated?",
-  "report.statuses.subtitle": "Select all that apply",
-  "report.statuses.title": "Are there any posts that back up this report?",
+  "report.reasons.dislike": "من آن را دوست ندارم",
+  "report.reasons.dislike_description": "این چیزی نیست که بخواهید ببینید",
+  "report.reasons.other": "بخواطر چیز دیگری است",
+  "report.reasons.other_description": "این موضوع در دسته‌بندی‌های دیگر نمی‌گنجد",
+  "report.reasons.spam": "این هرزنامه است",
+  "report.reasons.spam_description": "پیوندهای مخرب، تعامل جعلی یا پاسخ‌های تکراری",
+  "report.reasons.violation": "قوانین کارساز را نقض می‌کند",
+  "report.reasons.violation_description": "شما آگاه هستید که قوانین خاصی را زیر پا می‌گذارد",
+  "report.rules.subtitle": "همهٔ موارد انجام شده را برگزینید",
+  "report.rules.title": "کدام قوانین نقض شده‌اند؟",
+  "report.statuses.subtitle": "همهٔ موارد انجام شده را برگزینید",
+  "report.statuses.title": "آیا فرسته‌ای وجود دارد که از این گزارش پشتیبانی کند؟",
   "report.submit": "فرستادن",
   "report.target": "در حال گزارش {target}",
-  "report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:",
-  "report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:",
-  "report.thanks.title": "Don't want to see this?",
-  "report.thanks.title_actionable": "Thanks for reporting, we'll look into this.",
-  "report.unfollow": "Unfollow @{name}",
-  "report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
+  "report.thanks.take_action": "در اینجا گزینه‌هایی برای کنترل آنچه در ماستودون میبینید، وجود دارد:",
+  "report.thanks.take_action_actionable": "در حالی که ما این مورد را بررسی می‌کنیم، می‌توانید علیه ‎@{name} اقدام کنید:",
+  "report.thanks.title": "نمی‌خواهید این را ببینید؟",
+  "report.thanks.title_actionable": "ممنون بابت گزارش، ما آن را بررسی خواهیم کرد.",
+  "report.unfollow": "ناپی‌گیری ‎@{name}",
+  "report.unfollow_explanation": "شما این حساب را پی‌گرفته‌اید، برای اینکه دیگر فرسته‌هایش را در خوراک خانه‌تان نبینید؛ آن را پی‌نگیرید.",
   "search.placeholder": "جست‌وجو",
   "search_popout.search_format": "راهنمای جست‌وجوی پیشرفته",
   "search_popout.tips.full_text": "جست‌وجوی متنی ساده فرسته‌هایی که نوشته، پسندیده، تقویت‌کرده یا در آن‌ها نام‌برده شده‌اید را به علاوهٔ نام‌های کاربری، نام‌های نمایشی و برچسب‌ها برمی‌گرداند.",
@@ -434,39 +434,39 @@
   "search_popout.tips.text": "جست‌وجوی متنی ساده برای نام‌ها، نام‌های کاربری، و برچسب‌ها",
   "search_popout.tips.user": "کاربر",
   "search_results.accounts": "افراد",
-  "search_results.all": "All",
+  "search_results.all": "همه",
   "search_results.hashtags": "برچسب‌ها",
-  "search_results.nothing_found": "Could not find anything for these search terms",
+  "search_results.nothing_found": "چیزی برای این عبارت جست‌وجو یافت نشد",
   "search_results.statuses": "فرسته‌ها",
-  "search_results.statuses_fts_disabled": "جست‌وجوی محتوای فرسته‌ها در این کارساز ماستودون فعال نشده است.",
+  "search_results.statuses_fts_disabled": "جست‌وجوی محتوای فرسته‌ها در این کارساز ماستودون به کار انداخته نشده است.",
   "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
-  "status.admin_account": "گشودن واسط مدیریت برای @{name}",
+  "status.admin_account": "گشودن واسط مدیریت برای ‎@{name}",
   "status.admin_status": "گشودن این فرسته در واسط مدیریت",
-  "status.block": "مسدود کردن @{name}",
+  "status.block": "مسدود کردن ‎@{name}",
   "status.bookmark": "نشانک",
-  "status.cancel_reblog_private": "لغو تقویت",
+  "status.cancel_reblog_private": "ناتقویت",
   "status.cannot_reblog": "این فرسته قابل تقویت نیست",
-  "status.copy": "رونویسی از نشانی فرسته",
+  "status.copy": "رونوشت پیوند فرسته",
   "status.delete": "حذف",
   "status.detailed_status": "نمایش کامل گفتگو",
-  "status.direct": "پیام مستقیم به @{name}",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
-  "status.embed": "جاگذاری",
+  "status.direct": "پیام مستقیم به ‎@{name}",
+  "status.edit": "ویرایش",
+  "status.edited": "ویرایش شده در {date}",
+  "status.edited_x_times": "{count, plural, one {{count} مرتبه} other {{count} مرتبه}} ویرایش شد",
+  "status.embed": "جاسازی",
   "status.favourite": "پسندیدن",
   "status.filtered": "پالوده",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "توسط {name} در {date} ایجاد شد",
+  "status.history.edited": "توسط {name} در {date} ویرایش شد",
   "status.load_more": "بار کردن بیش‌تر",
   "status.media_hidden": "رسانهٔ نهفته",
-  "status.mention": "نام‌بردن از @{name}",
+  "status.mention": "نام‌بردن از ‎@{name}",
   "status.more": "بیشتر",
-  "status.mute": "خموشاندن @{name}",
+  "status.mute": "خموشاندن ‎@{name}",
   "status.mute_conversation": "خموشاندن گفت‌وگو",
   "status.open": "گسترش این فرسته",
-  "status.pin": "سنجاق‌کردن در نمایه",
-  "status.pinned": "فرستهٔ سنجاق‌شده",
+  "status.pin": "سنجاق کردن در نمایه",
+  "status.pinned": "فرستهٔ سنجاق شده",
   "status.read_more": "بیشتر بخوانید",
   "status.reblog": "تقویت",
   "status.reblog_private": "تقویت برای مخاطبان نخستین",
@@ -476,7 +476,7 @@
   "status.remove_bookmark": "برداشتن نشانک",
   "status.reply": "پاسخ",
   "status.replyAll": "پاسخ به رشته",
-  "status.report": "گزارش @{name}",
+  "status.report": "گزارش ‎@{name}",
   "status.sensitive_warning": "محتوای حساس",
   "status.share": "هم‌رسانی",
   "status.show_less": "نمایش کمتر",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index 791b802a1..caef22ee9 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -229,7 +229,7 @@
   "keyboard_shortcuts.blocked": "Ouvrir la liste des comptes bloqués",
   "keyboard_shortcuts.boost": "Partager le message",
   "keyboard_shortcuts.column": "Se placer dans une colonne",
-  "keyboard_shortcuts.compose": "se placer dans la zone de rédaction",
+  "keyboard_shortcuts.compose": "Se placer dans la zone de rédaction",
   "keyboard_shortcuts.description": "Description",
   "keyboard_shortcuts.direct": "Ouvrir la colonne des messages directs",
   "keyboard_shortcuts.down": "Descendre dans la liste",
@@ -246,7 +246,7 @@
   "keyboard_shortcuts.muted": "Ouvrir la liste des comptes masqués",
   "keyboard_shortcuts.my_profile": "Ouvrir votre profil",
   "keyboard_shortcuts.notifications": "Ouvrir la colonne de notifications",
-  "keyboard_shortcuts.open_media": "ouvrir le média",
+  "keyboard_shortcuts.open_media": "Ouvrir le média",
   "keyboard_shortcuts.pinned": "Ouvrir la liste des messages épinglés",
   "keyboard_shortcuts.profile": "Ouvrir le profil de l’auteur·rice",
   "keyboard_shortcuts.reply": "Répondre au message",
diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json
index aae73a23e..9ca41b2f6 100644
--- a/app/javascript/mastodon/locales/gd.json
+++ b/app/javascript/mastodon/locales/gd.json
@@ -18,7 +18,7 @@
   "account.followers": "Luchd-leantainn",
   "account.followers.empty": "Chan eil neach sam bith a’ leantainn air a’ chleachdaiche seo fhathast.",
   "account.followers_counter": "{count, plural, one {{counter} neach-leantainn} two {{counter} neach-leantainn} few {{counter} luchd-leantainn} other {{counter} luchd-leantainn}}",
-  "account.following": "Following",
+  "account.following": "A’ leantainn",
   "account.following_counter": "{count, plural, one {A’ leantainn air {counter}} two {A’ leantainn air {counter}} few {A’ leantainn air {counter}} other {A’ leantainn air {counter}}}",
   "account.follows.empty": "Chan eil an cleachdaiche seo a’ leantainn air neach sam bith fhathast.",
   "account.follows_you": "’Gad leantainn",
@@ -41,12 +41,12 @@
   "account.statuses_counter": "{count, plural, one {{counter} phost} two {{counter} phost} few {{counter} postaichean} other {{counter} post}}",
   "account.unblock": "Dì-bhac @{name}",
   "account.unblock_domain": "Dì-bhac an àrainn {domain}",
-  "account.unblock_short": "Unblock",
+  "account.unblock_short": "Dì-bhac",
   "account.unendorse": "Na brosnaich air a’ phròifil",
   "account.unfollow": "Na lean tuilleadh",
   "account.unmute": "Dì-mhùch @{name}",
   "account.unmute_notifications": "Dì-mhùch na brathan o @{name}",
-  "account.unmute_short": "Unmute",
+  "account.unmute_short": "Dì-mhùch",
   "account_note.placeholder": "Briog airson nòta a chur ris",
   "admin.dashboard.daily_retention": "Reat glèidheadh nan cleachdaichean às dèidh an clàradh a-rèir latha",
   "admin.dashboard.monthly_retention": "Reat glèidheadh nan cleachdaichean às dèidh an clàradh a-rèir mìos",
@@ -294,7 +294,7 @@
   "navigation_bar.discover": "Fidir",
   "navigation_bar.domain_blocks": "Àrainnean bacte",
   "navigation_bar.edit_profile": "Deasaich a’ phròifil",
-  "navigation_bar.explore": "Explore",
+  "navigation_bar.explore": "Rùraich",
   "navigation_bar.favourites": "Na h-annsachdan",
   "navigation_bar.filters": "Faclan mùchte",
   "navigation_bar.follow_requests": "Iarrtasan leantainn",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 3fd164f85..8f6fe61d4 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -18,7 +18,7 @@
   "account.followers": "フォロワー",
   "account.followers.empty": "まだ誰もフォローしていません。",
   "account.followers_counter": "{counter} フォロワー",
-  "account.following": "Following",
+  "account.following": "フォロー中",
   "account.following_counter": "{counter} フォロー",
   "account.follows.empty": "まだ誰もフォローしていません。",
   "account.follows_you": "フォローされています",
@@ -41,12 +41,12 @@
   "account.statuses_counter": "{counter} 投稿",
   "account.unblock": "@{name}さんのブロックを解除",
   "account.unblock_domain": "{domain}のブロックを解除",
-  "account.unblock_short": "Unblock",
+  "account.unblock_short": "ブロック解除",
   "account.unendorse": "プロフィールから外す",
   "account.unfollow": "フォロー解除",
   "account.unmute": "@{name}さんのミュートを解除",
   "account.unmute_notifications": "@{name}さんからの通知を受け取るようにする",
-  "account.unmute_short": "Unmute",
+  "account.unmute_short": "ミュート解除",
   "account_note.placeholder": "クリックしてメモを追加",
   "admin.dashboard.daily_retention": "サインアップ後の日ごとのユーザー継続率",
   "admin.dashboard.monthly_retention": "サインアップ後の月ごとのユーザー継続率",
@@ -192,7 +192,7 @@
   "errors.unexpected_crash.copy_stacktrace": "スタックトレースをクリップボードにコピー",
   "errors.unexpected_crash.report_issue": "問題を報告",
   "explore.search_results": "検索結果",
-  "explore.suggested_follows": "For you",
+  "explore.suggested_follows": "おすすめ",
   "explore.title": "エクスプローラー",
   "explore.trending_links": "ニュース",
   "explore.trending_statuses": "投稿",
@@ -326,7 +326,7 @@
   "notification.update": "{name} が投稿を編集しました",
   "notifications.clear": "通知を消去",
   "notifications.clear_confirmation": "本当に通知を消去しますか?",
-  "notifications.column_settings.admin.sign_up": "New sign-ups:",
+  "notifications.column_settings.admin.sign_up": "新規登録:",
   "notifications.column_settings.alert": "デスクトップ通知",
   "notifications.column_settings.favourite": "お気に入り:",
   "notifications.column_settings.filter_bar.advanced": "すべてのカテゴリを表示",
@@ -395,7 +395,7 @@
   "relative_time.today": "今日",
   "reply_indicator.cancel": "キャンセル",
   "report.block": "ブロック",
-  "report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.",
+  "report.block_explanation": "相手の投稿が表示されなくなります。相手はあなたの投稿を見ることやフォローすることができません。相手はブロックされていることがわかります。",
   "report.categories.other": "その他",
   "report.categories.spam": "スパム",
   "report.categories.violation": "サーバーのルールに違反",
@@ -404,11 +404,11 @@
   "report.category.title_account": "プロフィール",
   "report.category.title_status": "投稿",
   "report.close": "完了",
-  "report.comment.title": "Is there anything else you think we should know?",
+  "report.comment.title": "その他に私たちに伝えておくべき事はありますか?",
   "report.forward": "{target} に転送する",
   "report.forward_hint": "このアカウントは別のサーバーに所属しています。通報内容を匿名で転送しますか?",
   "report.mute": "ミュート",
-  "report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
+  "report.mute_explanation": "相手の投稿は表示されなくなります。相手は引き続きあなたをフォローして、あなたの投稿を表示することができますが、ミュートされていることはわかりません。",
   "report.next": "次へ",
   "report.placeholder": "追加コメント",
   "report.reasons.dislike": "興味がありません",
@@ -426,9 +426,9 @@
   "report.submit": "通報する",
   "report.target": "{target}さんを通報する",
   "report.thanks.take_action": "次のような方法はいかがでしょうか?",
-  "report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:",
+  "report.thanks.take_action_actionable": "私達が確認している間でも、あなたは @{name} さんに対して対応することが出来ます:",
   "report.thanks.title": "見えないようにしたいですか?",
-  "report.thanks.title_actionable": "Thanks for reporting, we'll look into this.",
+  "report.thanks.title_actionable": "ご報告ありがとうございます、追って確認します。",
   "report.unfollow": "@{name}のフォローを解除",
   "report.unfollow_explanation": "このアカウントをフォローしています。ホームフィードに彼らの投稿を表示しないようにするには、彼らのフォローを外してください。",
   "search.placeholder": "検索",
@@ -441,7 +441,7 @@
   "search_results.accounts": "人々",
   "search_results.all": "すべて",
   "search_results.hashtags": "ハッシュタグ",
-  "search_results.nothing_found": "Could not find anything for these search terms",
+  "search_results.nothing_found": "この検索条件では何も見つかりませんでした",
   "search_results.statuses": "投稿",
   "search_results.statuses_fts_disabled": "このサーバーでは投稿本文の検索は利用できません。",
   "search_results.total": "{count, number}件の結果",
diff --git a/app/javascript/mastodon/locales/ku.json b/app/javascript/mastodon/locales/ku.json
index 0298c51ec..2bf8fc520 100644
--- a/app/javascript/mastodon/locales/ku.json
+++ b/app/javascript/mastodon/locales/ku.json
@@ -342,7 +342,7 @@
   "notifications.filter.all": "Hemû",
   "notifications.filter.boosts": "Bilindkirî",
   "notifications.filter.favourites": "Bijarte",
-  "notifications.filter.follows": "Şopîner",
+  "notifications.filter.follows": "Dişopîne",
   "notifications.filter.mentions": "Qalkirin",
   "notifications.filter.polls": "Encamên rapirsiyê",
   "notifications.filter.statuses": "Ji kesên tu dişopînî re rojanekirin",
@@ -501,7 +501,7 @@
   "time_remaining.seconds": "{number, plural, one {# çirke} other {# çirke}} maye",
   "timeline_hint.remote_resource_not_displayed": "{resource} Ji rajekerên din nayê dîtin.",
   "timeline_hint.resources.followers": "Şopîner",
-  "timeline_hint.resources.follows": "Şopîner",
+  "timeline_hint.resources.follows": "Dişopîne",
   "timeline_hint.resources.statuses": "Şandiyên kevn",
   "trends.counter_by_accounts": "{count, plural, one {{counter} kes} other {{counter} kes}} diaxivin",
   "trends.trending_now": "Rojev",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index c77970f69..604589b06 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -9,44 +9,44 @@
   "account.browse_more_on_origin_server": "Meer op het originele profiel bekijken",
   "account.cancel_follow_request": "Volgverzoek annuleren",
   "account.direct": "@{name} een direct bericht sturen",
-  "account.disable_notifications": "Geef geen melding meer wanneer @{name} toot",
+  "account.disable_notifications": "Geef geen melding meer wanneer @{name} een bericht plaatst",
   "account.domain_blocked": "Domein geblokkeerd",
   "account.edit_profile": "Profiel bewerken",
-  "account.enable_notifications": "Geef een melding wanneer @{name} toot",
+  "account.enable_notifications": "Geef een melding wanneer @{name} een bericht plaatst",
   "account.endorse": "Op profiel weergeven",
   "account.follow": "Volgen",
   "account.followers": "Volgers",
   "account.followers.empty": "Niemand volgt nog deze gebruiker.",
   "account.followers_counter": "{count, plural, one {{counter} volger} other {{counter} volgers}}",
-  "account.following": "Following",
+  "account.following": "Volgend",
   "account.following_counter": "{count, plural, one {{counter} volgend} other {{counter} volgend}}",
   "account.follows.empty": "Deze gebruiker volgt nog niemand.",
   "account.follows_you": "Volgt jou",
   "account.hide_reblogs": "Boosts van @{name} verbergen",
   "account.joined": "Geregistreerd in {date}",
   "account.link_verified_on": "Eigendom van deze link is gecontroleerd op {date}",
-  "account.locked_info": "De privacystatus van dit account is op besloten gezet. De eigenaar bepaalt handmatig wie hen kan volgen.",
+  "account.locked_info": "De privacystatus van dit account is op besloten gezet. De eigenaar bepaalt handmatig wie diegene kan volgen.",
   "account.media": "Media",
   "account.mention": "@{name} vermelden",
   "account.moved_to": "{name} is verhuisd naar:",
   "account.mute": "@{name} negeren",
   "account.mute_notifications": "Meldingen van @{name} negeren",
   "account.muted": "Genegeerd",
-  "account.posts": "Toots",
-  "account.posts_with_replies": "Toots en reacties",
+  "account.posts": "Berichten",
+  "account.posts_with_replies": "Berichten en reacties",
   "account.report": "@{name} rapporteren",
   "account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren",
   "account.share": "Profiel van @{name} delen",
   "account.show_reblogs": "Boosts van @{name} tonen",
-  "account.statuses_counter": "{count, plural, one {{counter} toot} other {{counter} toots}}",
+  "account.statuses_counter": "{count, plural, one {{counter} bericht} other {{counter} berichten}}",
   "account.unblock": "@{name} deblokkeren",
   "account.unblock_domain": "{domain} niet langer verbergen",
-  "account.unblock_short": "Unblock",
+  "account.unblock_short": "Deblokkeren",
   "account.unendorse": "Niet op profiel weergeven",
   "account.unfollow": "Ontvolgen",
   "account.unmute": "@{name} niet langer negeren",
   "account.unmute_notifications": "Meldingen van @{name} niet langer negeren",
-  "account.unmute_short": "Unmute",
+  "account.unmute_short": "Niet langer negeren",
   "account_note.placeholder": "Klik om een opmerking toe te voegen",
   "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
   "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
@@ -79,7 +79,7 @@
   "column.lists": "Lijsten",
   "column.mutes": "Genegeerde gebruikers",
   "column.notifications": "Meldingen",
-  "column.pins": "Vastgezette toots",
+  "column.pins": "Vastgezette berichten",
   "column.public": "Globale tijdlijn",
   "column_back_button.label": "Terug",
   "column_header.hide_settings": "Instellingen verbergen",
@@ -92,10 +92,10 @@
   "community.column_settings.local_only": "Alleen lokaal",
   "community.column_settings.media_only": "Alleen media",
   "community.column_settings.remote_only": "Alleen andere servers",
-  "compose_form.direct_message_warning": "Deze toot wordt alleen naar vermelde gebruikers verstuurd.",
+  "compose_form.direct_message_warning": "Dit bericht wordt alleen naar vermelde gebruikers verstuurd.",
   "compose_form.direct_message_warning_learn_more": "Meer leren",
-  "compose_form.hashtag_warning": "Deze toot valt niet onder een hashtag te bekijken, omdat deze niet op openbare tijdlijnen wordt getoond. Alleen openbare toots kunnen via hashtags gevonden worden.",
-  "compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en kan de toots zien die je alleen aan jouw volgers hebt gericht.",
+  "compose_form.hashtag_warning": "Dit bericht valt niet onder een hashtag te bekijken, omdat deze niet op openbare tijdlijnen wordt getoond. Alleen openbare berichten kunnen via hashtags gevonden worden.",
+  "compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en kan de berichten zien die je alleen aan jouw volgers hebt gericht.",
   "compose_form.lock_disclaimer.lock": "besloten",
   "compose_form.placeholder": "Wat wil je kwijt?",
   "compose_form.poll.add_option": "Keuze toevoegen",
@@ -106,7 +106,7 @@
   "compose_form.poll.switch_to_single": "Poll wijzigen om een enkele keuze toe te staan",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.save_changes": "Save changes",
+  "compose_form.save_changes": "Wijzigingen opslaan",
   "compose_form.sensitive.hide": "{count, plural, one {Media als gevoelig markeren} other {Media als gevoelig markeren}}",
   "compose_form.sensitive.marked": "{count, plural, one {Media is als gevoelig gemarkeerd} other {Media is als gevoelig gemarkeerd}}",
   "compose_form.sensitive.unmarked": "{count, plural, one {Media is niet als gevoelig gemarkeerd} other {Media is niet als gevoelig gemarkeerd}}",
@@ -118,22 +118,22 @@
   "confirmations.block.confirm": "Blokkeren",
   "confirmations.block.message": "Weet je het zeker dat je {name} wilt blokkeren?",
   "confirmations.delete.confirm": "Verwijderen",
-  "confirmations.delete.message": "Weet je het zeker dat je deze toot wilt verwijderen?",
+  "confirmations.delete.message": "Weet je het zeker dat je dit bericht wilt verwijderen?",
   "confirmations.delete_list.confirm": "Verwijderen",
   "confirmations.delete_list.message": "Weet je zeker dat je deze lijst definitief wilt verwijderen?",
   "confirmations.discard_edit_media.confirm": "Weggooien",
   "confirmations.discard_edit_media.message": "Je hebt niet-opgeslagen wijzigingen in de mediabeschrijving of voorvertonning, wil je deze toch weggooien?",
   "confirmations.domain_block.confirm": "Verberg alles van deze server",
-  "confirmations.domain_block.message": "Weet je het echt heel erg zeker dat je alles van {domain} wilt negeren? In de meeste gevallen is het blokkeren of negeren van een paar specifieke personen voldoende en beter. Je zult geen toots van deze server op openbare tijdlijnen zien of in jouw meldingen. Jouw volgers van deze server worden verwijderd.",
+  "confirmations.domain_block.message": "Weet je het echt heel erg zeker dat je alles van {domain} wilt negeren? In de meeste gevallen is het blokkeren of negeren van een paar specifieke personen voldoende en beter. Je zult geen berichten van deze server op openbare tijdlijnen zien of in jouw meldingen. Jouw volgers van deze server worden verwijderd.",
   "confirmations.logout.confirm": "Uitloggen",
   "confirmations.logout.message": "Weet je zeker dat je wilt uitloggen?",
   "confirmations.mute.confirm": "Negeren",
-  "confirmations.mute.explanation": "Dit verbergt toots van hen en toots waar hen in wordt vermeld, maar hen kan nog steeds jouw toots bekijken en jou volgen.",
+  "confirmations.mute.explanation": "Dit verbergt diens berichten en berichten waar diegene in wordt vermeld, maar diegene kan nog steeds jouw berichten bekijken en jou volgen.",
   "confirmations.mute.message": "Weet je het zeker dat je {name} wilt negeren?",
   "confirmations.redraft.confirm": "Verwijderen en herschrijven",
-  "confirmations.redraft.message": "Weet je zeker dat je deze toot wilt verwijderen en herschrijven? Je verliest wel de boosts en favorieten, en de reacties op de originele toot zitten niet meer aan de nieuwe toot vast.",
+  "confirmations.redraft.message": "Weet je zeker dat je dit bericht wilt verwijderen en herschrijven? Je verliest wel de boosts en favorieten, en de reacties op het originele bericht raak je kwijt.",
   "confirmations.reply.confirm": "Reageren",
-  "confirmations.reply.message": "Door nu te reageren overschrijf je de toot die je op dit moment aan het schrijven bent. Weet je zeker dat je verder wil gaan?",
+  "confirmations.reply.message": "Door nu te reageren overschrijf je het bericht dat je op dit moment aan het schrijven bent. Weet je zeker dat je verder wil gaan?",
   "confirmations.unfollow.confirm": "Ontvolgen",
   "confirmations.unfollow.message": "Weet je het zeker dat je {name} wilt ontvolgen?",
   "conversation.delete": "Gesprek verwijderen",
@@ -144,7 +144,7 @@
   "directory.local": "Alleen {domain}",
   "directory.new_arrivals": "Nieuwe accounts",
   "directory.recently_active": "Onlangs actief",
-  "embed.instructions": "Embed deze toot op jouw website, door de onderstaande code te kopiëren.",
+  "embed.instructions": "Embed dit bericht op jouw website door de onderstaande code te kopiëren.",
   "embed.preview": "Zo komt het eruit te zien:",
   "emoji_button.activity": "Activiteiten",
   "emoji_button.custom": "Lokale emoji’s",
@@ -161,41 +161,41 @@
   "emoji_button.symbols": "Symbolen",
   "emoji_button.travel": "Reizen en locaties",
   "empty_column.account_suspended": "Account opgeschort",
-  "empty_column.account_timeline": "Hier zijn geen toots!",
+  "empty_column.account_timeline": "Hier zijn geen berichten!",
   "empty_column.account_unavailable": "Profiel is niet beschikbaar",
   "empty_column.blocks": "Jij hebt nog geen enkele gebruiker geblokkeerd.",
-  "empty_column.bookmarked_statuses": "Jij hebt nog geen toots aan je bladwijzers toegevoegd. Wanneer je er een aan jouw bladwijzers toevoegt, valt deze hier te zien.",
-  "empty_column.community": "De lokale tijdlijn is nog leeg. Toot iets in het openbaar om de spits af te bijten!",
+  "empty_column.bookmarked_statuses": "Jij hebt nog geen berichten aan je bladwijzers toegevoegd. Wanneer je er een aan jouw bladwijzers toevoegt, valt deze hier te zien.",
+  "empty_column.community": "De lokale tijdlijn is nog leeg. Plaats een openbaar bericht om de spits af te bijten!",
   "empty_column.direct": "Je hebt nog geen directe berichten. Wanneer je er een verzend of ontvangt, zijn deze hier te zien.",
   "empty_column.domain_blocks": "Er zijn nog geen geblokkeerde domeinen.",
-  "empty_column.explore_statuses": "Nothing is trending right now. Check back later!",
-  "empty_column.favourited_statuses": "Jij hebt nog geen favoriete toots. Wanneer je er een aan jouw favorieten toevoegt, valt deze hier te zien.",
-  "empty_column.favourites": "Niemand heeft deze toot nog aan hun favorieten toegevoegd. Wanneer iemand dit doet, valt dat hier te zien.",
+  "empty_column.explore_statuses": "Momenteel zijn er geen trends. Kom later terug!",
+  "empty_column.favourited_statuses": "Jij hebt nog geen favoriete berichten. Wanneer je er een aan jouw favorieten toevoegt, valt deze hier te zien.",
+  "empty_column.favourites": "Niemand heeft dit bericht nog aan diens favorieten toegevoegd. Wanneer iemand dit doet, valt dat hier te zien.",
   "empty_column.follow_recommendations": "Het lijkt er op dat er geen aanbevelingen voor jou aangemaakt kunnen worden. Je kunt proberen te zoeken naar mensen die je wellicht kent, zoeken op hashtags, de lokale en globale tijdlijnen bekijken of de gebruikersgids doorbladeren.",
   "empty_column.follow_requests": "Jij hebt nog enkel volgverzoek ontvangen. Wanneer je er eentje ontvangt, valt dat hier te zien.",
   "empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.",
   "empty_column.home": "Deze tijdlijn is leeg! Volg meer mensen om het te vullen. {suggestions}",
   "empty_column.home.suggestions": "Enkele aanbevelingen bekijken",
-  "empty_column.list": "Er is nog niks te zien in deze lijst. Wanneer lijstleden nieuwe toots publiceren, zijn deze hier te zien.",
+  "empty_column.list": "Er is nog niks te zien in deze lijst. Wanneer lijstleden nieuwe berichten plaatsen, zijn deze hier te zien.",
   "empty_column.lists": "Jij hebt nog geen enkele lijst. Wanneer je er eentje hebt aangemaakt, valt deze hier te zien.",
   "empty_column.mutes": "Jij hebt nog geen gebruikers genegeerd.",
   "empty_column.notifications": "Je hebt nog geen meldingen. Begin met iemand een gesprek.",
-  "empty_column.public": "Er is hier helemaal niks! Toot iets in het openbaar of volg mensen van andere servers om het te vullen",
+  "empty_column.public": "Er is hier helemaal niks! Plaatst een openbaar bericht of volg mensen van andere servers om het te vullen",
   "error.unexpected_crash.explanation": "Als gevolg van een bug in onze broncode of als gevolg van een compatibiliteitsprobleem met jouw webbrowser, kan deze pagina niet goed worden weergegeven.",
   "error.unexpected_crash.explanation_addons": "Deze pagina kon niet correct geladen worden. Deze fout wordt waarschijnlijk door een browser-add-on of een automatische vertalingshulpmiddel veroorzaakt.",
   "error.unexpected_crash.next_steps": "Probeer deze pagina te vernieuwen. Wanneer dit niet helpt is het nog steeds mogelijk om Mastodon in een andere webbrowser of mobiele app te gebruiken.",
   "error.unexpected_crash.next_steps_addons": "Probeer deze uit te schakelen en de pagina te verversen. Wanneer dat niet helpt, kun je Mastodon nog altijd met een andere webbrowser of mobiele app gebruiken.",
   "errors.unexpected_crash.copy_stacktrace": "Stacktrace naar klembord kopiëren",
   "errors.unexpected_crash.report_issue": "Technisch probleem melden",
-  "explore.search_results": "Search results",
-  "explore.suggested_follows": "For you",
-  "explore.title": "Explore",
-  "explore.trending_links": "News",
-  "explore.trending_statuses": "Posts",
+  "explore.search_results": "Zoekresultaten",
+  "explore.suggested_follows": "Voor jou",
+  "explore.title": "Verkennen",
+  "explore.trending_links": "Nieuws",
+  "explore.trending_statuses": "Berichten",
   "explore.trending_tags": "Hashtags",
   "follow_recommendations.done": "Klaar",
-  "follow_recommendations.heading": "Volg mensen waarvan je graag toots wil zien! Hier zijn enkele aanbevelingen.",
-  "follow_recommendations.lead": "Toots van mensen die je volgt zullen in chronologische volgorde onder start verschijnen. Wees niet bang om hierin fouten te maken, want je kunt mensen op elk moment net zo eenvoudig ontvolgen!",
+  "follow_recommendations.heading": "Volg mensen waarvan je graag berichten wil zien! Hier zijn enkele aanbevelingen.",
+  "follow_recommendations.lead": "Berichten van mensen die je volgt zullen in chronologische volgorde onder start verschijnen. Wees niet bang om hierin fouten te maken, want je kunt mensen op elk moment net zo eenvoudig ontvolgen!",
   "follow_request.authorize": "Goedkeuren",
   "follow_request.reject": "Afkeuren",
   "follow_requests.unlocked_explanation": "Ook al is jouw account niet besloten, de medewerkers van {domain} denken dat jij misschien de volgende volgverzoeken handmatig wil controleren.",
@@ -227,13 +227,13 @@
   "intervals.full.minutes": "{number, plural, one {# minuut} other {# minuten}}",
   "keyboard_shortcuts.back": "Ga terug",
   "keyboard_shortcuts.blocked": "Geblokkeerde gebruikers tonen",
-  "keyboard_shortcuts.boost": "Toot boosten",
+  "keyboard_shortcuts.boost": "Bericht boosten",
   "keyboard_shortcuts.column": "Op één van de kolommen focussen",
-  "keyboard_shortcuts.compose": "Tekstveld voor toots focussen",
+  "keyboard_shortcuts.compose": "Tekstveld om een bericht te schrijven focussen",
   "keyboard_shortcuts.description": "Omschrijving",
   "keyboard_shortcuts.direct": "Jouw directe berichten tonen",
   "keyboard_shortcuts.down": "Naar beneden in de lijst bewegen",
-  "keyboard_shortcuts.enter": "Toot volledig tonen",
+  "keyboard_shortcuts.enter": "Volledig bericht tonen",
   "keyboard_shortcuts.favourite": "Aan jouw favorieten toevoegen",
   "keyboard_shortcuts.favourites": "Favorieten tonen",
   "keyboard_shortcuts.federated": "Globale tijdlijn tonen",
@@ -247,7 +247,7 @@
   "keyboard_shortcuts.my_profile": "Jouw profiel tonen",
   "keyboard_shortcuts.notifications": "Meldingen tonen",
   "keyboard_shortcuts.open_media": "Media openen",
-  "keyboard_shortcuts.pinned": "Jouw vastgezette toots tonen",
+  "keyboard_shortcuts.pinned": "Jouw vastgemaakte berichten tonen",
   "keyboard_shortcuts.profile": "Gebruikersprofiel auteur openen",
   "keyboard_shortcuts.reply": "Reageren",
   "keyboard_shortcuts.requests": "Jouw volgverzoeken tonen",
@@ -256,7 +256,7 @@
   "keyboard_shortcuts.start": "\"Aan de slag\" tonen",
   "keyboard_shortcuts.toggle_hidden": "Inhoudswaarschuwing tonen/verbergen",
   "keyboard_shortcuts.toggle_sensitivity": "Media tonen/verbergen",
-  "keyboard_shortcuts.toot": "Nieuwe toot schrijven",
+  "keyboard_shortcuts.toot": "Nieuw bericht schrijven",
   "keyboard_shortcuts.unfocus": "Tekst- en zoekveld ontfocussen",
   "keyboard_shortcuts.up": "Naar boven in de lijst bewegen",
   "lightbox.close": "Sluiten",
@@ -289,12 +289,12 @@
   "navigation_bar.blocks": "Geblokkeerde gebruikers",
   "navigation_bar.bookmarks": "Bladwijzers",
   "navigation_bar.community_timeline": "Lokale tijdlijn",
-  "navigation_bar.compose": "Nieuw toot schrijven",
+  "navigation_bar.compose": "Nieuw bericht schrijven",
   "navigation_bar.direct": "Directe berichten",
   "navigation_bar.discover": "Ontdekken",
   "navigation_bar.domain_blocks": "Geblokkeerde domeinen",
   "navigation_bar.edit_profile": "Profiel bewerken",
-  "navigation_bar.explore": "Explore",
+  "navigation_bar.explore": "Verkennen",
   "navigation_bar.favourites": "Favorieten",
   "navigation_bar.filters": "Filters",
   "navigation_bar.follow_requests": "Volgverzoeken",
@@ -305,23 +305,23 @@
   "navigation_bar.logout": "Uitloggen",
   "navigation_bar.mutes": "Genegeerde gebruikers",
   "navigation_bar.personal": "Persoonlijk",
-  "navigation_bar.pins": "Vastgezette toots",
+  "navigation_bar.pins": "Vastgemaakte berichten",
   "navigation_bar.preferences": "Instellingen",
   "navigation_bar.public_timeline": "Globale tijdlijn",
   "navigation_bar.security": "Beveiliging",
-  "notification.admin.sign_up": "{name} signed up",
-  "notification.favourite": "{name} voegde jouw toot als favoriet toe",
+  "notification.admin.sign_up": "{name} heeft zich aangemeld",
+  "notification.favourite": "{name} voegde jouw bericht als favoriet toe",
   "notification.follow": "{name} volgt jou nu",
   "notification.follow_request": "{name} wil jou graag volgen",
   "notification.mention": "{name} vermeldde jou",
   "notification.own_poll": "Jouw poll is beëindigd",
   "notification.poll": "Een poll waaraan jij hebt meegedaan is beëindigd",
-  "notification.reblog": "{name} boostte jouw toot",
-  "notification.status": "{name} heeft zojuist een toot geplaatst",
-  "notification.update": "{name} edited a post",
+  "notification.reblog": "{name} boostte jouw bericht",
+  "notification.status": "{name} heeft zojuist een bericht geplaatst",
+  "notification.update": "{name} heeft een bericht bewerkt",
   "notifications.clear": "Meldingen verwijderen",
   "notifications.clear_confirmation": "Weet je het zeker dat je al jouw meldingen wilt verwijderen?",
-  "notifications.column_settings.admin.sign_up": "New sign-ups:",
+  "notifications.column_settings.admin.sign_up": "Nieuwe aanmeldingen:",
   "notifications.column_settings.alert": "Desktopmeldingen",
   "notifications.column_settings.favourite": "Favorieten:",
   "notifications.column_settings.filter_bar.advanced": "Alle categorieën tonen",
@@ -335,10 +335,10 @@
   "notifications.column_settings.reblog": "Boosts:",
   "notifications.column_settings.show": "In kolom tonen",
   "notifications.column_settings.sound": "Geluid afspelen",
-  "notifications.column_settings.status": "Nieuwe toots:",
+  "notifications.column_settings.status": "Nieuwe berichten:",
   "notifications.column_settings.unread_notifications.category": "Ongelezen meldingen",
   "notifications.column_settings.unread_notifications.highlight": "Ongelezen meldingen markeren",
-  "notifications.column_settings.update": "Edits:",
+  "notifications.column_settings.update": "Bewerkingen:",
   "notifications.filter.all": "Alles",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favorieten",
@@ -365,7 +365,7 @@
   "poll.votes": "{votes, plural, one {# stem} other {# stemmen}}",
   "poll_button.add_poll": "Poll toevoegen",
   "poll_button.remove_poll": "Poll verwijderen",
-  "privacy.change": "Zichtbaarheid van toot aanpassen",
+  "privacy.change": "Zichtbaarheid van bericht aanpassen",
   "privacy.direct.long": "Alleen aan vermelde gebruikers tonen",
   "privacy.direct.short": "Direct",
   "privacy.private.long": "Alleen aan volgers tonen",
@@ -378,100 +378,100 @@
   "regeneration_indicator.label": "Aan het laden…",
   "regeneration_indicator.sublabel": "Jouw tijdlijn wordt aangemaakt!",
   "relative_time.days": "{number}d",
-  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
-  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
-  "relative_time.full.just_now": "just now",
-  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
-  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.full.days": "{number, plural, one {# dag} other {# dagen}} geleden",
+  "relative_time.full.hours": "{number, plural, one {# uur} other {# uur}} geleden",
+  "relative_time.full.just_now": "zojuist",
+  "relative_time.full.minutes": "{number, plural, one {# minuut} other {# minuten}} geleden",
+  "relative_time.full.seconds": "{number, plural, one {# seconde} other {# seconden}} geleden",
   "relative_time.hours": "{number}u",
   "relative_time.just_now": "nu",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "relative_time.today": "vandaag",
   "reply_indicator.cancel": "Annuleren",
-  "report.block": "Block",
-  "report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.",
-  "report.categories.other": "Other",
+  "report.block": "Blokkeren",
+  "report.block_explanation": "Je kunt diens berichten niet zien. Je kunt door diegene niet gevolgd worden en jouw berichten zijn onzichtbaar. Diegene kan zien dat die door jou is geblokkeerd.",
+  "report.categories.other": "Overig",
   "report.categories.spam": "Spam",
-  "report.categories.violation": "Content violates one or more server rules",
-  "report.category.subtitle": "Choose the best match",
-  "report.category.title": "Tell us what's going on with this {type}",
-  "report.category.title_account": "profile",
-  "report.category.title_status": "post",
-  "report.close": "Done",
-  "report.comment.title": "Is there anything else you think we should know?",
+  "report.categories.violation": "De inhoud overtreedt een of meerdere serverregels",
+  "report.category.subtitle": "Kies wat het meeste overeenkomt",
+  "report.category.title": "Vertel ons wat er met dit {type} aan de hand is",
+  "report.category.title_account": "profiel",
+  "report.category.title_status": "bericht",
+  "report.close": "Klaar",
+  "report.comment.title": "Zijn er nog andere dingen waarvan je denkt dat wij dat moeten weten?",
   "report.forward": "Naar {target} doorsturen",
   "report.forward_hint": "Het account bevindt zich op een andere server. Wil je daar eveneens een geanonimiseerde kopie van deze rapportage naar toe sturen?",
-  "report.mute": "Mute",
-  "report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
-  "report.next": "Next",
+  "report.mute": "Negeren",
+  "report.mute_explanation": "Je kunt diens berichten niet zien. Je kunt nog wel gevolgd worden en jouw berichten zijn nog zichtbaar, maar diegene kan niet zien dat die wordt genegeerd.",
+  "report.next": "Volgende",
   "report.placeholder": "Extra opmerkingen",
-  "report.reasons.dislike": "I don't like it",
-  "report.reasons.dislike_description": "It is not something you want to see",
-  "report.reasons.other": "It's something else",
-  "report.reasons.other_description": "The issue does not fit into other categories",
-  "report.reasons.spam": "It's spam",
-  "report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies",
-  "report.reasons.violation": "It violates server rules",
-  "report.reasons.violation_description": "You are aware that it breaks specific rules",
-  "report.rules.subtitle": "Select all that apply",
-  "report.rules.title": "Which rules are being violated?",
-  "report.statuses.subtitle": "Select all that apply",
-  "report.statuses.title": "Are there any posts that back up this report?",
+  "report.reasons.dislike": "Ik vind het niet leuk",
+  "report.reasons.dislike_description": "Het is iets wat je niet wilt zien",
+  "report.reasons.other": "Het is iets anders",
+  "report.reasons.other_description": "Het probleem past niet in een andere categorie",
+  "report.reasons.spam": "Het is spam",
+  "report.reasons.spam_description": "Schadelijke links, reclame, misleiding of herhalende antwoorden",
+  "report.reasons.violation": "Het schendt de serverregels",
+  "report.reasons.violation_description": "Je weet dat het specifieke regels schendt",
+  "report.rules.subtitle": "Selecteer wat van toepassing is",
+  "report.rules.title": "Welke regels worden geschonden?",
+  "report.statuses.subtitle": "Selecteer wat van toepassing is",
+  "report.statuses.title": "Zijn er berichten die deze rapportage ondersteunen?",
   "report.submit": "Verzenden",
   "report.target": "{target} rapporteren",
-  "report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:",
-  "report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:",
-  "report.thanks.title": "Don't want to see this?",
-  "report.thanks.title_actionable": "Thanks for reporting, we'll look into this.",
-  "report.unfollow": "Unfollow @{name}",
-  "report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
+  "report.thanks.take_action": "Hier zijn jouw opties waarmee je kunt bepalen wat je in Mastodon wilt zien:",
+  "report.thanks.take_action_actionable": "Terwijl wij jouw rapportage beroordelen, kun je deze acties ondernemen tegen @{name}:",
+  "report.thanks.title": "Wil je dit niet zien?",
+  "report.thanks.title_actionable": "Dank je voor het rapporteren. Wij gaan er naar kijken.",
+  "report.unfollow": "@{name} ontvolgen",
+  "report.unfollow_explanation": "Je volgt dit account. Om diens berichten niet meer op jouw starttijdlijn te zien, kun je diegene ontvolgen.",
   "search.placeholder": "Zoeken",
   "search_popout.search_format": "Geavanceerd zoeken",
-  "search_popout.tips.full_text": "Gebruik gewone tekst om te zoeken in jouw toots, gebooste toots, favorieten en in toots waarin je bent vermeldt, en tevens naar gebruikersnamen, weergavenamen en hashtags.",
+  "search_popout.tips.full_text": "Gebruik gewone tekst om te zoeken in jouw berichten, gebooste berichten, favorieten en in berichten waarin je bent vermeldt, en tevens naar gebruikersnamen, weergavenamen en hashtags.",
   "search_popout.tips.hashtag": "hashtag",
-  "search_popout.tips.status": "toot",
+  "search_popout.tips.status": "bericht",
   "search_popout.tips.text": "Gebruik gewone tekst om te zoeken op weergavenamen, gebruikersnamen en hashtags",
   "search_popout.tips.user": "gebruiker",
   "search_results.accounts": "Gebruikers",
-  "search_results.all": "All",
+  "search_results.all": "Alles",
   "search_results.hashtags": "Hashtags",
-  "search_results.nothing_found": "Could not find anything for these search terms",
-  "search_results.statuses": "Toots",
-  "search_results.statuses_fts_disabled": "Het zoeken in toots is op deze Mastodon-server niet ingeschakeld.",
+  "search_results.nothing_found": "Deze zoektermen leveren geen resultaat op",
+  "search_results.statuses": "Berichten",
+  "search_results.statuses_fts_disabled": "Het zoeken in berichten is op deze Mastodon-server niet ingeschakeld.",
   "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
   "status.admin_account": "Moderatie-omgeving van @{name} openen",
-  "status.admin_status": "Deze toot in de moderatie-omgeving openen",
+  "status.admin_status": "Dit bericht in de moderatie-omgeving openen",
   "status.block": "@{name} blokkeren",
   "status.bookmark": "Bladwijzer toevoegen",
   "status.cancel_reblog_private": "Niet langer boosten",
-  "status.cannot_reblog": "Deze toot kan niet geboost worden",
-  "status.copy": "Link naar toot kopiëren",
+  "status.cannot_reblog": "Dit bericht kan niet geboost worden",
+  "status.copy": "Link naar bericht kopiëren",
   "status.delete": "Verwijderen",
   "status.detailed_status": "Uitgebreide gespreksweergave",
   "status.direct": "@{name} een direct bericht sturen",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edit": "Bewerken",
+  "status.edited": "Bewerkt op {date}",
+  "status.edited_x_times": "{count, plural, one {{count} keer} other {{count} keer}} bewerkt",
   "status.embed": "Insluiten",
   "status.favourite": "Favoriet",
   "status.filtered": "Gefilterd",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.history.created": "{name} plaatste dit {date}",
+  "status.history.edited": "{name} bewerkte dit {date}",
   "status.load_more": "Meer laden",
   "status.media_hidden": "Media verborgen",
   "status.mention": "@{name} vermelden",
   "status.more": "Meer",
   "status.mute": "@{name} negeren",
   "status.mute_conversation": "Negeer gesprek",
-  "status.open": "Volledige toot tonen",
+  "status.open": "Volledig bericht tonen",
   "status.pin": "Aan profielpagina vastmaken",
-  "status.pinned": "Vastgemaakte toot",
+  "status.pinned": "Vastgemaakt bericht",
   "status.read_more": "Meer lezen",
   "status.reblog": "Boosten",
   "status.reblog_private": "Boost naar oorspronkelijke ontvangers",
   "status.reblogged_by": "{name} boostte",
-  "status.reblogs.empty": "Niemand heeft deze toot nog geboost. Wanneer iemand dit doet, valt dat hier te zien.",
+  "status.reblogs.empty": "Niemand heeft dit bericht nog geboost. Wanneer iemand dit doet, valt dat hier te zien.",
   "status.redraft": "Verwijderen en herschrijven",
   "status.remove_bookmark": "Bladwijzer verwijderen",
   "status.reply": "Reageren",
@@ -502,7 +502,7 @@
   "timeline_hint.remote_resource_not_displayed": "{resource} van andere servers worden niet getoond.",
   "timeline_hint.resources.followers": "Volgers",
   "timeline_hint.resources.follows": "Volgend",
-  "timeline_hint.resources.statuses": "Oudere toots",
+  "timeline_hint.resources.statuses": "Oudere berichten",
   "trends.counter_by_accounts": "{count, plural, one {{counter} persoon} other {{counter} personen}} zijn aan het praten",
   "trends.trending_now": "Huidige trends",
   "ui.beforeunload": "Je concept gaat verloren wanneer je Mastodon verlaat.",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index 18e429641..f620e02b5 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -18,7 +18,7 @@
   "account.followers": "Seguidores",
   "account.followers.empty": "Nada aqui.",
   "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
-  "account.following": "Following",
+  "account.following": "Seguindo",
   "account.following_counter": "{count, plural, one {segue {counter}} other {segue {counter}}}",
   "account.follows.empty": "Nada aqui.",
   "account.follows_you": "te segue",
@@ -41,14 +41,14 @@
   "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}",
   "account.unblock": "Desbloquear @{name}",
   "account.unblock_domain": "Desbloquear domínio {domain}",
-  "account.unblock_short": "Unblock",
+  "account.unblock_short": "Desbloquear",
   "account.unendorse": "Remover",
   "account.unfollow": "Deixar de seguir",
   "account.unmute": "Dessilenciar @{name}",
   "account.unmute_notifications": "Mostrar notificações de @{name}",
-  "account.unmute_short": "Unmute",
+  "account.unmute_short": "Reativar",
   "account_note.placeholder": "Nota pessoal sobre este perfil aqui",
-  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
+  "admin.dashboard.daily_retention": "Taxa de retenção de usuários por dia, após a inscrição",
   "admin.dashboard.monthly_retention": "Taxa de retenção de usuários por mês, após a inscrição",
   "admin.dashboard.retention.average": "Média",
   "admin.dashboard.retention.cohort": "Mês de inscrição",
@@ -168,7 +168,7 @@
   "empty_column.community": "A linha local está vazia. Publique algo para começar!",
   "empty_column.direct": "Nada aqui. Quando você enviar ou receber toots diretos, eles aparecerão aqui.",
   "empty_column.domain_blocks": "Nada aqui.",
-  "empty_column.explore_statuses": "Nothing is trending right now. Check back later!",
+  "empty_column.explore_statuses": "Nada está em alta no momento. Volte mais tarde!",
   "empty_column.favourited_statuses": "Nada aqui. Quando você favoritar um toot, ele aparecerá aqui.",
   "empty_column.favourites": "Nada aqui. Quando alguém favoritar, o autor aparecerá aqui.",
   "empty_column.follow_recommendations": "Parece que não há sugestões para você. Tente usar a pesquisa para encontrar pessoas que você possa conhecer ou explorar hashtags.",
@@ -294,7 +294,7 @@
   "navigation_bar.discover": "Descobrir",
   "navigation_bar.domain_blocks": "Domínios bloqueados",
   "navigation_bar.edit_profile": "Editar perfil",
-  "navigation_bar.explore": "Explore",
+  "navigation_bar.explore": "Explorar",
   "navigation_bar.favourites": "Favoritos",
   "navigation_bar.filters": "Palavras filtradas",
   "navigation_bar.follow_requests": "Seguidores pendentes",
@@ -390,42 +390,42 @@
   "relative_time.today": "hoje",
   "reply_indicator.cancel": "Cancelar",
   "report.block": "Bloquear",
-  "report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.",
+  "report.block_explanation": "Você não verá suas postagens. Eles não poderão ver suas postagens ou segui-lo. Eles serão capazes de perceber que estão bloqueados.",
   "report.categories.other": "Outro",
   "report.categories.spam": "Spam",
   "report.categories.violation": "O conteúdo viola uma ou mais regras do servidor",
-  "report.category.subtitle": "Choose the best match",
-  "report.category.title": "Tell us what's going on with this {type}",
+  "report.category.subtitle": "Escolha a alternativa de melhor correspondência",
+  "report.category.title": "Conte-nos o que está acontecendo com esse {type}",
   "report.category.title_account": "perfil",
   "report.category.title_status": "publicação",
   "report.close": "Concluído",
-  "report.comment.title": "Is there anything else you think we should know?",
+  "report.comment.title": "Há algo mais que você acredita que devemos saber?",
   "report.forward": "Encaminhar para {target}",
   "report.forward_hint": "A conta está em outra instância. Enviar uma cópia anônima da denúncia para lá?",
   "report.mute": "Silenciar",
-  "report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
+  "report.mute_explanation": "Você não verá suas postagens. Eles ainda podem seguir você e ver suas postagens e não saberão que estão silenciados.",
   "report.next": "Próximo",
   "report.placeholder": "Comentários adicionais aqui",
   "report.reasons.dislike": "Eu não gosto disso",
-  "report.reasons.dislike_description": "It is not something you want to see",
-  "report.reasons.other": "It's something else",
+  "report.reasons.dislike_description": "Não é algo que você quer ver",
+  "report.reasons.other": "É outra coisa",
   "report.reasons.other_description": "O problema não se encaixa em outras categorias",
   "report.reasons.spam": "É spam",
-  "report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies",
-  "report.reasons.violation": "It violates server rules",
+  "report.reasons.spam_description": "Links maliciosos, envolvimento falso ou respostas repetitivas",
+  "report.reasons.violation": "Viola as regras do servidor",
   "report.reasons.violation_description": "Você está ciente de que isso quebra regras específicas",
   "report.rules.subtitle": "Selecione tudo que se aplica",
-  "report.rules.title": "Which rules are being violated?",
+  "report.rules.title": "Que regras estão sendo violadas?",
   "report.statuses.subtitle": "Selecione tudo que se aplica",
-  "report.statuses.title": "Are there any posts that back up this report?",
+  "report.statuses.title": "Existem postagens que respaldam esse relatório?",
   "report.submit": "Enviar",
   "report.target": "Denunciando {target}",
-  "report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:",
+  "report.thanks.take_action": "Aqui estão suas opções para controlar o que você vê no Mastodon:",
   "report.thanks.take_action_actionable": "Enquanto revisamos isso, você pode tomar medidas contra @{name}:",
   "report.thanks.title": "Não quer ver isto?",
   "report.thanks.title_actionable": "Obrigado por reportar. Vamos analisar.",
-  "report.unfollow": "Unfollow @{name}",
-  "report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
+  "report.unfollow": "Deixar de seguir @{name}",
+  "report.unfollow_explanation": "Você está seguindo esta conta. Para não mais ver os posts dele em sua página inicial, deixe de segui-lo.",
   "search.placeholder": "Pesquisar",
   "search_popout.search_format": "Formato de pesquisa avançada",
   "search_popout.tips.full_text": "Texto simples retorna toots que você escreveu, favoritou, deu boost, ou em que foi mencionado, assim como nomes de usuário e de exibição, e hashtags correspondentes.",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index bf3097dc0..7267fc99d 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -18,7 +18,7 @@
   "account.followers": "Подписчики",
   "account.followers.empty": "На этого пользователя пока никто не подписан.",
   "account.followers_counter": "{count, plural, one {{counter} подписчик} many {{counter} подписчиков} other {{counter} подписчика}}",
-  "account.following": "Following",
+  "account.following": "Подписки",
   "account.following_counter": "{count, plural, one {{counter} подписка} many {{counter} подписок} other {{counter} подписки}}",
   "account.follows.empty": "Этот пользователь пока ни на кого не подписался.",
   "account.follows_you": "Подписан(а) на вас",
@@ -95,7 +95,7 @@
   "compose_form.direct_message_warning": "Адресованные посты отправляются и видны только упомянутым в них пользователям.",
   "compose_form.direct_message_warning_learn_more": "Подробнее",
   "compose_form.hashtag_warning": "Так как этот пост не публичный, он не отобразится в поиске по хэштегам.",
-  "compose_form.lock_disclaimer": "Ваша учётная запись не {locked}. Любой пользователь сможет подписаться на вас и просматривать посты для подписчиков.",
+  "compose_form.lock_disclaimer": "Ваша учётная запись {locked}. Любой пользователь сможет подписаться на вас и просматривать посты для подписчиков.",
   "compose_form.lock_disclaimer.lock": "не закрыта",
   "compose_form.placeholder": "О чём думаете?",
   "compose_form.poll.add_option": "Добавить вариант",
@@ -390,42 +390,42 @@
   "relative_time.today": "сегодня",
   "reply_indicator.cancel": "Отмена",
   "report.block": "Заблокировать",
-  "report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.",
+  "report.block_explanation": "В перестаните видеть посты этого пользователя, а он(а) больше не сможет подписаться на вас и читать ваши посты. Он(а) сможет понять что вы заблокировали его/её.",
   "report.categories.other": "Другое",
   "report.categories.spam": "Спам",
   "report.categories.violation": "Содержимое нарушает одно или несколько правил узла",
   "report.category.subtitle": "Выберите наиболее подходящее",
-  "report.category.title": "Расскажите нам, что происходит с {type}",
-  "report.category.title_account": "профиль",
-  "report.category.title_status": "пост",
+  "report.category.title": "Расскажите нам, что не так с {type}",
+  "report.category.title_account": "этим профилем",
+  "report.category.title_status": "этим постом",
   "report.close": "Готово",
-  "report.comment.title": "Есть что-нибудь еще, что мы должны знать?",
-  "report.forward": "Переслать в {target}",
+  "report.comment.title": "Есть ли что-нибудь ещё, что нам стоит знать?",
+  "report.forward": "Переслать на {target}",
   "report.forward_hint": "Эта учётная запись расположена на другом узле. Отправить туда анонимную копию вашей жалобы?",
-  "report.mute": "Mute",
+  "report.mute": "Игнорировать",
   "report.mute_explanation": "Вы не будете видеть их посты. Они по-прежнему могут подписываться на вас и видеть ваши посты, но не будут знать, что они в списке игнорируемых.",
   "report.next": "Далее",
-  "report.placeholder": "Комментарий",
+  "report.placeholder": "Дополнительные комментарии",
   "report.reasons.dislike": "Мне не нравится",
   "report.reasons.dislike_description": "Не хотел(а) бы видеть такой контент",
   "report.reasons.other": "Другое",
-  "report.reasons.other_description": "Проблема не подпадает под другие категории",
+  "report.reasons.other_description": "Проблема не попадает ни под одну из категорий",
   "report.reasons.spam": "Это спам",
-  "report.reasons.spam_description": "Вредоносные ссылки, поддельные действия или повторяющиеся ответы",
+  "report.reasons.spam_description": "Вредоносные ссылки, фальшивое взаимодействие или повторяющиеся ответы",
   "report.reasons.violation": "Нарушаются правила сервера",
   "report.reasons.violation_description": "Вы знаете, что подобное нарушает определенные правила",
   "report.rules.subtitle": "Выберите все подходящие варианты",
   "report.rules.title": "Какие правила нарушены?",
   "report.statuses.subtitle": "Выберите все подходящие варианты",
-  "report.statuses.title": "Есть ли сообщения, подтверждающие основания этой жалобы?",
+  "report.statuses.title": "Выберите посты, которые относятся к вашей жалобе.",
   "report.submit": "Отправить",
   "report.target": "Жалоба на {target}",
-  "report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:",
-  "report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:",
+  "report.thanks.take_action": "Вот несколько опций управления тем, что вы видите в Mastodon:",
+  "report.thanks.take_action_actionable": "Пока мы рассматриваем его, вот действия, которые вы можете предпринять лично против @{name}:",
   "report.thanks.title": "Не хотите видеть это?",
   "report.thanks.title_actionable": "Спасибо за обращение, мы его рассмотрим.",
   "report.unfollow": "Отписаться от @{name}",
-  "report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
+  "report.unfollow_explanation": "Вы подписаны на этого пользователя. Чтобы не видеть его/её посты в своей домашней ленте, отпишитесь от него/неё.",
   "search.placeholder": "Поиск",
   "search_popout.search_format": "Продвинутый формат поиска",
   "search_popout.tips.full_text": "Поиск по простому тексту отобразит посты, которые вы написали, добавили в избранное, продвинули или в которых были упомянуты, а также подходящие имена пользователей и хэштеги.",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 60b513aa1..860e65413 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -389,7 +389,7 @@
   "relative_time.seconds": "{number}sek",
   "relative_time.today": "dnes",
   "reply_indicator.cancel": "Zrušiť",
-  "report.block": "Block",
+  "report.block": "Blokuj",
   "report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.",
   "report.categories.other": "Other",
   "report.categories.spam": "Spam",
@@ -424,7 +424,7 @@
   "report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:",
   "report.thanks.title": "Nechceš to vidieť?",
   "report.thanks.title_actionable": "Thanks for reporting, we'll look into this.",
-  "report.unfollow": "Unfollow @{name}",
+  "report.unfollow": "Nesleduj @{name}",
   "report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
   "search.placeholder": "Hľadaj",
   "search_popout.search_format": "Pokročilé vyhľadávanie",
@@ -434,7 +434,7 @@
   "search_popout.tips.text": "Vráti jednoduchý textový výpis zhodujúcich sa mien, prezývok a haštagov",
   "search_popout.tips.user": "užívateľ",
   "search_results.accounts": "Ľudia",
-  "search_results.all": "All",
+  "search_results.all": "Všetky",
   "search_results.hashtags": "Haštagy",
   "search_results.nothing_found": "Could not find anything for these search terms",
   "search_results.statuses": "Príspevky",
@@ -451,7 +451,7 @@
   "status.detailed_status": "Podrobný náhľad celej konverzácie",
   "status.direct": "Priama správa pre @{name}",
   "status.edit": "Uprav",
-  "status.edited": "Edited {date}",
+  "status.edited": "Upravené {date}",
   "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
   "status.embed": "Vložiť",
   "status.favourite": "Páči sa mi",
diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json
index d1047f5da..ce3cdabd0 100644
--- a/app/javascript/mastodon/locales/sq.json
+++ b/app/javascript/mastodon/locales/sq.json
@@ -18,7 +18,7 @@
   "account.followers": "Ndjekës",
   "account.followers.empty": "Këtë përdorues ende s’e ndjek kush.",
   "account.followers_counter": "{count, plural, one {{counter} Ndjekës} other {{counter} Ndjekës}}",
-  "account.following": "Following",
+  "account.following": "Ndjekje",
   "account.following_counter": "{count, plural, one {{counter} i Ndjekur} other {{counter} të Ndjekur}}",
   "account.follows.empty": "Ky përdorues ende s’ndjek kënd.",
   "account.follows_you": "Ju ndjek",
@@ -41,12 +41,12 @@
   "account.statuses_counter": "{count, plural, one {{counter} Mesazh} other {{counter} Mesazhe}}",
   "account.unblock": "Zhbllokoje @{name}",
   "account.unblock_domain": "Zhblloko përkatësinë {domain}",
-  "account.unblock_short": "Unblock",
+  "account.unblock_short": "Zhbllokoje",
   "account.unendorse": "Mos e përfshi në profil",
   "account.unfollow": "Resht së ndjekuri",
   "account.unmute": "Ktheji zërin @{name}",
   "account.unmute_notifications": "Hiqua ndalimin e shfaqjes njoftimeve nga @{name}",
-  "account.unmute_short": "Unmute",
+  "account.unmute_short": "Çheshtoje",
   "account_note.placeholder": "Klikoni për të shtuar shënim",
   "admin.dashboard.daily_retention": "Shkallë mbajtjeje përdoruesi, në ditë, pas regjistrimit",
   "admin.dashboard.monthly_retention": "Shkallë mbajtjeje përdoruesi, në muaj, pas regjistrimit",
@@ -294,7 +294,7 @@
   "navigation_bar.discover": "Zbuloni",
   "navigation_bar.domain_blocks": "Përkatësi të bllokuara",
   "navigation_bar.edit_profile": "Përpunoni profilin",
-  "navigation_bar.explore": "Explore",
+  "navigation_bar.explore": "Eksploroni",
   "navigation_bar.favourites": "Të parapëlqyer",
   "navigation_bar.filters": "Fjalë të heshtuara",
   "navigation_bar.follow_requests": "Kërkesa për ndjekje",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index f7eddd11b..f8477e8fd 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -128,7 +128,7 @@
   "confirmations.logout.confirm": "ออกจากระบบ",
   "confirmations.logout.message": "คุณแน่ใจหรือไม่ว่าต้องการออกจากระบบ?",
   "confirmations.mute.confirm": "ซ่อน",
-  "confirmations.mute.explanation": "นี่จะซ่อนโพสต์จากเขาและโพสต์ที่กล่าวถึงเขา แต่จะยังอนุญาตให้เขาเห็นโพสต์ของคุณและติดตามคุณ",
+  "confirmations.mute.explanation": "นี่จะซ่อนโพสต์จากเขาและโพสต์ที่กล่าวถึงเขา แต่จะยังคงอนุญาตให้เขาเห็นโพสต์ของคุณและติดตามคุณ",
   "confirmations.mute.message": "คุณแน่ใจหรือไม่ว่าต้องการซ่อน {name}?",
   "confirmations.redraft.confirm": "ลบแล้วร่างใหม่",
   "confirmations.redraft.message": "คุณแน่ใจหรือไม่ว่าต้องการลบโพสต์นี้แล้วร่างโพสต์ใหม่? รายการโปรดและการดันจะหายไป และการตอบกลับโพสต์ดั้งเดิมจะไม่มีความเกี่ยวพัน",
@@ -403,7 +403,7 @@
   "report.forward": "ส่งต่อไปยัง {target}",
   "report.forward_hint": "บัญชีมาจากเซิร์ฟเวอร์อื่น ส่งสำเนาของรายงานที่ไม่ระบุตัวตนไปที่นั่นด้วย?",
   "report.mute": "ซ่อน",
-  "report.mute_explanation": "คุณจะไม่เห็นโพสต์ของเขา เขายังสามารถติดตามคุณและเห็นโพสต์ของคุณและจะไม่ทราบว่ามีการซ่อนเขา",
+  "report.mute_explanation": "คุณจะไม่เห็นโพสต์ของเขา เขายังคงสามารถติดตามคุณและเห็นโพสต์ของคุณและจะไม่ทราบว่ามีการซ่อนเขา",
   "report.next": "ถัดไป",
   "report.placeholder": "ความคิดเห็นเพิ่มเติม",
   "report.reasons.dislike": "ฉันไม่ชอบโพสต์",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 06bb94307..e2e1386e6 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -18,7 +18,7 @@
   "account.followers": "Підписники",
   "account.followers.empty": "Ніхто ще не підписався на цього користувача.",
   "account.followers_counter": "{count, plural, one {{counter} Підписник} few {{counter} Підписники} many {{counter} Підписників} other {{counter} Підписники}}",
-  "account.following": "Following",
+  "account.following": "Стежите",
   "account.following_counter": "{count, plural, one {{counter} Підписка} few {{counter} Підписки} many {{counter} Підписок} other {{counter} Підписки}}",
   "account.follows.empty": "Цей користувач ще ні на кого не підписався.",
   "account.follows_you": "Підписаний(-а) на вас",
@@ -41,7 +41,7 @@
   "account.statuses_counter": "{count, plural, one {{counter} Пост} few {{counter} Пости} many {{counter} Постів} other {{counter} Пости}}",
   "account.unblock": "Розблокувати @{name}",
   "account.unblock_domain": "Розблокувати {domain}",
-  "account.unblock_short": "Unblock",
+  "account.unblock_short": "Розблокувати",
   "account.unendorse": "Не публікувати у профілі",
   "account.unfollow": "Відписатися",
   "account.unmute": "Зняти глушення з @{name}",
@@ -187,7 +187,7 @@
   "error.unexpected_crash.next_steps_addons": "Спробуйте їх вимкнути та оновити сторінку. Якщо це не допомагає, ви можете використовувати Mastodon через інший браузер або окремий застосунок.",
   "errors.unexpected_crash.copy_stacktrace": "Скопіювати трасування стека у буфер обміну",
   "errors.unexpected_crash.report_issue": "Повідомити про проблему",
-  "explore.search_results": "Search results",
+  "explore.search_results": "Результати пошуку",
   "explore.suggested_follows": "Для вас",
   "explore.title": "Огляд",
   "explore.trending_links": "Новини",
@@ -309,7 +309,7 @@
   "navigation_bar.preferences": "Налаштування",
   "navigation_bar.public_timeline": "Глобальна стрічка",
   "navigation_bar.security": "Безпека",
-  "notification.admin.sign_up": "{name} signed up",
+  "notification.admin.sign_up": "{name} приєднується",
   "notification.favourite": "{name} вподобав(-ла) ваш допис",
   "notification.follow": "{name} підписався(-лась) на вас",
   "notification.follow_request": "{name} відправив(-ла) запит на підписку",
@@ -321,7 +321,7 @@
   "notification.update": "{name} змінює допис",
   "notifications.clear": "Очистити сповіщення",
   "notifications.clear_confirmation": "Ви впевнені, що хочете назавжди видалити всі сповіщеня?",
-  "notifications.column_settings.admin.sign_up": "New sign-ups:",
+  "notifications.column_settings.admin.sign_up": "Нові реєстрації:",
   "notifications.column_settings.alert": "Сповіщення на комп'ютері",
   "notifications.column_settings.favourite": "Вподобане:",
   "notifications.column_settings.filter_bar.advanced": "Показати всі категорії",
@@ -389,7 +389,7 @@
   "relative_time.seconds": "{number}с",
   "relative_time.today": "сьогодні",
   "reply_indicator.cancel": "Відмінити",
-  "report.block": "Block",
+  "report.block": "Заблокувати",
   "report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.",
   "report.categories.other": "Інше",
   "report.categories.spam": "Спам",
@@ -402,7 +402,7 @@
   "report.comment.title": "Is there anything else you think we should know?",
   "report.forward": "Надіслати до {target}",
   "report.forward_hint": "Це акаунт з іншого серверу. Відправити анонімізовану копію скарги і туди?",
-  "report.mute": "Mute",
+  "report.mute": "Заглушити",
   "report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
   "report.next": "Далі",
   "report.placeholder": "Додаткові коментарі",
@@ -415,8 +415,8 @@
   "report.reasons.violation": "It violates server rules",
   "report.reasons.violation_description": "You are aware that it breaks specific rules",
   "report.rules.subtitle": "Select all that apply",
-  "report.rules.title": "Which rules are being violated?",
-  "report.statuses.subtitle": "Select all that apply",
+  "report.rules.title": "Які правила порушено?",
+  "report.statuses.subtitle": "Виберіть усі варіанти, що підходять",
   "report.statuses.title": "Are there any posts that back up this report?",
   "report.submit": "Відправити",
   "report.target": "Скаржимося на {target}",
@@ -424,7 +424,7 @@
   "report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:",
   "report.thanks.title": "Don't want to see this?",
   "report.thanks.title_actionable": "Thanks for reporting, we'll look into this.",
-  "report.unfollow": "Unfollow @{name}",
+  "report.unfollow": "Відписатися від @{name}",
   "report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
   "search.placeholder": "Пошук",
   "search_popout.search_format": "Розширений формат пошуку",
diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json
index b6db3c14b..d47033881 100644
--- a/app/javascript/mastodon/locales/vi.json
+++ b/app/javascript/mastodon/locales/vi.json
@@ -8,7 +8,7 @@
   "account.blocked": "Đã chặn",
   "account.browse_more_on_origin_server": "Truy cập trang của người này",
   "account.cancel_follow_request": "Hủy yêu cầu theo dõi",
-  "account.direct": "Nhắn tin @{name}",
+  "account.direct": "Nhắn riêng @{name}",
   "account.disable_notifications": "Không thông báo khi @{name} đăng tút",
   "account.domain_blocked": "Người đã chặn",
   "account.edit_profile": "Chỉnh sửa trang cá nhân",
@@ -49,9 +49,9 @@
   "account.unmute_short": "Bỏ ẩn",
   "account_note.placeholder": "Nhấn để thêm",
   "admin.dashboard.daily_retention": "Tỉ lệ người dùng sau đăng ký ở lại theo ngày",
-  "admin.dashboard.monthly_retention": "Tỉ lệ người dùng sau đăng ký ở lại theo tháng",
+  "admin.dashboard.monthly_retention": "Tỉ lệ người dùng ở lại sau khi đăng ký",
   "admin.dashboard.retention.average": "Trung bình",
-  "admin.dashboard.retention.cohort": "Đăng ký tháng",
+  "admin.dashboard.retention.cohort": "Tháng đăng ký",
   "admin.dashboard.retention.cohort_size": "Người dùng mới",
   "alert.rate_limited.message": "Vui lòng thử lại sau {retry_time, time, medium}.",
   "alert.rate_limited.title": "Vượt giới hạn",
@@ -179,7 +179,7 @@
   "empty_column.list": "Chưa có tút. Khi những người trong danh sách này đăng tút mới, chúng sẽ xuất hiện ở đây.",
   "empty_column.lists": "Bạn chưa tạo danh sách nào.",
   "empty_column.mutes": "Bạn chưa ẩn bất kỳ ai.",
-  "empty_column.notifications": "Bạn chưa có thông báo nào. Hãy thử theo dõi hoặc nhắn tin cho một ai đó.",
+  "empty_column.notifications": "Bạn chưa có thông báo nào. Hãy thử theo dõi hoặc nhắn riêng cho một ai đó.",
   "empty_column.public": "Trống trơn! Bạn hãy viết gì đó hoặc bắt đầu theo dõi những người khác",
   "error.unexpected_crash.explanation": "Trang này có thể không hiển thị chính xác do lỗi lập trình Mastodon hoặc vấn đề tương thích trình duyệt.",
   "error.unexpected_crash.explanation_addons": "Trang này không thể hiển thị do xung khắc với add-on của trình duyệt hoặc công cụ tự động dịch ngôn ngữ.",
@@ -188,7 +188,7 @@
   "errors.unexpected_crash.copy_stacktrace": "Sao chép stacktrace vào clipboard",
   "errors.unexpected_crash.report_issue": "Báo cáo lỗi",
   "explore.search_results": "Kết quả tìm kiếm",
-  "explore.suggested_follows": "Đề xuất cho bạn",
+  "explore.suggested_follows": "Dành cho bạn",
   "explore.title": "Khám phá",
   "explore.trending_links": "Tin tức",
   "explore.trending_statuses": "Tút",
@@ -294,7 +294,7 @@
   "navigation_bar.discover": "Khám phá",
   "navigation_bar.domain_blocks": "Máy chủ đã ẩn",
   "navigation_bar.edit_profile": "Trang cá nhân",
-  "navigation_bar.explore": "Khám phá",
+  "navigation_bar.explore": "Xu hướng",
   "navigation_bar.favourites": "Thích",
   "navigation_bar.filters": "Bộ lọc từ ngữ",
   "navigation_bar.follow_requests": "Yêu cầu theo dõi",
@@ -309,7 +309,7 @@
   "navigation_bar.preferences": "Cài đặt",
   "navigation_bar.public_timeline": "Thế giới",
   "navigation_bar.security": "Bảo mật",
-  "notification.admin.sign_up": "{name} vừa đăng ký",
+  "notification.admin.sign_up": "{name} đăng ký máy chủ của bạn",
   "notification.favourite": "{name} thích tút của bạn",
   "notification.follow": "{name} theo dõi bạn",
   "notification.follow_request": "{name} yêu cầu theo dõi bạn",
@@ -367,7 +367,7 @@
   "poll_button.remove_poll": "Hủy cuộc bình chọn",
   "privacy.change": "Thay đổi quyền riêng tư",
   "privacy.direct.long": "Chỉ người được nhắc đến mới thấy",
-  "privacy.direct.short": "Tin nhắn",
+  "privacy.direct.short": "Nhắn riêng",
   "privacy.private.long": "Dành riêng cho người theo dõi",
   "privacy.private.short": "Riêng tư",
   "privacy.public.long": "Hiện trên bảng tin máy chủ",
@@ -449,7 +449,7 @@
   "status.copy": "Sao chép URL",
   "status.delete": "Xóa",
   "status.detailed_status": "Xem chi tiết thêm",
-  "status.direct": "Nhắn tin @{name}",
+  "status.direct": "Nhắn riêng @{name}",
   "status.edit": "Sửa",
   "status.edited": "Đã sửa {date}",
   "status.edited_x_times": "Đã sửa {count, plural, other {{count} lần}}",
@@ -483,7 +483,7 @@
   "status.show_less_all": "Thu gọn toàn bộ",
   "status.show_more": "Xem thêm",
   "status.show_more_all": "Hiển thị tất cả",
-  "status.show_thread": "Toàn chủ đề",
+  "status.show_thread": "Xem chuỗi tút này",
   "status.uncached_media_warning": "Uncached",
   "status.unmute_conversation": "Quan tâm",
   "status.unpin": "Bỏ ghim trên trang cá nhân",
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 40cd899b3..27be22f1b 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -1322,7 +1322,7 @@ a.sparkline {
       width: 50px;
       height: 21px;
       position: absolute;
-      bottom: 8px;
+      bottom: 0;
       right: 15px;
       background: linear-gradient(to left, $ui-base-color, transparent);
       pointer-events: none;
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 295b0ddfb..442ffd2c0 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -596,30 +596,24 @@
         display: flex;
         align-items: flex-start;
         justify-content: space-between;
-        opacity: 0;
-        transition: opacity .1s ease;
+      }
 
-        .icon-button {
-          flex: 0 1 auto;
-          color: $secondary-text-color;
-          font-size: 14px;
-          font-weight: 500;
-          padding: 10px;
-          font-family: inherit;
-
-          &:hover,
-          &:focus,
-          &:active {
-            color: lighten($secondary-text-color, 7%);
-          }
-        }
+      .icon-button {
+        flex: 0 1 auto;
+        color: $secondary-text-color;
+        font-size: 14px;
+        font-weight: 500;
+        padding: 10px;
+        font-family: inherit;
 
-        &.active {
-          opacity: 1;
+        &:hover,
+        &:focus,
+        &:active {
+          color: lighten($secondary-text-color, 7%);
         }
       }
 
-      &-description {
+      &__warning {
         position: absolute;
         z-index: 2;
         bottom: 0;
@@ -627,34 +621,6 @@
         right: 0;
         box-sizing: border-box;
         background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
-        padding: 10px;
-        opacity: 0;
-        transition: opacity .1s ease;
-
-        textarea {
-          background: transparent;
-          color: $secondary-text-color;
-          border: 0;
-          padding: 0;
-          margin: 0;
-          width: 100%;
-          font-family: inherit;
-          font-size: 14px;
-          font-weight: 500;
-
-          &:focus {
-            color: $white;
-          }
-
-          &::placeholder {
-            opacity: 0.75;
-            color: $secondary-text-color;
-          }
-        }
-
-        &.active {
-          opacity: 1;
-        }
       }
     }
 
@@ -870,7 +836,8 @@
   .status__content__spoiler-link {
     background: $action-button-color;
 
-    &:hover {
+    &:hover,
+    &:focus {
       background: lighten($action-button-color, 7%);
       text-decoration: none;
     }
@@ -982,7 +949,7 @@
   text-transform: uppercase;
   line-height: 20px;
   cursor: pointer;
-  vertical-align: middle;
+  vertical-align: top;
 }
 
 .status__wrapper--filtered {
@@ -1072,7 +1039,8 @@
         color: $primary-text-color;
         background: $ui-primary-color;
 
-        &:hover {
+        &:hover,
+        &:focus {
           background: lighten($ui-primary-color, 8%);
         }
       }
@@ -1646,7 +1614,8 @@ a.account__display-name {
     background: $ui-base-lighter-color;
     color: $inverted-text-color;
 
-    &:hover {
+    &:hover,
+    &:focus {
       background: lighten($ui-base-lighter-color, 7%);
       text-decoration: none;
     }
@@ -5158,6 +5127,15 @@ a.status-card.compact:hover {
     color: $inverted-text-color;
   }
 
+  .status__content__spoiler-link {
+    color: $primary-text-color;
+    background: $ui-primary-color;
+
+    &:hover {
+      background: lighten($ui-primary-color, 8%);
+    }
+  }
+
   .dialog-option .poll__input {
     border-color: $inverted-text-color;
     color: $ui-secondary-color;
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index cf31b6ff6..1ac509f18 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class ActivityPub::Activity::Create < ActivityPub::Activity
+  include FormattingHelper
+
   def perform
     dereference_object!
 
@@ -367,7 +369,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   end
 
   def converted_text
-    Formatter.instance.linkify([@status_parser.title.presence, @status_parser.spoiler_text.presence, @status_parser.url || @status_parser.uri].compact.join("\n\n"))
+    linkify([@status_parser.title.presence, @status_parser.spoiler_text.presence, @status_parser.url || @status_parser.uri].compact.join("\n\n"))
   end
 
   def unsupported_media_type?(mime_type)
diff --git a/app/lib/activitypub/parser/media_attachment_parser.rb b/app/lib/activitypub/parser/media_attachment_parser.rb
index 1798e58a4..30bea1f0e 100644
--- a/app/lib/activitypub/parser/media_attachment_parser.rb
+++ b/app/lib/activitypub/parser/media_attachment_parser.rb
@@ -27,7 +27,9 @@ class ActivityPub::Parser::MediaAttachmentParser
   end
 
   def description
-    @json['summary'].presence || @json['name'].presence
+    str = @json['summary'].presence || @json['name'].presence
+    str = str.strip[0...MediaAttachment::MAX_DESCRIPTION_LENGTH] if str.present?
+    str
   end
 
   def focus
diff --git a/app/lib/admin/system_check.rb b/app/lib/admin/system_check.rb
index afb20cb47..877a42ef6 100644
--- a/app/lib/admin/system_check.rb
+++ b/app/lib/admin/system_check.rb
@@ -5,6 +5,7 @@ class Admin::SystemCheck
     Admin::SystemCheck::DatabaseSchemaCheck,
     Admin::SystemCheck::SidekiqProcessCheck,
     Admin::SystemCheck::RulesCheck,
+    Admin::SystemCheck::ElasticsearchCheck,
   ].freeze
 
   def self.perform
diff --git a/app/lib/admin/system_check/elasticsearch_check.rb b/app/lib/admin/system_check/elasticsearch_check.rb
new file mode 100644
index 000000000..1b48a5415
--- /dev/null
+++ b/app/lib/admin/system_check/elasticsearch_check.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck
+  def pass?
+    return true unless Chewy.enabled?
+
+    running_version.present? && compatible_version?
+  end
+
+  def message
+    if running_version.present?
+      Admin::SystemCheck::Message.new(:elasticsearch_version_check, I18n.t('admin.system_checks.elasticsearch_version_check.version_comparison', running_version: running_version, required_version: required_version))
+    else
+      Admin::SystemCheck::Message.new(:elasticsearch_running_check)
+    end
+  end
+
+  private
+
+  def running_version
+    @running_version ||= begin
+      Chewy.client.info['version']['number']
+    rescue Faraday::ConnectionFailed
+      nil
+    end
+  end
+
+  def required_version
+    '7.x'
+  end
+
+  def compatible_version?
+    Gem::Version.new(running_version) >= Gem::Version.new(required_version)
+  end
+
+  def missing_queues
+    @missing_queues ||= Sidekiq::ProcessSet.new.reduce(SIDEKIQ_QUEUES) { |queues, process| queues - process['queues'] }
+  end
+end
diff --git a/app/lib/advanced_text_formatter.rb b/app/lib/advanced_text_formatter.rb
new file mode 100644
index 000000000..728400819
--- /dev/null
+++ b/app/lib/advanced_text_formatter.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+class AdvancedTextFormatter < TextFormatter
+  class HTMLRenderer < Redcarpet::Render::HTML
+    def initialize(options, &block)
+      super(options)
+      @format_link = block
+    end
+
+    def block_code(code, _language)
+      <<~HTML.squish
+        <pre><code>#{ERB::Util.h(code).gsub("\n", '<br/>')}</code></pre>
+      HTML
+    end
+
+    def autolink(link, link_type)
+      return link if link_type == :email
+      @format_link.call(link)
+    end
+  end
+
+  # @param [String] text
+  # @param [Hash] options
+  # @option options [Boolean] :multiline
+  # @option options [Boolean] :with_domains
+  # @option options [Boolean] :with_rel_me
+  # @option options [Array<Account>] :preloaded_accounts
+  # @option options [String] :content_type
+  def initialize(text, options = {})
+    content_type = options.delete(:content_type)
+    super(text, options)
+
+    @text = format_markdown(text) if content_type == 'text/markdown'
+  end
+
+  # Differs from TextFormatter by not messing with newline after parsing
+  def to_s
+    return ''.html_safe if text.blank?
+
+    html = rewrite do |entity|
+      if entity[:url]
+        link_to_url(entity)
+      elsif entity[:hashtag]
+        link_to_hashtag(entity)
+      elsif entity[:screen_name]
+        link_to_mention(entity)
+      end
+    end
+
+    html.html_safe # rubocop:disable Rails/OutputSafety
+  end
+
+  # Differs from `TextFormatter` by skipping HTML tags and entities
+  def entities
+    @entities ||= begin
+      gaps = []
+      total_offset = 0
+
+      escaped = text.gsub(/<[^>]*>|&#[0-9]+;/) do |match|
+        total_offset += match.length - 1
+        end_offset = Regexp.last_match.end(0)
+        gaps << [end_offset - total_offset, total_offset]
+        ' '
+      end
+
+      Extractor.extract_entities_with_indices(escaped, extract_url_without_protocol: false).map do |entity|
+        start_pos, end_pos = entity[:indices]
+        offset_idx = gaps.rindex { |gap| gap.first <= start_pos }
+        offset = offset_idx.nil? ? 0 : gaps[offset_idx].last
+        entity.merge(indices: [start_pos + offset, end_pos + offset])
+      end
+    end
+  end
+
+  private
+
+  # Differs from `TextFormatter` in that it keeps HTML; but it sanitizes at the end to remain safe
+  def rewrite
+    entities.sort_by! do |entity|
+      entity[:indices].first
+    end
+
+    result = ''.dup
+
+    last_index = entities.reduce(0) do |index, entity|
+      indices = entity[:indices]
+      result << text[index...indices.first]
+      result << yield(entity)
+      indices.last
+    end
+
+    result << text[last_index..-1]
+
+    Sanitize.fragment(result, Sanitize::Config::MASTODON_OUTGOING)
+  end
+
+  def format_markdown(html)
+    html = markdown_formatter.render(html)
+    html.delete("\r").delete("\n")
+  end
+
+  def markdown_formatter
+    extensions = {
+      autolink: true,
+      no_intra_emphasis: true,
+      fenced_code_blocks: true,
+      disable_indented_code_blocks: true,
+      strikethrough: true,
+      lax_spacing: true,
+      space_after_headers: true,
+      superscript: true,
+      underline: true,
+      highlight: true,
+      footnotes: false,
+    }
+
+    renderer = HTMLRenderer.new({
+      filter_html: false,
+      escape_html: false,
+      no_images: true,
+      no_styles: true,
+      safe_links_only: true,
+      hard_wrap: true,
+      link_attributes: { target: '_blank', rel: 'nofollow noopener' },
+    }) do |url|
+      link_to_url({ url: url })
+    end
+
+    Redcarpet::Markdown.new(renderer, extensions)
+  end
+end
diff --git a/app/lib/emoji_formatter.rb b/app/lib/emoji_formatter.rb
new file mode 100644
index 000000000..f808f3a22
--- /dev/null
+++ b/app/lib/emoji_formatter.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+class EmojiFormatter
+  include RoutingHelper
+
+  DISALLOWED_BOUNDING_REGEX = /[[:alnum:]:]/.freeze
+
+  attr_reader :html, :custom_emojis, :options
+
+  # @param [ActiveSupport::SafeBuffer] html
+  # @param [Array<CustomEmoji>] custom_emojis
+  # @param [Hash] options
+  # @option options [Boolean] :animate
+  def initialize(html, custom_emojis, options = {})
+    raise ArgumentError unless html.html_safe?
+
+    @html = html
+    @custom_emojis = custom_emojis
+    @options = options
+  end
+
+  def to_s
+    return html if custom_emojis.empty? || html.blank?
+
+    i                     = -1
+    tag_open_index        = nil
+    inside_shortname      = false
+    shortname_start_index = -1
+    invisible_depth       = 0
+    last_index            = 0
+    result                = ''.dup
+
+    while i + 1 < html.size
+      i += 1
+
+      if invisible_depth.zero? && inside_shortname && html[i] == ':'
+        inside_shortname = false
+        shortcode = html[shortname_start_index + 1..i - 1]
+        char_after = html[i + 1]
+
+        next unless (char_after.nil? || !DISALLOWED_BOUNDING_REGEX.match?(char_after)) && (emoji = emoji_map[shortcode])
+
+        result << html[last_index..shortname_start_index - 1] if shortname_start_index.positive?
+        result << image_for_emoji(shortcode, emoji)
+        last_index = i + 1
+      elsif tag_open_index && html[i] == '>'
+        tag = html[tag_open_index..i]
+        tag_open_index = nil
+
+        if invisible_depth.positive?
+          invisible_depth += count_tag_nesting(tag)
+        elsif tag == '<span class="invisible">'
+          invisible_depth = 1
+        end
+      elsif html[i] == '<'
+        tag_open_index = i
+        inside_shortname = false
+      elsif !tag_open_index && html[i] == ':' && (i.zero? || !DISALLOWED_BOUNDING_REGEX.match?(html[i - 1]))
+        inside_shortname = true
+        shortname_start_index = i
+      end
+    end
+
+    result << html[last_index..-1]
+
+    result.html_safe # rubocop:disable Rails/OutputSafety
+  end
+
+  private
+
+  def emoji_map
+    @emoji_map ||= custom_emojis.each_with_object({}) { |e, h| h[e.shortcode] = [full_asset_url(e.image.url), full_asset_url(e.image.url(:static))] }
+  end
+
+  def count_tag_nesting(tag)
+    if tag[1] == '/'
+      -1
+    elsif tag[-2] == '/'
+      0
+    else
+      1
+    end
+  end
+
+  def image_for_emoji(shortcode, emoji)
+    original_url, static_url = emoji
+
+    if animate?
+      image_tag(original_url, draggable: false, class: 'emojione', alt: ":#{shortcode}:", title: ":#{shortcode}:")
+    else
+      image_tag(original_url, draggable: false, class: 'emojione custom-emoji', alt: ":#{shortcode}:", title: ":#{shortcode}:", data: { original: original_url, static: static_url })
+    end
+  end
+
+  def animate?
+    @options[:animate]
+  end
+end
diff --git a/app/lib/extractor.rb b/app/lib/extractor.rb
index 8020aa916..aea60dae5 100644
--- a/app/lib/extractor.rb
+++ b/app/lib/extractor.rb
@@ -1,22 +1,44 @@
 # frozen_string_literal: true
 
 module Extractor
+  MAX_DOMAIN_LENGTH = 253
+
   extend Twitter::TwitterText::Extractor
 
   module_function
 
-  # :yields: username, list_slug, start, end
+  def extract_entities_with_indices(text, options = {}, &block)
+    entities = begin
+      extract_urls_with_indices(text, options) +
+        extract_hashtags_with_indices(text, check_url_overlap: false) +
+        extract_mentions_or_lists_with_indices(text) +
+        extract_extra_uris_with_indices(text)
+    end
+
+    return [] if entities.empty?
+
+    entities = remove_overlapping_entities(entities)
+    entities.each(&block) if block_given?
+    entities
+  end
+
   def extract_mentions_or_lists_with_indices(text)
-    return [] unless Twitter::TwitterText::Regex[:at_signs].match?(text)
+    return [] unless text && Twitter::TwitterText::Regex[:at_signs].match?(text)
 
     possible_entries = []
 
-    text.to_s.scan(Account::MENTION_RE) do |screen_name, _|
+    text.scan(Account::MENTION_RE) do |screen_name, _|
       match_data = $LAST_MATCH_INFO
-      after = $'
+      after      = $'
+
       unless Twitter::TwitterText::Regex[:end_mention_match].match?(after)
+        _, domain = screen_name.split('@')
+
+        next if domain.present? && domain.length > MAX_DOMAIN_LENGTH
+
         start_position = match_data.char_begin(1) - 1
-        end_position = match_data.char_end(1)
+        end_position   = match_data.char_end(1)
+
         possible_entries << {
           screen_name: screen_name,
           indices: [start_position, end_position],
@@ -29,36 +51,70 @@ module Extractor
         yield mention[:screen_name], mention[:indices].first, mention[:indices].last
       end
     end
+
     possible_entries
   end
 
-  def extract_hashtags_with_indices(text, **)
-    return [] unless /#/.match?(text)
+  def extract_hashtags_with_indices(text, _options = {})
+    return [] unless text&.index('#')
+
+    possible_entries = []
 
-    tags = []
     text.scan(Tag::HASHTAG_RE) do |hash_text, _|
-      match_data = $LAST_MATCH_INFO
+      match_data     = $LAST_MATCH_INFO
       start_position = match_data.char_begin(1) - 1
-      end_position = match_data.char_end(1)
-      after = $'
+      end_position   = match_data.char_end(1)
+      after          = $'
+
       if %r{\A://}.match?(after)
         hash_text.match(/(.+)(https?\Z)/) do |matched|
-          hash_text = matched[1]
+          hash_text     = matched[1]
           end_position -= matched[2].codepoint_length
         end
       end
 
-      tags << {
+      possible_entries << {
         hashtag: hash_text,
         indices: [start_position, end_position],
       }
     end
 
-    tags.each { |tag| yield tag[:hashtag], tag[:indices].first, tag[:indices].last } if block_given?
-    tags
+    if block_given?
+      possible_entries.each do |tag|
+        yield tag[:hashtag], tag[:indices].first, tag[:indices].last
+      end
+    end
+
+    possible_entries
   end
 
   def extract_cashtags_with_indices(_text)
-    [] # always returns empty array
+    []
+  end
+
+  def extract_extra_uris_with_indices(text)
+    return [] unless text&.index(':')
+
+    possible_entries = []
+
+    text.scan(Twitter::TwitterText::Regex[:valid_extended_uri]) do
+      valid_uri_match_data = $LAST_MATCH_INFO
+
+      start_position = valid_uri_match_data.char_begin(3)
+      end_position   = valid_uri_match_data.char_end(3)
+
+      possible_entries << {
+        url: valid_uri_match_data[3],
+        indices: [start_position, end_position],
+      }
+    end
+
+    if block_given?
+      possible_entries.each do |url|
+        yield url[:url], url[:indices].first, url[:indices].last
+      end
+    end
+
+    possible_entries
   end
 end
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index efc9da34b..6994f00ae 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -5,6 +5,7 @@ require 'singleton'
 class FeedManager
   include Singleton
   include Redisable
+  include FormattingHelper
 
   # Maximum number of items stored in a single feed
   MAX_ITEMS = 400
@@ -503,7 +504,7 @@ class FeedManager
     status         = status.reblog if status.reblog?
 
     combined_text = [
-      Formatter.instance.plaintext(status),
+      extract_status_plain_text(status),
       status.spoiler_text,
       status.preloadable_poll ? status.preloadable_poll.options.join("\n\n") : nil,
       status.ordered_media_attachments.map(&:description).join("\n\n"),
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
deleted file mode 100644
index dfa493ed5..000000000
--- a/app/lib/formatter.rb
+++ /dev/null
@@ -1,382 +0,0 @@
-# frozen_string_literal: true
-
-require 'singleton'
-
-class HTMLRenderer < Redcarpet::Render::HTML
-  def block_code(code, language)
-    "<pre><code>#{encode(code).gsub("\n", "<br/>")}</code></pre>"
-  end
-
-  def autolink(link, link_type)
-    return link if link_type == :email
-    Formatter.instance.link_url(link)
-  rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
-    encode(link)
-  end
-
-  private
-
-  def html_entities
-    @html_entities ||= HTMLEntities.new
-  end
-
-  def encode(html)
-    html_entities.encode(html)
-  end
-end
-
-class Formatter
-  include Singleton
-  include RoutingHelper
-
-  include ActionView::Helpers::TextHelper
-
-  def format(status, **options)
-    if status.respond_to?(:reblog?) && status.reblog?
-      prepend_reblog = status.reblog.account.acct
-      status         = status.proper
-    else
-      prepend_reblog = false
-    end
-
-    raw_content = status.text
-
-    if options[:inline_poll_options] && status.preloadable_poll
-      raw_content = raw_content + "\n\n" + status.preloadable_poll.options.map { |title| "[ ] #{title}" }.join("\n")
-    end
-
-    return '' if raw_content.blank?
-
-    unless status.local?
-      html = reformat(raw_content)
-      html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
-      return html.html_safe # rubocop:disable Rails/OutputSafety
-    end
-
-    linkable_accounts = status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []
-    linkable_accounts << status.account
-
-    html = raw_content
-    html = "RT @#{prepend_reblog} #{html}" if prepend_reblog
-    html = format_markdown(html) if status.content_type == 'text/markdown'
-    html = encode_and_link_urls(html, linkable_accounts, keep_html: %w(text/markdown text/html).include?(status.content_type))
-    html = reformat(html, true) if %w(text/markdown text/html).include?(status.content_type)
-    html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
-
-    unless %w(text/markdown text/html).include?(status.content_type)
-      html = simple_format(html, {}, sanitize: false)
-      html = html.delete("\n")
-    end
-
-    html.html_safe # rubocop:disable Rails/OutputSafety
-  end
-
-  def format_markdown(html)
-    html = markdown_formatter.render(html)
-    html.delete("\r").delete("\n")
-  end
-
-  def reformat(html, outgoing = false)
-    sanitize(html, Sanitize::Config::MASTODON_STRICT.merge(outgoing: outgoing))
-  rescue ArgumentError
-    ''
-  end
-
-  def plaintext(status)
-    return status.text if status.local?
-
-    text = status.text.gsub(/(<br \/>|<br>|<\/p>)+/) { |match| "#{match}\n" }
-    strip_tags(text)
-  end
-
-  def simplified_format(account, **options)
-    return '' if account.note.blank?
-
-    html = account.local? ? linkify(account.note) : reformat(account.note)
-    html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
-    html.html_safe # rubocop:disable Rails/OutputSafety
-  end
-
-  def sanitize(html, config)
-    Sanitize.fragment(html, config)
-  end
-
-  def format_spoiler(status, **options)
-    html = encode(status.spoiler_text)
-    html = encode_custom_emojis(html, status.emojis, options[:autoplay])
-    html.html_safe # rubocop:disable Rails/OutputSafety
-  end
-
-  def format_poll_option(status, option, **options)
-    html = encode(option.title)
-    html = encode_custom_emojis(html, status.emojis, options[:autoplay])
-    html.html_safe # rubocop:disable Rails/OutputSafety
-  end
-
-  def format_display_name(account, **options)
-    html = encode(account.display_name.presence || account.username)
-    html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
-    html.html_safe # rubocop:disable Rails/OutputSafety
-  end
-
-  def format_field(account, str, **options)
-    html = account.local? ? encode_and_link_urls(str, me: true, with_domain: true) : reformat(str)
-    html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
-    html.html_safe # rubocop:disable Rails/OutputSafety
-  end
-
-  def linkify(text)
-    html = encode_and_link_urls(text)
-    html = simple_format(html, {}, sanitize: false)
-    html = html.delete("\n")
-
-    html.html_safe # rubocop:disable Rails/OutputSafety
-  end
-
-  def link_url(url)
-    "<a href=\"#{encode(url)}\" target=\"blank\" rel=\"nofollow noopener noreferrer\">#{link_html(url)}</a>"
-  end
-
-  private
-
-  def markdown_formatter
-    extensions = {
-      autolink: true,
-      no_intra_emphasis: true,
-      fenced_code_blocks: true,
-      disable_indented_code_blocks: true,
-      strikethrough: true,
-      lax_spacing: true,
-      space_after_headers: true,
-      superscript: true,
-      underline: true,
-      highlight: true,
-      footnotes: false,
-    }
-
-    renderer = HTMLRenderer.new({
-      filter_html: false,
-      escape_html: false,
-      no_images: true,
-      no_styles: true,
-      safe_links_only: true,
-      hard_wrap: true,
-      link_attributes: { target: '_blank', rel: 'nofollow noopener' },
-    })
-
-    Redcarpet::Markdown.new(renderer, extensions)
-  end
-
-  def html_entities
-    @html_entities ||= HTMLEntities.new
-  end
-
-  def encode(html)
-    html_entities.encode(html)
-  end
-
-  def encode_and_link_urls(html, accounts = nil, options = {})
-    if accounts.is_a?(Hash)
-      options  = accounts
-      accounts = nil
-    end
-
-    entities = options[:keep_html] ? html_friendly_extractor(html) : utf8_friendly_extractor(html, extract_url_without_protocol: false)
-
-    rewrite(html.dup, entities, options[:keep_html]) do |entity|
-      if entity[:url]
-        link_to_url(entity, options)
-      elsif entity[:hashtag]
-        link_to_hashtag(entity)
-      elsif entity[:screen_name]
-        link_to_mention(entity, accounts, options)
-      end
-    end
-  end
-
-  def count_tag_nesting(tag)
-    if tag[1] == '/' then -1
-    elsif tag[-2] == '/' then 0
-    else 1
-    end
-  end
-
-  # rubocop:disable Metrics/BlockNesting
-  def encode_custom_emojis(html, emojis, animate = false)
-    return html if emojis.empty?
-
-    emoji_map = emojis.each_with_object({}) { |e, h| h[e.shortcode] = [full_asset_url(e.image.url), full_asset_url(e.image.url(:static))] }
-
-    i                     = -1
-    tag_open_index        = nil
-    inside_shortname      = false
-    shortname_start_index = -1
-    invisible_depth       = 0
-
-    while i + 1 < html.size
-      i += 1
-
-      if invisible_depth.zero? && inside_shortname && html[i] == ':'
-        shortcode = html[shortname_start_index + 1..i - 1]
-        emoji     = emoji_map[shortcode]
-
-        if emoji
-          original_url, static_url = emoji
-          replacement = begin
-            if animate
-              image_tag(original_url, draggable: false, class: 'emojione', alt: ":#{shortcode}:", title: ":#{shortcode}:")
-            else
-              image_tag(original_url, draggable: false, class: 'emojione custom-emoji', alt: ":#{shortcode}:", title: ":#{shortcode}:", data: { original: original_url, static: static_url })
-            end
-          end
-          before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : ''
-          html        = before_html + replacement + html[i + 1..-1]
-          i          += replacement.size - (shortcode.size + 2) - 1
-        else
-          i -= 1
-        end
-
-        inside_shortname = false
-      elsif tag_open_index && html[i] == '>'
-        tag = html[tag_open_index..i]
-        tag_open_index = nil
-        if invisible_depth.positive?
-          invisible_depth += count_tag_nesting(tag)
-        elsif tag == '<span class="invisible">'
-          invisible_depth = 1
-        end
-      elsif html[i] == '<'
-        tag_open_index   = i
-        inside_shortname = false
-      elsif !tag_open_index && html[i] == ':'
-        inside_shortname      = true
-        shortname_start_index = i
-      end
-    end
-
-    html
-  end
-  # rubocop:enable Metrics/BlockNesting
-
-  def rewrite(text, entities, keep_html = false)
-    text = text.to_s
-
-    # Sort by start index
-    entities = entities.sort_by do |entity|
-      indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices]
-      indices.first
-    end
-
-    result = []
-
-    last_index = entities.reduce(0) do |index, entity|
-      indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices]
-      result << (keep_html ? text[index...indices.first] : encode(text[index...indices.first]))
-      result << yield(entity)
-      indices.last
-    end
-
-    result << (keep_html ? text[last_index..-1] : encode(text[last_index..-1]))
-
-    result.flatten.join
-  end
-
-  def utf8_friendly_extractor(text, options = {})
-    # Note: I couldn't obtain list_slug with @user/list-name format
-    # for mention so this requires additional check
-    special = Extractor.extract_urls_with_indices(text, options)
-    standard = Extractor.extract_entities_with_indices(text, options)
-    extra = Extractor.extract_extra_uris_with_indices(text, options)
-
-    Extractor.remove_overlapping_entities(special + standard + extra)
-  end
-
-  def html_friendly_extractor(html, options = {})
-    gaps = []
-    total_offset = 0
-
-    escaped = html.gsub(/<[^>]*>|&#[0-9]+;/) do |match|
-      total_offset += match.length - 1
-      end_offset = Regexp.last_match.end(0)
-      gaps << [end_offset - total_offset, total_offset]
-      "\u200b"
-    end
-
-    entities = Extractor.extract_hashtags_with_indices(escaped, :check_url_overlap => false) +
-               Extractor.extract_mentions_or_lists_with_indices(escaped)
-    Extractor.remove_overlapping_entities(entities).map do |extract|
-      pos = extract[:indices].first
-      offset_idx = gaps.rindex { |gap| gap.first <= pos }
-      offset = offset_idx.nil? ? 0 : gaps[offset_idx].last
-      next extract.merge(
-        :indices => [extract[:indices].first + offset, extract[:indices].last + offset]
-      )
-    end
-  end
-
-  def link_to_url(entity, options = {})
-    url        = Addressable::URI.parse(entity[:url])
-    html_attrs = { target: '_blank', rel: 'nofollow noopener noreferrer' }
-
-    html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me]
-
-    Twitter::TwitterText::Autolink.send(:link_to_text, entity, link_html(entity[:url]), url, html_attrs)
-  rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
-    encode(entity[:url])
-  end
-
-  def link_to_mention(entity, linkable_accounts, options = {})
-    acct = entity[:screen_name]
-
-    return link_to_account(acct, options) unless linkable_accounts
-
-    same_username_hits = 0
-    account = nil
-    username, domain = acct.split('@')
-    domain = nil if TagManager.instance.local_domain?(domain)
-
-    linkable_accounts.each do |item|
-      same_username = item.username.casecmp(username).zero?
-      same_domain   = item.domain.nil? ? domain.nil? : item.domain.casecmp(domain)&.zero?
-
-      if same_username && !same_domain
-        same_username_hits += 1
-      elsif same_username && same_domain
-        account = item
-      end
-    end
-
-    account ? mention_html(account, with_domain: same_username_hits.positive? || options[:with_domain]) : "@#{encode(acct)}"
-  end
-
-  def link_to_account(acct, options = {})
-    username, domain = acct.split('@')
-
-    domain  = nil if TagManager.instance.local_domain?(domain)
-    account = EntityCache.instance.mention(username, domain)
-
-    account ? mention_html(account, with_domain: options[:with_domain]) : "@#{encode(acct)}"
-  end
-
-  def link_to_hashtag(entity)
-    hashtag_html(entity[:hashtag])
-  end
-
-  def link_html(url)
-    url    = Addressable::URI.parse(url).to_s
-    prefix = url.match(/\A(https?:\/\/(www\.)?|xmpp:)/).to_s
-    text   = url[prefix.length, 30]
-    suffix = url[prefix.length + 30..-1]
-    cutoff = url[prefix.length..-1].length > 30
-
-    "<span class=\"invisible\">#{encode(prefix)}</span><span class=\"#{cutoff ? 'ellipsis' : ''}\">#{encode(text)}</span><span class=\"invisible\">#{encode(suffix)}</span>"
-  end
-
-  def hashtag_html(tag)
-    "<a href=\"#{encode(tag_url(tag))}\" class=\"mention hashtag\" rel=\"tag\">#<span>#{encode(tag)}</span></a>"
-  end
-
-  def mention_html(account, with_domain: false)
-    "<span class=\"h-card\"><a href=\"#{encode(ActivityPub::TagManager.instance.url_for(account))}\" class=\"u-url mention\">@<span>#{encode(with_domain ? account.pretty_acct : account.username)}</span></a></span>"
-  end
-end
diff --git a/app/lib/html_aware_formatter.rb b/app/lib/html_aware_formatter.rb
new file mode 100644
index 000000000..7a1cd0340
--- /dev/null
+++ b/app/lib/html_aware_formatter.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class HtmlAwareFormatter
+  attr_reader :text, :local, :options
+
+  alias local? local
+
+  # @param [String] text
+  # @param [Boolean] local
+  # @param [Hash] options
+  def initialize(text, local, options = {})
+    @text    = text
+    @local   = local
+    @options = options
+  end
+
+  def to_s
+    return ''.html_safe if text.blank?
+
+    if local?
+      linkify
+    else
+      reformat.html_safe # rubocop:disable Rails/OutputSafety
+    end
+  rescue ArgumentError
+    ''.html_safe
+  end
+
+  private
+
+  def reformat
+    Sanitize.fragment(text, Sanitize::Config::MASTODON_STRICT)
+  end
+
+  def linkify
+    if %w(text/markdown text/html).include?(@options[:content_type])
+      AdvancedTextFormatter.new(text, options).to_s
+    else
+      TextFormatter.new(text, options).to_s
+    end
+  end
+end
diff --git a/app/lib/link_details_extractor.rb b/app/lib/link_details_extractor.rb
index fabbd244d..b0c4e4f42 100644
--- a/app/lib/link_details_extractor.rb
+++ b/app/lib/link_details_extractor.rb
@@ -208,7 +208,7 @@ class LinkDetailsExtractor
   end
 
   def valid_url_or_nil(str, same_origin_only: false)
-    return if str.blank?
+    return if str.blank? || str == 'null'
 
     url = @original_url + Addressable::URI.parse(str)
 
diff --git a/app/lib/plain_text_formatter.rb b/app/lib/plain_text_formatter.rb
new file mode 100644
index 000000000..08aa29696
--- /dev/null
+++ b/app/lib/plain_text_formatter.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class PlainTextFormatter
+  include ActionView::Helpers::TextHelper
+
+  NEWLINE_TAGS_RE = /(<br \/>|<br>|<\/p>)+/.freeze
+
+  attr_reader :text, :local
+
+  alias local? local
+
+  def initialize(text, local)
+    @text  = text
+    @local = local
+  end
+
+  def to_s
+    if local?
+      text
+    else
+      strip_tags(insert_newlines).chomp
+    end
+  end
+
+  private
+
+  def insert_newlines
+    text.gsub(NEWLINE_TAGS_RE) { |match| "#{match}\n" }
+  end
+end
diff --git a/app/lib/rss/serializer.rb b/app/lib/rss/serializer.rb
index 7e3ed1f17..d44e94221 100644
--- a/app/lib/rss/serializer.rb
+++ b/app/lib/rss/serializer.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class RSS::Serializer
+  include FormattingHelper
+
   private
 
   def render_statuses(builder, statuses)
@@ -9,7 +11,7 @@ class RSS::Serializer
         item.title(status_title(status))
             .link(ActivityPub::TagManager.instance.url_for(status))
             .pub_date(status.created_at)
-            .description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str)
+            .description(status_description(status))
 
         status.ordered_media_attachments.each do |media|
           item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
@@ -19,9 +21,8 @@ class RSS::Serializer
   end
 
   def status_title(status)
-    return "#{status.account.acct} deleted status" if status.destroyed?
-
     preview = status.proper.spoiler_text.presence || status.proper.text
+
     if preview.length > 30 || preview[0, 30].include?("\n")
       preview = preview[0, 30]
       preview = preview[0, preview.index("\n").presence || 30] + '…'
@@ -35,4 +36,20 @@ class RSS::Serializer
       "#{status.account.acct}: #{preview}"
     end
   end
+
+  def status_description(status)
+    if status.proper.spoiler_text?
+      status.proper.spoiler_text
+    else
+      html = status_content_format(status.proper).to_str
+      after_html = ''
+
+      if status.proper.preloadable_poll
+        poll_options_html = status.proper.preloadable_poll.options.map { |o| "[ ] #{o}" }.join('<br />')
+        after_html = "<p>#{poll_options_html}</p>"
+      end
+
+      "#{html}#{after_html}"
+    end
+  end
 end
diff --git a/app/lib/text_formatter.rb b/app/lib/text_formatter.rb
new file mode 100644
index 000000000..48e2fc233
--- /dev/null
+++ b/app/lib/text_formatter.rb
@@ -0,0 +1,158 @@
+# frozen_string_literal: true
+
+class TextFormatter
+  include ActionView::Helpers::TextHelper
+  include ERB::Util
+  include RoutingHelper
+
+  URL_PREFIX_REGEX = /\A(https?:\/\/(www\.)?|xmpp:)/.freeze
+
+  DEFAULT_REL = %w(nofollow noopener noreferrer).freeze
+
+  DEFAULT_OPTIONS = {
+    multiline: true,
+  }.freeze
+
+  attr_reader :text, :options
+
+  # @param [String] text
+  # @param [Hash] options
+  # @option options [Boolean] :multiline
+  # @option options [Boolean] :with_domains
+  # @option options [Boolean] :with_rel_me
+  # @option options [Array<Account>] :preloaded_accounts
+  def initialize(text, options = {})
+    @text    = text
+    @options = DEFAULT_OPTIONS.merge(options)
+  end
+
+  def entities
+    @entities ||= Extractor.extract_entities_with_indices(text, extract_url_without_protocol: false)
+  end
+
+  def to_s
+    return ''.html_safe if text.blank?
+
+    html = rewrite do |entity|
+      if entity[:url]
+        link_to_url(entity)
+      elsif entity[:hashtag]
+        link_to_hashtag(entity)
+      elsif entity[:screen_name]
+        link_to_mention(entity)
+      end
+    end
+
+    html = simple_format(html, {}, sanitize: false).delete("\n") if multiline?
+
+    html.html_safe # rubocop:disable Rails/OutputSafety
+  end
+
+  private
+
+  def rewrite
+    entities.sort_by! do |entity|
+      entity[:indices].first
+    end
+
+    result = ''.dup
+
+    last_index = entities.reduce(0) do |index, entity|
+      indices = entity[:indices]
+      result << h(text[index...indices.first])
+      result << yield(entity)
+      indices.last
+    end
+
+    result << h(text[last_index..-1])
+
+    result
+  end
+
+  def link_to_url(entity)
+    url = Addressable::URI.parse(entity[:url]).to_s
+    rel = with_rel_me? ? (DEFAULT_REL + %w(me)) : DEFAULT_REL
+
+    prefix      = url.match(URL_PREFIX_REGEX).to_s
+    display_url = url[prefix.length, 30]
+    suffix      = url[prefix.length + 30..-1]
+    cutoff      = url[prefix.length..-1].length > 30
+
+    <<~HTML.squish
+      <a href="#{h(url)}" target="_blank" rel="#{rel.join(' ')}"><span class="invisible">#{h(prefix)}</span><span class="#{cutoff ? 'ellipsis' : ''}">#{h(display_url)}</span><span class="invisible">#{h(suffix)}</span></a>
+    HTML
+  rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
+    h(entity[:url])
+  end
+
+  def link_to_hashtag(entity)
+    hashtag = entity[:hashtag]
+    url     = tag_url(hashtag)
+
+    <<~HTML.squish
+      <a href="#{h(url)}" class="mention hashtag" rel="tag">#<span>#{h(hashtag)}</span></a>
+    HTML
+  end
+
+  def link_to_mention(entity)
+    username, domain = entity[:screen_name].split('@')
+    domain           = nil if local_domain?(domain)
+    account          = nil
+
+    if preloaded_accounts?
+      same_username_hits = 0
+
+      preloaded_accounts.each do |other_account|
+        same_username = other_account.username.casecmp(username).zero?
+        same_domain   = other_account.domain.nil? ? domain.nil? : other_account.domain.casecmp(domain)&.zero?
+
+        if same_username && !same_domain
+          same_username_hits += 1
+        elsif same_username && same_domain
+          account = other_account
+        end
+      end
+    else
+      account = entity_cache.mention(username, domain)
+    end
+
+    return "@#{h(entity[:screen_name])}" if account.nil?
+
+    url = ActivityPub::TagManager.instance.url_for(account)
+    display_username = same_username_hits&.positive? || with_domains? ? account.pretty_acct : account.username
+
+    <<~HTML.squish
+      <span class="h-card"><a href="#{h(url)}" class="u-url mention">@<span>#{h(display_username)}</span></a></span>
+    HTML
+  end
+
+  def entity_cache
+    @entity_cache ||= EntityCache.instance
+  end
+
+  def tag_manager
+    @tag_manager ||= TagManager.instance
+  end
+
+  delegate :local_domain?, to: :tag_manager
+
+  def multiline?
+    options[:multiline]
+  end
+
+  def with_domains?
+    options[:with_domains]
+  end
+
+  def with_rel_me?
+    options[:with_rel_me]
+  end
+
+  def preloaded_accounts
+    options[:preloaded_accounts]
+  end
+
+  def preloaded_accounts?
+    preloaded_accounts.present?
+  end
+end
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
index cc585c3b7..a37682eca 100644
--- a/app/mailers/application_mailer.rb
+++ b/app/mailers/application_mailer.rb
@@ -5,6 +5,7 @@ class ApplicationMailer < ActionMailer::Base
 
   helper :application
   helper :instance
+  helper :formatting
 
   protected
 
diff --git a/app/models/account.rb b/app/models/account.rb
index dfbe0b8bc..1966c5a48 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -132,13 +132,13 @@ class Account < ApplicationRecord
            :approved?,
            :pending?,
            :disabled?,
+           :unconfirmed?,
            :unconfirmed_or_pending?,
            :role,
            :admin?,
            :moderator?,
            :staff?,
            :locale,
-           :hides_network?,
            :shows_application?,
            to: :user,
            prefix: true,
diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb
index 9da1522dd..ec309ce09 100644
--- a/app/models/account_filter.rb
+++ b/app/models/account_filter.rb
@@ -80,6 +80,10 @@ class AccountFilter
       accounts_with_users.merge(User.pending)
     when 'suspended'
       Account.suspended
+    when 'disabled'
+      accounts_with_users.merge(User.disabled)
+    when 'silenced'
+      Account.silenced
     else
       raise "Unknown status: #{value}"
     end
diff --git a/app/models/concerns/status_snapshot_concern.rb b/app/models/concerns/status_snapshot_concern.rb
new file mode 100644
index 000000000..c728db7c3
--- /dev/null
+++ b/app/models/concerns/status_snapshot_concern.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module StatusSnapshotConcern
+  extend ActiveSupport::Concern
+
+  included do
+    has_many :edits, class_name: 'StatusEdit', inverse_of: :status, dependent: :destroy
+  end
+
+  def edited?
+    edited_at.present?
+  end
+
+  def build_snapshot(account_id: nil, at_time: nil, rate_limit: true)
+    # We don't use `edits#new` here to avoid it having saved when the
+    # status is saved, since we want to control that manually
+
+    StatusEdit.new(
+      status_id: id,
+      text: text,
+      spoiler_text: spoiler_text,
+      sensitive: sensitive,
+      ordered_media_attachment_ids: ordered_media_attachment_ids&.dup || media_attachments.pluck(:id),
+      media_descriptions: ordered_media_attachments.map(&:description),
+      poll_options: preloadable_poll&.options&.dup,
+      account_id: account_id || self.account_id,
+      content_type: content_type,
+      created_at: at_time || edited_at,
+      rate_limit: rate_limit
+    )
+  end
+
+  def snapshot!(**options)
+    build_snapshot(**options).save!
+  end
+end
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 4b38d729e..a5ce1e837 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -185,7 +185,7 @@ class MediaAttachment < ApplicationRecord
   remotable_attachment :thumbnail, IMAGE_LIMIT, suppress_errors: true, download_on_assign: false
 
   validates :account, presence: true
-  validates :description, length: { maximum: MAX_DESCRIPTION_LENGTH }, if: :local?
+  validates :description, length: { maximum: MAX_DESCRIPTION_LENGTH }
   validates :file, presence: true, if: :local?
   validates :thumbnail, absence: true, if: -> { local? && !audio_or_video? }
 
@@ -258,7 +258,6 @@ class MediaAttachment < ApplicationRecord
   after_commit :enqueue_processing, on: :create
   after_commit :reset_parent_cache, on: :update
 
-  before_create :prepare_description, unless: :local?
   before_create :set_unknown_type
   before_create :set_processing
 
@@ -306,10 +305,6 @@ class MediaAttachment < ApplicationRecord
     self.type = :unknown if file.blank? && !type_changed?
   end
 
-  def prepare_description
-    self.description = description.strip[0...MAX_DESCRIPTION_LENGTH] unless description.nil?
-  end
-
   def set_type_and_extension
     self.type = begin
       if VIDEO_MIME_TYPES.include?(file_content_type)
diff --git a/app/models/status.rb b/app/models/status.rb
index b979252b3..62f9e5831 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -37,6 +37,7 @@ class Status < ApplicationRecord
   include Paginable
   include Cacheable
   include StatusThreadingConcern
+  include StatusSnapshotConcern
   include RateLimitable
 
   rate_limit by: :account, family: :statuses
@@ -61,8 +62,6 @@ class Status < ApplicationRecord
   belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies, optional: true
   belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, optional: true
 
-  has_many :edits, class_name: 'StatusEdit', inverse_of: :status, dependent: :destroy
-
   has_many :favourites, inverse_of: :status, dependent: :destroy
   has_many :bookmarks, inverse_of: :status, dependent: :destroy
   has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy
@@ -217,25 +216,6 @@ class Status < ApplicationRecord
     public_visibility? || unlisted_visibility?
   end
 
-  def snapshot!(account_id: nil, at_time: nil, rate_limit: true)
-    edits.create!(
-      text: text,
-      spoiler_text: spoiler_text,
-      sensitive: sensitive,
-      ordered_media_attachment_ids: ordered_media_attachment_ids || media_attachments.pluck(:id),
-      media_descriptions: ordered_media_attachments.map(&:description),
-      poll_options: preloadable_poll&.options,
-      account_id: account_id || self.account_id,
-      content_type: content_type,
-      created_at: at_time || edited_at,
-      rate_limit: rate_limit
-    )
-  end
-
-  def edited?
-    edited_at.present?
-  end
-
   alias sign? distributable?
 
   def with_media?
diff --git a/app/models/trends/query.rb b/app/models/trends/query.rb
index 64a4c0c1f..231b65228 100644
--- a/app/models/trends/query.rb
+++ b/app/models/trends/query.rb
@@ -37,7 +37,7 @@ class Trends::Query
   end
 
   def offset!(value)
-    @offset = value
+    @offset = value.to_i
     self
   end
 
@@ -46,7 +46,7 @@ class Trends::Query
   end
 
   def limit!(value)
-    @limit = value
+    @limit = value.to_i
     self
   end
 
diff --git a/app/models/user.rb b/app/models/user.rb
index f657f1b27..76ad7d1b2 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -91,11 +91,11 @@ class User < ApplicationRecord
   validates :invite_request, presence: true, on: :create, if: :invite_text_required?
 
   validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
-  validates_with BlacklistedEmailValidator, on: :create
+  validates_with BlacklistedEmailValidator, if: -> { !confirmed? }
   validates_with EmailMxValidator, if: :validate_email_dns?
   validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
 
-  # Those are honeypot/antispam fields
+  # Honeypot/anti-spam fields
   attr_accessor :registration_form_time, :website, :confirm_password
 
   validates_with RegistrationFormTimeValidator, on: :create
@@ -208,8 +208,12 @@ class User < ApplicationRecord
     confirmed? && approved? && !disabled? && !account.suspended? && !account.memorial?
   end
 
+  def unconfirmed?
+    !confirmed?
+  end
+
   def unconfirmed_or_pending?
-    !(confirmed? && approved?)
+    unconfirmed? || pending?
   end
 
   def inactive_message
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index 48707aa16..e6dd8040e 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -2,6 +2,7 @@
 
 class ActivityPub::ActorSerializer < ActivityPub::Serializer
   include RoutingHelper
+  include FormattingHelper
 
   context :security
 
@@ -102,7 +103,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
   end
 
   def summary
-    object.suspended? ? '' : Formatter.instance.simplified_format(object)
+    object.suspended? ? '' : account_bio_format(object)
   end
 
   def icon
@@ -185,6 +186,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
   end
 
   class Account::FieldSerializer < ActivityPub::Serializer
+    include FormattingHelper
+
     attributes :type, :name, :value
 
     def type
@@ -192,7 +195,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
     end
 
     def value
-      Formatter.instance.format_field(object.account, object.value)
+      account_field_value_format(object)
     end
   end
 
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 05f2ee14f..ca067ed9b 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class ActivityPub::NoteSerializer < ActivityPub::Serializer
+  include FormattingHelper
+
   context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :direct_message
 
   attributes :id, :type, :summary,
@@ -50,11 +52,11 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
   end
 
   def content
-    Formatter.instance.format(object)
+    status_content_format(object)
   end
 
   def content_map
-    { object.language => Formatter.instance.format(object) }
+    { object.language => content }
   end
 
   def replies
diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb
index 36886181f..113e0cca7 100644
--- a/app/serializers/rest/account_serializer.rb
+++ b/app/serializers/rest/account_serializer.rb
@@ -2,6 +2,7 @@
 
 class REST::AccountSerializer < ActiveModel::Serializer
   include RoutingHelper
+  include FormattingHelper
 
   attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at,
              :note, :url, :avatar, :avatar_static, :header, :header_static,
@@ -14,10 +15,12 @@ class REST::AccountSerializer < ActiveModel::Serializer
   attribute :suspended, if: :suspended?
 
   class FieldSerializer < ActiveModel::Serializer
+    include FormattingHelper
+
     attributes :name, :value, :verified_at
 
     def value
-      Formatter.instance.format_field(object.account, object.value)
+      account_field_value_format(object)
     end
   end
 
@@ -32,7 +35,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
   end
 
   def note
-    object.suspended? ? '' : Formatter.instance.simplified_format(object)
+    object.suspended? ? '' : account_bio_format(object)
   end
 
   def url
diff --git a/app/serializers/rest/announcement_serializer.rb b/app/serializers/rest/announcement_serializer.rb
index 9343b97d2..23b2fa514 100644
--- a/app/serializers/rest/announcement_serializer.rb
+++ b/app/serializers/rest/announcement_serializer.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class REST::AnnouncementSerializer < ActiveModel::Serializer
+  include FormattingHelper
+
   attributes :id, :content, :starts_at, :ends_at, :all_day,
              :published_at, :updated_at
 
@@ -25,7 +27,7 @@ class REST::AnnouncementSerializer < ActiveModel::Serializer
   end
 
   def content
-    Formatter.instance.linkify(object.text)
+    linkify(object.text)
   end
 
   def reactions
diff --git a/app/serializers/rest/status_edit_serializer.rb b/app/serializers/rest/status_edit_serializer.rb
index 05ccd5e94..f7a48797d 100644
--- a/app/serializers/rest/status_edit_serializer.rb
+++ b/app/serializers/rest/status_edit_serializer.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class REST::StatusEditSerializer < ActiveModel::Serializer
+  include FormattingHelper
+
   has_one :account, serializer: REST::AccountSerializer
 
   attributes :content, :spoiler_text, :sensitive, :created_at
@@ -11,7 +13,7 @@ class REST::StatusEditSerializer < ActiveModel::Serializer
   attribute :poll, if: -> { object.poll_options.present? }
 
   def content
-    Formatter.instance.format(object)
+    status_content_format(object)
   end
 
   def poll
diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb
index 0a00ed77e..daa7de7ea 100644
--- a/app/serializers/rest/status_serializer.rb
+++ b/app/serializers/rest/status_serializer.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class REST::StatusSerializer < ActiveModel::Serializer
+  include FormattingHelper
+
   attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
              :sensitive, :spoiler_text, :visibility, :language,
              :uri, :url, :replies_count, :reblogs_count,
@@ -73,7 +75,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
   end
 
   def content
-    Formatter.instance.format(object)
+    status_content_format(object)
   end
 
   def url
diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb
index 47a788c30..6dc14d8c2 100644
--- a/app/services/activitypub/process_status_update_service.rb
+++ b/app/services/activitypub/process_status_update_service.rb
@@ -4,6 +4,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
   include JsonLdHelper
 
   def call(status, json)
+    raise ArgumentError, 'Status has unsaved changes' if status.changed?
+
     @json                      = json
     @status_parser             = ActivityPub::Parser::StatusParser.new(@json)
     @uri                       = @status_parser.uri
@@ -17,16 +19,19 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
 
     last_edit_date = status.edited_at.presence || status.created_at
 
+    # Since we rely on tracking of previous changes, ensure clean slate
+    status.clear_changes_information
+
     # Only allow processing one create/update per status at a time
     RedisLock.acquire(lock_options) do |lock|
       if lock.acquired?
         Status.transaction do
-          create_previous_edit!
+          record_previous_edit!
           update_media_attachments!
           update_poll!
           update_immediate_attributes!
           update_metadata!
-          create_edit!
+          create_edits!
         end
 
         queue_poll_notifications!
@@ -216,19 +221,14 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
     { redis: Redis.current, key: "create:#{@uri}", autorelease: 15.minutes.seconds }
   end
 
-  def create_previous_edit!
-    # We only need to create a previous edit when no previous edits exist, e.g.
-    # when the status has never been edited. For other cases, we always create
-    # an edit, so the step can be skipped
-
-    return if @status.edits.any?
-
-    @status.snapshot!(at_time: @status.created_at, rate_limit: false)
+  def record_previous_edit!
+    @previous_edit = @status.build_snapshot(at_time: @status.created_at, rate_limit: false) if @status.edits.empty?
   end
 
-  def create_edit!
+  def create_edits!
     return unless significant_changes?
 
+    @previous_edit&.save!
     @status.snapshot!(account_id: @account.id, rate_limit: false)
   end
 
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index 239ab9b93..9c8b5ea20 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -134,7 +134,7 @@ class FetchLinkCardService < BaseService
     when 'video'
       @card.width            = embed[:width].presence  || 0
       @card.height           = embed[:height].presence || 0
-      @card.html             = Formatter.instance.sanitize(embed[:html], Sanitize::Config::MASTODON_OEMBED)
+      @card.html             = Sanitize.fragment(embed[:html], Sanitize::Config::MASTODON_OEMBED)
       @card.image_remote_url = (url + embed[:thumbnail_url]).to_s if embed[:thumbnail_url].present?
     when 'rich'
       # Most providers rely on <script> tags, which is a no-no
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index b1f9fd755..a90f17cfd 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -48,47 +48,23 @@ class NotifyService < BaseService
     return false if @notification.target_status.in_reply_to_id.nil?
 
     # Using an SQL CTE to avoid unneeded back-and-forth with SQL server in case of long threads
-    !Status.count_by_sql([<<-SQL.squish, id: @notification.target_status.in_reply_to_id, recipient_id: @recipient.id, sender_id: @notification.from_account.id]).zero?
-      WITH RECURSIVE ancestors(id, in_reply_to_id, replying_to_sender, path) AS (
-          SELECT
-            s.id,
-            s.in_reply_to_id,
-            (CASE
-              WHEN s.account_id = :recipient_id THEN
-                EXISTS (
-                  SELECT *
-                  FROM mentions m
-                  WHERE m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
-                )
-              ELSE
-                FALSE
-             END),
-            ARRAY[s.id]
+    !Status.count_by_sql([<<-SQL.squish, id: @notification.target_status.in_reply_to_id, recipient_id: @recipient.id, sender_id: @notification.from_account.id, depth_limit: 100]).zero?
+      WITH RECURSIVE ancestors(id, in_reply_to_id, mention_id, path, depth) AS (
+          SELECT s.id, s.in_reply_to_id, m.id, ARRAY[s.id], 0
           FROM statuses s
+          LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
           WHERE s.id = :id
         UNION ALL
-          SELECT
-            s.id,
-            s.in_reply_to_id,
-            (CASE
-              WHEN s.account_id = :recipient_id THEN
-                EXISTS (
-                  SELECT *
-                  FROM mentions m
-                  WHERE m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
-                )
-              ELSE
-                FALSE
-             END),
-            st.path || s.id
+          SELECT s.id, s.in_reply_to_id, m.id, st.path || s.id, st.depth + 1
           FROM ancestors st
           JOIN statuses s ON s.id = st.in_reply_to_id
-          WHERE st.replying_to_sender IS FALSE AND NOT s.id = ANY(path)
+          LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
+          WHERE st.mention_id IS NULL AND NOT s.id = ANY(path) AND st.depth < :depth_limit
       )
       SELECT COUNT(*)
       FROM ancestors st
       JOIN statuses s ON s.id = st.id
-      WHERE st.replying_to_sender IS TRUE AND s.visibility = 3
+      WHERE st.mention_id IS NOT NULL AND s.visibility = 3
     SQL
   end
 
diff --git a/app/services/update_status_service.rb b/app/services/update_status_service.rb
index f5155a2f5..cc4ec670d 100644
--- a/app/services/update_status_service.rb
+++ b/app/services/update_status_service.rb
@@ -4,6 +4,8 @@ class UpdateStatusService < BaseService
   include Redisable
   include LanguagesHelper
 
+  class NoChangesSubmittedError < StandardError; end
+
   # @param [Status] status
   # @param [Integer] account_id
   # @param [Hash] options
@@ -18,6 +20,8 @@ class UpdateStatusService < BaseService
     @status                    = status
     @options                   = options
     @account_id                = account_id
+    @media_attachments_changed = false
+    @poll_changed              = false
 
     Status.transaction do
       create_previous_edit!
@@ -33,18 +37,24 @@ class UpdateStatusService < BaseService
     broadcast_updates!
 
     @status
+  rescue NoChangesSubmittedError
+    # For calls that result in no changes, swallow the error
+    # but get back to the original state
+
+    @status.reload
   end
 
   private
 
   def update_media_attachments!
-    previous_media_attachments = @status.media_attachments.to_a
+    previous_media_attachments = @status.ordered_media_attachments.to_a
     next_media_attachments     = validate_media!
     added_media_attachments    = next_media_attachments - previous_media_attachments
 
     MediaAttachment.where(id: added_media_attachments.map(&:id)).update_all(status_id: @status.id)
 
     @status.ordered_media_attachment_ids = (@options[:media_ids] || []).map(&:to_i) & next_media_attachments.map(&:id)
+    @media_attachments_changed = previous_media_attachments.map(&:id) != @status.ordered_media_attachment_ids
     @status.media_attachments.reload
   end
 
@@ -70,20 +80,23 @@ class UpdateStatusService < BaseService
 
       # If for some reasons the options were changed, it invalidates all previous
       # votes, so we need to remove them
-      poll_changed = true if @options[:poll][:options] != poll.options || ActiveModel::Type::Boolean.new.cast(@options[:poll][:multiple]) != poll.multiple
+      @poll_changed = true if @options[:poll][:options] != poll.options || ActiveModel::Type::Boolean.new.cast(@options[:poll][:multiple]) != poll.multiple
 
       poll.options     = @options[:poll][:options]
       poll.hide_totals = @options[:poll][:hide_totals] || false
       poll.multiple    = @options[:poll][:multiple] || false
       poll.expires_in  = @options[:poll][:expires_in]
-      poll.reset_votes! if poll_changed
+      poll.reset_votes! if @poll_changed
       poll.save!
 
       @status.poll_id = poll.id
     elsif previous_poll.present?
       previous_poll.destroy
+      @poll_changed = true
       @status.poll_id = nil
     end
+
+    @poll_changed = true if @previous_expires_at != @status.preloadable_poll&.expires_at
   end
 
   def update_immediate_attributes!
@@ -92,8 +105,11 @@ class UpdateStatusService < BaseService
     @status.sensitive    = @options[:sensitive] || @options[:spoiler_text].present? if @options.key?(:sensitive) || @options.key?(:spoiler_text)
     @status.language     = valid_locale_cascade(@options[:language], @status.language, @status.account.user&.preferred_posting_language, I18n.default_locale)
     @status.content_type = @options[:content_type] || @status.content_type
-    @status.edited_at    = Time.now.utc
 
+    # We raise here to rollback the entire transaction
+    raise NoChangesSubmittedError unless significant_changes?
+
+    @status.edited_at = Time.now.utc
     @status.save!
   end
 
@@ -139,4 +155,8 @@ class UpdateStatusService < BaseService
   def create_edit!
     @status.snapshot!(account_id: @account_id)
   end
+
+  def significant_changes?
+    @status.changed? || @poll_changed || @media_attachments_changed
+  end
 end
diff --git a/app/validators/status_length_validator.rb b/app/validators/status_length_validator.rb
index 2a3ac8862..f93450ba6 100644
--- a/app/validators/status_length_validator.rb
+++ b/app/validators/status_length_validator.rb
@@ -3,35 +3,57 @@
 class StatusLengthValidator < ActiveModel::Validator
   MAX_CHARS = (ENV['MAX_TOOT_CHARS'] || 500).to_i
   URL_PLACEHOLDER_CHARS = 23
-  URL_PLACEHOLDER = "\1#{'x' * URL_PLACEHOLDER_CHARS}"
+  URL_PLACEHOLDER = 'x' * 23
 
   def validate(status)
     return unless status.local? && !status.reblog?
 
-    @status = status
-    status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if too_long?
+    status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if too_long?(status)
   end
 
   private
 
-  def too_long?
-    countable_length > MAX_CHARS
+  def too_long?(status)
+    countable_length(combined_text(status)) > MAX_CHARS
   end
 
-  def countable_length
-    total_text.mb_chars.grapheme_length
+  def countable_length(str)
+    str.mb_chars.grapheme_length
   end
 
-  def total_text
-    [@status.spoiler_text, countable_text].join
+  def combined_text(status)
+    [status.spoiler_text, countable_text(status.text)].join
   end
 
-  def countable_text
-    return '' if @status.text.nil?
+  def countable_text(str)
+    return '' if str.blank?
 
-    @status.text.dup.tap do |new_text|
-      new_text.gsub!(FetchLinkCardService::URL_PATTERN, URL_PLACEHOLDER)
-      new_text.gsub!(Account::MENTION_RE, '@\2')
+    # To ensure that we only give length concessions to entities that
+    # will be correctly parsed during formatting, we go through full
+    # entity extraction
+
+    entities = Extractor.remove_overlapping_entities(Extractor.extract_urls_with_indices(str, extract_url_without_protocol: false) + Extractor.extract_mentions_or_lists_with_indices(str))
+
+    rewrite_entities(str, entities) do |entity|
+      if entity[:url]
+        URL_PLACEHOLDER
+      elsif entity[:screen_name]
+        "@#{entity[:screen_name].split('@').first}"
+      end
     end
   end
+
+  def rewrite_entities(str, entities)
+    entities.sort_by! { |entity| entity[:indices].first }
+    result = ''.dup
+
+    last_index = entities.reduce(0) do |index, entity|
+      result << str[index...entity[:indices].first]
+      result << yield(entity)
+      entity[:indices].last
+    end
+
+    result << str[last_index..-1]
+    result
+  end
 end
diff --git a/app/views/accounts/_bio.html.haml b/app/views/accounts/_bio.html.haml
index e8a49a1aa..e2539b1d4 100644
--- a/app/views/accounts/_bio.html.haml
+++ b/app/views/accounts/_bio.html.haml
@@ -5,17 +5,17 @@
     .account__header__fields
       - fields.each do |field|
         %dl
-          %dt.emojify{ title: field.name }= Formatter.instance.format_field(account, field.name, custom_emojify: true)
+          %dt.emojify{ title: field.name }= prerender_custom_emojis(h(field.name), account.emojis)
           %dd{ title: field.value, class: custom_field_classes(field) }
             - if field.verified?
               %span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) }
                 = fa_icon 'check'
-            = Formatter.instance.format_field(account, field.value, custom_emojify: true)
+            = prerender_custom_emojis(account_field_value_format(field), account.emojis)
 
   = account_badge(account)
 
   - if account.note.present?
-    .account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
+    .account__header__content.emojify= prerender_custom_emojis(account_bio_format(account), account.emojis)
 
   .public-account-bio__extra
     = t 'accounts.joined', date: l(account.created_at, format: :month)
diff --git a/app/views/accounts/_moved.html.haml b/app/views/accounts/_moved.html.haml
index 4f71b062d..2f46e0dd0 100644
--- a/app/views/accounts/_moved.html.haml
+++ b/app/views/accounts/_moved.html.haml
@@ -3,7 +3,7 @@
 .moved-account-widget
   .moved-account-widget__message
     = fa_icon 'suitcase'
-    = t('accounts.moved_html', name: content_tag(:bdi, content_tag(:strong, display_name(account, custom_emojify: true), class: :emojify)), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.acct)])), ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'mention'))
+    = t('accounts.moved_html', name: content_tag(:bdi, content_tag(:strong, display_name(account, custom_emojify: true), class: :emojify)), new_profile_link: link_to(content_tag(:strong, safe_join(['@', content_tag(:span, moved_to_account.pretty_acct)])), ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'mention'))
 
   .moved-account-widget__card
     = link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener noreferrer' do
@@ -17,4 +17,4 @@
       %span.display-name
         %bdi
           %strong.emojify= display_name(moved_to_account, custom_emojify: true)
-        %span @#{moved_to_account.acct}
+        %span @#{moved_to_account.pretty_acct}
diff --git a/app/views/admin/account_actions/new.html.haml b/app/views/admin/account_actions/new.html.haml
index ca4f9663f..c7bb618df 100644
--- a/app/views/admin/account_actions/new.html.haml
+++ b/app/views/admin/account_actions/new.html.haml
@@ -1,11 +1,11 @@
 - content_for :page_title do
-  = t('admin.account_actions.title', acct: @account.acct)
+  = t('admin.account_actions.title', acct: @account.pretty_acct)
 
 = simple_form_for @account_action, url: admin_account_action_path(@account.id) do |f|
   = f.input :report_id, as: :hidden
 
   .fields-group
-    = f.input :type, as: :radio_buttons, collection: Admin::AccountAction.types_for_account(@account), include_blank: false, wrapper: :with_block_label, label_method: ->(type) { safe_join([I18n.t("simple_form.labels.admin_account_action.types.#{type}"), content_tag(:span, I18n.t("simple_form.hints.admin_account_action.types.#{type}"), class: 'hint')])}, hint: t('simple_form.hints.admin_account_action.type_html', acct: @account.acct)
+    = f.input :type, as: :radio_buttons, collection: Admin::AccountAction.types_for_account(@account), include_blank: false, wrapper: :with_block_label, label_method: ->(type) { safe_join([I18n.t("simple_form.labels.admin_account_action.types.#{type}"), content_tag(:span, I18n.t("simple_form.hints.admin_account_action.types.#{type}"), class: 'hint')])}, hint: t('simple_form.hints.admin_account_action.type_html', acct: @account.pretty_acct)
 
   - if @account.local?
     %hr.spacer/
diff --git a/app/views/admin/account_warnings/_account_warning.html.haml b/app/views/admin/account_warnings/_account_warning.html.haml
index 1462e76d0..030635185 100644
--- a/app/views/admin/account_warnings/_account_warning.html.haml
+++ b/app/views/admin/account_warnings/_account_warning.html.haml
@@ -5,7 +5,7 @@
         = fa_icon 'warning'
     .log-entry__content
       .log-entry__title
-        = t(account_warning.action, scope: 'admin.strikes.actions', name: content_tag(:span, account_warning.account.username, class: 'username'), target: content_tag(:span, account_warning.target_account.acct, class: 'target')).html_safe
+        = t(account_warning.action, scope: 'admin.strikes.actions', name: content_tag(:span, account_warning.account.username, class: 'username'), target: content_tag(:span, account_warning.target_account.pretty_acct, class: 'target')).html_safe
       .log-entry__timestamp
         %time.formatted{ datetime: account_warning.created_at.iso8601 }
           = l(account_warning.created_at)
diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml
index 2df91301e..82dd8dfb2 100644
--- a/app/views/admin/accounts/_account.html.haml
+++ b/app/views/admin/accounts/_account.html.haml
@@ -1,4 +1,4 @@
-.batch-table__row{ class: [!account.suspended? && account.user_pending? && 'batch-table__row--attention', account.suspended? && 'batch-table__row--muted'] }
+.batch-table__row{ class: [!account.suspended? && account.user_pending? && 'batch-table__row--attention', (account.suspended? || account.user_unconfirmed?) && 'batch-table__row--muted'] }
   %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
     = f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id
   .batch-table__row__content.batch-table__row__content--unpadded
diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml
index fc667b376..0290df7de 100644
--- a/app/views/admin/accounts/index.html.haml
+++ b/app/views/admin/accounts/index.html.haml
@@ -33,7 +33,7 @@
         = hidden_field_tag key, params[key]
 
     - %i(username by_domain display_name email ip).each do |key|
-      - unless key == :by_domain && params[:remote].blank?
+      - unless key == :by_domain && params[:origin] != 'remote'
         .input.string.optional
           = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.accounts.#{key}")
 
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index 9a1f07a06..1230294fe 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -1,5 +1,5 @@
 - content_for :page_title do
-  = @account.acct
+  = @account.pretty_acct
 
 - if @account.instance_actor?
   .flash-message.notice
@@ -16,16 +16,16 @@
         .account__header__fields
           - fields.each do |field|
             %dl
-              %dt.emojify{ title: field.name }= Formatter.instance.format_field(account, field.name, custom_emojify: true)
+              %dt.emojify{ title: field.name }= prerender_custom_emojis(h(field.name), account.emojis)
               %dd{ title: field.value, class: custom_field_classes(field) }
                 - if field.verified?
                   %span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) }
                     = fa_icon 'check'
-                = Formatter.instance.format_field(account, field.value, custom_emojify: true)
+                = prerender_custom_emojis(account_field_value_format(field, with_rel_me: false), account.emojis)
 
     - if account.note.present?
       %div
-        .account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
+        .account__header__content.emojify= prerender_custom_emojis(account_bio_format(account), account.emojis)
 
 .dashboard__counters.admin-account-counters
   %div
diff --git a/app/views/admin/change_emails/show.html.haml b/app/views/admin/change_emails/show.html.haml
index 6ff0d785e..bc00d6114 100644
--- a/app/views/admin/change_emails/show.html.haml
+++ b/app/views/admin/change_emails/show.html.haml
@@ -1,5 +1,5 @@
 - content_for :page_title do
-  = t('admin.accounts.change_email.title', username: @account.acct)
+  = t('admin.accounts.change_email.title', username: @account.username)
 
 = simple_form_for @user, url: admin_account_change_email_path(@account.id) do |f|
   .fields-group
diff --git a/app/views/admin/disputes/appeals/_appeal.html.haml b/app/views/admin/disputes/appeals/_appeal.html.haml
index 02b8777e1..3f6efb856 100644
--- a/app/views/admin/disputes/appeals/_appeal.html.haml
+++ b/app/views/admin/disputes/appeals/_appeal.html.haml
@@ -4,7 +4,7 @@
       = image_tag appeal.account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar'
     .log-entry__content
       .log-entry__title
-        = t(appeal.strike.action, scope: 'admin.strikes.actions', name: content_tag(:span, appeal.strike.account.username, class: 'username'), target: content_tag(:span, appeal.account.acct, class: 'target')).html_safe
+        = t(appeal.strike.action, scope: 'admin.strikes.actions', name: content_tag(:span, appeal.strike.account.username, class: 'username'), target: content_tag(:span, appeal.account.username, class: 'target')).html_safe
       .log-entry__timestamp
         %time.formatted{ datetime: appeal.strike.created_at.iso8601 }
           = l(appeal.strike.created_at)
diff --git a/app/views/admin/instances/_instance.html.haml b/app/views/admin/instances/_instance.html.haml
index 8a4396002..93f9bd418 100644
--- a/app/views/admin/instances/_instance.html.haml
+++ b/app/views/admin/instances/_instance.html.haml
@@ -1,7 +1,7 @@
 .directory__tag
   = link_to admin_instance_path(instance) do
     %h4
-      = fa_icon 'warning fw' if instance.failing?
+      = fa_icon 'warning fw', title: t('admin.instances.availability.warning') if instance.failing?
       = instance.domain
 
       %small
diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml
index 9b2a46e7f..e8cc1c400 100644
--- a/app/views/admin/instances/show.html.haml
+++ b/app/views/admin/instances/show.html.haml
@@ -84,7 +84,8 @@
     - else
       %span.negative-hint
         = t('admin.instances.availability.failures_recorded', count: @instance.delivery_failure_tracker.days)
-        = link_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post } unless @instance.exhausted_deliveries_days.empty?
+        %span= link_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post } unless @instance.exhausted_deliveries_days.empty?
+        %span= link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
 
 - if @instance.purgeable?
   %p= t('admin.instances.purge_description_html')
diff --git a/app/views/admin/relationships/index.html.haml b/app/views/admin/relationships/index.html.haml
index 60b9b5b25..f82cf26a3 100644
--- a/app/views/admin/relationships/index.html.haml
+++ b/app/views/admin/relationships/index.html.haml
@@ -1,5 +1,5 @@
 - content_for :page_title do
-  = t('admin.relationships.title', acct: @account.acct)
+  = t('admin.relationships.title', acct: @account.pretty_acct)
 
 .filters
   .filter-subset
diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml
index 7538cfd54..392fc8f81 100644
--- a/app/views/admin/reports/_status.html.haml
+++ b/app/views/admin/reports/_status.html.haml
@@ -4,12 +4,12 @@
   .batch-table__row__content
     .status__content><
       - if status.proper.spoiler_text.blank?
-        = Formatter.instance.format(status.proper, custom_emojify: true)
+        = prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
       - else
         %details<
           %summary><
-            %strong> Content warning: #{Formatter.instance.format_spoiler(status.proper)}
-          = Formatter.instance.format(status.proper, custom_emojify: true)
+            %strong> Content warning: #{prerender_custom_emojis(h(status.proper.spoiler_text), status.proper.emojis)}
+          = prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
 
     - unless status.proper.ordered_media_attachments.empty?
       - if status.proper.ordered_media_attachments.first.video?
diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml
index 209fbb698..e5ea56779 100644
--- a/app/views/admin/reports/show.html.haml
+++ b/app/views/admin/reports/show.html.haml
@@ -23,7 +23,7 @@
             = fa_icon('lock') if @report.target_account.locked?
       - if @report.target_account.note.present?
         .account-card__bio.emojify
-          = Formatter.instance.simplified_format(@report.target_account, custom_emojify: true)
+          = prerender_custom_emojis(account_bio_format(@report.target_account), @report.target_account.emojis)
       .account-card__actions
         .account-card__counters
           .account-card__counters__item
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index a287e52ff..a47cb2a88 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -87,7 +87,7 @@
       = f.input :trendable_by_default, as: :boolean, wrapper: :with_label, label: t('admin.settings.trendable_by_default.title'), hint: t('admin.settings.trendable_by_default.desc_html')
 
     .fields-group
-      = f.input :trending_status_cw, as: :boolean, wrapper: :with_label, label: t('admin.settings.trending_status_cw.title'), hint: t('trending_status_cw.desc_html')
+      = f.input :trending_status_cw, as: :boolean, wrapper: :with_label, label: t('admin.settings.trending_status_cw.title'), hint: t('admin.settings.trending_status_cw.desc_html')
 
     .fields-group
       = f.input :noindex, as: :boolean, wrapper: :with_label, label: t('admin.settings.default_noindex.title'), hint: t('admin.settings.default_noindex.desc_html')
diff --git a/app/views/admin/statuses/index.html.haml b/app/views/admin/statuses/index.html.haml
index 865464c72..9163dee79 100644
--- a/app/views/admin/statuses/index.html.haml
+++ b/app/views/admin/statuses/index.html.haml
@@ -1,7 +1,7 @@
 - content_for :page_title do
   = t('admin.statuses.title')
   \-
-  = "@#{@account.acct}"
+  = "@#{@account.pretty_acct}"
 
 .filters
   .filter-subset
diff --git a/app/views/admin/tags/show.html.haml b/app/views/admin/tags/show.html.haml
index c41ce2fc2..5ac57e1f2 100644
--- a/app/views/admin/tags/show.html.haml
+++ b/app/views/admin/tags/show.html.haml
@@ -8,7 +8,7 @@
 
 .dashboard
   .dashboard__item
-    = react_admin_component :counter, measure: 'tag_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_accounts_measure')
+    = react_admin_component :counter, measure: 'tag_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_accounts_measure'), href: tag_url(@tag), target: '_blank'
   .dashboard__item
     = react_admin_component :counter, measure: 'tag_uses', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_uses_measure')
   .dashboard__item
diff --git a/app/views/admin_mailer/new_report.text.erb b/app/views/admin_mailer/new_report.text.erb
index d6c7d6bab..f8a5224a1 100644
--- a/app/views/admin_mailer/new_report.text.erb
+++ b/app/views/admin_mailer/new_report.text.erb
@@ -1,5 +1,5 @@
 <%= raw t('application_mailer.salutation', name: display_name(@me)) %>
 
-<%= raw(@report.account.local? ? t('admin_mailer.new_report.body', target: @report.target_account.acct, reporter: @report.account.acct) : t('admin_mailer.new_report.body_remote', target: @report.target_account.acct, domain: @report.account.domain)) %>
+<%= raw(@report.account.local? ? t('admin_mailer.new_report.body', target: @report.target_account.pretty_acct, reporter: @report.account.pretty_acct) : t('admin_mailer.new_report.body_remote', target: @report.target_account.acct, domain: @report.account.domain)) %>
 
 <%= raw t('application_mailer.view')%> <%= admin_report_url(@report) %>
diff --git a/app/views/auth/registrations/_status.html.haml b/app/views/auth/registrations/_status.html.haml
index 68954a5da..759bbc41c 100644
--- a/app/views/auth/registrations/_status.html.haml
+++ b/app/views/auth/registrations/_status.html.haml
@@ -7,7 +7,7 @@
     = t('auth.status.pending')
 - elsif @user.account.moved_to_account_id.present?
   .flash-message.warning
-    = t('auth.status.redirecting_to', acct: @user.account.moved_to_account.acct)
+    = t('auth.status.redirecting_to', acct: @user.account.moved_to_account.pretty_acct)
     = link_to t('migrations.cancel'), settings_migration_path
 
 %h3= t('auth.status.account_status')
diff --git a/app/views/authorize_interactions/show.html.haml b/app/views/authorize_interactions/show.html.haml
index 42c874134..2b4d2ed62 100644
--- a/app/views/authorize_interactions/show.html.haml
+++ b/app/views/authorize_interactions/show.html.haml
@@ -1,5 +1,5 @@
 - content_for :page_title do
-  = t('authorize_follow.title', acct: @resource.acct)
+  = t('authorize_follow.title', acct: @resource.pretty_acct)
 
 .form-container
   .follow-prompt
diff --git a/app/views/authorize_interactions/success.html.haml b/app/views/authorize_interactions/success.html.haml
index 47fd09767..86fa55eac 100644
--- a/app/views/authorize_interactions/success.html.haml
+++ b/app/views/authorize_interactions/success.html.haml
@@ -1,5 +1,5 @@
 - content_for :page_title do
-  = t('authorize_follow.title', acct: @resource.acct)
+  = t('authorize_follow.title', acct: @resource.pretty_acct)
 
 .form-container
   .follow-prompt
diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml
index 2ac700fe6..4872432d4 100644
--- a/app/views/directories/index.html.haml
+++ b/app/views/directories/index.html.haml
@@ -34,7 +34,7 @@
                 = fa_icon('lock') if account.locked?
         - if account.note.present?
           .account-card__bio.emojify
-            = Formatter.instance.simplified_format(account, custom_emojify: true)
+            = prerender_custom_emojis(account_bio_format(account), account.emojis)
         - else
           .flex-spacer
         .account-card__actions
diff --git a/app/views/disputes/strikes/show.html.haml b/app/views/disputes/strikes/show.html.haml
index 0fc32b918..0b71e14a3 100644
--- a/app/views/disputes/strikes/show.html.haml
+++ b/app/views/disputes/strikes/show.html.haml
@@ -26,7 +26,7 @@
         %p= t "user_mailer.warning.explanation.#{@strike.action}", instance: Rails.configuration.x.local_domain
 
       - unless @strike.text.blank?
-        = Formatter.instance.linkify(@strike.text)
+        = linkify(@strike.text)
 
       - if @strike.report && !@strike.report.other?
         %p
diff --git a/app/views/notification_mailer/_status.html.haml b/app/views/notification_mailer/_status.html.haml
index f520208e1..444b06fe6 100644
--- a/app/views/notification_mailer/_status.html.haml
+++ b/app/views/notification_mailer/_status.html.haml
@@ -23,15 +23,15 @@
                                       = image_tag full_asset_url(status.account.avatar.url), alt:''
                                     %td{ align: 'left' }
                                       %bdi= display_name(status.account)
-                                      = "@#{status.account.acct}"
+                                      = "@#{status.account.pretty_acct}"
 
                               - if status.spoiler_text?
                                 %div.auto-dir
                                   %p
-                                    = Formatter.instance.format_spoiler(status)
+                                    = status.spoiler_text
 
                               %div.auto-dir
-                                = Formatter.instance.format(status)
+                                = status_content_format(status)
 
                                 - if status.ordered_media_attachments.size > 0
                                   %p
diff --git a/app/views/notification_mailer/_status.text.erb b/app/views/notification_mailer/_status.text.erb
index c43f32d9f..1dc8de739 100644
--- a/app/views/notification_mailer/_status.text.erb
+++ b/app/views/notification_mailer/_status.text.erb
@@ -3,6 +3,6 @@
 > ----
 >
 <% end %>
-> <%= raw word_wrap(Formatter.instance.plaintext(status), break_sequence: "\n> ") %>
+> <%= raw word_wrap(extract_status_plain_text(status), break_sequence: "\n> ") %>
 
 <%= raw t('application_mailer.view')%> <%= web_url("statuses/#{status.id}") %>
diff --git a/app/views/notification_mailer/digest.text.erb b/app/views/notification_mailer/digest.text.erb
index b2c85a9e3..0f84a4ef0 100644
--- a/app/views/notification_mailer/digest.text.erb
+++ b/app/views/notification_mailer/digest.text.erb
@@ -3,9 +3,9 @@
 <%= raw t('notification_mailer.digest.body', since: l(@me.user_current_sign_in_at || @since), instance: root_url) %>
 <% @notifications.each do |notification| %>
 
-* <%= raw t('notification_mailer.digest.mention', name: notification.from_account.acct) %>
+* <%= raw t('notification_mailer.digest.mention', name: notification.from_account.pretty_acct) %>
 
-  <%= raw Formatter.instance.plaintext(notification.target_status) %>
+  <%= raw extract_status_plain_text(notification.target_status) %>
 
   <%= raw t('application_mailer.view')%> <%= web_url("statuses/#{notification.target_status.id}") %>
 <% end %>
diff --git a/app/views/notification_mailer/favourite.html.haml b/app/views/notification_mailer/favourite.html.haml
index a715d615c..ebc5c29c7 100644
--- a/app/views/notification_mailer/favourite.html.haml
+++ b/app/views/notification_mailer/favourite.html.haml
@@ -20,7 +20,7 @@
                                       = image_tag full_pack_url('media/images/mailer/icon_grade.png'), alt:''
 
                               %h1= t 'notification_mailer.favourite.title'
-                              %p.lead= t('notification_mailer.favourite.body', name: @account.acct)
+                              %p.lead= t('notification_mailer.favourite.body', name: @account.pretty_acct)
 
 = render 'status', status: @status
 
diff --git a/app/views/notification_mailer/favourite.text.erb b/app/views/notification_mailer/favourite.text.erb
index 2581b4909..f4f869656 100644
--- a/app/views/notification_mailer/favourite.text.erb
+++ b/app/views/notification_mailer/favourite.text.erb
@@ -1,5 +1,5 @@
 <%= raw t('application_mailer.salutation', name: display_name(@me)) %>
 
-<%= raw t('notification_mailer.favourite.body', name: @account.acct) %>
+<%= raw t('notification_mailer.favourite.body', name: @account.pretty_acct) %>
 
 <%= render 'status', status: @status %>
diff --git a/app/views/notification_mailer/follow.html.haml b/app/views/notification_mailer/follow.html.haml
index cd84f7858..a59ef8835 100644
--- a/app/views/notification_mailer/follow.html.haml
+++ b/app/views/notification_mailer/follow.html.haml
@@ -20,7 +20,7 @@
                                       = image_tag full_pack_url('media/images/mailer/icon_person_add.png'), alt: ''
 
                               %h1= t 'notification_mailer.follow.title'
-                              %p.lead= t('notification_mailer.follow.body', name: @account.acct)
+                              %p.lead= t('notification_mailer.follow.body', name: @account.pretty_acct)
 
 %table.email-table{ cellspacing: 0, cellpadding: 0 }
   %tbody
diff --git a/app/views/notification_mailer/follow.text.erb b/app/views/notification_mailer/follow.text.erb
index cbe46f552..016a0a4cf 100644
--- a/app/views/notification_mailer/follow.text.erb
+++ b/app/views/notification_mailer/follow.text.erb
@@ -1,5 +1,5 @@
 <%= raw t('application_mailer.salutation', name: display_name(@me)) %>
 
-<%= raw t('notification_mailer.follow.body', name: @account.acct) %>
+<%= raw t('notification_mailer.follow.body', name: @account.pretty_acct) %>
 
 <%= raw t('application_mailer.view')%> <%= web_url("accounts/#{@account.id}") %>
diff --git a/app/views/notification_mailer/follow_request.html.haml b/app/views/notification_mailer/follow_request.html.haml
index a63e27a90..4c32c831e 100644
--- a/app/views/notification_mailer/follow_request.html.haml
+++ b/app/views/notification_mailer/follow_request.html.haml
@@ -20,7 +20,7 @@
                                       = image_tag full_pack_url('media/images/mailer/icon_person_add.png'), alt: ''
 
                               %h1= t 'notification_mailer.follow_request.title'
-                              %p.lead= t('notification_mailer.follow_request.body', name: @account.acct)
+                              %p.lead= t('notification_mailer.follow_request.body', name: @account.pretty_acct)
 
 %table.email-table{ cellspacing: 0, cellpadding: 0 }
   %tbody
diff --git a/app/views/notification_mailer/follow_request.text.erb b/app/views/notification_mailer/follow_request.text.erb
index a018394b8..66aa97fe3 100644
--- a/app/views/notification_mailer/follow_request.text.erb
+++ b/app/views/notification_mailer/follow_request.text.erb
@@ -1,5 +1,5 @@
 <%= raw t('application_mailer.salutation', name: display_name(@me)) %>
 
-<%= raw t('notification_mailer.follow_request.body', name: @account.acct) %>
+<%= raw t('notification_mailer.follow_request.body', name: @account.pretty_acct) %>
 
 <%= raw t('application_mailer.view')%> <%= web_url("follow_requests") %>
diff --git a/app/views/notification_mailer/mention.html.haml b/app/views/notification_mailer/mention.html.haml
index 619873cfa..cfb7465c1 100644
--- a/app/views/notification_mailer/mention.html.haml
+++ b/app/views/notification_mailer/mention.html.haml
@@ -20,7 +20,7 @@
                                       = image_tag full_pack_url('media/images/mailer/icon_reply.png'), alt: ''
 
                               %h1= t 'notification_mailer.mention.title'
-                              %p.lead= t('notification_mailer.mention.body', name: @status.account.acct)
+                              %p.lead= t('notification_mailer.mention.body', name: @status.account.pretty_acct)
 
 = render 'status', status: @status
 
diff --git a/app/views/notification_mailer/mention.text.erb b/app/views/notification_mailer/mention.text.erb
index 03f53813b..f104d5f92 100644
--- a/app/views/notification_mailer/mention.text.erb
+++ b/app/views/notification_mailer/mention.text.erb
@@ -1,5 +1,5 @@
 <%= raw t('application_mailer.salutation', name: display_name(@me)) %>
 
-<%= raw t('notification_mailer.mention.body', name: @status.account.acct) %>
+<%= raw t('notification_mailer.mention.body', name: @status.account.pretty_acct) %>
 
 <%= render 'status', status: @status %>
diff --git a/app/views/notification_mailer/reblog.html.haml b/app/views/notification_mailer/reblog.html.haml
index a2811be23..c528536ec 100644
--- a/app/views/notification_mailer/reblog.html.haml
+++ b/app/views/notification_mailer/reblog.html.haml
@@ -20,7 +20,7 @@
                                       = image_tag full_pack_url('media/images/mailer/icon_cached.png'), alt: ''
 
                               %h1= t 'notification_mailer.reblog.title'
-                              %p.lead= t('notification_mailer.reblog.body', name: @account.acct)
+                              %p.lead= t('notification_mailer.reblog.body', name: @account.pretty_acct)
 
 = render 'status', status: @status
 
diff --git a/app/views/notification_mailer/reblog.text.erb b/app/views/notification_mailer/reblog.text.erb
index 8fc841bf6..73a3b3945 100644
--- a/app/views/notification_mailer/reblog.text.erb
+++ b/app/views/notification_mailer/reblog.text.erb
@@ -1,5 +1,5 @@
 <%= raw t('application_mailer.salutation', name: display_name(@me)) %>
 
-<%= raw t('notification_mailer.reblog.body', name: @account.acct) %>
+<%= raw t('notification_mailer.reblog.body', name: @account.pretty_acct) %>
 
 <%= render 'status', status: @status %>
diff --git a/app/views/settings/aliases/index.html.haml b/app/views/settings/aliases/index.html.haml
index 5df0c9669..c618a82f1 100644
--- a/app/views/settings/aliases/index.html.haml
+++ b/app/views/settings/aliases/index.html.haml
@@ -29,5 +29,5 @@
       - else
         - @aliases.each do |account_alias|
           %tr
-            %td= account_alias.acct
+            %td= account_alias.pretty_acct
             %td= table_link_to 'trash', t('aliases.remove'), settings_alias_path(account_alias), data: { method: :delete }
diff --git a/app/views/settings/migrations/show.html.haml b/app/views/settings/migrations/show.html.haml
index 078eaebc6..492f6fe12 100644
--- a/app/views/settings/migrations/show.html.haml
+++ b/app/views/settings/migrations/show.html.haml
@@ -8,7 +8,7 @@
         = render 'application/card', account: current_account.moved_to_account
       .fields-row__column.fields-group.fields-row__column-6
         %p.hint
-          %span.positive-hint= t('migrations.redirecting_to', acct: current_account.moved_to_account.acct)
+          %span.positive-hint= t('migrations.redirecting_to', acct: current_account.moved_to_account.pretty_acct)
 
         %p.hint= t('migrations.cancel_explanation')
 
@@ -76,7 +76,7 @@
               - if migration.target_account.present?
                 = compact_account_link_to migration.target_account
               - else
-                = migration.acct
+                = migration.pretty_acct
 
             %td= number_with_delimiter migration.followers_count
 
diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml
index fd7e034b1..1d0e5a38c 100644
--- a/app/views/statuses/_detailed_status.html.haml
+++ b/app/views/statuses/_detailed_status.html.haml
@@ -18,10 +18,11 @@
   .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
     - if status.spoiler_text?
       %p<
-        %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: prefers_autoplay?)}&nbsp;
+        %span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}&nbsp;
         %button.status__content__spoiler-link= t('statuses.show_more')
     .e-content
-      = Formatter.instance.format(status, custom_emojify: true, autoplay: prefers_autoplay?)
+      = prerender_custom_emojis(status_content_format(status), status.emojis)
+
       - if status.preloadable_poll
         = render_poll_component(status)
 
diff --git a/app/views/statuses/_poll.html.haml b/app/views/statuses/_poll.html.haml
index 3546a923e..d0f264095 100644
--- a/app/views/statuses/_poll.html.haml
+++ b/app/views/statuses/_poll.html.haml
@@ -12,7 +12,7 @@
             %span.poll__number><
               = "#{percent.round}%"
             %span.poll__option__text
-              = Formatter.instance.format_poll_option(status, option, autoplay: prefers_autoplay?)
+              = prerender_custom_emojis(h(option.title), status.emojis)
             - if own_votes.include?(index)
               %span.poll__voted
                 %i.poll__voted__mark.fa.fa-check
@@ -23,7 +23,7 @@
           %label.poll__option><
             %span.poll__input{ class: poll.multiple? ? 'checkbox' : nil}><
             %span.poll__option__text
-              = Formatter.instance.format_poll_option(status, option, autoplay: prefers_autoplay?)
+              = prerender_custom_emojis(h(option.title), status.emojis)
   .poll__footer
     - unless show_results
       %button.button.button-secondary{ disabled: true }
diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml
index a41656323..7b672bda7 100644
--- a/app/views/statuses/_simple_status.html.haml
+++ b/app/views/statuses/_simple_status.html.haml
@@ -30,10 +30,11 @@
   .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
     - if status.spoiler_text?
       %p<
-        %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: prefers_autoplay?)}&nbsp;
+        %span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}&nbsp;
         %button.status__content__spoiler-link= t('statuses.show_more')
     .e-content<
-      = Formatter.instance.format(status, custom_emojify: true, autoplay: prefers_autoplay?)
+      = prerender_custom_emojis(status_content_format(status), status.emojis)
+
       - if status.preloadable_poll
         = render_poll_component(status)
 
diff --git a/app/views/user_mailer/warning.html.haml b/app/views/user_mailer/warning.html.haml
index b308e18f7..fff61fa90 100644
--- a/app/views/user_mailer/warning.html.haml
+++ b/app/views/user_mailer/warning.html.haml
@@ -40,7 +40,7 @@
                                 %p= t "user_mailer.warning.explanation.#{@warning.action}", instance: @instance
 
                               - unless @warning.text.blank?
-                                = Formatter.instance.linkify(@warning.text)
+                                = linkify(@warning.text)
 
                               - if @warning.report && !@warning.report.other?
                                 %p
diff --git a/app/workers/scheduler/user_cleanup_scheduler.rb b/app/workers/scheduler/user_cleanup_scheduler.rb
index 750d2127b..d1f00c47f 100644
--- a/app/workers/scheduler/user_cleanup_scheduler.rb
+++ b/app/workers/scheduler/user_cleanup_scheduler.rb
@@ -27,7 +27,7 @@ class Scheduler::UserCleanupScheduler
   end
 
   def clean_discarded_statuses!
-    Status.discarded.where('deleted_at <= ?', 30.days.ago).find_in_batches do |statuses|
+    Status.unscoped.discarded.where('deleted_at <= ?', 30.days.ago).find_in_batches do |statuses|
       RemovalWorker.push_bulk(statuses) do |status|
         [status.id, { 'immediate' => true }]
       end
diff --git a/boxfile.yml b/boxfile.yml
index c1d89bb15..27166cec9 100644
--- a/boxfile.yml
+++ b/boxfile.yml
@@ -43,20 +43,19 @@ run.config:
 
   fs_watch: true
 
-
 deploy.config:
   extra_steps:
     - NODE_ENV=production bundle exec rake assets:precompile
   transform:
-    - "envsubst < /app/.env.nanobox > /app/.env.production"
+    - 'envsubst < /app/.env.nanobox > /app/.env.production'
     - |-
-        if [ -z "$LOCAL_DOMAIN" ]
-        then
-          . /app/.env.production
-          export LOCAL_DOMAIN
-        fi
-        erb /app/nanobox/nginx-web.conf.erb > /app/nanobox/nginx-web.conf
-        erb /app/nanobox/nginx-stream.conf.erb > /app/nanobox/nginx-stream.conf
+      if [ -z "$LOCAL_DOMAIN" ]
+      then
+        . /app/.env.production
+        export LOCAL_DOMAIN
+      fi
+      erb /app/nanobox/nginx-web.conf.erb > /app/nanobox/nginx-web.conf
+      erb /app/nanobox/nginx-stream.conf.erb > /app/nanobox/nginx-stream.conf
     - touch /app/log/production.log
   before_live:
     web.web:
@@ -65,11 +64,10 @@ deploy.config:
   after_live:
     worker.sidekiq:
       - |-
-          if [[ "${ES_ENABLED}" != "false" ]]
-          then
-            bin/tootctl search deploy
-          fi
-
+        if [[ "${ES_ENABLED}" != "false" ]]
+        then
+          bin/tootctl search deploy
+        fi
 
 web.web:
   start:
@@ -89,7 +87,6 @@ web.web:
     data.storage:
       - public/system
 
-
 web.stream:
   start:
     nginx: nginx -c /app/nanobox/nginx-stream.conf
@@ -103,7 +100,6 @@ web.stream:
   writable_dirs:
     - tmp
 
-
 worker.sidekiq:
   start:
     default: bundle exec sidekiq -c 5 -q default -L /app/log/sidekiq.log
@@ -123,7 +119,6 @@ worker.sidekiq:
     data.storage:
       - public/system
 
-
 data.db:
   image: nanobox/postgresql:9.6
 
@@ -145,7 +140,6 @@ data.db:
           curl -k -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/${file} -X DELETE
         done
 
-
 data.elastic:
   image: nanobox/elasticsearch:5
 
@@ -171,7 +165,6 @@ data.elastic:
           curl -k -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/${file} -X DELETE
         done
 
-
 data.redis:
   image: nanobox/redis:4.0
 
@@ -191,7 +184,6 @@ data.redis:
           curl -k -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/${file} -X DELETE
         done
 
-
 data.storage:
   image: nanobox/unfs:0.9
 
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
index c24146da4..80c5f6d4e 100644
--- a/config/brakeman.ignore
+++ b/config/brakeman.ignore
@@ -7,7 +7,7 @@
       "check_name": "SQL",
       "message": "Possible SQL injection",
       "file": "app/models/status.rb",
-      "line": 105,
+      "line": 106,
       "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
       "code": "result.joins(\"INNER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}\")",
       "render_path": null,
@@ -27,7 +27,7 @@
       "check_name": "SQL",
       "message": "Possible SQL injection",
       "file": "app/models/trends/query.rb",
-      "line": 60,
+      "line": 76,
       "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
       "code": "klass.joins(\"join unnest(array[#{ids.join(\",\")}]) with ordinality as x (id, ordering) on #{klass.table_name}.id = x.id\")",
       "render_path": null,
@@ -61,6 +61,36 @@
       "note": ""
     },
     {
+      "warning_type": "Cross-Site Scripting",
+      "warning_code": 2,
+      "fingerprint": "71cf98c8235b5cfa9946b5db8fdc1a2f3a862566abb34e4542be6f3acae78233",
+      "check_name": "CrossSiteScripting",
+      "message": "Unescaped model attribute",
+      "file": "app/views/admin/disputes/appeals/_appeal.html.haml",
+      "line": 7,
+      "link": "https://brakemanscanner.org/docs/warning_types/cross_site_scripting",
+      "code": "t((Unresolved Model).new.strike.action, :scope => \"admin.strikes.actions\", :name => content_tag(:span, (Unresolved Model).new.strike.account.username, :class => \"username\"), :target => content_tag(:span, (Unresolved Model).new.account.username, :class => \"target\"))",
+      "render_path": [
+        {
+          "type": "template",
+          "name": "admin/disputes/appeals/index",
+          "line": 20,
+          "file": "app/views/admin/disputes/appeals/index.html.haml",
+          "rendered": {
+            "name": "admin/disputes/appeals/_appeal",
+            "file": "app/views/admin/disputes/appeals/_appeal.html.haml"
+          }
+        }
+      ],
+      "location": {
+        "type": "template",
+        "template": "admin/disputes/appeals/_appeal"
+      },
+      "user_input": "(Unresolved Model).new.strike",
+      "confidence": "Weak",
+      "note": ""
+    },
+    {
       "warning_type": "SQL Injection",
       "warning_code": 0,
       "fingerprint": "75fcd147b7611763ab6915faf8c5b0709e612b460f27c05c72d8b9bd0a6a77f8",
@@ -121,33 +151,23 @@
       "note": ""
     },
     {
-      "warning_type": "Cross-Site Scripting",
-      "warning_code": 2,
-      "fingerprint": "afad51718ae373b2f19d2513029fd2afccf58b9148e475934bc6a162ee33c352",
-      "check_name": "CrossSiteScripting",
-      "message": "Unescaped model attribute",
-      "file": "app/views/admin/disputes/appeals/_appeal.html.haml",
-      "line": 7,
-      "link": "https://brakemanscanner.org/docs/warning_types/cross_site_scripting",
-      "code": "t((Unresolved Model).new.strike.action, :scope => \"admin.strikes.actions\", :name => content_tag(:span, (Unresolved Model).new.strike.account.username, :class => \"username\"), :target => content_tag(:span, (Unresolved Model).new.account.acct, :class => \"target\"))",
-      "render_path": [
-        {
-          "type": "template",
-          "name": "admin/disputes/appeals/index",
-          "line": 20,
-          "file": "app/views/admin/disputes/appeals/index.html.haml",
-          "rendered": {
-            "name": "admin/disputes/appeals/_appeal",
-            "file": "app/views/admin/disputes/appeals/_appeal.html.haml"
-          }
-        }
-      ],
+      "warning_type": "Mass Assignment",
+      "warning_code": 105,
+      "fingerprint": "ab5035dd1a9f8c3a8d92fb2c37e8fe86fede4f87c91b71aa32e89c9eede602fc",
+      "check_name": "PermitAttributes",
+      "message": "Potentially dangerous key allowed for mass assignment",
+      "file": "app/controllers/api/v1/notifications_controller.rb",
+      "line": 81,
+      "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
+      "code": "params.permit(:account_id, :types => ([]), :exclude_types => ([]))",
+      "render_path": null,
       "location": {
-        "type": "template",
-        "template": "admin/disputes/appeals/_appeal"
+        "type": "method",
+        "class": "Api::V1::NotificationsController",
+        "method": "browserable_params"
       },
-      "user_input": "(Unresolved Model).new.strike",
-      "confidence": "Weak",
+      "user_input": ":account_id",
+      "confidence": "High",
       "note": ""
     },
     {
@@ -184,7 +204,7 @@
         {
           "type": "template",
           "name": "admin/trends/links/index",
-          "line": 45,
+          "line": 49,
           "file": "app/views/admin/trends/links/index.html.haml",
           "rendered": {
             "name": "admin/trends/links/_preview_card",
@@ -207,7 +227,7 @@
       "check_name": "PermitAttributes",
       "message": "Potentially dangerous key allowed for mass assignment",
       "file": "app/controllers/api/v1/reports_controller.rb",
-      "line": 36,
+      "line": 26,
       "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
       "code": "params.permit(:account_id, :comment, :category, :forward, :status_ids => ([]), :rule_ids => ([]))",
       "render_path": null,
@@ -221,6 +241,6 @@
       "note": ""
     }
   ],
-  "updated": "2022-02-15 03:48:53 +0100",
+  "updated": "2022-03-22 07:48:32 +0100",
   "brakeman_version": "5.2.1"
 }
diff --git a/config/database.yml b/config/database.yml
index 9b8d096e9..127a78abf 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -32,4 +32,3 @@ production:
   host: <%= ENV['DB_HOST'] || 'localhost' %>
   port: <%= ENV['DB_PORT'] || 5432 %>
   prepared_statements: <%= ENV['PREPARED_STATEMENTS'] || 'true' %>
-
diff --git a/config/deploy.rb b/config/deploy.rb
index f642e6e59..8a2316b57 100644
--- a/config/deploy.rb
+++ b/config/deploy.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-lock '3.16.0'
+lock '3.17.0'
 
 set :repo_url, ENV.fetch('REPO', 'https://github.com/mastodon/mastodon.git')
 set :branch, ENV.fetch('BRANCH', 'master')
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 1d9063cd6..4446a9152 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -87,11 +87,13 @@ Rails.application.configure do
 
   # E-mails
   outgoing_email_address = ENV.fetch('SMTP_FROM_ADDRESS', 'notifications@localhost')
-  outgoing_mail_domain   = Mail::Address.new(outgoing_email_address).domain
+  outgoing_email_domain  = Mail::Address.new(outgoing_email_address).domain
+
   config.action_mailer.default_options = {
     from: outgoing_email_address,
     reply_to: ENV['SMTP_REPLY_TO'],
-    'Message-ID': -> { "<#{Mail.random_tag}@#{outgoing_mail_domain}>" },
+    return_path: ENV['SMTP_RETURN_PATH'],
+    message_id: -> { "<#{Mail.random_tag}@#{outgoing_email_domain}>" },
   }
 
   config.action_mailer.smtp_settings = {
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index 84f5d6666..017bd1a70 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -17,8 +17,8 @@ data:
 
 search:
   paths:
-   - app/
-   - config/navigation.rb
+    - app/
+    - config/navigation.rb
 
   relative_roots:
     - app/controllers
diff --git a/config/initializers/twitter_regex.rb b/config/initializers/twitter_regex.rb
index d2ea5f974..6a7723fd2 100644
--- a/config/initializers/twitter_regex.rb
+++ b/config/initializers/twitter_regex.rb
@@ -75,30 +75,4 @@ module Twitter::TwitterText
       )
     }iox
   end
-
-  module Extractor
-    # 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 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_extra_uris_with_indices(text, _options = {}) # :yields: uri, start, end
-      return [] unless text && text.index(":")
-      urls = []
-
-      text.to_s.scan(Twitter::TwitterText::Regex[:valid_extended_uri]) do
-        valid_uri_match_data = $~
-
-        start_position = valid_uri_match_data.char_begin(3)
-        end_position = valid_uri_match_data.char_end(3)
-
-        urls << {
-          :url => valid_uri_match_data[3],
-          :indices => [start_position, end_position]
-        }
-      end
-      urls.each{|url| yield url[:url], url[:indices].first, url[:indices].last} if block_given?
-      urls
-    end
-  end
 end
diff --git a/config/locales/activerecord.ku.yml b/config/locales/activerecord.ku.yml
index 3a3a1d843..340b7a777 100644
--- a/config/locales/activerecord.ku.yml
+++ b/config/locales/activerecord.ku.yml
@@ -9,7 +9,7 @@ ku:
         agreement: Peymana karûbarê
         email: Navnîşana E-nameyê
         locale: Herêmî
-        password: Pêborîn
+        password: Borînpeyv
       user/account:
         username: Navê bikarhêneriyê
       user/invite_request:
diff --git a/config/locales/activerecord.nl.yml b/config/locales/activerecord.nl.yml
index 6bbdc5b40..b5a122001 100644
--- a/config/locales/activerecord.nl.yml
+++ b/config/locales/activerecord.nl.yml
@@ -24,7 +24,7 @@ nl:
         status:
           attributes:
             reblog:
-              taken: van toot bestaat al
+              taken: van bericht bestaat al
         user:
           attributes:
             email:
diff --git a/config/locales/ar.yml b/config/locales/ar.yml
index 4ccadab8b..ba66c5749 100644
--- a/config/locales/ar.yml
+++ b/config/locales/ar.yml
@@ -474,9 +474,6 @@ ar:
       delivery_error_days: أيام أخطاء التوصيل
       delivery_error_hint: إذا كان التوصيل غير ممكناً لـ%{count} يوم، فستوضع عليها علامة {غير قابلة للتسليم} تلقائياً.
       empty: لم يتم العثور على نطاقات.
-      known_accounts:
-        one: "%{count} حساب معروف"
-        other: "%{count} حسابات معروفة"
       moderation:
         all: كافتها
         limited: محدود
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index 460feb037..d7a8a4858 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -1,7 +1,7 @@
 ---
 ca:
   about:
-    about_hashtag_html: Aquests són tuts públics etiquetats amb <strong>#%{hashtag}</strong>. Pots interactuar amb elles si tens un compte a qualsevol lloc del fedivers.
+    about_hashtag_html: Aquests són publicacions públiques etiquetades amb <strong>#%{hashtag}</strong>. Pots interactuar amb elles si tens un compte a qualsevol lloc del fedivers.
     about_mastodon_html: 'La xarxa social del futur: sense anuncis, sense vigilància corporativa, disseny ètic i descentralització. Posseeix les teves dades amb Mastodon!'
     about_this: Quant a
     active_count_after: actiu
@@ -25,7 +25,7 @@ ca:
     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.
-    learn_more: Més informació
+    learn_more: Aprèn més
     logged_in_as_html: Actualment has iniciat sessió com a %{username}.
     logout_before_registering: Ja has iniciat sessió.
     privacy_policy: Política de privadesa
@@ -35,12 +35,12 @@ ca:
     server_stats: 'Estadístiques del servidor:'
     source_code: Codi font
     status_count_after:
-      one: estat
-      other: tuts
-    status_count_before: Que han escrit
+      one: publicació
+      other: publicacions
+    status_count_before: Que han publicat
     tagline: Segueix els teus amics i descobreix-ne de nous
     terms: Termes del servei
-    unavailable_content: Contingut no disponible
+    unavailable_content: Servidors moderats
     unavailable_content_description:
       domain: Servidor
       reason: Raó
@@ -78,10 +78,10 @@ ca:
     pin_errors:
       following: Has d'estar seguint la persona que vulguis avalar
     posts:
-      one: Tut
-      other: Tuts
-    posts_tab_heading: Tuts
-    posts_with_replies: Tuts i respostes
+      one: Publicació
+      other: Publicacions
+    posts_tab_heading: Publicacions
+    posts_with_replies: Publicacions i respostes
     roles:
       admin: Administrador
       bot: Bot
@@ -98,7 +98,7 @@ ca:
       created_msg: La nota de moderació s'ha creat correctament!
       destroyed_msg: Nota de moderació destruïda amb èxit!
     accounts:
-      add_email_domain_block: Afegir el domini de correu a la llista negra
+      add_email_domain_block: Bloquejar el domini de l'adreça de correu electrònic
       approve: Aprova
       approved_msg: L’aplicació del registre de %{username} s’ha aprovat amb èxit
       are_you_sure: N'estàs segur?
@@ -168,7 +168,6 @@ ca:
       previous_strikes_description_html:
         one: Aquest compte té <strong>una</strong> acció.
         other: Aquest compte té <strong>%{count}</strong> accions.
-        zero: Aquest compte està <strong>al dia</strong>.
       promote: Promociona
       protocol: Protocol
       public: Públic
@@ -490,6 +489,7 @@ ca:
           other: Intents fallits en %{count} diferents dies.
         no_failures_recorded: Sense errors registrats.
         title: Disponibilitat
+        warning: El darrer intent de connectar a aquest servidor no ha tingut èxit
       back_to_all: Totes
       back_to_limited: Limitades
       back_to_warning: Avís
@@ -529,7 +529,6 @@ ca:
       known_accounts:
         one: "%{count} compte conegut"
         other: "%{count} comptes coneguts"
-        zero: Cap compte conegut
       moderation:
         all: Totes
         limited: Limitades
@@ -774,6 +773,11 @@ ca:
     system_checks:
       database_schema_check:
         message_html: Hi ha pendents migracions de la base de dades. Si us plau executa-les per a assegurar que l'aplicació es comporta com s'espera
+      elasticsearch_running_check:
+        message_html: No s'ha pogut connectar a Elasticsearch. Si us plau verifica que estigui funcionant o desactiva la cerca de text complet
+      elasticsearch_version_check:
+        message_html: 'Versió incompatible de Elasticsearch: %{value}'
+        version_comparison: Elasticsearch %{running_version} està funcionant mentre %{required_version} és requerida
       rules_check:
         action: Gestiona les normes del servidor
         message_html: No has definit cap norma del servidor.
@@ -794,9 +798,8 @@ ca:
         disallow: No permetre l'enllaç
         disallow_provider: No permetre el mitjà
         shared_by_over_week:
-          one: Compartit per un usuari en la darrera setmana
-          other: Compartit per %{count} usuaris en la darrera setmana
-          zero: Compartit per ningú en la darrera setmana
+          one: Compartit per una persona en la darrera setmana
+          other: Compartit per %{count} persones en la darrera setmana
         title: Enllaços en tendència
         usage_comparison: Compartit %{today} vegades avui, comparat amb %{yesterday} d'ahir
       pending_review: Revisió pendent
@@ -837,9 +840,8 @@ ca:
         usable: Pot ser emprat
         usage_comparison: Usat %{today} vegades avui, comparat amb %{yesterday} d'ahir
         used_by_over_week:
-          one: Emprat per un usuari en la darrera setmana
-          other: Emprat per %{count} usuaris en la darrera setmana
-          zero: Emprat per ningú en la darrera setmana
+          one: Emprat per una persona en la darrera setmana
+          other: Emprat per %{count} persones en la darrera setmana
       title: Tendència
     warning_presets:
       add_new: Afegir-ne un de nou
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index 8eda24b4f..107aa9a0b 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -168,6 +168,11 @@ cs:
       not_subscribed: Neodebírá
       pending: Čeká na posouzení
       perform_full_suspension: Pozastavit
+      previous_strikes_description_html:
+        few: Tento účet má <strong>%{count}</strong> strajky.
+        many: Tento účet má <strong>%{count}</strong> strajků.
+        one: Tento účet má <strong>jeden</strong> strajk.
+        other: Tento účet má <strong>%{count}</strong> strajků.
       promote: Povýšit
       protocol: Protokol
       public: Veřejný
@@ -366,6 +371,7 @@ cs:
       enable: Povolit
       enabled: Povoleno
       enabled_msg: Emoji bylo úspěšně povoleno
+      image_hint: PNG nebo GIF do %{size}
       list: Uvést
       listed: Uvedeno
       new:
@@ -446,6 +452,8 @@ cs:
       title: Doporučená sledování
       unsuppress: Obnovit doporučení sledování
     instances:
+      availability:
+        warning: Poslední pokus o připojení k tomuto serveru byl neúspěšný
       back_to_all: Vše
       back_to_limited: Omezený
       back_to_warning: Varování
@@ -527,12 +535,15 @@ cs:
           one: "%{count} poznámka"
           other: "%{count} poznámek"
       action_taken_by: Akci vykonal uživatel
-      are_you_sure: Opravu?
+      are_you_sure: Jste si jisti?
       assign_to_self: Přidělit ke mně
       assigned: Přiřazený moderátor
       by_target_domain: Doména nahlášeného účtu
+      category: Kategorie
+      category_description_html: Důvod nahlášení tohoto účtu a/nebo obsahu bude uveden v komunikaci s nahlášeným účtem
       comment:
         none: Žádné
+      comment_description_html: 'Pro upřesnění uživatel %{name} napsal:'
       created_at: Nahlášené
       forwarded: Přeposláno
       forwarded_to: Přeposláno na %{domain}
@@ -544,13 +555,19 @@ cs:
         create_and_unresolve: Znovu otevřít s poznámkou
         delete: Smazat
         placeholder: Popište, jaké akce byly vykonány, nebo jakékoliv jiné související aktuality…
+        title: Poznámky
+      notes_description_html: Zobrazit a zanechat poznámky pro ostatní moderátory i sebe v budoucnu
+      remote_user_placeholder: vzdálený uživatel z %{instance}
       reopen: Znovu otevřít hlášení
       report: 'Nahlásit #%{id}'
       reported_account: Nahlášený účet
       reported_by: Nahlášeno uživatelem
       resolved: Vyřešeno
       resolved_msg: Hlášení úspěšně vyřešeno!
+      skip_to_actions: Přeskočit k akcím
       status: Stav
+      statuses: Nahlášený obsah
+      statuses_description_html: Obsah porušující pravidla bude uveden v komunikaci s nahlášeným účtem
       target_origin: Původ nahlášeného účtu
       title: Hlášení
       unassign: Odebrat
@@ -666,6 +683,11 @@ cs:
     system_checks:
       database_schema_check:
         message_html: Na spuštění čekají databázové migrace. Nechte je prosím proběhnout pro zajištění očekávaného chování aplikace
+      elasticsearch_running_check:
+        message_html: Nelze se připojit k Elasticsearch. Prosím zkontrolujte, že běží, nebo vypněte fulltextové vyhledávání
+      elasticsearch_version_check:
+        message_html: 'Nekompatibilní verze Elasticsearch: %{value}'
+        version_comparison: Je spuštěn Elasticsearch %{running_version} místo vyžadovaného %{required_version}
       rules_check:
         action: Spravovat pravidla serveru
         message_html: Nedefinovali jste žádná pravidla serveru.
@@ -1041,6 +1063,9 @@ cs:
     carry_mutes_over_text: Tento účet se přesunul z %{acct}, který jste skryli.
     copy_account_note_text: 'Tento účet se přesunul z %{acct}, zde byly Vaše předchozí poznámky o něm:'
   notification_mailer:
+    admin:
+      sign_up:
+        subject: Uživatel %{name} se zaregistroval
     digest:
       action: Zobrazit všechna oznámení
       body: Zde najdete stručný souhrn zpráv, které jste zmeškali od vaší poslední návštěvy %{since}
@@ -1082,6 +1107,8 @@ cs:
       title: Nový boost
     status:
       subject: Nový příspěvek od %{name}
+    update:
+      subject: Uživatel %{name} upravil příspěvek
   notifications:
     email_events: Události pro e-mailová oznámení
     email_events_hint: 'Vyberte události, pro které chcete dostávat oznámení:'
@@ -1163,6 +1190,9 @@ cs:
     reply:
       proceed: Pokračovat k odpovědi
       prompt: 'Chcete odpovědět na tento příspěvek:'
+  reports:
+    errors:
+      invalid_rules: neodkazuje na platná pravidla
   scheduled_statuses:
     over_daily_limit: Překročili jste limit %{limit} příspěvků naplánovaných na tento den
     over_total_limit: Překročili jste limit %{limit} naplánovaných příspěvků
@@ -1251,6 +1281,7 @@ cs:
         other: "%{count} videí"
     boosted_from_html: Boostnuto z %{acct_link}
     content_warning: 'Varování o obsahu: %{warning}'
+    default_language: Stejný jako jazyk rozhraní
     disallowed_hashtags:
       few: 'obsahoval nepovolené hashtagy: %{tags}'
       many: 'obsahoval nepovolené hashtagy: %{tags}'
@@ -1450,6 +1481,8 @@ cs:
       subject: Potvrďte prosím pokus o přihlášení
       title: Pokus o přihlášení
     warning:
+      explanation:
+        delete_statuses: Bylo shledáno, že některé vaše příspěvky porušují jednu nebo více zásad komunity a následně byly odstraněny moderátory %{instance}.
       subject:
         disable: Váš účet %{acct} byl zmrazen
         none: Varování pro %{acct}
diff --git a/config/locales/da.yml b/config/locales/da.yml
index 34e56ded4..d3182ad7f 100644
--- a/config/locales/da.yml
+++ b/config/locales/da.yml
@@ -166,9 +166,8 @@ da:
       perform_full_suspension: Suspendér
       previous_strikes: Tidligere anmeldelser (strikes)
       previous_strikes_description_html:
-        one: Denne konto har <strong>et</strong> anmeldelse.
+        one: Denne konto har <strong>en</strong> anmeldelse.
         other: Denne konto har <strong>%{count}</strong> anmeldelser.
-        zero: Denne konto er <strong>på god fod</strong>.
       promote: Forfrem
       protocol: Protokol
       public: Offentlig
@@ -490,6 +489,7 @@ da:
           other: Mislykkede forsøg på %{count} forskellige dage.
         no_failures_recorded: Ingen fejl noteret.
         title: Tilgængelighed
+        warning: Seneste forsøg på at oprette forbindelse til denne server mislykkedes
       back_to_all: Alle
       back_to_limited: Begrænset
       back_to_warning: Advarsel
@@ -529,7 +529,6 @@ da:
       known_accounts:
         one: "%{count} kendt konto"
         other: "%{count} kendte konti"
-        zero: Ingen kendt konto
       moderation:
         all: Alle
         limited: Begrænset
@@ -774,6 +773,11 @@ da:
     system_checks:
       database_schema_check:
         message_html: Databasemigreringer afventer. Kør dem for at sikre den forventede adfærd fra applikationen
+      elasticsearch_running_check:
+        message_html: Kunne ikke oprette forbindelse til Elasticsearch. Tjek, at den kører, eller deaktivér fuldtekstsøgning
+      elasticsearch_version_check:
+        message_html: 'Inkompatibel Elasticsearch-version: %{value}'
+        version_comparison: Elasticsearch %{running_version} kører, men %{required_version} kræves
       rules_check:
         action: Håndtér serverregler
         message_html: Ingen serverregler defineret.
@@ -794,9 +798,8 @@ da:
         disallow: Tillad ikke link
         disallow_provider: Tillad ikke udgiver
         shared_by_over_week:
-          one: Delt af én person i løbet af den seneste uge
-          other: Delt af %{count} personer i løbet af den seneste uge
-          zero: Ikke delt af nogen i løbet af den seneste uge
+          one: Delt af én person den seneste uge
+          other: Delt af %{count} personer den seneste uge
         title: Populære links
         usage_comparison: Delt %{today} gange i dag, sammenlignet med %{yesterday} i går
       pending_review: Afventer revision
@@ -837,9 +840,8 @@ da:
         usable: Kan anvendes
         usage_comparison: Anvendt %{today} gange i dag, sammenlignet med %{yesterday} i går
         used_by_over_week:
-          one: Anvendt af én person i løbet af den seneste uge
-          other: Anvendt af %{count} personer i løbet af den seneste uge
-          zero: Ikke anvendt af nogen i løbet af den seneste uge
+          one: Brugt af én person den seneste uge
+          other: Brugt af %{count} personer den seneste uge
       title: Trends
     warning_presets:
       add_new: Tilføj ny
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 4768f4e80..78ba2c440 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -165,10 +165,6 @@ de:
       pending: In Warteschlange
       perform_full_suspension: Verbannen
       previous_strikes: Vorherige Strikes
-      previous_strikes_description_html:
-        one: Dieses Konto hat <strong>einen</strong> Strike.
-        other: Dieses Konto hat <strong>%{count}</strong> Strikes.
-        zero: Dieses Konto ist <strong>in gutem Stand</strong>.
       promote: Befördern
       protocol: Protokoll
       public: Öffentlich
@@ -373,6 +369,7 @@ de:
       enable: Aktivieren
       enabled: Aktiviert
       enabled_msg: Das Emoji wurde aktiviert
+      image_hint: PNG oder GIF bis %{size}
       list: Liste
       listed: Gelistet
       new:
@@ -489,6 +486,7 @@ de:
           other: Fehlgeschlagener Versuch am %{count}. Tag.
         no_failures_recorded: Keine Fehler bei der Aufzeichnung.
         title: Verfügbarkeit
+        warning: Der letzte Versuch, sich mit diesem Server zu verbinden, war nicht erfolgreich
       back_to_all: Alle
       back_to_limited: Beschränkt
       back_to_warning: Warnung
@@ -525,10 +523,6 @@ de:
       delivery_error_hint: Wenn eine Lieferung für %{count} Tage nicht möglich ist, wird sie automatisch als nicht lieferbar markiert.
       destroyed_msg: Daten von %{domain} sind nun in der Warteschlange für die bevorstehende Löschung.
       empty: Keine Domains gefunden.
-      known_accounts:
-        one: "%{count} bekanntes Konto"
-        other: "%{count} bekannte Konten"
-        zero: Kein bekanntes Konto
       moderation:
         all: Alle
         limited: Beschränkt
@@ -792,10 +786,6 @@ de:
         description_html: Dies sind Links, die derzeit von Konten geteilt werden, von denen dein Server Beiträge sieht. Es kann deinen Benutzern helfen, herauszufinden, was in der Welt vor sich geht. Es werden keine Links öffentlich angezeigt, bis du den Publisher genehmigst. Du kannst auch einzelne Links zulassen oder ablehnen.
         disallow: Verbiete Link
         disallow_provider: Verbiete Herausgeber
-        shared_by_over_week:
-          one: In der letzten Woche geteilt von einer Person
-          other: In der letzten Woche geteilt von %{count} Personen
-          zero: Geteilt von niemandem in der letzten Woche
         title: Angesagte Links
         usage_comparison: Heute %{today} mal geteilt, gestern %{yesterday} mal
       pending_review: Überprüfung ausstehend
@@ -835,10 +825,6 @@ de:
         trending_rank: 'Trend #%{rank}'
         usable: Kann verwendet werden
         usage_comparison: Heute %{today} mal genutzt, gestern %{yesterday} mal
-        used_by_over_week:
-          one: In der letzten Woche genutzt von einer Person
-          other: In der letzten Woche genutzt von %{count} Personen
-          zero: Genutzt von niemandem in der letzten Woche
       title: Trends
     warning_presets:
       add_new: Neu hinzufügen
@@ -1436,6 +1422,7 @@ de:
     disallowed_hashtags:
       one: 'enthält einen verbotenen Hashtag: %{tags}'
       other: 'enthält verbotene Hashtags: %{tags}'
+    edited_at_html: Bearbeitet %{date}
     errors:
       in_reply_not_found: Der Beitrag, auf den du antworten möchtest, scheint nicht zu existieren.
     open_in_web: Im Web öffnen
diff --git a/config/locales/devise.ca.yml b/config/locales/devise.ca.yml
index ff7836711..4c58c432f 100644
--- a/config/locales/devise.ca.yml
+++ b/config/locales/devise.ca.yml
@@ -25,7 +25,7 @@ ca:
         explanation: Has creat un compte a %{host} amb aquesta adreça de correu electrònic. Estàs a un sol clic de l'activació. Si no fos així, ignora aquest correu electrònic.
         explanation_when_pending: Has sol·licitat una invitació a %{host} amb aquesta adreça de correu electrònic. Un cop confirmis la teva adreça de correu electrònic revisarem la teva sol·licitud. No es pot iniciar la sessió fins llavors. Si la teva sol·licitud és rebutjada les teves dades s’eliminaran, de manera que no s’exigirà cap altra acció. Si no has estat tu qui ha fet aquest sol·licitud si us plau ignora aquest correu electrònic.
         extra_html: Si us plau consulta també <a href="%{terms_path}"> les regles del servidor</a> i <a href="%{policy_path}"> les nostres condicions de servei</a>.
-        subject: 'Mastodon: Instruccions de confirmació de %{instance}'
+        subject: 'Mastodon: Instruccions de confirmació per a %{instance}'
         title: Verifica l'adreça de correu
       email_changed:
         explanation: 'L''adreça de correu del teu compte s''està canviant a:'
@@ -34,7 +34,7 @@ ca:
         title: Adreça de correu electrònic nova
       password_change:
         explanation: S'ha canviat la contrasenya del teu compte.
-        extra: Si no has canviat el teu correu electrònic, és probable que algú hagi accedit al teu compte. Si us plau, canvia la contrasenya immediatament o posa't en contacte amb l'administrador del servidor si no pots accedir al teu compte.
+        extra: Si no has canviat la teva contrasenya, és probable que algú hagi accedit al teu compte. Si us plau, canvia la contrasenya immediatament o posa't en contacte amb l'administrador del servidor si no pots accedir al teu compte.
         subject: 'Mastodon: Contrasenya canviada'
         title: Contrasenya canviada
       reconfirmation_instructions:
@@ -61,7 +61,7 @@ ca:
         subject: 'Mastodon: codis de recuperació de Dos factors regenerats'
         title: 2FA codis de recuperació canviats
       unlock_instructions:
-        subject: 'Mastodon: Instruccions per a desbloquejar'
+        subject: 'Mastodon: Instruccions de desbloqueig'
       webauthn_credential:
         added:
           explanation: La següent clau de seguretat s'ha afegit al teu compte
@@ -84,34 +84,34 @@ ca:
       success: Autenticat amb èxit des del compte %{kind}.
     passwords:
       no_token: No pots accedir a aquesta pàgina sense provenir des del correu de restabliment de la contrasenya. Si vens des del correu de restabliment de contrasenya, assegura't que estàs emprant l'adreça completa proporcionada.
-      send_instructions: Si el teu correu electrònic existeix en la nostra base de dades, rebràs en pocs minuts un enllaç de restabliment de contrasenya en l'adreça de correu. Si us plau verifica la teva carpeta de correu brossa if no rebut aquest correu.
-      send_paranoid_instructions: Si el seu correu electrònic existeix en la nostra base de dades, rebràs un enllaç de restabliment de contrasenya en l'adreça de correu en pocs minuts.
+      send_instructions: Si el teu correu electrònic existeix en la nostra base de dades, rebràs en pocs minuts un enllaç de restabliment de contrasenya en l'adreça de correu. Si us plau verifica la teva carpeta de correu brossa si no has rebut aquest correu.
+      send_paranoid_instructions: Si el teu correu electrònic existeix en la nostra base de dades, rebràs un enllaç de restabliment de contrasenya en l'adreça de correu en pocs minuts. Si us plau verifica la carpeta de correu brossa si no reps aquest correu.
       updated: La contrasenya s'ha canviat correctament. Ara ja estàs registrat.
       updated_not_active: La contrasenya s'ha canviat correctament.
     registrations:
-      destroyed: Adéu! el compte s'ha cancel·lat amb èxit. Desitgem veure't de nou aviat.
+      destroyed: Adéu! el compte s'ha cancel·lat amb èxit. Desitgem veure't de nou ben aviat.
       signed_up: Benvingut! T'has registrat amb èxit.
-      signed_up_but_inactive: T´has registrat amb èxit. No obstant, no podem identificar-te perquè el compte encara no s'ha activat.
-      signed_up_but_locked: T´has registrat amb èxit. No obstant, no podem identificar-te perquè el compte està blocat.
+      signed_up_but_inactive: T´has registrat amb èxit. No obstant, no podem iniciar la teva sessió perquè el teu compte encara no s'ha activat.
+      signed_up_but_locked: T´has registrat amb èxit. No obstant, no podem iniciar la teva sessió perquè el teu compte està bloquejat.
       signed_up_but_pending: S'ha enviat un missatge amb un enllaç de confirmació a la teva adreça de correu electrònic. Després que hagis fet clic a l'enllaç, revisarem la sol·licitud. Se't notificarà si s'aprova.
-      signed_up_but_unconfirmed: S'ha enviat per correu electrònic un missatge amb un enllaç de confirmació. Fes clic a l'enllaç per a activar el compte.
-      update_needs_confirmation: Ha actualitzat el seu compte amb èxit, però necessitem verificar la nova adreça de correu. Si us plau comprovi el correu i segueixi l'enllaç per confirmar la nova adreça de correu.
-      updated: El seu compte ha estat actualitzat amb èxit.
+      signed_up_but_unconfirmed: S'ha enviat per correu electrònic un missatge amb un enllaç de confirmació. Fes clic a l'enllaç per a activar el compte. Si us plau verifica la teva carpeta de correu brossa si no reps aquest correu.
+      update_needs_confirmation: Has actualitzat el teu compte amb èxit, però necessitem verificar la teva nova adreça de correu. Si us plau comprova el correu i segueixi l'enllaç per confirmar la nova adreça de correu. Si us plau verifica la teva carpeta de correu brossa si no reps aquest correu.
+      updated: El teu compte ha estat actualitzat amb èxit.
     sessions:
       already_signed_out: Has tancat la sessió amb èxit.
       signed_in: T'has registrat amb èxit.
       signed_out: Has tancat la sessió amb èxit.
     unlocks:
       send_instructions: En pocs minuts rebràs un correu electrònic amb instruccions sobre com desbloquejar el teu compte. Si us plau verifica la teva carpeta de correu brossa si no has rebut aquest correu.
-      send_paranoid_instructions: Si el compte existeix, rebràs en pocs minuts un correu electrònic amb instruccions sobre com desbloquejar-lo. Verifica la carpeta de correu brossa si no has rebut aquest correu.
-      unlocked: El compte s'ha desblocat correctament. Inicia sessió per a continuar.
+      send_paranoid_instructions: Si el teu compte existeix, rebràs en pocs minuts un correu electrònic amb instruccions sobre com desbloquejar-lo. Verifica la carpeta de correu brossa si no has rebut aquest correu.
+      unlocked: El compte s'ha desbloquejat correctament. Inicia sessió per a continuar.
   errors:
     messages:
       already_confirmed: ja està confirmat. Intenta d'iniciar sessió
-      confirmation_period_expired: calia fer la confirmació dins de %{period}, torna a sol·licitar-la
-      expired: ha expirat, demana'n una altra
+      confirmation_period_expired: cal fer la confirmació dins de %{period}, si us plau sol·licita-ho de nou
+      expired: ha expirat, si us plau demana'n una altra
       not_found: no s'ha trobat
       not_locked: no està bloquejada
       not_saved:
         one: '1 error ha impedit desar aquest %{resource}:'
-        other: "%{count} errors hab impedit desar aquest %{resource}:"
+        other: "%{count} errors han impedit desar aquest %{resource}:"
diff --git a/config/locales/devise.da.yml b/config/locales/devise.da.yml
index 6e4d5ad62..18aa43162 100644
--- a/config/locales/devise.da.yml
+++ b/config/locales/devise.da.yml
@@ -37,21 +37,21 @@ da:
         title: Adgangskode skiftet
       reconfirmation_instructions:
         explanation: Bekræft den nye adresse for at skifte din e-mail.
-        extra: Er denne ændring ikke foranlediget af dig, så ignorér denne e-mail. Mastodon-kontoens e-mailadresse skiftes ikke, før linket ovenfor benyttes.
+        extra: Er denne ændring ikke foranlediget af dig, så ignorér denne e-mail. Mastodon-kontoens e-mailadresse kan kun skiftes vha. linket ovenfor.
         subject: 'Mastodon: Bekræft e-mail for %{instance}'
         title: Bekræft e-mailadresse
       reset_password_instructions:
         action: Skift adgangskode
         explanation: Du har anmodet om en ny kontoadgangskode.
-        extra: Har du ikke anmodet om dette, så ignorér denne e-mail. Din adgangskode skiftes først, når linket ovenfor er benyttet til at oprette en ny.
-        subject: 'Mastodon: Nulstil adgangskode-instruktioner'
+        extra: Har du ikke anmodet om dette, så ignorér denne e-mail. Din adgangskode skiftes først, når en ny er oprettet vha. linket ovenfor.
+        subject: 'Mastodon: Instruktioner til adgangskodenulstilling'
         title: Adgangskodenulstilling
       two_factor_disabled:
-        explanation: Tofaktorgodkendelse for din konto er blevet deaktiveret. Indlogning er nu kun mulig via email og adgangskode.
+        explanation: Tofaktorgodkendelse for kontoen er blevet deaktiveret. Indlogning er nu kun mulig via email og adgangskode.
         subject: 'Mastodon: Tofaktorgodkendelse deaktiveret'
         title: 2FA deaktiveret
       two_factor_enabled:
-        explanation: Tofaktorgodkendelse er blevet aktiveret for din konto. Et login-token genereret af den parrede TOTP-app vil være nødvendig.
+        explanation: Tofaktorgodkendelse er blevet aktiveret for kontoen. Indlogning vil kærve et token genereret af den parrede TOTP-app.
         subject: 'Mastodon: Tofaktorgodkendelse aktiveret'
         title: 2FA aktiveret
       two_factor_recovery_codes_changed:
@@ -70,38 +70,38 @@ da:
           subject: 'Mastodon: Sikkerhedsnøgle slettet'
           title: En af dine sikkerhedsnøgler er blevet slettet
       webauthn_disabled:
-        explanation: Godkendelse med sikkerhedsnøgler er blevet deaktiveret for din konto. Indlogning er nu kun mulig via token genereret af den parrede TOTP-app.
+        explanation: Godkendelse med sikkerhedsnøgler er blevet deaktiveret for kontoen. Indlogning er nu kun mulig via token genereret af den parrede TOTP-app.
         subject: 'Mastodon: Godkendelse med sikkerhedsnøgler deaktiveret'
         title: Sikkerhedsnøgler deaktiveret
       webauthn_enabled:
-        explanation: Sikkerhedsnøglegodkendelse er aktiveret for din konto. Din sikkerhedsnøgle kan nu bruges til indlogning.
+        explanation: Sikkerhedsnøglegodkendelse er aktiveret for kontoen. Din sikkerhedsnøgle kan nu bruges til indlogning.
         subject: 'Mastodon: Sikkerhedsnøglegodkendelse aktiveret'
         title: Sikkerhedsnøgler aktiveret
     omniauth_callbacks:
       failure: Kunne ikke godkende dig fra %{kind} fordi "%{reason}".
       success: Godkendt fra %{kind}-konto.
     passwords:
-      no_token: Denne side er kun tilgængelig via linket fra en adgangskodenulstillings e-mail. Husk i den forbindelse at benytte den fuldstændige URL fra e-mailen.
+      no_token: Denne side er kun tilgængelig via linket fra en e-mail til adgangskodenulstillings. Husk i den forbindelse at benytte den fuldstændige URL fra e-mailen.
       send_instructions: Er din e-mailadresse allerede registreret, e-mailes du et link til adgangskodenulstilling. Tjek spammappen, hvis e-mailen ikke ses i indbakken indenfor få minutter.
-      send_paranoid_instructions: Er din e-mail-adresse allerede registreret, e-mailes du et link til adgangskodegendannelse. Tjek spammappen, hvis e-mailen ikke ses i indbakken indenfor få minutter.
+      send_paranoid_instructions: Er din e-mailadresse allerede registreret, e-mailes du et link til adgangskodenulstilling. Tjek spammappen, hvis e-mailen ikke ses i indbakken indenfor få minutter.
       updated: Din adgangskode er skiftet, og du er nu logget ind.
       updated_not_active: Din adgangskode er skiftet.
     registrations:
       destroyed: Farvel! Din konto er nu annulleret. Vi håber snart at se dig igen.
-      signed_up: Velkommen! Du har nu tilmeldt dig.
-      signed_up_but_inactive: Du har nu oprettet dig. Da din konto endnu ikke er aktiveret, kan du dog pt. ikke logge ind.
-      signed_up_but_locked: Du har nu oprettet dig. Da din konto er låst, kan du pt. ikke logge ind.
-      signed_up_but_pending: En besked med et bekræftelseslink er e-mailet til dig. Når du har klikket på linket, gennemgår vi din ansøgning, og du får besked, hvis den godkendes.
-      signed_up_but_unconfirmed: En besked med et bekræftelseslink er e-mailet til dig. Følg linket for at aktivere din konto. Tjek din spammappe, hvis du ikke ser denne e-mail i din indbakke.
-      update_needs_confirmation: Du har opdateret din konto. Din nye e-mailadresse skal dog bekræftes. For at gøre dette, tjek din e-mail og følg bekræftelseslinket. Tjek din spammappe, hvis du ikke ser denne e-mail i din indbakke indenfor få minutter.
+      signed_up: Velkommen! Du er nu tilmeldt.
+      signed_up_but_inactive: Du har nu oprettet dig. Da din konto endnu ikke er aktiveret, kan du dog ikke logge ind med det samme.
+      signed_up_but_locked: Du har nu oprettet dig. Da din konto er låst, kan du ikke logge ind med det samme.
+      signed_up_but_pending: Et bekræftelseslink er e-mailet til dig. Når du har klikket på linket, gennemgår vi din ansøgning, og du får besked, hvis den godkendes.
+      signed_up_but_unconfirmed: Et bekræftelseslink er e-mailet til dig. Følg linket for at aktivere din konto. Tjek spammappen, hvis e-mailen ikke dukker op i indbakken.
+      update_needs_confirmation: Du har opdateret din konto. Din nye e-mailadresse skal nu bekræftes. Til dette formål er du blevet e-mailet et bekræftelseslink, så følg dette for at bekræfte den nye e-mailadresse. Ser du ikke e-mailen i din indbakke snarest, så tjek Spam-mappen.
       updated: Din konto er nu opdateret.
     sessions:
       already_signed_out: Du er nu logget ud.
       signed_in: Du er nu logget ind.
       signed_out: Du er nu logget ud.
     unlocks:
-      send_instructions: Instruktioner mailes til dig om, hvordan du oplåser din konto. Er denne e-mail ikke er i din indbakke inden for få minutter, så tjek spammappe.
-      send_paranoid_instructions: Findes din konto, mailes du instrukser om, hvordan du oplåser den. Ser du ikke denne e-mail i din indbakke undenfor få minutter, så tjek spammappen.
+      send_instructions: Instruktioner e-mailes til dig om, hvordan du oplåser din konto. Er e-mailen ikke er i din indbakke inden for få minutter, så tjek i Spam.
+      send_paranoid_instructions: Findes din konto, e-mailes du instrukser om, hvordan du oplåser den. Er e-mailen i din indbakke indenfor få minutter, så tjek i Spam.
       unlocked: Din konto er nu oplåst. Log ind for at fortsætte.
   errors:
     messages:
diff --git a/config/locales/devise.ku.yml b/config/locales/devise.ku.yml
index 9ae76dab2..18187a156 100644
--- a/config/locales/devise.ku.yml
+++ b/config/locales/devise.ku.yml
@@ -8,10 +8,10 @@ ku:
     failure:
       already_authenticated: Jixwe te berê têketin kiriye.
       inactive: Ajimêra te hîn nehatiye çalakkirin.
-      invalid: Nederbasdar %{authentication_keys} an jî şîfre.
+      invalid: "%{authentication_keys} an jî borînpeyv nederbasdar e."
       last_attempt: Peşiya kilît kirina ajimêra te carek din jî biceribîne.
       locked: Ajimêra ye hat kilît kirin.
-      not_found_in_database: Nederbasdar %{authentication_keys} an jî şîfre.
+      not_found_in_database: "%{authentication_keys} an jî borînpeyv nederbasdar e."
       pending: Ajimêra te hîn tê vekolandin.
       timeout: Danişîna te qedîya. Ji kerema xwe ji bo berdewamiyê dîsa têkeve.
       unauthenticated: Peşiya berdewamiya te têketina xwe bike an jî xwe tomar bike.
@@ -33,10 +33,10 @@ ku:
         subject: 'Mastodon: E-name hate guhertin'
         title: Navnîşana e-nameya nû
       password_change:
-        explanation: Pêborîna ajimêra te hate guhertin.
+        explanation: Borînpeyva ajimêra te hate guhertin.
         extra: Heke te ajimêra xwe ne guhertiye. Ew tê wateya ku kesek ketiye ajimêrê te. Jkx pêborîna xwe zû biguherîne an jî bi rêveberiya rajekar re têkeve têkiliyê heke tu êdî nikare ajimêra xwe bi kar bînî.
         subject: 'Mastodon: pêborîn hate guhertin'
-        title: Pêborîn hate guhertin
+        title: Borînpeyv hate guhertin
       reconfirmation_instructions:
         explanation: Navnîşana nû piştrast bike da ku tu e-nameya xwe biguherînî.
         extra: |-
@@ -45,13 +45,13 @@ ku:
         subject: 'Mastodon: E-nameyê piştrast bike bo %{instance}'
         title: Navnîşana e-nameyê piştrast bike
       reset_password_instructions:
-        action: Pêborînê biguherîne
+        action: Borînpeyvê biguherîne
         explanation: Te ji bo ajimêra xwe daxwaza pêborîneke nû kiriye.
-        extra: Heke te ev daxwaz nekir, jkx guh nede vê e-nameyê. Pêborîna te wê neyê guhertin heya ku tu li girêdana Jêrin bitikînî û yeka nû çê bikî.
+        extra: Heke te ev daxwaz nekir, jkx guh nede vê e-nameyê. Borînpeyva te wê neyê guhertin heya ku tu li girêdana Jêrin bitikînî û yeka nû çê bikî.
         subject: 'Mastodon: rêwerzên jê birina pêborîn'
-        title: Pêborîn ji nû ve saz bike
+        title: Borînpeyv ji nû ve saz bike
       two_factor_disabled:
-        explanation: Ji bo ajimêrê te piştrastkirina du-faktorî hat asteng kirin. Niha tu tenê bi navnîşana e-name û şîfre ya xwe dikarî têketin bikî.
+        explanation: Ji bo ajimêrê te piştrastkirina du-faktorî hat asteng kirin. Niha tu tenê bi navnîşana e-name û borînpeyva xwe dikarî têketinê bikî.
         subject: 'Mastodon: piştrastkirina du- faktorî neçalak bike'
         title: 2FA Neçalak e
       two_factor_enabled:
@@ -85,11 +85,11 @@ ku:
       failure: Nikare ji %{kind} rastandinê bikê ji bo " %{reason}".
       success: Ji ajimêra %{kind} bi serkeftî hate rastandin.
     passwords:
-      no_token: Tu nikarî xwe bigihînî vê rûpelê bêyî ku tu ji e-nameya ji nû ve sazkirina pêborînê wernegerî. Heke tu ji e-nameya ji nû ve sazkirina pêborînê tê, ji kerema xwe pê ewle be ku tu girêdanê ya tevahî bi kar tînî.
-      send_instructions: Heke navnîşana te ya e-nameyê di danegeha me da hebê, tu yê di navnîşana xwe ya e-nameyê da girêdana rizgarkirina pêborînê bistînî. Heke te ev e-name wernegirtibe, ji kerema xwe peldanka xwe ya spamê kontrol bike.
-      send_paranoid_instructions: Heke navnîşana te ya e-nameyê di danegeha me da hebê, tu yê di navnîşana xwe ya e-nameyê da girêdana rizgarkirina pêborînê bistînî di hundir çend xulkan de. Heke te ev e-name wernegirtibe, ji kerema xwe peldanka xwe ya spamê kontrol bike.
-      updated: Pêborîna te bi serkeftî hate guhertin. Niha tu têketî ye.
-      updated_not_active: Pêborîna te bi serkeftî hate guhertin.
+      no_token: Tu nikarî xwe bigihînî vê rûpelê bêyî ku tu ji e-nameya ji nû ve sazkirina borînpeyvê wernegerî. Heke tu ji e-nameya ji nû ve sazkirina borînpeyvê tê, ji kerema xwe pê ewle be ku tu girêdanê ya tevahî bi kar tînî.
+      send_instructions: Heke navnîşana te ya e-nameyê di danegeha me da hebê, tu yê di navnîşana xwe ya e-nameyê da girêdana rizgarkirina borînpeyvê bistînî. Heke te ev e-name wernegirtibe, ji kerema xwe peldanka xwe ya spamê kontrol bike.
+      send_paranoid_instructions: Heke navnîşana te ya e-nameyê di danegeha me da hebê, tu yê di navnîşana xwe ya e-nameyê da girêdana rizgarkirina borînpeyvê bistînî di hundir çend xulkan de. Heke te ev e-name wernegirtibe, ji kerema xwe peldanka xwe ya spamê kontrol bike.
+      updated: Borînpeyva te bi serkeftî hate guhertin. Niha tu têketî ye.
+      updated_not_active: Borînpeyva te bi serkeftî hate guhertin.
     registrations:
       destroyed: Xatirê te! Ajimêra te bi serkeftî hate pûçkirin. Em hêvî dikin ku tu di nêzîk de te dîsa bibînin.
       signed_up: Bi xêr hatî! Te bi serkeftî tomarkirin kir.
diff --git a/config/locales/devise.th.yml b/config/locales/devise.th.yml
index 14f99f2c3..287ea49c5 100644
--- a/config/locales/devise.th.yml
+++ b/config/locales/devise.th.yml
@@ -12,7 +12,7 @@ th:
       last_attempt: คุณลองได้อีกหนึ่งครั้งก่อนที่บัญชีของคุณจะถูกล็อค
       locked: บัญชีของคุณถูกล็อค
       not_found_in_database: "%{authentication_keys} หรือรหัสผ่านไม่ถูกต้อง"
-      pending: บัญชีของคุณยังอยู่ระหว่างการตรวจทาน
+      pending: บัญชีของคุณยังคงอยู่ระหว่างการตรวจทาน
       timeout: เซสชันของคุณหมดอายุแล้ว โปรดลงชื่อเข้าอีกครั้งเพื่อดำเนินการต่อ
       unauthenticated: คุณจำเป็นต้องลงชื่อเข้าหรือลงทะเบียนก่อนดำเนินการต่อ
       unconfirmed: คุณต้องยืนยันที่อยู่อีเมลของคุณก่อนดำเนินการต่อ
diff --git a/config/locales/doorkeeper.ca.yml b/config/locales/doorkeeper.ca.yml
index a4d37f417..9725efe6c 100644
--- a/config/locales/doorkeeper.ca.yml
+++ b/config/locales/doorkeeper.ca.yml
@@ -13,9 +13,9 @@ ca:
           attributes:
             redirect_uri:
               fragment_present: no pot contenir un fragment.
-              invalid_uri: ha de ser un URI válid.
-              relative_uri: ha de ser un URI absoluta.
-              secured_uri: ha de ser un URI HTTPS/SSL.
+              invalid_uri: ha de ser una URI vàlida.
+              relative_uri: ha de ser una URI absoluta.
+              secured_uri: ha de ser una URI HTTPS/SSL.
   doorkeeper:
     applications:
       buttons:
@@ -82,9 +82,9 @@ ca:
       messages:
         access_denied: El propietari del recurs o servidor d'autorizació ha denegat la petició.
         credential_flow_not_configured: Les credencials de contrasenya del propietari del recurs han fallat degut a que Doorkeeper.configure.resource_owner_from_credentials està sense configurar.
-        invalid_client: La autentificació del client ha fallat perquè és un client desconegut o no està inclòs l'autentificació del client o el mètode d'autenticació no està confirmat.
-        invalid_grant: La concessió d'autorizació oferida és invàlida, ha vençut, s'ha revocat, no coincideix amb l'URI de redirecció utilizada en la petició d'autorizació, o fou emesa per a un altre client.
-        invalid_redirect_uri: L'URI de redirecció inclòs no és vàlid.
+        invalid_client: La autentificació del client ha fallat perquè és un client desconegut o no està inclòsa l'autentificació del client o el mètode d'autenticació no està confirmat.
+        invalid_grant: La concessió d'autorizació oferta és invàlida, ha vençut, s'ha revocat, no coincideix amb l'URI de redirecció utilizada en la petició d'autorizació, o fou emesa per a un altre client.
+        invalid_redirect_uri: L'uri de redirecció inclòsa no és vàlida.
         invalid_request:
           missing_param: 'Falta paràmetre requerit: %{value}.'
           request_not_authorized: La petició ha de ser autoritzada. Falta o és invàlid un paràmetre requerit per l'autorització de la petició.
@@ -154,7 +154,7 @@ ca:
       admin:write:accounts: fer l'acció de moderació en els comptes
       admin:write:reports: fer l'acció de moderació en els informes
       crypto: usa xifrat d'extrem a extrem
-      follow: seguir, blocar, desblocar i deixar de seguir comptes
+      follow: modificar relacions dels comptes
       push: rebre notificacions push del teu compte
       read: llegir les dades del teu compte
       read:accounts: veure informació dels comptes
@@ -168,13 +168,13 @@ ca:
       read:notifications: veure les teves notificacions
       read:reports: veure els teus informes
       read:search: cerca en nom teu
-      read:statuses: veure tots els tuts
-      write: publicar en el teu nom
+      read:statuses: veure tots les publicacions
+      write: modificar totes les dades del teu compte
       write:accounts: modifica el teu perfil
       write:blocks: bloqueja comptes i dominis
       write:bookmarks: publicacions a marcadors
       write:conversations: silencia i esborra converses
-      write:favourites: afavoreix tuts
+      write:favourites: afavoreix publicacions
       write:filters: crear filtres
       write:follows: seguir usuaris
       write:lists: crear llistes
@@ -182,4 +182,4 @@ ca:
       write:mutes: silencia usuaris i converses
       write:notifications: esborra les teves notificacions
       write:reports: informe d’altres persones
-      write:statuses: publicar tuts
+      write:statuses: publicar publicacions
diff --git a/config/locales/doorkeeper.da.yml b/config/locales/doorkeeper.da.yml
index 19f4307f6..094faedba 100644
--- a/config/locales/doorkeeper.da.yml
+++ b/config/locales/doorkeeper.da.yml
@@ -4,7 +4,7 @@ da:
     attributes:
       doorkeeper/application:
         name: Applikationsnavn
-        redirect_uri: Link
+        redirect_uri: Omdirigerings-URI
         scopes: Områder
         website: Applikationswebsted
     errors:
@@ -13,7 +13,7 @@ da:
           attributes:
             redirect_uri:
               fragment_present: kan ikke indeholde et fragment.
-              invalid_uri: skal være en gyldigt URI.
+              invalid_uri: skal være en gyldig URI.
               relative_uri: skal være en absolut URI.
               secured_uri: skal være en HTTPS-/SSL-URI.
   doorkeeper:
@@ -33,7 +33,7 @@ da:
       help:
         native_redirect_uri: Brug %{native_redirect_uri} til lokale tests
         redirect_uri: Brug én linje pr. URI
-        scopes: Adskil omfang med mellemrum. Lad være tomt for standardomfang.
+        scopes: Adskil omfang med mellemrum. Lad stå tomt for standardomfang.
       index:
         application: Applikation
         callback_url: Callback-URL
@@ -60,9 +60,9 @@ da:
       error:
         title: En fejl opstod
       new:
-        prompt_html: "%{client_name} ønsker tilladelse til at tilgå din konto. Den er en tredjepartsapplikation. <strong>Er der ikke tillid til den, bør den ikke godkendes.</strong>"
+        prompt_html: "%{client_name} ønsker tilladelse til at tilgå din konto. Den er en tredjepartsapplikation. <strong>Har du ikke tillid til den, bør den ikke godkendes.</strong>"
         review_permissions: Gennemgå tilladelser
-        title: Godkendelse krævet
+        title: Godkendelse kræves
       show:
         title: Kopiér og indsæt denne godkendelseskode i applikationen.
     authorized_applications:
@@ -72,7 +72,7 @@ da:
         revoke: Sikker?
       index:
         authorized_at: Godkendt pr. %{date}
-        description_html: Disse er applikationer, som kan tilgå din konto vha. API'en. Er der applikationer her, som ikke genkendes eller udviser mærkelig adfærd, kan deres adgang tilbagekaldes.
+        description_html: Disse er applikationer, som kan tilgå din konto vha. API'en. Er her applikationer, som ikke genkendes eller udviser mærkværdig adfærd, kan deres adgangstilladelse ophæves.
         last_used_at: Senest brugt pr. %{date}
         never_used: Aldrig brugt
         scopes: Tilladelser
@@ -81,25 +81,25 @@ da:
     errors:
       messages:
         access_denied: Ressourceejeren eller godkendelsesserveren afviste anmodningen.
-        credential_flow_not_configured: Ressourceejeradgangskodeakkreditiv flow mislykkedes grundet ikke-opsat Doorkeeper.configure.resource_owner_from_credentials.
-        invalid_client: Klientbekræftelse mislykkedes grundet en ukendt klient, ingen klientbekræftelse inkluderet, eller uunderstøttet bekræftelsesmetode.
-        invalid_grant: Den leverede godkendelse er ugyldig, udløbet, tilbagekaldt, matcher ikke omdirigerings-URI brugt i godkendelsesanmodningen, eller er udstedt til en anden klient.
+        credential_flow_not_configured: Ressourceejeradgangskodeakkreditiver-flow mislykkedes grundet ikke-opsat Doorkeeper.configure.resource_owner_from_credentials.
+        invalid_client: Klientgodkendelse mislykkedes grundet en ukendt klient, ingen inkluderet klientgodkendelse eller uunderstøttet godkendelsesmetode.
+        invalid_grant: Den leverede godkendelse er ugyldig, udløbet, ophævet, matcher ikke omdirigerings-URI'en brugt i godkendelsesanmodningen eller er udstedt til en anden klient.
         invalid_redirect_uri: Inkluderede ormdirigerings-URI er ugyldig.
         invalid_request:
-          missing_param: 'Mangler krævet parameter: %{value}.'
-          request_not_authorized: Anmodning skal godkendes. Krævet parameter til godkendelse af anmodning mangler eller er ugyldig.
-          unknown: Anmodningen mangler en krævet parametre, inkluderer en uunderstøttet parametre værdi eller er på anden vis fejlbehæftet.
-        invalid_resource_owner: De angivne ressourceejerakkreditiver er ugyldige, eller ressourceejer kunne ikke findes
-        invalid_scope: Det anmodede omfang er ugyldigt, ukendt eller fejlbehæftet.
+          missing_param: 'Mangler obligatoriske parameter: %{value}.'
+          request_not_authorized: Anmodning kræver godkendelse. Obligatorisk parameter til godkendelse af anmodning mangler eller er ugyldig.
+          unknown: Anmodningen mangler en obligatorisk parameter, indeholder en uunderstøttet parameterværdi eller er på anden vis fejlbehæftet.
+        invalid_resource_owner: De angivne ressourceejerakkreditiver er ugyldige, eller ressourceejer kan ikke findes
+        invalid_scope: Det anmodede omfang er ugyldigt, ukendt eller forkert udformet.
         invalid_token:
           expired: Adgangstoken er udløbet
-          revoked: Adgangstoken er tilbagekaldt
-          unknown: Adgangstoken er ugyldig
+          revoked: Adgangstoken er ophævet
+          unknown: Adgangstoken er ugyldigt
         resource_owner_authenticator_not_configured: Ressourceejer kunne ikke findes grundet ikke-opsat Doorkeeper.configure.resource_owner_authenticator.
         server_error: Godkejdelsesserveren stødte på en uventet betingelse, der forhindrede den i at imødekomme anmodningen.
         temporarily_unavailable: Godkendelsesserveren kan pt. ikke håndtere anmodningen grundet midlertidig overbelastning eller servervedligehold.
-        unauthorized_client: Klienten er ikke godkendt til at udføre denne anmodning via denne metode.
-        unsupported_grant_type: Godkendelsestypen understøttes ikke af godkendelsesserveren.
+        unauthorized_client: Klienten er ikke godkendt til at udføre denne anmodning vha. denne metode.
+        unsupported_grant_type: Godkendelsestildelingstypen understøttes ikke af godkendelsesserveren.
         unsupported_response_type: Godkendelsesserveren understøtter ikke denne svartype.
     flash:
       applications:
@@ -111,7 +111,7 @@ da:
           notice: Applikation opdateret.
       authorized_applications:
         destroy:
-          notice: Applikation tilbagekaldt.
+          notice: Applikation ophævet.
     grouped_scopes:
       access:
         read: Skrivebeskyttet adgang
@@ -133,7 +133,7 @@ da:
         follows: Følger
         lists: Lister
         media: Medievedhæftninger
-        mutes: Tavsgjorte
+        mutes: Tavsgørelser
         notifications: Notifikationer
         push: Push-notifikationer
         reports: Anmeldelser
@@ -143,43 +143,43 @@ da:
       admin:
         nav:
           applications: Applikationer
-          oauth2_provider: OAuth-udbyder
+          oauth2_provider: OAuth2-leverandør
       application:
-        title: OAuth-godkendelse krævet
+        title: OAuth-godkendelse obligatorisk
     scopes:
-      admin:read: læs al data på serveren
+      admin:read: læs alle data på serveren
       admin:read:accounts: læs sensitiv information fra alle konti
       admin:read:reports: læs sensitiv information fra alle anmeldelser og anmeldte konti
-      admin:write: redigér al data på serveren
+      admin:write: redigér alle data på serveren
       admin:write:accounts: udfør modereringshandlinger på konti
       admin:write:reports: udfør modereringshandlinger på anmeldelser
       crypto: benyt ende-til-ende kryptering
       follow: ændre kontorelationer
-      push: modtage dine push-notifikationer
-      read: læse alle dine kontodata
+      push: modtag dine push-notifikationer
+      read: læs alle dine kontodata
       read:accounts: se kontooplysninger
       read:blocks: se dine blokeringer
       read:bookmarks: se dine bogmærker
       read:favourites: se dine favoritter
       read:filters: se dine filtre
-      read:follows: se, hvem du følger
+      read:follows: se dine følger
       read:lists: se dine lister
       read:mutes: se dine tavsgørelser
       read:notifications: se dine notifikationer
       read:reports: se dine anmeldelser
-      read:search: søge på dine vegne
-      read:statuses: se alle statusser
+      read:search: søg på dine vegne
+      read:statuses: se alle indlæg
       write: ændre alle dine kontodata
       write:accounts: ændre din profil
       write:blocks: blokere konti og domæner
-      write:bookmarks: bogmærke statusser
-      write:conversations: tavsgør og slet konversationer
-      write:favourites: favoritmarkerede indlæg
+      write:bookmarks: bogmærke indlæg
+      write:conversations: tavsgøre og slette konversationer
+      write:favourites: favoritmarkere indlæg
       write:filters: oprette filtre
       write:follows: følge personer
       write:lists: oprette lister
-      write:media: uploade multimediefiler
+      write:media: uploade mediefiler
       write:mutes: tavsgøre personer og konversationer
       write:notifications: rydde dine notifikationer
       write:reports: anmelde personer
-      write:statuses: udgive statusser
+      write:statuses: udgive indlæg
diff --git a/config/locales/doorkeeper.fa.yml b/config/locales/doorkeeper.fa.yml
index cc479fbc1..9f455e64b 100644
--- a/config/locales/doorkeeper.fa.yml
+++ b/config/locales/doorkeeper.fa.yml
@@ -29,7 +29,7 @@ fa:
       edit:
         title: ویرایش برنامه
       form:
-        error: اوخ! ببینید چیزی را اشتباهی در فرم وارد نکرده‌اید؟
+        error: اوخ! ببینید چیزی را اشتباهی در فرم وارد نکرده‌اید
       help:
         native_redirect_uri: برای آزمایش‌های محلی %{native_redirect_uri} را به کار ببرید
         redirect_uri: هر URI را در یک سطر جدا بنویسید
@@ -60,6 +60,7 @@ fa:
       error:
         title: خطایی رخ داد
       new:
+        review_permissions: بازبینی اجازه‌ها
         title: نیاز به اجازه دادن
       show:
         title: این کد مجوز را کپی کرده و در برنامه وارد کنید.
@@ -69,6 +70,7 @@ fa:
       confirmations:
         revoke: آیا مطمئن هستید؟
       index:
+        scopes: اجازه‌ها
         title: برنامه‌های مجاز
     errors:
       messages:
@@ -104,6 +106,29 @@ fa:
       authorized_applications:
         destroy:
           notice: برنامه فسخ شد.
+    grouped_scopes:
+      access:
+        read: فقط دسترسی خواندن
+        read/write: دسترسی خواندن و نوشتن
+        write: فقط دسترسی نوشتن
+      title:
+        accounts: حساب‌ها
+        all: همه چیز
+        blocks: مسدودها
+        bookmarks: نشانک‌ها
+        conversations: گفت‌وگوها
+        crypto: رمزگذاری سرتاسری
+        favourites: پسندیده‌ها
+        filters: پالایه‌ها
+        follows: پی‌گرفتگان
+        lists: سیاهه‌ها
+        media: پیوست‌های رسانه‌ای
+        mutes: خموش‌ها
+        notifications: آگاهی‌ها
+        push: آگاهی‌های ارسالی
+        reports: گزارش‌ها
+        search: جست‌وجو
+        statuses: فرسته‌ها
     layouts:
       admin:
         nav:
@@ -118,11 +143,12 @@ fa:
       admin:write: تغییر تمام داده‌ها روی کارساز
       admin:write:accounts: انجام کنش مدیریتی روی حساب‌ها
       admin:write:reports: انجام کنش مدیریتی روی گزارش‌ها
+      crypto: از رمزگذاری سرتاسر استفاده کنید
       follow: پیگیری، مسدودسازی، لغو مسدودسازی، و لغو پیگیری حساب‌ها
       push: دریافت آگاهی‌ای ارسالیتان
       read: خواندن اطلاعات حساب شما
       read:accounts: دیدن اطّلاعات حساب
-      read:blocks: دیدن انسدادهایتان
+      read:blocks: دیدن مسدودهایتان
       read:bookmarks: دیدن نشانک‌هایتان
       read:favourites: دیدن برگزیده‌هایتان
       read:filters: دیدن پالایه‌هایتان
@@ -137,6 +163,7 @@ fa:
       write:accounts: تغییر نمایه‌تان
       write:blocks: انسداد حساب‌ها و دامنه‌ها
       write:bookmarks: نشانک‌گذاری وضعیت‌ها
+      write:conversations: مکالمات را بی‌صدا و حذف کنید
       write:favourites: برگزیدن وضعیت‌ها
       write:filters: ایحاد پالایش‌ها
       write:follows: پی‌گیری افراد
diff --git a/config/locales/doorkeeper.gd.yml b/config/locales/doorkeeper.gd.yml
index 217dca738..c5a830fc7 100644
--- a/config/locales/doorkeeper.gd.yml
+++ b/config/locales/doorkeeper.gd.yml
@@ -60,6 +60,8 @@ gd:
       error:
         title: Thachair mearachd
       new:
+        prompt_html: Bu mhiann le %{client_name} cead gus an cunntas agad inntrigeadh. Seo aplacaid threas-phàrtaidh. <strong>Mur eil earbsa agad ann, na ùghdarraich e.</strong>
+        review_permissions: Thoir sùil air na ceadan
         title: Tha feum air ùghdarrachadh
       show:
         title: Dèan lethbhreac dhen chòd ùghdarrachaidh seo ’s cuir san aplacaid e.
@@ -69,6 +71,12 @@ gd:
       confirmations:
         revoke: A bheil thu cinnteach?
       index:
+        authorized_at: Air ùghdarrachadh %{date}
+        description_html: Seo na h-aplacaidean as urrainn dhaibh an cunntas agad inntrigeadh leis an API. Ma tha aplacaid an-seo nach aithne dhut no ma tha droch-ghiùlan air aplacaid, ’s urrainn dhut an t-inntrigeadh aice a chùl-ghairm.
+        last_used_at: Air a chleachdadh %{date} an turas mu dheireadh
+        never_used: Cha deach a chleachdadh a-riamh
+        scopes: Ceadan
+        superapp: Inntearnail
         title: Na h-aplacaidean ùghdarraichte agad
     errors:
       messages:
@@ -104,6 +112,33 @@ gd:
       authorized_applications:
         destroy:
           notice: Chaidh an t-iarrtas a chùl-ghairm.
+    grouped_scopes:
+      access:
+        read: Inntrigeadh leughaidh a-mhàin
+        read/write: Inntrigeadh leughaidh is sgrìobhaidh
+        write: Inntrigeadh sgrìobhaidh a-mhàin
+      title:
+        accounts: Cunntasan
+        admin/accounts: Rianachd nan cunntas
+        admin/all: Gach gleus na rianachd
+        admin/reports: Rianachd nan gearan
+        all: A h-uile rud
+        blocks: Bacaidhean
+        bookmarks: Comharran-lìn
+        conversations: Còmhraidhean
+        crypto: Crioptachadh o cheann gu ceann
+        favourites: Annsachdan
+        filters: Criathragan
+        follow: Dàimhean
+        follows: Leantainn
+        lists: Liostaichean
+        media: Ceanglachain mheadhanan
+        mutes: Mùchaidhean
+        notifications: Brathan
+        push: Brathan putaidh
+        reports: Gearanan
+        search: Lorg
+        statuses: Postaichean
     layouts:
       admin:
         nav:
@@ -118,6 +153,7 @@ gd:
       admin:write: dàta sam bith atharrachadh air an fhrithealaiche
       admin:write:accounts: gnìomhan na maorsainneachd a ghabhail air cunntasan
       admin:write:reports: gnìomhan na maorsainneachd a ghabhail air gearanan
+      crypto: crioptachadh o cheann gu ceann a chleachdadh
       follow: dàimhean chunntasan atharrachadh
       push: na brathan putaidh agad fhaighinn
       read: dàta sam bith a’ cunntais agad a leughadh
@@ -137,6 +173,7 @@ gd:
       write:accounts: a’ phròifil agad atharrachadh
       write:blocks: cunntasan is àrainnean a bhacadh
       write:bookmarks: comharran-lìn a dhèanamh de phostaichean
+      write:conversations: còmhraidhean a mhùchadh is a sguabadh às
       write:favourites: postaichean a chur ris na h-annsachdan
       write:filters: criathragan a chruthachadh
       write:follows: leantainn air daoine
diff --git a/config/locales/doorkeeper.id.yml b/config/locales/doorkeeper.id.yml
index 050d97dc5..9a3fed94d 100644
--- a/config/locales/doorkeeper.id.yml
+++ b/config/locales/doorkeeper.id.yml
@@ -73,6 +73,10 @@ id:
       index:
         authorized_at: Diberi hak otorisasi pada %{date}
         description_html: Ini adalah aplikasi yang dapat mengakses akun Anda menggunakan API. Jika ada aplikasi yang tidak Anda kenal di sini, atau aplikasi yang berperilaku aneh, Anda dapat mencabut hak aksesnya.
+        last_used_at: Terakhir dipakai pada %{date}
+        never_used: Tidak pernah dipakai
+        scopes: Hak akses
+        superapp: Internal
         title: Aplikasi yang anda izinkan
     errors:
       messages:
@@ -108,6 +112,33 @@ id:
       authorized_applications:
         destroy:
           notice: Aplikasi dicabut.
+    grouped_scopes:
+      access:
+        read: Akses baca-saja
+        read/write: Akses baca dan tulis
+        write: Akses tulis-saja
+      title:
+        accounts: Akun
+        admin/accounts: Administrasi akun
+        admin/all: Semua fungsi administratif
+        admin/reports: Administrasi laporan
+        all: Segalanya
+        blocks: Blokir
+        bookmarks: Markah
+        conversations: Percakapan
+        crypto: Enkripsi end-to-end
+        favourites: Favorit
+        filters: Saringan
+        follow: Hubungan
+        follows: Mengikuti
+        lists: Daftar
+        media: Lampiran media
+        mutes: Bisukan
+        notifications: Notifikasi
+        push: Notifikasi dorong
+        reports: Laporan
+        search: Pencarian
+        statuses: Kiriman
     layouts:
       admin:
         nav:
@@ -122,6 +153,7 @@ id:
       admin:write: ubah semua data di server
       admin:write:accounts: lakukan aksi moderasi akun
       admin:write:reports: lakukan aksi moderasi laporan
+      crypto: menggunakan enkripsi end-to-end
       follow: mengikuti, blokir, menghapus blokir, dan berhenti mengikuti akun
       push: terima notifikasi dorong
       read: membaca data pada akun anda
@@ -141,6 +173,7 @@ id:
       write:accounts: ubah profil Anda
       write:blocks: blokir akun dan domain
       write:bookmarks: status markah
+      write:conversations: bisukan dan hapus percakapan
       write:favourites: status favorit
       write:filters: buat saringan
       write:follows: ikuti orang
diff --git a/config/locales/doorkeeper.ku.yml b/config/locales/doorkeeper.ku.yml
index 3a98486e3..f92a228d1 100644
--- a/config/locales/doorkeeper.ku.yml
+++ b/config/locales/doorkeeper.ku.yml
@@ -31,7 +31,7 @@ ku:
       form:
         error: Wey li min! kontrol bikeku form çewtî tê de tune
       help:
-        native_redirect_uri: Bo testên herêmî %{native_redirect_uri} bikar bîne
+        native_redirect_uri: Bo testên herêmî %{native_redirect_uri} bi kar bîne
         redirect_uri: Serê URl de rêzek bikarbînin
         scopes: Berfirehî bi valahîyan re veqetîne. Bo bikaranîna berfirehî ya standard vala bihêle.
       index:
@@ -81,7 +81,7 @@ ku:
     errors:
       messages:
         access_denied: Xwedîyê çavkanîyê an jî destûrmendê rajeker daxwazî red kirin.
-        credential_flow_not_configured: Herikîna pêborînê bawername ya xwediyê çavkaniyê, ji ber Doorkeeper.configure.resource_owner_from_credentials nehat pevsazkirin.
+        credential_flow_not_configured: Herikîna borînpeyvê bawername ya xwediyê çavkaniyê, ji ber Doorkeeper.configure.resource_owner_from_credentials nehat pevsazkirin.
         invalid_client: Erêkirina nasnameyê rajegir ji ber rajegirê nediyar têk çû, erêkirina nasnameyê rajegir di nav da tinne an jî rêbaza erêkirinê ne piştgirêdayî ye.
         invalid_grant: Mafê ku hatiye peyda kirin ne derbasdar e, qediya ye, pûç e, girêdana ya ku di daxwaza mafê de tê bikaranîn li hev nagire an jî rajegirekî din hildaye.
         invalid_redirect_uri: Girêdan beralîkirî ya di nav da ne derbasdar e.
@@ -130,7 +130,7 @@ ku:
         favourites: Bijarte
         filters: Parzûn
         follow: Pêwendî
-        follows: Şopîner
+        follows: Dişopîne
         lists: Rêzok
         media: Pêvekên medya
         mutes: Bêdengkirin
@@ -162,7 +162,7 @@ ku:
       read:bookmarks: şûnpelên xwe bibîne
       read:favourites: bijarteyên xwe bibîne
       read:filters: parzûnûn xwe bibîne
-      read:follows: şopînerên xwe bibîne
+      read:follows: ên tu dişopînî bibîne
       read:lists: rêzoka xwe bibîne
       read:mutes: ajimêrên bêdeng kirî bibîne
       read:notifications: agahdariyên xwe bibîne
diff --git a/config/locales/doorkeeper.nl.yml b/config/locales/doorkeeper.nl.yml
index cb0c70aab..76f3b88c3 100644
--- a/config/locales/doorkeeper.nl.yml
+++ b/config/locales/doorkeeper.nl.yml
@@ -60,6 +60,8 @@ nl:
       error:
         title: Er is een fout opgetreden
       new:
+        prompt_html: "%{client_name} heeft toestemming nodig om toegang te krijgen tot jouw account. Het betreft een third-party-toepassing.<strong>Als je dit niet vertrouwt, moet je geen toestemming verlenen.</strong>"
+        review_permissions: Toestemmingen beoordelen
         title: Autorisatie vereist
       show:
         title: Kopieer deze autorisatiecode en plak het in de toepassing.
@@ -69,6 +71,11 @@ nl:
       confirmations:
         revoke: Weet je het zeker?
       index:
+        authorized_at: Toestemming verleent op %{date}
+        last_used_at: Voor het laatst gebruikt op %{date}
+        never_used: Nooit gebruikt
+        scopes: Toestemmingen
+        superapp: Intern
         title: Jouw geautoriseerde toepassingen
     errors:
       messages:
@@ -104,6 +111,33 @@ nl:
       authorized_applications:
         destroy:
           notice: Toepassing ingetrokken.
+    grouped_scopes:
+      access:
+        read: Alleen leestoegang
+        read/write: Lees- en schrijftoegang
+        write: Alleen schrijftoegang
+      title:
+        accounts: Accounts
+        admin/accounts: Accountbeheer
+        admin/all: Alle beheerfuncties
+        admin/reports: Rapportagebeheer
+        all: Alles
+        blocks: Blokkeren
+        bookmarks: Bladwijzers
+        conversations: Gesprekken
+        crypto: End-to-end-encryptie
+        favourites: Favorieten
+        filters: Filters
+        follow: Relaties
+        follows: Volgend
+        lists: Lijsten
+        media: Mediabijlagen
+        mutes: Negeren
+        notifications: Meldingen
+        push: Pushmeldingen
+        reports: Rapportages
+        search: Zoeken
+        statuses: Berichten
     layouts:
       admin:
         nav:
@@ -118,6 +152,7 @@ nl:
       admin:write: wijzig alle gegevens op de server
       admin:write:accounts: moderatieacties op accounts uitvoeren
       admin:write:reports: moderatieacties op rapportages uitvoeren
+      crypto: end-to-end-encryptie gebruiken
       follow: relaties tussen accounts bewerken
       push: jouw pushmeldingen ontvangen
       read: alle gegevens van jouw account lezen
@@ -130,14 +165,15 @@ nl:
       read:lists: jouw lijsten bekijken
       read:mutes: jouw genegeerde gebruikers bekijken
       read:notifications: jouw meldingen bekijken
-      read:reports: jouw gerapporteerde toots bekijken
+      read:reports: jouw gerapporteerde berichten bekijken
       read:search: namens jou zoeken
-      read:statuses: alle toots bekijken
+      read:statuses: alle berichten bekijken
       write: alle gegevens van jouw account bewerken
       write:accounts: jouw profiel bewerken
       write:blocks: accounts en domeinen blokkeren
-      write:bookmarks: toots aan bladwijzers toevoegen
-      write:favourites: toots als favoriet markeren
+      write:bookmarks: berichten aan bladwijzers toevoegen
+      write:conversations: gespreken negeren en verwijderen
+      write:favourites: berichten als favoriet markeren
       write:filters: filters aanmaken
       write:follows: mensen volgen
       write:lists: lijsten aanmaken
@@ -145,4 +181,4 @@ nl:
       write:mutes: mensen en gesprekken negeren
       write:notifications: meldingen verwijderen
       write:reports: andere mensen rapporteren
-      write:statuses: toots publiceren
+      write:statuses: berichten plaatsen
diff --git a/config/locales/doorkeeper.pt-BR.yml b/config/locales/doorkeeper.pt-BR.yml
index 7c77af88b..93cd0c55a 100644
--- a/config/locales/doorkeeper.pt-BR.yml
+++ b/config/locales/doorkeeper.pt-BR.yml
@@ -113,6 +113,7 @@ pt-BR:
         accounts: Contas
         all: Tudo
         blocks: Blocos
+        bookmarks: Salvos
         conversations: Conversas
         crypto: Criptografia de ponta a ponta
         favourites: Favoritos
diff --git a/config/locales/doorkeeper.sk.yml b/config/locales/doorkeeper.sk.yml
index 3f92a67ae..3e29a3332 100644
--- a/config/locales/doorkeeper.sk.yml
+++ b/config/locales/doorkeeper.sk.yml
@@ -101,6 +101,7 @@ sk:
           notice: Oprávnenia aplikácie zrušené.
     grouped_scopes:
       title:
+        blocks: Blokovania
         mutes: Nevšímané
     layouts:
       admin:
diff --git a/config/locales/doorkeeper.uk.yml b/config/locales/doorkeeper.uk.yml
index 44562f4b8..e9000cf46 100644
--- a/config/locales/doorkeeper.uk.yml
+++ b/config/locales/doorkeeper.uk.yml
@@ -60,6 +60,7 @@ uk:
       error:
         title: Сталася помилка
       new:
+        review_permissions: Переглянути дозволи
         title: Необхідна авторизація
       show:
         title: Скопіюйте цей код авторизації та вставте його у додаток.
@@ -69,8 +70,11 @@ uk:
       confirmations:
         revoke: Ви впевнені?
       index:
+        authorized_at: Авторизовано %{date}
+        last_used_at: Востаннє використано %{date}
         never_used: Ніколи не використовувалися
         scopes: Дозволи
+        superapp: Внутрішній
         title: Ваші авторизовані додатки
     errors:
       messages:
@@ -107,13 +111,27 @@ uk:
         destroy:
           notice: Авторизацію додатка відкликано.
     grouped_scopes:
+      access:
+        read: Доступ без права внесення змін
+        read/write: Доступ до читання і запису
+        write: Доступ лише для запису
       title:
         accounts: Облікові записи
+        admin/accounts: Адміністрація облікових записів
+        admin/all: Усі адміністративні функції
+        admin/reports: Адміністрація звітів
         all: Усе
+        blocks: Блокування
+        bookmarks: Закладки
+        conversations: Бесіди
         crypto: Наскрізне шифрування
+        favourites: Вподобане
         filters: Фільтри
+        follow: Взаємозв'язки
+        follows: Підписки
         lists: Списки
         media: Мультимедійні вкладення
+        mutes: Заглушені
         notifications: Сповіщення
         push: Push-сповіщення
         reports: Скарги
@@ -153,6 +171,7 @@ uk:
       write:accounts: змінювати ваш профіль
       write:blocks: блокувати облікові записи і домени
       write:bookmarks: додавати пости в закладки
+      write:conversations: заглушити і видалити розмови
       write:favourites: вподобані статуси
       write:filters: створювати фільтри
       write:follows: підписуйтесь на людей
diff --git a/config/locales/doorkeeper.vi.yml b/config/locales/doorkeeper.vi.yml
index 4acfd9977..ecd5cfc4c 100644
--- a/config/locales/doorkeeper.vi.yml
+++ b/config/locales/doorkeeper.vi.yml
@@ -71,13 +71,13 @@ vi:
       confirmations:
         revoke: Bạn có chắc không?
       index:
-        authorized_at: Cấp quyền vào %{date}
+        authorized_at: Cho phép vào %{date}
         description_html: Đây là những ứng dụng có thể truy cập tài khoản của bạn bằng API. Nếu có ứng dụng bạn không nhận ra ở đây hoặc ứng dụng hoạt động sai, bạn có thể thu hồi quyền truy cập của ứng dụng đó.
-        last_used_at: Dùng gần nhất vào %{date}
-        never_used: Chưa dùng bao giờ
+        last_used_at: Dùng lần cuối %{date}
+        never_used: Chưa dùng
         scopes: Quyền cho phép
         superapp: Đang dùng
-        title: Các ứng dụng đang cho phép
+        title: Các ứng dụng đang dùng
     errors:
       messages:
         access_denied: Chủ sở hữu tài nguyên hoặc máy chủ đã từ chối yêu cầu.
diff --git a/config/locales/en.yml b/config/locales/en.yml
index d4a42e867..829cd61d0 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -168,7 +168,6 @@ en:
       previous_strikes_description_html:
         one: This account has <strong>one</strong> strike.
         other: This account has <strong>%{count}</strong> strikes.
-        zero: This account is <strong>in good standing</strong>.
       promote: Promote
       protocol: Protocol
       public: Public
@@ -490,6 +489,7 @@ en:
           other: Failed attempts on %{count} different days.
         no_failures_recorded: No failures on record.
         title: Availability
+        warning: The last attempt to connect to this server has been unsuccessful
       back_to_all: All
       back_to_limited: Limited
       back_to_warning: Warning
@@ -529,7 +529,6 @@ en:
       known_accounts:
         one: "%{count} known account"
         other: "%{count} known accounts"
-        zero: No known account
       moderation:
         all: All
         limited: Limited
@@ -774,6 +773,11 @@ en:
     system_checks:
       database_schema_check:
         message_html: There are pending database migrations. Please run them to ensure the application behaves as expected
+      elasticsearch_running_check:
+        message_html: Could not connect to Elasticsearch. Please check that it is running, or disable full-text search
+      elasticsearch_version_check:
+        message_html: 'Incompatible Elasticsearch version: %{value}'
+        version_comparison: Elasticsearch %{running_version} is running while %{required_version} is required
       rules_check:
         action: Manage server rules
         message_html: You haven't defined any server rules.
@@ -796,7 +800,6 @@ en:
         shared_by_over_week:
           one: Shared by one person over the last week
           other: Shared by %{count} people over the last week
-          zero: Shared by noone over the last week
         title: Trending links
         usage_comparison: Shared %{today} times today, compared to %{yesterday} yesterday
       pending_review: Pending review
@@ -839,7 +842,6 @@ en:
         used_by_over_week:
           one: Used by one person over the last week
           other: Used by %{count} people over the last week
-          zero: Used by noone over the last week
       title: Trends
     warning_presets:
       add_new: Add new
diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml
index 7dee30a27..36b46eb47 100644
--- a/config/locales/es-AR.yml
+++ b/config/locales/es-AR.yml
@@ -168,7 +168,6 @@ es-AR:
       previous_strikes_description_html:
         one: Esta cuenta tiene <strong>un</strong> incumplimiento.
         other: Esta cuenta tiene <strong>%{count}</strong> incumplimientos.
-        zero: Esta cuenta está <strong>en buen estado</strong>.
       promote: Promover
       protocol: Protocolo
       public: Pública
@@ -490,6 +489,7 @@ es-AR:
           other: Intentos fallidos en %{count} días.
         no_failures_recorded: No hay fallos en el registro.
         title: Disponibilidad
+        warning: El último intento de conexión a este servidor no fue exitoso
       back_to_all: Todos
       back_to_limited: Limitados
       back_to_warning: Advertencia
@@ -529,7 +529,6 @@ es-AR:
       known_accounts:
         one: "%{count} cuenta conocida"
         other: "%{count} cuentas conocidas"
-        zero: Ninguna cuenta conocida
       moderation:
         all: Todas
         limited: Limitadas
@@ -774,6 +773,11 @@ es-AR:
     system_checks:
       database_schema_check:
         message_html: Hay migraciones pendientes de la base de datos. Por favor, ejecutalas para asegurarte de que la aplicación funciona según lo esperado
+      elasticsearch_running_check:
+        message_html: No se pudo conectar a Elasticsearch. Por favor, revisá que se esté ejecutando, o deshabilitá la búsqueda de texto completo
+      elasticsearch_version_check:
+        message_html: 'Versión incompatible de Elasticsearch: %{value}'
+        version_comparison: Se está ejecutando la versión %{running_version} de Elasticsearch, mientras que la versión requerida es la %{required_version}
       rules_check:
         action: Administrar reglas del servidor
         message_html: No definiste ninguna regla del servidor.
@@ -794,9 +798,8 @@ es-AR:
         disallow: Rechazar enlace
         disallow_provider: Rechazar medio
         shared_by_over_week:
-          one: Compartido por una persona en la última semana
-          other: Compartido por %{count} personas en la última semana
-          zero: Compartido por nadie en la última semana
+          one: Compartido por una persona durante la última semana
+          other: Compartido por %{count} personas durante la última semana
         title: Enlaces en tendencia
         usage_comparison: Compartido %{today} veces hoy, comparado con la/s %{yesterday} vez/veces de ayer
       pending_review: Revisión pendiente
@@ -837,9 +840,8 @@ es-AR:
         usable: Pueden usarse
         usage_comparison: Usadas %{today} veces hoy, comparado con la/s %{yesterday} vez/veces de ayer
         used_by_over_week:
-          one: Usada por una persona en la última semana
-          other: Usada por %{count} personas en la última semana
-          zero: Usada por nadie en la última semana
+          one: Usada por una persona durante la última semana
+          other: Usada por %{count} personas durante la última semana
       title: Tendencias
     warning_presets:
       add_new: Agregar nuevo
diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml
index da321d7ee..17af40f2c 100644
--- a/config/locales/es-MX.yml
+++ b/config/locales/es-MX.yml
@@ -165,10 +165,6 @@ es-MX:
       pending: Revisión pendiente
       perform_full_suspension: Suspender
       previous_strikes: Amonestaciones anteriores
-      previous_strikes_description_html:
-        one: Esta cuenta tiene <strong>una</strong> amonestación.
-        other: Esta cuenta tiene <strong>%{count}</strong> amonestaciones.
-        zero: Esta cuenta está <strong>en buen estado</strong>.
       promote: Promocionar
       protocol: Protocolo
       public: Público
@@ -490,6 +486,7 @@ es-MX:
           other: Intentos fallidos en %{count} días diferentes.
         no_failures_recorded: No hay fallos en el registro.
         title: Disponibilidad
+        warning: El último intento de conexión a este servidor no ha tenido éxito
       back_to_all: Todos
       back_to_limited: Limitados
       back_to_warning: Advertencia
@@ -526,10 +523,6 @@ es-MX:
       delivery_error_hint: Si la entrega no es posible a lo largo de %{count} días, se marcará automáticamente como no entregable.
       destroyed_msg: Los datos de %{domain} están ahora en cola para su inminente eliminación.
       empty: No se encontraron dominios.
-      known_accounts:
-        one: "%{count} cuenta conocida"
-        other: "%{count} cuentas conocidas"
-        zero: Ninguna cuenta conocida
       moderation:
         all: Todos
         limited: Limitado
@@ -774,6 +767,11 @@ es-MX:
     system_checks:
       database_schema_check:
         message_html: Hay migraciones pendientes de la base de datos. Por favor, ejecútalas para asegurarte de que la aplicación funciona como debería
+      elasticsearch_running_check:
+        message_html: No se pudo conectar a Elasticsearch. Por favor, comprueba que está ejecutándose, o desactiva la búsqueda de texto completo
+      elasticsearch_version_check:
+        message_html: 'Versión incompatible de Elasticsearch: %{value}'
+        version_comparison: Elasticsearch %{running_version} se está ejecutando pero se necesita Elasticsearch %{required_version}
       rules_check:
         action: Administrar reglas del servidor
         message_html: No ha definido ninguna regla del servidor.
@@ -793,10 +791,6 @@ es-MX:
         description_html: Estos son enlaces que actualmente están siendo compartidos mucho por las cuentas desde las que tu servidor ve los mensajes. Pueden ayudar a tus usuarios a averiguar qué está pasando en el mundo. Ningún enlace se muestren públicamente hasta que autorice al dominio. También puede permitir o rechazar enlaces individuales.
         disallow: Rechazar enlace
         disallow_provider: Rechazar editor
-        shared_by_over_week:
-          one: Compartido por una persona en la última semana
-          other: Compartido por %{count} personas durante la última semana
-          zero: Compartido por nadie en la última semana
         title: Enlaces en tendencia
         usage_comparison: Compartido %{today} veces hoy, comparado a %{yesterday} ayer
       pending_review: Revisión pendiente
@@ -836,10 +830,6 @@ es-MX:
         trending_rank: Tendencia n.º %{rank}
         usable: Pueden usarse
         usage_comparison: Usada %{today} veces hoy, comparado con %{yesterday} ayer
-        used_by_over_week:
-          one: Usada por una persona durante la última semana
-          other: Usada por %{count} personas durante la última semana
-          zero: Usada por nadie en la última semana
       title: Tendencias
     warning_presets:
       add_new: Añadir nuevo
diff --git a/config/locales/es.yml b/config/locales/es.yml
index bcce44e20..2ac09ea44 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -168,7 +168,6 @@ es:
       previous_strikes_description_html:
         one: Esta cuenta tiene <strong>una</strong> amonestación.
         other: Esta cuenta tiene <strong>%{count}</strong> amonestaciones.
-        zero: Esta cuenta está <strong>en buen estado</strong>.
       promote: Promocionar
       protocol: Protocolo
       public: Público
@@ -490,6 +489,7 @@ es:
           other: Intentos fallidos en %{count} días diferentes.
         no_failures_recorded: No hay fallos en el registro.
         title: Disponibilidad
+        warning: El último intento de conexión a este servidor no ha tenido éxito
       back_to_all: Todos
       back_to_limited: Limitados
       back_to_warning: Advertencia
@@ -529,7 +529,6 @@ es:
       known_accounts:
         one: "%{count} cuenta conocida"
         other: "%{count} cuentas conocidas"
-        zero: Ninguna cuenta conocida
       moderation:
         all: Todos
         limited: Limitado
@@ -774,6 +773,11 @@ es:
     system_checks:
       database_schema_check:
         message_html: Hay migraciones pendientes de la base de datos. Por favor, ejecútalas para asegurarte de que la aplicación funciona como debería
+      elasticsearch_running_check:
+        message_html: No se pudo conectar a Elasticsearch. Por favor, comprueba que está ejecutándose, o desactiva la búsqueda de texto completo
+      elasticsearch_version_check:
+        message_html: 'Versión incompatible de Elasticsearch: %{value}'
+        version_comparison: Elasticsearch %{running_version} se está ejecutando pero se necesita Elasticsearch %{required_version}
       rules_check:
         action: Administrar reglas del servidor
         message_html: No ha definido ninguna regla del servidor.
@@ -794,9 +798,8 @@ es:
         disallow: Rechazar enlace
         disallow_provider: Rechazar medio
         shared_by_over_week:
-          one: Compartido por una persona en la última semana
+          one: Compartido por una persona durante la última semana
           other: Compartido por %{count} personas durante la última semana
-          zero: Compartido por nadie en la última semana
         title: Enlaces en tendencia
         usage_comparison: Compartido %{today} veces hoy, comparado con %{yesterday} ayer
       pending_review: Revisión pendiente
@@ -839,7 +842,6 @@ es:
         used_by_over_week:
           one: Usada por una persona durante la última semana
           other: Usada por %{count} personas durante la última semana
-          zero: Usada por nadie en la última semana
       title: Tendencias
     warning_presets:
       add_new: Añadir nuevo
diff --git a/config/locales/eu.yml b/config/locales/eu.yml
index d6f49058e..a41a77baf 100644
--- a/config/locales/eu.yml
+++ b/config/locales/eu.yml
@@ -163,10 +163,6 @@ eu:
       pending: Berrikusketa egiteke
       perform_full_suspension: Kanporatu
       previous_strikes: Aurreko abisuak
-      previous_strikes_description_html:
-        one: Kontu honek abisu <strong>bat</strong> dauka.
-        other: Kontu honek <strong>%{count}</strong> abisu dauzka.
-        zero: Kontu honek <strong>ez dauka abisurik</strong>.
       promote: Sustatu
       protocol: Protokoloa
       public: Publikoa
@@ -497,10 +493,6 @@ eu:
       delivery_error_hint: Banaketa ezin bada %{count} egunean egin, banaezin bezala markatuko da automatikoki.
       destroyed_msg: "%{domain} domeinuko datuak berehala ezabatzeko ilaran daude orain."
       empty: Ez da domeinurik aurkitu.
-      known_accounts:
-        one: Kontu ezagun %{count}
-        other: "%{count} kontu ezagun"
-        zero: Kontu ezagunik ez
       moderation:
         all: Denak
         limited: Mugatua
diff --git a/config/locales/fa.yml b/config/locales/fa.yml
index 1776505ba..2379e63da 100644
--- a/config/locales/fa.yml
+++ b/config/locales/fa.yml
@@ -16,6 +16,7 @@ fa:
     contact: تماس
     contact_missing: تنظیم نشده
     contact_unavailable: موجود نیست
+    continue_to_web: در کارهٔ وب ادامه دهید
     discover_users: یافتن کاربران
     documentation: مستندات
     federation_hint_html: با حسابی روی %{instance} می‌توانید افراد روی هر کارساز ماستودون و بیش از آن را پی بگیرید.
@@ -25,6 +26,8 @@ fa:
       این حساب، بازیگری مجازی به نمایندگی خود کارساز بوده و کاربری واقعی نیست.
       این حساب برای مقاصد خودگردانی به کار می‌رفته و نباید مسدود شود؛ مگر این که بخواهید کل نمونه را مسدود کنید که در آن صورت نیز باید از انسداد دامنه استفاده کنید.
     learn_more: بیشتر بدانید
+    logged_in_as_html: شما هم‌اکنون به عنوان %{username} وارد شده‌اید.
+    logout_before_registering: شما هم‌اکنون وارد شده‌اید.
     privacy_policy: سیاست رازداری
     rules: قوانین کارساز
     rules_html: 'در زیر خلاصه‌ای از قوانینی که در صورت علاقه به داشتن حسابی روی این کارساز ماستودون، باید رعایت کنید آمده است:'
@@ -75,10 +78,10 @@ fa:
     pin_errors:
       following: باید کاربری که می‌خواهید پیشنهاد دهید را دنبال کرده باشید
     posts:
-      one: بوق
-      other: بوق
-    posts_tab_heading: بوق‌ها
-    posts_with_replies: بوق‌ها و پاسخ‌ها
+      one: فرسته
+      other: فرسته‌ها
+    posts_tab_heading: فرسته‌ها
+    posts_with_replies: فرسته‌ها و پاسخ‌ها
     roles:
       admin: مدیر
       bot: ربات
@@ -161,6 +164,7 @@ fa:
       not_subscribed: مشترک نیست
       pending: در انتظار بررسی
       perform_full_suspension: تعلیق
+      previous_strikes: اخطارهای پیشین
       promote: ترفیع‌دادن
       protocol: پروتکل
       public: عمومی
@@ -202,8 +206,9 @@ fa:
       silence: خموشاندن
       silenced: خموشانده
       statuses: نوشته‌ها
-      strikes: اخطار های پیشین
+      strikes: اخطارهای پیشین
       subscribe: اشتراک
+      suspend: تعلیق
       suspended: تعلیق‌شده
       suspension_irreversible: داده‌های این حساب به صورت بی‌بازگشت حذف شد. می‌توانید برای قابل استفاده کردنش، آن را نامعلّق کنید، ولی این کار هیچ داده‌ای را که از پیش داده، برنخواهد گرداند.
       suspension_reversible_hint_html: حساب معلّق شد و داده‌ها به صورت کامل در %{date} برداشته خواهند شد. تا آن زمان، حساب می‌تواند بی هیچ عوارضی بازگردانده شود. اگر می‌خواهید فوراً همهٔ داده‌های حساب را بردارید، می‌توانید در پایین این کار را بکنید.
@@ -224,6 +229,7 @@ fa:
       whitelisted: فهرست مجاز
     action_logs:
       action_types:
+        approve_appeal: پذیرش درخواست تجدیدنظر
         approve_user: تایید کاربر
         assigned_to_self_report: واگذاری گزارش
         change_email_user: تغییر رایانامه برای کاربر
@@ -255,6 +261,7 @@ fa:
         enable_user: به کار انداختن کاربر
         memorialize_account: یادسپاری حساب
         promote_user: ترفیع کاربر
+        reject_appeal: رد کردن درخواست تجدیدنظر
         reject_user: رد کاربر
         remove_avatar_user: برداشتن تصویر نمایه
         reopen_report: بازگشایی گزارش
@@ -273,6 +280,7 @@ fa:
         update_domain_block: به‌روزرسانی مسدودسازی دامنه
         update_status: به‌روز رسانی وضعیت
       actions:
+        approve_appeal_html: "%{name} درخواست تجدیدنظر تصمیم مدیر را از %{target} پذیرفت"
         approve_user_html: "%{name} ثبت نام %{target} را تایید کرد"
         assigned_to_self_report_html: "%{name} رسیدگی به گزارش %{target} را به عهده گرفت"
         change_email_user_html: "%{name} نشانی رایانامهٔ کاربر %{target} را عوض کرد"
@@ -304,6 +312,7 @@ fa:
         enable_user_html: "%{name} ورود را برای کاربر %{target} فعال کرد"
         memorialize_account_html: "%{name} حساب %{target} را تبدیل به صفحهٔ یادمان کرد"
         promote_user_html: "%{name} کاربر %{target} را ترفیع داد"
+        reject_appeal_html: "%{name} درخواست تجدیدنظر تصمیم مدیر را از %{target} رد کرد"
         reject_user_html: "%{name} ثبت نام %{target} را رد کرد"
         remove_avatar_user_html: "%{name} تصویر نمایهٔ %{target} را حذف کرد"
         reopen_report_html: "%{name} گزارش %{target} را دوباره به جریان انداخت"
@@ -381,6 +390,18 @@ fa:
       media_storage: ذخیره‌ساز رسانه
       new_users: کاربران جدید
       opened_reports: گزارش باز شده
+      pending_appeals_html:
+        one: "<strong>%{count}</strong> درخواست تجدیدنظر در انتظار"
+        other: "<strong>%{count}</strong> درخواست تجدیدنظر در انتظار"
+      pending_reports_html:
+        one: "<strong>%{count}</strong> گزارش در انتظار"
+        other: "<strong>%{count}</strong> گزارش در انتظار"
+      pending_tags_html:
+        one: "<strong>%{count}</strong> هشتگ در انتظار"
+        other: "<strong>%{count}</strong> هشتگ در انتظار"
+      pending_users_html:
+        one: "<strong>%{count}</strong> کاربر در انتظار"
+        other: "<strong>%{count}</strong> کاربر در انتظار"
       resolved_reports: گزارش حل شده
       software: نرم‌افزار
       sources: منابع ثبت‌نام
@@ -389,6 +410,10 @@ fa:
       top_languages: زبان‌های فعّال‌تر
       top_servers: کاربران فعّال‌تر
       website: پایگاه وب
+    disputes:
+      appeals:
+        empty: هیچ درخواست تجدیدنظری یافت نشد
+        title: درخواست‌های تجدیدنظر
     domain_allows:
       add_new: مجاز کردن دامنه
       created_msg: دامنه با موفقیت مجاز شد
@@ -497,11 +522,11 @@ fa:
     relays:
       add_new: افزودن رلهٔ تازه
       delete: حذف
-      description_html: یک <strong>رلهٔ میان‌سروری</strong> (federation relay) یک سرور میانجی است که حجم زیادی از بوق‌های عمومی را بین سرورهای گوناگونی که عضوش می‌شوند جابه‌جا می‌کند. <strong>رله‌ها به سرورهای کوچک و متوسط کمک می‌کنند تا مطالب عمومی بیشتری را بیابند.</strong> اگر رله نباشد، این مطالب عمومی تنها وقتی پیدا می‌شوند که کاربران محلی خودشان پیگیر کاربران روی سرورهای دیگر شوند.
+      description_html: یک <strong>رلهٔ میان‌سروری</strong> (federation relay) یک سرور میانجی است که حجم زیادی از فرسته‌های عمومی را بین سرورهای گوناگونی که عضوش می‌شوند جابه‌جا می‌کند. <strong>رله‌ها به سرورهای کوچک و متوسط کمک می‌کنند تا مطالب عمومی بیشتری را بیابند.</strong> اگر رله نباشد، این مطالب عمومی تنها وقتی پیدا می‌شوند که کاربران محلی خودشان پیگیر کاربران روی سرورهای دیگر شوند.
       disable: از کار انداختن
       disabled: از کار افتاده
       enable: به کار انداختن
-      enable_hint: اگر فعال باشد، کارساز شما عضو همهٔ بوق‌های عمومی‌ای را که از این رله می‌آید می‌گیرد، و بوق‌های عمومی این کارساز را به آن می‌فرستند.
+      enable_hint: اگر فعال باشد، کارساز شما عضو همهٔ فرسته‌های عمومی‌ای را که از این رله می‌آید می‌گیرد، و فرسته‌های عمومی این کارساز را به آن می‌فرستند.
       enabled: فعال
       inbox_url: نشانی رله
       pending: در انتظار پذیرش رله
@@ -567,10 +592,10 @@ fa:
       title: قوانین کارساز
     settings:
       activity_api_enabled:
-        desc_html: تعداد بوق‌های محلی، کاربران فعال، و کاربران تازه در هر هفته
+        desc_html: تعداد فرسته‌های محلی، کاربران فعال، و کاربران تازه در هر هفته
         title: انتشار آمار تجمیعی دربارهٔ فعالیت کاربران
       bootstrap_timeline_accounts:
-        desc_html: نام‌های کاربری را با ویرگول از هم جدا کنید. تنها حساب‌های محلی و قفل‌نشده کار می‌کنند. اگر این‌جا را خالی بگذارید، به طور پیش‌فرض همهٔ مدیرهای این سرور پی‌گرفته خواهند شد.
+        desc_html: نام‌های کاربری را با ویرگول از هم جدا کنید. این حساب‌ها تضمین می‌شوند که در پیشنهادهای پی‌گیری نشان داده شوند
         title: پیگیری‌های پیش‌فرض برای کاربران تازه
       contact_information:
         email: ایمیل کاری
@@ -584,7 +609,7 @@ fa:
       domain_blocks:
         all: برای همه
         disabled: برای هیچ‌کدام
-        title: نمایش دامین‌های مسدودشده
+        title: نمایش دامنه‌های مسدود شده
         users: برای کاربران محلی واردشده
       domain_blocks_rationale:
         title: دیدن دلیل
@@ -623,7 +648,7 @@ fa:
           open: همه می‌توانند ثبت نام کنند
         title: شرایط ثبت نام
       show_known_fediverse_at_about_page:
-        desc_html: اگر انتخاب شود، بوق‌های همهٔ سرورهای دیگر نیز در پیش‌نمایش این سرور نمایش می‌یابد. وگرنه فقط بوق‌های محلی نشان داده می‌شوند.
+        desc_html: اگر از کار انداخته شود، خط‌زمانی همگانی را محدود می‌کند؛ تا فقط محتوای محلّی را نمایش دهد.
         title: نمایش سرورهای دیگر در پیش‌نمایش این سرور
       show_staff_badge:
         desc_html: نمایش علامت همکار روی صفحهٔ کاربر
@@ -653,7 +678,7 @@ fa:
         title: بگذارید که برچسب‌های پرطرفدار بدون بازبینی قبلی نمایش داده شوند
       trends:
         desc_html: برچسب‌های عمومی که پیش‌تر بازبینی شده‌اند و هم‌اینک پرطرفدارند
-        title: برچسب‌های پرطرفدار
+        title: پرطرفدارها
     site_uploads:
       delete: پرونده بارگذاری شده را پاک کنید
       destroyed_msg: بارگذاری پایگاه با موفقیت حذف شد!
@@ -666,9 +691,12 @@ fa:
       deleted: پاک‌شده
       media:
         title: رسانه
-      no_status_selected: هیچ بوقی تغییری نکرد زیرا هیچ‌کدام از آن‌ها انتخاب نشده بودند
+      no_status_selected: هیچ فرسته‌ای تغییری نکرد زیرا هیچ‌کدام از آن‌ها انتخاب نشده بودند
       title: نوشته‌های حساب
       with_media: دارای عکس یا ویدیو
+    strikes:
+      appeal_approved: درخواست تجدیدنظر کرد
+      appeal_pending: درخواست تجدیدنظر در انتظار
     system_checks:
       database_schema_check:
         message_html: تعداد مهاجرت پایگاه داده در انتظار انجام هستند. لطفا آن‌ها را اجرا کنید تا اطمینان یابید که برنامه مطابق انتظار رفتار خواهد کرد
@@ -707,6 +735,7 @@ fa:
         not_listable: پیشنهاد نخواهد شد
         not_usable: غیر قابل استفاده
         title: برچسب‌های پرطرفدار
+        trending_rank: 'پرطرفدار #%{rank}'
         usable: قابل استفاده
       title: پرطرفدار
     warning_presets:
@@ -716,6 +745,10 @@ fa:
       empty: هنز هیچ پیش‌تنظیم هشداری را تعریف نکرده‌اید.
       title: مدیریت هشدارهای پیش‌فرض
   admin_mailer:
+    new_appeal:
+      actions:
+        none: یک هشدار
+        silence: برای محدود کردن حساب آنها
     new_pending_account:
       body: جزئیات حساب تازه این‌جاست. شما می‌توانید آن را تأیید یا رد کنید.
       subject: حساب تازه‌ای در %{instance} نیازمند بررسی است (%{username})
@@ -723,6 +756,9 @@ fa:
       body: کاربر %{reporter} کاربر %{target} را گزارش داد
       body_remote: کسی از %{domain} گزارش %{target} را فرستاده
       subject: گزارش تازه‌ای برای %{instance} (#%{id})
+    new_trends:
+      new_trending_links:
+        no_approved_links: در حال حاضر هیچ پیوند پرطرفداری پذیرفته نشده است.
   aliases:
     add_new: ساختن نام مستعار
     created_msg: نام مستعار تازه با موفقیت ساخته شد. الان می‌توانید انتقال از حساب قدیمی را آغاز کنید.
@@ -741,7 +777,7 @@ fa:
       guide_link: https://crowdin.com/project/mastodon
       guide_link_text: همه می‌توانند کمک کنند.
     sensitive_content: محتوای حساس
-    toot_layout: آرایش بوق
+    toot_layout: آرایش فرسته
   application_mailer:
     notification_preferences: تغییر ترجیحات ایمیل
     salutation: "%{name}،"
@@ -861,6 +897,18 @@ fa:
     directory: شاخهٔ نمایه
     explanation: کاربران را بر اساس علاقه‌مندی‌هایشان بیابید
     explore_mastodon: گشت و گذار در %{title}
+  disputes:
+    strikes:
+      appeal: درخواست تجدیدنظر
+      appeal_rejected: درخواست تجدیدنظر رد شده است
+      appeal_submitted_at: درخواست تجدیدنظر فرستاده شد
+      appeals:
+        submit: فرستادن درخواست تجدیدنظر
+      title_actions:
+        none: هشدار
+      your_appeal_approved: درخواست تجدیدنظر شما پذیرفته شد
+      your_appeal_pending: شما یک درخواست تجدیدنظر فرستادید
+      your_appeal_rejected: درخواست تجدیدنظر شما رد شد
   domain_validator:
     invalid_domain: نام دامین معتبر نیست
   errors:
@@ -892,7 +940,7 @@ fa:
     blocks: حساب‌های مسدودشده
     bookmarks: نشانک‌ها
     csv: CSV
-    domain_blocks: دامین‌های مسدودشده
+    domain_blocks: دامنه‌های مسدود شده
     lists: سیاهه‌ها
     mutes: حساب‌های بی‌صداشده
     storage: تصویرهای ذخیره‌شده
@@ -998,7 +1046,7 @@ fa:
       not_ready: پرونده‌هایی که پردازش را تمام نکرده‌اند نمی‌توانند پیوست شوند. یکبار دیگر امتحان کنید!
       too_many: نمی‌توان بیشتر از ۴ تصویر بارگذاری کرد
   migrations:
-    acct: username@domain حساب تازه
+    acct: جابه‌جایی به
     cancel: لغو انتقال
     cancel_explanation: با لغو انتقال، حساب شما دوباره فعال می‌شود، ولی این کار پیگیران شما را که به حساب دیگر منتقل شده‌اند برنمی‌گرداند.
     cancelled_msg: انتقال حساب با موفقیت لغو شد.
@@ -1067,9 +1115,9 @@ fa:
     poll:
       subject: نظرسنجی‌ای از %{name} پایان یافت
     reblog:
-      body: "%{name} نوشتهٔ شما را بازبوقید:"
-      subject: "%{name} نوشتهٔ شما را بازبوقید"
-      title: بازبوق تازه
+      body: "%{name} فرستهٔ شما را تقویت کرد:"
+      subject: "%{name} فرستهٔ شما را تقویت کرد"
+      title: تقویت تازه
     status:
       subject: "%{name} چیزی فرستاد"
   notifications:
@@ -1145,17 +1193,17 @@ fa:
     reason_html: "<strong>چرا این گام ضروریست؟</strong> ممکن است <code>%{instance}</code> کارسازی نباشد که شما رویش حساب دارید؛ پس لازم است پیش از هرچیز، به کارساز خودتان هدایتتان کنیم."
   remote_interaction:
     favourite:
-      proceed: به سمت پسندیدن این بوق
-      prompt: 'شما می‌خواهید این بوق را بپسندید:'
+      proceed: به سمت پسندیدن
+      prompt: 'شما می‌خواهید این فرسته را بپسندید:'
     reblog:
-      proceed: به سمت بازبوقیدن
-      prompt: 'شما می‌خواهید این بوق را بازببوقید:'
+      proceed: به سمت تقویت
+      prompt: 'شما می‌خواهید این فرسته را تقویت کنید:'
     reply:
       proceed: به سمت پاسخ‌دادن
-      prompt: 'شما می‌خواهید به این بوق پاسخ دهید:'
+      prompt: 'شما می‌خواهید به این فرسته پاسخ دهید:'
   scheduled_statuses:
-    over_daily_limit: شما از حد مجاز %{limit} بوق زمان‌بندی‌شده در آن روز فراتر رفته‌اید
-    over_total_limit: شما از حد مجاز %{limit} بوق زمان‌بندی‌شده فراتر رفته‌اید
+    over_daily_limit: شما از حد مجاز %{limit} فرسته زمان‌بندی‌شده در آن روز فراتر رفته‌اید
+    over_total_limit: شما از حد مجاز %{limit} فرسته زمان‌بندی‌شده فراتر رفته‌اید
     too_soon: زمان تعیین‌شده باید در آینده باشد
   sessions:
     activity: آخرین فعالیت
@@ -1233,19 +1281,19 @@ fa:
       video:
         one: "%{count} ویدیو"
         other: "%{count} ویدیو"
-    boosted_from_html: بازبوقیده از طرف %{acct_link}
+    boosted_from_html: تقویت شده از طرف %{acct_link}
     content_warning: 'هشدا محتوا: %{warning}'
     disallowed_hashtags:
       one: 'دارای هشتگ غیرمجاز: %{tags}'
       other: 'دارای هشتگ‌های غیرمجاز: %{tags}'
     errors:
       in_reply_not_found: به نظر نمی‌رسد وضعیتی که می‌خواهید به آن پاسخ دهید، وجود داشته باشد.
-    open_in_web: بازکردن در وب
+    open_in_web: گشودن در وب
     over_character_limit: از حد مجاز %{max} حرف فراتر رفتید
     pin_errors:
       limit: از این بیشتر نمی‌شود نوشته‌های ثابت داشت
       ownership: نوشته‌های دیگران را نمی‌توان ثابت کرد
-      reblog: بازبوق‌ها را نمی‌توان ثابت کرد
+      reblog: تقویت را نمی‌توان سنجاق کرد
     poll:
       total_people:
         one: "%{count} نفر"
@@ -1295,17 +1343,17 @@ fa:
       '2629746': ۱ ماه
       '31556952': ۱ سال
       '5259492': ۲ ماه
-      '604800': 1 week
+      '604800': ۱ هفته
       '63113904': ۲ سال
       '7889238': ۳ ماه
     min_age_label: کرانهٔ سن
     min_favs: نگه داشتن فرسته‌هایی با برگزینش بیش از
     min_favs_hint: هیچ یک از فرسته‌هایتان را که بیش از این تعداد برگزیده شده باشند، حذف نمی‌کند. برای حذف فرسته‌ها فارغ از تعداد برگزینش‌هایشان، خالی بگذارید
     min_reblogs: نگه داشتن فرسته‌هایی با تقویت بیش از
-    min_reblogs_hint: هیچ یک از فرسته‌هایتان را که بیش از این تعداد تق. یت شده باشند، حذف نمی‌کند. برای حذف فرسته‌ها فارغ از تعداد تقویت‌هایشان، خالی بگذارید
+    min_reblogs_hint: هیچ یک از فرسته‌هایتان را که بیش از این تعداد تقویت شده باشند، حذف نمی‌کند. برای حذف فرسته‌ها فارغ از تعداد تقویت‌هایشان، خالی بگذارید
   stream_entries:
     pinned: نوشته‌های ثابت
-    reblogged: بازبوقید
+    reblogged: تقویت شده
     sensitive_content: محتوای حساس
   tags:
     does_not_match_previous_name: با نام پیشین مطابق نیست
@@ -1417,6 +1465,15 @@ fa:
     recovery_instructions_html: اگر تلفن خود را گم کردید، می‌توانید با یکی از کدهای بازیابی زیر کنترل حساب خود را به دست بگیرید. <strong>این کدها را در جای امنی نگه دارید.</strong> مثلاً آن‌ها را چاپ کنید و کنار سایر مدارک مهم خود قرار دهید.
     webauthn: کلیدهای امنیتی
   user_mailer:
+    appeal_approved:
+      action: به حساب خودتان بروید
+      explanation: درخواست تجدیدنظر اخطار علیه حساب شما در %{strike_date} که در %{appeal_date} ارسال کرده‌اید، پذیرفته شده است. حساب شما بار دیگر در وضعیت خوبی قرار دارد.
+      subject: درخواست تجدیدنظر شما در %{date} پذیرفته شد
+      title: درخواست تجدیدنظر پذیرفته شد
+    appeal_rejected:
+      explanation: درخواست تجدیدنظر اخطار علیه حساب شما در %{strike_date} که در %{appeal_date} ارسال کرده‌اید، رد شده است.
+      subject: درخواست تجدیدنظر شما در %{date} رد شده است
+      title: درخواست تجدیدنظر رد شد
     backup_ready:
       explanation: شما یک نسخهٔ پشتیبان کامل از حساب خود را درخواست کردید. این پشتیبان الان آمادهٔ بارگیری است!
       subject: بایگانی شما آمادهٔ دریافت است
@@ -1428,6 +1485,8 @@ fa:
       subject: لطفاً تلاش برای ورود را تأیید کنید
       title: تلاش برای ورود
     warning:
+      appeal: فرستادن یک درخواست تجدیدنظر
+      appeal_description: اگر فکر می‌کنید این یک خطا است، می‌توانید یک درخواست تجدیدنظر به کارکنان %{instance} ارسال کنید.
       categories:
         spam: هرزنامه
       reason: 'دلیل:'
diff --git a/config/locales/fi.yml b/config/locales/fi.yml
index 918c505b1..1cdaed6ef 100644
--- a/config/locales/fi.yml
+++ b/config/locales/fi.yml
@@ -165,10 +165,6 @@ fi:
       pending: Odottaa tarkistusta
       perform_full_suspension: Siirrä kokonaan jäähylle
       previous_strikes: Aiemmat varoitukset
-      previous_strikes_description_html:
-        one: Tällä tilillä on <strong>yksi</strong> varoitus.
-        other: Tällä tilillä on <strong>%{count}</strong> varoitusta.
-        zero: Tämä tili on <strong>hyvässä kunnossa</strong>.
       promote: Ylennä
       protocol: Protokolla
       public: Julkinen
@@ -495,10 +491,6 @@ fi:
       delivery_error_hint: Jos toimitus ei ole mahdollista %{count} päivän aikana, se merkitään automaattisesti toimittamattomaksi.
       destroyed_msg: Tiedot %{domain} on nyt jonossa välitöntä poistoa varten.
       empty: Verkkotunnuksia ei löytynyt.
-      known_accounts:
-        one: "%{count} tunnettu tili"
-        other: "%{count} tunnettua tiliä"
-        zero: Ei tunnettua tiliä
       moderation:
         all: Kaikki
         limited: Rajoitettu
@@ -760,10 +752,6 @@ fi:
         description_html: Nämä ovat linkkejä, joita jaetaan tällä hetkellä paljon tileillä, joilta palvelimesi näkee viestejä. Se voi auttaa käyttäjiäsi saamaan selville, mitä maailmassa tapahtuu. Linkkejä ei näytetä julkisesti, ennen kuin hyväksyt julkaisijan. Voit myös sallia tai hylätä yksittäiset linkit.
         disallow: Hylkää linkki
         disallow_provider: Estä julkaisija
-        shared_by_over_week:
-          one: Jakanut yksi henkilö viimeisen viikon aikana
-          other: Jakanut %{count} henkilöä viimeisen viikon aikana
-          zero: Kukaan ei ole jakanut viimeisen viikon aikana
         title: Suositut linkit
         usage_comparison: Jaettu %{today} kertaa tänään verrattuna eilen %{yesterday}
       pending_review: Odottaa tarkistusta
@@ -803,10 +791,6 @@ fi:
         trending_rank: 'Nousussa #%{rank}'
         usable: Voidaan käyttää
         usage_comparison: Käytetty %{today} kertaa tänään, verrattuna %{yesterday} eiliseen
-        used_by_over_week:
-          one: Yhden henkilön käyttämä viimeisen viikon aikana
-          other: Käyttänyt %{count} henkilöä viimeisen viikon aikana
-          zero: Ei kenenkään käytössä viimeisen viikon aikana
       title: Trendit
     warning_presets:
       add_new: Lisää uusi
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index e3879be0a..bc1902cf8 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -166,9 +166,8 @@ fr:
       perform_full_suspension: Suspendre
       previous_strikes: Sanctions précédentes
       previous_strikes_description_html:
-        one: Ce compte a reçu <strong>une</strong> sanction.
+        one: Ce compte a reçu <strong>%{count}</strong> sanction.
         other: Ce compte a reçu <strong>%{count}</strong> sanctions.
-        zero: Ce compte est <strong>en règle</strong>.
       promote: Promouvoir
       protocol: Protocole
       public: Publique
@@ -482,8 +481,12 @@ fr:
     instances:
       availability:
         failure_threshold_reached: Le seuil de défaillance a été atteint le %{date}.
+        failures_recorded:
+          one: Tentative échouée pendant %{count} jour.
+          other: Tentatives échouées pendant %{count} jours différents.
         no_failures_recorded: Pas d'échec enregistré.
         title: Disponibilité
+        warning: La dernière tentative de connexion à ce serveur a échoué
       back_to_all: Tout
       back_to_limited: Limité
       back_to_warning: Avertissement
@@ -523,7 +526,6 @@ fr:
       known_accounts:
         one: "%{count} compte connu"
         other: "%{count} comptes connus"
-        zero: Pas de compte connu
       moderation:
         all: Tout
         limited: Limité
@@ -768,6 +770,11 @@ fr:
     system_checks:
       database_schema_check:
         message_html: Vous avez des migrations de base de données en attente. Veuillez les exécuter pour vous assurer que l'application se comporte comme prévu
+      elasticsearch_running_check:
+        message_html: Impossible de se connecter à Elasticsearch. Veuillez vérifier qu’il est en cours d’exécution ou désactiver la recherche en plein texte
+      elasticsearch_version_check:
+        message_html: 'Version d’Elasticsearch incompatible : %{value}'
+        version_comparison: Elasticsearch %{running_version} est en cours d’exécution alors que %{required_version} est requise
       rules_check:
         action: Gérer les règles du serveur
         message_html: Vous n'avez pas défini de règles pour le serveur.
@@ -788,9 +795,8 @@ fr:
         disallow: Interdire le lien
         disallow_provider: Interdire l'éditeur
         shared_by_over_week:
-          one: Partagé par une personne au cours de la semaine dernière
-          other: Partagé par %{count} personnes au cours de la semaine dernière
-          zero: Non partagé au cours de la semaine dernière
+          one: Partagé par %{count} personne au cours de la dernière semaine
+          other: Partagé par %{count} personnes au cours de la dernière semaine
         title: Liens tendances
         usage_comparison: Partagé %{today} fois aujourd'hui, comparé à %{yesterday} hier
       pending_review: En attente de révision
@@ -831,9 +837,8 @@ fr:
         usable: Peut être utilisé
         usage_comparison: Utilisé %{today} fois aujourd'hui, comparé à %{yesterday} hier
         used_by_over_week:
-          one: Utilisé par une personne au cours de la semaine dernière
-          other: Utilisé par %{count} personnes au cours de la semaine dernière
-          zero: Non utilisé au cours de la semaine dernière
+          one: Utilisé par %{count} personne au cours de la dernière semaine
+          other: Utilisé par %{count} personnes au cours de la dernière semaine
       title: Tendances
     warning_presets:
       add_new: Ajouter un nouveau
@@ -1431,6 +1436,7 @@ fr:
     disallowed_hashtags:
       one: 'contient un hashtag désactivé : %{tags}'
       other: 'contient les hashtags désactivés : %{tags}'
+    edited_at_html: Édité le %{date}
     errors:
       in_reply_not_found: Le message auquel vous essayez de répondre ne semble pas exister.
     open_in_web: Ouvrir sur le web
diff --git a/config/locales/gd.yml b/config/locales/gd.yml
index 6729b0443..0f6524fbd 100644
--- a/config/locales/gd.yml
+++ b/config/locales/gd.yml
@@ -173,10 +173,6 @@ gd:
       pending: A’ feitheamh air lèirmheas
       perform_full_suspension: Cuir à rèim
       previous_strikes: Rabhaidhean roimhe
-      previous_strikes_description_html:
-        one: Fhuair an cunntas seo <strong>aon</strong> rabhadh.
-        other: Fhuair an cunntas seo <strong>%{count}</strong> rabhaidhean.
-        zero: Tha <strong>deagh chliù</strong> aig a’ chunntas seo.
       promote: Àrdaich
       protocol: Pròtacal
       public: Poblach
@@ -381,6 +377,7 @@ gd:
       enable: Cuir an comas
       enabled: Chaidh a chur an comas
       enabled_msg: Chaidh an t-Emoji sin a chur an comas
+      image_hint: PNG no GIF suas ri %{size}
       list: Liosta
       listed: Liostaichte
       new:
@@ -485,6 +482,7 @@ gd:
         resolve: Fuasgail an àrainn
         title: Bac àrainn puist-d ùr
       no_email_domain_block_selected: Cha deach bacadh àrainn puist-d sam bith atharrachadh o nach deach gin dhiubh a thaghadh
+      resolved_dns_records_hint_html: Thèid ainm na h-àrainne fhuasgladh nan àrainnean MX a leanas agus an urra riutha-san gun gabh iad ri post-d. Ma bhacas tu àrainn MX, bacaidh seo an clàradh o sheòladh puist-d sam bith a chleachdas an aon àrainn MX fiù ’s ma bhios ainm àrainne eadar-dhealaichte ’ga sealltainn. <strong>Thoir an aire nach bac thu solaraichean puist-d mòra.</strong>
       resolved_through_html: Chaidh fuasgladh slighe %{domain}
       title: Àrainnean puist-d ’gam bacadh
     follow_recommendations:
@@ -496,11 +494,46 @@ gd:
       title: Molaidhean leantainn
       unsuppress: Aisig am moladh leantainn
     instances:
+      availability:
+        description_html:
+          few: Ma dh’fhàilligeas an lìbhrigeadh dhan àrainn fad <strong>%{count} làithean</strong>, chan fheuch sinn a-rithist leis an lìbhrigeadh ach às dèidh lìbhrigeadh fhaighinn <em>on àrainn ud fhèin</em>.
+          one: Ma dh’fhàilligeas an lìbhrigeadh dhan àrainn fad <strong>%{count} latha</strong>, chan fheuch sinn a-rithist leis an lìbhrigeadh ach às dèidh lìbhrigeadh fhaighinn <em>on àrainn ud fhèin</em>.
+          other: Ma dh’fhàilligeas an lìbhrigeadh dhan àrainn fad <strong>%{count} latha</strong>, chan fheuch sinn a-rithist leis an lìbhrigeadh ach às dèidh lìbhrigeadh fhaighinn <em>on àrainn ud fhèin</em>.
+          two: Ma dh’fhàilligeas an lìbhrigeadh dhan àrainn fad <strong>%{count} latha</strong>, chan fheuch sinn a-rithist leis an lìbhrigeadh ach às dèidh lìbhrigeadh fhaighinn <em>on àrainn ud fhèin</em>.
+        failure_threshold_reached: Chaidh stairsneach an fhàilligidh a ruigsinn %{date}.
+        failures_recorded:
+          few: Oidhirp a dh’fhàillig rè %{count} làithean.
+          one: Oidhirp a dh’fhàillig rè %{count} latha.
+          other: Oidhirp a dh’fhàillig rè %{count} latha.
+          two: Oidhirp a dh’fhàillig rè %{count} latha.
+        no_failures_recorded: Cha deach fàilligeadh sam bith a chlàradh.
+        title: Faotainneachd
+        warning: Cha deach leis an oidhirp mu dheireadh air ceangal ris an fhrithealaiche seo
       back_to_all: Na h-uile
       back_to_limited: Cuingichte
       back_to_warning: Rabhadh
       by_domain: Àrainn
       confirm_purge: A bheil thu cinnteach gu bheil thu airson an dàta on àrainn seo a sguabadh às gu buan?
+      content_policies:
+        comment: Nòta taobh a-staigh
+        description_html: "’S urrainn dhut poileasaidhean susbainte a mhìneachadh a thèid a chur an sàs air a h-uile cunntas on àrainn seo ’s a fo-àrainnean-se."
+        policies:
+          reject_media: Diùlt meadhanan
+          reject_reports: Diùlt gearanan
+          silence: Cuingich
+          suspend: Cuir à rèim
+        policy: Poileasaidh
+        reason: Adhbhar poblach
+        title: Poileasaidhean susbainte
+      dashboard:
+        instance_accounts_dimension: Cunntasan ’gan leantainn as trice
+        instance_accounts_measure: cunntasan ’gan stòradh
+        instance_followers_measure: an luchd-leantainn againne thall
+        instance_follows_measure: an luchd-leantainn acasan an-seo
+        instance_languages_dimension: Brod nan cànan
+        instance_media_attachments_measure: ceanglachain mheadhanan ’gan stòradh
+        instance_reports_measure: gearanan mun dèidhinn
+        instance_statuses_measure: postaichean ’gan stòradh
       delivery:
         all: Na h-uile
         clear: Falamhaich na mearachdan lìbhrigidh
@@ -512,10 +545,6 @@ gd:
       delivery_error_hint: Mura gabh a lìbhrigeadh fad %{count} là(ithean), thèid comharra a chur ris gu fèin-obrachail a dh’innseas nach gabh a lìbhrigeadh.
       destroyed_msg: Tha an dàta o %{domain} air ciutha an sguabaidh às aithghearr.
       empty: Cha deach àrainn a lorg.
-      known_accounts:
-        one: "%{count} chunntas as aithne dhuinn"
-        other: "%{count} cunntas(an) as aithne dhuinn"
-        zero: Gun chunntas as aithne dhuinn
       moderation:
         all: Na h-uile
         limited: Cuingichte
@@ -523,12 +552,14 @@ gd:
       private_comment: Beachd prìobhaideachd
       public_comment: Beachd poblach
       purge: Purgaidich
+      purge_description_html: Ma tha thu dhen bheachd gu bheil an àrainn seo far loidhne gu buan, ’s urrainn dhut a h-uile clàr cunntais ’s an dàta co-cheangailte on àrainn ud a sguabadh às san stòras agad. Dh’fhaoidte gun doir sin greis mhath.
       title: Co-nasgadh
       total_blocked_by_us: "‘Ga bhacadh leinne"
       total_followed_by_them: "’Ga leantainn leotha-san"
       total_followed_by_us: "’Ga leantainn leinne"
       total_reported: Gearanan mun dèidhinn
       total_storage: Ceanglachain mheadhanan
+      totals_time_period_hint_html: Gabhaidh na h-iomlanan gu h-ìosal a-staigh an dàta o chian nan cian.
     invites:
       deactivate_all: Cuir na h-uile à gnìomh
       filter:
@@ -584,9 +615,13 @@ gd:
       action_log: Sgrùd an loga
       action_taken_by: Chaidh an gnìomh a ghabhail le
       actions:
+        delete_description_html: Thèid na postaichean le gearan orra a sguabadh às agus rabhadh a chlàradh gus do chuideachadh ach am bi thu nas teinne le droch-ghiùlan on aon chunntas sam àm ri teachd.
+        mark_as_sensitive_description_html: Thèid comharra an fhrionasachd a chur ris na meadhanan sna postaichean le gearan orra agus rabhadh a chlàradh gus do chuideachadh ach am bi thu nas teinne le droch-ghiùlan on aon chunntas sam àm ri teachd.
         other_description_html: Seall barrachd roghainnean airson giùlan a’ chunntais a stiùireadh agus an conaltradh leis a’ chunntas a chaidh gearan a dhèanamh mu dhèidhinn a ghnàthachadh.
+        resolve_description_html: Cha dèid gnìomh sam bith a ghabhail an aghaidh a’ chunntais le gearan air agus thèid an gearan a dhùnadh gun rabhadh a chlàradh.
         silence_description_html: Chan fhaic ach an fheadhainn a tha a’ leantainn oirre mu thràth no a lorgas a làimh i a’ phròifil seo agus cuingichidh seo uiread nan daoine a ruigeas i gu mòr. Gabhaidh seo a neo-dhèanamh uair sam bith.
         suspend_description_html: Cha ghabh a’ phròifil seo agus an t-susbaint gu leòr aice inntrigeadh gus an dèid a sguabadh às air deireadh na sgeòil. Cha ghabh eadar-ghabhail a dhèanamh leis a’ chunntas. Gabhaidh seo a neo-dhèanamh am broinn 30 latha.
+      actions_description_html: Cuir romhad dè an gnìomh a ghabhas tu gus an gearan seo fhuasgladh. Ma chuireas tu peanas air a’ chunntas le gearan air, gheibh iad brath air a’ phost-d mura tagh thu an roinn-seòrsa <strong>Spama</strong>.
       add_to_report: Cuir barrachd ris a’ ghearan
       are_you_sure: A bheil thu cinnteach?
       assign_to_self: Iomruin dhomh-sa
@@ -756,6 +791,11 @@ gd:
     system_checks:
       database_schema_check:
         message_html: Tha imrichean stòir-dhàta ri dhèiligeadh ann. Ruith iad a dhèanamh cinnteach gum bi giùlan na h-aplacaid mar a bhiodhte ’n dùil
+      elasticsearch_running_check:
+        message_html: Cha b’ urrainn dhuinn ceangal ri Elasticsearch. Dearbh thu bheil e a’ ruith no cuir an lorg làn-teacsa à comas
+      elasticsearch_version_check:
+        message_html: 'Tionndadh Elasticsearch nach eil co-chòrdail: %{value}'
+        version_comparison: Tha Elasticsearch %{running_version} a ruith ach tha feum air %{required_version}
       rules_check:
         action: Stiùirich riaghailtean an fhrithealaiche
         message_html: Cha do mhìnich thu riaghailtean an fhrithealaiche fhathast.
@@ -772,23 +812,22 @@ gd:
       links:
         allow: Ceadaich an ceangal
         allow_provider: Ceadaich am foillsichear
+        description_html: Seo na ceanglaichean a tha ’gan co-roinneadh le iomadh cunntas on a chì am frithealaiche agad na postaichean. Faodaidh iad a bhith ’nan cuideachadh dhan luchd-cleachdaidh ach am faigh iad a-mach dè tha tachairt air an t-saoghal. Cha dèid ceanglaichean a shealltainn gu poblach gus an aontaich thu ris an fhoillsichear. ’S urrainn dhut ceanglaichean àraidh a cheadachadh no a dhiùltadh cuideachd.
         disallow: Na ceadaich an ceangal
         disallow_provider: Na ceadaich am foillsichear
-        shared_by_over_week:
-          one: Chaidh a cho-roinneadh le aonar rè na seachdain seo chaidh
-          other: Chaidh a cho-roinneadh le %{count} rè na seachdain seo chaidh
-          zero: Cha deach a cho-roinneadh rè na seachdain seo chaidh
         title: Ceanglaichean a’ treandadh
         usage_comparison: Chaidh a cho-roinneadh %{today} tura(i)s an-diugh an coimeas ri %{yesterday} an-dè
       pending_review: A’ feitheamh air lèirmheas
       preview_card_providers:
         allowed: Faodaidh ceanglaichean on fhoillsichear seo treandadh
+        description_html: Seo na h-àrainnean on a thèid ceanglaichean a cho-roinneadh air an fhrithealaiche agad gu tric. Cha bhi ceanglaichean a’ treandadh mura dh’aontaich thu ri àrainn a’ cheangail. Gabhaidh d’ aonta (no do dhiùltadh) a-staigh na fo-àrainnean.
         rejected: Cha treandaich ceanglaichean on fhoillsichear seo
         title: Foillsichearan
       rejected: Air a dhiùltadh
       statuses:
         allow: Ceadaich am post
         allow_account: Ceadaich an t-ùghdar
+        description_html: Seo na postaichean air a bheil am frithealaiche agad eòlach ’s a tha ’gan co-roinneadh is ’nan annsachd gu tric aig an àm seo. Faodaidh iad a bhith ’nan cuideachadh dhan luchd-cleachdaidh ùr no a thill ach an lorg iad daoine airson leantainn orra. Cha dèid postaichean a shealltainn gu poblach gus an gabh thu ris an ùghdar agus gus an aontaich an t-ùghdar gun dèid an cunntas aca a mholadh do dhaoine eile. ’S urrainn dhut postaichean àraidh a cheadachadh no a dhiùltadh cuideachd.
         disallow: Na ceadaich am post
         disallow_account: Na ceadaich an t-ùghdar
         not_discoverable: Cha do chuir an t-ùghdar roimhe gun gabh a lorg
@@ -806,6 +845,7 @@ gd:
           tag_servers_dimension: Brod nam frithealaichean
           tag_servers_measure: frithealaichean eadar-dhealaichte
           tag_uses_measure: cleachdaidhean iomlan
+        description_html: Seo na tagaichean hais a nochdas ann an grunn phostaichean a chì am frithealaiche agad aig an àm seo. Faodaidh iad a bhith ’nan cuideachadh dhan luchd-cleachdaidh agad ach am faigh iad a-mach cò air a tha daoine a’ bruidhinn nas trice aig an àm seo. Cha dèid tagaichean hais a shealltainn gu poblach gus an aontaich thu riutha.
         listable: Gabhaidh a mholadh
         not_listable: Cha dèid a mholadh
         not_trendable: Cha nochd e am measg nan treandaichean
@@ -816,10 +856,6 @@ gd:
         trending_rank: 'A’ treandadh #%{rank}'
         usable: Gabhaidh a chleachdadh
         usage_comparison: Chaidh a chleachdadh %{today} tura(i)s an-diugh an coimeas ri %{yesterday} an-dè
-        used_by_over_week:
-          one: Chaidh a chleachdadh le aonar rè na seachdain seo chaidh
-          other: Chaidh a chleachdadh le %{count} rè na seachdain seo chaidh
-          zero: Cha deach a chleachdadh rè na seachdain seo chaidh
       title: Treandaichean
     warning_presets:
       add_new: Cuir fear ùr ris
@@ -851,12 +887,15 @@ gd:
       body: 'Tha na nithean seo feumach air lèirmheas mus nochd iad gu poblach:'
       new_trending_links:
         no_approved_links: Chan eil ceangal a’ treandadh le aontachadh ann.
+        requirements: "’S urrainn do ghin dhe na tagraichean seo dol thairis air #%{rank} a tha aig a’ cheangal “%{lowest_link_title}” a’ treandadh as ìsle le aontachadh agus sgòr de %{lowest_link_score} air."
         title: Ceanglaichean a’ treandadh
       new_trending_statuses:
         no_approved_statuses: Chan eil post a’ treandadh le aontachadh ann.
+        requirements: "’S urrainn do ghin dhe na tagraichean seo dol thairis air #%{rank} a tha aig a’ phost %{lowest_status_url} a’ treandadh as ìsle le aontachadh agus sgòr de %{lowest_status_score} air."
         title: Postaichean a’ treandadh
       new_trending_tags:
         no_approved_tags: Chan eil taga hais a’ treandadh le aontachadh ann.
+        requirements: "’S urrainn do ghin dhe na tagraichean seo dol thairis air #%{rank} a tha aig an taga hais #%{lowest_tag_name} a’ treandadh as ìsle le aontachadh agus sgòr de %{lowest_tag_score} air."
         title: Tagaichean hais a’ treandadh
       subject: Tha treandaichean ùra a’ feitheamh air lèirmheas air %{instance}
   aliases:
@@ -1011,6 +1050,19 @@ gd:
         submit: Cuir a-null an t-ath-thagradh
       associated_report: An gearan co-cheangailte
       created_at: Ceann-là
+      description_html: Seo na gnìomhan a chaidh a ghabhail an aghaidh a’ chunntais agad agus na rabhaidhean a chaidh a chur thugad le luchd-obrach %{instance}.
+      recipient: Faightear
+      status: 'Post #%{id}'
+      status_removed: Chaidh am post a thoirt air falbh on t-siostam mu thràth
+      title: "%{action} o %{date}"
+      title_actions:
+        delete_statuses: Toirt air falbh puist
+        disable: Reòthadh cunntais
+        mark_statuses_as_sensitive: Comharra na frionasachd air postaichean
+        none: Rabhadh
+        sensitive: Comharra na frionasachd air cunntais
+        silence: Cuingeachadh cunntais
+        suspend: Cur à rèim cunntais
       your_appeal_approved: Chaidh aontachadh ris an ath-thagradh agad
       your_appeal_pending: Chuir thu ath-thagradh a-null
       your_appeal_rejected: Chaidh an t-ath-thagradh agad a dhiùltadh
@@ -1192,6 +1244,9 @@ gd:
     carry_mutes_over_text: Chaidh an cleachdaiche seo imrich o %{acct} a b’ àbhaist dhut a mhùchadh.
     copy_account_note_text: 'Da cleachdaiche air gluasad o %{acct}, seo na nòtaichean a bh’ agad mu dhèidhinn roimhe:'
   notification_mailer:
+    admin:
+      sign_up:
+        subject: Chlàraich %{name}
     digest:
       action: Seall a h-uile brath
       body: Seo geàrr-chunntas air na h-atharraichean nach fhaca thu on tadhal mu dheireadh agad %{since}
@@ -1233,6 +1288,8 @@ gd:
       title: Brosnachadh ùr
     status:
       subject: Tha %{name} air post a sgrìobhadh
+    update:
+      subject: Dheasaich %{name} post
   notifications:
     email_events: Tachartasan nam brathan puist-d
     email_events_hint: 'Tagh na tachartasan dhan a bheil thu airson brathan fhaighinn:'
@@ -1314,6 +1371,9 @@ gd:
     reply:
       proceed: Lean air adhart gus freagairt
       prompt: 'Tha thu airson freagairt dhan phost seo:'
+  reports:
+    errors:
+      invalid_rules: gun iomradh air riaghailtean dligheach
   scheduled_statuses:
     over_daily_limit: Chaidh thu thar na crìoch de %{limit} post(aichean) sgeidealaichte an-diugh
     over_total_limit: Chaidh thu thar na crìoch de %{limit} post(aichean) sgeidealaichte
@@ -1380,6 +1440,7 @@ gd:
     profile: Pròifil
     relationships: Dàimhean leantainn
     statuses_cleanup: Sguabadh às fèin-obrachail phostaichean
+    strikes: Rabhaidhean na maorsainneachd
     two_factor_authentication: Dearbhadh dà-cheumnach
     webauthn_authentication: Iuchraichean tèarainteachd
   statuses:
@@ -1402,11 +1463,13 @@ gd:
         two: "%{count} video"
     boosted_from_html: Brosnachadh o %{acct_link}
     content_warning: 'Rabhadh susbainte: %{warning}'
+    default_language: Co-ionnan ri cànan na h-eadar-aghaidh
     disallowed_hashtags:
       few: "– bha na tagaichean hais toirmisgte seo ann: %{tags}"
       one: "– bha na tagaichean hais toirmisgte seo ann: %{tags}"
       other: "– bha na tagaichean hais toirmisgte seo ann: %{tags}"
       two: "– bha na tagaichean hais toirmisgte seo ann: %{tags}"
+    edited_at_html: Air a dheasachadh %{date}
     errors:
       in_reply_not_found: Tha coltas nach eil am post dhan a tha thu airson freagairt ann.
     open_in_web: Fosgail air an lìon
@@ -1469,7 +1532,7 @@ gd:
       '2629746': Mìos
       '31556952': Bliadhna
       '5259492': 2 mhìos
-      '604800': 1 week
+      '604800': Seachdain
       '63113904': 2 bhliadhna
       '7889238': 3 mìosan
     min_age_label: Stairsneach aoise
@@ -1484,7 +1547,7 @@ gd:
   tags:
     does_not_match_previous_name: "– chan eil seo a-rèir an ainm roimhe"
   terms:
-    body_html: '<h2>Poileasaidh prìobhaideachd</h2> <h3 id="collect">Dè am fiosrachadh a chruinnicheas sinn?</h3> <ul> <li><em>Fiosrachadh bunasach a’ cunntais</em>: Ma chlàraicheas tu leis an fhrithealaiche seo, dh’fhaoidte gun dèid iarraidh ort gun cuir thu a-steach ainm-cleachdaiche, seòladh puist-d agus facal-faire. Faodaidh tu barrachd fiosrachaidh a chur ris a’ phròifil agad ma thogras tu, can ainm-taisbeanaidh agus teacsa mu do dhèidhinn agus dealbhan pròifile ’s banna-chinn a luchdadh suas. Thèid an t-ainm-cleachdaiche, an t-ainm-taisbeanaidh, an teacsa mu do dhèidhinn agus dealbhan na pròifile ’s a bhanna-chinn a shealltainn gu poblach an-còmhnaidh.</li> <li><em>Postaichean, luchd-leantainn agus fiosrachadh poblach eile</em>: Tha liosta nan daoine air a leanas tu poblach mar a tha i dhan luchd-leantainn agad. Nuair a chuireas tu a-null teachdaireachd, thèid an t-àm ’s an ceann-latha a stòradh cho math ris an aplacaid leis an do chuir thu am foirm a-null. Faodaidh ceanglachain meadhain a bhith am broinn teachdaireachdan, can dealbhan no videothan. Tha postaichean poblach agus postaichean falaichte o liostaichean ri ’m faighinn gu poblach. Nuair a bhrosnaicheas tu post air a’ phròifil agad, ’s e fiosrachadh poblach a tha sin cuideachd. Thèid na postaichean agad a lìbhrigeadh dhan luchd-leantainn agad agus is ciall dha seo gun dèid an lìbhrigeadh gu frithealaichean eile aig amannan is gun dèid lethbhreacan dhiubh a stòradh thall. Nuair a sguabas tu às post, thèid sin a lìbhrigeadh dhan luchd-leantainn agad cuideachd. Tha ath-bhlogachadh no dèanamh annsachd de phost eile poblach an-còmhnaidh.</li> <li><em>Postaichean dìreach is dhan luchd-leantainn a-mhàin</em>: Thèid a h-uile post a stòradh ’s a phròiseasadh air an fhrithealaiche. Thèid na postaichean dhan luchd-leantainn a-mhàin a lìbhrigeadh dhan luchd-leantainn agad agus dhan luchd-chleachdaidh a chaidh iomradh a dhèanamh orra sa phost. Thèid postaichean dìreach a lìbhrigeadh dhan luchd-chleachdaidh a chaidh iomradh a dhèanamh orra sa phost a-mhàin. Is ciall dha seo gun dèid an lìbhrigeadh gu frithealaichean eile aig amannan is gun dèid lethbhreacan dhiubh a stòradh thall. Nì sinn ar dìcheall gun cuingich sinn an t-inntrigeadh dha na postaichean air na daoine a fhuair ùghdarrachadh dhaibh ach dh’fhaoidte nach dèan frithealaichean eile seo. Mar sin dheth, tha e cudromach gun doir thu sùil air na frithealaichean dhan a bhuineas an luchd-leantainn agad. Faodaidh tu roghainn a chur air no dheth a leigeas leat aontachadh ri luchd-leantainn ùra no an diùltadh a làimh. <em>Thoir an aire gum faic rianairean an fhrithealaiche agus frithealaiche sam bith a gheibh am fiosrachadh na teachdaireachdan dhen leithid</em> agus gur urrainn dha na faightearan glacaidhean-sgrìn no lethbhreacan dhiubh a dhèanamh no an cho-roinneadh air dòighean eile. <em>Na co-roinn fiosrachadh cunnartach air Mastodon idir.</em></li> <li><em>IPan is meata-dàta eile</em>: Nuair a nì thu clàradh a-steach, clàraidh sinn an seòladh IP on a rinn thu clàradh a-steach cuide ri ainm aplacaid a’ bhrabhsair agad. Bidh a h-uile seisean clàraidh a-steach ri làimh dhut airson an lèirmheas agus an cùl-ghairm sna roghainnean. Thèid an seòladh IP as ùire a chleachd thu a stòradh suas ri 12 mhìos. Faodaidh sinn cuideachd logaichean an fhrithealaiche a chumail a ghabhas a-steach seòladh IP aig a h-uile iarrtas dhan fhrithealaiche againn.</li> </ul> <hr class="spacer" /> <h3 id="use">Dè na h-adhbharan air an cleachd sinn am fiosrachadh agad?</h3> <p>Seo na dòighean air an cleachd sinn fiosrachadh sam bith a chruinnich sinn uat ma dh’fhaoidte:</p> <ul> <li>Airson bun-ghleusan Mhastodon a lìbhrigeadh. Chan urrainn dhut conaltradh le susbaint càich no an t-susbaint agad fhèin a phostadh ach nuair a bhios tu air do chlàradh a-steach. Mar eisimpleir, faodaidh tu leantainn air càch ach am faic thu na postaichean aca còmhla air loidhne-ama pearsanaichte na dachaigh agad.</li> <li>Airson cuideachadh le maorsainneachd na coimhearsnachd, can airson coimeas a dhèanamh eadar an seòladh IP agad ri feadhainn eile feuch am mothaich sinn do sheachnadh toirmisg no briseadh eile nan riaghailtean.</li> <li>Faodaidh sinn an seòladh puist-d agad a chleachdadh airson fiosrachadh no brathan mu chonaltraidhean càich leis an t-susbaint agad no teachdaireachdan a chur thugad, airson freagairt ri ceasnachaidhean agus/no iarrtasan no ceistean eile.</li> </ul> <hr class="spacer" /> <h3 id="protect">Ciamar a dhìonas sinn am fiosrachadh agad?</h3> <p>Cuiridh sinn iomadh gleus tèarainteachd an sàs ach an glèidheadh sinn sàbhailteachd an fhiosrachaidh phearsanta agad nuair a chuireas tu gin a-steach, nuair a chuireas tu a-null e no nuair a nì thu inntrigeadh air. Am measg gleusan eile, thèid seisean a’ bhrabhsair agad cuide ris an trafaig eadar na h-aplacaidean agad ’s an API a dhìon le SSL agus thèid hais a dhèanamh dhen fhacal-fhaire agad le algairim aon-shligheach làidir. Faodaidh tu dearbhadh dà-cheumnach a chur an comas airson barrachd tèarainteachd a chur ris an inntrigeadh dhan chunntas agad.</p> <hr class="spacer" /> <h3 id="data-retention">Dè am poileasaidh cumail dàta againn?</h3> <p>Nì sinn ar dìcheall:</p> <ul> <li>Nach cùm sinn logaidhean an fhrithealaiche sa bheil seòlaidhean IP nan iarrtasan uile dhan fhrithealaiche seo nas fhaide na 90 latha ma chumas sinn logaichean dhen leithid idir.</li> <li>Nach cùm sinn na seòlaidhean IP a tha co-cheangailte ri cleachdaichean clàraichte nas fhaide na 12 mhìos.</li> </ul> <p>’S urrainn dhut tasg-lann iarraidh dhen t-susbaint agad ’s a luchdadh a-nuas is gabhaidh seo a-staigh na postaichean, na ceanglachain meadhain, dealbh na pròifil agus dealbh a’ bhanna-chinn agad.</p> <p>’S urrainn dhut an cunntas agad a sguabadh às gu buan uair sam bith.</p> <hr class="spacer"/> <h3 id="cookies">An cleachd sinn briosgaidhean?</h3> <p>Cleachdaidh. ’S e faidhlichean beaga a tha sna briosgaidean a thar-chuireas làrach no solaraiche seirbheise gu clàr-cruaidh a’ choimpiutair agad leis a’ bhrabhsair-lìn agad (ma cheadaicheas tu sin). Bheir na briosgaidean sin comas dhan làrach gun aithnich i am brabhsair agad agus ma tha cunntas clàraichte agad, gun co-cheangail i ris a’ chunntas chlàraichte agad e.</p> <p>Cleachdaidh sinn briosgaidean airson na roghainnean agad a thuigsinn ’s a ghlèidheadh gus an tadhail thu oirnn san àm ri teachd.</p> <hr class="spacer" /> <h3 id="disclose">Am foillsich sinn fiosrachadh sam bith gu pàrtaidhean air an taobh a-muigh?</h3> <p>Cha reic, malairt no tar-chuir sinn fiosrachadh air a dh’aithnichear thu fhèin gu pàrtaidh sam bith air an taobh a-muigh. Cha ghabh seo a-staigh treas-phàrtaidhean earbsach a chuidicheas leinn le ruith na làraich againn, le obrachadh a’ ghnìomhachais againn no gus an t-seirbheis a thoirt leat cho fada ’s a dh’aontaicheas na treas-phàrtaidhean sin gun cùm iad am fiosrachadh dìomhair. Faodaidh sinn am fiosrachadh agad fhoillseachadh cuideachd nuair a bhios sinn dhen bheachd gu bheil am foillseachadh sin iomchaidh airson gèilleadh dhan lagh, poileasaidhean na làraich againn èigneachadh no na còraichean, an sealbh no an t-sàbhailteachd againn fhèin no aig càch a dhìon.</p> <p>Dh’fhaoidte gun dèid an t-susbaint phoblach agad a luchdadh a-nuas le frithealaichean eile san lìonra. Thèid na postaichean poblach agad ’s an fheadhainn dhan luchd-leantainn a-mhàin a lìbhrigeadh dha na frithealaichean far a bheil an luchd-leantainn agad a’ còmhnaidh agus thèid na teachdaireachdan dìreach a lìbhrigeadh gu frithealaichean nam faightearan nuair a bhios iad a’ còmhnaidh air frithealaiche eile.</p> <p>Nuair a dh’ùghdarraicheas tu aplacaid gun cleachd i an cunntas agad, a-rèir sgòp nan ceadan a dh’aontaicheas tu riutha, faodaidh i fiosrachadh poblach na pròifil agad, liosta na feadhna air a bhios tu a’ leantainn, an luchd-leantainn agad, na liostaichean agad, na postaichean agad uile ’s na h-annsachdan agad inntrigeadh. Chan urrainn do dh’aplacaidean an seòladh puist-d no am facal-faire agad inntrigeadh idir.</p> <hr class="spacer" /> <h3 id="children">Cleachdadh na làraich leis a’ chloinn</h3> <p>Ma tha am frithealaiche seo san Aonadh Eòrpach (AE) no san Roinn Eaconomach na h-Eòrpa (EEA): Tha an làrach, na batharan agus na seirbheisean againn uile ag amas air an fheadhainn a tha co-dhiù 16 bliadhnaichean a dh’aois. Ma tha thu nas òige na 16 bliadhnaichean a dh’aois, tha e riatanach fon GDPR (<a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">General Data Protection Regulation</a>) nach cleachd thu an làrach seo.</p> <p>Ma tha am frithealaiche seo sna Stàitean Aonaichte (SAA): Tha an làrach, na batharan agus na seirbheisean againn uile ag amas air an fheadhainn a tha co-dhiù 13 bliadhnaichean a dh’aois. Ma tha thu nas òige na 16 bliadhnaichean a dh’aois, tha e riatanach fon COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children''s Online Privacy Protection Act</a>)ha an làrach, na batharan agus na seirbheisean againn uile ag amas air an fheadhainn a tha co-dhiù 16 bliadhnaichean a dh’aois. Ma tha thu nas òige na 16 bliadhnaichean a dh’aois, tha e riatanach fon GDPR (<a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">General Data Protection Regulation</a>) nach cleachd thu an làrach seo.</p> <p>Ma tha am frithealaiche seo sna Stàitean Aonaichte (SAA): Tha an làrach, na batharan agus na seirbheisean againn uile ag amas air an fheadhainn a tha co-dhiù 13 bliadhnaichean a dh’aois. Ma tha thu nas òige na 16 bliadhnaichean a dh’aois, tha e riatanach fon COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children''s Online Privacy Protection Act</a>) nach cleachd thu an làrach seo.</p> <p>Dh’fhaoidte gu bheil am frithealaiche seo fo riatanasan lagha eile ma tha e ann an uachdranas laghail eile.</p> <hr class="spacer" /> <h3 id="changes">Atharraichean air a’ phoileasaidh phrìobhaideachd againn</h3> <p>Ma chuireas sinn romhainn am poileasaidh prìobhaideachd againn atharrachadh, postaichidh sinn na h-atharraichean dhan duilleag seo.</p> <p>Tha an sgrìobhainn seo fo cheadachas CC-BY-SA. Chaidh ùrachadh an turas mu dheireadh an t-7mh dhen Mhart 2018.</p> <p>Chaidh a fhreagarrachadh o thùs o <a href="https://github.com/discourse/discourse">phoileasaidh prìobhaideachd Discourse</a>.</p> nach cleachd thu an làrach seo.</p> <p>Dh’fhaoidte gu bheil am frithealaiche seo fo riatanasan lagha eile ma tha e ann an uachdranas laghail eile.</p> <hr class="spacer" /> <h3 id="changes">Atharraichean air a’ phoileasaidh phrìobhaideachd againn</h3> <p>Ma chuireas sinn romhainn am poileasaidh prìobhaideachd againn atharrachadh, postaichidh sinn na h-atharraichean dhan duilleag seo.</p> <p>Tha an sgrìobhainn seo fo cheadachas CC-BY-SA. Chaidh ùrachadh an turas mu dheireadh an t-7mh dhen Mhart 2018.</p> <p>Chaidh a fhreagarrachadh o thùs o <a href="https://github.com/discourse/discourse">phoileasaidh prìobhaideachd Discourse</a>.</p>
+    body_html: '<h2>Poileasaidh prìobhaideachd</h2> <h3 id="collect">Dè am fiosrachadh a chruinnicheas sinn?</h3> <ul> <li><em>Fiosrachadh bunasach a’ cunntais</em>: Ma chlàraicheas tu leis an fhrithealaiche seo, dh’fhaoidte gun dèid iarraidh ort gun cuir thu a-steach ainm-cleachdaiche, seòladh puist-d agus facal-faire. Faodaidh tu barrachd fiosrachaidh a chur ris a’ phròifil agad ma thogras tu, can ainm-taisbeanaidh agus teacsa mu do dhèidhinn agus dealbhan pròifile ’s banna-chinn a luchdadh suas. Thèid an t-ainm-cleachdaiche, an t-ainm-taisbeanaidh, an teacsa mu do dhèidhinn agus dealbhan na pròifile ’s a bhanna-chinn a shealltainn gu poblach an-còmhnaidh.</li> <li><em>Postaichean, luchd-leantainn agus fiosrachadh poblach eile</em>: Tha liosta nan daoine air a leanas tu poblach mar a tha i dhan luchd-leantainn agad. Nuair a chuireas tu a-null teachdaireachd, thèid an t-àm ’s an ceann-latha a stòradh cho math ris an aplacaid leis an do chuir thu am foirm a-null. Faodaidh ceanglachain meadhain a bhith am broinn teachdaireachdan, can dealbhan no videothan. Tha postaichean poblach agus postaichean falaichte o liostaichean ri ’m faighinn gu poblach. Nuair a bhrosnaicheas tu post air a’ phròifil agad, ’s e fiosrachadh poblach a tha sin cuideachd. Thèid na postaichean agad a lìbhrigeadh dhan luchd-leantainn agad agus is ciall dha seo gun dèid an lìbhrigeadh gu frithealaichean eile aig amannan is gun dèid lethbhreacan dhiubh a stòradh thall. Nuair a sguabas tu às post, thèid sin a lìbhrigeadh dhan luchd-leantainn agad cuideachd. Tha ath-bhlogachadh no dèanamh annsachd de phost eile poblach an-còmhnaidh.</li> <li><em>Postaichean dìreach is dhan luchd-leantainn a-mhàin</em>: Thèid a h-uile post a stòradh ’s a phròiseasadh air an fhrithealaiche. Thèid na postaichean dhan luchd-leantainn a-mhàin a lìbhrigeadh dhan luchd-leantainn agad agus dhan luchd-chleachdaidh a chaidh iomradh a dhèanamh orra sa phost. Thèid postaichean dìreach a lìbhrigeadh dhan luchd-chleachdaidh a chaidh iomradh a dhèanamh orra sa phost a-mhàin. Is ciall dha seo gun dèid an lìbhrigeadh gu frithealaichean eile aig amannan is gun dèid lethbhreacan dhiubh a stòradh thall. Nì sinn ar dìcheall gun cuingich sinn an t-inntrigeadh dha na postaichean air na daoine a fhuair ùghdarrachadh dhaibh ach dh’fhaoidte nach dèan frithealaichean eile seo. Mar sin dheth, tha e cudromach gun doir thu sùil air na frithealaichean dhan a bhuineas an luchd-leantainn agad. Faodaidh tu roghainn a chur air no dheth a leigeas leat aontachadh ri luchd-leantainn ùra no an diùltadh a làimh. <em>Thoir an aire gum faic rianairean an fhrithealaiche agus frithealaiche sam bith a gheibh am fiosrachadh na teachdaireachdan dhen leithid</em> agus gur urrainn dha na faightearan glacaidhean-sgrìn no lethbhreacan dhiubh a dhèanamh no an cho-roinneadh air dòighean eile. <em>Na co-roinn fiosrachadh cunnartach air Mastodon idir.</em></li> <li><em>IPan is meata-dàta eile</em>: Nuair a nì thu clàradh a-steach, clàraidh sinn an seòladh IP on a rinn thu clàradh a-steach cuide ri ainm aplacaid a’ bhrabhsair agad. Bidh a h-uile seisean clàraidh a-steach ri làimh dhut airson an lèirmheas agus an cùl-ghairm sna roghainnean. Thèid an seòladh IP as ùire a chleachd thu a stòradh suas ri 12 mhìos. Faodaidh sinn cuideachd logaichean an fhrithealaiche a chumail a ghabhas a-steach seòladh IP aig a h-uile iarrtas dhan fhrithealaiche againn.</li> </ul> <hr class="spacer" /> <h3 id="use">Dè na h-adhbharan air an cleachd sinn am fiosrachadh agad?</h3> <p>Seo na dòighean air an cleachd sinn fiosrachadh sam bith a chruinnich sinn uat ma dh’fhaoidte:</p> <ul> <li>Airson bun-ghleusan Mhastodon a lìbhrigeadh. Chan urrainn dhut eadar-ghnìomh a ghabhail le susbaint càich no an t-susbaint agad fhèin a phostadh ach nuair a bhios tu air do chlàradh a-steach. Mar eisimpleir, faodaidh tu leantainn air càch ach am faic thu na postaichean aca còmhla air loidhne-ama pearsanaichte na dachaigh agad.</li> <li>Airson cuideachadh le maorsainneachd na coimhearsnachd, can airson coimeas a dhèanamh eadar an seòladh IP agad ri feadhainn eile feuch am mothaich sinn do sheachnadh toirmisg no briseadh eile nan riaghailtean.</li> <li>Faodaidh sinn an seòladh puist-d agad a chleachdadh airson fiosrachadh no brathan mu eadar-ghnìomhan a ghabh càch leis an t-susbaint agad no teachdaireachdan a chur thugad, airson freagairt ri ceasnachaidhean agus/no iarrtasan no ceistean eile.</li> </ul> <hr class="spacer" /> <h3 id="protect">Ciamar a dhìonas sinn am fiosrachadh agad?</h3> <p>Cuiridh sinn iomadh gleus tèarainteachd an sàs ach an glèidheadh sinn sàbhailteachd an fhiosrachaidh phearsanta agad nuair a chuireas tu gin a-steach, nuair a chuireas tu a-null e no nuair a nì thu inntrigeadh air. Am measg gleusan eile, thèid seisean a’ bhrabhsair agad cuide ris an trafaig eadar na h-aplacaidean agad ’s an API a dhìon le SSL agus thèid hais a dhèanamh dhen fhacal-fhaire agad le algairim aon-shligheach làidir. Faodaidh tu dearbhadh dà-cheumnach a chur an comas airson barrachd tèarainteachd a chur ris an inntrigeadh dhan chunntas agad.</p> <hr class="spacer" /> <h3 id="data-retention">Dè am poileasaidh cumail dàta againn?</h3> <p>Nì sinn ar dìcheall:</p> <ul> <li>Nach cùm sinn logaidhean an fhrithealaiche sa bheil seòlaidhean IP nan iarrtasan uile dhan fhrithealaiche seo nas fhaide na 90 latha ma chumas sinn logaichean dhen leithid idir.</li> <li>Nach cùm sinn na seòlaidhean IP a tha co-cheangailte ri cleachdaichean clàraichte nas fhaide na 12 mhìos.</li> </ul> <p>’S urrainn dhut tasg-lann iarraidh dhen t-susbaint agad ’s a luchdadh a-nuas is gabhaidh seo a-staigh na postaichean, na ceanglachain meadhain, dealbh na pròifil agus dealbh a’ bhanna-chinn agad.</p> <p>’S urrainn dhut an cunntas agad a sguabadh às gu buan uair sam bith.</p> <hr class="spacer"/> <h3 id="cookies">An cleachd sinn briosgaidhean?</h3> <p>Cleachdaidh. ’S e faidhlichean beaga a tha sna briosgaidean a thar-chuireas làrach no solaraiche seirbheise gu clàr-cruaidh a’ choimpiutair agad leis a’ bhrabhsair-lìn agad (ma cheadaicheas tu sin). Bheir na briosgaidean sin comas dhan làrach gun aithnich i am brabhsair agad agus ma tha cunntas clàraichte agad, gun co-cheangail i ris a’ chunntas chlàraichte agad e.</p> <p>Cleachdaidh sinn briosgaidean airson na roghainnean agad a thuigsinn ’s a ghlèidheadh gus an tadhail thu oirnn san àm ri teachd.</p> <hr class="spacer" /> <h3 id="disclose">Am foillsich sinn fiosrachadh sam bith gu pàrtaidhean air an taobh a-muigh?</h3> <p>Cha reic, malairt no tar-chuir sinn fiosrachadh air a dh’aithnichear thu fhèin gu pàrtaidh sam bith air an taobh a-muigh. Cha ghabh seo a-staigh treas-phàrtaidhean earbsach a chuidicheas leinn le ruith na làraich againn, le obrachadh a’ ghnìomhachais againn no gus an t-seirbheis a thoirt leat cho fada ’s a dh’aontaicheas na treas-phàrtaidhean sin gun cùm iad am fiosrachadh dìomhair. Faodaidh sinn am fiosrachadh agad fhoillseachadh cuideachd nuair a bhios sinn dhen bheachd gu bheil am foillseachadh sin iomchaidh airson gèilleadh dhan lagh, poileasaidhean na làraich againn èigneachadh no na còraichean, an sealbh no an t-sàbhailteachd againn fhèin no aig càch a dhìon.</p> <p>Dh’fhaoidte gun dèid an t-susbaint phoblach agad a luchdadh a-nuas le frithealaichean eile san lìonra. Thèid na postaichean poblach agad ’s an fheadhainn dhan luchd-leantainn a-mhàin a lìbhrigeadh dha na frithealaichean far a bheil an luchd-leantainn agad a’ còmhnaidh agus thèid na teachdaireachdan dìreach a lìbhrigeadh gu frithealaichean nam faightearan nuair a bhios iad a’ còmhnaidh air frithealaiche eile.</p> <p>Nuair a dh’ùghdarraicheas tu aplacaid gun cleachd i an cunntas agad, a-rèir sgòp nan ceadan a dh’aontaicheas tu riutha, faodaidh i fiosrachadh poblach na pròifil agad, liosta na feadhna air a bhios tu a’ leantainn, an luchd-leantainn agad, na liostaichean agad, na postaichean agad uile ’s na h-annsachdan agad inntrigeadh. Chan urrainn do dh’aplacaidean an seòladh puist-d no am facal-faire agad inntrigeadh idir.</p> <hr class="spacer" /> <h3 id="children">Cleachdadh na làraich leis a’ chloinn</h3> <p>Ma tha am frithealaiche seo san Aonadh Eòrpach (AE) no san Roinn Eaconomach na h-Eòrpa (EEA): Tha an làrach, na batharan agus na seirbheisean againn uile ag amas air an fheadhainn a tha co-dhiù 16 bliadhnaichean a dh’aois. Ma tha thu nas òige na 16 bliadhnaichean a dh’aois, tha e riatanach fon GDPR (<a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">General Data Protection Regulation</a>) nach cleachd thu an làrach seo.</p> <p>Ma tha am frithealaiche seo sna Stàitean Aonaichte (SAA): Tha an làrach, na batharan agus na seirbheisean againn uile ag amas air an fheadhainn a tha co-dhiù 13 bliadhnaichean a dh’aois. Ma tha thu nas òige na 16 bliadhnaichean a dh’aois, tha e riatanach fon COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children''s Online Privacy Protection Act</a>)ha an làrach, na batharan agus na seirbheisean againn uile ag amas air an fheadhainn a tha co-dhiù 16 bliadhnaichean a dh’aois. Ma tha thu nas òige na 16 bliadhnaichean a dh’aois, tha e riatanach fon GDPR (<a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">General Data Protection Regulation</a>) nach cleachd thu an làrach seo.</p> <p>Ma tha am frithealaiche seo sna Stàitean Aonaichte (SAA): Tha an làrach, na batharan agus na seirbheisean againn uile ag amas air an fheadhainn a tha co-dhiù 13 bliadhnaichean a dh’aois. Ma tha thu nas òige na 16 bliadhnaichean a dh’aois, tha e riatanach fon COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children''s Online Privacy Protection Act</a>) nach cleachd thu an làrach seo.</p> <p>Dh’fhaoidte gu bheil am frithealaiche seo fo riatanasan lagha eile ma tha e ann an uachdranas laghail eile.</p> <hr class="spacer" /> <h3 id="changes">Atharraichean air a’ phoileasaidh phrìobhaideachd againn</h3> <p>Ma chuireas sinn romhainn am poileasaidh prìobhaideachd againn atharrachadh, postaichidh sinn na h-atharraichean dhan duilleag seo.</p> <p>Tha an sgrìobhainn seo fo cheadachas CC-BY-SA. Chaidh ùrachadh an turas mu dheireadh an t-7mh dhen Mhart 2018.</p> <p>Chaidh a fhreagarrachadh o thùs o <a href="https://github.com/discourse/discourse">phoileasaidh prìobhaideachd Discourse</a>.</p>
 
       '
     title: Teirmichean na seirbheise ⁊ poileasaidh prìobhaideachd %{instance}
@@ -1513,6 +1576,15 @@ gd:
     recovery_instructions_html: Ma chailleas tu an t-inntrigeadh dhan fhòn agad, ’s urrainn dhut fear dhe na còdan aisig gu h-ìosal a chleachdadh airson faighinn a-steach dhan chunntas agad a-rithist. <strong>Cùm na còdan aisig sàbhailte</strong>. Mar eisimpleir, ’s urrainn dhut an clò-bhualadh ’s a chumail far a bheil thu a’ cumail na sgrìobhainnean cudromach eile agad.
     webauthn: Iuchraichean tèarainteachd
   user_mailer:
+    appeal_approved:
+      action: Tadhail air a’ chunntas agad
+      explanation: Chaidh aontachadh ris an ath-thagradh agad air an rabhadh o %{strike_date} a chuir thu a-null %{appeal_date}. Tha deagh chliù air a’ chunntas agad a-rithist.
+      subject: Chaidh aontachadh ris an ath-thagradh agad o %{date}
+      title: Chaidh aontachadh ri ath-thagradh
+    appeal_rejected:
+      explanation: Chaidh an t-ath-thagradh agad air an rabhadh o %{strike_date} a chuir thu a-null %{appeal_date} a dhiùltadh.
+      subject: Chaidh an t-ath-thagradh agad o %{date} a dhiùltadh
+      title: Chaidh ath-thagradh a dhiùltadh
     backup_ready:
       explanation: Dh’iarr thu lethbhreac-glèidhidh slàn dhen chunntas Mastodon agad. Tha e deis ri luchdadh a-nuas a-nis!
       subject: Tha an tasg-lann agad deis ri luchdadh a-nuas
@@ -1524,25 +1596,34 @@ gd:
       subject: Dearbh an oidhirp air clàradh a-steach
       title: Oidhirp clàraidh a-steach
     warning:
+      appeal: Cuir ath-thagradh a-null
+      appeal_description: Ma tha thu dhen bheachd gur e mearachd a th’ ann, ’s urrainn dhut ath-thagradh a chur a-null gun sgioba aig %{instance}.
       categories:
         spam: Spama
         violation: Tha an t-susbaint a’ briseadh na riaghailtean giùlain a leanas
       explanation:
+        delete_statuses: Thathar dhen bheachd gu bheil cuid dhe na postaichean agad a’ briseadh riaghailt no riaghailtean giùlain agus chaidh an toirt air falbh le maoir %{instance} an uairsin.
         disable: Chan urrainn dhut an cunntas agad a chleachdadh tuilleadh ach mairidh a’ phròifil ’s an dàta eile agad. Faodaidh tu lethbhreac-glèidhidh dhen dàta agad iarraidh, roghainnean a’ chunntais atharrachadh no an cunntas agad a sguabadh às.
+        mark_statuses_as_sensitive: Chuir maoir %{instance} comharra na frionasachd ri cuid dhe na postaichean agad. Is ciall dha seo gum feumar gnogag a thoirt air na meadhanan sna postaichean mus faicear ro-shealladh. ’S urrainn dhut fhèin comharra a chur gu bheil meadhan frionasach nuair a sgrìobhas tu post san à ri teachd.
         sensitive: O seo a-mach, thèid comharra na frionasachd a chur ri faidhle meadhain sam bith a luchdaicheas tu suas agus thèid am falach air cùlaibh rabhaidh a ghabhas briogadh air.
         silence: "’S urrainn dhut an cunntas agad a chleachdadh fhathast ach chan fhaic ach na daoine a tha a’ leantainn ort mu thràth na postaichean agad air an fhrithealaiche seo agus dh’fhaoidte gun dèid d’ às-dhùnadh o iomadh gleus luirg. Gidheadh, faodaidh càch leantainn ort a làimh fhathast."
         suspend: Chan urrainn dhut an cunntas agad a chleachdadh tuilleadh agus chan fhaigh thu grèim air a’ phròifil no air an dàta eile agad. ’S urrainn dhut clàradh a-steach fhathast airson lethbhreac-glèidhidh dhen dàta agad iarraidh mur dèid an dàta a thoirt air falbh an ceann 30 latha gu slàn ach cumaidh sinn cuid dhen dàta bhunasach ach nach seachain thu an cur à rèim.
       reason: 'Adhbhar:'
+      statuses: 'Iomradh air postaichean:'
       subject:
         delete_statuses: Chaidh na postaichean agad air %{acct} a thoirt air falbh
         disable: Chaidh an cunntas %{acct} agad a reòthadh
+        mark_statuses_as_sensitive: Chaidh comharra na frionasachd a chur ris na postaichean agad air %{acct}
         none: Rabhadh dha %{acct}
+        sensitive: Thèid comharra na frionasachd a chur ris na postaichean agad air %{acct} o seo a-mach
         silence: Chaidh an cunntas %{acct} agad a chuingeachadh
         suspend: Chaidh an cunntas %{acct} agad a chur à rèim
       title:
         delete_statuses: Chaidh postaichean a thoirt air falbh
         disable: Cunntas reòite
+        mark_statuses_as_sensitive: Chaidh comharra na frionasachd a chur ri postaichean
         none: Rabhadh
+        sensitive: Chaidh comharra na frionasachd a chur ri cunntas
         silence: Cunntas cuingichte
         suspend: Cunntas à rèim
     welcome:
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index f3a4beb70..e28d9e018 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -165,10 +165,6 @@ gl:
       pending: Revisión pendente
       perform_full_suspension: Suspender
       previous_strikes: Accións previas
-      previous_strikes_description_html:
-        one: Esta conta ten <strong>un</strong> evento.
-        other: Esta conta ten <strong>%{count}</strong> eventos.
-        zero: Esta conta ten <strong>boa reputación</strong>.
       promote: Promocionar
       protocol: Protocolo
       public: Público
@@ -490,6 +486,7 @@ gl:
           other: Intentos fallidos durante %{count} días distintos.
         no_failures_recorded: Non hai fallos rexistrados.
         title: Dispoñibilidade
+        warning: Fallou o último intento de conectar con este servidor
       back_to_all: Todo
       back_to_limited: Limitado
       back_to_warning: Aviso
@@ -526,10 +523,6 @@ gl:
       delivery_error_hint: Se non é posible a entrega durante %{count} días, será automáticamente marcado como non entregable.
       destroyed_msg: Os datos desde %{domain} están na cola para o borrado inminente.
       empty: Non se atopan dominios.
-      known_accounts:
-        one: "%{count} conta coñecida"
-        other: "%{count} contas coñecidas"
-        zero: Sen contas coñecidas
       moderation:
         all: Todo
         limited: Limitado
@@ -774,6 +767,11 @@ gl:
     system_checks:
       database_schema_check:
         message_html: Existen migracións pendentes na base de datos. Bota man desta tarefa para facer que a aplicación funcione como se agarda dela
+      elasticsearch_running_check:
+        message_html: Non se puido conectar con Elasticsearch. Comproba que está funcionando, ou desactiva a busca por texto completo
+      elasticsearch_version_check:
+        message_html: 'Versión incompatible de Elasticsearch: %{value}'
+        version_comparison: Está executándose Elasticsearch %{running_version} pero requírese a %{required_version}
       rules_check:
         action: Xestionar regras do servidor
         message_html: Non tes definidas regras para o servidor.
@@ -793,10 +791,6 @@ gl:
         description_html: Estas son ligazóns que actualmente están sendo compartidas por moitas contas das que o teu servidor recibe publicación. Pode ser de utilidade para as túas usuarias para saber o que acontece polo mundo. Non se mostran ligazóns de xeito público a non ser que autorices a quen as publica. Tamén podes permitir ou rexeitar ligazóns de xeito individual.
         disallow: Denegar ligazón
         disallow_provider: Denegar orixe
-        shared_by_over_week:
-          one: Compartido por unha persoa na última semana
-          other: Compartido por %{count} persoas na última semana
-          zero: Non foi compartido na última semana
         title: Ligazóns en voga
         usage_comparison: Compartido %{today} veces hoxe, comparado con %{yesterday} onte
       pending_review: Revisión pendente
@@ -836,10 +830,6 @@ gl:
         trending_rank: 'En voga #%{rank}'
         usable: Pode ser usado
         usage_comparison: Utilizado %{today} veces hoxe, comparado coas %{yesterday} de onte
-        used_by_over_week:
-          one: Utilizado por unha persoa na última semana
-          other: Utilizado por %{count} persoas na última semana
-          zero: Non foi utilizado na última semana
       title: Tendencias
     warning_presets:
       add_new: Engadir novo
diff --git a/config/locales/he.yml b/config/locales/he.yml
index 67f3df609..97138fffa 100644
--- a/config/locales/he.yml
+++ b/config/locales/he.yml
@@ -169,17 +169,6 @@ he:
         remove_from_report: הסרה מהדיווח
         report: דווח
     title: ניהול
-    trends:
-      links:
-        shared_by_over_week:
-          one: שותף ע"י משתמש\ת אחד\ת בשבוע האחרון
-          other: שותף ע"י %{count} משתמשים בשבוע האחרון
-          zero: לא שותף בכלל בשבוע האחרון
-      tags:
-        used_by_over_week:
-          one: היה בשימוש משתמש\ת אחד\ת בשבוע האחרון
-          other: היה בשימוש ע"י %{count} משתמשים בשבוע האחרון
-          zero: לא היה בשימוש בכלל בשבוע האחרון
   application_mailer:
     settings: 'שינוי הגדרות דוא"ל: %{link}'
     view: 'תצוגה:'
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index 000184317..abe5baf48 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -170,7 +170,6 @@ hu:
       previous_strikes_description_html:
         one: Ezt a fiókot <strong>egyszer</strong> szankcionálták.
         other: Ezt a fiókot <strong>%{count}</strong> esetben szankcionálták.
-        zero: Ez a fiók <strong>makulátlan</strong>.
       promote: Előléptetés
       protocol: Protokoll
       public: Nyilvános
@@ -492,6 +491,7 @@ hu:
           other: Sikertelen próbálkozás %{count} különböző napon.
         no_failures_recorded: Nem rögzítettünk hibát.
         title: Elérhetőség
+        warning: Sikertelen volt az utolsó csatlakozási próbálkozás ehhez a szerverhez
       back_to_all: Mind
       back_to_limited: Korlátozott
       back_to_warning: Figyelmeztetés
@@ -531,7 +531,6 @@ hu:
       known_accounts:
         one: "%{count} ismert fiók"
         other: "%{count} ismert fiók"
-        zero: Nincs ismert fiók
       moderation:
         all: Mind
         limited: Korlátozott
@@ -776,6 +775,11 @@ hu:
     system_checks:
       database_schema_check:
         message_html: Vannak esedékes adatbázis migrációink. Kérlek, futtasd őket, hogy biztosítsd, hogy az alkalmazás megfelelően működjön
+      elasticsearch_running_check:
+        message_html: Nem sikerült az Elasticsearchhöz kapcsolódni. Ellenőrizze, hogy fut-e, vagy kapcsolja ki a teljes szöveges keresést.
+      elasticsearch_version_check:
+        message_html: 'Nem kompatibilis Elasticsearch verzió: %{value}'
+        version_comparison: Az Elasticsearch %{running_version} fut, de %{required_version} szükséges
       rules_check:
         action: Szerver szabályok menedzselése
         message_html: Még nem definiáltál egy szerver szabályt sem.
@@ -796,9 +800,8 @@ hu:
         disallow: Hivatkozás letiltása
         disallow_provider: Közzétevő letiltása
         shared_by_over_week:
-          one: Egy ember osztotta meg az elmúlt héten
-          other: "%{count} ember osztotta meg az elmúlt héten"
-          zero: Senki sem osztotta meg az elmúlt héten
+          one: Egy ember osztotta meg a múlt héten
+          other: "%{count} ember osztotta meg a múlt héten"
         title: Felkapott hivatkozások
         usage_comparison: "%{today} alkalommal lett ma megosztva, a tegnapi %{yesterday} alkalomhoz képest"
       pending_review: Áttekintésre vár
@@ -841,7 +844,6 @@ hu:
         used_by_over_week:
           one: Egy ember használta a múlt héten
           other: "%{count} ember használta a múlt héten"
-          zero: Senki sem használta a múlt héten
       title: Trendek
     warning_presets:
       add_new: Új hozzáadása
diff --git a/config/locales/id.yml b/config/locales/id.yml
index 97443b4a5..ccb71ebdd 100644
--- a/config/locales/id.yml
+++ b/config/locales/id.yml
@@ -160,9 +160,7 @@ id:
       perform_full_suspension: Lakukan suspen penuh
       previous_strikes: Peringatan sebelumnya
       previous_strikes_description_html:
-        one: Akun ini mendapatkan <strong>satu</strong> peringatan.
-        other: Akun ini mendapatkan <strong>%{count}</strong> peringatan.
-        zero: Akun ini <strong>status bagus</strong>.
+        other: Akun ini mendapatkan <strong>%{count}</strong> hukuman.
       promote: Promosikan
       protocol: Protokol
       public: Publik
@@ -477,6 +475,7 @@ id:
           other: Upaya gagal dalam %{count} hari berbeda.
         no_failures_recorded: Tidak ada kegagalan tercatat.
         title: Ketersediaan
+        warning: Upaya terakhir untuk menyambung ke server ini tidak berhasil
       back_to_all: Semua
       back_to_limited: Terbatas
       back_to_warning: Peringatan
@@ -514,9 +513,7 @@ id:
       destroyed_msg: Data dari %{domain} masuk antrean dihapus dalam waktu dekat.
       empty: Domain tidak ditemukan.
       known_accounts:
-        one: "%{count} akun dikenal"
-        other: "%{count} akun dikenal"
-        zero: Tidak ada akun yang dikenal
+        other: "%{count} akun yang dikenal"
       moderation:
         all: Semua
         limited: Terbatas
@@ -760,6 +757,11 @@ id:
     system_checks:
       database_schema_check:
         message_html: Ada proses migrasi basis data tertunda. Silakan jalankan untuk memastikan aplikasi bekerja seperti yang diharapkan
+      elasticsearch_running_check:
+        message_html: Tidak dapat tersambung ke Elasticsearch. Pastikan itu berjalan, atau nonaktifkan pencarian teks penuh
+      elasticsearch_version_check:
+        message_html: 'Versi Elasticsearch tidak kompatibel: %{value}'
+        version_comparison: Elasticsearch %{running_version} sedang berjalan, sementara yang diwajibkan adalah %{required_version}
       rules_check:
         action: Kelola aturan server
         message_html: Anda belum menentukan aturan server apapun.
@@ -780,9 +782,7 @@ id:
         disallow: Batalkan izin tautan
         disallow_provider: Batalkan izin penerbit
         shared_by_over_week:
-          one: Dibagikan oleh satu orang lebih dari seminggu lalu
-          other: Dibagikan oleh %{count} orang lebih dari seminggu lalu
-          zero: Tidak dibagikan siapapun lebih dari seminggu lalu
+          other: Dibagikan oleh %{count} orang selama seminggu terakhir
         title: Tautan sedang tren
         usage_comparison: Dibagikan %{today} kali hari ini, dibandingkan %{yesterday} kemarin
       pending_review: Tinjauan tertunda
@@ -822,9 +822,7 @@ id:
         usable: Dapat digunakan
         usage_comparison: Digunakan %{today} kali hari ini, dibandingkan %{yesterday} kemarin
         used_by_over_week:
-          one: Dipakai oleh satu orang lebih dari seminggu lalu
-          other: Dipakai oleh %{count} orang selama seminggu terakhir
-          zero: Tidak dipakai siapapun lebih dari seminggu lalu
+          other: Digunakan oleh %{count} orang selama seminggu terakhir
       title: Tren
     warning_presets:
       add_new: Tambah baru
diff --git a/config/locales/is.yml b/config/locales/is.yml
index 04d193975..910ae0b79 100644
--- a/config/locales/is.yml
+++ b/config/locales/is.yml
@@ -168,7 +168,6 @@ is:
       previous_strikes_description_html:
         one: Þessi notandaaðgangur er með <strong>eina</strong> refsingu.
         other: Þessi notandaaðgangur er með <strong>%{count}</strong> refsingar.
-        zero: Þessi notandaaðgangur er <strong>í góðu lagi</strong>.
       promote: Hækka í tign
       protocol: Samskiptamáti
       public: Opinber
@@ -490,6 +489,7 @@ is:
           other: Misheppnaðar tilraunir á %{count} mismunandi dögum.
         no_failures_recorded: Engar misheppnaðar tilraunir á skrá.
         title: Tiltækileiki
+        warning: Síðasta tilraun til að tengjast þessum netþjóni mistókst
       back_to_all: Allt
       back_to_limited: Takmarkað
       back_to_warning: Aðvörun
@@ -529,7 +529,6 @@ is:
       known_accounts:
         one: "%{count} þekktur notandaaðgangur"
         other: "%{count} þekktir notendaaðgangar"
-        zero: Enginn þekktur notandaaðgangur
       moderation:
         all: Allt
         limited: Takmarkað
@@ -774,6 +773,11 @@ is:
     system_checks:
       database_schema_check:
         message_html: Það eru fyrirliggjandi yfirfærslur á gagnagrunnum. Keyrðu þær til að tryggja að forritið hegði sér eins og skyldi
+      elasticsearch_running_check:
+        message_html: Gat ekki tengst við Elasticsearch-leitina. Gakktu úr skugga um að hún sé í gangi, eða gerðu leit í öllum texta óvirka
+      elasticsearch_version_check:
+        message_html: 'Ósamhæfð útgáfa Elasticsearch-leitar: %{value}'
+        version_comparison: Elasticsearch %{running_version} er í gangi á meðan útgáfa %{required_version} er nauðsynleg
       rules_check:
         action: Sýsla með reglur netþjónsins
         message_html: Þú hefur ekki skilgreint neinar reglur fyrir netþjón.
@@ -796,7 +800,6 @@ is:
         shared_by_over_week:
           one: Deilt af einum aðila síðustu vikuna
           other: Deilt af %{count} aðilum síðustu vikuna
-          zero: Ekki deilt af neinum aðila síðustu vikuna
         title: Vinsælir tenglar
         usage_comparison: Deilt %{today} sinnum í dag, samanborið við %{yesterday} í gær
       pending_review: Bíður eftir yfirlestri
@@ -839,7 +842,6 @@ is:
         used_by_over_week:
           one: Notað af einum aðila síðustu vikuna
           other: Notað af %{count} aðilum síðustu vikuna
-          zero: Ekki notað af neinum aðila síðustu vikuna
       title: Tilhneiging
     warning_presets:
       add_new: Bæta við nýju
diff --git a/config/locales/it.yml b/config/locales/it.yml
index d96e58540..92cd18d1a 100644
--- a/config/locales/it.yml
+++ b/config/locales/it.yml
@@ -168,7 +168,6 @@ it:
       previous_strikes_description_html:
         one: Questo account ha <strong>una</strong> violazione.
         other: Questo account ha <strong>%{count}</strong> violazioni.
-        zero: Questo account ha <strong>una buona reputazione</strong>.
       promote: Promuovi
       protocol: Protocollo
       public: Pubblico
@@ -490,6 +489,7 @@ it:
           other: Tentativo fallito %{count} giorni differenti.
         no_failures_recorded: Nessun fallimento registrato.
         title: Disponibilità
+        warning: L'ultimo tentativo di connessione a questo server non è riuscito
       back_to_all: Tutto
       back_to_limited: Limitato
       back_to_warning: Avviso
@@ -529,7 +529,6 @@ it:
       known_accounts:
         one: "%{count} account noto"
         other: "%{count} account noti"
-        zero: Nessun account noto
       moderation:
         all: Tutto
         limited: Limitato
@@ -774,6 +773,11 @@ it:
     system_checks:
       database_schema_check:
         message_html: Ci sono migrazioni del database in attesa. Sei pregato di eseguirle per assicurarti che l'applicazione si comporti come previsto
+      elasticsearch_running_check:
+        message_html: Impossibile connettersi a Elasticsearch. Verificare che sia in esecuzione o disabilitare la ricerca full-text
+      elasticsearch_version_check:
+        message_html: 'Versione Elasticsearch incompatibile: %{value}'
+        version_comparison: È in esecuzione la versione %{running_version} di Elasticsearch, ma è richiesta la versione %{required_version}
       rules_check:
         action: Gestisci regole del server
         message_html: Non hai definito alcuna regola del server.
@@ -796,7 +800,6 @@ it:
         shared_by_over_week:
           one: Condiviso da una persona nell'ultima settimana
           other: Condiviso da %{count} persone nell'ultima settimana
-          zero: Condiviso da nessuno nell'ultima settimana
         title: Link in tendenza
         usage_comparison: Condiviso %{today} volte oggi, rispetto a %{yesterday} ieri
       pending_review: Revisione in sospeso
@@ -839,7 +842,6 @@ it:
         used_by_over_week:
           one: Usato da una persona nell'ultima settimana
           other: Usato da %{count} persone nell'ultima settimana
-          zero: Usato da nessuno nell'ultima settimana
       title: Tendenze
     warning_presets:
       add_new: Aggiungi nuovo
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 32b101147..84559e499 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -474,10 +474,6 @@ ja:
       delivery_error_hint: "%{count} 日間配送ができない場合は、自動的に配送不可としてマークされます。"
       destroyed_msg: "%{domain} からのデータは、すぐに削除されるように、キューに追加されました。"
       empty: ドメインが見つかりませんでした。
-      known_accounts:
-        one: 既知のアカウントが%{count}件あります
-        other: 既知のアカウントが%{count}件あります
-        zero: 既知のアカウントはありません
       moderation:
         all: すべて
         limited: 制限あり
@@ -716,7 +712,7 @@ ja:
     trends:
       allow: 許可
       approved: 承認
-      disallow: 不許可
+      disallow: 拒否
       links:
         allow: リンクの許可
         allow_provider: 発行者の承認
@@ -731,8 +727,11 @@ ja:
         title: 発行者
       rejected: 拒否
       statuses:
-        allow: 投稿を許可する
-        disallow: 投稿を許可しない
+        allow: 掲載を許可
+        allow_account: 投稿者を許可
+        disallow: 掲載を拒否
+        disallow_account: 投稿者を拒否
+        title: トレンド投稿
       tags:
         current_score: 現在のスコア %{score}
         dashboard:
@@ -1309,6 +1308,7 @@ ja:
     default_language: UIの表示言語
     disallowed_hashtags:
       other: '許可されていないハッシュタグが含まれています: %{tags}'
+    edited_at_html: "%{date} 編集済み"
     errors:
       in_reply_not_found: あなたが返信しようとしている投稿は存在しないようです。
     open_in_web: Webで開く
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index 7dacd90d3..a8e8e3622 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -162,9 +162,7 @@ ko:
       perform_full_suspension: 정지시키기
       previous_strikes: 이전의 처벌들
       previous_strikes_description_html:
-        one: 이 계정은 <strong>한<strong> 번의 처벌이 있었습니다.
         other: 이 계정은 <strong>%{count}</strong> 번의 처벌이 있었습니다.
-        zero: 이 계정은 처벌 기록이 <strong>없습니다</strong>.
       promote: 승급
       protocol: 프로토콜
       public: 공개
@@ -481,6 +479,7 @@ ko:
           other: 실패한 전달 시도 총 %{count}일.
         no_failures_recorded: 실패 기록이 없습니다.
         title: 가용성
+        warning: 이 서버에 대한 마지막 연결 시도가 성공적이지 않았습니다
       back_to_all: 전체
       back_to_limited: 제한됨
       back_to_warning: 경고
@@ -518,9 +517,7 @@ ko:
       destroyed_msg: "%{domain}의 데이터는 곧바로 지워지도록 대기열에 들어갔습니다."
       empty: 도메인이 하나도 없습니다.
       known_accounts:
-        one: "%{count} 개의 알려진 계정"
         other: "%{count} 개의 알려진 계정"
-        zero: 알려진 계정이 없습니다
       moderation:
         all: 모두
         limited: 제한됨
@@ -764,6 +761,11 @@ ko:
     system_checks:
       database_schema_check:
         message_html: 데이터베이스 마이그레이션이 대기중입니다. 응용프로그램이 예상한대로 동작할 수 있도록 마이그레이션을 실행해 주세요
+      elasticsearch_running_check:
+        message_html: Elasticsearch에 연결할 수 없습니다. 실행중인지 확인하거나, 전문검색을 비활성화하세요
+      elasticsearch_version_check:
+        message_html: '호환되지 않는 Elasticsearch 버전: %{value}'
+        version_comparison: Elasticsearch %{required_version}버전이 필요하지만 %{running_version}버전이 실행 중입니다.
       rules_check:
         action: 서버 규칙 관리
         message_html: 아직 서버규칙을 정하지 않았습니다.
@@ -784,9 +786,7 @@ ko:
         disallow: 링크 거부하기
         disallow_provider: 출처 거부하기
         shared_by_over_week:
-          one: 지난 주 동안 한 명의 사람이 공유했습니다
           other: 지난 주 동안 %{count} 명의 사람들이 공유했습니다
-          zero: 지난 주 동안 공유한 사람이 없습니다
         title: 유행하는 링크
         usage_comparison: 오늘은 %{today}회 공유되었고, 어제는 %{yesterday}회 공유되었습니다
       pending_review: 심사 대기
@@ -826,9 +826,7 @@ ko:
         usable: 사용 가능
         usage_comparison: 오늘은 %{today}회 사용되었고, 어제는 %{yesterday}회 사용되었습니다
         used_by_over_week:
-          one: 지난 주 동안 한 명의 사람이 사용했습니다
           other: 지난 주 동안 %{count} 명의 사람들이 사용했습니다
-          zero: 지난 주 동안 사용한 사람이 없습니다
       title: 유행
     warning_presets:
       add_new: 새로 추가
diff --git a/config/locales/ku.yml b/config/locales/ku.yml
index a457b4b07..1f5554f4b 100644
--- a/config/locales/ku.yml
+++ b/config/locales/ku.yml
@@ -21,7 +21,7 @@ ku:
     documentation: Pelbend
     federation_hint_html: Bi ajimêrê xwe %{instance} re tu dikarî kesên rajekar û li derveyî mastodonê bişopînî.
     get_apps: Sepaneke mobîl bicerbîne
-    hosted_on: Mastodon li ser%{domain} tê hildanê
+    hosted_on: Mastodon li ser %{domain} tê pêşkêşkirin
     instance_actor_flash: 'Ev ajimêr aktorekî aşopî ye ji bo rajekar were temsîl kirin tê bikaranîn ne ajimêra kesî ye. Ji bo armanca federasyonê dixebite û divê ney asteng kirin heta ku te xwest hemû nimûneyan asteng bikî, di vir de ger tu blogek navper bikarbînî.
 
       '
@@ -133,7 +133,7 @@ ku:
       enabled: Çalakkirî
       enabled_msg: Ajimêra %{username} bi serkeftî hat çalak kirin
       followers: Şopîner
-      follows: Dişopînê
+      follows: Dişopîne
       header: Jormalper
       inbox_url: Peyamên hatî URl
       invite_request_text: Sedemên tevlêbûnê
@@ -168,7 +168,6 @@ ku:
       previous_strikes_description_html:
         one: Ev ajimêr <strong>yek</strong> binpêkirin kiriye.
         other: Ev ajimêr <strong>%{count}</strong> binpêkirin kiriye.
-        zero: Ev ajimêr <strong>di rewşeke baş de ye</strong>.
       promote: Derbasê asteke bilind be
       protocol: Protokol
       public: Gelemperî
@@ -186,7 +185,7 @@ ku:
         send: E-nameya pejirandinê dîsa bişîne
         success: E-nameya pejirandinê bi awayekî serkeftî hate şandin!
       reset: Ji nû ve saz bike
-      reset_password: Pêborînê ji nû ve saz bike
+      reset_password: Borînpeyvê ji nû ve saz bike
       resubscribe: Dîsa beşdar bibe
       role: Maf
       roles:
@@ -198,9 +197,9 @@ ku:
       search_same_email_domain: Bikarhênerên din ên bi heman navpera e-nameyê
       search_same_ip: Bikarhênerên din ên xwedî heman IP
       security_measures:
-        only_password: Têne pêborîn
-        password_and_2fa: Pêborîn û 2FA
-        password_and_sign_in_token: Pêborîn û navnîşana e-nameyê
+        only_password: Têne borînpeyv
+        password_and_2fa: Borînpeyv û 2FA
+        password_and_sign_in_token: Borînpeyv û navnîşana e-nameyê
       sensitive: Hêz-hestiyar
       sensitized: Wek hestiyar hatiye nîşankirin
       shared_inbox_url: URLya wergirtiyên parvekirî
@@ -269,7 +268,7 @@ ku:
         reject_user: Bikarhêner nepejirîne
         remove_avatar_user: Avatarê rake
         reopen_report: Ragihandina ji nû ve veke
-        reset_password_user: Pêborînê ji nû ve saz bike
+        reset_password_user: Borînpeyvê ji nû ve saz bike
         resolve_report: Ragihandinê çareser bike
         sensitive_account: Ajimêra hêz-hestiyar
         silence_account: Ajimêrê bi sînor bike
@@ -320,7 +319,7 @@ ku:
         reject_user_html: "%{name} tomarkirina ji %{target} nepejirand"
         remove_avatar_user_html: "%{name} avatara bikarhêner %{target} rakir"
         reopen_report_html: "%{name} ragihandina %{target} ji nû ve vekir"
-        reset_password_user_html: "%{name} pêborîna bikarhênerê %{target} ji nû ve saz kir"
+        reset_password_user_html: "%{name} borînpeyva bikarhêner %{target} ji nû ve saz kir"
         resolve_report_html: "%{name} ragihandina %{target} çareser kir"
         sensitive_account_html: "%{name} medyayê %{target} wekî hestiyarî nîşan kir"
         silence_account_html: "%{name} ajimêra %{target} bi sînor kir"
@@ -492,6 +491,7 @@ ku:
           other: Hewldanên têkçûyî di %{count} rojên cuda de.
         no_failures_recorded: Di tomarê de têkçûn tune.
         title: Berdestbûnî
+        warning: Hewldana dawî ji bo girêdana bi vê rajekarê re bi ser neket
       back_to_all: Hemû
       back_to_limited: Sînorkirî
       back_to_warning: Hişyarî
@@ -531,7 +531,6 @@ ku:
       known_accounts:
         one: "%{count} ajimêra naskirî"
         other: "%{count} ajimêrên naskirî"
-        zero: Ajimêra naskirî tune ye
       moderation:
         all: Hemû
         limited: Sînorkirî
@@ -543,7 +542,7 @@ ku:
       title: Giştî
       total_blocked_by_us: Ji aliyê me ve hatiye astengkirin
       total_followed_by_them: Ji aliyê wan ve hatiye şopandin
-      total_followed_by_us: Ji aliyê ve me hate şopandin
+      total_followed_by_us: Ji aliyê me ve hatiye şopandin
       total_reported: Giliyên derheqê wan de
       total_storage: Pêvekên medyayê
       totals_time_period_hint_html: Tevahiyên ku li jêr têne xuyakirin daneyên hemû deman dihewîne.
@@ -776,6 +775,11 @@ ku:
     system_checks:
       database_schema_check:
         message_html: Koçberiyên databasê yên li bendê hene. Ji kerema xwe wan bişopîne da ku bicîh bikî ku sepan wekî ku tê hêvî kirin tevbigere
+      elasticsearch_running_check:
+        message_html: Bi Elasticsearch re nayê girêdan. Ji kerema xwe kontrol bike ku ew dixebite, an lêgerîna tev-nivîsî neçalak bike
+      elasticsearch_version_check:
+        message_html: 'Guhertoya Elasticsearch a nelihevhatî: %{value}'
+        version_comparison: Elasticsearch %{running_version} dixebite lê %{required_version} pêwîst e
       rules_check:
         action: Rêzikên rajekara bi rê ve bibe
         message_html: Te qet rêzikên rajekara diyar nekiriye.
@@ -797,8 +801,7 @@ ku:
         disallow_provider: Mafê nede weşanger
         shared_by_over_week:
           one: Di nava hefteya dawî de ji aliyê keskekî ve hate parvekirin
-          other: Di nava hefteya dawî de ji aliyê %{count} kes ve hate parvekirin
-          zero: Di nava hefteya dawî de ji aliyê kesekî ve nehate parvekirin
+          other: Di nava hefteya dawî de ji aliyê %{count} ve hate parvekirin
         title: Girêdanên di rojevê de
         usage_comparison: Îro %{today} car hate parvekirin, li gorî %{yesterday} duh
       pending_review: Li benda nirxandinê ye
@@ -841,7 +844,6 @@ ku:
         used_by_over_week:
           one: Di nava hefteya dawî de ji aliyê kesekî ve hatiye bikaranîn
           other: Di nava hefteya dawî de ji %{count} kes ve hatiye bikaranîn
-          zero: Di nava hefteya dawî de ji aliyê kesekî ve nehate bikaranîn
       title: Rojev
     warning_presets:
       add_new: Yeka nû tevlî bike
@@ -904,9 +906,9 @@ ku:
     sensitive_content: Naveroka hestiyarî
     toot_layout: Xêzkirina şandîya
   application_mailer:
-    notification_preferences: Hevyazên e-name yê biguherîne
+    notification_preferences: Sazkariyên e-nameyê biguherîne
     salutation: "%{name},"
-    settings: 'Hevyazên e-name yê biguherîne: %{link}'
+    settings: 'Sazkariyên e-nameyê biguherîne: %{link}'
     view: 'Nîşan bide:'
     view_profile: Profîlê nîşan bide
     view_status: Şandiyê nîşan bide
@@ -920,7 +922,7 @@ ku:
     your_token: Nîşana gihîştina te
   auth:
     apply_for_account: Daxwaza vexwendinekê bike
-    change_password: Pêborîn
+    change_password: Borînpeyv
     checkbox_agreement_html: Ez <a href="%{rules_path}" target="_blank">rêbazên rajeker</a> û <a href="%{terms_path}" target="_blank">hêmanên karûbaran</a> dipejirînim
     checkbox_agreement_without_rules_html: Ez <a href="%{terms_path}" target="_blank">hêmanên karûbaran</a> rêbazên rajeker dipejirînim
     delete_account: Ajimêr jê bibe
@@ -931,8 +933,8 @@ ku:
       suffix: Bi ajimêrekê, tu yê karibî kesan bişopînî, rojanekirinan bişînî û bi bikarhênerên ji her rajekarê Mastodon re peyaman bişînî û bêhtir!
     didnt_get_confirmation: Te rêwerzên pejirandinê wernegirt?
     dont_have_your_security_key: Kilîda te ya ewlehiyê tune ye?
-    forgot_password: Te pêborîna xwe jibîrkir?
-    invalid_reset_password_token: Ji nû ve sazkirina pêborînê nederbasdar e an jî qediya ye. Jkx daxwaza yeka nû bike.
+    forgot_password: Te borînpeyva xwe ji bîr kir?
+    invalid_reset_password_token: Ji nû ve sazkirina borînpeyvê nederbasdar e an jî qediya ye. Jkx daxwaza yeka nû bike.
     link_to_otp: Ji têlefona xwe an jî ji kodeke rizgarkirinê kodeke du-gavî binivîsine
     link_to_webauth: Amûra kilîta ewlehiya xwe bi kar bîne
     log_in_with: Têkeve bi riya
@@ -947,9 +949,9 @@ ku:
     register: Tomar bibe
     registration_closed: "%{instance} endamên nû napejirîne"
     resend_confirmation: Rêwerên pejirandinê ji nû ve bişîne
-    reset_password: Pêborînê ji nû ve saz bike
+    reset_password: Borînpeyvê ji nû ve saz bike
     security: Ewlehî
-    set_new_password: Pêborîneke nû ji nû ve saz bike
+    set_new_password: Borînpeyveke nû ji nû ve saz bike
     setup:
       email_below_hint_html: Heke navnîşana e-nameya jêrîn ne rast be, tu dikarî wê li vir biguherîne û e-nameyeke pejirandinê ya nû bistîne.
       email_settings_hint_html: E-nameya pejirandinê ji %{email} re hate şandin. Heke ew navnîşana e-nameyê ne rast be, tu dikarî wê di sazkariyên ajimêr de biguherîne.
@@ -963,7 +965,7 @@ ku:
       view_strikes: Binpêkirinên berê yên dijî ajimêrê xwe bibîne
     too_fast: Form pir zû hat şandin, dîsa biceribîne.
     trouble_logging_in: Têketina te de pirsgirêk çêdibe?
-    use_security_key: Kilîteke ewlehiyê bikar bîne
+    use_security_key: Kilîteke ewlehiyê bi kar bîne
   authorize_follow:
     already_following: Jixwe tu vê ajimêrê dişopînî
     already_requested: Jixwe te ji vê ajimêrê re daxwazîya şopandinê şandi bû
@@ -975,12 +977,12 @@ ku:
       close: An jî, tu dikarî tenê ev çarçoveyê bigirî.
       return: Profîla vê bikarhênerê nîşan bike
       web: Biçe tevneyê
-    title: Bişopîne %{acct}
+    title: "%{acct} bişopîne"
   challenge:
     confirm: Bidomîne
-    hint_html: "<strong>Nîşe:</strong>Ji bo demjimêreke din em ê pêborîna te careke din ji te nexwazin."
-    invalid_password: Pêborîna nederbasdar
-    prompt: Ji bo bidomî lêborînê bipejirîne
+    hint_html: "<strong>Nîşe:</strong>Ji bo demjimêreke din em ê borînpeyva te careke din ji te nexwazin."
+    invalid_password: Borînpeyva nederbasdar
+    prompt: Ji bo bidomî borînpeyvê bipejirîne
   crypto:
     errors:
       invalid_key: ed25519 ne derbasdare ne jî Curve25519 kilîta
@@ -1005,7 +1007,7 @@ ku:
       x_seconds: "%{count}çirke"
   deletes:
     challenge_not_passed: Zanyariyên ku te nivîsandî ne rast in
-    confirm_password: Pêborîna xwe ya heyî binivîsine da ku nasnameya xwe piştrast bikî
+    confirm_password: Borînpeyva xwe ya heyî binivîsine da ku nasnameya xwe piştrast bikî
     confirm_username: Navê bikarhêneriyê xwe binivîse da ku prosedurê piştrast bike
     proceed: Ajimêr jê bibe
     success_msg: Ajimêra te bi serkeftî hate jêbirin
@@ -1175,10 +1177,10 @@ ku:
   login_activities:
     authentication_methods:
       otp: sepandina rastandina du-gavî
-      password: pêborîn
+      password: borînpeyv
       sign_in_token: koda ewlehiyê bo e-nameyê
       webauthn: kilîtên ewlehiyê
-    description_html: Heke çalakiya ku nas nakî dibînî, çêtir dibe ku pêborîna xwe biguherînî û rastandina du-gavî çalak bikî.
+    description_html: Heke çalakiya ku nas nakî dibînî, çêtir dibe ku borînpeyva xwe biguherînî û rastandina du-gavî çalak bikî.
     empty: Dîroka piştrastkirinê tune ye
     failed_sign_in_html: Hewldana têketinê ser neket bi%{method} ji %{ip} (%{browser}) de
     successful_sign_in_html: Bi serkeftî têketin bi %{method} ji %{ip}(%{browser}) çêbû
@@ -1247,7 +1249,7 @@ ku:
     follow:
       body: "%{name} niha te dişopîne!"
       subject: "%{name} niha te dişopîne"
-      title: Şopînereke nû
+      title: Şopînera nû
     follow_request:
       action: Daxwazên şopandinê bi rê ve bibe
       body: "%{name} daxwaza şopandina te kir"
@@ -1416,7 +1418,7 @@ ku:
     notifications: Agahdarî
     preferences: Hilbijarte
     profile: Profîl
-    relationships: Yên tê şopandin û şopîner
+    relationships: Şopandin û şopîner
     statuses_cleanup: Bi xweberî ve jêbirina şandiya
     strikes: Binpêkirinên çavdêriyê
     two_factor_authentication: Piştrastkirinê du-faktorî
@@ -1596,7 +1598,7 @@ ku:
       <p>Originally adapted from the <a href="https://github.com/discourse/discourse">Discourse privacy policy</a>.</p>
     title: "%{instance} mercên bikaranîn û politîkayên nehêniyê"
   themes:
-    contrast: Mastodon (dijberiya bilind)
+    contrast: Mastodon (Dijberiya bilind)
     default: Mastodon (Tarî)
     mastodon-light: Mastodon (Ronahî)
   time:
@@ -1635,8 +1637,8 @@ ku:
       title: Pakêtkirina arşîvan
     sign_in_token:
       details: 'Li vir hûrgiliyên hewldanê hene:'
-      explanation: 'Me hewildanek têketina ajimêra te ji navnîşana IPya nenas nas kir. Ger ev tu bî, ji kerema xwe koda ewlehiyê ya jêr têkeve rûpela jêpirsînê:'
-      further_actions: 'Ger ev ne tu bî, ji kerema xwe re şîfreya xwe biguherîne û li ser hesaba xwe rastkirina du-gavî çalak bike. Tu dikarî wê ji vê derê çêkî:'
+      explanation: 'Me hewildanek têketina ajimêra te ji navnîşana IP ya nenas destnîşan kir. Ger ev tu bî, ji kerema xwe koda ewlehiyê ya jêr binivîsîne di rûpela jêpirsînê de:'
+      further_actions: 'Ku ev ne tu bî, ji kerema xwe re borînpeyva xwe biguherîne û li ser ajimêra xwe rastkirina du-gavî çalak bike. Tu dikarî wê ji vê derê çê bikî:'
       subject: Ji kerema xwe re hewldanên têketinê piştrast bike
       title: Hewldanên têketinê
     warning:
@@ -1693,7 +1695,7 @@ ku:
     invalid_otp_token: Koda du-gavî ya nelê
     invalid_sign_in_token: Kilîda ewlehiyê a nelê
     otp_lost_help_html: Heke te gihîştina herduyan ji dest da, dibe ku tu bi %{email} re têkilî deyne
-    seamless_external_login: Te bi servîsekî biyanî re têketina xwe kir, ji ber vê yekê şîfre û e-name nayê bikaranîn.
+    seamless_external_login: Te bi rajekarke biyanî re têketina xwe kir, ji ber vê yekê borînpeyv û e-name nayê bikaranîn.
     signed_in_as: 'Têketin wekî:'
     suspicious_sign_in_confirmation: Xuya dike ku te berê têketin ji vê amûrê nekiriye, ji ber vê yekê em kodeke ewlehiyê ji navnîşana e-nameya te re dişînin da ku piştrast bikî ku tu ye an na.
   verification:
diff --git a/config/locales/lv.yml b/config/locales/lv.yml
index 8f6df9961..d695c5191 100644
--- a/config/locales/lv.yml
+++ b/config/locales/lv.yml
@@ -169,10 +169,6 @@ lv:
       pending: Gaida pārskatīšanu
       perform_full_suspension: Apturēt
       previous_strikes: Iepriekšējie brīdinājumi
-      previous_strikes_description_html:
-        one: Šim kontam ir <strong>viens</strong> brīdinājums.
-        other: Šim kontam ir <strong>%{count}</strong> brīdinājumi.
-        zero: Š konta <strong>stāvoklis ir labs</strong>.
       promote: Veicināt
       protocol: Protokols
       public: Publisks
@@ -501,6 +497,7 @@ lv:
           zero: Neizdevušies mēģinājumi %{count} dienās.
         no_failures_recorded: Nav reģistrētu kļūdu.
         title: Pieejamība
+        warning: Pēdējais mēģinājums izveidot savienojumu ar šo serveri ir bijis neveiksmīgs
       back_to_all: Visas
       back_to_limited: Ierobežotās
       back_to_warning: Brīdinājums
@@ -537,10 +534,6 @@ lv:
       delivery_error_hint: Ja piegāde nav iespējama %{count} dienas, tā tiks automātiski atzīmēta kā nepiegādājama.
       destroyed_msg: Dati no %{domain} tagad ir gaidīšanas rindā, lai tos drīzumā dzēstu.
       empty: Domēni nav atrasti.
-      known_accounts:
-        one: "%{count} zināms konts"
-        other: "%{count} zināmi konti"
-        zero: Nav zināmu kontu
       moderation:
         all: Visas
         limited: Ierobežotās
@@ -786,6 +779,11 @@ lv:
     system_checks:
       database_schema_check:
         message_html: Notiek datubāzu migrācijas. Lūdzu, palaid tās, lai nodrošinātu, ka lietojumprogramma darbojas, kā paredzēts
+      elasticsearch_running_check:
+        message_html: Nevarēja izveidot savienojumu ar Elasticsearch. Lūdzu, pārbaudi, vai tā darbojas, vai atspējo pilna teksta meklēšanu
+      elasticsearch_version_check:
+        message_html: 'Nesaderīga Elasticsearch versija: %{value}'
+        version_comparison: Darbojas Elasticsearch %{running_version}, tomēr ir nepieciešama %{required_version}
       rules_check:
         action: Pārvaldīt servera nosacījumus
         message_html: Tu neesi definējis nevienu servera nosacījumu.
@@ -805,10 +803,6 @@ lv:
         description_html: Šīs ir saites, kuras pašlaik bieži koplieto konti, no kuriem tavs serveris redz ziņas. Tas var palīdzēt taviem lietotājiem uzzināt, kas notiek pasaulē. Kamēr tu neapstiprini izdevēju, neviena saite netiek rādīta publiski. Vari arī atļaut vai noraidīt atsevišķas saites.
         disallow: Neatļaut saiti
         disallow_provider: Neatļaut publicētāju
-        shared_by_over_week:
-          one: Pēdējās nedēļas laikā kopīgoja viena persona
-          other: Pēdējās nedēļas laikā kopīgoja %{count} personas
-          zero: Pēdējās nedēļas laikā neviens nav kopīgojis
         title: Populārākās saites
         usage_comparison: Šodien kopīgots %{today} reizes, salīdzinot ar %{yesterday} vakar
       pending_review: Gaida pārskatīšanu
@@ -849,10 +843,6 @@ lv:
         trending_rank: 'Populārākie #%{rank}'
         usable: Var tikt lietots
         usage_comparison: Šodien lietots %{today} reizes, salīdzinot ar %{yesterday} vakar
-        used_by_over_week:
-          one: Pēdējās nedēļas laikā izmantoja viens cilvēks
-          other: Pēdējās nedēļas laikā izmantoja %{count} personas
-          zero: Pēdējās nedēļas laikā neviens nav izmantojis
       title: Tendences
     warning_presets:
       add_new: Pievienot jaunu
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index a51ef07af..276fdb9b2 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -1,7 +1,7 @@
 ---
 nl:
   about:
-    about_hashtag_html: Dit zijn openbare toots die getagged zijn met <strong>#%{hashtag}</strong>. Je kunt er op reageren of iets anders mee doen als je op Mastodon (of ergens anders in de fediverse) een account hebt.
+    about_hashtag_html: Dit zijn openbare berichten die getagged zijn met <strong>#%{hashtag}</strong>. Je kunt er op reageren of iets anders mee doen als je op Mastodon (of ergens anders in de fediverse) een account hebt.
     about_mastodon_html: Mastodon is een sociaal netwerk dat gebruikt maakt van open webprotocollen en vrije software. Het is net zoals e-mail gedecentraliseerd.
     about_this: Over deze server
     active_count_after: actief
@@ -31,7 +31,7 @@ nl:
     source_code: Broncode
     status_count_after:
       one: toot
-      other: toots
+      other: berichten
     status_count_before: Zij schreven
     tagline: Vrienden volgen en nieuwe ontdekken
     terms: Gebruiksvoorwaarden
@@ -41,7 +41,7 @@ nl:
       reason: 'Reden:'
       rejecting_media: 'Mediabestanden van deze server worden niet verwerkt en er worden geen thumbnails getoond. Je moet handmatig naar deze server doorklikken om de mediabestanden te kunnen bekijken:'
       rejecting_media_title: Mediabestanden geweigerd
-      silenced: Toots van deze server worden nergens weergegeven, behalve op jouw eigen starttijdlijn wanneer je het account volgt.
+      silenced: Berichten van deze server worden nergens weergegeven, behalve op jouw eigen starttijdlijn wanneer je het account volgt.
       silenced_title: Beperkte servers
       suspended: Je bent niet in staat om iemand van deze server te volgen, en er worden geen gegevens van deze server verwerkt of opgeslagen, en met deze server uitgewisseld.
       suspended_title: Opgeschorte servers
@@ -74,9 +74,9 @@ nl:
       following: Je moet dit account wel al volgen, alvorens je het kan aanbevelen
     posts:
       one: Toot
-      other: Toots
-    posts_tab_heading: Toots
-    posts_with_replies: Toots en reacties
+      other: Berichten
+    posts_tab_heading: Berichten
+    posts_with_replies: Berichten en reacties
     roles:
       admin: Beheerder
       bot: Bot
@@ -193,7 +193,7 @@ nl:
         targeted_reports: Door anderen gerapporteerd
       silence: Beperken
       silenced: Beperkt
-      statuses: Toots
+      statuses: Berichten
       subscribe: Abonneren
       suspended: Opgeschort
       suspension_irreversible: De gegevens van dit account zijn onomkeerbaar verwijderd. Je kunt het opschorten van dit account ongedaan maken zodat het weer valt te gebruiken, maar de verwijderde gegevens worden hiermee niet hersteld.
@@ -229,7 +229,7 @@ nl:
         destroy_custom_emoji: Lokale emoji verwijderen
         destroy_domain_allow: Domeingoedkeuring verwijderen
         destroy_domain_block: Domeinblokkade verwijderen
-        destroy_email_domain_block: E-maildomeinblokkade verwijderen
+        destroy_email_domain_block: Blokkade van e-maildomein verwijderen
         destroy_ip_block: IP-regel verwijderen
         destroy_status: Toot verwijderen
         destroy_unavailable_domain: Niet beschikbaar domein verwijderen
@@ -245,16 +245,16 @@ nl:
         reset_password_user: Wachtwoord opnieuw instellen
         resolve_report: Rapportage oplossen
         sensitive_account: De media in jouw account als gevoelig markeren
-        silence_account: Account negeren
+        silence_account: Account beperken
         suspend_account: Account opschorten
         unassigned_report: Rapportage niet langer toewijzen
         unsensitive_account: De media in jouw account niet langer als gevoelig markeren
-        unsilence_account: Account niet langer negeren
+        unsilence_account: Account niet langer beperken
         unsuspend_account: Account niet langer opschorten
         update_announcement: Mededeling bijwerken
         update_custom_emoji: Lokale emoji bijwerken
         update_domain_block: Domeinblokkade bijwerken
-        update_status: Toot bijwerken
+        update_status: Bericht bijwerken
       actions:
         assigned_to_self_report_html: "%{name} heeft rapportage %{target} aan zichzelf toegewezen"
         change_email_user_html: "%{name} veranderde het e-mailadres van gebruiker %{target}"
@@ -274,7 +274,7 @@ nl:
         destroy_domain_block_html: Domein %{target} is door %{name} gedeblokkeerd
         destroy_email_domain_block_html: "%{name} heeft het e-maildomein %{target} gedeblokkeerd"
         destroy_ip_block_html: "%{name} verwijderde regel voor IP %{target}"
-        destroy_status_html: Toot van %{target} is door %{name} verwijderd
+        destroy_status_html: Bericht van %{target} is door %{name} verwijderd
         destroy_unavailable_domain_html: "%{name} heeft de bezorging voor domein %{target} hervat"
         disable_2fa_user_html: De vereiste tweestapsverificatie voor %{target} is door %{name} uitgeschakeld
         disable_custom_emoji_html: Emoji %{target} is door %{name} uitgeschakeld
@@ -297,8 +297,8 @@ nl:
         update_announcement_html: "%{name} heeft de mededeling %{target} bijgewerkt"
         update_custom_emoji_html: Emoji %{target} is door %{name} bijgewerkt
         update_domain_block_html: "%{name} heeft de domeinblokkade bijgewerkt voor %{target}"
-        update_status_html: "%{name} heeft de toots van %{target} bijgewerkt"
-      deleted_status: "(verwijderde toot}"
+        update_status_html: "%{name} heeft de berichten van %{target} bijgewerkt"
+      deleted_status: "(verwijderd bericht}"
       empty: Geen logs gevonden.
       filter_by_action: Op actie filteren
       filter_by_user: Op gebruiker filteren
@@ -461,11 +461,11 @@ nl:
     relays:
       add_new: Nieuwe relayserver toevoegen
       delete: Verwijderen
-      description_html: Een <strong>federatierelay</strong> is een tussenliggende server die grote hoeveelheden openbare toots uitwisselt tussen servers die zich hierop hebben geabonneerd. <strong>Het kan kleine en middelgrote servers helpen om content uit de fediverse te ontdekken</strong>, waarvoor anders lokale gebruikers handmatig mensen van externe servers moeten volgen.
+      description_html: Een <strong>federatierelay</strong> is een tussenliggende server die grote hoeveelheden openbare berichten uitwisselt tussen servers die zich hierop hebben geabonneerd. <strong>Het kan kleine en middelgrote servers helpen om content van de fediverse te ontdekken</strong>, waarvoor anders lokale gebruikers handmatig mensen van externe servers moeten volgen.
       disable: Uitschakelen
       disabled: Uitgeschakeld
       enable: Inschakelen
-      enable_hint: Eenmaal ingeschakeld gaat jouw server zich op alle openbare toots van deze relayserver abonneren en stuurt het de openbare toots van jouw server naar de relayserver.
+      enable_hint: Eenmaal ingeschakeld gaat jouw server zich op alle openbare berichten van deze relayserver abonneren en stuurt het de openbare berichten van jouw server naar de relayserver.
       enabled: Ingeschakeld
       inbox_url: Relay-URL
       pending: Aan het wachten op toestemming van de relayserver
@@ -506,7 +506,7 @@ nl:
       reported_by: Gerapporteerd door
       resolved: Opgelost
       resolved_msg: Rapportage succesvol opgelost!
-      status: Toot
+      status: Bericht
       title: Rapportages
       unassign: Niet langer toewijzen
       unresolved: Onopgelost
@@ -520,7 +520,7 @@ nl:
       title: Serverregels
     settings:
       activity_api_enabled:
-        desc_html: Wekelijks overzicht van de hoeveelheid lokale toots, actieve gebruikers en nieuwe registraties
+        desc_html: Wekelijks overzicht van de hoeveelheid lokale berichten, actieve gebruikers en nieuwe registraties
         title: Statistieken over gebruikersactiviteit via de API publiceren
       bootstrap_timeline_accounts:
         desc_html: Meerdere gebruikersnamen met komma's scheiden. Deze accounts worden in ieder geval aan nieuwe gebruikers aanbevolen
@@ -533,7 +533,7 @@ nl:
         title: Aangepaste CSS
       default_noindex:
         desc_html: Heeft invloed op alle gebruikers die deze instelling niet zelf hebben veranderd
-        title: Toots van gebruikers standaard niet door zoekmachines laten indexeren
+        title: Berichten van gebruikers standaard niet door zoekmachines laten indexeren
       domain_blocks:
         all: Aan iedereen
         disabled: Aan niemand
@@ -561,7 +561,7 @@ nl:
           desc_html: Wordt op de voorpagina weergegeven wanneer registratie van nieuwe accounts is uitgeschakeld<br>En ook hier kan je HTML gebruiken
           title: Bericht wanneer registratie is uitgeschakeld
         deletion:
-          desc_html: Toestaan dat iedereen hun eigen account kan verwijderen
+          desc_html: Toestaan dat iedereen diens eigen account kan verwijderen
           title: Verwijderen account toestaan
         min_invite_role:
           disabled: Niemand
@@ -606,7 +606,7 @@ nl:
         title: Hashtags toestaan om trending te worden zonder voorafgaande beoordeling
       trends:
         desc_html: Eerder beoordeelde hashtags die op dit moment trending zijn openbaar tonen
-        title: Trending hashtags
+        title: Trends
     site_uploads:
       delete: Geüpload bestand verwijderen
       destroyed_msg: Verwijderen website-upload geslaagd!
@@ -615,8 +615,8 @@ nl:
       deleted: Verwijderd
       media:
         title: Media
-      no_status_selected: Er werden geen toots gewijzigd, omdat er geen enkele werd geselecteerd
-      title: Toots van account
+      no_status_selected: Er werden geen berichten gewijzigd, omdat er geen enkele werd geselecteerd
+      title: Berichten van account
       with_media: Met media
     system_checks:
       database_schema_check:
@@ -662,14 +662,14 @@ nl:
       guide_link: https://crowdin.com/project/mastodon/nl
       guide_link_text: Iedereen kan bijdragen.
     sensitive_content: Gevoelige inhoud
-    toot_layout: Lay-out van toots
+    toot_layout: Lay-out van berichten
   application_mailer:
     notification_preferences: E-mailvoorkeuren wijzigen
     salutation: "%{name},"
     settings: 'E-mailvoorkeuren wijzigen: %{link}'
     view: 'Bekijk:'
     view_profile: Profiel bekijken
-    view_status: Toot bekijken
+    view_status: Bericht bekijken
   applications:
     created: Aanmaken toepassing geslaagd
     destroyed: Verwijderen toepassing geslaagd
@@ -768,8 +768,8 @@ nl:
     success_msg: Jouw account is succesvol verwijderd
     warning:
       before: 'Lees deze tekst zorgvuldig voordat je verder gaat:'
-      caches: Toots en media die op andere servers zijn opgeslagen kunnen daar achterblijven
-      data_removal: Jouw toots en andere gegevens worden permanent verwijderd
+      caches: Berichten en media die op andere servers zijn opgeslagen kunnen daar achterblijven
+      data_removal: Jouw berichten en andere gegevens worden permanent verwijderd
       email_change_html: Je kunt <a href="%{path}">je e-mailadres wijzigen</a> zonder dat je jouw account hoeft te verwijderen
       email_contact_html: Wanneer het nog steeds niet aankomt, kun je voor hulp e-mailen naar <a href="mailto:%{email}">%{email}</a>
       email_reconfirmation_html: Wanneer je de bevestigingsmail niet hebt ontvangen, kun je deze <a href="%{path}">opnieuw aanvragen</a>
@@ -805,7 +805,7 @@ nl:
     archive_takeout:
       date: Datum
       download: Jouw archief downloaden
-      hint_html: Je kunt een archief opvragen van jouw <strong>toots en geüploade media</strong>. De geëxporteerde gegevens zijn in het ActivityPub-formaat, dat door hiervoor geschikte software valt uit te lezen. Je kunt elke 7 dagen een kopie van je archief aanvragen.
+      hint_html: Je kunt een archief opvragen van jouw <strong>berichten en geüploade media</strong>. De geëxporteerde gegevens zijn in het ActivityPub-formaat, dat door hiervoor geschikte software valt uit te lezen. Je kunt elke 7 dagen een kopie van je archief aanvragen.
       in_progress: Jouw archief wordt samengesteld...
       request: Jouw archief opvragen
       size: Omvang
@@ -820,7 +820,7 @@ nl:
     add_new: Nieuwe toevoegen
     errors:
       limit: Je hebt al het maximaal aantal hashtags uitgelicht
-    hint_html: "<strong>Wat zijn uitgelichte hashtags?</strong> Deze worden prominent op jouw openbare profiel getoond en stelt mensen in staat om jouw openbare toots per hashtag te bekijken. Het zijn een goed hulpmiddel om creatieve werkzaamheden of langetermijnprojecten bij te houden."
+    hint_html: "<strong>Wat zijn uitgelichte hashtags?</strong> Deze worden prominent op jouw openbare profiel getoond en stelt mensen in staat om jouw openbare berichten per hashtag te bekijken. Het zijn een goed hulpmiddel om creatieve werkzaamheden of langetermijnprojecten bij te houden."
   filters:
     contexts:
       account: Profielen
@@ -901,7 +901,7 @@ nl:
       limit: Je hebt het maximaal aantal lijsten bereikt
   media_attachments:
     validations:
-      images_and_video: Een video kan niet aan een toot met afbeeldingen worden gekoppeld
+      images_and_video: Een video kan niet aan een bericht met afbeeldingen worden gekoppeld
       not_ready: Kan geen bestanden toevoegen die nog niet zijn verwerkt. Probeer het later opnieuw!
       too_many: Er kunnen niet meer dan 4 afbeeldingen toegevoegd worden
   migrations:
@@ -954,8 +954,8 @@ nl:
         other: "%{count} nieuwe meldingen sinds jouw laatste bezoek \U0001F418"
       title: Tijdens jouw afwezigheid...
     favourite:
-      body: 'Jouw toot werd door %{name} aan hun favorieten toegevoegd:'
-      subject: "%{name} voegde jouw toot als favoriet toe"
+      body: 'Jouw bericht werd door %{name} aan diens favorieten toegevoegd:'
+      subject: "%{name} voegde jouw bericht als favoriet toe"
       title: Nieuwe favoriet
     follow:
       body: "%{name} volgt jou nu!"
@@ -974,11 +974,11 @@ nl:
     poll:
       subject: Een poll van %{name} is beëindigd
     reblog:
-      body: 'Jouw toot werd door %{name} geboost:'
-      subject: "%{name} boostte jouw toot"
+      body: 'Jouw bericht werd door %{name} geboost:'
+      subject: "%{name} boostte jouw bericht"
       title: Nieuwe boost
     status:
-      subject: "%{name} heeft zojuist een toot geplaatst"
+      subject: "%{name} heeft zojuist een bericht geplaatst"
   notifications:
     email_events: E-mailmeldingen voor gebeurtenissen
     email_events_hint: 'Selecteer gebeurtenissen waarvoor je meldingen wilt ontvangen:'
@@ -997,7 +997,7 @@ nl:
     code_hint: Voer de code in die door de authenticatie-app werd gegenereerd
     description_html: Na het instellen van <strong>tweestapsverificatie</strong> met een authenticatie-app, kun je alleen inloggen als je jouw mobiele telefoon bij je hebt. Hiermee genereer je namelijk de in te voeren toegangscode.
     enable: Inschakelen
-    instructions_html: "<strong>Scan deze QR-code in Google Authenticator of een soortgelijke app op jouw mobiele telefoon</strong>. Van nu af aan genereert deze app toegangscodes die je bij het inloggen moet invoeren."
+    instructions_html: "<strong>Scan deze QR-code in Google Authenticator of een soortgelijke app op jouw mobiele telefoon</strong>. Vanaf nu genereert deze app toegangscodes die je bij het inloggen moet invoeren."
     manual_instructions: 'Voor het geval je de QR-code niet kunt scannen en het handmatig moet invoeren, vind je hieronder de geheime code in platte tekst:'
     setup: Instellen
     wrong_code: De ingevoerde code is ongeldig! Klopt de systeemtijd van de server en die van jouw apparaat?
@@ -1053,17 +1053,17 @@ nl:
   remote_interaction:
     favourite:
       proceed: Doorgaan met toevoegen aan jouw favorieten
-      prompt: 'Je wilt de volgende toot aan jouw favorieten toevoegen:'
+      prompt: 'Je wilt het volgende bericht aan jouw favorieten toevoegen:'
     reblog:
       proceed: Doorgaan met boosten
-      prompt: 'Je wilt de volgende toot boosten:'
+      prompt: 'Je wilt het volgende bericht boosten:'
     reply:
       proceed: Doorgaan met reageren
-      prompt: 'Je wilt op de volgende toot reageren:'
+      prompt: 'Je wilt op het volgende bericht reageren:'
   scheduled_statuses:
-    over_daily_limit: Je hebt de limiet van %{limit} in te plannen toots voor die dag overschreden
-    over_total_limit: Je hebt de limiet van %{limit} in te plannen toots overschreden
-    too_soon: De datum voor de ingeplande toot moet in de toekomst liggen
+    over_daily_limit: Je hebt de limiet van %{limit} in te plannen berichten voor vandaag overschreden
+    over_total_limit: Je hebt de limiet van %{limit} in te plannen berichten overschreden
+    too_soon: De datum voor het ingeplande bericht moet in de toekomst liggen
   sessions:
     activity: Laatst actief
     browser: Webbrowser
@@ -1093,7 +1093,7 @@ nl:
       adobe_air: Adobe Air
       android: Android
       blackberry: Blackberry
-      chrome_os: ChromeOS
+      chrome_os: Chrome OS
       firefox_os: Firefox OS
       ios: iOS
       linux: Linux
@@ -1144,12 +1144,12 @@ nl:
       one: 'bevatte een niet toegestane hashtag: %{tags}'
       other: 'bevatte niet toegestane hashtags: %{tags}'
     errors:
-      in_reply_not_found: De toot waarop je probeert te reageren lijkt niet te bestaan.
+      in_reply_not_found: Het bericht waarop je probeert te reageren lijkt niet te bestaan.
     open_in_web: In de webapp openen
     over_character_limit: Limiet van %{max} tekens overschreden
     pin_errors:
-      limit: Je hebt het maximaal aantal toots al vastgezet
-      ownership: Een toot van iemand anders kan niet worden vastgezet
+      limit: Je hebt het maximaal aantal bericht al vastgemaakt
+      ownership: Een bericht van iemand anders kan niet worden vastgemaakt
       reblog: Een boost kan niet worden vastgezet
     poll:
       total_people:
@@ -1174,7 +1174,7 @@ nl:
       unlisted: Minder openbaar
       unlisted_long: Aan iedereen tonen, maar niet op openbare tijdlijnen
   stream_entries:
-    pinned: Vastgemaakte toot
+    pinned: Vastgemaakt bericht
     reblogged: boostte
     sensitive_content: Gevoelige inhoud
   tags:
@@ -1332,7 +1332,7 @@ nl:
     otp_lost_help_html: Als je toegang tot beiden kwijt bent geraakt, neem dan contact op via %{email}
     seamless_external_login: Je bent ingelogd via een externe dienst, daarom zijn wachtwoorden en e-mailinstellingen niet beschikbaar.
     signed_in_as: 'Ingelogd als:'
-    suspicious_sign_in_confirmation: Het lijkt er op dat je nog niet eerder op dit apparaat bent ingelogd, en je bent een tijdje niet ingelogd, dus sturen we een beveiligingscode naar je e-mailadres om te bevestigen dat jij het bent.
+    suspicious_sign_in_confirmation: Het lijkt er op dat je nog niet eerder op dit apparaat bent ingelogd, dus sturen we een beveiligingscode naar jouw e-mailadres om te bevestigen dat jij het bent.
   verification:
     explanation_html: 'Je kunt <strong>jezelf verifiëren als de eigenaar van de links in de metadata van jouw profiel</strong>. Hiervoor moet op de gelinkte website een link terug naar jouw Mastodonprofiel staan. Deze link <strong>moet</strong> het <code>rel="me"</code>-attribuut bevatten. De omschrijving van de link maakt niet uit. Hier is een voorbeeld:'
     verification: Verificatie
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index 9a093f9d3..15755bde7 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -170,10 +170,6 @@ pl:
       pending: Oczekuje na przegląd
       perform_full_suspension: Zawieś
       previous_strikes: Poprzednie ostrzeżenia
-      previous_strikes_description_html:
-        one: To konto ma <strong>jedno</strong> ostrzeżenie.
-        other: To konto ma <strong>%{count}</strong> ostrzeżeń.
-        zero: To konto jest <strong>w dobrym stanie</strong>.
       promote: Podnieś uprawnienia
       protocol: Protokół
       public: Publiczne
@@ -748,6 +744,11 @@ pl:
     system_checks:
       database_schema_check:
         message_html: Istnieją oczekujące migracje bazy danych. Uruchom je, aby upewnić się, że aplikacja działa tak, jak powinna
+      elasticsearch_running_check:
+        message_html: Nie można połączyć się z Elasticsearch. Sprawdź czy jest uruchomiony lub wyłącz wyszukiwanie pełnotekstowe
+      elasticsearch_version_check:
+        message_html: 'Niekompatybilna wersja Elasticsearch: %{value}'
+        version_comparison: Elasticsearch w wersji %{running_version} jest uruchomiony, ale wymagana wersja to %{required_version}
       rules_check:
         action: Zarządzaj regułami serwera
         message_html: Nie zdefiniowano żadnych reguł serwera.
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 912e42a7d..1d0de0d4a 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -1471,6 +1471,7 @@ pt-BR:
       title:
         delete_statuses: Publicações removidas
         disable: Conta bloqueada
+        mark_statuses_as_sensitive: Postagens marcadas como sensíveis
         none: Aviso
         silence: Conta silenciada
         suspend: Conta banida
diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml
index 0fdfff517..05f6ebf07 100644
--- a/config/locales/pt-PT.yml
+++ b/config/locales/pt-PT.yml
@@ -165,10 +165,6 @@ pt-PT:
       pending: Pendente de revisão
       perform_full_suspension: Fazer suspensão completa
       previous_strikes: Punições anteriores
-      previous_strikes_description_html:
-        one: Esta conta tem <strong>1</strong> punição.
-        other: Esta conta tem <strong>%{count}</strong> punições.
-        zero: Esta conta está <strong>em situação regular</strong>.
       promote: Promover
       protocol: Protocolo
       public: Público
@@ -490,6 +486,7 @@ pt-PT:
           other: Tentativas em %{count} dias diferentes.
         no_failures_recorded: Sem falhas registadas.
         title: Disponibilidade
+        warning: A última tentativa de conectar a este servidor não foi bem sucedida
       back_to_all: Todas
       back_to_limited: Limitadas
       back_to_warning: Aviso
@@ -526,10 +523,6 @@ pt-PT:
       delivery_error_hint: Se a entrega não for possível durante %{count} dias, será automaticamente marcada como não realizável.
       destroyed_msg: Dados de %{domain} estão agora na fila para iminente eliminação.
       empty: Não foram encontrados domínios.
-      known_accounts:
-        one: "%{count} conta conhecida"
-        other: "%{count} contas conhecidas"
-        zero: Nenhuma conta conhecida
       moderation:
         all: Todas
         limited: Limitadas
@@ -774,6 +767,11 @@ pt-PT:
     system_checks:
       database_schema_check:
         message_html: Existem migrações de base de dados pendentes. Por favor, execute-as para garantir que o aplicativo se comporte como esperado
+      elasticsearch_running_check:
+        message_html: Não foi possível conectar ao Elasticsearch. Por favor, verifique se está em execução, ou desabilite a pesquisa de texto completo
+      elasticsearch_version_check:
+        message_html: 'Versão de Elasticsearch incompatível: %{value}'
+        version_comparison: A versão de Elasticsearch %{running_version} está em execução. No entanto, é obrigatória a versão %{required_version}
       rules_check:
         action: Gerir regras da instância
         message_html: Não definiu nenhuma regra para a instância.
@@ -793,10 +791,6 @@ pt-PT:
         description_html: Estes são links que atualmente estão a ser frequentemente partilhados por contas visiveis pelo seu servidor. Eles podem ajudar os seus utilizador a descobrir o que está a acontecer no mundo. Nenhum link é exibido publicamente até que aprove o editor. Também pode permitir ou rejeitar links individualmente.
         disallow: Não permitir link
         disallow_provider: Não permitir editor
-        shared_by_over_week:
-          one: Partilhado por uma pessoa na última semana
-          other: Partilhado por %{count} pessoas na última semana
-          zero: Partilhado por ninguém na última semana
         title: Links em destaque
         usage_comparison: Partilhado %{today} vezes hoje, em comparação com %{yesterday} ontem
       pending_review: Pendente de revisão
@@ -836,10 +830,6 @@ pt-PT:
         trending_rank: 'Tendência #%{rank}'
         usable: Pode ser utilizada
         usage_comparison: Utilizada %{today} vezes hoje, em comparação com %{yesterday} ontem
-        used_by_over_week:
-          one: Utilizada por uma pessoa na última semana
-          other: Utilizada por %{count} pessoas na última semana
-          zero: Utilizada por ninguém na última semana
       title: Tendências
     warning_presets:
       add_new: Adicionar novo
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 579ea6462..d6eab2a99 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -174,9 +174,10 @@ ru:
       perform_full_suspension: Блокировка
       previous_strikes: Предыдущие замечания
       previous_strikes_description_html:
-        one: У этой учетной записи <strong>одно</strong> замечание.
-        other: У этой учетной записи <strong>%{count}</strong> замечания.
-        zero: У этой учетной записи <strong>хорошая репутация</strong>.
+        few: У этой учётной записи <strong>%{count}</strong> замечания.
+        many: У этой учётной записи <strong>%{count}</strong> замечаний.
+        one: У этой учётной записи <strong>одно</strong> замечание.
+        other: У этой учетной записи <strong>%{count}</strong> замечание.
       promote: Повысить
       protocol: Протокол
       public: Публичный
@@ -480,6 +481,7 @@ ru:
       availability:
         no_failures_recorded: Сбоев в записи нет.
         title: Доступность
+        warning: Последняя попытка подключения к этому серверу не удалась
       back_to_all: Все узлы
       back_to_limited: Все ограниченные узлы
       back_to_warning: Все узлы требующие внимания
@@ -504,9 +506,10 @@ ru:
       destroyed_msg: Данные для домена %{domain} поставлены в очередь на удаление.
       empty: Домены не найдены.
       known_accounts:
+        few: "%{count} известные учётные записи"
+        many: "%{count} известных учётных записей"
         one: "%{count} известная учётная запись"
-        other: "%{count} известных учётных записей"
-        zero: Нет известных учётных записей
+        other: "%{count} известная учётная запись"
       moderation:
         all: Все
         limited: Ограниченные
@@ -742,10 +745,16 @@ ru:
         none: "%{name} отправил(а) предупреждение %{target}"
         sensitive: "%{name} отметил(а) учетную запись %{target} как деликатную"
         silence: "%{name} ограничил(а) учетную запись %{target}"
+      appeal_approved: Обжаловано
       appeal_pending: Обжалование в обработке
     system_checks:
       database_schema_check:
         message_html: Есть отложенные миграции базы данных. Запустите их, чтобы убедиться, что приложение работает должным образом
+      elasticsearch_running_check:
+        message_html: Не удалось подключиться к Elasticsearch. Пожалуйста, проверьте, что он запущен, или отключите полнотекстовый поиск
+      elasticsearch_version_check:
+        message_html: 'Несовместимая версия Elasticsearch: %{value}'
+        version_comparison: Запущен Elasticsearch %{running_version}, а необходим %{required_version}
       rules_check:
         action: Управление правилами сервера
         message_html: Вы не определили правила сервера.
@@ -765,9 +774,10 @@ ru:
         disallow: Запретить ссылку
         disallow_provider: Отклонить издание
         shared_by_over_week:
+          few: Поделилось %{count} человека за последнюю неделю
+          many: Поделилось %{count} человек за последнюю неделю
           one: Поделился один человек за последнюю неделю
-          other: Поделилось %{count} людей за последнюю неделю
-          zero: Никто не поделился за последнюю неделю
+          other: Поделился %{count} человек за последнюю неделю
         title: Актуальные ссылки
         usage_comparison: Поделились %{today} раз сегодня, по сравнению с %{yesterday} вчера
       pending_review: Ожидает рассмотрения
@@ -795,9 +805,10 @@ ru:
         usable: Может использоваться
         usage_comparison: Использовано %{today} сегодня, для сравнения вчера %{yesterday}
         used_by_over_week:
-          one: За последнюю неделю использовано одним человеком
-          other: За последнюю неделю использовано %{count} людьми
-          zero: За последнюю неделю никто не использовал
+          few: За последнюю неделю использовало %{count} человека
+          many: За последнюю неделю использовало %{count} человек
+          one: За последнюю неделю использовал один человек
+          other: За последнюю неделю использовал %{count} человек
       title: Популярное
     warning_presets:
       add_new: Добавить
@@ -806,6 +817,8 @@ ru:
       empty: Вы еще не определили пресеты предупреждений.
       title: Управление шаблонами предупреждений
   admin_mailer:
+    new_appeal:
+      subject: "%{username} обжалует решение модерации на %{instance}"
     new_pending_account:
       body: Ниже указана информация учётной записи. Вы можете одобрить или отклонить заявку.
       subject: Новая учётная запись для рассмотрения на %{instance} (%{username})
diff --git a/config/locales/simple_form.cs.yml b/config/locales/simple_form.cs.yml
index c8cc9985d..767558b6f 100644
--- a/config/locales/simple_form.cs.yml
+++ b/config/locales/simple_form.cs.yml
@@ -35,6 +35,7 @@ cs:
         current_password: Z bezpečnostních důvodů prosím zadejte heslo současného účtu
         current_username: Potvrďte prosím tuto akci zadáním uživatelského jména aktuálního účtu
         digest: Odesíláno pouze po dlouhé době nečinnosti a pouze, pokud jste při své nepřítomnosti obdrželi osobní zprávy
+        discoverable: Umožnit, aby mohli váš účet objevit neznámí lidé pomocí doporučení, trendů a dalších funkcí
         email: Bude vám poslán potvrzovací e-mail
         fields: Na profilu můžete mít až 4 položky zobrazené jako tabulka
         header: PNG, GIF či JPG. Maximálně %{size}. Bude zmenšen na %{dimensions} px
diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml
index c44a789d9..e6af4cee7 100644
--- a/config/locales/simple_form.fa.yml
+++ b/config/locales/simple_form.fa.yml
@@ -7,12 +7,12 @@ fa:
       account_migration:
         acct: نشانی username@domain را برای حسابی که می‌خواهید به آن منتقل شوید بنویسید
       account_warning_preset:
-        text: می‌توانید مانند بوق‌های معمولی کاربران دیگر را نام ببرید یا پیوند و برچسب بگذارید
+        text: می‌توانید مانند فرسته‌های معمولی کاربران دیگر را نام ببرید یا پیوند و برچسب بگذارید
         title: اختیاری. برای گیرنده قابل مشاهده نیست
       admin_account_action:
-        include_statuses: این کاربر خواهد دید که کدام بوق او موجب اقدام مدیریتی یا هشدار شده است
+        include_statuses: این کاربر خواهد دید که کدام فرسته او موجب اقدام مدیریتی یا هشدار شده است
         send_email_notification: توضیحی که کاربر می‌بینید که برای حسابش چه رخ داده است
-        text_html: اختیاری. می‌توانید مثل بوق‌های معمولی بنویسید. می‌توانید برای صرفه‌جویی در زمان <a href="%{path}">هشدارهای ازپیش‌آماده بیفزایید</a>
+        text_html: اختیاری. می‌توانید مثل فرسته‌های معمولی بنویسید. می‌توانید برای صرفه‌جویی در زمان <a href="%{path}">هشدارهای ازپیش‌آماده بیفزایید</a>
         type_html: با حساب <strong>%{acct}</strong> می‌خواهید چه کار کنید؟‌
         types:
           disable: از استفادهٔ کاربر از حسابش جلوگیری می‌کند، ولی محتوایش را حذف یا پنهان نمی‌کند.
@@ -26,7 +26,9 @@ fa:
         ends_at: اختیاری. اعلامیه در این به صورت خودکار نامنتشر خواهد شد
         scheduled_at: برای انتشار فوری اعلامیه، خالی بگذارید
         starts_at: اختیاری. در صورتی که اعلامیه‌تان محدود به بازهٔ زمانی خاصی است
-        text: می‌توانید مانند یک بوق‌ معمولی بنویسید. یادتان باشد که اعلامیهٔ شما فضای صفحهٔ کاربران را اشغال خواهد کرد
+        text: می‌توانید مانند یک فرسته‌ معمولی بنویسید. یادتان باشد که اعلامیهٔ شما فضای صفحهٔ کاربران را اشغال خواهد کرد
+      appeal:
+        text: فقط یک بار می‌توانید برای اخطار درخواست تجدیدنظر کنید
       defaults:
         autofollow: کسانی که از راه دعوت‌نامه عضو می‌شوند به طور خودکار پیگیر شما خواهند شد
         avatar: یکی از قالب‌های PNG یا  GIF یا JPG. بیشترین اندازه %{size}. تصویر به اندازهٔ %{dimensions} پیکسل تبدیل خواهد شد
@@ -35,24 +37,25 @@ fa:
         current_password: به دلایل امنیتی لطفاً رمز این حساب را وارد کنید
         current_username: برای تأیید، لطفاً نام کاربری حساب فعلی را وارد کنید
         digest: تنها وقتی فرستاده می‌شود که مدتی طولانی فعالیتی نداشته باشید و در این مدت برای شما پیغام خصوصی‌ای نوشته شده باشد
+        discoverable: اجازه دهید حساب‌تان از طریق پیشنهادها، پرطرفدارها و سایر قابلیت‌ها، توسط افراد غریبه قابل کشف باشد
         email: به شما ایمیل تأییدی فرستاده خواهد شد
         fields: شما می‌توانید تا چهار مورد را در یک جدول در نمایهٔ خود نمایش دهید
         header: یکی از قالب‌های PNG یا  GIF یا JPG. بیشترین اندازه %{size}. تصویر به اندازهٔ %{dimensions} پیکسل تبدیل خواهد شد
         inbox_url: نشانی صفحهٔ اصلی رله‌ای را که می‌خواهید به کار ببرید کپی کنید
-        irreversible: بوق‌های پالوده به طور برگشت‌ناپذیری ناپدید می‌شوند، حتا اگر بعدها پالایه برداشته شود
+        irreversible: فرسته‌های پالوده به طور برگشت‌ناپذیری ناپدید می‌شوند، حتا اگر بعدها پالایه برداشته شود
         locale: زبان واسط کاربری، رایانامه‌ها و آگاهی‌های ارسالی
         locked: باید پیگیران تازه را خودتان تأیید کنید
         password: دست‌کم باید ۸ نویسه داشته باشد
-        phrase: مستقل از کوچکی و بزرگی حروف، با متن اصلی یا هشدار محتوای بوق‌ها مقایسه می‌شود
+        phrase: مستقل از کوچکی و بزرگی حروف، با متن اصلی یا هشدار محتوای فرسته‌ها مقایسه می‌شود
         scopes: واسط‌های برنامه‌نویسی که این برنامه به آن دسترسی دارد. اگر بالاترین سطح دسترسی را انتخاب کنید، دیگر نیازی به انتخاب سطح‌های پایینی ندارید.
-        setting_aggregate_reblogs: برای بازبوق‌هایی که به تازگی برایتان نمایش داده شده‌اند، بازبوق‌های بیشتر را نشان نده (فقط روی بازبوق‌های اخیر تأثیر می‌گذارد)
+        setting_aggregate_reblogs: برای تقویت‌هایی که به تازگی برایتان نمایش داده شده‌اند، تقویت‌های بیشتر را نمایش نده (فقط روی تقویت‌های اخیر تأثیر می‌گذارد)
         setting_default_sensitive: تصاویر حساس به طور پیش‌فرض پنهان هستند و می‌توانند با یک کلیک آشکار شوند
         setting_display_media_default: تصویرهایی را که به عنوان حساس علامت زده شده‌اند پنهان کن
         setting_display_media_hide_all: همیشه همهٔ عکس‌ها و ویدیوها را پنهان کن
         setting_display_media_show_all: همیشه تصویرهایی را که به عنوان حساس علامت زده شده‌اند را نشان بده
         setting_hide_network: فهرست پیگیران شما و فهرست کسانی که شما پی می‌گیرید روی نمایهٔ شما دیده نخواهد شد
         setting_noindex: روی نمایهٔ عمومی و صفحهٔ نوشته‌های شما تأثیر می‌گذارد
-        setting_show_application: برنامه‌ای که به کمک آن بوق می‌زنید، در جزئیات بوق شما نمایش خواهد یافت
+        setting_show_application: برنامه‌ای که به کمک آن فرسته می‌زنید، در جزئیات فرسته شما نمایش خواهد یافت
         setting_use_blurhash: سایه‌ها بر اساس رنگ‌های به‌کاررفته در تصویر پنهان‌شده ساخته می‌شوند ولی جزئیات تصویر در آن‌ها آشکار نیست
         setting_use_pending_items: به جای پیش‌رفتن خودکار در فهرست، به‌روزرسانی فهرست نوشته‌ها را پشت یک کلیک پنهان کن
         username: نام کاربری شما روی %{domain} یکتا خواهد بود
@@ -60,6 +63,7 @@ fa:
       domain_allow:
         domain: این دامین خواهد توانست داده‌ها از این سرور را دریافت کند و داده‌های از این دامین در این‌جا پردازش و ذخیره خواهند شد
       email_domain_block:
+        domain: این می‌تواند نام دامنه‌ای باشد که در نشانی رایانامه یا رکورد MX استفاده می‌شود. پس از ثبت نام بررسی خواهند شد.
         with_dns_records: تلاشی برای resolve کردن رکوردهای ساناد دامنهٔ داده‌شده انجام شده و نتیجه نیز مسدود خواهد شد
       featured_tag:
         name: 'شاید بخواهید چنین چیزهایی را به کار ببرید:'
@@ -99,7 +103,7 @@ fa:
         text: متن از پیش آماده‌شده
         title: عنوان
       admin_account_action:
-        include_statuses: بوق‌های گزارش‌شده را در ایمیل بگنجان
+        include_statuses: فرسته‌های گزارش‌شده را در ایمیل بگنجان
         send_email_notification: اطلاع‌رسانی به کاربر از راه ایمیل
         text: هشدار موردی
         type: کنش
@@ -116,6 +120,8 @@ fa:
         scheduled_at: زمان‌بندی انتشار
         starts_at: آغاز رویداد
         text: اعلامیه
+      appeal:
+        text: توضیح دهید که چرا این تصمیم باید معکوس شود
       defaults:
         autofollow: دعوت از دیگران برای عضو شدن و پیگیری حساب شما
         avatar: تصویر نمایه
@@ -144,10 +150,10 @@ fa:
         password: رمز
         phrase: کلیدواژه یا عبارت
         setting_advanced_layout: فعال‌سازی رابط کاربری پیشرفته
-        setting_aggregate_reblogs: بازبوق‌ها را متحد کن
+        setting_aggregate_reblogs: تقویت‌ها را در خط‌زمانی گروه‌بندی کن
         setting_auto_play_gif: پخش خودکار تصویرهای متحرک
-        setting_boost_modal: نمایش پیغام تأیید پیش از بازبوقیدن
-        setting_crop_images: در بوق‌های بازنشده، تصویرها را به ابعاد ۱۶×۹ کوچک کن
+        setting_boost_modal: نمایش پیغام تأیید پیش از تقویت کردن
+        setting_crop_images: در فرسته‌های ناگسترده، تصویرها را به ابعاد ‎۱۶×۹ کوچک کن
         setting_default_language: زبان نوشته‌های شما
         setting_default_privacy: حریم خصوصی نوشته‌ها
         setting_default_sensitive: همیشه تصاویر را به عنوان حساس علامت بزن
@@ -157,7 +163,7 @@ fa:
         setting_display_media_default: پیش‌فرض
         setting_display_media_hide_all: نهفتن همه
         setting_display_media_show_all: نمایش همه
-        setting_expand_spoilers: همیشه بوق‌هایی را که هشدار محتوا دارند کامل نشان بده
+        setting_expand_spoilers: همیشه فرسته‌هایی را که هشدار محتوا دارند کامل نشان بده
         setting_hide_network: نهفتن شبکهٔ ارتباطی
         setting_noindex: درخواست از موتورهای جستجوگر برای ظاهر نشدن در نتایج جستجو
         setting_reduce_motion: کاستن از حرکت در پویانمایی‌ها
@@ -194,20 +200,23 @@ fa:
           sign_up_requires_approval: محدود کردن ثبت نام‌ها
         severity: قانون
       notification_emails:
+        appeal: شخصی به تصمیم مدیر اعتراض کرد
         digest: فرستادن رایانامه‌های خلاصه
         favourite: وقتی کسی نوشتهٔ شما را پسندید ایمیل بفرست
         follow: وقتی کسی پیگیر شما شد ایمیل بفرست
         follow_request: وقتی کسی درخواست پیگیری کرد ایمیل بفرست
         mention: وقتی کسی از شما نام برد ایمیل بفرست
         pending_account: وقتی حساب تازه‌ای نیاز به بازبینی داشت ایمیل بفرست
-        reblog: وقتی کسی نوشتهٔ شما را بازبوقید ایمیل بفرست
+        reblog: وقتی کسی فرستهٔ شما را تقویت کرد ایمیل بفرست
+        report: گزارش جدیدی فرستاده شد
+        trending_tag: روند جدیدی نیازمند بازبینی است
       rule:
         text: قانون
       tag:
         listable: اجازه به این برچسب برای ظاهر شدن در جست‌وجوها و پیشنهادها
         name: برچسب
         trendable: بگذارید که این برچسب در موضوعات پرطرفدار دیده شود
-        usable: بگذارید که این برچسب در بوق‌ها به کار بروند
+        usable: بگذارید که این برچسب در فرسته‌ها به کار بروند
     'no': خیر
     required:
       mark: "*"
diff --git a/config/locales/simple_form.gd.yml b/config/locales/simple_form.gd.yml
index ac71f8307..31b636344 100644
--- a/config/locales/simple_form.gd.yml
+++ b/config/locales/simple_form.gd.yml
@@ -27,6 +27,8 @@ gd:
         scheduled_at: Fàg seo bàn airson am brath-fios fhoillseachadh sa bhad
         starts_at: Roghainnean. Cleachd seo airson am brath-fios a chuingeachadh rè ama shònraichte
         text: "’S urrainn dhut co-chàradh puist a chleachdadh. Thoir an aire air am meud a chaitheas am brath-fios air sgrìn an luchd-chleachdaidh"
+      appeal:
+        text: Chan urrainn dhut ath-thagradh a dhèanamh air rabhadh ach aon turas
       defaults:
         autofollow: Leanaidh na daoine a chlàraicheas leis a cuireadh ort gu fèin-obrachail
         avatar: PNG, GIF or JPG. %{size} air a char as motha. Thèid a sgèileadh sìos gu %{dimensions}px
@@ -35,6 +37,7 @@ gd:
         current_password: A chùm tèarainteachd, cuir a-steach facal-faire a’ chunntais làithrich
         current_username: Airson seo a dhearbhadh, cuir a-steach ainm-cleachdaiche a’ chunntais làithrich
         digest: Cha dèid seo a chur ach nuair a bhios tu air ùine mhòr gun ghnìomh a ghabhail agus ma fhuair thu teachdaireachd phearsanta fhad ’s a bha thu air falbh
+        discoverable: Ceadaich gun lorg coigrich an cunntas agad le taic o mholaidhean, treandaichean is gleusan eile
         email: Thèid post-d dearbhaidh a chur thugad
         fields: Faodaidh tu suas ri 4 nithean a shealltainn mar chlàr air a’ phròifil agad
         header: PNG, GIF or JPG. %{size} air a char as motha. Thèid a sgèileadh sìos gu %{dimensions}px
@@ -60,6 +63,7 @@ gd:
       domain_allow:
         domain: "’S urrainn dhan àrainn seo dàta fhaighinn on fhrithealaiche seo agus thèid an dàta a thig a-steach uaithe a phròiseasadh ’s a stòradh"
       email_domain_block:
+        domain: Seo ainm na h-àrainne a nochdas san t-seòladh puist-d no sa chlàr MX a chleachdas e. Thèid an dearbhadh aig àm a’ chlàraidh.
         with_dns_records: Thèid oidhirp a dhèanamh air fuasgladh clàran DNS na h-àrainne a chaidh a thoirt seachad agus thèid na toraidhean a bhacadh cuideachd
       featured_tag:
         name: 'Mholamaid fear dhe na tagaichean seo:'
@@ -116,6 +120,8 @@ gd:
         scheduled_at: Cuir foillseachadh air an sgeideal
         starts_at: Toiseach an tachartais
         text: Brath-fios
+      appeal:
+        text: Mìnich carson a bu chòir an caochladh a chur orra
       defaults:
         autofollow: Thoir cuireadh dhaibh airson leantainn air a’ chunntas agad
         avatar: Avatar
@@ -194,6 +200,7 @@ gd:
           sign_up_requires_approval: Cuingich clàraidhean ùra
         severity: Riaghailt
       notification_emails:
+        appeal: Tha cuideigin ag ath-thagradh co-dhùnadh na maorsainneachd
         digest: Cuir puist-d le geàrr-chunntas
         favourite: Is annsa le cuideigin am post agad
         follow: Lean cuideigin ort
@@ -201,6 +208,8 @@ gd:
         mention: Thug cuideigin iomradh ort
         pending_account: Tha cunntas ùr feumach air lèirmheas
         reblog: Bhrosnaich cuideigin am post agad
+        report: Chaidh gearan ùr a chur a-null
+        trending_tag: Tha treand ùr ri lèirmheasadh
       rule:
         text: Riaghailt
       tag:
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
index 8eb39dd9e..852c3abeb 100644
--- a/config/locales/simple_form.ja.yml
+++ b/config/locales/simple_form.ja.yml
@@ -27,6 +27,8 @@ ja:
         scheduled_at: お知らせを今すぐ掲載する場合は空欄にしてください
         starts_at: オプションです。お知らせしたい事柄の期間が決まっている場合に使用します
         text: 投稿と同じ構文を使用できます。アナウンスが占める画面のスペースに注意してください
+      appeal:
+        text: 一度だけ異議を申し立てることができます
       defaults:
         autofollow: 招待から登録した人が自動的にあなたをフォローするようになります
         avatar: "%{size}までのPNG、GIF、JPGが利用可能です。%{dimensions}pxまで縮小されます"
@@ -116,6 +118,8 @@ ja:
         scheduled_at: 掲載予約日時
         starts_at: 予定開始日時
         text: お知らせ
+      appeal:
+        text: この決定を覆すべき理由を説明してください
       defaults:
         autofollow: 招待から参加後、あなたをフォロー
         avatar: アイコン
@@ -194,6 +198,7 @@ ja:
           sign_up_requires_approval: 登録を制限
         severity: ルール
       notification_emails:
+        appeal: モデレーターの判断に異議申し立てが行われました
         digest: タイムラインからピックアップしてメールで通知する
         favourite: お気に入り登録された時
         follow: フォローされた時
@@ -201,6 +206,8 @@ ja:
         mention: 返信が来た時
         pending_account: 新しいアカウントの承認が必要な時
         reblog: 投稿がブーストされた時
+        report: 新しいレポートが送信されました
+        trending_tag: 新しいトレンドタグにはレビューが必要です
       rule:
         text: ルール
       tag:
diff --git a/config/locales/simple_form.ku.yml b/config/locales/simple_form.ku.yml
index 4f38ae030..09621771d 100644
--- a/config/locales/simple_form.ku.yml
+++ b/config/locales/simple_form.ku.yml
@@ -3,20 +3,20 @@ ku:
   simple_form:
     hints:
       account_alias:
-        acct: Ajimêrê ku tu dixwazî bar bikî navê bikarhêner@domain diyar bike
+        acct: Ajimêrê ku tu dixwazî jê bar bikî navê bikarhêner@navpar diyar bike
       account_migration:
-        acct: Ajimêrê ku tu dixwazî bar bikî navê bikarhêner@domain diyar bike
+        acct: Ajimêrê ku tu dixwazî bar bikî bo wê navê bikarhêner@navpar diyar bike
       account_warning_preset:
-        text: Tu dikarî wek URLyan, hashtagan û şîroveyan, tootê ristesazî jî bikarbînî
+        text: Tu dikarî hevoksaziya şandiyê wekî URL, hashtag û şîroveyan, bi kar bînî
         title: Bi dilê xwe ye. Ji wergir re nay xûyakirin
       admin_account_action:
-        include_statuses: Bikarhênerê bibîne kîjan toot dibin sedemê çalakî an jî agahdarî
+        include_statuses: Bikarhêner wê bibîne kîjan şandî dibin sedemê çalakî an jî agahdarikirina çavdêriyê
         send_email_notification: Bikarhêner dê ravekirinê tiştê ku bi ajimêra wan re qewimî bistîne
-        text_html: Bi dili xwe ye. Tu dikarî hevoksazî ye toot bikarbînî. Tu dikarî <a href="%{path}"> pêşsazîyên hişyariyê lê zêde bikî </a> ji bo ku demê derbas nekî
+        text_html: Bi dilê xwe ye. Tu dikarî hevoksaziye şandiyê bi kar bînî. Tu dikarî <a href="%{path}"> pêşsaziyên hişyariyê tevlî bikî </a> ji bo ku demê derbas nekî
         type_html: Hilbijêre ka tu yê çi bikî bi <strong>%{acct}</strong> re
         types:
-          disable: Nehêle bila bikarhêner ajimêrê xwe bikar bîne lê naverokan jê nebe an jî veneşêre.
-          none: Ji bo ku tu hişyariyekê ji bikarhêner re bişînî vê bi kar bîne, bêyî ku çalakiyeke din dest lê neda.
+          disable: Nehêle bikarhêner ajimêrê xwe bi kar bîne lê naverokan jê nabe an jî veneşêre.
+          none: Ji bo ku tu hişyariyekê ji bikarhêner re bişînî vê bi kar bîne, bêyî ku çalakiyeke din bikî.
           sensitive: Neçar bihêle ku ev bikarhêner hemû pêvekên medyayê hestiyar nîşan bike.
           silence: Pêşî li bikarhêneran bigire ku bikarhêner bi herkesî ra xûyabarî neşîne, post û agahdarîyên xwe ji mirovên ku wan naşopîne veşêre.
           suspend: Pêşîya hevbandorîya vî ajimêrê bigire û naveroka wê jê bibe. Di nava 30 rojan de tê vegerandin.
@@ -26,7 +26,7 @@ ku:
         ends_at: Bi dilê xwe ye. Daxuyanî di vê demê de bi xweberî ji weşanê de rabe
         scheduled_at: Vala bihêle ku yekcar daxûyanî were weşandin
         starts_at: Bi dilê xwe ye. Heke daxûyanî ya te di demeke diyar ve girêdayî be
-        text: Tu dikarî hevoksazî yên toot bikarbînî. Ji kerema xwe cihê ku ev daxuyanî li ser dîmenderê bikarhêner bigire baldar be
+        text: Tu dikarî hevoksaziya şandiyê bi kar bînî. Ji kerema xwe bila haya te ji cihê ku ev daxuyanî li ser dîmenderê bikarhêner bigire hebe
       appeal:
         text: Tu dikarî tenê carekê îtîraza binpêkirinê bikî
       defaults:
@@ -34,7 +34,7 @@ ku:
         avatar: PNG, GIF an jî JPG. Herî zêde %{size} dê ber bi %{dimensions}px ve were kêmkirin
         bot: Ji yên din re nîşan bike ku ajimêr bi piranî kiryarên xweberî dike û dibe ku neyê çavdêrî kirin
         context: Yek an jî zêdetir girêdan divê parzûn were sepandin
-        current_password: Ji bo ewlehiyê ji kerema xwe şîfreya ajimêrê xwe niha têkevin
+        current_password: Ji bo ewlehiyê ji kerema xwe borînpeyva ajimêrê xwe têxe
         current_username: Ji bo piştrastkirinê, ji kerema xwe navê bikarhêner ya ajimêrê niha binvîse
         digest: Tenê piştî demek dirêj neçalakiyê de û tenê di nebûna te da peyamên teybetî standî be tê şandin
         discoverable: Mafê biden ku ajimêra te bi pêşniyar û taybetmendiyên din ji aliyê biyaniyan ve bê vedîtin
@@ -42,10 +42,10 @@ ku:
         fields: Tu dikarî heya 4 hêmanan wekî tabloyek li ser profîla xwe nîşan bidî
         header: PNG, GIF an jî JPG. Herî zêde %{size} ber bi %{dimensions}px ve were kêmkirin
         inbox_url: URLyê di rûpela pêşî de guhêrkerê ku tu dixwazî bi kar bînî jê bigire
-        irreversible: Tootên parzûnkirî êdî bê veger wenda bibe, heger parzûn paşê were rakirin jî nabe
+        irreversible: Şandiyên parzûnkirî êdî bê veger wenda bibe, heger parzûn paşê were rakirin jî nabe
         locale: Zimanê navrûyê bikarhêner, agahdarîyên e-name û pêl kirin
         locked: Bi destan daxwazên şopê hilbijêrîne da ku kî bikaribe te bişopîne
-        password: Herî kêm 8 karakter bikar bîne
+        password: Herî kêm 8 tîpan bi kar bîne
         phrase: Ji rewşa nivîsê tîpên girdek/hûrdek an jî ji hişyariya naveroka ya şandiyê wek serbixwe wê were hevbeş kirin
         scopes: |-
           Sepana ku dê kîjan maf bide bigihije APIyan.
@@ -56,7 +56,7 @@ ku:
         setting_display_media_hide_all: Medyayê tim veşêre
         setting_display_media_show_all: Medyayê tim nîşan bike
         setting_hide_network: Kesên ku te dişopîne û kesên tu dişopînî ev ên profîla te de were veşartin
-        setting_noindex: Bandor li hemî profîla te û tootên rûpela te dike
+        setting_noindex: Bandor li hemî profîla te û şandiyên rûpela te dike
         setting_show_application: Navê sepana ku tu ji bo şandinê wê bi kar tîne dê di dîtinê berferh ên di şandiyên te de were xuyakirin
         setting_use_blurhash: Gradyen xwe bi rengên dîtbarîyên veşartî ve radigire, lê belê hûrgilîyan diveşêre
         setting_use_pending_items: Li şûna ku herkê wek bixweber bizivirînî nûvekirina demnameyê li paş tikandinekî veşêre
@@ -129,10 +129,10 @@ ku:
         avatar: Wêne
         bot: Ev ajimêrekî bot e
         chosen_languages: Parzûnê zimanan
-        confirm_new_password: Peborîna nû bipejirîne
-        confirm_password: Peborîn bipejirîne
+        confirm_new_password: Borînpeyva nû bipejirîne
+        confirm_password: Borînpeyvê bipejirîne
         context: Parzûnê naverokan
-        current_password: Pêborîna heyî
+        current_password: Borînpeyva heyî
         data: Dane
         discoverable: Ji yên din re ajimêrê pêşniyar bike
         display_name: Navê nîşandanê
@@ -144,12 +144,12 @@ ku:
         inbox_url: URLya guhêzkera wergirtî
         irreversible: Li şûna veşartinê jê bibe
         locale: Zimanê navrûyê
-        locked: Ajimêr qefl bike
+        locked: Ajimêr kilît bike
         max_uses: Hejmara bikaranîna herî zêde
-        new_password: Pêborîna nû
+        new_password: Borînpeyva nû
         note: Jiyanname
         otp_attempt: Koda du faktoran
-        password: Pêborîn
+        password: Borînpeyv
         phrase: Peyvkilîd an jî hevok
         setting_advanced_layout: Navrûya tevnê yê pêşketî çalak bike
         setting_aggregate_reblogs: Di demnameyê de şandiyên bilindkirî kom bike
@@ -169,7 +169,7 @@ ku:
         setting_hide_network: Grafîka xwe ya civakî veşêre
         setting_noindex: Bes e nexe di nav rêzên lêgerîna gerokan
         setting_reduce_motion: Lîstikên livoka kêm bike
-        setting_show_application: Sepana ku ji bo şandina toot'a tê bikaranîn diyar bike
+        setting_show_application: Sepana ku ji bo şandina şandiyan tê bikaranîn diyar bike
         setting_system_font_ui: Curenivîsa berdest a pergalê bi kar bîne
         setting_theme: Rûkarê malperê
         setting_trends: Rojeva îro nîşan bide
diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml
index 56a3f2b76..4ecbe1c9d 100644
--- a/config/locales/simple_form.nl.yml
+++ b/config/locales/simple_form.nl.yml
@@ -7,18 +7,18 @@ nl:
       account_migration:
         acct: Vul de gebruikersnaam@domein van het account in, waarnaartoe je wilt verhuizen
       account_warning_preset:
-        text: Je kunt voor toots specifieke tekst gebruiken, zoals URL's, hashtags en vermeldingen
+        text: Je kunt specifieke tekst voor berichten gebruiken, zoals URL's, hashtags en vermeldingen
         title: Optioneel. Niet zichtbaar voor de ontvanger
       admin_account_action:
-        include_statuses: De gebruiker ziet welke toots verantwoordelijk zijn voor de moderatieactie of waarschuwing
-        send_email_notification: De gebruiker ontvangt een uitleg over wat er met hun account is gebeurd
-        text_html: Optioneel. Je kunt voor toots specifieke tekst gebruiken. Om tijd te besparen kun je <a href="%{path}">presets voor waarschuwingen toevoegen</a>
+        include_statuses: De gebruiker ziet welke berichten verantwoordelijk zijn voor de moderatieactie of waarschuwing
+        send_email_notification: De gebruiker ontvangt een uitleg over wat er met diens account is gebeurd
+        text_html: Optioneel. Je kunt specifieke tekst voor berichten gebruiken. Om tijd te besparen kun je <a href="%{path}">presets voor waarschuwingen toevoegen</a>
         type_html: Kies wat er met <strong>%{acct}</strong> moet gebeuren
         types:
-          disable: Voorkom dat de gebruiker hun account gebruikt, maar verwijder of verberg de inhoud niet.
+          disable: Voorkom dat de gebruiker diens account gebruikt, maar verwijder of verberg de inhoud niet.
           none: Gebruik dit om een waarschuwing naar de gebruiker te sturen, zonder dat nog een andere actie wordt uitgevoerd.
           sensitive: Forceer dat alle mediabijlagen van deze gebruiker als gevoelig worden gemarkeerd.
-          silence: Voorkom dat de gebruiker openbare toots kan versturen, verberg hun toots en meldingen voor mensen die hen niet volgen.
+          silence: Voorkom dat de gebruiker openbare berichten kan versturen, verberg diens berichten en meldingen voor mensen die diegene niet volgen.
           suspend: Alle interacties van en met dit account blokkeren en de inhoud verwijderen. Dit kan binnen dertig dagen worden teruggedraaid.
         warning_preset_id: Optioneel. Je kunt nog steeds handmatig tekst toevoegen aan het eind van de voorinstelling
       announcement:
@@ -26,7 +26,7 @@ nl:
         ends_at: Optioneel. De publicatie van de mededeling wordt op dit tijdstip automatisch beëindigd
         scheduled_at: Laat leeg om de mededeling meteen te publiceren
         starts_at: Optioneel. In het geval dat jouw mededeling aan een bepaald tijdvak is gebonden
-        text: Je kunt voor toots specifieke tekst gebruiken. Let op de ruimte die de mededeling op het scherm van de gebruiker inneemt
+        text: Je kunt specifieke tekst voor berichten gebruiken. Let op de ruimte die de mededeling op het scherm van de gebruiker inneemt
       defaults:
         autofollow: Mensen die zich via de uitnodiging hebben geregistreerd, volgen jou automatisch
         avatar: PNG, GIF of JPG. Maximaal %{size}. Wordt teruggeschaald naar %{dimensions}px
@@ -39,20 +39,20 @@ nl:
         fields: Je kan maximaal 4 items als een tabel op je profiel weergeven
         header: PNG, GIF of JPG. Maximaal %{size}. Wordt teruggeschaald naar %{dimensions}px
         inbox_url: Kopieer de URL van de voorpagina van de relayserver die je wil gebruiken
-        irreversible: Gefilterde toots verdwijnen onomkeerbaar, zelfs als de filter later wordt verwijderd
+        irreversible: Gefilterde berichten verdwijnen onomkeerbaar, zelfs als de filter later wordt verwijderd
         locale: De taal van de gebruikersomgeving, e-mails en pushmeldingen
         locked: Door het goedkeuren van volgers handmatig bepalen wie jou mag volgen
         password: Gebruik tenminste 8 tekens
         phrase: Komt overeen ongeacht hoofd-/kleine letters of een inhoudswaarschuwing
         scopes: Tot welke API's heeft de toepassing toegang. Wanneer je een toestemming van het bovenste niveau kiest, hoef je geen individuele toestemmingen meer te kiezen.
-        setting_aggregate_reblogs: Geen nieuwe boosts tonen voor toots die recentelijk nog zijn geboost (heeft alleen effect op nieuw ontvangen boosts)
+        setting_aggregate_reblogs: Geen nieuwe boosts tonen voor berichten die recentelijk nog zijn geboost (heeft alleen effect op nieuw ontvangen boosts)
         setting_default_sensitive: Gevoelige media wordt standaard verborgen en kan met één klik worden getoond
         setting_display_media_default: Als gevoelig gemarkeerde media verbergen
         setting_display_media_hide_all: Media altijd verbergen
         setting_display_media_show_all: Media altijd tonen
         setting_hide_network: Wie jij volgt en wie jou volgen wordt niet op jouw profiel getoond
-        setting_noindex: Heeft invloed op jouw openbare profiel en toots
-        setting_show_application: De toepassing de je gebruikt om te tooten wordt in de gedetailleerde weergave van de toot getoond
+        setting_noindex: Heeft invloed op jouw openbare profiel en pagina's met berichten
+        setting_show_application: De toepassing de je gebruikt om berichten te plaatsen wordt in de gedetailleerde weergave van het bericht getoond
         setting_use_blurhash: Wazige kleurovergangen zijn gebaseerd op de kleuren van de verborgen media, waarmee elk detail verdwijnt
         setting_use_pending_items: De tijdlijn wordt bijgewerkt door op het aantal nieuwe items te klikken, in plaats van dat deze automatisch wordt bijgewerkt
         username: Jouw gebruikersnaam is uniek op %{domain}
@@ -85,7 +85,7 @@ nl:
       tag:
         name: Je kunt elk woord met een hoofdletter beginnen, om zo bijvoorbeeld de tekst leesbaarder te maken
       user:
-        chosen_languages: Alleen toots in de aangevinkte talen worden op de openbare tijdlijnen getoond
+        chosen_languages: Alleen berichten in de aangevinkte talen worden op de openbare tijdlijnen getoond
     labels:
       account:
         fields:
@@ -99,7 +99,7 @@ nl:
         text: Tekst van preset
         title: Titel
       admin_account_action:
-        include_statuses: Gerapporteerde toots aan de e-mail toevoegen
+        include_statuses: Gerapporteerde berichten aan de e-mail toevoegen
         send_email_notification: Meld dit per e-mail aan de gebruiker
         text: Aangepaste waarschuwing
         type: Actie
@@ -146,22 +146,22 @@ nl:
         setting_advanced_layout: Geavanceerde webomgeving inschakelen
         setting_aggregate_reblogs: Boosts in tijdlijnen groeperen
         setting_auto_play_gif: Speel geanimeerde GIF's automatisch af
-        setting_boost_modal: Vraag voor het boosten van een toot een bevestiging
-        setting_crop_images: Afbeeldingen bijsnijden tot 16x9 in toots op tijdlijnen
-        setting_default_language: Taal van jouw toots
-        setting_default_privacy: Standaardzichtbaarheid van jouw toots
+        setting_boost_modal: Vraag voor het boosten van een bericht een bevestiging
+        setting_crop_images: Afbeeldingen bijsnijden tot 16x9 in berichten op tijdlijnen
+        setting_default_language: Taal van jouw berichten
+        setting_default_privacy: Zichtbaarheid van nieuwe berichten
         setting_default_sensitive: Media altijd als gevoelig markeren
-        setting_delete_modal: Vraag voor het verwijderen van een toot een bevestiging
+        setting_delete_modal: Vraag voor het verwijderen van een bericht een bevestiging
         setting_disable_swiping: Swipebewegingen uitschakelen
         setting_display_media: Mediaweergave
         setting_display_media_default: Standaard
         setting_display_media_hide_all: Alles verbergen
         setting_display_media_show_all: Alles tonen
-        setting_expand_spoilers: Altijd toots met inhoudswaarschuwingen uitklappen
+        setting_expand_spoilers: Altijd berichten met inhoudswaarschuwingen uitklappen
         setting_hide_network: Jouw volgers en wie je volgt verbergen
-        setting_noindex: Jouw toots niet door zoekmachines laten indexeren
+        setting_noindex: Jouw berichten niet door zoekmachines laten indexeren
         setting_reduce_motion: Langzamere animaties
-        setting_show_application: Toepassing onthullen die je voor het verzenden van toots gebruikt
+        setting_show_application: Toepassing onthullen die je voor het verzenden van berichten gebruikt
         setting_system_font_ui: Standaardlettertype van jouw systeem gebruiken
         setting_theme: Thema website
         setting_trends: Trends van vandaag tonen
@@ -195,19 +195,19 @@ nl:
         severity: Regel
       notification_emails:
         digest: Periodiek e-mails met een samenvatting versturen
-        favourite: Wanneer iemand jouw toot aan hun favorieten heeft toegevoegd
+        favourite: Wanneer iemand jouw bericht aan diens favorieten heeft toegevoegd
         follow: Wanneer iemand jou is gaan volgen
         follow_request: Wanneer iemand jou wil volgen
         mention: Wanneer iemand jou heeft vermeld
         pending_account: Wanneer een nieuw account moet worden beoordeeld
-        reblog: Wanneer iemand jouw toot heeft geboost
+        reblog: Wanneer iemand jouw bericht heeft geboost
       rule:
         text: Regel
       tag:
         listable: Toestaan dat deze hashtag in zoekopdrachten en aanbevelingen te zien valt
         name: Hashtag
         trendable: Toestaan dat deze hashtag onder trends te zien valt
-        usable: Toestaan dat deze hashtag in toots gebruikt mag worden
+        usable: Toestaan dat deze hashtag in berichten gebruikt mag worden
     'no': Nee
     required:
       mark: "*"
diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml
index 5d42684c8..0bdc72a5e 100644
--- a/config/locales/simple_form.sk.yml
+++ b/config/locales/simple_form.sk.yml
@@ -153,6 +153,9 @@ sk:
         comment: Okomentuj
       invite_request:
         text: Prečo sa k nám chceš pridať?
+      ip_block:
+        comment: Komentár
+        severity: Pravidlo
       notification_emails:
         digest: Zasielať súhrnné emaily
         favourite: Zaslať email, ak si niekto obľúbi tvoj príspevok
diff --git a/config/locales/simple_form.th.yml b/config/locales/simple_form.th.yml
index 6557ecc4a..9ca4790fd 100644
--- a/config/locales/simple_form.th.yml
+++ b/config/locales/simple_form.th.yml
@@ -20,7 +20,7 @@ th:
           sensitive: บังคับให้ทำเครื่องหมายไฟล์แนบสื่อของผู้ใช้นี้ทั้งหมดว่าละเอียดอ่อน
           silence: ป้องกันไม่ให้ผู้ใช้สามารถโพสต์โดยมีการมองเห็นเป็นสาธารณะ ซ่อนโพสต์และการแจ้งเตือนของเขาจากผู้คนที่ไม่ได้กำลังติดตามผู้ใช้
           suspend: ป้องกันไม่ให้มีการโต้ตอบใด ๆ จากหรือไปยังบัญชีนี้และลบเนื้อหาของบัญชี แปลงกลับได้ภายใน 30 วัน
-        warning_preset_id: ไม่จำเป็น คุณยังสามารถเพิ่มข้อความที่กำหนดเองที่จุดสิ้นสุดของค่าที่ตั้งไว้ล่วงหน้า
+        warning_preset_id: ไม่จำเป็น คุณยังคงสามารถเพิ่มข้อความที่กำหนดเองที่จุดสิ้นสุดของค่าที่ตั้งไว้ล่วงหน้า
       announcement:
         all_day: เมื่อกาเครื่องหมาย จะแสดงเฉพาะวันที่ของช่วงเวลาเท่านั้น
         ends_at: ไม่จำเป็น จะเลิกเผยแพร่ประกาศที่เวลานี้โดยอัตโนมัติ
@@ -74,7 +74,7 @@ th:
         text: นี่จะช่วยให้เราตรวจทานใบสมัครของคุณ
       ip_block:
         comment: ไม่จำเป็น จดจำเหตุผลที่คุณเพิ่มกฎนี้
-        ip: ป้อนที่อยู่ IPv4 หรือ IPv6 คุณสามารถปิดกั้นทั้งช่วงได้โดยใช้ไวยากรณ์ CIDR ระวังอย่าล็อคตัวเองออก!
+        ip: ป้อนที่อยู่ IPv4 หรือ IPv6 คุณสามารถปิดกั้นทั้งช่วงได้โดยใช้ไวยากรณ์ CIDR ระวังอย่าล็อคตัวคุณเองออก!
         severities:
           no_access: ปิดกั้นการเข้าถึงทรัพยากรทั้งหมด
           sign_up_requires_approval: การลงทะเบียนใหม่จะต้องมีการอนุมัติของคุณ
@@ -133,6 +133,7 @@ th:
         expires_in: หมดอายุหลังจาก
         fields: ข้อมูลเมตาโปรไฟล์
         header: ส่วนหัว
+        honeypot: "%{label} (ไม่ต้องกรอก)"
         inbox_url: URL กล่องขาเข้าแบบรีเลย์
         irreversible: ลบแทนที่จะซ่อน
         locale: ภาษาส่วนติดต่อ
@@ -194,6 +195,7 @@ th:
           sign_up_requires_approval: จำกัดการลงทะเบียน
         severity: กฎ
       notification_emails:
+        appeal: ใครสักคนอุทธรณ์การตัดสินใจของผู้ควบคุม
         digest: ส่งอีเมลสรุป
         favourite: ใครสักคนได้ชื่นชอบโพสต์ของคุณ
         follow: ใครสักคนได้ติดตามคุณ
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index 2405c5872..687f4d40d 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -15,6 +15,7 @@ sk:
     contact: Kontakt
     contact_missing: Nezadaný
     contact_unavailable: Neuvedený/á
+    continue_to_web: Pokračovať na webovú aplikáciu
     discover_users: Objavuj užívateľov
     documentation: Dokumentácia
     federation_hint_html: S účtom na %{instance} budeš môcť následovať ľúdí na hociakom Mastodon serveri, ale aj na iných serveroch.
@@ -24,6 +25,7 @@ sk:
       Tento účet je virtuálnym aktérom, ktorý predstavuje samotný server a nie žiadného jedného užívateľa.
       Je využívaný pre potreby federovania a nemal by byť blokovaný, pokiaľ nechceš zablokovať celý server, čo ide lepšie dosiahnúť cez blokovanie domény.
     learn_more: Zisti viac
+    logged_in_as_html: Práve si prihlásený/á ako %{username}.
     logout_before_registering: Už si prihlásený/á.
     privacy_policy: Zásady súkromia
     rules: Serverové pravidlá
diff --git a/config/locales/sq.yml b/config/locales/sq.yml
index a497ef81a..560f4bff7 100644
--- a/config/locales/sq.yml
+++ b/config/locales/sq.yml
@@ -166,9 +166,8 @@ sq:
       perform_full_suspension: Pezulloje
       previous_strikes: Paralajmërime të mëparshme
       previous_strikes_description_html:
-        one: Kjo llogari ka <strong>one</strong> paralajmërim.
+        one: Kjo llogari ka <strong>një</strong> paralajmërim.
         other: Kjo llogari ka <strong>%{count}</strong> paralajmërime.
-        zero: Kjo llogari është <strong>në pozita të mira</strong>.
       promote: Promovojeni
       protocol: Protokoll
       public: Publike
@@ -373,6 +372,7 @@ sq:
       enable: Aktivizoje
       enabled: I aktivizuar
       enabled_msg: Ai emoxhi u aktivizua me sukses
+      image_hint: PNG ose GIF deri në %{size}
       list: Vëre në listë
       listed: Në listë
       new:
@@ -468,6 +468,7 @@ sq:
         title: Zë i ri email në listë bllokimesh
       no_email_domain_block_selected: S’u ndryshuan blloqe përkatësish email, ngaqë s’qe përzgjedhur ndonjë
       resolved_dns_records_hint_html: Emri i përkatësisë jep u përket përkatësive vijuese MX, që janë përgjegjëset për pranim email-esh. Bllokimi i një përkatësie MX do të bllokojë regjistrime nga çfarëdo adrese email që përdor të njëjtën përkatësi MX, edhe nëse emri i dukshëm i përkatësisë është i ndryshëm. <strong>Jini i kujdesshëm të mos bllokoni shërbime të njohur email-esh.</strong>
+      resolved_through_html: Zgjidhur përmes %{domain}
       title: Listë bllokimesh email-esh
     follow_recommendations:
       description_html: "<strong>Rekomandimet për ndjekje ndihmojnë përdoruesit e rinj të gjejnë shpejt lëndë me interes</strong>. Kur një përdorues nuk ka ndërvepruar mjaftueshëm me të tjerët, që të formohen rekomandime të personalizuara ndjekjeje, rekomandohen këto llogari. Ato përzgjidhen çdo ditë, prej një përzierje llogarish me shkallën më të lartë të angazhimit dhe numrin më të lartë të ndjekësve vendorë për një gjuhë të dhënë."
@@ -487,6 +488,7 @@ sq:
           one: Përpjekje e dështuar në %{count} ditë.
           other: Përpjekje e dështuar në %{count} ditë të ndryshme.
         no_failures_recorded: S’ka dështime të regjistruara.
+        warning: Përpjekja e fundit për t’u lidhur me këtë shërbyes ka qenë e pasuksesshme
       back_to_all: Krejt
       back_to_limited: E kufizuar
       back_to_warning: Kujdes
@@ -526,7 +528,6 @@ sq:
       known_accounts:
         one: "%{count} llogari e njohur"
         other: "%{count} llogari të njohura"
-        zero: Pa llogari të njohur
       moderation:
         all: Krejt
         limited: Të kufizuarat
@@ -771,6 +772,11 @@ sq:
     system_checks:
       database_schema_check:
         message_html: Ka migrime bazash të dhënash pezull. Ju lutemi, kryejini, për të qenë të sigurt se aplikacioni sillet siç priteet
+      elasticsearch_running_check:
+        message_html: S’u lidh dot me Elasticsearch. Ju lutemi, kontrolloni nëse ky xhiron, ose çaktivizoni kërkimin në tërë tekstin
+      elasticsearch_version_check:
+        message_html: 'Version Elasticsearch i papërputhshëm: %{value}'
+        version_comparison: Xhiron Elasticsearch %{running_version}, ndërkohë që është i domosdoshëm %{required_version}
       rules_check:
         action: Administroni rregulla shërbyesi
         message_html: S’keni përcaktuar ndonjë rregull shërbyesi.
@@ -791,9 +797,8 @@ sq:
         disallow: Hiq lejimin e lidhjes
         disallow_provider: Mos e lejo botuesin
         shared_by_over_week:
-          one: Ndarë nga një person javën e kaluar
-          other: Ndarë nga %{count} persona javën e kaluar
-          zero: E pandarë nga njeri gjatë javës së kaluar
+          one: Ndarë me të tjerë nga një person gjatë javës së kaluar
+          other: Ndarë me të tjerë nga %{count} vetë gjatë javës së kaluar
         title: Lidhje në modë
         usage_comparison: Ndarë %{today} herë sot, kundrejt %{yesterday} dje
       pending_review: Në pritje të shqyrtimit
@@ -832,9 +837,8 @@ sq:
         usable: Mund të përdoret
         usage_comparison: Përdorur %{today} herë sot, krahasuar me %{yesterday} dje
         used_by_over_week:
-          one: Përdorur nga një persona gjatë javës së kaluar
-          other: Përdorur nga %{count} persona gjatë javës së kaluar
-          zero: E papërdorur nga njeri gjatë javës së kaluar
+          one: Përdorur nga një person gjatë javës së kaluar
+          other: Përdorur nga %{count} vetë gjatë javës së kaluar
       title: Në modë
     warning_presets:
       add_new: Shtoni të ri
@@ -1408,6 +1412,7 @@ sq:
     profile: Profil
     relationships: Ndjekje dhe ndjekës
     statuses_cleanup: Fshirje e automatizuar postimesh
+    strikes: Paralajmërime nga moderimi
     two_factor_authentication: Mirëfilltësim Dyfaktorësh
     webauthn_authentication: Kyçe sigurie
   statuses:
@@ -1428,6 +1433,7 @@ sq:
     disallowed_hashtags:
       one: 'përmbante një hashtag të palejuar: %{tags}'
       other: 'përmbante hashtag-ë të palejuar: %{tags}'
+    edited_at_html: Përpunuar më %{date}
     errors:
       in_reply_not_found: Gjendja të cilës po provoni t’i përgjigjeni s’duket se ekziston.
     open_in_web: Hape në internet
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index 40337ce69..c8424e5f5 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -160,10 +160,6 @@ sv:
       pending: Inväntar granskning
       perform_full_suspension: Utför full avstängning
       previous_strikes: Tidigare varningar
-      previous_strikes_description_html:
-        one: Detta konto har <strong>en</strong> varning.
-        other: Detta konto har <strong>%{count}</strong> varningar.
-        zero: Detta konto är <strong>i gott skick</strong>.
       promote: Befordra
       protocol: Protokoll
       public: Offentlig
@@ -404,6 +400,8 @@ sv:
       status: Status
       title: Följ rekommendationer
     instances:
+      availability:
+        warning: Det senaste försöket att ansluta till denna värddator har misslyckats
       back_to_all: Alla
       back_to_limited: Begränsat
       back_to_warning: Varning
diff --git a/config/locales/th.yml b/config/locales/th.yml
index c4294be7f..ce40b9517 100644
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -52,6 +52,7 @@ th:
     what_is_mastodon: Mastodon คืออะไร?
   accounts:
     choices_html: 'ตัวเลือกของ %{name}:'
+    endorsements_hint: คุณสามารถแนะนำผู้คนที่คุณติดตามจากส่วนติดต่อเว็บ และเขาจะปรากฏที่นี่
     featured_tags_hint: คุณสามารถแนะนำแฮชแท็กที่เฉพาะเจาะจงที่จะแสดงที่นี่
     follow: ติดตาม
     followers:
@@ -66,6 +67,8 @@ th:
     nothing_here: ไม่มีสิ่งใดที่นี่!
     people_followed_by: ผู้คนที่ %{name} ติดตาม
     people_who_follow: ผู้คนที่ติดตาม %{name}
+    pin_errors:
+      following: คุณต้องกำลังติดตามคนที่คุณต้องการแนะนำอยู่แล้ว
     posts:
       other: โพสต์
     posts_tab_heading: โพสต์
@@ -153,10 +156,6 @@ th:
       pending: การตรวจทานที่รอดำเนินการ
       perform_full_suspension: ระงับ
       previous_strikes: การดำเนินการก่อนหน้านี้
-      previous_strikes_description_html:
-        one: บัญชีนี้มี <strong>หนึ่ง</strong> การดำเนินการ
-        other: บัญชีนี้มี <strong>%{count}</strong> การดำเนินการ
-        zero: บัญชีนี้ <strong>อยู่ในสถานะที่ดี</strong>
       promote: เลื่อนขั้น
       protocol: โปรโตคอล
       public: สาธารณะ
@@ -269,6 +268,7 @@ th:
         update_domain_block: อัปเดตการปิดกั้นโดเมน
         update_status: อัปเดตโพสต์
       actions:
+        approve_appeal_html: "%{name} ได้อนุมัติการอุทธรณ์การตัดสินใจในการควบคุมจาก %{target}"
         approve_user_html: "%{name} ได้อนุมัติการลงทะเบียนจาก %{target}"
         assigned_to_self_report_html: "%{name} ได้มอบหมายรายงาน %{target} ให้กับตนเอง"
         change_email_user_html: "%{name} ได้เปลี่ยนที่อยู่อีเมลของผู้ใช้ %{target}"
@@ -299,6 +299,7 @@ th:
         enable_user_html: "%{name} ได้เปิดใช้งานการเข้าสู่ระบบสำหรับผู้ใช้ %{target}"
         memorialize_account_html: "%{name} ได้เปลี่ยนบัญชีของ %{target} เป็นหน้าอนุสรณ์"
         promote_user_html: "%{name} ได้เลื่อนขั้นผู้ใช้ %{target}"
+        reject_appeal_html: "%{name} ได้ปฏิเสธการอุทธรณ์การตัดสินใจในการควบคุมจาก %{target}"
         reject_user_html: "%{name} ได้ปฏิเสธการลงทะเบียนจาก %{target}"
         remove_avatar_user_html: "%{name} ได้เอาภาพประจำตัวของ %{target} ออก"
         reopen_report_html: "%{name} ได้เปิดรายงาน %{target} ใหม่"
@@ -485,10 +486,6 @@ th:
       delivery_available: มีการจัดส่ง
       delivery_error_days: วันที่มีข้อผิดพลาดการจัดส่ง
       empty: ไม่พบโดเมน
-      known_accounts:
-        one: "%{count} บัญชีที่รู้จัก"
-        other: "%{count} บัญชีที่รู้จัก"
-        zero: ไม่มีบัญชีที่รู้จัก
       moderation:
         all: ทั้งหมด
         limited: จำกัดอยู่
@@ -551,6 +548,9 @@ th:
           other: "%{count} หมายเหตุ"
       action_log: รายการบันทึกการตรวจสอบ
       action_taken_by: ใช้การกระทำโดย
+      actions:
+        resolve_description_html: จะไม่ใช้การกระทำกับบัญชีที่รายงาน ไม่มีการบันทึกการดำเนินการ และจะปิดรายงาน
+      actions_description_html: ตัดสินใจว่าการกระทำใดที่จะใช้เพื่อแก้ปัญหารายงานนี้ หากคุณใช้การกระทำที่เป็นการลงโทษกับบัญชีที่รายงาน จะส่งการแจ้งเตือนอีเมลถึงเขา ยกเว้นเมื่อมีการเลือกหมวดหมู่ <strong>สแปม</strong>
       are_you_sure: คุณแน่ใจหรือไม่?
       assign_to_self: มอบหมายให้ฉัน
       assigned: ผู้ควบคุมที่ได้รับมอบหมาย
@@ -709,6 +709,8 @@ th:
         suspend: "%{name} ได้ระงับบัญชีของ %{target}"
       appeal_approved: อุทธรณ์แล้ว
     system_checks:
+      elasticsearch_version_check:
+        message_html: 'รุ่น Elasticsearch ที่เข้ากันไม่ได้: %{value}'
       rules_check:
         action: จัดการกฎของเซิร์ฟเวอร์
         message_html: คุณไม่ได้กำหนดกฎของเซิร์ฟเวอร์ใด ๆ
@@ -727,10 +729,6 @@ th:
         allow_provider: อนุญาตผู้เผยแพร่
         disallow: ไม่อนุญาตลิงก์
         disallow_provider: ไม่อนุญาตผู้เผยแพร่
-        shared_by_over_week:
-          one: แบ่งปันโดยหนึ่งคนในช่วงสัปดาห์ที่ผ่านมา
-          other: แบ่งปันโดย %{count} คนในช่วงสัปดาห์ที่ผ่านมา
-          zero: ไม่มีใครแบ่งปันในช่วงสัปดาห์ที่ผ่านมา
         title: ลิงก์ที่กำลังนิยม
         usage_comparison: แบ่งปัน %{today} ครั้งวันนี้ เทียบกับ %{yesterday} เมื่อวานนี้
       pending_review: การตรวจทานที่รอดำเนินการ
@@ -765,10 +763,6 @@ th:
         trending_rank: 'กำลังนิยม #%{rank}'
         usable: สามารถใช้
         usage_comparison: ใช้ %{today} ครั้งวันนี้ เทียบกับ %{yesterday} เมื่อวานนี้
-        used_by_over_week:
-          one: ใช้โดยหนึ่งคนในช่วงสัปดาห์ที่ผ่านมา
-          other: ใช้โดย %{count} คนในช่วงสัปดาห์ที่ผ่านมา
-          zero: ไม่มีใครใช้ในช่วงสัปดาห์ที่ผ่านมา
       title: แนวโน้ม
     warning_presets:
       add_new: เพิ่มใหม่
@@ -786,6 +780,9 @@ th:
         sensitive: เพื่อทำเครื่องหมายบัญชีของเขาว่าละเอียดอ่อน
         silence: เพื่อจำกัดบัญชีของเขา
         suspend: เพื่อระงับบัญชีของเขา
+      body: "%{target} กำลังอุทธรณ์การตัดสินใจในการควบคุมโดย %{action_taken_by} จาก %{date} ซึ่งเป็น %{type} เขาเขียนว่า:"
+      next_steps: คุณสามารถอนุมัติการอุทธรณ์เพื่อเลิกทำการตัดสินใจในการควบคุม หรือเพิกเฉยการอุทธรณ์
+      subject: "%{username} กำลังอุทธรณ์การตัดสินใจในการควบคุมใน %{instance}"
     new_pending_account:
       body: รายละเอียดของบัญชีใหม่อยู่ด้านล่าง คุณสามารถอนุมัติหรือปฏิเสธใบสมัครนี้
     new_report:
@@ -875,6 +872,7 @@ th:
       confirming: กำลังรอการยืนยันอีเมลให้เสร็จสมบูรณ์
       functional: บัญชีของคุณทำงานได้อย่างเต็มที่
       pending: ใบสมัครของคุณกำลังรอดำเนินการตรวจทานโดยพนักงานของเรา นี่อาจใช้เวลาสักครู่ คุณจะได้รับอีเมลหากใบสมัครของคุณได้รับการอนุมัติ
+      view_strikes: ดูการดำเนินการที่ผ่านมากับบัญชีของคุณ
     too_fast: ส่งแบบฟอร์มเร็วเกินไป ลองอีกครั้ง
     trouble_logging_in: มีปัญหาในการเข้าสู่ระบบ?
     use_security_key: ใช้กุญแจความปลอดภัย
@@ -946,6 +944,7 @@ th:
         submit: ส่งการอุทธรณ์
       associated_report: รายงานที่เกี่ยวข้อง
       created_at: ลงวันที่
+      description_html: นี่คือการกระทำที่ใช้กับบัญชีของคุณและคำเตือนที่ส่งถึงคุณโดยพนักงานของ %{instance}
       recipient: ส่งถึง
       status: 'โพสต์ #%{id}'
       title: "%{action} จาก %{date}"
@@ -1402,9 +1401,11 @@ th:
   user_mailer:
     appeal_approved:
       action: ไปยังบัญชีของคุณ
+      explanation: อนุมัติการอุทธรณ์การดำเนินการกับบัญชีของคุณเมื่อ %{strike_date} ที่คุณได้ส่งเมื่อ %{appeal_date} แล้ว บัญชีของคุณอยู่ในสถานะที่ดีอีกครั้งหนึ่ง
       subject: อนุมัติการอุทธรณ์ของคุณจาก %{date} แล้ว
       title: อนุมัติการอุทธรณ์แล้ว
     appeal_rejected:
+      explanation: ปฏิเสธการอุทธรณ์การดำเนินการกับบัญชีของคุณเมื่อ %{strike_date} ที่คุณได้ส่งเมื่อ %{appeal_date} แล้ว
       subject: ปฏิเสธการอุทธรณ์ของคุณจาก %{date} แล้ว
       title: ปฏิเสธการอุทธรณ์แล้ว
     backup_ready:
@@ -1423,6 +1424,13 @@ th:
       categories:
         spam: สแปม
         violation: เนื้อหาละเมิดหลักเกณฑ์ชุมชนดังต่อไปนี้
+      explanation:
+        delete_statuses: มีการพบว่าโพสต์บางส่วนของคุณละเมิดหนึ่งหลักเกณฑ์ชุมชนหรือมากกว่าและได้รับการเอาออกโดยผู้ควบคุมของ %{instance} ในเวลาต่อมา
+        disable: คุณไม่สามารถใช้บัญชีของคุณได้อีกต่อไป แต่โปรไฟล์และข้อมูลอื่น ๆ ของคุณยังคงอยู่ในสภาพเดิม คุณสามารถขอข้อมูลสำรองของข้อมูลของคุณ เปลี่ยนการตั้งค่าบัญชี หรือลบบัญชีของคุณ
+        mark_statuses_as_sensitive: ทำเครื่องหมายโพสต์บางส่วนของคุณว่าละเอียดอ่อนโดยผู้ควบคุมของ %{instance} แล้ว นี่หมายความว่าผู้คนจะต้องแตะสื่อในโพสต์ก่อนที่จะแสดงตัวอย่าง คุณสามารถทำเครื่องหมายสื่อว่าละเอียดอ่อนด้วยตัวคุณเองเมื่อโพสต์ในอนาคต
+        sensitive: จากนี้ไป จะทำเครื่องหมายไฟล์สื่อที่อัปโหลดทั้งหมดของคุณว่าละเอียดอ่อนและซ่อนอยู่หลังการคลิกไปยังคำเตือน
+        silence: คุณยังคงสามารถใช้บัญชีของคุณแต่เฉพาะผู้คนที่กำลังติดตามคุณอยู่แล้วเท่านั้นที่จะเห็นโพสต์ของคุณในเซิร์ฟเวอร์นี้ และอาจแยกคุณออกจากคุณลักษณะการค้นพบต่าง ๆ อย่างไรก็ตาม ผู้อื่นอาจยังติดตามคุณด้วยตนเอง
+        suspend: คุณไม่สามารถใช้บัญชีของคุณได้อีกต่อไป และจะไม่สามารถเข้าถึงโปรไฟล์และข้อมูลอื่น ๆ ของคุณได้อีกต่อไป คุณยังคงสามารถเข้าสู่ระบบเพื่อขอข้อมูลสำรองของข้อมูลของคุณจนกว่าจะเอาข้อมูลออกอย่างสมบูรณ์ในเวลาประมาณ 30 วัน แต่เราจะเก็บรักษาข้อมูลพื้นฐานบางอย่างไว้เพื่อป้องกันไม่ให้คุณหลบเลี่ยงการระงับ
       reason: 'เหตุผล:'
       statuses: 'โพสต์ที่อ้างถึง:'
       subject:
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index 6e6477b92..2733f5eba 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -165,10 +165,6 @@ tr:
       pending: Bekleyen yorum
       perform_full_suspension: Askıya al
       previous_strikes: Önceki eylemler
-      previous_strikes_description_html:
-        one: Bu hesap için <strong>bir</strong> eylem yapılmış.
-        other: Bu hesap için <strong>%{count}</strong> eylem yapılmış.
-        zero: Bu hesap <strong>hali iyi durumda</strong>.
       promote: Yükselt
       protocol: Protokol
       public: Herkese açık
@@ -490,6 +486,7 @@ tr:
           other: "%{count} farklı gün başarısız girişim."
         no_failures_recorded: Kayıtlı başarısızlık yok.
         title: Ulaşılabilirlik
+        warning: Bu sunucuya önceki bağlanma denemesi başarısız olmuştu
       back_to_all: Tümü
       back_to_limited: Sınırlı
       back_to_warning: Uyarı
@@ -526,10 +523,6 @@ tr:
       delivery_error_hint: Eğer teslimat %{count} gün boyunca mümkün olmazsa, otomatik olarak teslim edilemiyor olarak işaretlenecek.
       destroyed_msg: "%{domain} alan adından veriler hemen silinmek üzere kuyruğa alındı."
       empty: Alan adı bulunamadı.
-      known_accounts:
-        one: "%{count} bilinen hesap"
-        other: "%{count} bilinen hesap"
-        zero: Bilinen hesap yok
       moderation:
         all: Tümü
         limited: Sınırlı
@@ -793,10 +786,6 @@ tr:
         description_html: Bu bağlantılar şu anda sunucunuzun gönderilerini gördüğü hesaplarca bolca paylaşılıyor. Kullanıcılarınızın dünyada neler olduğunu görmesine yardımcı olabilir. Yayıncıyı onaylamadığınız sürece hiçbir bağlantı herkese açık yayınlanmaz. Tekil bağlantıları onaylayabilir veya reddedebilirsiniz.
         disallow: Bağlantıya izin verme
         disallow_provider: Yayıncıya izin verme
-        shared_by_over_week:
-          one: Geçen hafta bir kişi paylaştı
-          other: Geçen hafta %{count} kişi paylaştı
-          zero: Geçen hafta kimse paylaşmadı
         title: Öne çıkan bağlantılar
         usage_comparison: Bugün %{today} kere paylaşıldı, dün %{yesterday} kere paylaşılmıştı
       pending_review: İnceleme bekliyor
@@ -836,10 +825,6 @@ tr:
         trending_rank: 'Öne çıkanlar #%{rank}'
         usable: Kullanılabilir
         usage_comparison: Bugün %{today} kere kullanıldı, dün %{yesterday} kere kullanılmıştı
-        used_by_over_week:
-          one: Geçen hafta bir kişi tarafından kullanıldı
-          other: Geçen hafta %{count} kişi tarafından kullanıldı
-          zero: Geçen hafta kimse tarafından kullanılmadı
       title: Öne çıkanlar
     warning_presets:
       add_new: Yeni ekle
diff --git a/config/locales/uk.yml b/config/locales/uk.yml
index a4d29d3e4..a42f048d6 100644
--- a/config/locales/uk.yml
+++ b/config/locales/uk.yml
@@ -440,10 +440,15 @@ uk:
       add_new: Додати
       created_msg: Успішно додано поштовий домен до чорного списку
       delete: Видалити
+      dns:
+        types:
+          mx: MX-запис
       domain: Домен
       new:
         create: Додати домен
+        resolve: Розв'язати домен
         title: Нове блокування поштового домену
+      resolved_through_html: Розв'язано через %{domain}
       title: Чорний список поштових доменів
     follow_recommendations:
       description_html: "<strong>Слідувати рекомендаціям та допомогти новим користувачам швидко знайти цікавий вміст</strong>. Коли користувачі не взаємодіяли з іншими людьми достатньо, щоб сформувати персоналізовані рекомендації, радимо замість цього вказувати ці облікові записи. Вони щоденно переобчислюються з масиву облікових записів з найбільшою кількістю недавніх взаємодій і найбільшою кількістю місцевих підписників розраховується для цієї мови."
@@ -459,6 +464,17 @@ uk:
       back_to_warning: Попередження
       by_domain: Домен
       confirm_purge: Ви впевнені, що хочете видалити ці дані з цього домену?
+      content_policies:
+        policies:
+          reject_media: Відхилити медіа
+          reject_reports: Відхилити скарги
+          silence: Обмеження
+          suspend: Призупинити
+        policy: Правила
+        reason: Суспільна причина
+        title: Політика вмісту
+      dashboard:
+        instance_languages_dimension: Найуживаніші мови
       delivery:
         all: Усі
         clear: Очистити помилки доставляння
@@ -470,10 +486,6 @@ uk:
       delivery_error_hint: Якщо доставляння неможливе впродовж %{count} днів, воно автоматично позначиться недоставленим.
       destroyed_msg: Дані з %{domain} тепер у черзі на видалення.
       empty: Доменів не знайдено.
-      known_accounts:
-        one: "%{count} відомий обліковий запис"
-        other: "%{count} відомих облікових записів"
-        zero: Немає відомих облікових записів
       moderation:
         all: Усі
         limited: Обмежені
@@ -560,6 +572,7 @@ uk:
       forwarded: Переслано
       forwarded_to: Переслано до %{domain}
       mark_as_resolved: Відмітити як вирішену
+      mark_as_sensitive: Позначити делікатним
       mark_as_unresolved: Відмітити як невирішену
       no_one_assigned: Ніхто
       notes:
@@ -731,6 +744,11 @@ uk:
         rejected: Посилання цього публікатора можуть не будуть популярними
         title: Публікатори
       rejected: Відхилено
+      statuses:
+        allow: Дозволити оприлюднення
+        allow_account: Дозволити автора
+        disallow: Заборонити допис
+        disallow_account: Заборонити автора
       tags:
         current_score: Поточний результат %{score}
         dashboard:
@@ -847,6 +865,7 @@ uk:
     status:
       account_status: Статус облікового запису
       confirming: Очікуємо на завершення підтвердження за допомогою електронної пошти.
+      functional: Ваш обліковий запис повністю робочий.
       pending: Ваша заява очікує на розгляд нашим персоналом. Це може зайняти деякий час. Ви отримаєте електронний лист, якщо ваша заява буде схвалена.
       redirecting_to: Ваш обліковий запис наразі неактивний, тому що він перенаправлений до %{acct}.
     too_fast: Форму подано занадто швидко, спробуйте ще раз.
@@ -923,13 +942,16 @@ uk:
         submit: Подати апеляцію
       associated_report: Пов'язана скарга
       created_at: Застарілі
+      recipient: Адресант
       status: 'Допис #%{id}'
       status_removed: Допис уже вилучено з системи
       title: "%{action} від %{date}"
       title_actions:
         delete_statuses: Вилучення допису
         disable: Заморожування облікового запису
+        mark_statuses_as_sensitive: Позначати дописи делікатними
         none: Попередження
+        sensitive: Позначення облікового запису делікатним
         silence: Обмеження облікового запису
         suspend: Призупинення облікового запису
       your_appeal_approved: Вашу апеляцію було схвалено
@@ -1113,6 +1135,9 @@ uk:
     carry_mutes_over_text: Цей користувач переїхав з %{acct}, який ви заглушили.
     copy_account_note_text: 'Цей користувач був переміщений з %{acct}, ось ваші попередні нотатки:'
   notification_mailer:
+    admin:
+      sign_up:
+        subject: "%{name} приєднується"
     digest:
       action: Показати усі сповіщення
       body: Коротко про пропущене вами з Вашого останнього входу %{since}
@@ -1334,6 +1359,7 @@ uk:
       many: 'заборонених хештеґів: %{tags}'
       one: 'заборонений хештеґ: %{tags}'
       other: 'заборонених хештеґів: %{tags}'
+    edited_at_html: Відредаговано %{date}
     errors:
       in_reply_not_found: Статуса, на який ви намагаєтеся відповісти, не існує.
     open_in_web: Відкрити у вебі
@@ -1396,7 +1422,7 @@ uk:
       '2629746': 1 місяць
       '31556952': 1 рік
       '5259492': 2 місяці
-      '604800': 1 week
+      '604800': 1 тиждень
       '63113904': 2 роки
       '7889238': 3 місяці
     min_age_label: Поріг давності
diff --git a/config/locales/vi.yml b/config/locales/vi.yml
index 459b950a6..14e6c1c3b 100644
--- a/config/locales/vi.yml
+++ b/config/locales/vi.yml
@@ -51,7 +51,7 @@ vi:
     user_count_after:
       other: người dùng
     user_count_before: Nhà của
-    what_is_mastodon: Tham gia Mastodon
+    what_is_mastodon: Mastodon
   accounts:
     choices_html: "%{name} tôn vinh:"
     endorsements_hint: Bạn có thể tôn vinh những người bạn theo dõi và họ sẽ hiển thị ở giao diện web.
@@ -160,9 +160,7 @@ vi:
       perform_full_suspension: Vô hiệu hóa
       previous_strikes: Lịch sử kiểm duyệt
       previous_strikes_description_html:
-        one: Người dùng này có <strong>một</strong> lần cảnh cáo.
-        other: Người dùng này có <strong>%{count}</strong> lần cảnh cáo.
-        zero: Người dùng này <strong>chưa từng bị cảnh cáo</strong>.
+        other: Người này bị cảnh cáo <strong>%{count}</strong> lần.
       promote: Chỉ định vai trò
       protocol: Giao thức
       public: Công khai
@@ -186,7 +184,7 @@ vi:
       roles:
         admin: Quản trị viên
         moderator: Kiểm duyệt viên
-        staff: Nhân viên
+        staff: Đội ngũ
         user: Người dùng
       search: Tìm kiếm
       search_same_email_domain: Tra cứu email
@@ -227,25 +225,25 @@ vi:
       whitelisted: Danh sách trắng
     action_logs:
       action_types:
-        approve_appeal: Đồng ý kháng cáo
+        approve_appeal: Phê duyệt kháng cáo
         approve_user: Phê duyệt người dùng
         assigned_to_self_report: Tự xử lý báo cáo
-        change_email_user: Đổi email
-        confirm_user: Xác thực
-        create_account_warning: Cảnh cáo
+        change_email_user: Đổi email người dùng
+        confirm_user: Xác thực người dùng
+        create_account_warning: Cảnh cáo người dùng
         create_announcement: Tạo thông báo mới
-        create_custom_emoji: Tạo emoji mới
-        create_domain_allow: Tạo cho phép máy chủ mới
-        create_domain_block: Tạo chặn máy chủ mới
-        create_email_domain_block: Tạo chặn tên miền email mới
+        create_custom_emoji: Tạo emoji
+        create_domain_allow: Cho phép máy chủ
+        create_domain_block: Chặn máy chủ
+        create_email_domain_block: Chặn tên miền email
         create_ip_block: Tạo chặn IP mới
-        create_unavailable_domain: Tạo máy chủ không khả dụng
+        create_unavailable_domain: Máy chủ không khả dụng
         demote_user: Xóa vai trò
         destroy_announcement: Xóa thông báo
         destroy_custom_emoji: Xóa emoji
-        destroy_domain_allow: Xóa máy chủ cho phép
-        destroy_domain_block: Xóa máy chủ đã chặn
-        destroy_email_domain_block: Xóa tên miền email đã chặn
+        destroy_domain_allow: Bỏ cho phép máy chủ
+        destroy_domain_block: Bỏ chặn máy chủ
+        destroy_email_domain_block: Bỏ chặn tên miền email
         destroy_instance: Thanh trừng máy chủ
         destroy_ip_block: Xóa IP đã chặn
         destroy_status: Xóa tút
@@ -253,10 +251,10 @@ vi:
         disable_2fa_user: Vô hiệu hóa 2FA
         disable_custom_emoji: Vô hiệu hóa emoji
         disable_sign_in_token_auth_user: Tắt xác thực bằng email cho người dùng
-        disable_user: Khóa người dùng
-        enable_custom_emoji: Kích hoạt Emoji
+        disable_user: Vô hiệu hóa đăng nhập
+        enable_custom_emoji: Cho phép Emoji
         enable_sign_in_token_auth_user: Bật xác thực bằng email cho người dùng
-        enable_user: Kích hoạt lại người dùng
+        enable_user: Bỏ vô hiệu hóa đăng nhập
         memorialize_account: Đánh dấu tưởng niệm
         promote_user: Chỉ định vai trò
         reject_appeal: Từ chối kháng cáo
@@ -265,24 +263,24 @@ vi:
         reopen_report: Mở lại báo cáo
         reset_password_user: Đặt lại mật khẩu
         resolve_report: Xử lý báo cáo
-        sensitive_account: Đánh dấu nhạy cảm
-        silence_account: Đánh dấu hạn chế
-        suspend_account: Đánh dấu vô hiệu hóa
+        sensitive_account: Áp đặt nhạy cảm
+        silence_account: Áp đặt hạn chế
+        suspend_account: Áp đặt vô hiệu hóa
         unassigned_report: Báo cáo chưa xử lý
         unblock_email_account: Mở khóa địa chỉ email
         unsensitive_account: Bỏ nhạy cảm
         unsilence_account: Bỏ hạn chế
         unsuspend_account: Bỏ vô hiệu hóa
         update_announcement: Cập nhật thông báo
-        update_custom_emoji: Cập nhật Emoji mới
+        update_custom_emoji: Cập nhật Emoji
         update_domain_block: Cập nhật máy chủ chặn
         update_status: Cập nhật tút
       actions:
         approve_appeal_html: "%{name} đã phê duyệt quyết định kiểm duyệt từ %{target}"
         approve_user_html: "%{name} đã chấp nhận đăng ký từ %{target}"
         assigned_to_self_report_html: "%{name} tự xử lý báo cáo %{target}"
-        change_email_user_html: "%{name} đã thay đổi địa chỉ email cho %{target}"
-        confirm_user_html: "%{name} xác nhận địa chỉ email của người dùng %{target}"
+        change_email_user_html: "%{name} đã thay đổi địa chỉ email của %{target}"
+        confirm_user_html: "%{name} đã xác thực địa chỉ email của %{target}"
         create_account_warning_html: "%{name} đã gửi cảnh cáo %{target}"
         create_announcement_html: "%{name} tạo thông báo mới %{target}"
         create_custom_emoji_html: "%{name} đã tải lên biểu tượng cảm xúc mới %{target}"
@@ -295,7 +293,7 @@ vi:
         destroy_announcement_html: "%{name} xóa thông báo %{target}"
         destroy_custom_emoji_html: "%{name} đã xóa emoji %{target}"
         destroy_domain_allow_html: "%{name} đã ngừng liên hợp với %{target}"
-        destroy_domain_block_html: "%{name} bỏ chặn tên miền email %{target}"
+        destroy_domain_block_html: "%{name} bỏ chặn máy chủ %{target}"
         destroy_email_domain_block_html: "%{name} bỏ chặn tên miền email %{target}"
         destroy_instance_html: "%{name} thanh trừng máy chủ %{target}"
         destroy_ip_block_html: "%{name} bỏ chặn IP %{target}"
@@ -307,14 +305,14 @@ vi:
         disable_user_html: "%{name} vô hiệu hóa đăng nhập %{target}"
         enable_custom_emoji_html: "%{name} cho phép Emoji %{target}"
         enable_sign_in_token_auth_user_html: "%{name} bật xác thực email của %{target}"
-        enable_user_html: "%{name} mở khóa cho người dùng %{target}"
+        enable_user_html: "%{name} bỏ vô hiệu hóa đăng nhập %{target}"
         memorialize_account_html: "%{name} đã biến tài khoản %{target} thành một trang tưởng niệm"
         promote_user_html: "%{name} chỉ định vai trò cho %{target}"
-        reject_appeal_html: "%{name} đã phản đối quyết định kiểm duyệt từ %{target}"
+        reject_appeal_html: "%{name} đã từ chối kháng cáo của %{target}"
         reject_user_html: "%{name} đã từ chối đăng ký từ %{target}"
         remove_avatar_user_html: "%{name} đã xóa ảnh đại diện của %{target}"
         reopen_report_html: "%{name} mở lại báo cáo %{target}"
-        reset_password_user_html: "%{name} đặt lại mật khẩu của người dùng %{target}"
+        reset_password_user_html: "%{name} đã đặt lại mật khẩu của %{target}"
         resolve_report_html: "%{name} đã xử lý báo cáo %{target}"
         sensitive_account_html: "%{name} đánh dấu nội dung của %{target} là nhạy cảm"
         silence_account_html: "%{name} đã ẩn %{target}"
@@ -323,15 +321,15 @@ vi:
         unblock_email_account_html: "%{name} mở khóa địa chỉ email của %{target}"
         unsensitive_account_html: "%{name} đánh dấu nội dung của %{target} là bình thường"
         unsilence_account_html: "%{name} đã bỏ ẩn %{target}"
-        unsuspend_account_html: "%{name} đã ngừng vô hiệu hóa %{target}"
+        unsuspend_account_html: "%{name} đã bỏ vô hiệu hóa %{target}"
         update_announcement_html: "%{name} cập nhật thông báo %{target}"
         update_custom_emoji_html: "%{name} đã cập nhật emoji %{target}"
         update_domain_block_html: "%{name} cập nhật chặn máy chủ %{target}"
         update_status_html: "%{name} cập nhật tút của %{target}"
       deleted_status: "(tút đã xóa)"
       empty: Không tìm thấy bản ghi.
-      filter_by_action: Lọc theo hành động
-      filter_by_user: Lọc theo người
+      filter_by_action: Theo hành động
+      filter_by_user: Theo người
       title: Nhật ký kiểm duyệt
     announcements:
       destroyed_msg: Xóa thông báo thành công!
@@ -388,7 +386,7 @@ vi:
       interactions: tương tác
       media_storage: Dung lượng lưu trữ
       new_users: người dùng mới
-      opened_reports: báo cáo chưa xử lí
+      opened_reports: tổng báo cáo
       pending_appeals_html:
         other: "<strong>%{count}</strong> kháng cáo đang chờ"
       pending_reports_html:
@@ -477,6 +475,7 @@ vi:
           other: Thất bại tạm thời vào %{count} ngày khác.
         no_failures_recorded: Chưa bao giờ thất bại.
         title: Khả dụng
+        warning: Lần thử cuối cùng để kết nối tới máy chủ này đã không thành công
       back_to_all: Toàn bộ
       back_to_limited: Hạn chế
       back_to_warning: Cảnh báo
@@ -514,9 +513,7 @@ vi:
       destroyed_msg: Dữ liệu từ %{domain} đã lên lịch để xóa.
       empty: Không có máy chủ nào.
       known_accounts:
-        one: "%{count} người dùng đã biết"
-        other: "%{count} người dùng đã biết"
-        zero: Không có người dùng đã biết
+        other: "%{count} tài khoản đã biết"
       moderation:
         all: Tất cả
         limited: Hạn chế
@@ -584,20 +581,20 @@ vi:
       action_log: Nhật ký kiểm duyệt
       action_taken_by: Hành động được thực hiện bởi
       actions:
-        delete_description_html: Những tút bị báo cáo sẽ được xóa và 1 thẹo sẽ được ghi lại để giúp bạn lưu ý về tài khoản này trong tương lai.
-        mark_as_sensitive_description_html: Media trong báo cáo sẽ bị đánh dấu nhạy cảm và bạn nhận 1 lần cảnh cáo.
+        delete_description_html: Những tút bị báo cáo sẽ được xóa và 1 lần cảnh cáo sẽ được ghi lại để giúp bạn lưu ý về tài khoản này trong tương lai.
+        mark_as_sensitive_description_html: Media trong báo cáo sẽ bị đánh dấu nhạy cảm và họ nhận 1 lần cảnh cáo.
         other_description_html: Những tùy chọn để kiểm soát tài khoản và giao tiếp với tài khoản bị báo cáo.
-        resolve_description_html: Không có hành động nào áp dụng đối với tài khoản bị báo cáo, không có thẹo, và báo cáo sẽ được đóng.
+        resolve_description_html: Không có hành động nào áp dụng đối với tài khoản bị báo cáo, không có cảnh cáo, và báo cáo sẽ được đóng.
         silence_description_html: Trang cá nhân sẽ chỉ hiển thị với những người đã theo dõi hoặc tìm kiếm thủ công, hạn chế tối đa tầm ảnh hưởng của nó. Có thể đổi lại bình thường sau.
         suspend_description_html: Trang cá nhân và tất cả các nội dung sẽ không thể truy cập cho đến khi nó bị xóa hoàn toàn. Không thể tương tác với tài khoản. Đảo ngược trong vòng 30 ngày.
-      actions_description_html: Quyết định hướng xử lý báo cáo này. Nếu áp đặt trừng phạt, một email thông báo sẽ được gửi cho họ, ngoại trừ nếu đó là <strong>Spam</strong>.
+      actions_description_html: Hướng xử lý báo cáo này. Nếu áp đặt trừng phạt, một email thông báo sẽ được gửi cho họ, ngoại trừ <strong>Spam</strong>.
       add_to_report: Bổ sung báo cáo
       are_you_sure: Bạn có chắc không?
       assign_to_self: Giao cho tôi
       assigned: Người xử lý
       by_target_domain: Tên tài khoản bị báo cáo
       category: Phân loại
-      category_description_html: Lý do tài khoản hoặc nội dung này bị báo cáo sẽ được trích dẫn trong giao tiếp với tài khoản báo cáo
+      category_description_html: Lý do tài khoản hoặc nội dung này bị báo cáo sẽ được trích dẫn khi giao tiếp với họ
       comment:
         none: Không có mô tả
       comment_description_html: 'Để cung cấp thêm thông tin, %{name} cho biết:'
@@ -628,7 +625,7 @@ vi:
       skip_to_actions: Kiểm duyệt ngay
       status: Trạng thái
       statuses: Nội dung bị báo cáo
-      statuses_description_html: Lý do tài khoản hoặc nội dung này bị báo cáo sẽ được trích dẫn trong giao tiếp với tài khoản báo cáo
+      statuses_description_html: Lý do tài khoản hoặc nội dung này bị báo cáo sẽ được trích dẫn khi giao tiếp với họ
       target_origin: Nguồn báo cáo
       title: Báo cáo
       unassign: Bỏ qua
@@ -703,8 +700,8 @@ vi:
         desc_html: Nếu tắt, bảng tin sẽ chỉ hiển thị nội dung do người dùng của máy chủ này tạo ra
         title: Bao gồm nội dung từ mạng liên hợp trên bảng tin không được xác thực
       show_staff_badge:
-        desc_html: Hiển thị huy hiệu nhân viên trên trang người dùng
-        title: Hiển thị huy hiệu nhân viên
+        desc_html: Hiện huy hiệu đội ngũ trên trang người dùng
+        title: Hiện huy hiệu đội ngũ
       site_description:
         desc_html: Nội dung giới thiệu về máy chủ. Mô tả những gì làm cho máy chủ Mastodon này đặc biệt và bất cứ điều gì quan trọng khác. Bạn có thể dùng các thẻ HTML, đặc biệt là <code>&lt;a&gt;</code> và <code>&lt;em&gt;</code>.
         title: Mô tả máy chủ
@@ -760,6 +757,11 @@ vi:
     system_checks:
       database_schema_check:
         message_html: Có cơ sở dữ liệu đang chờ xử lý. Xin khởi động nó để ứng dụng có thể hoạt động một cách ổn định nhất
+      elasticsearch_running_check:
+        message_html: Không thể kết nối Elasticsearch. Hãy kiểm tra xem nó có đang chạy, hay tìm kiếm full-text bị tắt
+      elasticsearch_version_check:
+        message_html: 'Phiên bản Elasticsearch không tương thích: %{value}'
+        version_comparison: Đang dùng Elasticsearch %{running_version} trong khi bắt buộc phải có %{required_version}
       rules_check:
         action: Sửa quy tắc máy chủ
         message_html: Bạn chưa cập nhật quy tắc máy chủ.
@@ -774,15 +776,13 @@ vi:
       approved: Đã cho phép
       disallow: Cấm
       links:
-        allow: Liên kết cho phép
-        allow_provider: Nguồn đăng cho phép
-        description_html: Đây là những liên kết được chia sẻ nhiều trên máy chủ của bạn. Nó có thể giúp người dùng tìm hiểu những gì đang xảy ra trên thế giới. Không có liên kết nào được hiển thị công khai cho đến khi bạn duyệt nguồn. Bạn cũng có thể cho phép hoặc từ chối từng liên kết riêng.
-        disallow: Liên kết cấm
-        disallow_provider: Nguồn đăng bị cấm
+        allow: Cho phép liên kết
+        allow_provider: Cho phép nguồn đăng
+        description_html: Đây là những liên kết được chia sẻ nhiều trên máy chủ của bạn. Nó có thể giúp người dùng tìm hiểu những gì đang xảy ra trên thế giới. Không có liên kết nào được hiển thị công khai cho đến khi bạn duyệt nguồn đăng. Bạn cũng có thể cho phép hoặc từ chối từng liên kết riêng.
+        disallow: Cấm liên kết
+        disallow_provider: Cấm nguồn đăng
         shared_by_over_week:
-          one: một người chia sẻ trong tuần qua
-          other: "%{count} người chia sẻ trong tuần qua"
-          zero: Không ai chia sẻ trong tuần qua
+          other: "%{count} người chia sẻ tuần rồi"
         title: Liên kết xu hướng
         usage_comparison: Chia sẻ %{today} lần hôm nay, so với %{yesterday} lần hôm qua
       pending_review: Đang chờ
@@ -796,8 +796,8 @@ vi:
         allow: Cho phép tút
         allow_account: Cho phép người đăng
         description_html: Đây là những tút đang được đăng lại và yêu thích rất nhiều trên máy chủ của bạn. Nó có thể giúp người dùng mới và người dùng cũ tìm thấy nhiều người hơn để theo dõi. Không có tút nào được hiển thị công khai cho đến khi bạn cho phép người đăng và người cho phép đề xuất tài khoản của họ cho người khác. Bạn cũng có thể cho phép hoặc từ chối từng tút riêng.
-        disallow: Không cho phép tút
-        disallow_account: Không cho phép người đăng
+        disallow: Cấm tút
+        disallow_account: Cấm người đăng
         not_discoverable: Tác giả đã chọn không tham gia mục khám phá
         shared_by:
           other: Được thích và đăng lại %{friendly_count} lần
@@ -817,14 +817,12 @@ vi:
         not_usable: Không được phép dùng
         peaked_on_and_decaying: Đỉnh điểm %{date}, giờ đang giảm
         title: Hashtag xu hướng
-        trendable: Có thể xuất hiện thành xu hướng
+        trendable: Có thể trở thành xu hướng
         trending_rank: 'Xu hướng #%{rank}'
         usable: Có thể dùng
         usage_comparison: Dùng %{today} lần hôm nay, so với %{yesterday} hôm qua
         used_by_over_week:
-          one: Dùng bởi một người trong tuần qua
-          other: Dùng bởi %{count} người trong tuần qua
-          zero: Không ai dùng trong tuần qua
+          other: "%{count} người dùng tuần rồi"
       title: Xu hướng
     warning_presets:
       add_new: Thêm mới
@@ -1094,7 +1092,7 @@ vi:
     new:
       title: Thêm bộ lọc mới
   footer:
-    developers: Nhà phát triển
+    developers: Phát triển
     more: Nhiều hơn
     resources: Quy tắc
     trending_now: Xu hướng
@@ -1433,7 +1431,7 @@ vi:
     show_more: Đọc thêm
     show_newer: Mới hơn
     show_older: Cũ hơn
-    show_thread: Toàn chủ đề
+    show_thread: Xem chuỗi tút này
     sign_in_to_participate: Đăng nhập để trả lời tút này
     title: '%{name}: "%{quote}"'
     visibilities:
@@ -1609,7 +1607,7 @@ vi:
         none: Cảnh báo
         sensitive: Tài khoản đã bị đánh dấu nhạy cảm
         silence: Tài khoản bị hạn chế
-        suspend: Toài khoản bị vô hiệu hóa
+        suspend: Tài khoản bị vô hiệu hóa
     welcome:
       edit_profile_action: Cài đặt trang cá nhân
       edit_profile_step: Bạn có thể tùy chỉnh trang cá nhân của mình bằng cách tải lên ảnh đại diện, ảnh bìa, thay đổi tên hiển thị và hơn thế nữa. Nếu bạn muốn những người theo dõi mới phải được phê duyệt, hãy chuyển tài khoản sang trạng thái khóa.
diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml
index 6561a5716..48c2ec09a 100644
--- a/config/locales/zh-CN.yml
+++ b/config/locales/zh-CN.yml
@@ -161,10 +161,6 @@ zh-CN:
       pending: 待审核
       perform_full_suspension: 封禁
       previous_strikes: 既往处罚
-      previous_strikes_description_html:
-        one: 此账号有<strong>1次</strong>处罚。
-        other: 此账号有<strong>%{count}次</strong>处罚。
-        zero: 此账号<strong>记录良好</strong>。
       promote: 升任
       protocol: 协议
       public: 公开页面
@@ -479,6 +475,7 @@ zh-CN:
           other: 在 %{count} 天中尝试失败。
         no_failures_recorded: 没有失败记录。
         title: 可用性
+        warning: 上一次连接到此服务器的尝试失败了
       back_to_all: 全部
       back_to_limited: 受限
       back_to_warning: 警告
@@ -515,10 +512,6 @@ zh-CN:
       delivery_error_hint: 如果投递已不可用 %{count} 天,它将被自动标记为无法投递。
       destroyed_msg: "%{domain} 中的数据现在正在排队等待被立刻删除。"
       empty: 暂无域名。
-      known_accounts:
-        one: "%{count} 个已知帐户"
-        other: "%{count} 个已知帐户"
-        zero: 没有已知账户
       moderation:
         all: 全部
         limited: 受限的
@@ -781,10 +774,6 @@ zh-CN:
         description_html: 这些是当前此服务器可见账号的嘟文中被大量分享的链接。它可以帮助用户了解正在发生的事情。发布者获得批准前不会公开显示任何链接。你也可以批准或拒绝单个链接。
         disallow: 不允许链接
         disallow_provider: 不允许发布者
-        shared_by_over_week:
-          one: 过去一周内被 1 个人分享过
-          other: 过去一周内被 %{count} 个人分享过
-          zero: 过去一周内未被分享过
         title: 热门链接
         usage_comparison: 今日被分享 %{today} 次,前一日为 %{yesterday} 次
       pending_review: 待审核
@@ -823,10 +812,6 @@ zh-CN:
         trending_rank: '热门 #%{rank}'
         usable: 可以使用
         usage_comparison: 今日被使用 %{today} 次,前一日为 %{yesterday} 次
-        used_by_over_week:
-          one: 过去一周内被 1 个人使用过
-          other: 过去一周内被 %{count} 个人使用过
-          zero: 过去一周内未被使用过
       title: 流行趋势
     warning_presets:
       add_new: 添加新条目
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index 43a71a5dd..90625c5fd 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -160,9 +160,7 @@ zh-TW:
       perform_full_suspension: 停權
       previous_strikes: 先前的警示
       previous_strikes_description_html:
-        one: 此帳號已有 <strong>1</strong> 次警示。
         other: 此帳號已有 <strong>%{count}</strong> 次警示。
-        zero: 此帳號<strong>信譽良好</strong>。
       promote: 晉級
       protocol: 協議
       public: 公開
@@ -479,6 +477,7 @@ zh-TW:
           other: 錯誤嘗試於 %{count} 天。
         no_failures_recorded: 報告中沒有錯誤。
         title: 可用狀態
+        warning: 上一次嘗試連線至本伺服器失敗
       back_to_all: 所有
       back_to_limited: 受限制的
       back_to_warning: 警告
@@ -516,9 +515,7 @@ zh-TW:
       destroyed_msg: 來自 %{domain} 的資料現在正在佇列中等待刪除。
       empty: 找不到網域
       known_accounts:
-        one: "%{count} 個已知帳號"
         other: "%{count} 個已知帳號"
-        zero: 沒有已知帳號
       moderation:
         all: 全部
         limited: 限制
@@ -762,6 +759,11 @@ zh-TW:
     system_checks:
       database_schema_check:
         message_html: 有挂起的数据库迁移,请运行它们以确保应用程序按照预期运行。
+      elasticsearch_running_check:
+        message_html: 無法連接 Elasticsearch。請檢查是否正在執行中,或者已關閉全文搜尋。
+      elasticsearch_version_check:
+        message_html: 不相容的 Elasticsearch 版本:%{value}
+        version_comparison: Elasticsearch %{running_version} 版正在執行,需要 %{required_version} 版。
       rules_check:
         action: 管理服务器规则
         message_html: 你没有定义任何服务器规则。
@@ -782,9 +784,7 @@ zh-TW:
         disallow: 不允許連結
         disallow_provider: 不允許發行者
         shared_by_over_week:
-          one: 上週由 1 個人分享
-          other: 上週由 %{count} 個人分享
-          zero: 上週無人分享
+          other: 上週被 %{count} 名使用者分享
         title: 熱門連結
         usage_comparison: 於今日被 %{today} 人分享,相較於昨日 %{yesterday} 人
       pending_review: 等待審核中
@@ -824,9 +824,7 @@ zh-TW:
         usable: 可被使用
         usage_comparison: 於今日被使用 %{today} 次,相較於昨日 %{yesterday} 次
         used_by_over_week:
-          one: 上週被 1 個人使用
           other: 上週被 %{count} 個人使用
-          zero: 上週無人使用
       title: 熱門榜
     warning_presets:
       add_new: 新增
diff --git a/config/routes.rb b/config/routes.rb
index 4ed2ee760..55e17ab14 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -581,6 +581,10 @@ Rails.application.routes.draw do
       resources :media, only: [:create]
       get '/search', to: 'search#index', as: :search
       resources :suggestions, only: [:index]
+
+      namespace :admin do
+        resources :accounts, only: [:index]
+      end
     end
 
     namespace :web do
diff --git a/config/sidekiq.yml b/config/sidekiq.yml
index c8b1a20dd..f2ae9279b 100644
--- a/config/sidekiq.yml
+++ b/config/sidekiq.yml
@@ -22,7 +22,7 @@
     class: Scheduler::EmailDomainBlockRefreshScheduler
     queue: scheduler
   trends_review_notifications_scheduler:
-    every: '2h'
+    every: '6h'
     class: Scheduler::Trends::ReviewNotificationsScheduler
     queue: scheduler
   media_cleanup_scheduler:
diff --git a/docker-compose.yml b/docker-compose.yml
index 01fe320a4..5c2c0c5df 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,6 +1,5 @@
 version: '3'
 services:
-
   db:
     restart: always
     image: postgres:14-alpine
@@ -8,11 +7,11 @@ services:
     networks:
       - internal_network
     healthcheck:
-      test: ["CMD", "pg_isready", "-U", "postgres"]
+      test: ['CMD', 'pg_isready', '-U', 'postgres']
     volumes:
       - ./postgres14:/var/lib/postgresql/data
     environment:
-      - "POSTGRES_HOST_AUTH_METHOD=trust"
+      - 'POSTGRES_HOST_AUTH_METHOD=trust'
 
   redis:
     restart: always
@@ -20,28 +19,28 @@ services:
     networks:
       - internal_network
     healthcheck:
-      test: ["CMD", "redis-cli", "ping"]
+      test: ['CMD', 'redis-cli', 'ping']
     volumes:
       - ./redis:/data
 
-#  es:
-#    restart: always
-#    image: docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2
-#    environment:
-#      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
-#      - "cluster.name=es-mastodon"
-#      - "discovery.type=single-node"
-#      - "bootstrap.memory_lock=true"
-#    networks:
-#      - internal_network
-#    healthcheck:
-#      test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
-#    volumes:
-#      - ./elasticsearch:/usr/share/elasticsearch/data
-#    ulimits:
-#      memlock:
-#        soft: -1
-#        hard: -1
+  # es:
+  #   restart: always
+  #   image: docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2
+  #   environment:
+  #     - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
+  #     - "cluster.name=es-mastodon"
+  #     - "discovery.type=single-node"
+  #     - "bootstrap.memory_lock=true"
+  #   networks:
+  #      - internal_network
+  #   healthcheck:
+  #      test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
+  #   volumes:
+  #      - ./elasticsearch:/usr/share/elasticsearch/data
+  #   ulimits:
+  #     memlock:
+  #       soft: -1
+  #       hard: -1
 
   web:
     build: .
@@ -53,13 +52,14 @@ services:
       - external_network
       - internal_network
     healthcheck:
-      test: ["CMD-SHELL", "wget -q --spider --proxy=off localhost:3000/health || exit 1"]
+      # prettier-ignore
+      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:3000/health || exit 1']
     ports:
-      - "127.0.0.1:3000:3000"
+      - '127.0.0.1:3000:3000'
     depends_on:
       - db
       - redis
-#      - es
+      # - es
     volumes:
       - ./public/system:/mastodon/public/system
 
@@ -73,9 +73,10 @@ services:
       - external_network
       - internal_network
     healthcheck:
-      test: ["CMD-SHELL", "wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1"]
+      # prettier-ignore
+      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1']
     ports:
-      - "127.0.0.1:4000:4000"
+      - '127.0.0.1:4000:4000'
     depends_on:
       - db
       - redis
@@ -95,24 +96,24 @@ services:
     volumes:
       - ./public/system:/mastodon/public/system
     healthcheck:
-      test: ["CMD-SHELL", "ps aux | grep '[s]idekiq\ 6' || false"]
+      test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]
 
-## Uncomment to enable federation with tor instances along with adding the following ENV variables
-## http_proxy=http://privoxy:8118
-## ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
-#  tor:
-#    image: sirboops/tor
-#    networks:
-#      - external_network
-#      - internal_network
-#
-#  privoxy:
-#    image: sirboops/privoxy
-#    volumes:
-#      - ./priv-config:/opt/config
-#    networks:
-#      - external_network
-#      - internal_network
+  ## Uncomment to enable federation with tor instances along with adding the following ENV variables
+  ## http_proxy=http://privoxy:8118
+  ## ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
+  # tor:
+  #   image: sirboops/tor
+  #   networks:
+  #      - external_network
+  #      - internal_network
+  #
+  # privoxy:
+  #   image: sirboops/privoxy
+  #   volumes:
+  #     - ./priv-config:/opt/config
+  #   networks:
+  #     - external_network
+  #     - internal_network
 
 networks:
   external_network:
diff --git a/lib/mastodon/email_domain_blocks_cli.rb b/lib/mastodon/email_domain_blocks_cli.rb
index f79df302a..f39f47069 100644
--- a/lib/mastodon/email_domain_blocks_cli.rb
+++ b/lib/mastodon/email_domain_blocks_cli.rb
@@ -32,9 +32,9 @@ module Mastodon
       multiple domains to the command.
 
       When the --with-dns-records option is given, an attempt to resolve the
-      given domains' DNS records will be made and the results (A, AAAA and MX) will
-      also be blocked. This can be helpful if you are blocking an e-mail server that
-      has many different domains pointing to it as it allows you to essentially block
+      given domains' MX records will be made and the results will also be blocked.
+      This can be helpful if you are blocking an e-mail server that has many
+      different domains pointing to it as it allows you to essentially block
       it at the root.
     LONG_DESC
     def add(*domains)
@@ -53,26 +53,19 @@ module Mastodon
           next
         end
 
-        email_domain_block = EmailDomainBlock.new(domain: domain, with_dns_records: options[:with_dns_records] || false)
-        email_domain_block.save!
-        processed += 1
-
-        next unless email_domain_block.with_dns_records?
-
-        hostnames = []
-        ips       = []
-
-        Resolv::DNS.open do |dns|
-          dns.timeouts = 5
-          hostnames = dns.getresources(email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }
-
-          ([email_domain_block.domain] + hostnames).uniq.each do |hostname|
-            ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::A).to_a.map { |e| e.address.to_s })
-            ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::AAAA).to_a.map { |e| e.address.to_s })
+        other_domains = []
+        if options[:with_dns_records]
+          Resolv::DNS.open do |dns|
+            dns.timeouts = 5
+            other_domains = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a
           end
         end
 
-        (hostnames + ips).uniq.each do |hostname|
+        email_domain_block = EmailDomainBlock.new(domain: domain, other_domains: other_domains)
+        email_domain_block.save!
+        processed += 1
+
+        (email_domain_block.other_domains || []).uniq.each do |hostname|
           another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: email_domain_block)
 
           if EmailDomainBlock.where(domain: hostname).exists?
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index cdeeb6ea2..ba060a7da 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -17,7 +17,7 @@ module Mastodon
     end
 
     def flags
-      'rc1'
+      ''
     end
 
     def suffix
diff --git a/lib/sanitize_ext/sanitize_config.rb b/lib/sanitize_ext/sanitize_config.rb
index fed504cf2..c02de967b 100644
--- a/lib/sanitize_ext/sanitize_config.rb
+++ b/lib/sanitize_ext/sanitize_config.rb
@@ -56,18 +56,6 @@ class Sanitize
       end
     end
 
-    LINK_REL_TRANSFORMER = lambda do |env|
-      return unless env[:node_name] == 'a' and env[:node]['href']
-
-      node = env[:node]
-
-      rel = (node['rel'] || '').split(' ') & ['tag']
-      unless env[:config][:outgoing] && TagManager.instance.local_url?(node['href'])
-        rel += ['nofollow', 'noopener', 'noreferrer']
-      end
-      node['rel'] = rel.join(' ')
-    end
-
     UNSUPPORTED_HREF_TRANSFORMER = lambda do |env|
       return unless env[:node_name] == 'a'
 
@@ -98,6 +86,7 @@ class Sanitize
 
       add_attributes: {
         'a' => {
+          'rel' => 'nofollow noopener noreferrer',
           'target' => '_blank',
         },
       },
@@ -111,7 +100,6 @@ class Sanitize
         CLASS_WHITELIST_TRANSFORMER,
         IMG_TAG_TRANSFORMER,
         UNSUPPORTED_HREF_TRANSFORMER,
-        LINK_REL_TRANSFORMER,
       ]
     )
 
@@ -136,5 +124,48 @@ class Sanitize
         'source' => { 'src' => HTTP_PROTOCOLS }
       )
     )
+
+    LINK_REL_TRANSFORMER = lambda do |env|
+      return unless env[:node_name] == 'a' && env[:node]['href']
+
+      node = env[:node]
+
+      rel = (node['rel'] || '').split(' ') & ['tag']
+      rel += ['nofollow', 'noopener', 'noreferrer'] unless TagManager.instance.local_url?(node['href'])
+
+      if rel.empty?
+        node['rel']&.delete
+      else
+        node['rel'] = rel.join(' ')
+      end
+    end
+
+    LINK_TARGET_TRANSFORMER = lambda do |env|
+      return unless env[:node_name] == 'a' && env[:node]['href']
+
+      node = env[:node]
+      if node['target'] != '_blank' && TagManager.instance.local_url?(node['href'])
+        node['target']&.delete
+      else
+        node['target'] = '_blank'
+      end
+    end
+
+    MASTODON_OUTGOING ||= freeze_config MASTODON_STRICT.merge(
+      attributes: merge(
+        MASTODON_STRICT[:attributes],
+        'a' => %w(href rel class title target)
+      ),
+
+      add_attributes: {},
+
+      transformers: [
+        CLASS_WHITELIST_TRANSFORMER,
+        IMG_TAG_TRANSFORMER,
+        UNSUPPORTED_HREF_TRANSFORMER,
+        LINK_REL_TRANSFORMER,
+        LINK_TARGET_TRANSFORMER,
+      ]
+    )
   end
 end
diff --git a/package.json b/package.json
index f35747bd9..99f0e3daa 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,9 @@
     "test:lint": "${npm_execpath} run test:lint:js && ${npm_execpath} run test:lint:sass",
     "test:lint:js": "eslint --ext=js . --cache",
     "test:lint:sass": "sass-lint -v",
-    "test:jest": "cross-env NODE_ENV=test jest"
+    "test:jest": "cross-env NODE_ENV=test jest",
+    "format": "prettier --write '**/*.{json,yml}",
+    "format-check": "prettier --write '**/*.{json,yml}"
   },
   "repository": {
     "type": "git",
@@ -61,13 +63,13 @@
   },
   "private": true,
   "dependencies": {
-    "@babel/core": "^7.17.7",
-    "@babel/plugin-proposal-decorators": "^7.17.2",
+    "@babel/core": "^7.17.8",
+    "@babel/plugin-proposal-decorators": "^7.17.8",
     "@babel/plugin-transform-react-inline-elements": "^7.16.7",
     "@babel/plugin-transform-runtime": "^7.17.0",
     "@babel/preset-env": "^7.16.11",
     "@babel/preset-react": "^7.16.7",
-    "@babel/runtime": "^7.17.7",
+    "@babel/runtime": "^7.17.8",
     "@gamestdio/websocket": "^0.3.2",
     "@github/webauthn-json": "^0.5.7",
     "@rails/ujs": "^6.1.5",
@@ -76,7 +78,7 @@
     "atrament": "0.2.4",
     "autoprefixer": "^9.8.8",
     "axios": "^0.26.1",
-    "babel-loader": "^8.2.3",
+    "babel-loader": "^8.2.4",
     "babel-plugin-lodash": "^3.3.4",
     "babel-plugin-preval": "^5.1.0",
     "babel-plugin-react-intl": "^6.2.0",
@@ -175,7 +177,7 @@
     "ws": "^8.5.0"
   },
   "devDependencies": {
-    "@testing-library/jest-dom": "^5.16.2",
+    "@testing-library/jest-dom": "^5.16.3",
     "@testing-library/react": "^12.1.4",
     "babel-eslint": "^10.1.0",
     "babel-jest": "^27.5.1",
@@ -183,14 +185,15 @@
     "eslint-plugin-import": "~2.25.4",
     "eslint-plugin-jsx-a11y": "~6.5.1",
     "eslint-plugin-promise": "~6.0.0",
-    "eslint-plugin-react": "~7.29.3",
+    "eslint-plugin-react": "~7.29.4",
     "jest": "^27.5.1",
+    "prettier": "^2.6.1",
     "raf": "^3.4.1",
     "react-intl-translations-manager": "^5.0.3",
     "react-test-renderer": "^16.14.0",
     "sass-lint": "^1.13.1",
     "webpack-dev-server": "^3.11.3",
-    "yargs": "^17.3.1"
+    "yargs": "^17.4.0"
   },
   "resolutions": {
     "kind-of": "^6.0.3"
diff --git a/scalingo.json b/scalingo.json
index 51d9b5b9f..511c1802a 100644
--- a/scalingo.json
+++ b/scalingo.json
@@ -92,8 +92,5 @@
   "scripts": {
     "postdeploy": "bundle exec rails db:migrate && bundle exec rails db:seed"
   },
-  "addons": [
-    "postgresql",
-    "redis"
-  ]
+  "addons": ["postgresql", "redis"]
 }
diff --git a/spec/controllers/admin/accounts_controller_spec.rb b/spec/controllers/admin/accounts_controller_spec.rb
index 0f71d697c..1779fb7c0 100644
--- a/spec/controllers/admin/accounts_controller_spec.rb
+++ b/spec/controllers/admin/accounts_controller_spec.rb
@@ -194,9 +194,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
   end
 
   describe 'POST #unblock_email' do
-    subject do
-      -> { post :unblock_email, params: { id: account.id } }
-    end
+    subject { post :unblock_email, params: { id: account.id } }
 
     let(:current_user) { Fabricate(:user, admin: admin) }
     let(:account) { Fabricate(:account, suspended: true) }
@@ -206,11 +204,11 @@ RSpec.describe Admin::AccountsController, type: :controller do
       let(:admin) { true }
 
       it 'succeeds in removing email blocks' do
-        is_expected.to change { CanonicalEmailBlock.where(reference_account: account).count }.from(1).to(0)
+        expect { subject }.to change { CanonicalEmailBlock.where(reference_account: account).count }.from(1).to(0)
       end
 
       it 'redirects to admin account path' do
-        subject.call
+        subject
         expect(response).to redirect_to admin_account_path(account.id)
       end
     end
@@ -219,7 +217,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
       let(:admin) { false }
 
       it 'fails to remove avatar' do
-        subject.call
+        subject
         expect(response).to have_http_status :forbidden
       end
     end
diff --git a/spec/controllers/api/v1/admin/accounts_controller_spec.rb b/spec/controllers/api/v1/admin/accounts_controller_spec.rb
index 3f61bbc0b..b69595f7e 100644
--- a/spec/controllers/api/v1/admin/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/accounts_controller_spec.rb
@@ -30,15 +30,44 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
   end
 
   describe 'GET #index' do
+    let!(:remote_account)       { Fabricate(:account, domain: 'example.org') }
+    let!(:other_remote_account) { Fabricate(:account, domain: 'foo.bar') }
+    let!(:suspended_account)    { Fabricate(:account, suspended: true) }
+    let!(:suspended_remote)     { Fabricate(:account, domain: 'foo.bar', suspended: true) }
+    let!(:disabled_account)     { Fabricate(:user, disabled: true).account }
+    let!(:pending_account)      { Fabricate(:user, approved: false).account }
+    let!(:admin_account)        { user.account }
+
+    let(:params) { {} }
+
     before do
-      get :index
+      pending_account.user.update(approved: false)
+      get :index, params: params
     end
 
     it_behaves_like 'forbidden for wrong scope', 'write:statuses'
     it_behaves_like 'forbidden for wrong role', 'user'
 
-    it 'returns http success' do
-      expect(response).to have_http_status(200)
+    [
+      [{ active: 'true', local: 'true', staff: 'true' }, [:admin_account]],
+      [{ by_domain: 'example.org', remote: 'true' }, [:remote_account]],
+      [{ suspended: 'true' }, [:suspended_account]],
+      [{ disabled: 'true' }, [:disabled_account]],
+      [{ pending: 'true' }, [:pending_account]],
+    ].each do |params, expected_results|
+      context "when called with #{params.inspect}" do
+        let(:params) { params }
+
+        it 'returns http success' do
+          expect(response).to have_http_status(200)
+        end
+
+        it "returns the correct accounts (#{expected_results.inspect})" do
+          json = body_as_json
+
+          expect(json.map { |a| a[:id].to_i }).to eq (expected_results.map { |symbol| send(symbol).id })
+        end
+      end
     end
   end
 
diff --git a/spec/controllers/api/v2/admin/accounts_controller_spec.rb b/spec/controllers/api/v2/admin/accounts_controller_spec.rb
new file mode 100644
index 000000000..3212ddb84
--- /dev/null
+++ b/spec/controllers/api/v2/admin/accounts_controller_spec.rb
@@ -0,0 +1,73 @@
+require 'rails_helper'
+
+RSpec.describe Api::V2::Admin::AccountsController, type: :controller do
+  render_views
+
+  let(:role)   { 'moderator' }
+  let(:user)   { Fabricate(:user, role: role) }
+  let(:scopes) { 'admin:read admin:write' }
+  let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
+  let(:account) { Fabricate(:account) }
+
+  before do
+    allow(controller).to receive(:doorkeeper_token) { token }
+  end
+
+  shared_examples 'forbidden for wrong scope' do |wrong_scope|
+    let(:scopes) { wrong_scope }
+
+    it 'returns http forbidden' do
+      expect(response).to have_http_status(403)
+    end
+  end
+
+  shared_examples 'forbidden for wrong role' do |wrong_role|
+    let(:role) { wrong_role }
+
+    it 'returns http forbidden' do
+      expect(response).to have_http_status(403)
+    end
+  end
+
+  describe 'GET #index' do
+    let!(:remote_account)       { Fabricate(:account, domain: 'example.org') }
+    let!(:other_remote_account) { Fabricate(:account, domain: 'foo.bar') }
+    let!(:suspended_account)    { Fabricate(:account, suspended: true) }
+    let!(:suspended_remote)     { Fabricate(:account, domain: 'foo.bar', suspended: true) }
+    let!(:disabled_account)     { Fabricate(:user, disabled: true).account }
+    let!(:pending_account)      { Fabricate(:user, approved: false).account }
+    let!(:admin_account)        { user.account }
+
+    let(:params) { {} }
+
+    before do
+      pending_account.user.update(approved: false)
+      get :index, params: params
+    end
+
+    it_behaves_like 'forbidden for wrong scope', 'write:statuses'
+    it_behaves_like 'forbidden for wrong role', 'user'
+
+    [
+      [{ status: 'active', origin: 'local', permissions: 'staff' }, [:admin_account]],
+      [{ by_domain: 'example.org', origin: 'remote' }, [:remote_account]],
+      [{ status: 'suspended' }, [:suspended_remote, :suspended_account]],
+      [{ status: 'disabled' }, [:disabled_account]],
+      [{ status: 'pending' }, [:pending_account]],
+    ].each do |params, expected_results|
+      context "when called with #{params.inspect}" do
+        let(:params) { params }
+
+        it 'returns http success' do
+          expect(response).to have_http_status(200)
+        end
+
+        it "returns the correct accounts (#{expected_results.inspect})" do
+          json = body_as_json
+
+          expect(json.map { |a| a[:id].to_i }).to eq (expected_results.map { |symbol| send(symbol).id })
+        end
+      end
+    end
+  end
+end
diff --git a/spec/controllers/settings/exports/bookmarks_controller_specs.rb b/spec/controllers/settings/exports/bookmarks_controller_spec.rb
index 85761577b..a06c02e0c 100644
--- a/spec/controllers/settings/exports/bookmarks_controller_specs.rb
+++ b/spec/controllers/settings/exports/bookmarks_controller_spec.rb
@@ -3,11 +3,16 @@ require 'rails_helper'
 describe Settings::Exports::BookmarksController do
   render_views
 
+  let(:user)    { Fabricate(:user) }
+  let(:account) { Fabricate(:account, domain: 'foo.bar') }
+  let(:status)  { Fabricate(:status, account: account, uri: 'https://foo.bar/statuses/1312') }
+
   describe 'GET #index' do
-    it 'returns a csv of the bookmarked toots' do
-      user = Fabricate(:user)
-      user.account.bookmarks.create!(status: Fabricate(:status, uri: 'https://foo.bar/statuses/1312'))
+    before do
+      user.account.bookmarks.create!(status: status)
+    end
 
+    it 'returns a csv of the bookmarked toots' do
       sign_in user, scope: :user
       get :index, format: :csv
 
diff --git a/spec/lib/advanced_text_formatter_spec.rb b/spec/lib/advanced_text_formatter_spec.rb
new file mode 100644
index 000000000..4e859c93c
--- /dev/null
+++ b/spec/lib/advanced_text_formatter_spec.rb
@@ -0,0 +1,282 @@
+require 'rails_helper'
+
+RSpec.describe AdvancedTextFormatter do
+  describe '#to_s' do
+    let(:preloaded_accounts) { nil }
+    let(:content_type) { 'text/markdown' }
+
+    subject { described_class.new(text, preloaded_accounts: preloaded_accounts, content_type: content_type).to_s }
+
+    context 'given a markdown source' do
+      let(:content_type) { 'text/markdown' }
+
+      context 'given text containing plain text' do
+        let(:text) { 'text' }
+
+        it 'paragraphizes the text' do
+          is_expected.to eq '<p>text</p>'
+        end
+      end
+
+      context 'given text containing line feeds' do
+        let(:text) { "line\nfeed" }
+
+        it 'removes line feeds' do
+          is_expected.not_to include "\n"
+        end
+      end
+
+      context 'given some inline code using backticks' do
+        let(:text) { 'test `foo` bar' }
+
+        it 'formats code using <code>' do
+          is_expected.to include 'test <code>foo</code> bar'
+        end
+      end
+
+      context 'given a block code' do
+        let(:text) { "test\n\n```\nint main(void) {\n  return 0;\n}\n```\n" }
+
+        it 'formats code using <pre> and <code>' do
+          is_expected.to include '<pre><code>int main'
+        end
+      end
+
+      context 'given some quote' do
+        let(:text) { "> foo\n\nbar" }
+
+        it 'formats code using <code>' do
+          is_expected.to include '<blockquote><p>foo</p></blockquote>'
+        end
+      end
+
+      context 'given text containing linkable mentions' do
+        let(:preloaded_accounts) { [Fabricate(:account, username: 'alice')] }
+        let(:text) { '@alice' }
+
+        it 'creates a mention link' do
+          is_expected.to include '<a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span>'
+        end
+      end
+
+      context 'given text containing unlinkable mentions' do
+        let(:preloaded_accounts) { [] }
+        let(:text) { '@alice' }
+
+        it 'does not create a mention link' do
+          is_expected.to include '@alice'
+        end
+      end
+
+      context 'given a stand-alone medium URL' do
+        let(:text) { 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4' }
+
+        it 'matches the full URL' do
+          is_expected.to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"'
+        end
+      end
+
+      context 'given a stand-alone google URL' do
+        let(:text) { 'http://google.com' }
+
+        it 'matches the full URL' do
+          is_expected.to include 'href="http://google.com"'
+        end
+      end
+
+      context 'given a stand-alone URL with a newer TLD' do
+        let(:text) { 'http://example.gay' }
+
+        it 'matches the full URL' do
+          is_expected.to include 'href="http://example.gay"'
+        end
+      end
+
+      context 'given a stand-alone IDN URL' do
+        let(:text) { 'https://nic.みんな/' }
+
+        it 'matches the full URL' do
+          is_expected.to include 'href="https://nic.みんな/"'
+        end
+
+        it 'has display URL' do
+          is_expected.to include '<span class="">nic.みんな/</span>'
+        end
+      end
+
+      context 'given a URL with a trailing period' do
+        let(:text) { 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ' }
+
+        it 'matches the full URL but not the period' do
+          is_expected.to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"'
+        end
+      end
+
+      context 'given a URL enclosed with parentheses' do
+        let(:text) { '(http://google.com/)' }
+
+        it 'matches the full URL but not the parentheses' do
+          is_expected.to include 'href="http://google.com/"'
+        end
+      end
+
+      context 'given a URL with a trailing exclamation point' do
+        let(:text) { 'http://www.google.com!' }
+
+        it 'matches the full URL but not the exclamation point' do
+          is_expected.to include 'href="http://www.google.com"'
+        end
+      end
+
+      context 'given a URL with a trailing single quote' do
+        let(:text) { "http://www.google.com'" }
+
+        it 'matches the full URL but not the single quote' do
+          is_expected.to include 'href="http://www.google.com"'
+        end
+      end
+    end
+
+    context 'given a URL with a trailing angle bracket' do
+      let(:text) { 'http://www.google.com>' }
+
+      it 'matches the full URL but not the angle bracket' do
+        is_expected.to include 'href="http://www.google.com"'
+      end
+    end
+
+    context 'given a URL with a query string' do
+      context 'with escaped unicode character' do
+        let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' }
+
+        it 'matches the full URL' do
+          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;q=autolink"'
+        end
+      end
+
+      context 'with unicode character' do
+        let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓&q=autolink' }
+
+        it 'matches the full URL' do
+          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&amp;q=autolink"'
+        end
+      end
+
+      context 'with unicode character at the end' do
+        let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓' }
+
+        it 'matches the full URL' do
+          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"'
+        end
+      end
+
+      context 'with escaped and not escaped unicode characters' do
+        let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink' }
+
+        it 'preserves escaped unicode characters' do
+          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;utf81=✓&amp;q=autolink"'
+        end
+      end
+
+      context 'given a URL with parentheses in it' do
+        let(:text) { 'https://en.wikipedia.org/wiki/Diaspora_(software)' }
+
+        it 'matches the full URL' do
+          is_expected.to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"'
+        end
+      end
+
+      context 'given a URL in quotation marks' do
+        let(:text) { '"https://example.com/"' }
+
+        it 'does not match the quotation marks' do
+          is_expected.to include 'href="https://example.com/"'
+        end
+      end
+
+      context 'given a URL in angle brackets' do
+        let(:text) { '<https://example.com/>' }
+
+        it 'does not match the angle brackets' do
+          is_expected.to include 'href="https://example.com/"'
+        end
+      end
+
+      context 'given a URL containing unsafe code (XSS attack, invisible part)' do
+        let(:text) { %q{http://example.com/blahblahblahblah/a<script>alert("Hello")</script>} }
+
+        it 'does not include the HTML in the URL' do
+          is_expected.to include '"http://example.com/blahblahblahblah/a"'
+        end
+
+        it 'does not include a script tag' do
+          is_expected.to_not include '<script>'
+        end
+      end
+
+      context 'given text containing HTML code (script tag)' do
+        let(:text) { '<script>alert("Hello")</script>' }
+
+        it 'does not include a script tag' do
+          is_expected.to_not include '<script>'
+        end
+      end
+
+      context 'given text containing HTML (XSS attack)' do
+        let(:text) { %q{<img src="javascript:alert('XSS');">} }
+
+        it 'does not include the javascript' do
+          is_expected.to_not include 'href="javascript:'
+        end
+      end
+
+      context 'given an invalid URL' do
+        let(:text) { 'http://www\.google\.com' }
+
+        it 'outputs the raw URL' do
+          is_expected.to eq '<p>http://www\.google\.com</p>'
+        end
+      end
+
+      context 'given text containing a hashtag' do
+        let(:text)  { '#hashtag' }
+
+        it 'creates a hashtag link' do
+          is_expected.to include '/tags/hashtag" class="mention hashtag" rel="tag">#<span>hashtag</span></a>'
+        end
+      end
+
+      context 'given text containing a hashtag with Unicode chars' do
+        let(:text)  { '#hashtagタグ' }
+
+        it 'creates a hashtag link' do
+          is_expected.to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#<span>hashtagタグ</span></a>'
+        end
+      end
+
+      context 'given text with a stand-alone xmpp: URI' do
+        let(:text) { 'xmpp:user@instance.com' }
+
+        it 'matches the full URI' do
+          is_expected.to include 'href="xmpp:user@instance.com"'
+        end
+      end
+
+      context 'given text with an xmpp: URI with a query-string' do
+        let(:text) { 'please join xmpp:muc@instance.com?join right now' }
+
+        it 'matches the full URI' 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
+  end
+end
diff --git a/spec/lib/emoji_formatter_spec.rb b/spec/lib/emoji_formatter_spec.rb
new file mode 100644
index 000000000..129445aa5
--- /dev/null
+++ b/spec/lib/emoji_formatter_spec.rb
@@ -0,0 +1,55 @@
+require 'rails_helper'
+
+RSpec.describe EmojiFormatter do
+  let!(:emoji) { Fabricate(:custom_emoji, shortcode: 'coolcat') }
+
+  def preformat_text(str)
+    TextFormatter.new(str).to_s
+  end
+
+  describe '#to_s' do
+    subject { described_class.new(text, emojis).to_s }
+
+    let(:emojis) { [emoji] }
+
+    context 'given text that is not marked as html-safe' do
+      let(:text) { 'Foo' }
+
+      it 'raises an argument error' do
+        expect { subject }.to raise_error ArgumentError
+      end
+    end
+
+    context 'given text with an emoji shortcode at the start' do
+      let(:text) { preformat_text(':coolcat: Beep boop') }
+
+      it 'converts the shortcode to an image tag' do
+        is_expected.to match(/<img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
+      end
+    end
+
+    context 'given text with an emoji shortcode in the middle' do
+      let(:text) { preformat_text('Beep :coolcat: boop') }
+
+      it 'converts the shortcode to an image tag' do
+        is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
+      end
+    end
+
+    context 'given text with concatenated emoji shortcodes' do
+      let(:text) { preformat_text(':coolcat::coolcat:') }
+
+      it 'does not touch the shortcodes' do
+        is_expected.to match(/:coolcat::coolcat:/)
+      end
+    end
+
+    context 'given text with an emoji shortcode at the end' do
+      let(:text) { preformat_text('Beep boop :coolcat:') }
+
+      it 'converts the shortcode to an image tag' do
+        is_expected.to match(/boop <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
+      end
+    end
+  end
+end
diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb
deleted file mode 100644
index 73cb39550..000000000
--- a/spec/lib/formatter_spec.rb
+++ /dev/null
@@ -1,638 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Formatter do
-  let(:local_account)  { Fabricate(:account, domain: nil, username: 'alice') }
-  let(:remote_account) { Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') }
-
-  shared_examples 'encode and link URLs' do
-    context 'given a stand-alone medium URL' do
-      let(:text) { 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4' }
-
-      it 'matches the full URL' do
-        is_expected.to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"'
-      end
-    end
-
-    context 'given a stand-alone google URL' do
-      let(:text) { 'http://google.com' }
-
-      it 'matches the full URL' do
-        is_expected.to include 'href="http://google.com"'
-      end
-    end
-
-    context 'given a stand-alone URL with a newer TLD' do
-      let(:text) { 'http://example.gay' }
-
-      it 'matches the full URL' do
-        is_expected.to include 'href="http://example.gay"'
-      end
-    end
-
-    context 'given a stand-alone IDN URL' do
-      let(:text) { 'https://nic.みんな/' }
-
-      it 'matches the full URL' do
-        is_expected.to include 'href="https://nic.みんな/"'
-      end
-
-      it 'has display URL' do
-        is_expected.to include '<span class="">nic.みんな/</span>'
-      end
-    end
-
-    context 'given a URL with a trailing period' do
-      let(:text) { 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ' }
-
-      it 'matches the full URL but not the period' do
-        is_expected.to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"'
-      end
-    end
-
-    context 'given a URL enclosed with parentheses' do
-      let(:text) { '(http://google.com/)' }
-
-      it 'matches the full URL but not the parentheses' do
-        is_expected.to include 'href="http://google.com/"'
-      end
-    end
-
-    context 'given a URL with a trailing exclamation point' do
-      let(:text) { 'http://www.google.com!' }
-
-      it 'matches the full URL but not the exclamation point' do
-        is_expected.to include 'href="http://www.google.com"'
-      end
-    end
-
-    context 'given a URL with a trailing single quote' do
-      let(:text) { "http://www.google.com'" }
-
-      it 'matches the full URL but not the single quote' do
-        is_expected.to include 'href="http://www.google.com"'
-      end
-    end
-
-    context 'given a URL with a trailing angle bracket' do
-      let(:text) { 'http://www.google.com>' }
-
-      it 'matches the full URL but not the angle bracket' do
-        is_expected.to include 'href="http://www.google.com"'
-      end
-    end
-
-    context 'given a URL with a query string' do
-      context 'with escaped unicode character' do
-        let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' }
-
-        it 'matches the full URL' do
-          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;q=autolink"'
-        end
-      end
-
-      context 'with unicode character' do
-        let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓&q=autolink' }
-
-        it 'matches the full URL' do
-          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&amp;q=autolink"'
-        end
-      end
-
-      context 'with unicode character at the end' do
-        let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓' }
-
-        it 'matches the full URL' do
-          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"'
-        end
-      end
-
-      context 'with escaped and not escaped unicode characters' do
-        let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink' }
-
-        it 'preserves escaped unicode characters' do
-          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;utf81=✓&amp;q=autolink"'
-        end
-      end
-    end
-
-    context 'given a URL with parentheses in it' do
-      let(:text) { 'https://en.wikipedia.org/wiki/Diaspora_(software)' }
-
-      it 'matches the full URL' do
-        is_expected.to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"'
-      end
-    end
-
-    context 'given a URL in quotation marks' do
-      let(:text) { '"https://example.com/"' }
-
-      it 'does not match the quotation marks' do
-        is_expected.to include 'href="https://example.com/"'
-      end
-    end
-
-    context 'given a URL in angle brackets' do
-      let(:text) { '<https://example.com/>' }
-
-      it 'does not match the angle brackets' do
-        is_expected.to include 'href="https://example.com/"'
-      end
-    end
-
-    context 'given a URL with Japanese path string' do
-      let(:text) { 'https://ja.wikipedia.org/wiki/日本' }
-
-      it 'matches the full URL' do
-        is_expected.to include 'href="https://ja.wikipedia.org/wiki/日本"'
-      end
-    end
-
-    context 'given a URL with Korean path string' do
-      let(:text) { 'https://ko.wikipedia.org/wiki/대한민국' }
-
-      it 'matches the full URL' do
-        is_expected.to include 'href="https://ko.wikipedia.org/wiki/대한민국"'
-      end
-    end
-
-    context 'given a URL with a full-width space' do
-      let(:text) { 'https://example.com/ abc123' }
-
-      it 'does not match the full-width space' do
-        is_expected.to include 'href="https://example.com/"'
-      end
-    end
-
-    context 'given a URL in Japanese quotation marks' do
-      let(:text) { '「[https://example.org/」' }
-
-      it 'does not match the quotation marks' do
-        is_expected.to include 'href="https://example.org/"'
-      end
-    end
-
-    context 'given a URL with Simplified Chinese path string' do
-      let(:text) { 'https://baike.baidu.com/item/中华人民共和国' }
-
-      it 'matches the full URL' do
-        is_expected.to include 'href="https://baike.baidu.com/item/中华人民共和国"'
-      end
-    end
-
-    context 'given a URL with Traditional Chinese path string' do
-      let(:text) { 'https://zh.wikipedia.org/wiki/臺灣' }
-
-      it 'matches the full URL' do
-        is_expected.to include 'href="https://zh.wikipedia.org/wiki/臺灣"'
-      end
-    end
-
-    context 'given a URL containing unsafe code (XSS attack, visible part)' do
-      let(:text) { %q{http://example.com/b<del>b</del>} }
-
-      it 'does not include the HTML in the URL' do
-        is_expected.to include '"http://example.com/b"'
-      end
-
-      it 'escapes the HTML' do
-        is_expected.to include '&lt;del&gt;b&lt;/del&gt;'
-      end
-    end
-
-    context 'given a URL containing unsafe code (XSS attack, invisible part)' do
-      let(:text) { %q{http://example.com/blahblahblahblah/a<script>alert("Hello")</script>} }
-
-      it 'does not include the HTML in the URL' do
-        is_expected.to include '"http://example.com/blahblahblahblah/a"'
-      end
-
-      it 'escapes the HTML' do
-        is_expected.to include '&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;'
-      end
-    end
-
-    context 'given text containing HTML code (script tag)' do
-      let(:text) { '<script>alert("Hello")</script>' }
-
-      it 'escapes the HTML' do
-        is_expected.to include '<p>&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;</p>'
-      end
-    end
-
-    context 'given text containing HTML (XSS attack)' do
-      let(:text) { %q{<img src="javascript:alert('XSS');">} }
-
-      it 'escapes the HTML' do
-        is_expected.to include '<p>&lt;img src=&quot;javascript:alert(&apos;XSS&apos;);&quot;&gt;</p>'
-      end
-    end
-
-    context 'given an invalid URL' do
-      let(:text) { 'http://www\.google\.com' }
-
-      it 'outputs the raw URL' do
-        is_expected.to eq '<p>http://www\.google\.com</p>'
-      end
-    end
-
-    context 'given text containing a hashtag' do
-      let(:text)  { '#hashtag' }
-
-      it 'creates a hashtag link' do
-        is_expected.to include '/tags/hashtag" class="mention hashtag" rel="tag">#<span>hashtag</span></a>'
-      end
-    end
-
-    context 'given text containing a hashtag with Unicode chars' do
-      let(:text)  { '#hashtagタグ' }
-
-      it 'creates a hashtag link' do
-        is_expected.to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#<span>hashtagタグ</span></a>'
-      end
-    end
-
-    context 'given a stand-alone xmpp: URI' do
-      let(:text) { 'xmpp:user@instance.com' }
-
-      it 'matches the full URI' do
-        is_expected.to include 'href="xmpp:user@instance.com"'
-      end
-    end
-
-    context 'given a an xmpp: URI with a query-string' do
-      let(:text) { 'please join xmpp:muc@instance.com?join right now' }
-
-      it 'matches the full URI' 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
-    subject { Formatter.instance.format_spoiler(status) }
-
-    context 'given a post containing plain text' do
-      let(:status) { Fabricate(:status, text: 'text', spoiler_text: 'Secret!', uri: nil) }
-
-      it 'Returns the spoiler text' do
-        is_expected.to eq 'Secret!'
-      end
-    end
-
-    context 'given a post with an emoji shortcode at the start' do
-      let!(:emoji) { Fabricate(:custom_emoji) }
-      let(:status) { Fabricate(:status, text: 'text', spoiler_text: ':coolcat: Secret!', uri: nil) }
-      let(:text) { ':coolcat: Beep boop' }
-
-      it 'converts the shortcode to an image tag' do
-        is_expected.to match(/<img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
-      end
-    end
-  end
-
-  describe '#format' do
-    subject { Formatter.instance.format(status) }
-
-    context 'given a post with local status' do
-      context 'given a reblogged post' do
-        let(:reblog) { Fabricate(:status, account: local_account, text: 'Hello world', uri: nil) }
-        let(:status) { Fabricate(:status, reblog: reblog) }
-
-        it 'returns original status with credit to its author' do
-          is_expected.to include 'RT <span class="h-card"><a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span> Hello world'
-        end
-      end
-
-      context 'given a post containing plain text' do
-        let(:status) { Fabricate(:status, text: 'text', uri: nil) }
-
-        it 'paragraphizes the text' do
-          is_expected.to eq '<p>text</p>'
-        end
-      end
-
-      context 'given a post containing line feeds' do
-        let(:status) { Fabricate(:status, text: "line\nfeed", uri: nil) }
-
-        it 'removes line feeds' do
-          is_expected.not_to include "\n"
-        end
-      end
-
-      context 'given a post containing linkable mentions' do
-        let(:status) { Fabricate(:status, mentions: [ Fabricate(:mention, account: local_account) ], text: '@alice') }
-
-        it 'creates a mention link' do
-          is_expected.to include '<a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span>'
-        end
-      end
-
-      context 'given a post containing unlinkable mentions' do
-        let(:status) { Fabricate(:status, text: '@alice', uri: nil) }
-
-        it 'does not create a mention link' do
-          is_expected.to include '@alice'
-        end
-      end
-
-      context do
-        let(:content_type) { 'text/plain' }
-
-        subject do
-          status = Fabricate(:status, text: text, content_type: content_type, uri: nil)
-          Formatter.instance.format(status)
-        end
-
-        context 'given an invalid URL (invalid port)' do
-          let(:text) { 'https://foo.bar:X/' }
-          let(:content_type) { 'text/markdown' }
-
-          it 'outputs the raw URL' do
-            is_expected.to eq '<p>https://foo.bar:X/</p>'
-          end
-        end
-
-        include_examples 'encode and link URLs'
-      end
-
-      context 'given a post with custom_emojify option' do
-        let!(:emoji) { Fabricate(:custom_emoji) }
-        let(:status) { Fabricate(:status, account: local_account, text: text) }
-
-        subject { Formatter.instance.format(status, custom_emojify: true) }
-
-        context 'given a post with an emoji shortcode at the start' do
-          let(:text) { ':coolcat: Beep boop' }
-
-          it 'converts the shortcode to an image tag' do
-            is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
-          end
-        end
-
-        context 'given a post with an emoji shortcode in the middle' do
-          let(:text) { 'Beep :coolcat: boop' }
-
-          it 'converts the shortcode to an image tag' do
-            is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
-          end
-        end
-
-        context 'given a post with concatenated emoji shortcodes' do
-          let(:text) { ':coolcat::coolcat:' }
-
-          it 'does not touch the shortcodes' do
-            is_expected.to match(/:coolcat::coolcat:/)
-          end
-        end
-
-        context 'given a post with an emoji shortcode at the end' do
-          let(:text) { 'Beep boop :coolcat:' }
-
-          it 'converts the shortcode to an image tag' do
-            is_expected.to match(/boop <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
-          end
-        end
-      end
-    end
-
-    context 'given a post with remote status' do
-      let(:status) { Fabricate(:status, account: remote_account, text: 'Beep boop') }
-
-      it 'reformats the post' do
-        is_expected.to eq 'Beep boop'
-      end
-
-      context 'given a post with custom_emojify option' do
-        let!(:emoji) { Fabricate(:custom_emoji, domain: remote_account.domain) }
-        let(:status) { Fabricate(:status, account: remote_account, text: text) }
-
-        subject { Formatter.instance.format(status, custom_emojify: true) }
-
-        context 'given a post with an emoji shortcode at the start' do
-          let(:text) { '<p>:coolcat: Beep boop<br />' }
-
-          it 'converts the shortcode to an image tag' do
-            is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
-          end
-        end
-
-        context 'given a post with an emoji shortcode in the middle' do
-          let(:text) { '<p>Beep :coolcat: boop</p>' }
-
-          it 'converts the shortcode to an image tag' do
-            is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
-          end
-        end
-
-        context 'given a post with concatenated emoji' do
-          let(:text) { '<p>:coolcat::coolcat:</p>' }
-
-          it 'does not touch the shortcodes' do
-            is_expected.to match(/<p>:coolcat::coolcat:<\/p>/)
-          end
-        end
-
-        context 'given a post with an emoji shortcode at the end' do
-          let(:text) { '<p>Beep boop<br />:coolcat:</p>' }
-
-          it 'converts the shortcode to an image tag' do
-            is_expected.to match(/<br><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
-          end
-        end
-      end
-    end
-  end
-
-  describe '#reformat' do
-    subject { Formatter.instance.reformat(text) }
-
-    context 'given a post containing plain text' do
-      let(:text) { 'Beep boop' }
-
-      it 'keeps the plain text' do
-        is_expected.to include 'Beep boop'
-      end
-    end
-
-    context 'given a post containing script tags' do
-      let(:text) { '<script>alert("Hello")</script>' }
-
-      it 'strips the scripts' do
-        is_expected.to_not include '<script>alert("Hello")</script>'
-      end
-    end
-
-    context 'given a post containing malicious classes' do
-      let(:text) { '<span class="mention	status__content__spoiler-link">Show more</span>' }
-
-      it 'strips the malicious classes' do
-        is_expected.to_not include 'status__content__spoiler-link'
-      end
-    end
-  end
-
-  describe '#plaintext' do
-    subject { Formatter.instance.plaintext(status) }
-
-    context 'given a post with local status' do
-      let(:status) { Fabricate(:status, text: '<p>a text by a nerd who uses an HTML tag in text</p>', content_type: content_type, uri: nil) }
-      let(:content_type) { 'text/plain' }
-
-      it 'returns the raw text' do
-        is_expected.to eq '<p>a text by a nerd who uses an HTML tag in text</p>'
-      end
-    end
-
-    context 'given a post with remote status' do
-      let(:status) { Fabricate(:status, account: remote_account, text: '<script>alert("Hello")</script>') }
-
-      it 'returns tag-stripped text' do
-        is_expected.to eq ''
-      end
-    end
-  end
-
-  describe '#simplified_format' do
-    subject { Formatter.instance.simplified_format(account) }
-
-    context 'given a post with local status' do
-      let(:account) { Fabricate(:account, domain: nil, note: text) }
-
-      context 'given a post containing linkable mentions for local accounts' do
-        let(:text) { '@alice' }
-
-        before { local_account }
-
-        it 'creates a mention link' do
-          is_expected.to eq '<p><span class="h-card"><a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span></p>'
-        end
-      end
-
-      context 'given a post containing linkable mentions for remote accounts' do
-        let(:text) { '@bob@remote.test' }
-
-        before { remote_account }
-
-        it 'creates a mention link' do
-          is_expected.to eq '<p><span class="h-card"><a href="https://remote.test/" class="u-url mention">@<span>bob</span></a></span></p>'
-        end
-      end
-
-      context 'given a post containing unlinkable mentions' do
-        let(:text) { '@alice' }
-
-        it 'does not create a mention link' do
-          is_expected.to eq '<p>@alice</p>'
-        end
-      end
-
-      context 'given a post with custom_emojify option' do
-        let!(:emoji) { Fabricate(:custom_emoji) }
-
-        before { account.note = text }
-        subject { Formatter.instance.simplified_format(account, custom_emojify: true) }
-
-        context 'given a post with an emoji shortcode at the start' do
-          let(:text) { ':coolcat: Beep boop' }
-
-          it 'converts the shortcode to an image tag' do
-            is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
-          end
-        end
-
-        context 'given a post with an emoji shortcode in the middle' do
-          let(:text) { 'Beep :coolcat: boop' }
-
-          it 'converts the shortcode to an image tag' do
-            is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
-          end
-        end
-
-        context 'given a post with concatenated emoji shortcodes' do
-          let(:text) { ':coolcat::coolcat:' }
-
-          it 'does not touch the shortcodes' do
-            is_expected.to match(/:coolcat::coolcat:/)
-          end
-        end
-
-        context 'given a post with an emoji shortcode at the end' do
-          let(:text) { 'Beep boop :coolcat:' }
-
-          it 'converts the shortcode to an image tag' do
-            is_expected.to match(/boop <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
-          end
-        end
-      end
-
-      include_examples 'encode and link URLs'
-    end
-
-    context 'given a post with remote status' do
-      let(:text) { '<script>alert("Hello")</script>' }
-      let(:account) { Fabricate(:account, domain: 'remote', note: text) }
-
-      it 'reformats' do
-        is_expected.to_not include '<script>alert("Hello")</script>'
-      end
-
-      context 'with custom_emojify option' do
-        let!(:emoji) { Fabricate(:custom_emoji, domain: remote_account.domain) }
-
-        before { remote_account.note = text }
-
-        subject { Formatter.instance.simplified_format(remote_account, custom_emojify: true) }
-
-        context 'given a post with an emoji shortcode at the start' do
-          let(:text) { '<p>:coolcat: Beep boop<br />' }
-
-          it 'converts shortcode to image tag' do
-            is_expected.to match(/<p><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
-          end
-        end
-
-        context 'given a post with an emoji shortcode in the middle' do
-          let(:text) { '<p>Beep :coolcat: boop</p>' }
-
-          it 'converts shortcode to image tag' do
-            is_expected.to match(/Beep <img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
-          end
-        end
-
-        context 'given a post with concatenated emoji shortcodes' do
-          let(:text) { '<p>:coolcat::coolcat:</p>' }
-
-          it 'does not touch the shortcodes' do
-            is_expected.to match(/<p>:coolcat::coolcat:<\/p>/)
-          end
-        end
-
-        context 'given a post with an emoji shortcode at the end' do
-          let(:text) { '<p>Beep boop<br />:coolcat:</p>' }
-
-          it 'converts shortcode to image tag' do
-            is_expected.to match(/<br><img draggable="false" class="emojione custom-emoji" alt=":coolcat:"/)
-          end
-        end
-      end
-    end
-  end
-
-  describe '#sanitize' do
-    let(:html) { '<script>alert("Hello")</script>' }
-
-    subject { Formatter.instance.sanitize(html, Sanitize::Config::MASTODON_STRICT) }
-
-    it 'sanitizes' do
-      is_expected.to eq ''
-    end
-  end
-end
diff --git a/spec/lib/html_aware_formatter_spec.rb b/spec/lib/html_aware_formatter_spec.rb
new file mode 100644
index 000000000..18d23abf5
--- /dev/null
+++ b/spec/lib/html_aware_formatter_spec.rb
@@ -0,0 +1,44 @@
+require 'rails_helper'
+
+RSpec.describe HtmlAwareFormatter do
+  describe '#to_s' do
+    subject { described_class.new(text, local).to_s }
+
+    context 'when local' do
+      let(:local) { true }
+      let(:text) { 'Foo bar' }
+
+      it 'returns formatted text' do
+        is_expected.to eq '<p>Foo bar</p>'
+      end
+    end
+
+    context 'when remote' do
+      let(:local) { false }
+
+      context 'given plain text' do
+        let(:text) { 'Beep boop' }
+
+        it 'keeps the plain text' do
+          is_expected.to include 'Beep boop'
+        end
+      end
+
+      context 'given text containing script tags' do
+        let(:text) { '<script>alert("Hello")</script>' }
+
+        it 'strips the scripts' do
+          is_expected.to_not include '<script>alert("Hello")</script>'
+        end
+      end
+
+      context 'given text containing malicious classes' do
+        let(:text) { '<span class="mention  status__content__spoiler-link">Show more</span>' }
+
+        it 'strips the malicious classes' do
+          is_expected.to_not include 'status__content__spoiler-link'
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/link_details_extractor_spec.rb b/spec/lib/link_details_extractor_spec.rb
index 84bb4579c..7ea867c61 100644
--- a/spec/lib/link_details_extractor_spec.rb
+++ b/spec/lib/link_details_extractor_spec.rb
@@ -25,6 +25,14 @@ RSpec.describe LinkDetailsExtractor do
         expect(subject.canonical_url).to eq 'https://foo.com/article'
       end
     end
+
+    context 'when canonical URL is set to "null"' do
+      let(:html) { '<!doctype html><link rel="canonical" href="null" />' }
+
+      it 'ignores the canonical URLs' do
+        expect(subject.canonical_url).to eq original_url
+      end
+    end
   end
 
   context 'when structured data is present' do
diff --git a/spec/lib/plain_text_formatter_spec.rb b/spec/lib/plain_text_formatter_spec.rb
new file mode 100644
index 000000000..c3d0ee630
--- /dev/null
+++ b/spec/lib/plain_text_formatter_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+RSpec.describe PlainTextFormatter do
+  describe '#to_s' do
+    subject { described_class.new(status.text, status.local?).to_s }
+
+    context 'given a post with local status' do
+      let(:status) { Fabricate(:status, text: '<p>a text by a nerd who uses an HTML tag in text</p>', uri: nil) }
+
+      it 'returns the raw text' do
+        is_expected.to eq '<p>a text by a nerd who uses an HTML tag in text</p>'
+      end
+    end
+
+    context 'given a post with remote status' do
+      let(:remote_account) { Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') }
+      let(:status) { Fabricate(:status, account: remote_account, text: '<p>Hello</p><script>alert("Hello")</script>') }
+
+      it 'returns tag-stripped text' do
+        is_expected.to eq 'Hello'
+      end
+    end
+  end
+end
diff --git a/spec/lib/rss/serializer_spec.rb b/spec/lib/rss/serializer_spec.rb
index 0364d13de..1da45d302 100644
--- a/spec/lib/rss/serializer_spec.rb
+++ b/spec/lib/rss/serializer_spec.rb
@@ -13,13 +13,6 @@ describe RSS::Serializer do
 
     subject { RSS::Serializer.new.send(:status_title, status) }
 
-    context 'if destroyed?' do
-      it 'returns "#{account.acct} deleted status"' do
-        status.destroy!
-        expect(subject).to eq "#{account.acct} deleted status"
-      end
-    end
-
     context 'on a toot with long text' do
       let(:text) { "This toot's text is longer than the allowed number of characters" }
 
diff --git a/spec/lib/sanitize_config_spec.rb b/spec/lib/sanitize_config_spec.rb
index 8bcffb2e5..dc6418e5b 100644
--- a/spec/lib/sanitize_config_spec.rb
+++ b/spec/lib/sanitize_config_spec.rb
@@ -41,18 +41,8 @@ describe Sanitize::Config do
     end
   end
 
-  describe '::MASTODON_STRICT' do
-    subject { Sanitize::Config::MASTODON_STRICT }
-
-    it_behaves_like 'common HTML sanitization'
-
-    it 'keeps a with href and rel tag' do
-      expect(Sanitize.fragment('<a href="http://example.com" rel="tag">Test</a>', subject)).to eq '<a href="http://example.com" rel="tag nofollow noopener noreferrer" target="_blank">Test</a>'
-    end
-  end
-
-  describe '::MASTODON_STRICT with outgoing toots' do
-    subject { Sanitize::Config::MASTODON_STRICT.merge(outgoing: true) }
+  describe '::MASTODON_OUTGOING' do
+    subject { Sanitize::Config::MASTODON_OUTGOING }
 
     around do |example|
       original_web_domain = Rails.configuration.x.web_domain
@@ -62,9 +52,9 @@ describe Sanitize::Config do
 
     it_behaves_like 'common HTML sanitization'
 
-    it 'keeps a with href and rel tag, not adding to rel if url is local' do
+    it 'keeps a with href and rel tag, not adding to rel or target if url is local' do
       Rails.configuration.x.web_domain = 'domain.test'
-      expect(Sanitize.fragment('<a href="http://domain.test/tags/foo" rel="tag">Test</a>', subject)).to eq '<a href="http://domain.test/tags/foo" rel="tag" target="_blank">Test</a>'
+      expect(Sanitize.fragment('<a href="http://domain.test/tags/foo" rel="tag">Test</a>', subject)).to eq '<a href="http://domain.test/tags/foo" rel="tag">Test</a>'
     end
   end
 end
diff --git a/spec/lib/text_formatter_spec.rb b/spec/lib/text_formatter_spec.rb
new file mode 100644
index 000000000..52a9d2498
--- /dev/null
+++ b/spec/lib/text_formatter_spec.rb
@@ -0,0 +1,313 @@
+require 'rails_helper'
+
+RSpec.describe TextFormatter do
+  describe '#to_s' do
+    let(:preloaded_accounts) { nil }
+
+    subject { described_class.new(text, preloaded_accounts: preloaded_accounts).to_s }
+
+    context 'given text containing plain text' do
+      let(:text) { 'text' }
+
+      it 'paragraphizes the text' do
+        is_expected.to eq '<p>text</p>'
+      end
+    end
+
+    context 'given text containing line feeds' do
+      let(:text) { "line\nfeed" }
+
+      it 'removes line feeds' do
+        is_expected.not_to include "\n"
+      end
+    end
+
+    context 'given text containing linkable mentions' do
+      let(:preloaded_accounts) { [Fabricate(:account, username: 'alice')] }
+      let(:text) { '@alice' }
+
+      it 'creates a mention link' do
+        is_expected.to include '<a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span>'
+      end
+    end
+
+    context 'given text containing unlinkable mentions' do
+      let(:preloaded_accounts) { [] }
+      let(:text) { '@alice' }
+
+      it 'does not create a mention link' do
+        is_expected.to include '@alice'
+      end
+    end
+
+    context 'given a stand-alone medium URL' do
+      let(:text) { 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4' }
+
+      it 'matches the full URL' do
+        is_expected.to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"'
+      end
+    end
+
+    context 'given a stand-alone google URL' do
+      let(:text) { 'http://google.com' }
+
+      it 'matches the full URL' do
+        is_expected.to include 'href="http://google.com"'
+      end
+    end
+
+    context 'given a stand-alone URL with a newer TLD' do
+      let(:text) { 'http://example.gay' }
+
+      it 'matches the full URL' do
+        is_expected.to include 'href="http://example.gay"'
+      end
+    end
+
+    context 'given a stand-alone IDN URL' do
+      let(:text) { 'https://nic.みんな/' }
+
+      it 'matches the full URL' do
+        is_expected.to include 'href="https://nic.みんな/"'
+      end
+
+      it 'has display URL' do
+        is_expected.to include '<span class="">nic.みんな/</span>'
+      end
+    end
+
+    context 'given a URL with a trailing period' do
+      let(:text) { 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ' }
+
+      it 'matches the full URL but not the period' do
+        is_expected.to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"'
+      end
+    end
+
+    context 'given a URL enclosed with parentheses' do
+      let(:text) { '(http://google.com/)' }
+
+      it 'matches the full URL but not the parentheses' do
+        is_expected.to include 'href="http://google.com/"'
+      end
+    end
+
+    context 'given a URL with a trailing exclamation point' do
+      let(:text) { 'http://www.google.com!' }
+
+      it 'matches the full URL but not the exclamation point' do
+        is_expected.to include 'href="http://www.google.com"'
+      end
+    end
+
+    context 'given a URL with a trailing single quote' do
+      let(:text) { "http://www.google.com'" }
+
+      it 'matches the full URL but not the single quote' do
+        is_expected.to include 'href="http://www.google.com"'
+      end
+    end
+
+    context 'given a URL with a trailing angle bracket' do
+      let(:text) { 'http://www.google.com>' }
+
+      it 'matches the full URL but not the angle bracket' do
+        is_expected.to include 'href="http://www.google.com"'
+      end
+    end
+
+    context 'given a URL with a query string' do
+      context 'with escaped unicode character' do
+        let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' }
+
+        it 'matches the full URL' do
+          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;q=autolink"'
+        end
+      end
+
+      context 'with unicode character' do
+        let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓&q=autolink' }
+
+        it 'matches the full URL' do
+          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&amp;q=autolink"'
+        end
+      end
+
+      context 'with unicode character at the end' do
+        let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓' }
+
+        it 'matches the full URL' do
+          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"'
+        end
+      end
+
+      context 'with escaped and not escaped unicode characters' do
+        let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink' }
+
+        it 'preserves escaped unicode characters' do
+          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;utf81=✓&amp;q=autolink"'
+        end
+      end
+    end
+
+    context 'given a URL with parentheses in it' do
+      let(:text) { 'https://en.wikipedia.org/wiki/Diaspora_(software)' }
+
+      it 'matches the full URL' do
+        is_expected.to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"'
+      end
+    end
+
+    context 'given a URL in quotation marks' do
+      let(:text) { '"https://example.com/"' }
+
+      it 'does not match the quotation marks' do
+        is_expected.to include 'href="https://example.com/"'
+      end
+    end
+
+    context 'given a URL in angle brackets' do
+      let(:text) { '<https://example.com/>' }
+
+      it 'does not match the angle brackets' do
+        is_expected.to include 'href="https://example.com/"'
+      end
+    end
+
+    context 'given a URL with Japanese path string' do
+      let(:text) { 'https://ja.wikipedia.org/wiki/日本' }
+
+      it 'matches the full URL' do
+        is_expected.to include 'href="https://ja.wikipedia.org/wiki/日本"'
+      end
+    end
+
+    context 'given a URL with Korean path string' do
+      let(:text) { 'https://ko.wikipedia.org/wiki/대한민국' }
+
+      it 'matches the full URL' do
+        is_expected.to include 'href="https://ko.wikipedia.org/wiki/대한민국"'
+      end
+    end
+
+    context 'given a URL with a full-width space' do
+      let(:text) { 'https://example.com/ abc123' }
+
+      it 'does not match the full-width space' do
+        is_expected.to include 'href="https://example.com/"'
+      end
+    end
+
+    context 'given a URL in Japanese quotation marks' do
+      let(:text) { '「[https://example.org/」' }
+
+      it 'does not match the quotation marks' do
+        is_expected.to include 'href="https://example.org/"'
+      end
+    end
+
+    context 'given a URL with Simplified Chinese path string' do
+      let(:text) { 'https://baike.baidu.com/item/中华人民共和国' }
+
+      it 'matches the full URL' do
+        is_expected.to include 'href="https://baike.baidu.com/item/中华人民共和国"'
+      end
+    end
+
+    context 'given a URL with Traditional Chinese path string' do
+      let(:text) { 'https://zh.wikipedia.org/wiki/臺灣' }
+
+      it 'matches the full URL' do
+        is_expected.to include 'href="https://zh.wikipedia.org/wiki/臺灣"'
+      end
+    end
+
+    context 'given a URL containing unsafe code (XSS attack, visible part)' do
+      let(:text) { %q{http://example.com/b<del>b</del>} }
+
+      it 'does not include the HTML in the URL' do
+        is_expected.to include '"http://example.com/b"'
+      end
+
+      it 'escapes the HTML' do
+        is_expected.to include '&lt;del&gt;b&lt;/del&gt;'
+      end
+    end
+
+    context 'given a URL containing unsafe code (XSS attack, invisible part)' do
+      let(:text) { %q{http://example.com/blahblahblahblah/a<script>alert("Hello")</script>} }
+
+      it 'does not include the HTML in the URL' do
+        is_expected.to include '"http://example.com/blahblahblahblah/a"'
+      end
+
+      it 'escapes the HTML' do
+        is_expected.to include '&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;'
+      end
+    end
+
+    context 'given text containing HTML code (script tag)' do
+      let(:text) { '<script>alert("Hello")</script>' }
+
+      it 'escapes the HTML' do
+        is_expected.to include '<p>&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;</p>'
+      end
+    end
+
+    context 'given text containing HTML (XSS attack)' do
+      let(:text) { %q{<img src="javascript:alert('XSS');">} }
+
+      it 'escapes the HTML' do
+        is_expected.to include '<p>&lt;img src=&quot;javascript:alert(&#39;XSS&#39;);&quot;&gt;</p>'
+      end
+    end
+
+    context 'given an invalid URL' do
+      let(:text) { 'http://www\.google\.com' }
+
+      it 'outputs the raw URL' do
+        is_expected.to eq '<p>http://www\.google\.com</p>'
+      end
+    end
+
+    context 'given text containing a hashtag' do
+      let(:text)  { '#hashtag' }
+
+      it 'creates a hashtag link' do
+        is_expected.to include '/tags/hashtag" class="mention hashtag" rel="tag">#<span>hashtag</span></a>'
+      end
+    end
+
+    context 'given text containing a hashtag with Unicode chars' do
+      let(:text)  { '#hashtagタグ' }
+
+      it 'creates a hashtag link' do
+        is_expected.to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#<span>hashtagタグ</span></a>'
+      end
+    end
+
+    context 'given text with a stand-alone xmpp: URI' do
+      let(:text) { 'xmpp:user@instance.com' }
+
+      it 'matches the full URI' do
+        is_expected.to include 'href="xmpp:user@instance.com"'
+      end
+    end
+
+    context 'given text with an xmpp: URI with a query-string' do
+      let(:text) { 'please join xmpp:muc@instance.com?join right now' }
+
+      it 'matches the full URI' 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
+end
diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb
index 7360b23cf..cbd9a09c5 100644
--- a/spec/models/media_attachment_spec.rb
+++ b/spec/models/media_attachment_spec.rb
@@ -186,14 +186,6 @@ RSpec.describe MediaAttachment, type: :model do
     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')
-
-      expect(media.description.size).to be <= 1_500
-    end
-  end
-
   describe 'size limit validation' do
     it 'rejects video files that are too large' do
       stub_const 'MediaAttachment::IMAGE_LIMIT', 100.megabytes
diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb
index 788c7c9d9..f87adcae1 100644
--- a/spec/services/activitypub/process_status_update_service_spec.rb
+++ b/spec/services/activitypub/process_status_update_service_spec.rb
@@ -46,6 +46,29 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
       expect(status.reload.spoiler_text).to eq 'Show more'
     end
 
+    context 'with no changes at all' do
+      let(:payload) do
+        {
+          '@context': 'https://www.w3.org/ns/activitystreams',
+          id: 'foo',
+          type: 'Note',
+          content: 'Hello world',
+        }
+      end
+
+      before do
+        subject.call(status, json)
+      end
+
+      it 'does not create any edits' do
+        expect(status.reload.edits).to be_empty
+      end
+
+      it 'does not mark status as edited' do
+        expect(status.edited?).to be false
+      end
+    end
+
     context 'with no changes and originally with no ordered_media_attachment_ids' do
       let(:payload) do
         {
@@ -61,8 +84,12 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
         subject.call(status, json)
       end
 
-      it 'does not record an update' do
-        expect(status.reload.edited?).to be false
+      it 'does not create any edits' do
+        expect(status.reload.edits).to be_empty
+      end
+
+      it 'does not mark status as edited' do
+        expect(status.edited?).to be false
       end
     end
 
diff --git a/spec/services/after_block_service_spec.rb b/spec/services/after_block_service_spec.rb
index fe5b26b2b..c09425d7c 100644
--- a/spec/services/after_block_service_spec.rb
+++ b/spec/services/after_block_service_spec.rb
@@ -1,9 +1,7 @@
 require 'rails_helper'
 
 RSpec.describe AfterBlockService, type: :service do
-  subject do
-    -> { described_class.new.call(account, target_account) }
-  end
+  subject { described_class.new.call(account, target_account) }
 
   let(:account)              { Fabricate(:account) }
   let(:target_account)       { Fabricate(:account) }
@@ -24,7 +22,7 @@ RSpec.describe AfterBlockService, type: :service do
       FeedManager.instance.push_to_home(account, other_account_status)
       FeedManager.instance.push_to_home(account, other_account_reblog)
 
-      is_expected.to change {
+      expect { subject }.to change {
         Redis.current.zrange(home_timeline_key, 0, -1)
       }.from([status.id.to_s, other_account_status.id.to_s, other_account_reblog.id.to_s]).to([other_account_status.id.to_s])
     end
@@ -43,7 +41,7 @@ RSpec.describe AfterBlockService, type: :service do
       FeedManager.instance.push_to_list(list, other_account_status)
       FeedManager.instance.push_to_list(list, other_account_reblog)
 
-      is_expected.to change {
+      expect { subject }.to change {
         Redis.current.zrange(list_timeline_key, 0, -1)
       }.from([status.id.to_s, other_account_status.id.to_s, other_account_reblog.id.to_s]).to([other_account_status.id.to_s])
     end
diff --git a/spec/services/delete_account_service_spec.rb b/spec/services/delete_account_service_spec.rb
index 9c785fc17..1fbe4d07c 100644
--- a/spec/services/delete_account_service_spec.rb
+++ b/spec/services/delete_account_service_spec.rb
@@ -23,12 +23,10 @@ RSpec.describe DeleteAccountService, type: :service do
 
     let!(:account_note) { Fabricate(:account_note, account: account) }
 
-    subject do
-      -> { described_class.new.call(account) }
-    end
+    subject { described_class.new.call(account) }
 
     it 'deletes associated owned records' do
-      is_expected.to change {
+      expect { subject }.to change {
         [
           account.statuses,
           account.media_attachments,
@@ -43,7 +41,7 @@ RSpec.describe DeleteAccountService, type: :service do
     end
 
     it 'deletes associated target records' do
-      is_expected.to change {
+      expect { subject }.to change {
         [
           AccountPin.where(target_account: account),
         ].map(&:count)
@@ -51,7 +49,7 @@ RSpec.describe DeleteAccountService, type: :service do
     end
 
     it 'deletes associated target notifications' do
-      is_expected.to change {
+      expect { subject }.to change {
         [
           'poll', 'favourite', 'status', 'mention', 'follow'
         ].map { |type| Notification.where(type: type).count }
@@ -73,7 +71,7 @@ RSpec.describe DeleteAccountService, type: :service do
       let!(:local_follower) { Fabricate(:account) }
 
       it 'sends a delete actor activity to all known inboxes' do
-        subject.call
+        subject
         expect(a_request(:post, "https://alice.com/inbox")).to have_been_made.once
         expect(a_request(:post, "https://bob.com/inbox")).to have_been_made.once
       end
@@ -91,7 +89,7 @@ RSpec.describe DeleteAccountService, type: :service do
       let!(:local_follower) { Fabricate(:account) }
 
       it 'sends a reject follow to follower inboxes' do
-        subject.call
+        subject
         expect(a_request(:post, account.inbox_url)).to have_been_made.once
       end
     end
diff --git a/spec/services/mute_service_spec.rb b/spec/services/mute_service_spec.rb
index 4bb839b8d..bdec1c67b 100644
--- a/spec/services/mute_service_spec.rb
+++ b/spec/services/mute_service_spec.rb
@@ -1,9 +1,7 @@
 require 'rails_helper'
 
 RSpec.describe MuteService, type: :service do
-  subject do
-    -> { described_class.new.call(account, target_account) }
-  end
+  subject { described_class.new.call(account, target_account) }
 
   let(:account) { Fabricate(:account) }
   let(:target_account) { Fabricate(:account) }
@@ -21,45 +19,41 @@ RSpec.describe MuteService, type: :service do
       FeedManager.instance.push_to_home(account, status)
       FeedManager.instance.push_to_home(account, other_account_status)
 
-      is_expected.to change {
+      expect { subject }.to change {
         Redis.current.zrange(home_timeline_key, 0, -1)
       }.from([status.id.to_s, other_account_status.id.to_s]).to([other_account_status.id.to_s])
     end
   end
 
   it 'mutes account' do
-    is_expected.to change {
+    expect { subject }.to change {
       account.muting?(target_account)
     }.from(false).to(true)
   end
 
   context 'without specifying a notifications parameter' do
     it 'mutes notifications from the account' do
-      is_expected.to change {
+      expect { subject }.to change {
         account.muting_notifications?(target_account)
       }.from(false).to(true)
     end
   end
 
   context 'with a true notifications parameter' do
-    subject do
-      -> { described_class.new.call(account, target_account, notifications: true) }
-    end
+    subject { described_class.new.call(account, target_account, notifications: true) }
 
     it 'mutes notifications from the account' do
-      is_expected.to change {
+      expect { subject }.to change {
         account.muting_notifications?(target_account)
       }.from(false).to(true)
     end
   end
 
   context 'with a false notifications parameter' do
-    subject do
-      -> { described_class.new.call(account, target_account, notifications: false) }
-    end
+    subject { described_class.new.call(account, target_account, notifications: false) }
 
     it 'does not mute notifications from the account' do
-      is_expected.to_not change {
+      expect { subject }.to_not change {
         account.muting_notifications?(target_account)
       }.from(false)
     end
diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb
index 83e62ff36..67dd0483b 100644
--- a/spec/services/notify_service_spec.rb
+++ b/spec/services/notify_service_spec.rb
@@ -1,9 +1,7 @@
 require 'rails_helper'
 
 RSpec.describe NotifyService, type: :service do
-  subject do
-    -> { described_class.new.call(recipient, type, activity) }
-  end
+  subject { described_class.new.call(recipient, type, activity) }
 
   let(:user) { Fabricate(:user) }
   let(:recipient) { user.account }
@@ -11,42 +9,42 @@ RSpec.describe NotifyService, type: :service do
   let(:activity) { Fabricate(:follow, account: sender, target_account: recipient) }
   let(:type) { :follow }
 
-  it { is_expected.to change(Notification, :count).by(1) }
+  it { expect { subject }.to change(Notification, :count).by(1) }
 
   it 'does not notify when sender is blocked' do
     recipient.block!(sender)
-    is_expected.to_not change(Notification, :count)
+    expect { subject }.to_not change(Notification, :count)
   end
 
   it 'does not notify when sender is muted with hide_notifications' do
     recipient.mute!(sender, notifications: true)
-    is_expected.to_not change(Notification, :count)
+    expect { subject }.to_not change(Notification, :count)
   end
 
   it 'does notify when sender is muted without hide_notifications' do
     recipient.mute!(sender, notifications: false)
-    is_expected.to change(Notification, :count)
+    expect { subject }.to change(Notification, :count)
   end
 
   it 'does not notify when sender\'s domain is blocked' do
     recipient.block_domain!(sender.domain)
-    is_expected.to_not change(Notification, :count)
+    expect { subject }.to_not change(Notification, :count)
   end
 
   it 'does still notify when sender\'s domain is blocked but sender is followed' do
     recipient.block_domain!(sender.domain)
     recipient.follow!(sender)
-    is_expected.to change(Notification, :count)
+    expect { subject }.to change(Notification, :count)
   end
 
   it 'does not notify when sender is silenced and not followed' do
     sender.silence!
-    is_expected.to_not change(Notification, :count)
+    expect { subject }.to_not change(Notification, :count)
   end
 
   it 'does not notify when recipient is suspended' do
     recipient.suspend!
-    is_expected.to_not change(Notification, :count)
+    expect { subject }.to_not change(Notification, :count)
   end
   
   context 'for direct messages' do
@@ -61,7 +59,7 @@ RSpec.describe NotifyService, type: :service do
       let(:enabled) { true }
 
       it 'does not notify' do
-        is_expected.to_not change(Notification, :count)
+        expect { subject }.to_not change(Notification, :count)
       end
 
       context 'if the message chain is initiated by recipient, but is not direct message' do
@@ -70,7 +68,7 @@ RSpec.describe NotifyService, type: :service do
         let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) }
 
         it 'does not notify' do
-          is_expected.to_not change(Notification, :count)
+          expect { subject }.to_not change(Notification, :count)
         end
       end
 
@@ -81,7 +79,7 @@ RSpec.describe NotifyService, type: :service do
         let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: dummy_reply)) }
 
         it 'does not notify' do
-          is_expected.to_not change(Notification, :count)
+          expect { subject }.to_not change(Notification, :count)
         end
       end
 
@@ -91,7 +89,7 @@ RSpec.describe NotifyService, type: :service do
         let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) }
 
         it 'does notify' do
-          is_expected.to change(Notification, :count)
+          expect { subject }.to change(Notification, :count)
         end
       end
     end
@@ -100,7 +98,7 @@ RSpec.describe NotifyService, type: :service do
       let(:enabled) { false }
 
       it 'does notify' do
-        is_expected.to change(Notification, :count)
+        expect { subject }.to change(Notification, :count)
       end
     end
   end
@@ -112,17 +110,17 @@ RSpec.describe NotifyService, type: :service do
 
     it 'shows reblogs by default' do
       recipient.follow!(sender)
-      is_expected.to change(Notification, :count)
+      expect { subject }.to change(Notification, :count)
     end
 
     it 'shows reblogs when explicitly enabled' do
       recipient.follow!(sender, reblogs: true)
-      is_expected.to change(Notification, :count)
+      expect { subject }.to change(Notification, :count)
     end
 
     it 'shows reblogs when disabled' do
       recipient.follow!(sender, reblogs: false)
-      is_expected.to change(Notification, :count)
+      expect { subject }.to change(Notification, :count)
     end
   end
 
@@ -134,12 +132,12 @@ RSpec.describe NotifyService, type: :service do
 
     it 'does not notify when conversation is muted' do
       recipient.mute_conversation!(activity.status.conversation)
-      is_expected.to_not change(Notification, :count)
+      expect { subject }.to_not change(Notification, :count)
     end
 
     it 'does not notify when it is a reply to a blocked user' do
       recipient.block!(asshole)
-      is_expected.to_not change(Notification, :count)
+      expect { subject }.to_not change(Notification, :count)
     end
   end
 
@@ -147,7 +145,7 @@ RSpec.describe NotifyService, type: :service do
     let(:sender) { recipient }
 
     it 'does not notify when recipient is the sender' do
-      is_expected.to_not change(Notification, :count)
+      expect { subject }.to_not change(Notification, :count)
     end
   end
 
@@ -163,7 +161,7 @@ RSpec.describe NotifyService, type: :service do
       let(:enabled) { true }
 
       it 'sends email' do
-        is_expected.to change(ActionMailer::Base.deliveries, :count).by(1)
+        expect { subject }.to change(ActionMailer::Base.deliveries, :count).by(1)
       end
     end
 
@@ -171,7 +169,7 @@ RSpec.describe NotifyService, type: :service do
       let(:enabled) { false }
 
       it "doesn't send email" do
-        is_expected.to_not change(ActionMailer::Base.deliveries, :count).from(0)
+        expect { subject }.to_not change(ActionMailer::Base.deliveries, :count).from(0)
       end
     end
   end
diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb
index cf7eb257a..5d45e4ffd 100644
--- a/spec/services/suspend_account_service_spec.rb
+++ b/spec/services/suspend_account_service_spec.rb
@@ -5,9 +5,7 @@ RSpec.describe SuspendAccountService, type: :service do
     let!(:local_follower) { Fabricate(:user, current_sign_in_at: 1.hour.ago).account }
     let!(:list)           { Fabricate(:list, account: local_follower) }
 
-    subject do
-      -> { described_class.new.call(account) }
-    end
+    subject { described_class.new.call(account) }
 
     before do
       allow(FeedManager.instance).to receive(:unmerge_from_home).and_return(nil)
@@ -18,13 +16,13 @@ RSpec.describe SuspendAccountService, type: :service do
     end
 
     it "unmerges from local followers' feeds" do
-      subject.call
+      subject
       expect(FeedManager.instance).to have_received(:unmerge_from_home).with(account, local_follower)
       expect(FeedManager.instance).to have_received(:unmerge_from_list).with(account, list)
     end
 
     it 'marks account as suspended' do
-      is_expected.to change { account.suspended? }.from(false).to(true)
+      expect { subject }.to change { account.suspended? }.from(false).to(true)
     end
   end
 
@@ -51,7 +49,7 @@ RSpec.describe SuspendAccountService, type: :service do
       end
 
       it 'sends an update actor to followers and reporters' do
-        subject.call
+        subject
         expect(a_request(:post, remote_follower.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
         expect(a_request(:post, remote_reporter.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
       end
@@ -77,7 +75,7 @@ RSpec.describe SuspendAccountService, type: :service do
       end
 
       it 'sends a reject follow' do
-        subject.call
+        subject
         expect(a_request(:post, account.inbox_url).with { |req| match_reject_follow_request(req, account, local_followee) }).to have_been_made.once
       end
     end
diff --git a/spec/services/unsuspend_account_service_spec.rb b/spec/services/unsuspend_account_service_spec.rb
index 0593beb6f..3ac4cc085 100644
--- a/spec/services/unsuspend_account_service_spec.rb
+++ b/spec/services/unsuspend_account_service_spec.rb
@@ -5,9 +5,7 @@ RSpec.describe UnsuspendAccountService, type: :service do
     let!(:local_follower) { Fabricate(:user, current_sign_in_at: 1.hour.ago).account }
     let!(:list)           { Fabricate(:list, account: local_follower) }
 
-    subject do
-      -> { described_class.new.call(account) }
-    end
+    subject { described_class.new.call(account) }
 
     before do
       allow(FeedManager.instance).to receive(:merge_into_home).and_return(nil)
@@ -33,7 +31,7 @@ RSpec.describe UnsuspendAccountService, type: :service do
     end
 
     it 'marks account as unsuspended' do
-      is_expected.to change { account.suspended? }.from(true).to(false)
+      expect { subject }.to change { account.suspended? }.from(true).to(false)
     end
 
     include_examples 'common behavior' do
@@ -47,13 +45,13 @@ RSpec.describe UnsuspendAccountService, type: :service do
       end
 
       it "merges back into local followers' feeds" do
-        subject.call
+        subject
         expect(FeedManager.instance).to have_received(:merge_into_home).with(account, local_follower)
         expect(FeedManager.instance).to have_received(:merge_into_list).with(account, list)
       end
 
       it 'sends an update actor to followers and reporters' do
-        subject.call
+        subject
         expect(a_request(:post, remote_follower.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
         expect(a_request(:post, remote_reporter.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
       end
@@ -75,18 +73,18 @@ RSpec.describe UnsuspendAccountService, type: :service do
         end
 
         it 're-fetches the account' do
-          subject.call
+          subject
           expect(resolve_account_service).to have_received(:call).with(account)
         end
 
         it "merges back into local followers' feeds" do
-          subject.call
+          subject
           expect(FeedManager.instance).to have_received(:merge_into_home).with(account, local_follower)
           expect(FeedManager.instance).to have_received(:merge_into_list).with(account, list)
         end
 
         it 'marks account as unsuspended' do
-          is_expected.to change { account.suspended? }.from(true).to(false)
+          expect { subject }.to change { account.suspended? }.from(true).to(false)
         end
       end
 
@@ -99,18 +97,18 @@ RSpec.describe UnsuspendAccountService, type: :service do
         end
 
         it 're-fetches the account' do
-          subject.call
+          subject
           expect(resolve_account_service).to have_received(:call).with(account)
         end
 
         it "does not merge back into local followers' feeds" do
-          subject.call
+          subject
           expect(FeedManager.instance).to_not have_received(:merge_into_home).with(account, local_follower)
           expect(FeedManager.instance).to_not have_received(:merge_into_list).with(account, list)
         end
 
         it 'does not mark the account as unsuspended' do
-          is_expected.not_to change { account.suspended? }
+          expect { subject }.not_to change { account.suspended? }
         end
       end
 
@@ -120,12 +118,12 @@ RSpec.describe UnsuspendAccountService, type: :service do
         end
 
         it 're-fetches the account' do
-          subject.call
+          subject
           expect(resolve_account_service).to have_received(:call).with(account)
         end
 
         it "does not merge back into local followers' feeds" do
-          subject.call
+          subject
           expect(FeedManager.instance).to_not have_received(:merge_into_home).with(account, local_follower)
           expect(FeedManager.instance).to_not have_received(:merge_into_list).with(account, list)
         end
diff --git a/spec/services/update_status_service_spec.rb b/spec/services/update_status_service_spec.rb
index 78cc89cd4..71a73be5b 100644
--- a/spec/services/update_status_service_spec.rb
+++ b/spec/services/update_status_service_spec.rb
@@ -3,6 +3,23 @@ require 'rails_helper'
 RSpec.describe UpdateStatusService, type: :service do
   subject { described_class.new }
 
+  context 'when nothing changes' do
+    let!(:status) { Fabricate(:status, text: 'Foo', language: 'en') }
+
+    before do
+      allow(ActivityPub::DistributionWorker).to receive(:perform_async)
+      subject.call(status, status.account_id, text: 'Foo')
+    end
+
+    it 'does not create an edit' do
+      expect(status.reload.edits).to be_empty
+    end
+
+    it 'does not notify anyone' do
+      expect(ActivityPub::DistributionWorker).to_not have_received(:perform_async)
+    end
+  end
+
   context 'when text changes' do
     let!(:status) { Fabricate(:status, text: 'Foo') }
     let(:preview_card) { Fabricate(:preview_card) }
diff --git a/spec/validators/status_length_validator_spec.rb b/spec/validators/status_length_validator_spec.rb
index 643ea6d22..4c80a59e6 100644
--- a/spec/validators/status_length_validator_spec.rb
+++ b/spec/validators/status_length_validator_spec.rb
@@ -55,6 +55,13 @@ describe StatusLengthValidator do
       expect(status.errors).to have_received(:add)
     end
 
+    it 'does not count overly long URLs as 23 characters flat' do
+      text = "http://example.com/valid?#{'#foo?' * 1000}"
+      status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false)
+      subject.validate(status)
+      expect(status.errors).to have_received(:add)
+    end
+
     it 'counts only the front part of remote usernames' do
       username = '@alice'
       chars = StatusLengthValidator::MAX_CHARS - 1 - username.length
@@ -64,5 +71,13 @@ describe StatusLengthValidator do
       subject.validate(status)
       expect(status.errors).to_not have_received(:add)
     end
+
+    it 'does count both parts of remote usernames for overly long domains' do
+      text   = "@alice@#{'b' * 500}.com"
+      status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false)
+
+      subject.validate(status)
+      expect(status.errors).to have_received(:add)
+    end
   end
 end
diff --git a/streaming/index.js b/streaming/index.js
index 3fdc9615e..780c4015d 100644
--- a/streaming/index.js
+++ b/streaming/index.js
@@ -167,6 +167,11 @@ const startWorker = async (workerId) => {
 
   const redisPrefix = redisNamespace ? `${redisNamespace}:` : '';
 
+  /**
+   * @type {Object.<string, Array.<function(string): void>>}
+   */
+  const subs = {};
+
   const redisSubscribeClient = await redisUrlToClient(redisParams, process.env.REDIS_URL);
   const redisClient = await redisUrlToClient(redisParams, process.env.REDIS_URL);
 
@@ -191,23 +196,55 @@ const startWorker = async (workerId) => {
   };
 
   /**
+   * @param {string} message
+   * @param {string} channel
+   */
+  const onRedisMessage = (message, channel) => {
+    const callbacks = subs[channel];
+
+    log.silly(`New message on channel ${channel}`);
+
+    if (!callbacks) {
+      return;
+    }
+
+    callbacks.forEach(callback => callback(message));
+  };
+
+  /**
    * @param {string} channel
    * @param {function(string): void} callback
    */
   const subscribe = (channel, callback) => {
     log.silly(`Adding listener for ${channel}`);
 
-    redisSubscribeClient.subscribe(channel, callback);
+    subs[channel] = subs[channel] || [];
+
+    if (subs[channel].length === 0) {
+      log.verbose(`Subscribe ${channel}`);
+      redisSubscribeClient.subscribe(channel, onRedisMessage);
+    }
+
+    subs[channel].push(callback);
   };
 
   /**
    * @param {string} channel
-   * @param {function(string): void} callback
    */
   const unsubscribe = (channel, callback) => {
     log.silly(`Removing listener for ${channel}`);
 
-    redisSubscribeClient.unsubscribe(channel, callback);
+    if (!subs[channel]) {
+      return;
+    }
+
+    subs[channel] = subs[channel].filter(item => item !== callback);
+
+    if (subs[channel].length === 0) {
+      log.verbose(`Unsubscribe ${channel}`);
+      redisSubscribeClient.unsubscribe(channel);
+      delete subs[channel];
+    }
   };
 
   const FALSE_VALUES = [
diff --git a/yarn.lock b/yarn.lock
index f96a75354..173fbaeb3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -33,18 +33,18 @@
   resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.7.tgz#078d8b833fbbcc95286613be8c716cef2b519fa2"
   integrity sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==
 
-"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.17.7", "@babel/core@^7.7.2", "@babel/core@^7.8.0":
-  version "7.17.7"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.7.tgz#f7c28228c83cdf2dbd1b9baa06eaf9df07f0c2f9"
-  integrity sha512-djHlEfFHnSnTAcPb7dATbiM5HxGOP98+3JLBZtjRb5I7RXrw7kFRoG2dXM8cm3H+o11A8IFH/uprmJpwFynRNQ==
+"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.17.8", "@babel/core@^7.7.2", "@babel/core@^7.8.0":
+  version "7.17.8"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.8.tgz#3dac27c190ebc3a4381110d46c80e77efe172e1a"
+  integrity sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ==
   dependencies:
     "@ampproject/remapping" "^2.1.0"
     "@babel/code-frame" "^7.16.7"
     "@babel/generator" "^7.17.7"
     "@babel/helper-compilation-targets" "^7.17.7"
     "@babel/helper-module-transforms" "^7.17.7"
-    "@babel/helpers" "^7.17.7"
-    "@babel/parser" "^7.17.7"
+    "@babel/helpers" "^7.17.8"
+    "@babel/parser" "^7.17.8"
     "@babel/template" "^7.16.7"
     "@babel/traverse" "^7.17.3"
     "@babel/types" "^7.17.0"
@@ -96,10 +96,10 @@
     browserslist "^4.17.5"
     semver "^6.3.0"
 
-"@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.17.1":
-  version "7.17.1"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.1.tgz#9699f14a88833a7e055ce57dcd3ffdcd25186b21"
-  integrity sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ==
+"@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.17.6":
+  version "7.17.6"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz#3778c1ed09a7f3e65e6d6e0f6fbfcc53809d92c9"
+  integrity sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==
   dependencies:
     "@babel/helper-annotate-as-pure" "^7.16.7"
     "@babel/helper-environment-visitor" "^7.16.7"
@@ -276,10 +276,10 @@
     "@babel/traverse" "^7.16.8"
     "@babel/types" "^7.16.8"
 
-"@babel/helpers@^7.17.7":
-  version "7.17.7"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.7.tgz#6fc0a24280fd00026e85424bbfed4650e76d7127"
-  integrity sha512-TKsj9NkjJfTBxM7Phfy7kv6yYc4ZcOo+AaWGqQOKTPDOmcGkIFb5xNA746eKisQkm4yavUYh4InYM9S+VnO01w==
+"@babel/helpers@^7.17.8":
+  version "7.17.8"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.8.tgz#288450be8c6ac7e4e44df37bcc53d345e07bc106"
+  integrity sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw==
   dependencies:
     "@babel/template" "^7.16.7"
     "@babel/traverse" "^7.17.3"
@@ -294,10 +294,10 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.3", "@babel/parser@^7.17.7", "@babel/parser@^7.7.0":
-  version "7.17.7"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.7.tgz#fc19b645a5456c8d6fdb6cecd3c66c0173902800"
-  integrity sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==
+"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.3", "@babel/parser@^7.17.8", "@babel/parser@^7.7.0":
+  version "7.17.8"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240"
+  integrity sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==
 
 "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7":
   version "7.16.7"
@@ -341,12 +341,12 @@
     "@babel/helper-plugin-utils" "^7.16.7"
     "@babel/plugin-syntax-class-static-block" "^7.14.5"
 
-"@babel/plugin-proposal-decorators@^7.17.2":
-  version "7.17.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.17.2.tgz#c36372ddfe0360cac1ee331a238310bddca11493"
-  integrity sha512-WH8Z95CwTq/W8rFbMqb9p3hicpt4RX4f0K659ax2VHxgOyT6qQmUaEVEjIh4WR9Eh9NymkVn5vwsrE68fAQNUw==
+"@babel/plugin-proposal-decorators@^7.17.8":
+  version "7.17.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.17.8.tgz#4f0444e896bee85d35cf714a006fc5418f87ff00"
+  integrity sha512-U69odN4Umyyx1xO1rTII0IDkAEC+RNlcKXtqOblfpzqy1C+aOplb76BQNq0+XdpVkOaPlpEDwd++joY8FNFJKA==
   dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.17.1"
+    "@babel/helper-create-class-features-plugin" "^7.17.6"
     "@babel/helper-plugin-utils" "^7.16.7"
     "@babel/helper-replace-supers" "^7.16.7"
     "@babel/plugin-syntax-decorators" "^7.17.0"
@@ -1008,10 +1008,10 @@
   dependencies:
     regenerator-runtime "^0.12.0"
 
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.7", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
-  version "7.17.7"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.7.tgz#a5f3328dc41ff39d803f311cfe17703418cf9825"
-  integrity sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.8", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+  version "7.17.8"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2"
+  integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA==
   dependencies:
     regenerator-runtime "^0.13.4"
 
@@ -1469,10 +1469,10 @@
     lz-string "^1.4.4"
     pretty-format "^27.0.2"
 
-"@testing-library/jest-dom@^5.16.2":
-  version "5.16.2"
-  resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.2.tgz#f329b36b44aa6149cd6ced9adf567f8b6aa1c959"
-  integrity sha512-6ewxs1MXWwsBFZXIk4nKKskWANelkdUehchEOokHsN8X7c2eKXGw+77aRV63UU8f/DTSVUPLaGxdrj4lN7D/ug==
+"@testing-library/jest-dom@^5.16.3":
+  version "5.16.3"
+  resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.3.tgz#b76851a909586113c20486f1679ffb4d8ec27bfa"
+  integrity sha512-u5DfKj4wfSt6akfndfu1eG06jsdyA/IUrlX2n3pyq5UXgXMhXY+NJb8eNK/7pqPWAhCKsCGWDdDO0zKMKAYkEA==
   dependencies:
     "@babel/runtime" "^7.9.2"
     "@types/testing-library__jest-dom" "^5.9.1"
@@ -2333,13 +2333,13 @@ babel-jest@^27.5.1:
     graceful-fs "^4.2.9"
     slash "^3.0.0"
 
-babel-loader@^8.2.3:
-  version "8.2.3"
-  resolved "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz"
-  integrity sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==
+babel-loader@^8.2.4:
+  version "8.2.4"
+  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.4.tgz#95f5023c791b2e9e2ca6f67b0984f39c82ff384b"
+  integrity sha512-8dytA3gcvPPPv4Grjhnt8b5IIiTcq/zeXOPk4iTYI0SVXcsmuGg7JtBRDp8S9X+gJfhQ8ektjXZlDu1Bb33U8A==
   dependencies:
     find-cache-dir "^3.3.1"
-    loader-utils "^1.4.0"
+    loader-utils "^2.0.0"
     make-dir "^3.1.0"
     schema-utils "^2.6.5"
 
@@ -2898,7 +2898,7 @@ caniuse-api@^3.0.0:
 
 caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001286:
   version "1.0.30001310"
-  resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001310.tgz"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001310.tgz#da02cd07432c9eece6992689d1b84ca18139eea8"
   integrity sha512-cb9xTV8k9HTIUA3GnPUJCk0meUnrHL5gy5QePfDjxHyNBcnzPzrHFv5GqfP7ue5b1ZyzZL0RJboD6hQlPXjhjg==
 
 chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
@@ -4320,10 +4320,10 @@ eslint-plugin-promise@~6.0.0:
   resolved "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz"
   integrity sha512-7GPezalm5Bfi/E22PnQxDWH2iW9GTvAlUNTztemeHb6c1BniSyoeTrM87JkC0wYdi6aQrZX9p2qEiAno8aTcbw==
 
-eslint-plugin-react@~7.29.3:
-  version "7.29.3"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.3.tgz#f4eab757f2756d25d6d4c2a58a9e20b004791f05"
-  integrity sha512-MzW6TuCnDOcta67CkpDyRfRsEVx9FNMDV8wZsDqe1luHPdGTrQIUaUXD27Ja3gHsdOIs/cXzNchWGlqm+qRVRg==
+eslint-plugin-react@~7.29.4:
+  version "7.29.4"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz#4717de5227f55f3801a5fd51a16a4fa22b5914d2"
+  integrity sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==
   dependencies:
     array-includes "^3.1.4"
     array.prototype.flatmap "^1.2.5"
@@ -8505,6 +8505,11 @@ prelude-ls@~1.1.2:
   resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
   integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
 
+prettier@^2.6.1:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.1.tgz#d472797e0d7461605c1609808e27b80c0f9cfe17"
+  integrity sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==
+
 pretty-format@^25.2.1, pretty-format@^25.5.0:
   version "25.5.0"
   resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz"
@@ -11343,10 +11348,10 @@ yargs@^16.2.0:
     y18n "^5.0.5"
     yargs-parser "^20.2.2"
 
-yargs@^17.3.1:
-  version "17.3.1"
-  resolved "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz"
-  integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==
+yargs@^17.4.0:
+  version "17.4.0"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.0.tgz#9fc9efc96bd3aa2c1240446af28499f0e7593d00"
+  integrity sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA==
   dependencies:
     cliui "^7.0.2"
     escalade "^3.1.1"