diff --git a/.circleci/config.yml b/.circleci/config.yml
index 2355d9d7d81baeff71ae65ca83c80e569b1f7e90..ff8eb485986f0a9bba3b091eb1c6ca895b3e5249 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,7 +3,7 @@ version: 2
 aliases:
   - &defaults
     docker:
-      - image: circleci/ruby:2.6.0-stretch-node
+      - image: circleci/ruby:2.6-stretch-node
         environment: &ruby_environment
           BUNDLE_APP_CONFIG: ./.bundle/
           DB_HOST: localhost
@@ -105,14 +105,14 @@ jobs:
   install-ruby2.5:
     <<: *defaults
     docker:
-      - image: circleci/ruby:2.5.3-stretch-node
+      - image: circleci/ruby:2.5-stretch-node
         environment: *ruby_environment
     <<: *install_ruby_dependencies
 
   install-ruby2.4:
     <<: *defaults
     docker:
-      - image: circleci/ruby:2.4.5-stretch-node
+      - image: circleci/ruby:2.4-stretch-node
         environment: *ruby_environment
     <<: *install_ruby_dependencies
 
@@ -131,40 +131,40 @@ jobs:
   test-ruby2.6:
     <<: *defaults
     docker:
-      - image: circleci/ruby:2.6.0-stretch-node
+      - image: circleci/ruby:2.6-stretch-node
         environment: *ruby_environment
       - image: circleci/postgres:10.6-alpine
         environment:
           POSTGRES_USER: root
-      - image: circleci/redis:5.0.3-alpine3.8
+      - image: circleci/redis:5-alpine
     <<: *test_steps
 
   test-ruby2.5:
     <<: *defaults
     docker:
-      - image: circleci/ruby:2.5.3-stretch-node
+      - image: circleci/ruby:2.5-stretch-node
         environment: *ruby_environment
       - image: circleci/postgres:10.6-alpine
         environment:
           POSTGRES_USER: root
-      - image: circleci/redis:4.0.12-alpine
+      - image: circleci/redis:5-alpine
     <<: *test_steps
 
   test-ruby2.4:
     <<: *defaults
     docker:
-      - image: circleci/ruby:2.4.5-stretch-node
+      - image: circleci/ruby:2.4-stretch-node
         environment: *ruby_environment
       - image: circleci/postgres:10.6-alpine
         environment:
           POSTGRES_USER: root
-      - image: circleci/redis:4.0.12-alpine
+      - image: circleci/redis:5-alpine
     <<: *test_steps
 
   test-webui:
     <<: *defaults
     docker:
-      - image: circleci/node:8.15.0-stretch
+      - image: circleci/node:12.9-stretch
     steps:
       - *attach_workspace
       - run: ./bin/retry yarn test:jest
@@ -173,9 +173,11 @@ jobs:
     <<: *defaults
     steps:
       - *attach_workspace
+      - *install_system_dependencies
       - run: bundle exec i18n-tasks check-normalized
       - run: bundle exec i18n-tasks unused -l en
       - run: bundle exec i18n-tasks check-consistent-interpolations
+      - run: bundle exec rake repo:check_locales_files
 
 workflows:
   version: 2
diff --git a/.env.nanobox b/.env.nanobox
index b60b6ee689729902d408338e32b20224ad933a0b..cfbe487fba769387b622a860a177b07662045a82 100644
--- a/.env.nanobox
+++ b/.env.nanobox
@@ -11,24 +11,14 @@ DB_NAME=gonano
 DB_PASS=$DATA_DB_PASS
 DB_PORT=5432
 
-DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
+# DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
 
 # Optional ElasticSearch configuration
 ES_ENABLED=true
 ES_HOST=$DATA_ELASTIC_HOST
 ES_PORT=9200
 
-# Optimizations
-LD_PRELOAD=/data/lib/libjemalloc.so
-
-# ImageMagick optimizations
-MAGICK_TEMPORARY_PATH=/app/tmp
-MAGICK_MEMORY_LIMIT=128MiB
-MAGICK_MAP_LIMIT=64MiB
-MAGICK_TIME_LIMIT=15
-MAGICK_AREA_LIMIT=16MP
-MAGICK_WIDTH_LIMIT=8KP
-MAGICK_HEIGHT_LIMIT=8KP
+BIND=0.0.0.0
 
 # Federation
 # Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
@@ -84,6 +74,7 @@ SMTP_PORT=587
 SMTP_LOGIN=$SMTP_LOGIN
 SMTP_PASSWORD=$SMTP_PASSWORD
 SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
+#SMTP_REPLY_TO=
 #SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
 #SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
 #SMTP_AUTH_METHOD=plain
@@ -97,9 +88,17 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
 # PAPERCLIP_ROOT_URL=/system
 
 # Optional asset host for multi-server setups
+# The asset host must allow cross origin request from WEB_DOMAIN or LOCAL_DOMAIN
+# if WEB_DOMAIN is not set. For example, the server may have the
+# following header field:
+# Access-Control-Allow-Origin: https://example.com/
 # CDN_HOST=https://assets.example.com
 
 # S3 (optional)
+# The attachment host must allow cross origin request from WEB_DOMAIN or
+# LOCAL_DOMAIN if WEB_DOMAIN is not set. For example, the server may have the
+# following header field:
+# Access-Control-Allow-Origin: https://192.168.1.123:9000/
 # S3_ENABLED=true
 # S3_BUCKET=
 # AWS_ACCESS_KEY_ID=
@@ -109,6 +108,8 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
 # S3_HOSTNAME=192.168.1.123:9000
 
 # S3 (Minio Config (optional) Please check Minio instance for details)
+# The attachment host must allow cross origin request - see the description
+# above.
 # S3_ENABLED=true
 # S3_BUCKET=
 # AWS_ACCESS_KEY_ID=
@@ -119,12 +120,30 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
 # S3_ENDPOINT=
 # S3_SIGNATURE_VERSION=
 
+# Google Cloud Storage (optional)
+# Use S3 compatible API. Since GCS does not support Multipart Upload,
+# increase the value of S3_MULTIPART_THRESHOLD to disable Multipart Upload.
+# The attachment host must allow cross origin request - see the description
+# above.
+# S3_ENABLED=true
+# AWS_ACCESS_KEY_ID=
+# AWS_SECRET_ACCESS_KEY=
+# S3_REGION=
+# S3_PROTOCOL=https
+# S3_HOSTNAME=storage.googleapis.com
+# S3_ENDPOINT=https://storage.googleapis.com
+# S3_MULTIPART_THRESHOLD=52428801 # 50.megabytes
+
 # Swift (optional)
+# The attachment host must allow cross origin request - see the description
+# above.
 # SWIFT_ENABLED=true
 # SWIFT_USERNAME=
 # For Keystone V3, the value for SWIFT_TENANT should be the project name
 # SWIFT_TENANT=
 # SWIFT_PASSWORD=
+# Some OpenStack V3 providers require PROJECT_ID (optional)
+# SWIFT_PROJECT_ID=
 # Keystone V2 and V3 URLs are supported. Use a V3 URL if possible to avoid
 # issues with token rate-limiting during high load.
 # SWIFT_AUTH_URL=
@@ -171,8 +190,8 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
 # The pam environment variable "email" is provided by:
 # https://github.com/devkral/pam_email_extractor
 # PAM_ENABLED=true
-# Fallback Suffix for email address generation (nil by default)
-# PAM_DEFAULT_SUFFIX=pam
+# Fallback email domain for email address generation (LOCAL_DOMAIN by default)
+# PAM_EMAIL_DOMAIN=example.com
 # Name of the pam service (pam "auth" section is evaluated)
 # PAM_DEFAULT_SERVICE=rpam
 # Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
@@ -220,7 +239,14 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
 # SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
 # SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
 # SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
-# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.5.4.42"
+# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241"
+# SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42"
+# SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4"
 # SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
 # SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
 # SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=
+
+# Use HTTP proxy for outgoing request (optional)
+# http_proxy=http://gateway.local:8118
+# Access control for hidden service.
+# ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
diff --git a/.env.production.sample b/.env.production.sample
index d66b0505095821de1beb2f4a2e9a6c05b3454121..f9a8bb7c1b2ae5be0ed334e4d565395b9b7fb613 100644
--- a/.env.production.sample
+++ b/.env.production.sample
@@ -69,6 +69,7 @@ SMTP_PORT=587
 SMTP_LOGIN=
 SMTP_PASSWORD=
 SMTP_FROM_ADDRESS=notifications@example.com
+#SMTP_REPLY_TO=
 #SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
 #SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
 #SMTP_AUTH_METHOD=plain
@@ -114,6 +115,20 @@ SMTP_FROM_ADDRESS=notifications@example.com
 # S3_ENDPOINT=
 # S3_SIGNATURE_VERSION=
 
+# Google Cloud Storage (optional)
+# Use S3 compatible API. Since GCS does not support Multipart Upload,
+# increase the value of S3_MULTIPART_THRESHOLD to disable Multipart Upload.
+# The attachment host must allow cross origin request - see the description
+# above.
+# S3_ENABLED=true
+# AWS_ACCESS_KEY_ID=
+# AWS_SECRET_ACCESS_KEY=
+# S3_REGION=
+# S3_PROTOCOL=https
+# S3_HOSTNAME=storage.googleapis.com
+# S3_ENDPOINT=https://storage.googleapis.com
+# S3_MULTIPART_THRESHOLD=52428801 # 50.megabytes
+
 # Swift (optional)
 # The attachment host must allow cross origin request - see the description
 # above.
@@ -163,7 +178,7 @@ STREAMING_CLUSTER_NUM=1
 # LDAP_BIND_DN=
 # LDAP_PASSWORD=
 # LDAP_UID=cn
-# LDAP_SEARCH_FILTER="%{uid}=%{email}"
+# LDAP_SEARCH_FILTER=%{uid}=%{email}
 
 # PAM authentication (optional)
 # PAM authentication uses for the email generation the "email" pam variable
diff --git a/.ruby-version b/.ruby-version
index 6a6a3d8e35c7a98eaeb47d4212cc13d1d8938003..57cf282ebbc41ec4cd51601733bc26d60c2341d4 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.6.1
+2.6.5
diff --git a/AUTHORS.md b/AUTHORS.md
index 8d3aaf480d5a25dddbe368e9f75b6c298bd97672..5f5985fba8328cd65216ff02ea9ffc5c11d462f7 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -5,89 +5,100 @@ Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon)
 and provided thanks to the work of the following contributors:
 
 * [Gargron](https://github.com/Gargron)
-* [ykzts](https://github.com/ykzts)
 * [ThibG](https://github.com/ThibG)
+* [ykzts](https://github.com/ykzts)
+* [dependabot[bot]](https://github.com/apps/dependabot)
 * [akihikodaki](https://github.com/akihikodaki)
+* [dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
 * [mjankowski](https://github.com/mjankowski)
-* [dependabot[bot]](https://github.com/apps/dependabot)
 * [unarist](https://github.com/unarist)
-* [m4sk1n](https://github.com/m4sk1n)
 * [yiskah](https://github.com/yiskah)
 * [nolanlawson](https://github.com/nolanlawson)
 * [ysksn](https://github.com/ysksn)
-* [sorin-davidoi](https://github.com/sorin-davidoi)
 * [abcang](https://github.com/abcang)
+* [sorin-davidoi](https://github.com/sorin-davidoi)
 * [lynlynlynx](https://github.com/lynlynlynx)
 * [mayaeh](https://github.com/mayaeh)
+* [m4sk1n](mailto:me@m4sk.in)
+* [Marcin Mikołajczak](mailto:me@m4sk.in)
+* [Kjwon15](https://github.com/Kjwon15)
 * [renatolond](https://github.com/renatolond)
 * [alpaca-tc](https://github.com/alpaca-tc)
+* [jeroenpraat](https://github.com/jeroenpraat)
 * [nclm](https://github.com/nclm)
 * [ineffyble](https://github.com/ineffyble)
-* [jeroenpraat](https://github.com/jeroenpraat)
+* [mabkenar](https://github.com/mabkenar)
 * [blackle](https://github.com/blackle)
 * [Quent-in](https://github.com/Quent-in)
 * [JantsoP](https://github.com/JantsoP)
-* [Kjwon15](https://github.com/Kjwon15)
-* [mabkenar](https://github.com/mabkenar)
+* [zunda](https://github.com/zunda)
 * [nullkal](https://github.com/nullkal)
 * [yookoala](https://github.com/yookoala)
+* [Aditoo17](https://github.com/Aditoo17)
+* [Quenty31](https://github.com/Quenty31)
+* [marek-lach](https://github.com/marek-lach)
 * [shuheiktgw](https://github.com/shuheiktgw)
 * [ashfurrow](https://github.com/ashfurrow)
-* [zunda](https://github.com/zunda)
-* [Quenty31](https://github.com/Quenty31)
 * [eramdam](https://github.com/eramdam)
+* [noellabo](https://github.com/noellabo)
 * [takayamaki](https://github.com/takayamaki)
+* [danhunsaker](https://github.com/danhunsaker)
 * [masarakki](https://github.com/masarakki)
 * [ticky](https://github.com/ticky)
-* [danhunsaker](https://github.com/danhunsaker)
 * [ThisIsMissEm](https://github.com/ThisIsMissEm)
 * [hcmiya](https://github.com/hcmiya)
 * [stephenburgess8](https://github.com/stephenburgess8)
 * [Wonderfall](https://github.com/Wonderfall)
 * [matteoaquila](https://github.com/matteoaquila)
 * [yukimochi](https://github.com/yukimochi)
+* [palindromordnilap](https://github.com/palindromordnilap)
 * [rkarabut](https://github.com/rkarabut)
 * [Artoria2e5](https://github.com/Artoria2e5)
 * [nightpool](https://github.com/nightpool)
 * [marrus-sh](https://github.com/marrus-sh)
+* [hinaloe](https://github.com/hinaloe)
 * [krainboltgreene](https://github.com/krainboltgreene)
 * [pfigel](https://github.com/pfigel)
 * [Aldarone](https://github.com/Aldarone)
 * [BoFFire](https://github.com/BoFFire)
 * [clworld](https://github.com/clworld)
+* [MasterGroosha](https://github.com/MasterGroosha)
 * [dracos](https://github.com/dracos)
+* [MaciekBaron](https://github.com/MaciekBaron)
 * [SerCom_KC](mailto:sercom-kc@users.noreply.github.com)
 * [Sylvhem](https://github.com/Sylvhem)
-* [MasterGroosha](https://github.com/MasterGroosha)
+* [MitarashiDango](https://github.com/MitarashiDango)
 * [JeanGauthier](https://github.com/JeanGauthier)
 * [kschaper](https://github.com/kschaper)
-* [MaciekBaron](https://github.com/MaciekBaron)
-* [MitarashiDango](mailto:mitarashidango@users.noreply.github.com)
 * [beatrix-bitrot](https://github.com/beatrix-bitrot)
-* [Aditoo17](https://github.com/Aditoo17)
+* [angristan](https://github.com/angristan)
 * [adbelle](https://github.com/adbelle)
 * [evanminto](https://github.com/evanminto)
 * [MightyPork](https://github.com/MightyPork)
+* [ashleyhull-versent](mailto:ashley.hull@versent.com.au)
 * [yhirano55](https://github.com/yhirano55)
 * [rinsuki](https://github.com/rinsuki)
 * [camponez](https://github.com/camponez)
-* [hinaloe](https://github.com/hinaloe)
-* [SerCom-KC](https://github.com/SerCom-KC)
+* [SerCom_KC](mailto:szescxz@gmail.com)
 * [aschmitz](https://github.com/aschmitz)
+* [trwnh](https://github.com/trwnh)
 * [devkral](https://github.com/devkral)
 * [fpiesche](https://github.com/fpiesche)
+* [hugogameiro](https://github.com/hugogameiro)
 * [gandaro](https://github.com/gandaro)
 * [johnsudaar](https://github.com/johnsudaar)
+* [ariasuni](https://github.com/ariasuni)
 * [trebmuh](https://github.com/trebmuh)
-* [Rakib Hasan](mailto:rmhasan@gmail.com)
-* [ashleyhull-versent](https://github.com/ashleyhull-versent)
+* [rmhasan](https://github.com/rmhasan)
+* [kedamaDQ](https://github.com/kedamaDQ)
 * [lindwurm](https://github.com/lindwurm)
 * [victorhck](mailto:victorhck@geeko.site)
 * [voidsatisfaction](https://github.com/voidsatisfaction)
+* [BenLubar](https://github.com/BenLubar)
 * [hikari-no-yume](https://github.com/hikari-no-yume)
-* [angristan](https://github.com/angristan)
 * [seefood](https://github.com/seefood)
 * [jackjennings](https://github.com/jackjennings)
+* [koyuawsmbrtn](https://github.com/koyuawsmbrtn)
 * [spla](mailto:spla@mastodont.cat)
 * [expenses](https://github.com/expenses)
 * [walf443](https://github.com/walf443)
@@ -95,18 +106,17 @@ and provided thanks to the work of the following contributors:
 * [mistydemeo](https://github.com/mistydemeo)
 * [dunn](https://github.com/dunn)
 * [xqus](https://github.com/xqus)
-* [hugogameiro](https://github.com/hugogameiro)
-* [ariasuni](https://github.com/ariasuni)
 * [pfm-eyesightjp](https://github.com/pfm-eyesightjp)
 * [fakenine](https://github.com/fakenine)
+* [Shleeble](https://github.com/Shleeble)
 * [tsuwatch](https://github.com/tsuwatch)
 * [victorhck](https://github.com/victorhck)
-* [kedamaDQ](https://github.com/kedamaDQ)
+* [mkljczk](https://github.com/mkljczk)
+* [manuelviens](https://github.com/manuelviens)
 * [puckipedia](https://github.com/puckipedia)
-* [trwnh](https://github.com/trwnh)
 * [fvh-P](https://github.com/fvh-P)
+* [rtucker](https://github.com/rtucker)
 * [Anna e só](mailto:contraexemplos@gmail.com)
-* [BenLubar](https://github.com/BenLubar)
 * [kazu9su](https://github.com/kazu9su)
 * [Komic](https://github.com/Komic)
 * [lmorchard](https://github.com/lmorchard)
@@ -119,6 +129,7 @@ and provided thanks to the work of the following contributors:
 * [goofy-bz](mailto:goofy@babelzilla.org)
 * [kadiix](https://github.com/kadiix)
 * [kodacs](https://github.com/kodacs)
+* [marcin mikołajczak](mailto:me@m4sk.in)
 * [JMendyk](https://github.com/JMendyk)
 * [KScl](https://github.com/KScl)
 * [sterdev](https://github.com/sterdev)
@@ -129,30 +140,31 @@ and provided thanks to the work of the following contributors:
 * [northerner](https://github.com/northerner)
 * [fhemberger](https://github.com/fhemberger)
 * [greysteil](https://github.com/greysteil)
-* [hensmith](https://github.com/hensmith)
+* [hencatsmith](https://github.com/hencatsmith)
 * [d6rkaiz](https://github.com/d6rkaiz)
 * [Reverite](https://github.com/Reverite)
 * [JohnD28](https://github.com/JohnD28)
 * [znz](https://github.com/znz)
-* [marek-lach](https://github.com/marek-lach)
 * [Naouak](https://github.com/Naouak)
 * [pawelngei](https://github.com/pawelngei)
-* [rtucker](https://github.com/rtucker)
 * [reneklacan](https://github.com/reneklacan)
 * [ekiru](https://github.com/ekiru)
-* [noellabo](https://github.com/noellabo)
 * [tcitworld](https://github.com/tcitworld)
 * [geta6](https://github.com/geta6)
 * [happycoloredbanana](https://github.com/happycoloredbanana)
 * [leopku](https://github.com/leopku)
 * [SansPseudoFix](https://github.com/SansPseudoFix)
+* [salvadorpla](https://github.com/salvadorpla)
 * [tomfhowe](https://github.com/tomfhowe)
 * [noraworld](https://github.com/noraworld)
 * [theboss](https://github.com/theboss)
+* [nzws](https://github.com/nzws)
 * [178inaba](https://github.com/178inaba)
+* [xgess](https://github.com/xgess)
 * [alyssais](https://github.com/alyssais)
-* [hiphref](https://github.com/hiphref)
+* [aablinov](https://github.com/aablinov)
 * [stalker314314](https://github.com/stalker314314)
+* [cutls](https://github.com/cutls)
 * [huertanix](https://github.com/huertanix)
 * [genesixx](https://github.com/genesixx)
 * [halkeye](https://github.com/halkeye)
@@ -162,21 +174,24 @@ and provided thanks to the work of the following contributors:
 * [kmichl](https://github.com/kmichl)
 * [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name)
 * [saper](https://github.com/saper)
+* [Dar13](https://github.com/Dar13)
 * [nevillepark](https://github.com/nevillepark)
 * [ornithocoder](https://github.com/ornithocoder)
+* [pwoolcoc](https://github.com/pwoolcoc)
 * [pierreozoux](https://github.com/pierreozoux)
 * [qguv](https://github.com/qguv)
 * [Ram Lmn](mailto:ramlmn@users.noreply.github.com)
-* [sascha-sl](https://github.com/sascha-sl)
+* [aurelia-sl](https://github.com/aurelia-sl)
 * [harukasan](https://github.com/harukasan)
 * [stamak](https://github.com/stamak)
-* [Technowix](mailto:technowix@users.noreply.github.com)
+* [Technowix](https://github.com/Technowix)
 * [Zoeille](https://github.com/Zoeille)
 * [Thor Harald Johansen](mailto:thj@thj.no)
 * [0x70b1a5](https://github.com/0x70b1a5)
 * [gled-rs](https://github.com/gled-rs)
 * [Valentin_NC](mailto:valentin.ouvrard@nautile.sarl)
 * [R0ckweb](https://github.com/R0ckweb)
+* [unasuke](https://github.com/unasuke)
 * [caasi](https://github.com/caasi)
 * [chr-1x](https://github.com/chr-1x)
 * [esetomo](https://github.com/esetomo)
@@ -184,8 +199,9 @@ and provided thanks to the work of the following contributors:
 * [hoodie](mailto:hoodiekitten@outlook.com)
 * [luzi82](https://github.com/luzi82)
 * [duxovni](https://github.com/duxovni)
+* [slice](https://github.com/slice)
 * [tmm576](https://github.com/tmm576)
-* [unsmell](https://github.com/unsmell)
+* [unsmell](mailto:unsmell@users.noreply.github.com)
 * [valerauko](https://github.com/valerauko)
 * [chriswmartin](https://github.com/chriswmartin)
 * [vahnj](https://github.com/vahnj)
@@ -193,21 +209,25 @@ and provided thanks to the work of the following contributors:
 * [AndreLewin](https://github.com/AndreLewin)
 * [0xflotus](https://github.com/0xflotus)
 * [redtachyons](https://github.com/redtachyons)
+* [acid-chicken](https://github.com/acid-chicken)
 * [thurloat](https://github.com/thurloat)
 * [aaribaud](https://github.com/aaribaud)
 * [pointlessone](https://github.com/pointlessone)
 * [Andrew](mailto:andrewlchronister@gmail.com)
+* [aurelien-reeves](https://github.com/aurelien-reeves)
+* [AnaGelez](https://github.com/AnaGelez)
 * [estuans](https://github.com/estuans)
 * [dissolve](https://github.com/dissolve)
 * [PurpleBooth](https://github.com/PurpleBooth)
 * [bradurani](https://github.com/bradurani)
 * [wavebeem](https://github.com/wavebeem)
 * [bruwalfas](https://github.com/bruwalfas)
-* [foxsan48](https://github.com/foxsan48)
+* [LottieVixen](https://github.com/LottieVixen)
 * [wchristian](https://github.com/wchristian)
 * [muffinista](https://github.com/muffinista)
 * [cdutson](https://github.com/cdutson)
 * [farlistener](https://github.com/farlistener)
+* [dariusk](https://github.com/dariusk)
 * [DavidLibeau](https://github.com/DavidLibeau)
 * [ddevault](https://github.com/ddevault)
 * [Fjoerfoks](https://github.com/Fjoerfoks)
@@ -216,6 +236,7 @@ and provided thanks to the work of the following contributors:
 * [Gomasy](https://github.com/Gomasy)
 * [unstabler](https://github.com/unstabler)
 * [potato4d](https://github.com/potato4d)
+* [Hanage999](https://github.com/Hanage999)
 * [h-izumi](https://github.com/h-izumi)
 * [ErikXXon](https://github.com/ErikXXon)
 * [ian-kelling](https://github.com/ian-kelling)
@@ -231,21 +252,23 @@ and provided thanks to the work of the following contributors:
 * [Kaylee](mailto:kaylee@codethat.sucks)
 * [Kazhnuz](https://github.com/Kazhnuz)
 * [connyduck](https://github.com/connyduck)
-* [Lindsey Bieda](mailto:lindseyb@users.noreply.github.com)
+* [LindseyB](https://github.com/LindseyB)
 * [Lorenz Diener](mailto:halcyon@icosahedron.website)
 * [alimony](https://github.com/alimony)
 * [mig5](https://github.com/mig5)
 * [moritzheiber](https://github.com/moritzheiber)
 * [ndarville](https://github.com/ndarville)
 * [Abzol](https://github.com/Abzol)
-* [pwoolcoc](https://github.com/pwoolcoc)
+* [PatOnTheBack](https://github.com/PatOnTheBack)
 * [xPaw](https://github.com/xPaw)
 * [petzah](https://github.com/petzah)
 * [ignisf](https://github.com/ignisf)
 * [raymestalez](https://github.com/raymestalez)
 * [remram44](https://github.com/remram44)
 * [sts10](https://github.com/sts10)
+* [SuperSandro2000](https://github.com/SuperSandro2000)
 * [u1-liquid](https://github.com/u1-liquid)
+* [rosylilly](https://github.com/rosylilly)
 * [sim6](https://github.com/sim6)
 * [Sir-Boops](https://github.com/Sir-Boops)
 * [stemid](https://github.com/stemid)
@@ -270,6 +293,7 @@ and provided thanks to the work of the following contributors:
 * [cpsdqs](https://github.com/cpsdqs)
 * [barzamin](https://github.com/barzamin)
 * [fhalna](https://github.com/fhalna)
+* [highemerly](https://github.com/highemerly)
 * [haoyayoi](https://github.com/haoyayoi)
 * [ik11235](https://github.com/ik11235)
 * [kawax](https://github.com/kawax)
@@ -279,6 +303,7 @@ and provided thanks to the work of the following contributors:
 * [mecab](https://github.com/mecab)
 * [nicobz25](https://github.com/nicobz25)
 * [oliverkeeble](https://github.com/oliverkeeble)
+* [partev](https://github.com/partev)
 * [pinfort](https://github.com/pinfort)
 * [rbaumert](https://github.com/rbaumert)
 * [rhoio](https://github.com/rhoio)
@@ -287,19 +312,17 @@ and provided thanks to the work of the following contributors:
 * [vjackson725](https://github.com/vjackson725)
 * [wxcafe](https://github.com/wxcafe)
 * [新都心(Neet Shin)](mailto:nucx@dio-vox.com)
+* [clarfon](https://github.com/clarfon)
 * [cygnan](https://github.com/cygnan)
 * [Awea](https://github.com/Awea)
 * [halcy](https://github.com/halcy)
-* [naaaaaaaaaaaf](https://github.com/naaaaaaaaaaaf)
 * [8398a7](https://github.com/8398a7)
 * [857b](https://github.com/857b)
 * [insom](https://github.com/insom)
 * [tachyons](https://github.com/tachyons)
-* [acid-chicken](https://github.com/acid-chicken)
 * [Esteth](https://github.com/Esteth)
 * [unascribed](https://github.com/unascribed)
 * [Aguay-val](https://github.com/Aguay-val)
-* [Akihiko Odaki](mailto:nekomanma@pixiv.co.jp)
 * [knu](https://github.com/knu)
 * [h3poteto](https://github.com/h3poteto)
 * [unleashed](https://github.com/unleashed)
@@ -307,8 +330,8 @@ and provided thanks to the work of the following contributors:
 * [console-cowboy](https://github.com/console-cowboy)
 * [Alkarex](https://github.com/Alkarex)
 * [a2](https://github.com/a2)
+* [alfiedotwtf](https://github.com/alfiedotwtf)
 * [0xa](https://github.com/0xa)
-* [palindromordnilap](https://github.com/palindromordnilap)
 * [virtualpain](https://github.com/virtualpain)
 * [sapphirus](https://github.com/sapphirus)
 * [amandavisconti](https://github.com/amandavisconti)
@@ -320,10 +343,9 @@ and provided thanks to the work of the following contributors:
 * [contraexemplo](https://github.com/contraexemplo)
 * [abackstrom](https://github.com/abackstrom)
 * [armandfardeau](https://github.com/armandfardeau)
+* [raboof](https://github.com/raboof)
 * [jumbosushi](https://github.com/jumbosushi)
-* [aurelien-reeves](https://github.com/aurelien-reeves)
 * [ayumin](https://github.com/ayumin)
-* [BaptisteGelez](https://github.com/BaptisteGelez)
 * [bzg](https://github.com/bzg)
 * [benediktg](https://github.com/benediktg)
 * [blakebarnett](https://github.com/blakebarnett)
@@ -337,15 +359,15 @@ and provided thanks to the work of the following contributors:
 * [DoubleMalt](https://github.com/DoubleMalt)
 * [Moosh-be](https://github.com/Moosh-be)
 * [Motoma](https://github.com/Motoma)
-* [chriswk](https://github.com/chriswk)
+* [Christopher Kolstad](mailto:christopher.kolstad@finn.no)
 * [csu](https://github.com/csu)
-* [clarfon](https://github.com/clarfon)
 * [kklleemm](https://github.com/kklleemm)
 * [colindean](https://github.com/colindean)
 * [dachinat](https://github.com/dachinat)
 * [multiple-creatures](https://github.com/multiple-creatures)
 * [watilde](https://github.com/watilde)
 * [daprice](https://github.com/daprice)
+* [da2x](https://github.com/da2x)
 * [dar5hak](https://github.com/dar5hak)
 * [kant](https://github.com/kant)
 * [maxolasersquad](https://github.com/maxolasersquad)
@@ -354,7 +376,7 @@ and provided thanks to the work of the following contributors:
 * [davefp](https://github.com/davefp)
 * [yipdw](https://github.com/yipdw)
 * [debanshuk](https://github.com/debanshuk)
-* [Derek Lewis](mailto:derekcecillewis@gmail.com)
+* [DerekNonGeneric](https://github.com/DerekNonGeneric)
 * [dblandin](https://github.com/dblandin)
 * [Drew Gates](mailto:aranaur@users.noreply.github.com)
 * [dtschust](https://github.com/dtschust)
@@ -366,11 +388,13 @@ and provided thanks to the work of the following contributors:
 * [ericblade](https://github.com/ericblade)
 * [mikoim](https://github.com/mikoim)
 * [espenronnevik](https://github.com/espenronnevik)
+* [fabianonline](https://github.com/fabianonline)
 * [Finariel](https://github.com/Finariel)
 * [siuying](https://github.com/siuying)
 * [zoc](https://github.com/zoc)
 * [fwenzel](https://github.com/fwenzel)
 * [GenbuHase](https://github.com/GenbuHase)
+* [nilsding](https://github.com/nilsding)
 * [hattori6789](https://github.com/hattori6789)
 * [algernon](https://github.com/algernon)
 * [Fastbyte01](https://github.com/Fastbyte01)
@@ -386,17 +410,19 @@ and provided thanks to the work of the following contributors:
 * [suzukaze](https://github.com/suzukaze)
 * [Hiromi-Kai](https://github.com/Hiromi-Kai)
 * [hishamhm](https://github.com/hishamhm)
+* [Slaynash](https://github.com/Slaynash)
 * [musashino205](https://github.com/musashino205)
 * [iwaim](https://github.com/iwaim)
 * [valrus](https://github.com/valrus)
 * [IMcD23](https://github.com/IMcD23)
 * [yi0713](https://github.com/yi0713)
 * [iblech](https://github.com/iblech)
-* [usbsnowcrash](https://github.com/usbsnowcrash)
+* [J Yeary](mailto:usbsnowcrash@users.noreply.github.com)
 * [jack-michaud](https://github.com/jack-michaud)
 * [Floppy](https://github.com/Floppy)
 * [loomchild](https://github.com/loomchild)
 * [jenkr55](https://github.com/jenkr55)
+* [hyenagirl64](https://github.com/hyenagirl64)
 * [press5](https://github.com/press5)
 * [TrollDecker](https://github.com/TrollDecker)
 * [jmontane](https://github.com/jmontane)
@@ -406,17 +432,17 @@ and provided thanks to the work of the following contributors:
 * [joshuap](https://github.com/joshuap)
 * [Tiwy57](https://github.com/Tiwy57)
 * [xuv](https://github.com/xuv)
-* [June Sallou](mailto:jnsll@users.noreply.github.com)
+* [Jnsll](https://github.com/Jnsll)
 * [j0k3r](https://github.com/j0k3r)
 * [KEINOS](https://github.com/KEINOS)
 * [futoase](https://github.com/futoase)
-* [Pneumaticat](https://github.com/Pneumaticat)
+* [pot8to](https://github.com/pot8to)
 * [Kit Redgrave](mailto:qwertyitis@gmail.com)
 * [Knut Erik](mailto:abjectio@users.noreply.github.com)
 * [mkody](https://github.com/mkody)
 * [k0ta0uchi](https://github.com/k0ta0uchi)
 * [KrzysiekJ](https://github.com/KrzysiekJ)
-* [leowzukw](https://github.com/leowzukw)
+* [Leo Wzukw](mailto:leowzukw@users.noreply.github.com)
 * [Tak](https://github.com/Tak)
 * [cacheflow](https://github.com/cacheflow)
 * [ldidry](https://github.com/ldidry)
@@ -424,6 +450,7 @@ and provided thanks to the work of the following contributors:
 * [lfuelling](https://github.com/lfuelling)
 * [Grabacr07](https://github.com/Grabacr07)
 * [mistermantas](https://github.com/mistermantas)
+* [MareenaKunjachan](https://github.com/MareenaKunjachan)
 * [mareklach](https://github.com/mareklach)
 * [wirehack7](https://github.com/wirehack7)
 * [martymcguire](https://github.com/martymcguire)
@@ -431,50 +458,53 @@ and provided thanks to the work of the following contributors:
 * [otsune](https://github.com/otsune)
 * [mbugowski](https://github.com/mbugowski)
 * [Mathias B](mailto:10813340+mathias-b@users.noreply.github.com)
+* [madmath03](https://github.com/madmath03)
 * [matt-auckland](https://github.com/matt-auckland)
 * [webroo](https://github.com/webroo)
-* [matthiasbeyer](https://github.com/matthiasbeyer)
-* [mattjmattj](https://github.com/mattjmattj)
-* [mtparet](https://github.com/mtparet)
-* [maximeborges](https://github.com/maximeborges)
-* [minacle](https://github.com/minacle)
-* [michaeljdeeb](https://github.com/michaeljdeeb)
-* [Themimitoof](https://github.com/Themimitoof)
-* [cyweo](https://github.com/cyweo)
+* [Matthias Beyer](mailto:mail@beyermatthias.de)
+* [Matthias Jouan](mailto:matthias.jouan@gmail.com)
+* [Matthieu Paret](mailto:matthieuparet69@gmail.com)
+* [Maxime BORGES](mailto:maxime.borges@gmail.com)
+* [Mayu Laierlence](mailto:minacle@live.com)
+* [Michael Deeb](mailto:michaeldeeb@me.com)
+* [Michael Vieira](mailto:dtox94@gmail.com)
+* [Michel](mailto:michel@cyweo.com)
 * [Midgard](mailto:m1dgard@users.noreply.github.com)
-* [mike-burns](https://github.com/mike-burns)
-* [verymilan](https://github.com/verymilan)
-* [milmazz](https://github.com/milmazz)
-* [premist](https://github.com/premist)
-* [Mnkai](https://github.com/Mnkai)
-* [mitchhentges](https://github.com/mitchhentges)
-* [mouse-reeve](https://github.com/mouse-reeve)
-* [Mozinet-fr](https://github.com/Mozinet-fr)
-* [lae](https://github.com/lae)
-* [nosada](https://github.com/nosada)
-* [Nanamachi](https://github.com/Nanamachi)
-* [orinthe](https://github.com/orinthe)
-* [NecroTechno](https://github.com/NecroTechno)
-* [Dar13](https://github.com/Dar13)
-* [ngerakines](https://github.com/ngerakines)
-* [vonneudeck](https://github.com/vonneudeck)
-* [Ninetailed](https://github.com/Ninetailed)
-* [k24](https://github.com/k24)
-* [noiob](https://github.com/noiob)
-* [kwaio](https://github.com/kwaio)
-* [norayr](https://github.com/norayr)
-* [joyeusenoelle](https://github.com/joyeusenoelle)
-* [OlivierNicole](https://github.com/OlivierNicole)
-* [noppa](https://github.com/noppa)
-* [Otakan951](https://github.com/Otakan951)
-* [fahy](https://github.com/fahy)
+* [Mike Burns](mailto:mburns@thoughtbot.com)
+* [Milan](mailto:me@petabyteboy.de)
+* [Milan*](mailto:tchncs@vivaldi.net)
+* [Milton Mazzarri](mailto:milmazz@gmail.com)
+* [Minku Lee](mailto:premist@me.com)
+* [Minori Hiraoka](mailto:mnkai@users.noreply.github.com)
+* [Mitchell Hentges](mailto:mitch9654@gmail.com)
+* [Mostafa Ahangarha](mailto:ahangarha@users.noreply.github.com)
+* [Mouse Reeve](mailto:mousereeve@riseup.net)
+* [Mozinet](mailto:mozinet-fr@users.noreply.github.com)
+* [Musee U](mailto:lae@users.noreply.github.com)
+* [NOGISAKA Sadata](mailto:ngsksdt@gmail.com)
+* [Naf](mailto:uenok.htc@gmail.com)
+* [Nanamachi](mailto:town7.haruki@gmail.com)
+* [Nathaniel Ekoniak](mailto:nekoniak@ennate.tech)
+* [NecroTechno](mailto:necrotechno@riseup.net)
+* [Nick Gerakines](mailto:nick@gerakines.net)
+* [Nicolai von Neudeck](mailto:nicolai@vonneudeck.com)
+* [Ninetailed](mailto:ninetailed@gmail.com)
+* [Nishi, Keisuke](mailto:k24@users.noreply.github.com)
+* [Noiob](mailto:noiob@users.noreply.github.com)
+* [Nope Nope](mailto:hireme@kwaio.ninja)
+* [Norayr Chilingarian](mailto:norayr@arnet.am)
+* [Noëlle Anthony](mailto:noelle.d.anthony@gmail.com)
+* [N氏](mailto:uenok.htc@gmail.com)
+* [Olivier Nicole](mailto:olivierthnicole@gmail.com)
+* [Oskari Noppa](mailto:noppa@users.noreply.github.com)
+* [Otakan](mailto:otakan951@gmail.com)
+* [Padraig Fahy](mailto:tech@padraigfahy.com)
 * [PatrickRWells](mailto:32802366+patrickrwells@users.noreply.github.com)
 * [Paul](mailto:naydex.mc+github@gmail.com)
 * [Pete Keen](mailto:pete@petekeen.net)
 * [Pierre-Morgan Gate](mailto:pgate@users.noreply.github.com)
 * [Ratmir Karabut](mailto:rkarabut@sfmodern.ru)
 * [Reto Kromer](mailto:retokromer@users.noreply.github.com)
-* [Rey Tucker](mailto:git@reytucker.us)
 * [Rob Watson](mailto:rfwatson@users.noreply.github.com)
 * [Ryan Freebern](mailto:ryan@freebern.org)
 * [Ryan Wade](mailto:ryan.wade@protonmail.com)
@@ -482,6 +512,7 @@ and provided thanks to the work of the following contributors:
 * [S.H](mailto:gamelinks007@gmail.com)
 * [Sadiq Saif](mailto:staticsafe@users.noreply.github.com)
 * [Sam Hewitt](mailto:hewittsamuel@gmail.com)
+* [Sasha Sorokin](mailto:dafri.nochiterov8@gmail.com)
 * [Satoshi KOJIMA](mailto:skoji@mac.com)
 * [ScienJus](mailto:i@scienjus.com)
 * [Scott Larkin](mailto:scott@codeclimate.com)
@@ -492,12 +523,10 @@ and provided thanks to the work of the following contributors:
 * [Shaun Gillies](mailto:me@shaungillies.net)
 * [Shin Adachi](mailto:shn@glucose.jp)
 * [Shin Kojima](mailto:shin@kojima.org)
-* [Sho Kusano](mailto:rosylilly@aduca.org)
 * [Shouko Yu](mailto:imshouko@gmail.com)
 * [Sina Mashek](mailto:sina@mashek.xyz)
 * [Soshi Kato](mailto:mail@sossii.com)
 * [Spanky](mailto:2788886+spankyworks@users.noreply.github.com)
-* [Stanislas](mailto:angristan@pm.me)
 * [StefOfficiel](mailto:pichard.stephane@free.fr)
 * [Steven Tappert](mailto:admin@dark-it.net)
 * [Svetlozar Todorov](mailto:svetlik@users.noreply.github.com)
@@ -506,6 +535,7 @@ and provided thanks to the work of the following contributors:
 * [Takayoshi Nishida](mailto:takayoshi.nishida@gmail.com)
 * [Takayuki KUSANO](mailto:github@tkusano.jp)
 * [TakesxiSximada](mailto:takesxi.sximada@gmail.com)
+* [Tao Bror Bojlén](mailto:brortao@users.noreply.github.com)
 * [TheInventrix](mailto:theinventrix@users.noreply.github.com)
 * [Thomas Alberola](mailto:thomas@needacoffee.fr)
 * [Toby Deshane](mailto:fortyseven@users.noreply.github.com)
@@ -515,10 +545,12 @@ and provided thanks to the work of the following contributors:
 * [Treyssat-Vincent Nino](mailto:treyssatvincent@users.noreply.github.com)
 * [Udo Kramer](mailto:optik@fluffel.io)
 * [Una](mailto:una@unascribed.com)
+* [Ushitora Anqou](mailto:ushitora@anqou.net)
 * [Ushitora Anqou](mailto:ushitora_anqou@yahoo.co.jp)
 * [Valentin Lorentz](mailto:progval+git@progval.net)
 * [Vladimir Mincev](mailto:vladimir@canicinteractive.com)
 * [Waldir Pimenta](mailto:waldyrious@gmail.com)
+* [Wenceslao Páez Chávez](mailto:wcpaez@gmail.com)
 * [Wesley Ellis](mailto:tahnok@gmail.com)
 * [Wiktor](mailto:wiktor@metacode.biz)
 * [Wonderfall](mailto:wonderfall@schrodinger.io)
@@ -529,6 +561,7 @@ and provided thanks to the work of the following contributors:
 * [YaQ](mailto:i_k_o_m_a_7@yahoo.co.jp)
 * [Yanaken](mailto:yanakend@gmail.com)
 * [Yann Klis](mailto:yann.klis@gmail.com)
+* [Yağızhan](mailto:35808275+yagizhan49@users.noreply.github.com)
 * [Yeechan Lu](mailto:wz.bluesnow@gmail.com)
 * [Yusuke Abe](mailto:moonset20@gmail.com)
 * [Zachary Spector](mailto:logicaldash@gmail.com)
@@ -542,6 +575,7 @@ and provided thanks to the work of the following contributors:
 * [chrolis](mailto:chrolis@users.noreply.github.com)
 * [cormo](mailto:cormorant2+github@gmail.com)
 * [d0p1](mailto:dopi-sama@hush.com)
+* [dxwc](mailto:dxwc@users.noreply.github.com)
 * [evilny0](mailto:evilny0@moomoocamp.net)
 * [febrezo](mailto:felixbrezo@gmail.com)
 * [fsubal](mailto:fsubal@users.noreply.github.com)
@@ -550,6 +584,7 @@ and provided thanks to the work of the following contributors:
 * [gol-cha](mailto:info@mevo.xyz)
 * [hakoai](mailto:hk--76@qa2.so-net.ne.jp)
 * [haosbvnker](mailto:github@chaosbunker.com)
+* [ichi_i](mailto:51489410+ichi-i@users.noreply.github.com)
 * [isati](mailto:phil@juchnowi.cz)
 * [jacob](mailto:jacobherringtondeveloper@gmail.com)
 * [jenn kaplan](mailto:me@jkap.io)
@@ -561,7 +596,6 @@ and provided thanks to the work of the following contributors:
 * [karlyeurl](mailto:karl.yeurl@gmail.com)
 * [kedama](mailto:32974885+kedamadq@users.noreply.github.com)
 * [kodai](mailto:shirafuta.kodai@gmail.com)
-* [koyu](mailto:me@koyu.space)
 * [kuro5hin](mailto:rusty@kuro5hin.org)
 * [luzpaz](mailto:luzpaz@users.noreply.github.com)
 * [maxypy](mailto:maxime@mpigou.fr)
@@ -573,6 +607,7 @@ and provided thanks to the work of the following contributors:
 * [muan](mailto:muan@github.com)
 * [namelessGonbai](mailto:43787036+namelessgonbai@users.noreply.github.com)
 * [neetshin](mailto:neetshin@neetsh.in)
+* [nzws](mailto:git-yuzu@svk.jp)
 * [rch850](mailto:rich850@gmail.com)
 * [roikale](mailto:roikale@users.noreply.github.com)
 * [rysiekpl](mailto:rysiek@hackerspace.pl)
@@ -585,6 +620,8 @@ and provided thanks to the work of the following contributors:
 * [tateisu](mailto:tateisu@gmail.com)
 * [tmyt](mailto:shigure@refy.net)
 * [trevDev()](mailto:trev@trevdev.ca)
+* [tsia](mailto:github@tsia.de)
+* [umonaca](mailto:53662960+umonaca@users.noreply.github.com)
 * [utam0k](mailto:k0ma@utam0k.jp)
 * [vpzomtrrfrt](mailto:vpzomtrrfrt@gmail.com)
 * [walfie](mailto:walfington@gmail.com)
@@ -593,9 +630,10 @@ and provided thanks to the work of the following contributors:
 * [yoshipc](mailto:yoooo@yoshipc.net)
 * [Özcan Zafer AYAN](mailto:ozcanzaferayan@gmail.com)
 * [ばん](mailto:detteiu0321@gmail.com)
-* [みたらしだんご](mailto:mitarashidango@users.noreply.github.com)
+* [ふるふる](mailto:frfs@users.noreply.github.com)
 * [りんすき](mailto:6533808+rinsuki@users.noreply.github.com)
 * [ヨイツの賢狼ホロ | 3rd style](mailto:horo@yoitsu.moe)
+* [唐宗勛](mailto:tangzongxun@hotmail.com)
 * [猫吸血鬼ディフリス / 猫ロキP](mailto:deflis@gmail.com)
 * [艮 鮟鱇](mailto:ushitora_anqou@yahoo.co.jp)
 * [西小倉宏信](mailto:nishiko@mindia.jp)
@@ -607,338 +645,122 @@ This document is provided for informational purposes only. Since it is only upda
 
 Following people have contributed to translation of Mastodon:
 
-- **Albanian**
-  - Besnik Bleta
-  - Aditoo
-- **Arabic**
-  - ButterflyOfFire
-  - Aditoo
-  - Amrz0
-- **Asturian**
-  - ButterflyOfFire
-  - Enol P.
-  - Aditoo
-- **Basque**
-  - Osoitz
-  - Aditoo
-  - Aitzol
-  - ButterflyOfFire
-  - Peru Iparragirre
-  - Gorka Azkarate
-- **Bengali**
-  - dxwc
-- **Bulgarian**
-  - ButterflyOfFire
-  - Aditoo
-- **Catalan**
-  - spla
-  - Aditoo
-  - ButterflyOfFire
-  - Joan Montané
-  - Jose Luis
-- **Chinese (Hong Kong)**
-  - ButterflyOfFire
-  - Luzi Leung
-  - Aditoo
-- **Chinese (Simplified)**
-  - Allen Zhong
-  - ButterflyOfFire
-  - SerCom_KC
-  - martialarts
-  - Kaitian Xie
-  - Aditoo
-  - pan93412
-- **Chinese (Traditional)**
-  - Aditoo
-  - ButterflyOfFire
-  - James58899
-  - pan93412
-  - S1ttidoe477
-  - SHA265
-  - Jeff Huang
-- **Corsican**
-  - Alix D. R.
-  - Aditoo
-  - ButterflyOfFire
-- **Croatian**
-  - ButterflyOfFire
-  - Aditoo
-- **Czech**
-  - Aditoo
-  - Marek Ľach
-  - ButterflyOfFire
-- **Danish**
-  - Einhjeriar
-  - Rasmus Sæderup
-  - Aditoo
-  - ButterflyOfFire
-- **Dutch**
-  - Albakham
-  - ButterflyOfFire
-  - jeroenpraat
-  - rscmbbng
-  - Aditoo
-  - Jelv
-- **English**
-  - ButterflyOfFire
-  - Renato "Lond" Cerqueira
-- **English (United Kingdom)**
-  - Albakham
-- **Esperanto**
-  - Aditoo
-  - ButterflyOfFire
-  - Becci Cat
-  - Jeong Arm
-  - Mélanie Chauvel
-  - Vanege
-  - Martin Bodin
-  - tuxayo/Victor Grousset
-- **Finnish**
-  - ButterflyOfFire
-  - Mikko Poussu
-  - Taru Luojola
-  - S Heija
-  - Aditoo
-  - Jonne Arjoranta
-- **French**
-  - Albakham
-  - Alix D. R.
-  - ButterflyOfFire
-  - codl
-  - Leia
-  - Alda Marteau-Hardi
-  - Mélanie Chauvel
-  - Paul Marques Mota
-  - azenet
-  - Olivier Humbert
-  - Aditoo
-  - Jonathan Chan
-  - Letiteuf55
-  - Baptiste Jonglez
-  - goofy-mdn
-  - Jean-Baptiste Holcroft
-  - Technowix
-  - Martin Bodin
-  - Théodore
-  - Thibaut Girka
-  - Franck Paul
-  - Sylvhem
-- **Galician**
-  - ButterflyOfFire
-  - Xose M.
-  - Aditoo
-  - manequim
-- **Georgian**
-  - ButterflyOfFire
-  - Aditoo
-- **German**
-  - Aditoo
-  - ButterflyOfFire
-  - Daniel
-  - averageunicorn
-  - Koyu Berteon
-  - larsreineke
-  - koyu
-  - Austin Jones
-  - lilo
-  - Benedikt Geißler
-  - ePirat
-  - Eugen Rochko
-  - Weblate Admin
-  - Patrick Figel
-- **Greek**
-  - Dimitris Maroulidis
-  - Antonis
-  - Aditoo
-  - ButterflyOfFire
-  - Konstantinos Grevenitis
-- **Hebrew**
-  - ButterflyOfFire
-  - Aditoo
-  - Ira
-  - Yaron Shahrabani
-- **Hungarian**
-  - ButterflyOfFire
-  - Adam Paszternak
-  - Aditoo
-  - Tibike Miklós
-- **Ido**
-  - ButterflyOfFire
-  - Aditoo
-- **Indonesian**
-  - afachri
-  - ButterflyOfFire
-  - Dito Kurnia Pratama
-  - Eirworks
-  - Aditoo
-  - Alfiana Sibuea
-  - se7entime
-- **Irish**
-  - Albakham
-  - Kevin Houlihan
-- **Italian**
-  - Alessandro Levati
-  - Albakham
-  - ButterflyOfFire
-  - Marcin Mikołajczak
-  - Aditoo
-  - Giuseppe Pignataro
-  - Stefano
-- **Japanese**
-  - Hinaloe
-  - 小鳥遊まりあ
-  - mayaeh
-  - osapon
-  - 森の子リスのミーコの大冒険
-  - Kumasun Morino
-  - Yamagishi Kazutoshi
-  - Aditoo
-  - ButterflyOfFire
-  - Jeong Arm
-  - unarist
-- **Kazakh**
-  - arshat
-  - Aditoo
-- **Korean**
-  - Aditoo
-  - Jeong Arm
-  - ButterflyOfFire
-  - Minori Hiraoka
-  - Yamagishi Kazutoshi
-- **Lithuanian**
-  - Sarunas Medeikis
-- **Malay**
-  - Muhammad Nur Hidayat (MNH48)
-  - Aditoo
-  - ButterflyOfFire
-- **Norwegian (old code)**
-  - ButterflyOfFire
-  - Espen Rønnevik
-  - Aditoo
-  - Tale
-- **Occitan**
-  - Aditoo
-  - ButterflyOfFire
-  - Quenti2
-  - Quentí
-  - Maxenç
-- **Persian**
-  - Masoud Abkenar
-  - Aditoo
-  - ButterflyOfFire
-- **Polish**
-  - Aditoo
-  - Albakham
-  - ButterflyOfFire
-  - Stasiek Michalski
-  - Marcin Mikołajczak
-  - Jakub Mendyk
-  - Marek Ľach
-  - krkk
-- **Portuguese**
-  - Albakham
-  - João Pinheiro
-  - manequim
-  - Aditoo
-  - ButterflyOfFire
-  - Hugo Gameiro
-- **Portuguese (Brazil)**
-  - Aditoo
-  - Albakham
-  - Anna e só
-  - Renato "Lond" Cerqueira
-  - André Andrade
-  - ButterflyOfFire
-- **Romanian**
-  - adrianbblk
-  - ButterflyOfFire
-  - Aditoo
-- **Russian**
-  - Albakham
-  - ButterflyOfFire
-  - Evgeny Petrov
-  - Aditoo
-  - Павел Гастелло
-  - Andrew Zyabin
-  - Yaron Shahrabani
-- **Serbian**
-  - Branko Kokanovic
-  - Burekz Finezt
-  - Aditoo
-  - ButterflyOfFire
-- **Serbian (latin)**
-  - ButterflyOfFire
-  - Aditoo
-- **Slovak**
-  - Aditoo
-  - ButterflyOfFire
-  - Ivan Pleva
-  - Marek Ľach
-  - Peter
-- **Slovenian**
-  - Kristijan Tkalec
-  - Aditoo
-  - ButterflyOfFire
-- **Spanish**
-  - Albakham
-  - ButterflyOfFire
-  - Carlos Mondragon
-  - Antón López
-  - Max Winkler
-  - Pablo de la Concepción Sanz
-  - Sergio Soriano
-  - Angeles Broullón
-  - Lothar Wolf
-  - Aditoo
-  - David Charte
-  - Emmanuel
-- **Swedish**
-  - ButterflyOfFire
-  - Isak Holmström
-  - Shellkr
-  - Aditoo
-  - Elias MÃ¥rtenson
-  - Stefan Midjich
-  - Tim Stahel
-  - Jonas Hultén
-- **Telugu**
-  - avndp
-  - Ranjith Tellakula
-  - Aditoo
-  - ButterflyOfFire
-  - Joseph Nuthalapati
-- **Thai**
-  - ButterflyOfFire
-  - parnikkapore
-  - Thai Localization
-  - Aditoo
-- **Turkish**
-  - Ali Demirtas
-  - ButterflyOfFire
-  - Aditoo
-- **Ukrainian**
-  - alexcleac
-  - ButterflyOfFire
-  - Aditoo
-  - Ivan Verchenko
-- **Welsh**
-  - carl morris
-  - Jaz-Michael King
-  - Owain Rhys Lewis
-  - Rhoslyn Prys
-  - Aditoo
-  - ButterflyOfFire
-  - Renato "Lond" Cerqueira
-  - Albakham
-  - Kevin Beynon
-- **Armenian**
-  - Aditoo
-  - ButterflyOfFire
-- **Latvian**
-  - Aditoo
-  - ButterflyOfFire
-  - Maigonis
-- **Tamil**
-  - Aditoo
-  - ButterflyOfFire
-  - Prasanna Venkadesh
+- Zoltán Gera (*Hungarian*)
+- Kristijan Tkalec (*Slovenian*)
+- Evert Prants (*Estonian*)
+- borys_sh (*Ukrainian*)
+- ButterflyOfFire (*Arabic; French*)
+- Osoitz (*Basque*)
+- oɹʇuʞ (*Spanish, Argentina*)
+- koyu (*German*)
+- Jeroen (*Dutch*)
+- Muha Aliss (*Turkish*)
+- 唐宗勛 (*Chinese Simplified*)
+- Jeong Arm (*Korean; Esperanto; Japanese*)
+- Oguz Ersen (*Turkish*)
+- spla (*Catalan*)
+- Ramdziana F Y (*Indonesian*)
+- Aditoo17 (*Czech*)
+- Xosé M. (*Galician*)
+- Roboron (*Spanish*)
+- Alix Rossi (*Corsican; French*)
+- Maya Minatsuki (*Japanese*)
+- Masoud Abkenar (*Persian*)
+- Thai Localization (*Thai*)
+- Marek Ľach (*Slovak; Polish*)
+- d5Ziif3K (*Ukrainian*)
+- lamnatos (*Greek*)
+- Emyn Nant Nefydd (*Welsh*)
+- Diluns (*Occitan*)
+- atarashiako (*Chinese Simplified*)
+- 101010 (*Polish*)
+- Yi-Jyun Pan (*Chinese Traditional*)
+- silkevicious (*Italian*)
+- FédiQuébec (*French*)
+- Jaz-Michael King (*Welsh*)
+- christalleras (*Norwegian Nynorsk*)
+- tykayn (*French*)
+- Alessandro Levati (*Italian*)
+- carolinagiorno (*Portuguese, Brazilian*)
+- taoxvx (*Danish*)
+- sabri (*Spanish*)
+- Sasha Sorokin (*Russian*)
+- shioko (*Chinese Simplified*)
+- Evgeny Petrov (*Russian*)
+- ariasuni (*French; Esperanto*)
+- Tiago Epifânio (*Portuguese*)
+- dxwc (*Bengali*)
+- liffon (*Swedish*)
+- Vanege (*Esperanto*)
+- Johan Schiff (*Swedish*)
+- kat (*Ukrainian; Russian*)
+- oti4500 (*Hungarian; Ukrainian*)
+- Juan José Salvador Piedra (*Spanish*)
+- diazepan (*Spanish*)
+- SHeija (*Finnish*)
+- Jack R (*Spanish*)
+- Saederup92 (*Danish*)
+- Stasiek Michalski (*Polish*)
+- Dewi (*Breton; French*)
+- cybergene (*Japanese*)
+- AW Unad (*Indonesian*)
+- Andrea Lo Iacono (*Italian*)
+- Ray (*Spanish*)
+- Unmual (*Spanish*)
+- Ryo (*Korean*)
+- juanda097 (*Spanish*)
+- Anunnakey (*Macedonian*)
+- Cutls (*Japanese*)
+- erikstl (*Esperanto*)
+- ruine (*Japanese*)
+- MadeInSteak (*Finnish*)
+- Sokratis Alichanidis (*Greek*)
+- dragnucs2 (*Arabic*)
+- frumble (*German*)
+- Rikard Linde (*Swedish*)
+- PPNplus (*Thai*)
+- arethsu (*Swedish*)
+- EPEMA YT (*German*)
+- Rhys Harrison (*Esperanto*)
+- KEINOS (*Japanese*)
+- filippodb (*Italian*)
+- JzshAC (*Chinese Simplified*)
+- Rintan1 (*Japanese*)
+- Antillion (*Spanish*)
+- hiphipvargas (*Portuguese*)
+- Ch. (*Korean*)
+- tctovsli (*Norwegian Nynorsk*)
+- vjasiegd (*Polish*)
+- SamitiMed (*Thai*)
+- umelard (*Hebrew*)
+- 硫酸鶏 (*Japanese*)
+- Adrián Lattes (*Spanish*)
+- Hinaloe (*Japanese*)
+- Renato "Lond" Cerqueira (*Portuguese, Brazilian*)
+- parnikkapore (*Thai*)
+- Marcin Mikołajczak (*Polish*)
+- 森の子リスのミーコの大冒険 (*Japanese*)
+- Marcepanek_ (*Polish*)
+- Sahak Petrosyan (*Armenian*)
+- Daniel Dimitrov (*Bulgarian*)
+- Hugh Liu (*Chinese Simplified*)
+- Rakino (*Chinese Simplified*)
+- hussama (*Portuguese, Brazilian*)
+- ThibG (*French*)
+- SnDer (*Dutch*)
+- PifyZ (*French*)
+- eichkat3r (*German*)
+- Karol Kosek (*Polish*)
+- Akarshan Biswas (*Bengali*)
+- Tradjincal (*French*)
+- Steven Tappert (*German*)
+- sergioaraujo1 (*Portuguese, Brazilian*)
+- mmokhi (*Persian*)
+- fedot (*Russian*)
+- skaaarrr (*German*)
+- JackXu (*Chinese Simplified*)
+- Lukas Fülling (*German*)
+- Zoé Bőle (*German*)
+- Dremski (*Bulgarian*)
+- tamaina (*Japanese*)
+- OpenAlgeria (*Arabic*)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a17fbf8f06385677eb05801b422998bb6613b9ea..b200747b12f5b81c4a476432e52a27f58aa006be 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,235 @@ Changelog
 
 All notable changes to this project will be documented in this file.
 
+## [3.0.1] - 2019-10-10
+### Added
+
+- Add `tootctl media usage` command ([Gargron](https://github.com/tootsuite/mastodon/pull/12115))
+- Add admin setting to auto-approve trending hashtags ([Gargron](https://github.com/tootsuite/mastodon/pull/12122), [Gargron](https://github.com/tootsuite/mastodon/pull/12130))
+
+### Changed
+
+- Change `tootctl media refresh` to skip already downloaded attachments ([Gargron](https://github.com/tootsuite/mastodon/pull/12118))
+
+### Removed
+
+- Remove auto-silence behaviour from spam check ([Gargron](https://github.com/tootsuite/mastodon/pull/12117))
+- Remove HTML `lang` attribute from individual statuses in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12124))
+- Remove fallback to long description on sidebar and meta description ([Gargron](https://github.com/tootsuite/mastodon/pull/12119))
+
+### Fixed
+
+- Fix preloaded JSON-LD context for identity not being used ([Gargron](https://github.com/tootsuite/mastodon/pull/12138))
+- Fix media editing modal changing dimensions once the image loads ([Gargron](https://github.com/tootsuite/mastodon/pull/12131))
+- Fix not showing whether a custom emoji has a local counterpart in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12135))
+- Fix attachment not being re-downloaded even if file is not stored ([Gargron](https://github.com/tootsuite/mastodon/pull/12125))
+- Fix old migration trying to use new column due to default status scope ([Gargron](https://github.com/tootsuite/mastodon/pull/12095))
+- Fix column back button missing for not found accounts ([trwnh](https://github.com/tootsuite/mastodon/pull/12094))
+- Fix issues with tootctl's parallelization and progress reporting ([Gargron](https://github.com/tootsuite/mastodon/pull/12093), [Gargron](https://github.com/tootsuite/mastodon/pull/12097))
+- Fix existing user records with now-renamed `pt` locale ([Gargron](https://github.com/tootsuite/mastodon/pull/12092))
+- Fix hashtag timeline REST API accepting too many hashtags ([Gargron](https://github.com/tootsuite/mastodon/pull/12091))
+- Fix `GET /api/v1/instance` REST APIs being unavailable in secure mode ([Gargron](https://github.com/tootsuite/mastodon/pull/12089))
+- Fix performance of home feed regeneration and merging ([Gargron](https://github.com/tootsuite/mastodon/pull/12084))
+- Fix ffmpeg performance issues due to stdout buffer overflow ([hugogameiro](https://github.com/tootsuite/mastodon/pull/12088))
+- Fix S3 adapter retrying failing uploads with exponential backoff ([Gargron](https://github.com/tootsuite/mastodon/pull/12085))
+- Fix `tootctl accounts cull` advertising unused option flag ([Kjwon15](https://github.com/tootsuite/mastodon/pull/12074))
+
+## [3.0.0] - 2019-10-03
+### Added
+
+- Add "not available" label to unloaded media attachments in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11715), [Gargron](https://github.com/tootsuite/mastodon/pull/11745))
+- **Add profile directory to web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11688), [mayaeh](https://github.com/tootsuite/mastodon/pull/11872))
+  - Add profile directory opt-in federation
+  - Add profile directory REST API
+- Add special alert for throttled requests in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11677))
+- Add confirmation modal when logging out from the web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11671))
+- **Add audio player in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11644), [Gargron](https://github.com/tootsuite/mastodon/pull/11652), [Gargron](https://github.com/tootsuite/mastodon/pull/11654), [ThibG](https://github.com/tootsuite/mastodon/pull/11629), [Gargron](https://github.com/tootsuite/mastodon/pull/12056))
+- **Add autosuggestions for hashtags in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11422), [ThibG](https://github.com/tootsuite/mastodon/pull/11632), [Gargron](https://github.com/tootsuite/mastodon/pull/11764), [Gargron](https://github.com/tootsuite/mastodon/pull/11588), [Gargron](https://github.com/tootsuite/mastodon/pull/11442))
+- **Add media editing modal with OCR tool in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11563), [Gargron](https://github.com/tootsuite/mastodon/pull/11566), [ThibG](https://github.com/tootsuite/mastodon/pull/11575), [ThibG](https://github.com/tootsuite/mastodon/pull/11576), [Gargron](https://github.com/tootsuite/mastodon/pull/11577), [Gargron](https://github.com/tootsuite/mastodon/pull/11573), [Gargron](https://github.com/tootsuite/mastodon/pull/11571))
+- Add indicator of unread notifications to window title when web UI is out of focus ([Gargron](https://github.com/tootsuite/mastodon/pull/11560), [Gargron](https://github.com/tootsuite/mastodon/pull/11572))
+- Add indicator for which options you voted for in a poll in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11195))
+- **Add search results pagination to web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11409), [ThibG](https://github.com/tootsuite/mastodon/pull/11447))
+- **Add option to disable real-time updates in web UI ("slow mode")** ([Gargron](https://github.com/tootsuite/mastodon/pull/9984), [ykzts](https://github.com/tootsuite/mastodon/pull/11880), [ThibG](https://github.com/tootsuite/mastodon/pull/11883), [Gargron](https://github.com/tootsuite/mastodon/pull/11898), [ThibG](https://github.com/tootsuite/mastodon/pull/11859))
+- Add option to disable blurhash previews in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11188))
+- Add native smooth scrolling when supported in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11207))
+- Add scrolling to the search bar on focus in web UI ([Kjwon15](https://github.com/tootsuite/mastodon/pull/12032))
+- Add refresh button to list of rebloggers/favouriters in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12031))
+- Add error description and button to copy stack trace to web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12033))
+- Add search and sort functions to hashtag admin UI ([mayaeh](https://github.com/tootsuite/mastodon/pull/11829), [Gargron](https://github.com/tootsuite/mastodon/pull/11897), [mayaeh](https://github.com/tootsuite/mastodon/pull/11875))
+- Add setting for default search engine indexing in admin UI ([brortao](https://github.com/tootsuite/mastodon/pull/11804))
+- Add account bio to account view in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11473))
+- **Add option to include reported statuses in warning e-mail from admin UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11639), [Gargron](https://github.com/tootsuite/mastodon/pull/11812), [Gargron](https://github.com/tootsuite/mastodon/pull/11741), [Gargron](https://github.com/tootsuite/mastodon/pull/11698), [mayaeh](https://github.com/tootsuite/mastodon/pull/11765))
+- Add number of pending accounts and pending hashtags to dashboard in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11514))
+- **Add account migration UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11846), [noellabo](https://github.com/tootsuite/mastodon/pull/11905), [noellabo](https://github.com/tootsuite/mastodon/pull/11907), [noellabo](https://github.com/tootsuite/mastodon/pull/11906), [noellabo](https://github.com/tootsuite/mastodon/pull/11902))
+- **Add table of contents to about page** ([Gargron](https://github.com/tootsuite/mastodon/pull/11885), [ykzts](https://github.com/tootsuite/mastodon/pull/11941), [ykzts](https://github.com/tootsuite/mastodon/pull/11895), [Kjwon15](https://github.com/tootsuite/mastodon/pull/11916))
+- **Add password challenge to 2FA settings, e-mail notifications** ([Gargron](https://github.com/tootsuite/mastodon/pull/11878))
+- **Add optional public list of domain blocks with comments** ([ThibG](https://github.com/tootsuite/mastodon/pull/11298), [ThibG](https://github.com/tootsuite/mastodon/pull/11515), [Gargron](https://github.com/tootsuite/mastodon/pull/11908))
+- Add an RSS feed for featured hashtags ([noellabo](https://github.com/tootsuite/mastodon/pull/10502))
+- Add explanations to featured hashtags UI and profile ([Gargron](https://github.com/tootsuite/mastodon/pull/11586))
+- **Add hashtag trends with admin and user settings** ([Gargron](https://github.com/tootsuite/mastodon/pull/11490), [Gargron](https://github.com/tootsuite/mastodon/pull/11502), [Gargron](https://github.com/tootsuite/mastodon/pull/11641), [Gargron](https://github.com/tootsuite/mastodon/pull/11594), [Gargron](https://github.com/tootsuite/mastodon/pull/11517), [mayaeh](https://github.com/tootsuite/mastodon/pull/11845), [Gargron](https://github.com/tootsuite/mastodon/pull/11774), [Gargron](https://github.com/tootsuite/mastodon/pull/11712), [Gargron](https://github.com/tootsuite/mastodon/pull/11791), [Gargron](https://github.com/tootsuite/mastodon/pull/11743), [Gargron](https://github.com/tootsuite/mastodon/pull/11740), [Gargron](https://github.com/tootsuite/mastodon/pull/11714), [ThibG](https://github.com/tootsuite/mastodon/pull/11631), [Sasha-Sorokin](https://github.com/tootsuite/mastodon/pull/11569), [Gargron](https://github.com/tootsuite/mastodon/pull/11524), [Gargron](https://github.com/tootsuite/mastodon/pull/11513))
+  - Add hashtag usage breakdown to admin UI
+  - Add batch actions for hashtags to admin UI
+  - Add trends to web UI
+  - Add trends to public pages
+  - Add user preference to hide trends
+  - Add admin setting to disable trends
+- **Add categories for custom emojis** ([Gargron](https://github.com/tootsuite/mastodon/pull/11196), [Gargron](https://github.com/tootsuite/mastodon/pull/11793), [Gargron](https://github.com/tootsuite/mastodon/pull/11920), [highemerly](https://github.com/tootsuite/mastodon/pull/11876))
+  - Add custom emoji categories to emoji picker in web UI
+  - Add `category` to custom emojis in REST API
+  - Add batch actions for custom emojis in admin UI
+- Add max image dimensions to error message ([raboof](https://github.com/tootsuite/mastodon/pull/11552))
+- Add aac, m4a, 3gp, amr, wma to allowed audio formats ([Gargron](https://github.com/tootsuite/mastodon/pull/11342), [umonaca](https://github.com/tootsuite/mastodon/pull/11687))
+- **Add search syntax for operators and phrases** ([Gargron](https://github.com/tootsuite/mastodon/pull/11411))
+- **Add REST API for managing featured hashtags** ([noellabo](https://github.com/tootsuite/mastodon/pull/11778))
+- **Add REST API for managing timeline read markers** ([Gargron](https://github.com/tootsuite/mastodon/pull/11762))
+- Add `exclude_unreviewed` param to `GET /api/v2/search` REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/11977))
+- Add `reason` param to `POST /api/v1/accounts` REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/12064))
+- **Add ActivityPub secure mode** ([Gargron](https://github.com/tootsuite/mastodon/pull/11269), [ThibG](https://github.com/tootsuite/mastodon/pull/11332), [ThibG](https://github.com/tootsuite/mastodon/pull/11295))
+- Add HTTP signatures to all outgoing ActivityPub GET requests ([Gargron](https://github.com/tootsuite/mastodon/pull/11284), [ThibG](https://github.com/tootsuite/mastodon/pull/11300))
+- Add support for ActivityPub Audio activities ([ThibG](https://github.com/tootsuite/mastodon/pull/11189))
+- Add ActivityPub actor representing the entire server ([ThibG](https://github.com/tootsuite/mastodon/pull/11321), [rtucker](https://github.com/tootsuite/mastodon/pull/11400), [ThibG](https://github.com/tootsuite/mastodon/pull/11561), [Gargron](https://github.com/tootsuite/mastodon/pull/11798))
+- **Add whitelist mode** ([Gargron](https://github.com/tootsuite/mastodon/pull/11291), [mayaeh](https://github.com/tootsuite/mastodon/pull/11634))
+- Add config of multipart threshold for S3 ([ykzts](https://github.com/tootsuite/mastodon/pull/11924), [ykzts](https://github.com/tootsuite/mastodon/pull/11944))
+- Add health check endpoint for web ([ykzts](https://github.com/tootsuite/mastodon/pull/11770), [ykzts](https://github.com/tootsuite/mastodon/pull/11947))
+- Add HTTP signature keyId to request log ([Gargron](https://github.com/tootsuite/mastodon/pull/11591))
+- Add `SMTP_REPLY_TO` environment variable ([hugogameiro](https://github.com/tootsuite/mastodon/pull/11718))
+- Add `tootctl preview_cards remove` command ([mayaeh](https://github.com/tootsuite/mastodon/pull/11320))
+- Add `tootctl media refresh` command ([Gargron](https://github.com/tootsuite/mastodon/pull/11775))
+- Add `tootctl cache recount` command ([Gargron](https://github.com/tootsuite/mastodon/pull/11597))
+- Add option to exclude suspended domains from `tootctl domains crawl` ([dariusk](https://github.com/tootsuite/mastodon/pull/11454))
+- Add parallelization to `tootctl search deploy` ([noellabo](https://github.com/tootsuite/mastodon/pull/12051))
+- Add soft delete for statuses for instant deletes through API ([Gargron](https://github.com/tootsuite/mastodon/pull/11623), [Gargron](https://github.com/tootsuite/mastodon/pull/11648))
+- Add rails-level JSON caching ([Gargron](https://github.com/tootsuite/mastodon/pull/11333), [Gargron](https://github.com/tootsuite/mastodon/pull/11271))
+- **Add request pool to improve delivery performance** ([Gargron](https://github.com/tootsuite/mastodon/pull/10353), [ykzts](https://github.com/tootsuite/mastodon/pull/11756))
+- Add concurrent connection attempts to resolved IP addresses ([ThibG](https://github.com/tootsuite/mastodon/pull/11757))
+- Add index for remember_token to improve login performance ([abcang](https://github.com/tootsuite/mastodon/pull/11881))
+- **Add more accurate hashtag search** ([Gargron](https://github.com/tootsuite/mastodon/pull/11579), [Gargron](https://github.com/tootsuite/mastodon/pull/11427), [Gargron](https://github.com/tootsuite/mastodon/pull/11448))
+- **Add more accurate account search** ([Gargron](https://github.com/tootsuite/mastodon/pull/11537), [Gargron](https://github.com/tootsuite/mastodon/pull/11580))
+- **Add a spam check** ([Gargron](https://github.com/tootsuite/mastodon/pull/11217), [Gargron](https://github.com/tootsuite/mastodon/pull/11806), [ThibG](https://github.com/tootsuite/mastodon/pull/11296))
+- Add new languages ([Gargron](https://github.com/tootsuite/mastodon/pull/12062))
+  - Breton
+  - Spanish (Argentina)
+  - Estonian
+  - Macedonian
+  - New Norwegian
+- Add NodeInfo endpoint ([Gargron](https://github.com/tootsuite/mastodon/pull/12002), [Gargron](https://github.com/tootsuite/mastodon/pull/12058))
+
+### Changed
+
+- **Change conversations UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/11896))
+- Change dashboard to short number notation ([noellabo](https://github.com/tootsuite/mastodon/pull/11847), [noellabo](https://github.com/tootsuite/mastodon/pull/11911))
+- Change REST API `GET /api/v1/timelines/public` to require authentication when public preview is off ([ThibG](https://github.com/tootsuite/mastodon/pull/11802))
+- Change REST API `POST /api/v1/follow_requests/:id/(approve|reject)` to return relationship ([ThibG](https://github.com/tootsuite/mastodon/pull/11800))
+- Change rate limit for media proxy ([ykzts](https://github.com/tootsuite/mastodon/pull/11814))
+- Change unlisted custom emoji to not appear in autosuggestions ([Gargron](https://github.com/tootsuite/mastodon/pull/11818))
+- Change max length of media descriptions from 420 to 1500 characters ([Gargron](https://github.com/tootsuite/mastodon/pull/11819), [ThibG](https://github.com/tootsuite/mastodon/pull/11836))
+- **Change deletes to preserve soft-deleted statuses in unresolved reports** ([Gargron](https://github.com/tootsuite/mastodon/pull/11805))
+- **Change tootctl to use inline parallelization instead of Sidekiq** ([Gargron](https://github.com/tootsuite/mastodon/pull/11776))
+- **Change account deletion page to have better explanations** ([Gargron](https://github.com/tootsuite/mastodon/pull/11753), [Gargron](https://github.com/tootsuite/mastodon/pull/11763))
+- Change hashtag component in web UI to show numbers for 2 last days ([Gargron](https://github.com/tootsuite/mastodon/pull/11742), [Gargron](https://github.com/tootsuite/mastodon/pull/11755), [Gargron](https://github.com/tootsuite/mastodon/pull/11754))
+- Change OpenGraph description on sign-up page to reflect invite ([Gargron](https://github.com/tootsuite/mastodon/pull/11744))
+- Change layout of public profile directory to be the same as in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11705))
+- Change detailed status child ordering to sort self-replies on top ([ThibG](https://github.com/tootsuite/mastodon/pull/11686))
+- Change window resize handler to switch to/from mobile layout as soon as needed ([ThibG](https://github.com/tootsuite/mastodon/pull/11656))
+- Change icon button styles to make hover/focus states more obvious ([ThibG](https://github.com/tootsuite/mastodon/pull/11474))
+- Change contrast of status links that are not mentions or hashtags ([ThibG](https://github.com/tootsuite/mastodon/pull/11406))
+- **Change hashtags to preserve first-used casing** ([Gargron](https://github.com/tootsuite/mastodon/pull/11416), [Gargron](https://github.com/tootsuite/mastodon/pull/11508), [Gargron](https://github.com/tootsuite/mastodon/pull/11504), [Gargron](https://github.com/tootsuite/mastodon/pull/11507), [Gargron](https://github.com/tootsuite/mastodon/pull/11441))
+- **Change unconfirmed user login behaviour** ([Gargron](https://github.com/tootsuite/mastodon/pull/11375), [ThibG](https://github.com/tootsuite/mastodon/pull/11394), [Gargron](https://github.com/tootsuite/mastodon/pull/11860))
+- **Change single-column mode to scroll the whole page** ([Gargron](https://github.com/tootsuite/mastodon/pull/11359), [Gargron](https://github.com/tootsuite/mastodon/pull/11894), [Gargron](https://github.com/tootsuite/mastodon/pull/11891), [ThibG](https://github.com/tootsuite/mastodon/pull/11655), [Gargron](https://github.com/tootsuite/mastodon/pull/11463), [Gargron](https://github.com/tootsuite/mastodon/pull/11458), [ThibG](https://github.com/tootsuite/mastodon/pull/11395), [Gargron](https://github.com/tootsuite/mastodon/pull/11418))
+- Change `tootctl accounts follow` to only work with local accounts ([angristan](https://github.com/tootsuite/mastodon/pull/11592))
+- Change Dockerfile ([Shleeble](https://github.com/tootsuite/mastodon/pull/11710), [ykzts](https://github.com/tootsuite/mastodon/pull/11768), [Shleeble](https://github.com/tootsuite/mastodon/pull/11707))
+- Change supported Node versions to include v12 ([abcang](https://github.com/tootsuite/mastodon/pull/11706))
+- Change Portuguese language from `pt` to `pt-PT` ([Gargron](https://github.com/tootsuite/mastodon/pull/11820))
+- Change domain block silence to always require approval on follow ([ThibG](https://github.com/tootsuite/mastodon/pull/11975))
+- Change link preview fetcher to not perform a HEAD request first ([Gargron](https://github.com/tootsuite/mastodon/pull/12028))
+- Change `tootctl domains purge` to accept multiple domains at once ([Gargron](https://github.com/tootsuite/mastodon/pull/12046))
+
+### Removed
+
+- **Remove OStatus support** ([Gargron](https://github.com/tootsuite/mastodon/pull/11205), [Gargron](https://github.com/tootsuite/mastodon/pull/11303), [Gargron](https://github.com/tootsuite/mastodon/pull/11460), [ThibG](https://github.com/tootsuite/mastodon/pull/11280), [ThibG](https://github.com/tootsuite/mastodon/pull/11278))
+- Remove Atom feeds and old URLs in the form of `GET /:username/updates/:id` ([Gargron](https://github.com/tootsuite/mastodon/pull/11247))
+- Remove WebP support ([angristan](https://github.com/tootsuite/mastodon/pull/11589))
+- Remove deprecated config options from Heroku and Scalingo ([ykzts](https://github.com/tootsuite/mastodon/pull/11925))
+- Remove deprecated REST API `GET /api/v1/search` API ([Gargron](https://github.com/tootsuite/mastodon/pull/11823))
+- Remove deprecated REST API `GET /api/v1/statuses/:id/card` ([Gargron](https://github.com/tootsuite/mastodon/pull/11213))
+- Remove deprecated REST API `POST /api/v1/notifications/dismiss?id=:id` ([Gargron](https://github.com/tootsuite/mastodon/pull/11214))
+- Remove deprecated REST API `GET /api/v1/timelines/direct` ([Gargron](https://github.com/tootsuite/mastodon/pull/11212))
+
+### Fixed
+
+- Fix manifest warning ([ykzts](https://github.com/tootsuite/mastodon/pull/11767))
+- Fix admin UI for custom emoji not respecting GIF autoplay preference ([ThibG](https://github.com/tootsuite/mastodon/pull/11801))
+- Fix page body not being scrollable in admin/settings layout ([Gargron](https://github.com/tootsuite/mastodon/pull/11893))
+- Fix placeholder colors for inputs not being explicitly defined ([Gargron](https://github.com/tootsuite/mastodon/pull/11890))
+- Fix incorrect enclosure length in RSS ([tsia](https://github.com/tootsuite/mastodon/pull/11889))
+- Fix TOTP codes not being filtered from logs during enabling/disabling ([Gargron](https://github.com/tootsuite/mastodon/pull/11877))
+- Fix webfinger response not returning 410 when account is suspended ([Gargron](https://github.com/tootsuite/mastodon/pull/11869))
+- Fix ActivityPub Move handler queuing jobs that will fail if account is suspended ([Gargron](https://github.com/tootsuite/mastodon/pull/11864))
+- Fix SSO login not using existing account when e-mail is verified ([Gargron](https://github.com/tootsuite/mastodon/pull/11862))
+- Fix web UI allowing uploads past status limit via drag & drop ([Gargron](https://github.com/tootsuite/mastodon/pull/11863))
+- Fix expiring polls not being displayed as such in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11835))
+- Fix 2FA challenge and password challenge for non-database users ([Gargron](https://github.com/tootsuite/mastodon/pull/11831), [Gargron](https://github.com/tootsuite/mastodon/pull/11943))
+- Fix profile fields overflowing page width in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11828))
+- Fix web push subscriptions being deleted on rate limit or timeout ([Gargron](https://github.com/tootsuite/mastodon/pull/11826))
+- Fix display of long poll options in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11717), [ThibG](https://github.com/tootsuite/mastodon/pull/11833))
+- Fix search API not resolving URL when `type` is given ([Gargron](https://github.com/tootsuite/mastodon/pull/11822))
+- Fix hashtags being split by ZWNJ character ([Gargron](https://github.com/tootsuite/mastodon/pull/11821))
+- Fix scroll position resetting when opening media modals in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11815))
+- Fix duplicate HTML IDs on about page ([ThibG](https://github.com/tootsuite/mastodon/pull/11803))
+- Fix admin UI showing superfluous reject media/reports on suspended domain blocks ([ThibG](https://github.com/tootsuite/mastodon/pull/11749))
+- Fix ActivityPub context not being dynamically computed ([ThibG](https://github.com/tootsuite/mastodon/pull/11746))
+- Fix Mastodon logo style on hover on public pages' footer ([ThibG](https://github.com/tootsuite/mastodon/pull/11735))
+- Fix height of dashboard counters ([ThibG](https://github.com/tootsuite/mastodon/pull/11736))
+- Fix custom emoji animation on hover in web UI directory bios ([ThibG](https://github.com/tootsuite/mastodon/pull/11716))
+- Fix non-numbers being passed to Redis and causing an error ([Gargron](https://github.com/tootsuite/mastodon/pull/11697))
+- Fix error in REST API for an account's statuses ([Gargron](https://github.com/tootsuite/mastodon/pull/11700))
+- Fix uncaught error when resource param is missing in Webfinger request ([Gargron](https://github.com/tootsuite/mastodon/pull/11701))
+- Fix uncaught domain normalization error in remote follow ([Gargron](https://github.com/tootsuite/mastodon/pull/11703))
+- Fix uncaught 422 and 500 errors ([Gargron](https://github.com/tootsuite/mastodon/pull/11590), [Gargron](https://github.com/tootsuite/mastodon/pull/11811))
+- Fix uncaught parameter missing exceptions and missing error templates ([Gargron](https://github.com/tootsuite/mastodon/pull/11702))
+- Fix encoding error when checking e-mail MX records ([Gargron](https://github.com/tootsuite/mastodon/pull/11696))
+- Fix items in StatusContent render list not all having a key ([ThibG](https://github.com/tootsuite/mastodon/pull/11645))
+- Fix remote and staff-removed statuses leaving media behind for a day ([Gargron](https://github.com/tootsuite/mastodon/pull/11638))
+- Fix CSP needlessly allowing blob URLs in script-src ([ThibG](https://github.com/tootsuite/mastodon/pull/11620))
+- Fix ignoring whole status because of one invalid hashtag ([Gargron](https://github.com/tootsuite/mastodon/pull/11621))
+- Fix hidden statuses losing focus ([ThibG](https://github.com/tootsuite/mastodon/pull/11208))
+- Fix loading bar being obscured by other elements in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11598))
+- Fix multiple issues with replies collection for pages further than self-replies ([ThibG](https://github.com/tootsuite/mastodon/pull/11582))
+- Fix blurhash and autoplay not working on public pages ([Gargron](https://github.com/tootsuite/mastodon/pull/11585))
+- Fix 422 being returned instead of 404 when POSTing to unmatched routes ([Gargron](https://github.com/tootsuite/mastodon/pull/11574), [Gargron](https://github.com/tootsuite/mastodon/pull/11704))
+- Fix client-side resizing of image uploads ([ThibG](https://github.com/tootsuite/mastodon/pull/11570))
+- Fix short number formatting for numbers above million in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11559))
+- Fix ActivityPub and REST API queries setting cookies and preventing caching ([ThibG](https://github.com/tootsuite/mastodon/pull/11539), [ThibG](https://github.com/tootsuite/mastodon/pull/11557), [ThibG](https://github.com/tootsuite/mastodon/pull/11336), [ThibG](https://github.com/tootsuite/mastodon/pull/11331))
+- Fix some emojis in profile metadata labels are not emojified. ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/11534))
+- Fix account search always returning exact match on paginated results ([Gargron](https://github.com/tootsuite/mastodon/pull/11525))
+- Fix acct URIs with IDN domains not being resolved ([Gargron](https://github.com/tootsuite/mastodon/pull/11520))
+- Fix admin dashboard missing latest features ([Gargron](https://github.com/tootsuite/mastodon/pull/11505))
+- Fix jumping of toot date when clicking spoiler button ([ariasuni](https://github.com/tootsuite/mastodon/pull/11449))
+- Fix boost to original audience not working on mobile in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11371))
+- Fix handling of webfinger redirects in ResolveAccountService ([ThibG](https://github.com/tootsuite/mastodon/pull/11279))
+- Fix URLs appearing twice in errors of ActivityPub::DeliveryWorker ([Gargron](https://github.com/tootsuite/mastodon/pull/11231))
+- Fix support for HTTP proxies ([ThibG](https://github.com/tootsuite/mastodon/pull/11245))
+- Fix HTTP requests to IPv6 hosts ([ThibG](https://github.com/tootsuite/mastodon/pull/11240))
+- Fix error in ElasticSearch index import ([mayaeh](https://github.com/tootsuite/mastodon/pull/11192))
+- Fix duplicate account error when seeding development database ([ysksn](https://github.com/tootsuite/mastodon/pull/11366))
+- Fix performance of session clean-up scheduler ([abcang](https://github.com/tootsuite/mastodon/pull/11871))
+- Fix older migrations not running ([zunda](https://github.com/tootsuite/mastodon/pull/11377))
+- Fix URLs counting towards RTL detection ([ahangarha](https://github.com/tootsuite/mastodon/pull/11759))
+- Fix unnecessary status re-rendering in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/11211))
+- Fix http_parser.rb gem not being compiled when no network available ([petabyteboy](https://github.com/tootsuite/mastodon/pull/11444))
+- Fix muted text color not applying to all text ([trwnh](https://github.com/tootsuite/mastodon/pull/11996))
+- Fix follower/following lists resetting on back-navigation in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11986))
+- Fix n+1 query when approving multiple follow requests ([abcang](https://github.com/tootsuite/mastodon/pull/12004))
+- Fix records not being indexed into ElasticSearch sometimes ([Gargron](https://github.com/tootsuite/mastodon/pull/12024))
+- Fix needlessly indexing unsearchable statuses into ElasticSearch ([Gargron](https://github.com/tootsuite/mastodon/pull/12041))
+- Fix new user bootstrapping crashing when to-be-followed accounts are invalid ([ThibG](https://github.com/tootsuite/mastodon/pull/12037))
+- Fix featured hashtag URL being interpreted as media or replies tab ([Gargron](https://github.com/tootsuite/mastodon/pull/12048))
+- Fix account counters being overwritten by parallel writes ([Gargron](https://github.com/tootsuite/mastodon/pull/12045))
+
+### Security
+
+- Fix performance of GIF re-encoding and always strip EXIF data from videos ([Gargron](https://github.com/tootsuite/mastodon/pull/12057))
+
 ## [2.9.3] - 2019-08-10
 ### Added
 
diff --git a/Dockerfile b/Dockerfile
index d8c7e0f0c53ef2de2588aac1b8489e51439cafe1..e963674a55eda713d915e846401447e762edfacd 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,22 +4,20 @@ FROM ubuntu:18.04 as build-dep
 SHELL ["bash", "-c"]
 
 # Install Node
-ENV NODE_VER="8.15.0"
+ENV NODE_VER="12.11.1"
 RUN	echo "Etc/UTC" > /etc/localtime && \
 	apt update && \
-	apt -y install wget make gcc g++ python && \
+	apt -y install wget python && \
 	cd ~ && \
-	wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER.tar.gz && \
-	tar xf node-v$NODE_VER.tar.gz && \
-	cd node-v$NODE_VER && \
-	./configure --prefix=/opt/node && \
-	make -j$(nproc) > /dev/null && \
-	make install
+	wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-x64.tar.gz && \
+	tar xf node-v$NODE_VER-linux-x64.tar.gz && \
+	rm node-v$NODE_VER-linux-x64.tar.gz && \
+	mv node-v$NODE_VER-linux-x64 /opt/node
 
 # Install jemalloc
-ENV JE_VER="5.1.0"
+ENV JE_VER="5.2.1"
 RUN apt update && \
-	apt -y install autoconf && \
+	apt -y install make autoconf gcc g++ && \
 	cd ~ && \
 	wget https://github.com/jemalloc/jemalloc/archive/$JE_VER.tar.gz && \
 	tar xf $JE_VER.tar.gz && \
@@ -30,7 +28,7 @@ RUN apt update && \
 	make install_bin install_include install_lib
 
 # Install ruby
-ENV RUBY_VER="2.6.1"
+ENV RUBY_VER="2.6.5"
 ENV CPPFLAGS="-I/opt/jemalloc/include"
 ENV LDFLAGS="-L/opt/jemalloc/lib/"
 RUN apt update && \
diff --git a/Gemfile b/Gemfile
index b72f550eb9ea3a32144ae7e9e78f30d0751b5fc5..6f1fcb6f104c78520cf9f7fc2117951f073eac77 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,17 +5,17 @@ ruby '>= 2.4.0', '< 2.7.0'
 
 gem 'pkg-config', '~> 1.3'
 
-gem 'puma', '~> 3.12'
+gem 'puma', '~> 4.2'
 gem 'rails', '~> 5.2.3'
 gem 'thor', '~> 0.20'
 
 gem 'hamlit-rails', '~> 0.2'
 gem 'pg', '~> 1.1'
 gem 'makara', '~> 0.4'
-gem 'pghero', '~> 2.2'
+gem 'pghero', '~> 2.3'
 gem 'dotenv-rails', '~> 2.7'
 
-gem 'aws-sdk-s3', '~> 1.42', require: false
+gem 'aws-sdk-s3', '~> 1.48', require: false
 gem 'fog-core', '<= 2.1.0'
 gem 'fog-openstack', '~> 0.3', require: false
 gem 'paperclip', '~> 6.0'
@@ -24,15 +24,15 @@ gem 'streamio-ffmpeg', '~> 3.0'
 gem 'blurhash', '~> 0.1'
 
 gem 'active_model_serializers', '~> 0.10'
-gem 'addressable', '~> 2.6'
+gem 'addressable', '~> 2.7'
 gem 'bootsnap', '~> 1.4', require: false
 gem 'browser'
 gem 'charlock_holmes', '~> 0.7.6'
 gem 'iso-639'
-gem 'chewy', '~> 5.0'
+gem 'chewy', '~> 5.1'
 gem 'cld3', '~> 3.2.4'
-gem 'devise', '~> 4.6'
-gem 'devise-two-factor', '~> 3.0'
+gem 'devise', '~> 4.7'
+gem 'devise-two-factor', '~> 3.1'
 
 group :pam_authentication, optional: true do
   gem 'devise_pam_authenticatable2', '~> 9.2'
@@ -43,54 +43,60 @@ gem 'omniauth-cas', '~> 1.1'
 gem 'omniauth-saml', '~> 1.10'
 gem 'omniauth', '~> 1.9'
 
-gem 'doorkeeper', '~> 5.1'
+gem 'discard', '~> 1.1'
+gem 'doorkeeper', '~> 5.2'
 gem 'fast_blank', '~> 1.0'
 gem 'fastimage'
 gem 'goldfinger', '~> 2.1'
 gem 'hiredis', '~> 0.6'
 gem 'redis-namespace', '~> 1.5'
+gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
 gem 'htmlentities', '~> 4.3'
 gem 'http', '~> 3.3'
 gem 'http_accept_language', '~> 2.1'
-gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2'
+gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2', submodules: true
 gem 'httplog', '~> 1.3'
 gem 'idn-ruby', require: 'idn'
 gem 'kaminari', '~> 1.1'
 gem 'link_header', '~> 0.0'
-gem 'mime-types', '~> 3.2', require: 'mime/types/columnar'
+gem 'mime-types', '~> 3.3', require: 'mime/types/columnar'
+gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
 gem 'nokogiri', '~> 1.10'
 gem 'nsa', '~> 0.2'
-gem 'oj', '~> 3.7'
+gem 'oj', '~> 3.9'
 gem 'ostatus2', '~> 2.0'
 gem 'ox', '~> 2.11'
+gem 'parslet'
+gem 'parallel', '~> 1.17'
 gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
-gem 'pundit', '~> 2.0'
+gem 'pundit', '~> 2.1'
 gem 'premailer-rails'
-gem 'rack-attack', '~> 6.0'
+gem 'rack-attack', '~> 6.1'
 gem 'rack-cors', '~> 1.0', require: 'rack/cors'
 gem 'rails-i18n', '~> 5.1'
 gem 'rails-settings-cached', '~> 0.6'
 gem 'redis', '~> 4.1', require: ['redis', 'redis/connection/hiredis']
 gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
 gem 'rqrcode', '~> 0.10'
-gem 'sanitize', '~> 5.0'
+gem 'ruby-progressbar', '~> 1.10'
+gem 'sanitize', '~> 5.1'
 gem 'sidekiq', '~> 5.2'
 gem 'sidekiq-scheduler', '~> 3.0'
 gem 'sidekiq-unique-jobs', '~> 6.0'
 gem 'sidekiq-bulk', '~>0.2.0'
-gem 'simple-navigation', '~> 4.0'
+gem 'simple-navigation', '~> 4.1'
 gem 'simple_form', '~> 4.1'
 gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
 gem 'stoplight', '~> 2.1.3'
 gem 'strong_migrations', '~> 0.4'
-gem 'tty-command', '~> 0.8', require: false
+gem 'tty-command', '~> 0.9', require: false
 gem 'tty-prompt', '~> 0.19', require: false
 gem 'twitter-text', '~> 1.14'
 gem 'tzinfo-data', '~> 1.2019'
 gem 'webpacker', '~> 4.0'
 gem 'webpush'
 
-gem 'json-ld', '~> 3.0'
+gem 'json-ld', git: 'https://github.com/ruby-rdf/json-ld.git', ref: 'e742697a0906e74e8bb777ef98137bc3955d981d'
 gem 'json-ld-preloaded', '~> 3.0'
 gem 'rdf-normalize', '~> 0.3'
 
@@ -108,14 +114,14 @@ group :production, :test do
 end
 
 group :test do
-  gem 'capybara', '~> 3.24'
+  gem 'capybara', '~> 3.29'
   gem 'climate_control', '~> 0.2'
-  gem 'faker', '~> 1.9'
+  gem 'faker', '~> 2.5'
   gem 'microformats', '~> 4.1'
   gem 'rails-controller-testing', '~> 1.0'
   gem 'rspec-sidekiq', '~> 3.0'
-  gem 'simplecov', '~> 0.16', require: false
-  gem 'webmock', '~> 3.6'
+  gem 'simplecov', '~> 0.17', require: false
+  gem 'webmock', '~> 3.7'
   gem 'parallel_tests', '~> 2.29'
 end
 
@@ -128,9 +134,9 @@ group :development do
   gem 'letter_opener', '~> 1.7'
   gem 'letter_opener_web', '~> 1.3'
   gem 'memory_profiler'
-  gem 'rubocop', '~> 0.71', require: false
-  gem 'rubocop-rails', '~> 2.0', require: false
-  gem 'brakeman', '~> 4.5', require: false
+  gem 'rubocop', '~> 0.74', require: false
+  gem 'rubocop-rails', '~> 2.3', require: false
+  gem 'brakeman', '~> 4.6', require: false
   gem 'bundler-audit', '~> 0.6', require: false
 
   gem 'capistrano', '~> 3.11'
@@ -148,3 +154,4 @@ group :production do
 end
 
 gem 'concurrent-ruby', require: false
+gem 'connection_pool', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index bda915dfada1079d7c415b6cca7aae0aa9bfeeff..3c52f378fab1e7dc7ab426457a81e7a2b690660c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,3 +1,11 @@
+GIT
+  remote: https://github.com/ianheggie/health_check
+  revision: 0b799ead604f900ed50685e9b2d469cd2befba5b
+  ref: 0b799ead604f900ed50685e9b2d469cd2befba5b
+  specs:
+    health_check (4.0.0.pre)
+      rails (>= 4.0)
+
 GIT
   remote: https://github.com/rtomayko/posix-spawn
   revision: 58465d2e213991f8afb13b984854a49fcdcc980c
@@ -5,13 +13,34 @@ GIT
   specs:
     posix-spawn (0.3.13)
 
+GIT
+  remote: https://github.com/ruby-rdf/json-ld.git
+  revision: e742697a0906e74e8bb777ef98137bc3955d981d
+  ref: e742697a0906e74e8bb777ef98137bc3955d981d
+  specs:
+    json-ld (3.0.2)
+      htmlentities (~> 4.3)
+      json-canonicalization (~> 0.1)
+      link_header (~> 0.0, >= 0.0.8)
+      multi_json (~> 1.13)
+      rack (>= 1.6, < 3.0)
+      rdf (~> 3.0, >= 3.0.8)
+
 GIT
   remote: https://github.com/tmm1/http_parser.rb
   revision: 54b17ba8c7d8d20a16dfc65d1775241833219cf2
   ref: 54b17ba8c7d8d20a16dfc65d1775241833219cf2
+  submodules: true
   specs:
     http_parser.rb (0.6.1)
 
+GIT
+  remote: https://github.com/witgo/nilsimsa
+  revision: fd184883048b922b176939f851338d0a4971a532
+  ref: fd184883048b922b176939f851338d0a4971a532
+  specs:
+    nilsimsa (1.1.2)
+
 GEM
   remote: https://rubygems.org/
   specs:
@@ -38,9 +67,9 @@ GEM
       erubi (~> 1.4)
       rails-dom-testing (~> 2.0)
       rails-html-sanitizer (~> 1.0, >= 1.0.3)
-    active_model_serializers (0.10.9)
-      actionpack (>= 4.1, < 6)
-      activemodel (>= 4.1, < 6)
+    active_model_serializers (0.10.10)
+      actionpack (>= 4.1, < 6.1)
+      activemodel (>= 4.1, < 6.1)
       case_transform (>= 0.2)
       jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
     active_record_query_trace (1.6.2)
@@ -62,9 +91,9 @@ GEM
       i18n (>= 0.7, < 2)
       minitest (~> 5.1)
       tzinfo (~> 1.1)
-    addressable (2.6.0)
-      public_suffix (>= 2.0.2, < 4.0)
-    airbrussh (1.3.0)
+    addressable (2.7.0)
+      public_suffix (>= 2.0.2, < 5.0)
+    airbrussh (1.3.4)
       sshkit (>= 1.6.1, != 1.7.0)
     annotate (2.7.5)
       activerecord (>= 3.2, < 7.0)
@@ -76,17 +105,17 @@ GEM
     av (0.9.0)
       cocaine (~> 0.5.3)
     aws-eventstream (1.0.3)
-    aws-partitions (1.175.0)
-    aws-sdk-core (3.55.0)
+    aws-partitions (1.207.0)
+    aws-sdk-core (3.65.1)
       aws-eventstream (~> 1.0, >= 1.0.2)
       aws-partitions (~> 1.0)
       aws-sigv4 (~> 1.1)
       jmespath (~> 1.0)
-    aws-sdk-kms (1.21.0)
-      aws-sdk-core (~> 3, >= 3.53.0)
+    aws-sdk-kms (1.24.0)
+      aws-sdk-core (~> 3, >= 3.61.1)
       aws-sigv4 (~> 1.1)
-    aws-sdk-s3 (1.42.0)
-      aws-sdk-core (~> 3, >= 3.53.0)
+    aws-sdk-s3 (1.48.0)
+      aws-sdk-core (~> 3, >= 3.61.1)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.1)
     aws-sigv4 (1.1.0)
@@ -101,19 +130,19 @@ GEM
       debug_inspector (>= 0.0.1)
     blurhash (0.1.3)
       ffi (~> 1.10.0)
-    bootsnap (1.4.4)
+    bootsnap (1.4.5)
       msgpack (~> 1.0)
-    brakeman (4.5.1)
-    browser (2.5.3)
+    brakeman (4.6.1)
+    browser (2.6.1)
     builder (3.2.3)
-    bullet (6.0.0)
+    bullet (6.0.2)
       activesupport (>= 3.0.0)
       uniform_notifier (~> 1.11)
     bundler-audit (0.6.1)
       bundler (>= 1.2.0, < 3)
       thor (~> 0.18)
     byebug (11.0.0)
-    capistrano (3.11.0)
+    capistrano (3.11.2)
       airbrussh (>= 1.0.0)
       i18n
       rake (>= 10.0.0)
@@ -129,7 +158,7 @@ GEM
       sshkit (~> 1.3)
     capistrano-yarn (2.0.2)
       capistrano (~> 3.0)
-    capybara (3.24.0)
+    capybara (3.29.0)
       addressable
       mini_mime (>= 0.1.3)
       nokogiri (~> 1.8)
@@ -140,7 +169,7 @@ GEM
     case_transform (0.2)
       activesupport
     charlock_holmes (0.7.6)
-    chewy (5.0.0)
+    chewy (5.1.0)
       activesupport (>= 4.0)
       elasticsearch (>= 2.0.0)
       elasticsearch-dsl
@@ -156,64 +185,67 @@ GEM
     crack (0.4.3)
       safe_yaml (~> 1.0.0)
     crass (1.0.4)
-    css_parser (1.6.0)
+    css_parser (1.7.0)
       addressable
     debug_inspector (0.0.3)
-    derailed_benchmarks (1.3.5)
+    derailed_benchmarks (1.4.0)
       benchmark-ips (~> 2)
       get_process_mem (~> 0)
       heapy (~> 0)
       memory_profiler (~> 0)
       rack (>= 1)
       rake (> 10, < 13)
+      ruby-statistics (>= 2.1)
       thor (~> 0.19)
-    devise (4.6.2)
+    devise (4.7.1)
       bcrypt (~> 3.0)
       orm_adapter (~> 0.1)
-      railties (>= 4.1.0, < 6.0)
+      railties (>= 4.1.0)
       responders
       warden (~> 1.2.3)
-    devise-two-factor (3.0.3)
-      activesupport (< 5.3)
+    devise-two-factor (3.1.0)
+      activesupport (< 6.1)
       attr_encrypted (>= 1.3, < 4, != 2)
       devise (~> 4.0)
-      railties (< 5.3)
+      railties (< 6.1)
       rotp (~> 2.0)
     devise_pam_authenticatable2 (9.2.0)
       devise (>= 4.0.0)
       rpam2 (~> 4.0)
     diff-lcs (1.3)
-    docile (1.3.0)
+    discard (1.1.0)
+      activerecord (>= 4.2, < 7)
+    docile (1.3.2)
     domain_name (0.5.20180417)
       unf (>= 0.0.5, < 1.0.0)
-    doorkeeper (5.1.0)
+    doorkeeper (5.2.1)
       railties (>= 5)
-    dotenv (2.7.2)
-    dotenv-rails (2.7.2)
-      dotenv (= 2.7.2)
+    dotenv (2.7.5)
+    dotenv-rails (2.7.5)
+      dotenv (= 2.7.5)
       railties (>= 3.2, < 6.1)
-    elasticsearch (6.0.2)
-      elasticsearch-api (= 6.0.2)
-      elasticsearch-transport (= 6.0.2)
-    elasticsearch-api (6.0.2)
+    elasticsearch (7.3.0)
+      elasticsearch-api (= 7.3.0)
+      elasticsearch-transport (= 7.3.0)
+    elasticsearch-api (7.3.0)
       multi_json
-    elasticsearch-dsl (0.1.5)
-    elasticsearch-transport (6.0.2)
+    elasticsearch-dsl (0.1.8)
+    elasticsearch-transport (7.3.0)
       faraday
       multi_json
     encryptor (3.0.0)
-    equatable (0.5.0)
+    equatable (0.6.1)
     erubi (1.8.0)
     et-orbi (1.1.6)
       tzinfo
     excon (0.62.0)
     fabrication (2.20.2)
-    faker (1.9.3)
-      i18n (>= 0.7)
-    faraday (0.15.0)
+    faker (2.5.0)
+      i18n (~> 1.6.0)
+    faraday (0.15.4)
       multipart-post (>= 1.2, < 3)
     fast_blank (1.0.0)
-    fastimage (2.1.5)
+    fastimage (2.1.7)
     ffi (1.10.0)
     fog-core (2.1.0)
       builder
@@ -234,7 +266,8 @@ GEM
     fuubar (2.4.1)
       rspec-core (~> 3.0)
       ruby-progressbar (~> 1.4)
-    get_process_mem (0.2.3)
+    get_process_mem (0.2.4)
+      ffi (~> 1.0)
     globalid (0.4.2)
       activesupport (>= 4.2.0)
     goldfinger (2.1.0)
@@ -253,7 +286,7 @@ GEM
       railties (>= 4.0.1)
     hamster (3.0.0)
       concurrent-ruby (~> 1.0)
-    hashdiff (0.4.0)
+    hashdiff (1.0.0)
     hashie (3.6.0)
     heapy (0.1.4)
     highline (2.0.1)
@@ -269,7 +302,7 @@ GEM
       domain_name (~> 0.5)
     http-form_data (2.1.1)
     http_accept_language (2.1.1)
-    httplog (1.3.1)
+    httplog (1.3.2)
       rack (>= 1.0)
       rainbow (>= 2.0.0)
     i18n (1.6.0)
@@ -287,17 +320,15 @@ GEM
     idn-ruby (0.1.0)
     ipaddress (0.8.3)
     iso-639 (0.2.8)
-    jaro_winkler (1.5.2)
+    jaro_winkler (1.5.3)
     jmespath (1.4.0)
-    json (2.1.0)
-    json-ld (3.0.2)
-      multi_json (~> 1.12)
-      rdf (>= 2.2.8, < 4.0)
-    json-ld-preloaded (3.0.2)
+    json (2.2.0)
+    json-canonicalization (0.1.0)
+    json-ld-preloaded (3.0.4)
       json-ld (~> 3.0)
       multi_json (~> 1.12)
       rdf (~> 3.0)
-    jsonapi-renderer (0.2.0)
+    jsonapi-renderer (0.2.2)
     jwt (2.1.0)
     kaminari (1.1.1)
       activesupport (>= 4.1.0)
@@ -336,37 +367,37 @@ GEM
       mimemagic (~> 0.3.2)
     mario-redis-lock (1.2.1)
       redis (>= 3.0.5)
-    memory_profiler (0.9.13)
+    memory_profiler (0.9.14)
     method_source (0.9.2)
     microformats (4.1.0)
       json (~> 2.1)
       nokogiri (~> 1.8, >= 1.8.3)
-    mime-types (3.2.2)
+    mime-types (3.3)
       mime-types-data (~> 3.2015)
-    mime-types-data (3.2018.0812)
+    mime-types-data (3.2019.0904)
     mimemagic (0.3.3)
-    mini_mime (1.0.1)
+    mini_mime (1.0.2)
     mini_portile2 (2.4.0)
-    minitest (5.11.3)
-    msgpack (1.2.10)
+    minitest (5.12.0)
+    msgpack (1.3.1)
     multi_json (1.13.1)
-    multipart-post (2.0.0)
+    multipart-post (2.1.1)
     necromancer (0.5.0)
     net-ldap (0.16.1)
-    net-scp (1.2.1)
-      net-ssh (>= 2.6.5)
-    net-ssh (5.0.2)
-    nio4r (2.3.1)
-    nokogiri (1.10.3)
+    net-scp (2.0.0)
+      net-ssh (>= 2.6.5, < 6.0.0)
+    net-ssh (5.2.0)
+    nio4r (2.5.1)
+    nokogiri (1.10.4)
       mini_portile2 (~> 2.4.0)
-    nokogumbo (2.0.0)
+    nokogumbo (2.0.1)
       nokogiri (~> 1.8, >= 1.8.4)
     nsa (0.2.7)
       activesupport (>= 4.2, < 6)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       sidekiq (>= 3.5)
       statsd-ruby (~> 1.4, >= 1.4.0)
-    oj (3.7.12)
+    oj (3.9.1)
     omniauth (1.9.0)
       hashie (>= 3.4.6, < 3.7.0)
       rack (>= 1.6.2, < 3)
@@ -393,23 +424,24 @@ GEM
       av (~> 0.9.0)
       paperclip (>= 2.5.2)
     parallel (1.17.0)
-    parallel_tests (2.29.0)
+    parallel_tests (2.29.2)
       parallel
-    parser (2.6.3.0)
+    parser (2.6.4.0)
       ast (~> 2.4.0)
-    pastel (0.7.2)
-      equatable (~> 0.5.0)
-      tty-color (~> 0.4.0)
+    parslet (1.8.2)
+    pastel (0.7.3)
+      equatable (~> 0.6)
+      tty-color (~> 0.5)
     pg (1.1.4)
-    pghero (2.2.1)
-      activerecord
-    pkg-config (1.3.7)
+    pghero (2.3.0)
+      activerecord (>= 5)
+    pkg-config (1.3.9)
     premailer (1.11.1)
       addressable
       css_parser (>= 1.6.0)
       htmlentities (>= 4.0.0)
-    premailer-rails (1.10.2)
-      actionmailer (>= 3, < 6)
+    premailer-rails (1.10.3)
+      actionmailer (>= 3)
       premailer (~> 1.7, >= 1.7.9)
     private_address_check (0.5.0)
     pry (0.12.2)
@@ -420,13 +452,14 @@ GEM
       pry (~> 0.10)
     pry-rails (0.3.9)
       pry (>= 0.10.4)
-    public_suffix (3.1.0)
-    puma (3.12.1)
-    pundit (2.0.1)
+    public_suffix (4.0.1)
+    puma (4.2.0)
+      nio4r (~> 2.0)
+    pundit (2.1.0)
       activesupport (>= 3.0.0)
     raabro (1.1.6)
     rack (2.0.7)
-    rack-attack (6.0.0)
+    rack-attack (6.1.0)
       rack (>= 1.0, < 3)
     rack-cors (1.0.3)
     rack-protection (2.0.5)
@@ -455,7 +488,7 @@ GEM
     rails-dom-testing (2.0.3)
       activesupport (>= 4.2.0)
       nokogiri (>= 1.6)
-    rails-html-sanitizer (1.0.4)
+    rails-html-sanitizer (1.2.0)
       loofah (~> 2.2, >= 2.2.2)
     rails-i18n (5.1.3)
       i18n (>= 0.7, < 2)
@@ -469,13 +502,13 @@ GEM
       rake (>= 0.8.7)
       thor (>= 0.19.0, < 2.0)
     rainbow (3.0.0)
-    rake (12.3.2)
-    rdf (3.0.9)
+    rake (12.3.3)
+    rdf (3.0.12)
       hamster (~> 3.0)
       link_header (~> 0.0, >= 0.0.8)
     rdf-normalize (0.3.3)
       rdf (>= 2.2, < 4.0)
-    redis (4.1.2)
+    redis (4.1.3)
     redis-actionpack (5.0.2)
       actionpack (>= 4.0, < 6)
       redis-rack (>= 1, < 3)
@@ -494,12 +527,12 @@ GEM
       redis-store (>= 1.2, < 2)
     redis-store (1.5.0)
       redis (>= 2.2, < 5)
-    regexp_parser (1.5.1)
+    regexp_parser (1.6.0)
     request_store (1.4.1)
       rack (>= 1.4)
-    responders (2.4.1)
-      actionpack (>= 4.2.0, < 6.0)
-      railties (>= 4.2.0, < 6.0)
+    responders (3.0.0)
+      actionpack (>= 5.0)
+      railties (>= 5.0)
     rotp (2.1.2)
     rpam2 (4.0.2)
     rqrcode (0.10.1)
@@ -524,23 +557,24 @@ GEM
       rspec-core (~> 3.0, >= 3.0.0)
       sidekiq (>= 2.4.0)
     rspec-support (3.8.0)
-    rubocop (0.71.0)
+    rubocop (0.74.0)
       jaro_winkler (~> 1.5.1)
       parallel (~> 1.10)
       parser (>= 2.6)
       rainbow (>= 2.2.2, < 4.0)
       ruby-progressbar (~> 1.7)
       unicode-display_width (>= 1.4.0, < 1.7)
-    rubocop-rails (2.0.1)
+    rubocop-rails (2.3.2)
       rack (>= 1.1)
-      rubocop (>= 0.70.0)
+      rubocop (>= 0.72.0)
     ruby-progressbar (1.10.1)
     ruby-saml (1.9.0)
       nokogiri (>= 1.5.10)
+    ruby-statistics (2.1.1)
     rufus-scheduler (3.5.2)
       fugit (~> 1.1, >= 1.1.5)
     safe_yaml (1.0.5)
-    sanitize (5.0.0)
+    sanitize (5.1.0)
       crass (~> 1.0.2)
       nokogiri (>= 1.8.0)
       nokogumbo (~> 2.0)
@@ -560,12 +594,12 @@ GEM
       concurrent-ruby (~> 1.0, >= 1.0.5)
       sidekiq (>= 4.0, < 7.0)
       thor (~> 0)
-    simple-navigation (4.0.5)
+    simple-navigation (4.1.0)
       activesupport (>= 2.3.2)
     simple_form (4.1.0)
       actionpack (>= 5.0)
       activemodel (>= 5.0)
-    simplecov (0.16.1)
+    simplecov (0.17.1)
       docile (~> 1.1)
       json (>= 1.8, < 3)
       simplecov-html (~> 0.10.0)
@@ -577,7 +611,7 @@ GEM
       actionpack (>= 4.0)
       activesupport (>= 4.0)
       sprockets (>= 3.0.0)
-    sshkit (1.17.0)
+    sshkit (1.20.0)
       net-scp (>= 1.1.2)
       net-ssh (>= 2.8.0)
     stackprof (0.2.12)
@@ -585,7 +619,7 @@ GEM
     stoplight (2.1.3)
     streamio-ffmpeg (3.0.2)
       multi_json (~> 1.8)
-    strong_migrations (0.4.0)
+    strong_migrations (0.4.1)
       activerecord (>= 5)
     temple (0.8.1)
     terminal-table (1.8.0)
@@ -595,8 +629,8 @@ GEM
     thor (0.20.3)
     thread_safe (0.3.6)
     tilt (2.0.9)
-    tty-color (0.4.3)
-    tty-command (0.8.2)
+    tty-color (0.5.0)
+    tty-command (0.9.0)
       pastel (~> 0.7.0)
     tty-cursor (0.7.0)
     tty-prompt (0.19.0)
@@ -612,7 +646,7 @@ GEM
       unf (~> 0.1.0)
     tzinfo (1.2.5)
       thread_safe (~> 0.1)
-    tzinfo-data (1.2019.1)
+    tzinfo-data (1.2019.3)
       tzinfo (>= 1.0.0)
     unf (0.1.4)
       unf_ext
@@ -621,7 +655,7 @@ GEM
     uniform_notifier (1.12.1)
     warden (1.2.8)
       rack (>= 2.0.6)
-    webmock (3.6.0)
+    webmock (3.7.6)
       addressable (>= 2.3.6)
       crack (>= 0.3.2)
       hashdiff (>= 0.4.0, < 2.0.0)
@@ -645,14 +679,14 @@ PLATFORMS
 DEPENDENCIES
   active_model_serializers (~> 0.10)
   active_record_query_trace (~> 1.6)
-  addressable (~> 2.6)
+  addressable (~> 2.7)
   annotate (~> 2.7)
-  aws-sdk-s3 (~> 1.42)
+  aws-sdk-s3 (~> 1.48)
   better_errors (~> 2.5)
   binding_of_caller (~> 0.7)
   blurhash (~> 0.1)
   bootsnap (~> 1.4)
-  brakeman (~> 4.5)
+  brakeman (~> 4.6)
   browser
   bullet (~> 6.0)
   bundler-audit (~> 0.6)
@@ -660,20 +694,22 @@ DEPENDENCIES
   capistrano-rails (~> 1.4)
   capistrano-rbenv (~> 2.1)
   capistrano-yarn (~> 2.0)
-  capybara (~> 3.24)
+  capybara (~> 3.29)
   charlock_holmes (~> 0.7.6)
-  chewy (~> 5.0)
+  chewy (~> 5.1)
   cld3 (~> 3.2.4)
   climate_control (~> 0.2)
   concurrent-ruby
+  connection_pool
   derailed_benchmarks
-  devise (~> 4.6)
-  devise-two-factor (~> 3.0)
+  devise (~> 4.7)
+  devise-two-factor (~> 3.1)
   devise_pam_authenticatable2 (~> 9.2)
-  doorkeeper (~> 5.1)
+  discard (~> 1.1)
+  doorkeeper (~> 5.2)
   dotenv-rails (~> 2.7)
   fabrication (~> 2.20)
-  faker (~> 1.9)
+  faker (~> 2.5)
   fast_blank (~> 1.0)
   fastimage
   fog-core (<= 2.1.0)
@@ -681,6 +717,7 @@ DEPENDENCIES
   fuubar (~> 2.4)
   goldfinger (~> 2.1)
   hamlit-rails (~> 0.2)
+  health_check!
   hiredis (~> 0.6)
   htmlentities (~> 4.3)
   http (~> 3.3)
@@ -690,7 +727,7 @@ DEPENDENCIES
   i18n-tasks (~> 0.9)
   idn-ruby
   iso-639
-  json-ld (~> 3.0)
+  json-ld!
   json-ld-preloaded (~> 3.0)
   kaminari (~> 1.1)
   letter_opener (~> 1.7)
@@ -701,11 +738,12 @@ DEPENDENCIES
   mario-redis-lock (~> 1.2)
   memory_profiler
   microformats (~> 4.1)
-  mime-types (~> 3.2)
+  mime-types (~> 3.3)
   net-ldap (~> 0.10)
+  nilsimsa!
   nokogiri (~> 1.10)
   nsa (~> 0.2)
-  oj (~> 3.7)
+  oj (~> 3.9)
   omniauth (~> 1.9)
   omniauth-cas (~> 1.1)
   omniauth-saml (~> 1.10)
@@ -713,18 +751,20 @@ DEPENDENCIES
   ox (~> 2.11)
   paperclip (~> 6.0)
   paperclip-av-transcoder (~> 0.6)
+  parallel (~> 1.17)
   parallel_tests (~> 2.29)
+  parslet
   pg (~> 1.1)
-  pghero (~> 2.2)
+  pghero (~> 2.3)
   pkg-config (~> 1.3)
   posix-spawn!
   premailer-rails
   private_address_check (~> 0.5)
   pry-byebug (~> 3.7)
   pry-rails (~> 0.3)
-  puma (~> 3.12)
-  pundit (~> 2.0)
-  rack-attack (~> 6.0)
+  puma (~> 4.2)
+  pundit (~> 2.1)
+  rack-attack (~> 6.1)
   rack-cors (~> 1.0)
   rails (~> 5.2.3)
   rails-controller-testing (~> 1.0)
@@ -737,32 +777,33 @@ DEPENDENCIES
   rqrcode (~> 0.10)
   rspec-rails (~> 3.8)
   rspec-sidekiq (~> 3.0)
-  rubocop (~> 0.71)
-  rubocop-rails (~> 2.0)
-  sanitize (~> 5.0)
+  rubocop (~> 0.74)
+  rubocop-rails (~> 2.3)
+  ruby-progressbar (~> 1.10)
+  sanitize (~> 5.1)
   sidekiq (~> 5.2)
   sidekiq-bulk (~> 0.2.0)
   sidekiq-scheduler (~> 3.0)
   sidekiq-unique-jobs (~> 6.0)
-  simple-navigation (~> 4.0)
+  simple-navigation (~> 4.1)
   simple_form (~> 4.1)
-  simplecov (~> 0.16)
+  simplecov (~> 0.17)
   sprockets-rails (~> 3.2)
   stackprof
   stoplight (~> 2.1.3)
   streamio-ffmpeg (~> 3.0)
   strong_migrations (~> 0.4)
   thor (~> 0.20)
-  tty-command (~> 0.8)
+  tty-command (~> 0.9)
   tty-prompt (~> 0.19)
   twitter-text (~> 1.14)
   tzinfo-data (~> 1.2019)
-  webmock (~> 3.6)
+  webmock (~> 3.7)
   webpacker (~> 4.0)
   webpush
 
 RUBY VERSION
-   ruby 2.6.1p33
+   ruby 2.6.5p114
 
 BUNDLED WITH
    1.17.3
diff --git a/README.md b/README.md
index 2d18a4ee22aa06d717c752e3af55c5d8eea0d869..d50c1b3bc0fd8db8e56d063f9c0cd85186d881ae 100644
--- a/README.md
+++ b/README.md
@@ -55,7 +55,7 @@ Private posts, locked accounts, phrase filtering, muting, blocking and all sorts
 
 **OAuth2 and a straightforward REST API**
 
-Mastodon acts as an OAuth2 provider so 3rd party apps can use the REST and Streaming APIs, resulting in a rich app ecosystem with a lot of choice!
+Mastodon acts as an OAuth2 provider so 3rd party apps can use the REST and Streaming APIs, resulting in a rich app ecosystem with a lot of choices!
 
 ## Deployment
 
diff --git a/app.json b/app.json
index 09adaac2c9fda1d1e21d56b17d5b728facf88232..211f17d812e221267284367ee30ce37b0efffe63 100644
--- a/app.json
+++ b/app.json
@@ -13,15 +13,6 @@
       "description": "The domain that your Mastodon instance will run on (this can be appname.herokuapp.com or a custom domain)",
       "required": true
     },
-    "LOCAL_HTTPS": {
-      "description": "Will your domain support HTTPS? (Automatic for herokuapp, requires manual configuration for custom domains)",
-      "value": "false",
-      "required": true
-    },
-    "PAPERCLIP_SECRET": {
-      "description": "The secret key for storing media files",
-      "generator": "secret"
-    },
     "SECRET_KEY_BASE": {
       "description": "The secret key base",
       "generator": "secret"
diff --git a/app/chewy/accounts_index.rb b/app/chewy/accounts_index.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b814e009e5f7792e73d56852f84f8c8d79af57a9
--- /dev/null
+++ b/app/chewy/accounts_index.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class AccountsIndex < Chewy::Index
+  settings index: { refresh_interval: '5m' }, analysis: {
+    analyzer: {
+      content: {
+        tokenizer: 'whitespace',
+        filter: %w(lowercase asciifolding cjk_width),
+      },
+
+      edge_ngram: {
+        tokenizer: 'edge_ngram',
+        filter: %w(lowercase asciifolding cjk_width),
+      },
+    },
+
+    tokenizer: {
+      edge_ngram: {
+        type: 'edge_ngram',
+        min_gram: 1,
+        max_gram: 15,
+      },
+    },
+  }
+
+  define_type ::Account.searchable.includes(:account_stat), delete_if: ->(account) { account.destroyed? || !account.searchable? } do
+    root date_detection: false do
+      field :id, type: 'long'
+
+      field :display_name, type: 'text', analyzer: 'content' do
+        field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
+      end
+
+      field :acct, type: 'text', analyzer: 'content', value: ->(account) { [account.username, account.domain].compact.join('@') } do
+        field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
+      end
+
+      field :following_count, type: 'long', value: ->(account) { account.following.local.count }
+      field :followers_count, type: 'long', value: ->(account) { account.followers.local.count }
+      field :last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at }
+    end
+  end
+end
diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb
index f5983a5a5f489d119c373e9c32e814ec2afa633c..f5735421c7c90bccc616cacbbd89b312eeb4629c 100644
--- a/app/chewy/statuses_index.rb
+++ b/app/chewy/statuses_index.rb
@@ -31,19 +31,19 @@ class StatusesIndex < Chewy::Index
     },
   }
 
-  define_type ::Status.unscoped.without_reblogs.includes(:media_attachments) do
+  define_type ::Status.unscoped.kept.without_reblogs.includes(:media_attachments), delete_if: ->(status) { status.searchable_by.empty? } do
     crutch :mentions do |collection|
-      data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
+      data = ::Mention.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id)
       data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
     end
 
     crutch :favourites do |collection|
-      data = ::Favourite.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
+      data = ::Favourite.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id)
       data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
     end
 
     crutch :reblogs do |collection|
-      data = ::Status.where(reblog_of_id: collection.map(&:id)).pluck(:reblog_of_id, :account_id)
+      data = ::Status.where(reblog_of_id: collection.map(&:id)).where(account: Account.local).pluck(:reblog_of_id, :account_id)
       data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
     end
 
@@ -51,7 +51,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.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, Formatter.instance.plaintext(status)].concat(status.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/chewy/tags_index.rb b/app/chewy/tags_index.rb
new file mode 100644
index 0000000000000000000000000000000000000000..300fc128f63f0f4684af335d3a01142712cab477
--- /dev/null
+++ b/app/chewy/tags_index.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+class TagsIndex < Chewy::Index
+  settings index: { refresh_interval: '15m' }, analysis: {
+    analyzer: {
+      content: {
+        tokenizer: 'keyword',
+        filter: %w(lowercase asciifolding cjk_width),
+      },
+
+      edge_ngram: {
+        tokenizer: 'edge_ngram',
+        filter: %w(lowercase asciifolding cjk_width),
+      },
+    },
+
+    tokenizer: {
+      edge_ngram: {
+        type: 'edge_ngram',
+        min_gram: 2,
+        max_gram: 15,
+      },
+    },
+  }
+
+  define_type ::Tag.listable, delete_if: ->(tag) { tag.destroyed? || !tag.listable? } do
+    root date_detection: false do
+      field :name, type: 'text', analyzer: 'content' do
+        field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
+      end
+
+      field :reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? }
+      field :usage, type: 'long', value: ->(tag) { tag.history.reduce(0) { |total, day| total + day[:accounts].to_i } }
+      field :last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at }
+    end
+  end
+end
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index 9f608a851c6467df8f26d59a7dc6145b79f26b04..abd1ec0cb647296b08ee042e3c89a8cfe0d7946c 100644
--- a/app/controllers/about_controller.rb
+++ b/app/controllers/about_controller.rb
@@ -3,20 +3,46 @@
 class AboutController < ApplicationController
   layout 'public'
 
-  before_action :set_instance_presenter, only: [:show, :more, :terms]
+  before_action :require_open_federation!, only: [:show, :more]
+  before_action :set_body_classes, only: :show
+  before_action :set_instance_presenter
+  before_action :set_expires_in, only: [:show, :more, :terms]
 
-  skip_before_action :check_user_permissions, only: [:more, :terms]
+  skip_before_action :require_functional!, only: [:more, :terms]
 
-  def show
-    @hide_navbar = true
-  end
+  def show; end
+
+  def more
+    flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor]
 
-  def more; end
+    toc_generator = TOCGenerator.new(@instance_presenter.site_extended_description)
+
+    @contents          = toc_generator.html
+    @table_of_contents = toc_generator.toc
+    @blocks            = DomainBlock.with_user_facing_limitations.by_severity if display_blocks?
+  end
 
   def terms; end
 
+  helper_method :display_blocks?
+  helper_method :display_blocks_rationale?
+  helper_method :public_fetch_mode?
+  helper_method :new_user
+
   private
 
+  def require_open_federation!
+    not_found if whitelist_mode?
+  end
+
+  def display_blocks?
+    Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?)
+  end
+
+  def display_blocks_rationale?
+    Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?)
+  end
+
   def new_user
     User.new.tap do |user|
       user.build_account
@@ -24,9 +50,15 @@ class AboutController < ApplicationController
     end
   end
 
-  helper_method :new_user
-
   def set_instance_presenter
     @instance_presenter = InstancePresenter.new
   end
+
+  def set_body_classes
+    @hide_navbar = true
+  end
+
+  def set_expires_in
+    expires_in 0, public: true
+  end
 end
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index f25a997744a3859577e6e73b0936ed48b8c0e93a..f6c599b8624de4c465cd3224ccaef1ea7428c462 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -4,17 +4,22 @@ class AccountsController < ApplicationController
   PAGE_SIZE = 20
 
   include AccountControllerConcern
+  include SignatureAuthentication
 
   before_action :set_cache_headers
+  before_action :set_body_classes
+
+  skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format) }
+  skip_before_action :require_functional!
 
   def show
     respond_to do |format|
       format.html do
-        mark_cacheable! unless user_signed_in?
+        expires_in 0, public: true unless user_signed_in?
 
-        @body_classes      = 'with-modals'
         @pinned_statuses   = []
         @endorsed_accounts = @account.endorsed_accounts.to_a.sample(4)
+        @featured_hashtags = @account.featured_tags.order(statuses_count: :desc)
 
         if current_account && @account.blocking?(current_account)
           @statuses = []
@@ -24,6 +29,7 @@ class AccountsController < ApplicationController
         @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
         @statuses        = filtered_status_page(params)
         @statuses        = cache_collection(@statuses, Status)
+        @rss_url         = rss_url
 
         unless @statuses.empty?
           @older_url = older_url if @statuses.last.id > filtered_statuses.last.id
@@ -31,30 +37,27 @@ class AccountsController < ApplicationController
         end
       end
 
-      format.atom do
-        mark_cacheable!
-
-        @entries = @account.stream_entries.where(hidden: false).with_includes.without_local_only.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id])
-        render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? }))
-      end
-
       format.rss do
-        mark_cacheable!
+        expires_in 1.minute, public: true
 
-        @statuses = cache_collection(default_statuses.without_local_only.without_reblogs.without_replies.limit(PAGE_SIZE), Status)
-        render xml: RSS::AccountSerializer.render(@account, @statuses)
+        @statuses = filtered_statuses.without_reblogs.without_local_only.without_replies.limit(PAGE_SIZE)
+        @statuses = cache_collection(@statuses, Status)
+        render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag])
       end
 
       format.json do
-        render_cached_json(['activitypub', 'actor', @account], content_type: 'application/activity+json') do
-          ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter)
-        end
+        expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?)
+        render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
       end
     end
   end
 
   private
 
+  def set_body_classes
+    @body_classes = 'with-modals'
+  end
+
   def show_pinned_statuses?
     [replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none?
   end
@@ -101,6 +104,14 @@ class AccountsController < ApplicationController
     params[:username]
   end
 
+  def rss_url
+    if tag_requested?
+      short_account_tag_url(@account, params[:tag], format: 'rss')
+    else
+      short_account_url(@account, format: 'rss')
+    end
+  end
+
   def older_url
     pagination_url(max_id: @statuses.last.id)
   end
@@ -122,15 +133,15 @@ class AccountsController < ApplicationController
   end
 
   def media_requested?
-    request.path.ends_with?('/media')
+    request.path.ends_with?('/media') && !tag_requested?
   end
 
   def replies_requested?
-    request.path.ends_with?('/with_replies')
+    request.path.ends_with?('/with_replies') && !tag_requested?
   end
 
   def tag_requested?
-    request.path.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
+    request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
   end
 
   def filtered_status_page(params)
@@ -140,4 +151,12 @@ class AccountsController < ApplicationController
       filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
     end
   end
+
+  def restrict_fields_to
+    if signed_request_account.present? || public_fetch_mode?
+      # Return all fields
+    else
+      %i(id type preferred_username inbox public_key endpoints)
+    end
+  end
 end
diff --git a/app/controllers/activitypub/base_controller.rb b/app/controllers/activitypub/base_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0c2591e9743ee534892adbbd8cc2329e5d1fd180
--- /dev/null
+++ b/app/controllers/activitypub/base_controller.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class ActivityPub::BaseController < Api::BaseController
+  skip_before_action :require_authenticated_user!
+
+  private
+
+  def set_cache_headers
+    response.headers['Vary'] = 'Signature' if authorized_fetch_mode?
+  end
+end
diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb
index 012c3c53885846e6ca5d33387e2c6dc0d1926285..910fefb1c7ae3161650b13ebc370d71490ae4ded 100644
--- a/app/controllers/activitypub/collections_controller.rb
+++ b/app/controllers/activitypub/collections_controller.rb
@@ -1,30 +1,21 @@
 # frozen_string_literal: true
 
-class ActivityPub::CollectionsController < Api::BaseController
+class ActivityPub::CollectionsController < ActivityPub::BaseController
   include SignatureVerification
+  include AccountOwnedConcern
 
-  before_action :set_account
+  before_action :require_signature!, if: :authorized_fetch_mode?
   before_action :set_size
   before_action :set_statuses
   before_action :set_cache_headers
 
   def show
-    render_cached_json(['activitypub', 'collection', @account, params[:id]], content_type: 'application/activity+json') do
-      ActiveModelSerializers::SerializableResource.new(
-        collection_presenter,
-        serializer: ActivityPub::CollectionSerializer,
-        adapter: ActivityPub::Adapter,
-        skip_activities: true
-      )
-    end
+    expires_in 3.minutes, public: public_fetch_mode?
+    render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true
   end
 
   private
 
-  def set_account
-    @account = Account.find_local!(params[:account_username])
-  end
-
   def set_statuses
     @statuses = scope_for_collection
     @statuses = cache_collection(@statuses, Status)
@@ -42,9 +33,9 @@ class ActivityPub::CollectionsController < Api::BaseController
   def scope_for_collection
     case params[:id]
     when 'featured'
-      @account.statuses.permitted_for(@account, signed_request_account).tap do |scope|
-        scope.merge!(@account.pinned_statuses)
-      end
+      return Status.none if @account.blocking?(signed_request_account)
+
+      @account.pinned_statuses
     else
       raise ActiveRecord::RecordNotFound
     end
diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb
index a0b7532c2e0ccedd25ba86504d635eea4ec519ea..bcfc1e6d4234ed4646b3e25dab3f5e1b38303769 100644
--- a/app/controllers/activitypub/inboxes_controller.rb
+++ b/app/controllers/activitypub/inboxes_controller.rb
@@ -1,40 +1,44 @@
 # frozen_string_literal: true
 
-class ActivityPub::InboxesController < Api::BaseController
+class ActivityPub::InboxesController < ActivityPub::BaseController
   include SignatureVerification
   include JsonLdHelper
+  include AccountOwnedConcern
 
-  before_action :set_account
+  before_action :skip_unknown_actor_delete
+  before_action :require_signature!
 
   def create
-    if unknown_deleted_account?
-      head 202
-    elsif signed_request_account
-      upgrade_account
-      process_payload
-      head 202
-    else
-      render plain: signature_verification_failure_reason, status: 401
-    end
+    upgrade_account
+    process_payload
+    head 202
   end
 
   private
 
+  def skip_unknown_actor_delete
+    head 202 if unknown_deleted_account?
+  end
+
   def unknown_deleted_account?
     json = Oj.load(body, mode: :strict)
-    json['type'] == 'Delete' && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists?
+    json.is_a?(Hash) && json['type'] == 'Delete' && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists?
   rescue Oj::ParseError
     false
   end
 
-  def set_account
-    @account = Account.find_local!(params[:account_username]) if params[:account_username]
+  def account_required?
+    params[:account_username].present?
   end
 
   def body
     return @body if defined?(@body)
-    @body = request.body.read.force_encoding('UTF-8')
+
+    @body = request.body.read
+    @body.force_encoding('UTF-8') if @body.present?
+
     request.body.rewind if request.body.respond_to?(:rewind)
+
     @body
   end
 
@@ -44,7 +48,6 @@ class ActivityPub::InboxesController < Api::BaseController
       ResolveAccountWorker.perform_async(signed_request_account.acct)
     end
 
-    Pubsubhubbub::UnsubscribeWorker.perform_async(signed_request_account.id) if signed_request_account.subscribed?
     DeliveryFailureTracker.track_inverse_success!(signed_request_account)
   end
 
diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb
index 5147afbf7822f8fb7651dc0b97081042a39a6a53..891756b7e6a8c7c5c2fd58e905978c5a47c079b6 100644
--- a/app/controllers/activitypub/outboxes_controller.rb
+++ b/app/controllers/activitypub/outboxes_controller.rb
@@ -1,26 +1,22 @@
 # frozen_string_literal: true
 
-class ActivityPub::OutboxesController < Api::BaseController
+class ActivityPub::OutboxesController < ActivityPub::BaseController
   LIMIT = 20
 
   include SignatureVerification
+  include AccountOwnedConcern
 
-  before_action :set_account
+  before_action :require_signature!, if: :authorized_fetch_mode?
   before_action :set_statuses
   before_action :set_cache_headers
 
   def show
-    expires_in 1.minute, public: true unless page_requested?
-
+    expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
     render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
   end
 
   private
 
-  def set_account
-    @account = Account.find_local!(params[:account_username])
-  end
-
   def outbox_presenter
     if page_requested?
       ActivityPub::CollectionPresenter.new(
diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c62061555bb6ffb1fd4211fd1be71bdb937fe229
--- /dev/null
+++ b/app/controllers/activitypub/replies_controller.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+class ActivityPub::RepliesController < ActivityPub::BaseController
+  include SignatureAuthentication
+  include Authorization
+  include AccountOwnedConcern
+
+  DESCENDANTS_LIMIT = 60
+
+  before_action :require_signature!, if: :authorized_fetch_mode?
+  before_action :set_status
+  before_action :set_cache_headers
+  before_action :set_replies
+
+  def index
+    expires_in 0, public: public_fetch_mode?
+    render json: replies_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true
+  end
+
+  private
+
+  def set_status
+    @status = @account.statuses.find(params[:status_id])
+    authorize @status, :show?
+  rescue Mastodon::NotPermittedError
+    raise ActiveRecord::RecordNotFound
+  end
+
+  def set_replies
+    @replies = page_params[:only_other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses
+    @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
+    @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
+  end
+
+  def replies_collection_presenter
+    page = ActivityPub::CollectionPresenter.new(
+      id: account_status_replies_url(@account, @status, page_params),
+      type: :unordered,
+      part_of: account_status_replies_url(@account, @status),
+      next: next_page,
+      items: @replies.map { |status| status.local ? status : status.uri }
+    )
+
+    return page if page_requested?
+
+    ActivityPub::CollectionPresenter.new(
+      id: account_status_replies_url(@account, @status),
+      type: :unordered,
+      first: page
+    )
+  end
+
+  def page_requested?
+    params[:page] == 'true'
+  end
+
+  def next_page
+    only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT)
+    account_status_replies_url(
+      @account,
+      @status,
+      page: true,
+      min_id: only_other_accounts && !page_params[:only_other_accounts] ? nil : @replies&.last&.id,
+      only_other_accounts: only_other_accounts
+    )
+  end
+
+  def page_params
+    params_slice(:only_other_accounts, :min_id).merge(page: true)
+  end
+end
diff --git a/app/controllers/admin/account_actions_controller.rb b/app/controllers/admin/account_actions_controller.rb
index a2cea461ee1950e2c971e3455378380407221a50..ea56fa0ac72295bea1a6c177a95af12cb88c5ad8 100644
--- a/app/controllers/admin/account_actions_controller.rb
+++ b/app/controllers/admin/account_actions_controller.rb
@@ -5,7 +5,7 @@ module Admin
     before_action :set_account
 
     def new
-      @account_action  = Admin::AccountAction.new(type: params[:type], report_id: params[:report_id], send_email_notification: true)
+      @account_action  = Admin::AccountAction.new(type: params[:type], report_id: params[:report_id], send_email_notification: true, include_statuses: true)
       @warning_presets = AccountWarningPreset.all
     end
 
@@ -30,7 +30,7 @@ module Admin
     end
 
     def resource_params
-      params.require(:admin_account_action).permit(:type, :report_id, :warning_preset_id, :text, :send_email_notification)
+      params.require(:admin_account_action).permit(:type, :report_id, :warning_preset_id, :text, :send_email_notification, :include_statuses)
     end
   end
 end
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index 0c7760d779932c83fe7ec18f9dbbc83f5f6602b0..68b6352f886faa839ac8d796a03a759a22fcb960 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -2,8 +2,8 @@
 
 module Admin
   class AccountsController < BaseController
-    before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject]
-    before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
+    before_action :set_account, only: [:show, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject]
+    before_action :require_remote_account!, only: [:redownload]
     before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
 
     def index
@@ -19,18 +19,6 @@ module Admin
       @warnings                = @account.targeted_account_warnings.latest.custom
     end
 
-    def subscribe
-      authorize @account, :subscribe?
-      Pubsubhubbub::SubscribeWorker.perform_async(@account.id)
-      redirect_to admin_account_path(@account.id)
-    end
-
-    def unsubscribe
-      authorize @account, :unsubscribe?
-      Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id)
-      redirect_to admin_account_path(@account.id)
-    end
-
     def memorialize
       authorize @account, :memorialize?
       @account.memorialize!
@@ -53,7 +41,7 @@ module Admin
 
     def reject
       authorize @account.user, :reject?
-      SuspendAccountService.new.call(@account, including_user: true, destroy: true, skip_distribution: true)
+      SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
       redirect_to admin_pending_accounts_path
     end
 
diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb
index f77699166769fd07cd21815444b9093adcc2bb3f..2af90f0513ce7b59a15f54210b22bcc6bb19a66d 100644
--- a/app/controllers/admin/custom_emojis_controller.rb
+++ b/app/controllers/admin/custom_emojis_controller.rb
@@ -2,19 +2,20 @@
 
 module Admin
   class CustomEmojisController < BaseController
-    before_action :set_custom_emoji, except: [:index, :new, :create]
-    before_action :set_filter_params
-
     include ObfuscateFilename
+
     obfuscate_filename [:custom_emoji, :image]
 
     def index
       authorize :custom_emoji, :index?
+
       @custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
+      @form          = Form::CustomEmojiBatch.new
     end
 
     def new
       authorize :custom_emoji, :create?
+
       @custom_emoji = CustomEmoji.new
     end
 
@@ -31,69 +32,17 @@ module Admin
       end
     end
 
-    def update
-      authorize @custom_emoji, :update?
-
-      if @custom_emoji.update(resource_params)
-        log_action :update, @custom_emoji
-        flash[:notice] = I18n.t('admin.custom_emojis.updated_msg')
-      else
-        flash[:alert] =  I18n.t('admin.custom_emojis.update_failed_msg')
-      end
-      redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
-    end
-
-    def destroy
-      authorize @custom_emoji, :destroy?
-      @custom_emoji.destroy!
-      log_action :destroy, @custom_emoji
-      flash[:notice] = I18n.t('admin.custom_emojis.destroyed_msg')
-      redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
-    end
-
-    def copy
-      authorize @custom_emoji, :copy?
-
-      emoji = CustomEmoji.find_or_initialize_by(domain: nil,
-                                                shortcode: @custom_emoji.shortcode)
-      emoji.image = @custom_emoji.image
-
-      if emoji.save
-        log_action :create, emoji
-        flash[:notice] = I18n.t('admin.custom_emojis.copied_msg')
-      else
-        flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg')
-      end
-
-      redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
-    end
-
-    def enable
-      authorize @custom_emoji, :enable?
-      @custom_emoji.update!(disabled: false)
-      log_action :enable, @custom_emoji
-      flash[:notice] = I18n.t('admin.custom_emojis.enabled_msg')
-      redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
-    end
-
-    def disable
-      authorize @custom_emoji, :disable?
-      @custom_emoji.update!(disabled: true)
-      log_action :disable, @custom_emoji
-      flash[:notice] = I18n.t('admin.custom_emojis.disabled_msg')
-      redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
+    def batch
+      @form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button))
+      @form.save
+    rescue ActionController::ParameterMissing
+      flash[:alert] = I18n.t('admin.accounts.no_account_selected')
+    ensure
+      redirect_to admin_custom_emojis_path(filter_params)
     end
 
     private
 
-    def set_custom_emoji
-      @custom_emoji = CustomEmoji.find(params[:id])
-    end
-
-    def set_filter_params
-      @filter_params = filter_params.to_hash.symbolize_keys
-    end
-
     def resource_params
       params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
     end
@@ -103,12 +52,29 @@ module Admin
     end
 
     def filter_params
-      params.permit(
-        :local,
-        :remote,
-        :by_domain,
-        :shortcode
-      )
+      params.slice(:local, :remote, :by_domain, :shortcode, :page).permit(:local, :remote, :by_domain, :shortcode, :page)
+    end
+
+    def action_from_button
+      if params[:update]
+        'update'
+      elsif params[:list]
+        'list'
+      elsif params[:unlist]
+        'unlist'
+      elsif params[:enable]
+        'enable'
+      elsif params[:disable]
+        'disable'
+      elsif params[:copy]
+        'copy'
+      elsif params[:delete]
+        'delete'
+      end
+    end
+
+    def form_custom_emoji_batch_params
+      params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: [])
     end
   end
 end
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index f23ed15086214372da97698925986669d6891636..7c2951acbb0e25d86501c279e4d8f2e94524fa35 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -5,6 +5,7 @@ module Admin
   class DashboardController < BaseController
     def index
       @users_count           = User.count
+      @pending_users_count   = User.pending.count
       @registrations_week    = Redis.current.get("activity:accounts:local:#{current_week}") || 0
       @logins_week           = Redis.current.pfcount("activity:logins:#{current_week}")
       @interactions_week     = Redis.current.get("activity:interactions:#{current_week}") || 0
@@ -19,7 +20,7 @@ module Admin
       @redis_version         = redis_info['redis_version']
       @reports_count         = Report.unresolved.count
       @queue_backlog         = Sidekiq::Stats.new.enqueued
-      @recent_users          = User.confirmed.recent.includes(:account).limit(4)
+      @recent_users          = User.confirmed.recent.includes(:account).limit(8)
       @database_size         = ActiveRecord::Base.connection.execute('SELECT pg_database_size(current_database())').first['pg_database_size']
       @redis_size            = redis_info['used_memory']
       @ldap_enabled          = ENV['LDAP_ENABLED'] == 'true'
@@ -27,9 +28,14 @@ module Admin
       @saml_enabled          = ENV['SAML_ENABLED'] == 'true'
       @pam_enabled           = ENV['PAM_ENABLED'] == 'true'
       @hidden_service        = ENV['ALLOW_ACCESS_TO_HIDDEN_SERVICE'] == 'true'
-      @trending_hashtags     = TrendingTags.get(7)
+      @trending_hashtags     = TrendingTags.get(10, filtered: false)
+      @pending_tags_count    = Tag.pending_review.count
+      @authorized_fetch      = authorized_fetch_mode?
+      @whitelist_enabled     = whitelist_mode?
       @profile_directory     = Setting.profile_directory
       @timeline_preview      = Setting.timeline_preview
+      @spam_check_enabled    = Setting.spam_check_enabled
+      @trends_enabled        = Setting.trends
     end
 
     private
@@ -39,7 +45,13 @@ module Admin
     end
 
     def redis_info
-      @redis_info ||= Redis.current.info
+      @redis_info ||= begin
+        if Redis.current.is_a?(Redis::Namespace)
+          Redis.current.redis.info
+        else
+          Redis.current.info
+        end
+      end
     end
   end
 end
diff --git a/app/controllers/admin/domain_allows_controller.rb b/app/controllers/admin/domain_allows_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..31be1978bbbb104ffc1820419bcce7694a40de10
--- /dev/null
+++ b/app/controllers/admin/domain_allows_controller.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class Admin::DomainAllowsController < Admin::BaseController
+  before_action :set_domain_allow, only: [:destroy]
+
+  def new
+    authorize :domain_allow, :create?
+
+    @domain_allow = DomainAllow.new(domain: params[:_domain])
+  end
+
+  def create
+    authorize :domain_allow, :create?
+
+    @domain_allow = DomainAllow.new(resource_params)
+
+    if @domain_allow.save
+      log_action :create, @domain_allow
+      redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.created_msg')
+    else
+      render :new
+    end
+  end
+
+  def destroy
+    authorize @domain_allow, :destroy?
+    UnallowDomainService.new.call(@domain_allow)
+    redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.destroyed_msg')
+  end
+
+  private
+
+  def set_domain_allow
+    @domain_allow = DomainAllow.find(params[:id])
+  end
+
+  def resource_params
+    params.require(:domain_allow).permit(:domain)
+  end
+end
diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb
index 7129656dabfe5e9674cff9cfd82800a468ec8d45..74a36b79ca3a2106308079e4f2065682445fb1bf 100644
--- a/app/controllers/admin/domain_blocks_controller.rb
+++ b/app/controllers/admin/domain_blocks_controller.rb
@@ -2,13 +2,17 @@
 
 module Admin
   class DomainBlocksController < BaseController
-    before_action :set_domain_block, only: [:show, :destroy]
+    before_action :set_domain_block, only: [:show, :destroy, :edit, :update]
 
     def new
       authorize :domain_block, :create?
       @domain_block = DomainBlock.new(domain: params[:_domain])
     end
 
+    def edit
+      authorize :domain_block, :create?
+    end
+
     def create
       authorize :domain_block, :create?
 
@@ -35,6 +39,22 @@ module Admin
       end
     end
 
+    def update
+      authorize :domain_block, :create?
+
+      @domain_block.update(update_params)
+
+      severity_changed = @domain_block.severity_changed?
+
+      if @domain_block.save
+        DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
+        log_action :create, @domain_block
+        redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
+      else
+        render :edit
+      end
+    end
+
     def show
       authorize @domain_block, :show?
     end
@@ -52,8 +72,12 @@ module Admin
       @domain_block = DomainBlock.find(params[:id])
     end
 
+    def update_params
+      params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment)
+    end
+
     def resource_params
-      params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports)
+      params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment)
     end
   end
 end
diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb
index 7888e844fb5abc0a2840ca7795e9462870e28650..b47b18f8eb81087769f2521b0298902d42e2259a 100644
--- a/app/controllers/admin/instances_controller.rb
+++ b/app/controllers/admin/instances_controller.rb
@@ -2,6 +2,10 @@
 
 module Admin
   class InstancesController < BaseController
+    before_action :set_domain_block, only: :show
+    before_action :set_domain_allow, only: :show
+    before_action :set_instance, only: :show
+
     def index
       authorize :instance, :index?
 
@@ -11,20 +15,40 @@ module Admin
     def show
       authorize :instance, :show?
 
-      @instance        = Instance.new(Account.by_domain_accounts.find_by(domain: params[:id]) || DomainBlock.find_by!(domain: params[:id]))
       @following_count = Follow.where(account: Account.where(domain: params[:id])).count
       @followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count
       @reports_count   = Report.where(target_account: Account.where(domain: params[:id])).count
       @blocks_count    = Block.where(target_account: Account.where(domain: params[:id])).count
       @available       = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url)
       @media_storage   = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
-      @domain_block    = DomainBlock.rule_for(params[:id])
+      @private_comment = @domain_block&.private_comment
+      @public_comment  = @domain_block&.public_comment
     end
 
     private
 
+    def set_domain_block
+      @domain_block = DomainBlock.rule_for(params[:id])
+    end
+
+    def set_domain_allow
+      @domain_allow = DomainAllow.rule_for(params[:id])
+    end
+
+    def set_instance
+      resource   = Account.by_domain_accounts.find_by(domain: params[:id])
+      resource ||= @domain_block
+      resource ||= @domain_allow
+
+      if resource
+        @instance = Instance.new(resource)
+      else
+        not_found
+      end
+    end
+
     def filtered_instances
-      InstanceFilter.new(filter_params).results
+      InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
     end
 
     def paginated_instances
diff --git a/app/controllers/admin/relays_controller.rb b/app/controllers/admin/relays_controller.rb
index 1b02d3c361a9e77c49c2fc40e2e15fdf52afc05c..6fbb6e0630947a40fedab2a090ecebd1be7c9bdc 100644
--- a/app/controllers/admin/relays_controller.rb
+++ b/app/controllers/admin/relays_controller.rb
@@ -3,6 +3,7 @@
 module Admin
   class RelaysController < BaseController
     before_action :set_relay, except: [:index, :new, :create]
+    before_action :require_signatures_enabled!, only: [:new, :create, :enable]
 
     def index
       authorize :relay, :update?
@@ -11,7 +12,7 @@ module Admin
 
     def new
       authorize :relay, :update?
-      @relay = Relay.new(inbox_url: Relay::PRESET_RELAY)
+      @relay = Relay.new
     end
 
     def create
@@ -54,5 +55,9 @@ module Admin
     def resource_params
       params.require(:relay).permit(:inbox_url)
     end
+
+    def require_signatures_enabled!
+      redirect_to admin_relays_path, alert: I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode?
+    end
   end
 end
diff --git a/app/controllers/admin/report_notes_controller.rb b/app/controllers/admin/report_notes_controller.rb
index bcb3f20260dc8046436a692436d111b5f4894b10..b816c5b5d484ec88c4129cad3911aa2cf9d603e2 100644
--- a/app/controllers/admin/report_notes_controller.rb
+++ b/app/controllers/admin/report_notes_controller.rb
@@ -5,10 +5,10 @@ module Admin
     before_action :set_report_note, only: [:destroy]
 
     def create
-      authorize ReportNote, :create?
+      authorize :report_note, :create?
 
       @report_note = current_account.report_notes.new(resource_params)
-      @report = @report_note.report
+      @report      = @report_note.report
 
       if @report_note.save
         if params[:create_and_resolve]
@@ -26,9 +26,8 @@ module Admin
 
         redirect_to admin_report_path(@report), notice: I18n.t('admin.report_notes.created_msg')
       else
-        @report_notes = @report.notes.latest
-        @report_history = @report.history
-        @form = Form::StatusBatch.new
+        @report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
+        @form         = Form::StatusBatch.new
 
         render template: 'admin/reports/show'
       end
diff --git a/app/controllers/admin/tags_controller.rb b/app/controllers/admin/tags_controller.rb
index e9f4f2cfa3b35a67101d0957ee6a22f1f66f41fc..65341bbfbe74cc4259ce46cbc53145b41a667c14 100644
--- a/app/controllers/admin/tags_controller.rb
+++ b/app/controllers/admin/tags_controller.rb
@@ -2,43 +2,102 @@
 
 module Admin
   class TagsController < BaseController
-    before_action :set_tags, only: :index
-    before_action :set_tag, except: :index
-    before_action :set_filter_params
+    before_action :set_tag, except: [:index, :batch, :approve_all, :reject_all]
+    before_action :set_usage_by_domain, except: [:index, :batch, :approve_all, :reject_all]
+    before_action :set_counters, except: [:index, :batch, :approve_all, :reject_all]
 
     def index
       authorize :tag, :index?
+
+      @tags = filtered_tags.page(params[:page])
+      @form = Form::TagBatch.new
     end
 
-    def hide
-      authorize @tag, :hide?
-      @tag.account_tag_stat.update!(hidden: true)
-      redirect_to admin_tags_path(@filter_params)
+    def batch
+      @form = Form::TagBatch.new(form_tag_batch_params.merge(current_account: current_account, action: action_from_button))
+      @form.save
+    rescue ActionController::ParameterMissing
+      flash[:alert] = I18n.t('admin.accounts.no_account_selected')
+    ensure
+      redirect_to admin_tags_path(filter_params)
     end
 
-    def unhide
-      authorize @tag, :unhide?
-      @tag.account_tag_stat.update!(hidden: false)
-      redirect_to admin_tags_path(@filter_params)
+    def approve_all
+      Form::TagBatch.new(current_account: current_account, tag_ids: Tag.pending_review.pluck(:id), action: 'approve').save
+      redirect_to admin_tags_path(filter_params)
     end
 
-    private
+    def reject_all
+      Form::TagBatch.new(current_account: current_account, tag_ids: Tag.pending_review.pluck(:id), action: 'reject').save
+      redirect_to admin_tags_path(filter_params)
+    end
+
+    def show
+      authorize @tag, :show?
+    end
+
+    def update
+      authorize @tag, :update?
 
-    def set_tags
-      @tags = Tag.discoverable
-      @tags.merge!(Tag.hidden) if filter_params[:hidden]
+      if @tag.update(tag_params.merge(reviewed_at: Time.now.utc))
+        redirect_to admin_tag_path(@tag.id), notice: I18n.t('admin.tags.updated_msg')
+      else
+        render :show
+      end
     end
 
+    private
+
     def set_tag
       @tag = Tag.find(params[:id])
     end
 
-    def set_filter_params
-      @filter_params = filter_params.to_hash.symbolize_keys
+    def set_usage_by_domain
+      @usage_by_domain = @tag.statuses
+                             .with_public_visibility
+                             .excluding_silenced_accounts
+                             .where(Status.arel_table[:id].gteq(Mastodon::Snowflake.id_at(Time.now.utc.beginning_of_day)))
+                             .joins(:account)
+                             .group('accounts.domain')
+                             .reorder('statuses_count desc')
+                             .pluck('accounts.domain, count(*) AS statuses_count')
+    end
+
+    def set_counters
+      @accounts_today = @tag.history.first[:accounts]
+      @accounts_week  = Redis.current.pfcount(*current_week_days.map { |day| "activity:tags:#{@tag.id}:#{day}:accounts" })
+    end
+
+    def filtered_tags
+      TagFilter.new(filter_params).results
     end
 
     def filter_params
-      params.permit(:hidden)
+      params.slice(:directory, :reviewed, :unreviewed, :pending_review, :page, :popular, :active, :name).permit(:directory, :reviewed, :unreviewed, :pending_review, :page, :popular, :active, :name)
+    end
+
+    def tag_params
+      params.require(:tag).permit(:name, :trendable, :usable, :listable)
+    end
+
+    def current_week_days
+      now = Time.now.utc.beginning_of_day.to_date
+
+      (Date.commercial(now.cwyear, now.cweek)..now).map do |date|
+        date.to_time(:utc).beginning_of_day.to_i
+      end
+    end
+
+    def form_tag_batch_params
+      params.require(:form_tag_batch).permit(:action, tag_ids: [])
+    end
+
+    def action_from_button
+      if params[:approve]
+        'approve'
+      elsif params[:reject]
+        'reject'
+      end
     end
   end
 end
diff --git a/app/controllers/admin/two_factor_authentications_controller.rb b/app/controllers/admin/two_factor_authentications_controller.rb
index 2577a4b17fff4b59c93a506e4f0fecca0cbcb4cc..0652c3a7a4a1235ebd43bbb1d8f3ef82cb247537 100644
--- a/app/controllers/admin/two_factor_authentications_controller.rb
+++ b/app/controllers/admin/two_factor_authentications_controller.rb
@@ -8,6 +8,7 @@ module Admin
       authorize @user, :disable_2fa?
       @user.disable_two_factor!
       log_action :disable_2fa, @user
+      UserMailer.two_factor_disabled(@user).deliver_later!
       redirect_to admin_accounts_path
     end
 
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index eca558f4216ee1f8e565867a6dfe39ee5420463b..33df75b374b388b8f9bf8cf276cf7574ad5ebd50 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -7,12 +7,15 @@ class Api::BaseController < ApplicationController
   include RateLimitHeaders
 
   skip_before_action :store_current_location
-  skip_before_action :check_user_permissions
+  skip_before_action :require_functional!
 
+  before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
   before_action :set_cache_headers
 
   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
@@ -33,6 +36,14 @@ class Api::BaseController < ApplicationController
     render json: { error: 'This action is not allowed' }, status: 403
   end
 
+  rescue_from Mastodon::RaceConditionError do
+    render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
+  end
+
+  rescue_from ActionController::ParameterMissing do |e|
+    render json: { error: e.to_s }, status: 400
+  end
+
   def doorkeeper_unauthorized_render_options(error: nil)
     { json: { error: (error.try(:description) || 'Not authorized') } }
   end
@@ -69,6 +80,10 @@ class Api::BaseController < ApplicationController
     nil
   end
 
+  def require_authenticated_user!
+    render json: { error: 'This API requires an authenticated user' }, status: 401 unless current_user
+  end
+
   def require_user!
     if !current_user
       render json: { error: 'This method requires an authenticated user' }, status: 422
@@ -94,4 +109,8 @@ class Api::BaseController < ApplicationController
   def set_cache_headers
     response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
   end
+
+  def disallow_unauthenticated_api_access?
+    authorized_fetch_mode?
+  end
 end
diff --git a/app/controllers/api/proofs_controller.rb b/app/controllers/api/proofs_controller.rb
index a84ad2014fe7320135257bdec73943ec55f47952..a98599eee668009f4d1d911fd364535698dedfae 100644
--- a/app/controllers/api/proofs_controller.rb
+++ b/app/controllers/api/proofs_controller.rb
@@ -1,10 +1,9 @@
 # frozen_string_literal: true
 
 class Api::ProofsController < Api::BaseController
-  before_action :set_account
+  include AccountOwnedConcern
+
   before_action :set_provider
-  before_action :check_account_approval
-  before_action :check_account_suspension
 
   def index
     render json: @account, serializer: @provider.serializer_class
@@ -16,15 +15,7 @@ class Api::ProofsController < Api::BaseController
     @provider = ProofProvider.find(params[:provider]) || raise(ActiveRecord::RecordNotFound)
   end
 
-  def set_account
-    @account = Account.find_local!(params[:username])
-  end
-
-  def check_account_approval
-    not_found if @account.user_pending?
-  end
-
-  def check_account_suspension
-    gone if @account.suspended?
+  def username_param
+    params[:username]
   end
 end
diff --git a/app/controllers/api/push_controller.rb b/app/controllers/api/push_controller.rb
deleted file mode 100644
index e04d19125b8d3ef67fd614e16216c5ee25539db3..0000000000000000000000000000000000000000
--- a/app/controllers/api/push_controller.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-class Api::PushController < Api::BaseController
-  include SignatureVerification
-
-  def update
-    response, status = process_push_request
-    render plain: response, status: status
-  end
-
-  private
-
-  def process_push_request
-    case hub_mode
-    when 'subscribe'
-      Pubsubhubbub::SubscribeService.new.call(account_from_topic, hub_callback, hub_secret, hub_lease_seconds, verified_domain)
-    when 'unsubscribe'
-      Pubsubhubbub::UnsubscribeService.new.call(account_from_topic, hub_callback)
-    else
-      ["Unknown mode: #{hub_mode}", 422]
-    end
-  end
-
-  def hub_mode
-    params['hub.mode']
-  end
-
-  def hub_topic
-    params['hub.topic']
-  end
-
-  def hub_callback
-    params['hub.callback']
-  end
-
-  def hub_lease_seconds
-    params['hub.lease_seconds']
-  end
-
-  def hub_secret
-    params['hub.secret']
-  end
-
-  def account_from_topic
-    if hub_topic.present? && local_domain? && account_feed_path?
-      Account.find_local(hub_topic_params[:username])
-    end
-  end
-
-  def hub_topic_params
-    @_hub_topic_params ||= Rails.application.routes.recognize_path(hub_topic_uri.path)
-  end
-
-  def hub_topic_uri
-    @_hub_topic_uri ||= Addressable::URI.parse(hub_topic).normalize
-  end
-
-  def local_domain?
-    TagManager.instance.web_domain?(hub_topic_domain)
-  end
-
-  def verified_domain
-    return signed_request_account.domain if signed_request_account
-  end
-
-  def hub_topic_domain
-    hub_topic_uri.host + (hub_topic_uri.port ? ":#{hub_topic_uri.port}" : '')
-  end
-
-  def account_feed_path?
-    hub_topic_params[:controller] == 'accounts' && hub_topic_params[:action] == 'show' && hub_topic_params[:format] == 'atom'
-  end
-end
diff --git a/app/controllers/api/salmon_controller.rb b/app/controllers/api/salmon_controller.rb
deleted file mode 100644
index ac5f3268d8c78a28428369c0166f430d71515958..0000000000000000000000000000000000000000
--- a/app/controllers/api/salmon_controller.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-class Api::SalmonController < Api::BaseController
-  include SignatureVerification
-
-  before_action :set_account
-  respond_to :txt
-
-  def update
-    if verify_payload?
-      process_salmon
-      head 202
-    elsif payload.present?
-      render plain: signature_verification_failure_reason, status: 401
-    else
-      head 400
-    end
-  end
-
-  private
-
-  def set_account
-    @account = Account.find(params[:id])
-  end
-
-  def payload
-    @_payload ||= request.body.read
-  end
-
-  def verify_payload?
-    payload.present? && VerifySalmonService.new.call(payload)
-  end
-
-  def process_salmon
-    SalmonWorker.perform_async(@account.id, payload.force_encoding('UTF-8'))
-  end
-end
diff --git a/app/controllers/api/subscriptions_controller.rb b/app/controllers/api/subscriptions_controller.rb
deleted file mode 100644
index 89007f3d6efbe6f26c26bb68d66a11f7e576e48d..0000000000000000000000000000000000000000
--- a/app/controllers/api/subscriptions_controller.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-class Api::SubscriptionsController < Api::BaseController
-  before_action :set_account
-  respond_to :txt
-
-  def show
-    if subscription.valid?(params['hub.topic'])
-      @account.update(subscription_expires_at: future_expires)
-      render plain: encoded_challenge, status: 200
-    else
-      head 404
-    end
-  end
-
-  def update
-    if subscription.verify(body, request.headers['HTTP_X_HUB_SIGNATURE'])
-      ProcessingWorker.perform_async(@account.id, body.force_encoding('UTF-8'))
-    end
-
-    head 200
-  end
-
-  private
-
-  def subscription
-    @_subscription ||= @account.subscription(
-      api_subscription_url(@account.id)
-    )
-  end
-
-  def body
-    @_body ||= request.body.read
-  end
-
-  def encoded_challenge
-    HTMLEntities.new.encode(params['hub.challenge'])
-  end
-
-  def future_expires
-    Time.now.utc + lease_seconds_or_default
-  end
-
-  def lease_seconds_or_default
-    (params['hub.lease_seconds'] || 1.day).to_i.seconds
-  end
-
-  def set_account
-    @account = Account.find(params[:id])
-  end
-end
diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb
index 13cb4caf1203c03b018fa42bf152fbd3a8494b63..333db96186cf67ef2e4cf9046e1d0ec89b56fdb4 100644
--- a/app/controllers/api/v1/accounts/statuses_controller.rb
+++ b/app/controllers/api/v1/accounts/statuses_controller.rb
@@ -29,14 +29,13 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
 
   def account_statuses
     statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
-    statuses = statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id))
 
     statuses.merge!(only_media_scope) if truthy_param?(:only_media)
     statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
     statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
     statuses.merge!(hashtag_scope)    if params[:tagged].present?
 
-    statuses
+    statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id))
   end
 
   def permitted_account_statuses
@@ -58,6 +57,8 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
   end
 
   def pinned_scope
+    return Status.none if @account.blocking?(current_account)
+
     @account.pinned_statuses
   end
 
diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index b0c62778e6563abe7894ff52e9493a1a377a89ab..d68d2715f71541ad619afdada6dad212f4a8fbfd 100644
--- a/app/controllers/api/v1/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts_controller.rb
@@ -12,6 +12,8 @@ class Api::V1::AccountsController < Api::BaseController
   before_action :check_account_suspension, only: [:show]
   before_action :check_enabled_registrations, only: [:create]
 
+  skip_before_action :require_authenticated_user!, only: :create
+
   respond_to :json
 
   def show
@@ -31,7 +33,7 @@ class Api::V1::AccountsController < Api::BaseController
   def follow
     FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs))
 
-    options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }
+    options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }
 
     render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
   end
@@ -76,7 +78,7 @@ class Api::V1::AccountsController < Api::BaseController
   end
 
   def account_params
-    params.permit(:username, :email, :password, :agreement, :locale)
+    params.permit(:username, :email, :password, :agreement, :locale, :reason)
   end
 
   def check_enabled_registrations
diff --git a/app/controllers/api/v1/admin/accounts_controller.rb b/app/controllers/api/v1/admin/accounts_controller.rb
index c306180ca25169a355b980a0237e479e5e7eaa1f..c35ea5ab254ffe77f33f193ec9febb3818fae72b 100644
--- a/app/controllers/api/v1/admin/accounts_controller.rb
+++ b/app/controllers/api/v1/admin/accounts_controller.rb
@@ -58,7 +58,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
 
   def reject
     authorize @account.user, :reject?
-    SuspendAccountService.new.call(@account, including_user: true, destroy: true, skip_distribution: true)
+    SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
     render json: @account, serializer: REST::Admin::AccountSerializer
   end
 
diff --git a/app/controllers/api/v1/apps_controller.rb b/app/controllers/api/v1/apps_controller.rb
index e9f7a7291c15d9fb896388e5e52e0cfcb4a33bc5..97177547a2b9aec652d50162472e86fe9972d7a8 100644
--- a/app/controllers/api/v1/apps_controller.rb
+++ b/app/controllers/api/v1/apps_controller.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Api::V1::AppsController < Api::BaseController
+  skip_before_action :require_authenticated_user!
+
   def create
     @app = Doorkeeper::Application.create!(application_options)
     render json: @app, serializer: REST::ApplicationSerializer
diff --git a/app/controllers/api/v1/custom_emojis_controller.rb b/app/controllers/api/v1/custom_emojis_controller.rb
index 1bb19a09d33ec4e3581f245154a001d2713b96d6..4e6d5d7c613a65da4203ceb4ce38f4f659fa64a0 100644
--- a/app/controllers/api/v1/custom_emojis_controller.rb
+++ b/app/controllers/api/v1/custom_emojis_controller.rb
@@ -6,8 +6,7 @@ class Api::V1::CustomEmojisController < Api::BaseController
   skip_before_action :set_cache_headers
 
   def index
-    render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do
-      ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer)
-    end
+    expires_in 3.minutes, public: true
+    render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.listed.includes(:category) }
   end
 end
diff --git a/app/controllers/api/v1/directories_controller.rb b/app/controllers/api/v1/directories_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c91543e3a3e969610a5b2d0b8a1c55808fcd1869
--- /dev/null
+++ b/app/controllers/api/v1/directories_controller.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class Api::V1::DirectoriesController < Api::BaseController
+  before_action :require_enabled!
+  before_action :set_accounts
+
+  def show
+    render json: @accounts, each_serializer: REST::AccountSerializer
+  end
+
+  private
+
+  def require_enabled!
+    return not_found unless Setting.profile_directory
+  end
+
+  def set_accounts
+    @accounts = accounts_scope.offset(params[:offset]).limit(limit_param(DEFAULT_ACCOUNTS_LIMIT))
+  end
+
+  def accounts_scope
+    Account.discoverable.tap do |scope|
+      scope.merge!(Account.local)                                          if truthy_param?(:local)
+      scope.merge!(Account.by_recent_status)                               if params[:order].blank? || params[:order] == 'active'
+      scope.merge!(Account.order(id: :desc))                               if params[:order] == 'new'
+      scope.merge!(Account.not_excluded_by_account(current_account))       if current_account
+      scope.merge!(Account.not_domain_blocked_by_account(current_account)) if current_account && !truthy_param?(:local)
+    end
+  end
+end
diff --git a/app/controllers/api/v1/featured_tags/suggestions_controller.rb b/app/controllers/api/v1/featured_tags/suggestions_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fb27ef88b93a95b7743cf66ac9d4b2ec4b4b6f89
--- /dev/null
+++ b/app/controllers/api/v1/featured_tags/suggestions_controller.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
+
+  before_action :require_user!
+  before_action :set_most_used_tags, only: :index
+
+  respond_to :json
+
+  def index
+    render json: @most_used_tags, each_serializer: REST::TagSerializer
+  end
+
+  private
+
+  def set_most_used_tags
+    @most_used_tags = Tag.most_used(current_account).where.not(id: current_account.featured_tags).limit(10)
+  end
+end
diff --git a/app/controllers/api/v1/featured_tags_controller.rb b/app/controllers/api/v1/featured_tags_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e4e836c9711dd4ab4fb346990d5611a95d5e5690
--- /dev/null
+++ b/app/controllers/api/v1/featured_tags_controller.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class Api::V1::FeaturedTagsController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
+  before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index
+
+  before_action :require_user!
+  before_action :set_featured_tags, only: :index
+  before_action :set_featured_tag, except: [:index, :create]
+
+  def index
+    render json: @featured_tags, each_serializer: REST::FeaturedTagSerializer
+  end
+
+  def create
+    @featured_tag = current_account.featured_tags.new(featured_tag_params)
+    @featured_tag.reset_data
+    @featured_tag.save!
+    render json: @featured_tag, serializer: REST::FeaturedTagSerializer
+  end
+
+  def destroy
+    @featured_tag.destroy!
+    render_empty
+  end
+
+  private
+
+  def set_featured_tag
+    @featured_tag = current_account.featured_tags.find(params[:id])
+  end
+
+  def set_featured_tags
+    @featured_tags = current_account.featured_tags.order(statuses_count: :desc)
+  end
+
+  def featured_tag_params
+    params.permit(:name)
+  end
+end
diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb
index e6888154e22217c23f79fd70d64ebd3c37cbd219..0ee6e531f07a4abacea52ccae938a14fdd92deb1 100644
--- a/app/controllers/api/v1/follow_requests_controller.rb
+++ b/app/controllers/api/v1/follow_requests_controller.rb
@@ -14,12 +14,12 @@ class Api::V1::FollowRequestsController < Api::BaseController
   def authorize
     AuthorizeFollowService.new.call(account, current_account)
     NotifyService.new.call(current_account, Follow.find_by(account: account, target_account: current_account))
-    render_empty
+    render json: account, serializer: REST::RelationshipSerializer, relationships: relationships
   end
 
   def reject
     RejectFollowService.new.call(account, current_account)
-    render_empty
+    render json: account, serializer: REST::RelationshipSerializer, relationships: relationships
   end
 
   private
@@ -28,6 +28,10 @@ class Api::V1::FollowRequestsController < Api::BaseController
     Account.find(params[:id])
   end
 
+  def relationships(**options)
+    AccountRelationshipsPresenter.new([params[:id]], current_user.account_id, options)
+  end
+
   def load_accounts
     default_accounts.merge(paginated_follow_requests).to_a
   end
diff --git a/app/controllers/api/v1/follows_controller.rb b/app/controllers/api/v1/follows_controller.rb
deleted file mode 100644
index 5420c053367ec68d2998f8af98effb671b03dcc3..0000000000000000000000000000000000000000
--- a/app/controllers/api/v1/follows_controller.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-class Api::V1::FollowsController < Api::BaseController
-  before_action -> { doorkeeper_authorize! :follow, :'write:follows' }
-  before_action :require_user!
-
-  respond_to :json
-
-  def create
-    raise ActiveRecord::RecordNotFound if follow_params[:uri].blank?
-
-    @account = FollowService.new.call(current_user.account, target_uri).try(:target_account)
-
-    if @account.nil?
-      username, domain = target_uri.split('@')
-      @account         = Account.find_remote!(username, domain)
-    end
-
-    render json: @account, serializer: REST::AccountSerializer
-  end
-
-  private
-
-  def target_uri
-    follow_params[:uri].strip.gsub(/\A@/, '')
-  end
-
-  def follow_params
-    params.permit(:uri)
-  end
-end
diff --git a/app/controllers/api/v1/instances/activity_controller.rb b/app/controllers/api/v1/instances/activity_controller.rb
index 09edfe365b07df3c057e3ca63f5dcb2a0c973ac3..b30e8464c3eb5d72563b9c42480dae10ac07bf96 100644
--- a/app/controllers/api/v1/instances/activity_controller.rb
+++ b/app/controllers/api/v1/instances/activity_controller.rb
@@ -2,12 +2,15 @@
 
 class Api::V1::Instances::ActivityController < Api::BaseController
   before_action :require_enabled_api!
+
   skip_before_action :set_cache_headers
+  skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
 
   respond_to :json
 
   def show
-    render_cached_json('api:v1:instances:activity:show', expires_in: 1.day) { activity }
+    expires_in 1.day, public: true
+    render_with_cache json: :activity, expires_in: 1.day
   end
 
   private
@@ -32,6 +35,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController
   end
 
   def require_enabled_api!
-    head 404 unless Setting.activity_api_enabled
+    head 404 unless Setting.activity_api_enabled && !whitelist_mode?
   end
 end
diff --git a/app/controllers/api/v1/instances/peers_controller.rb b/app/controllers/api/v1/instances/peers_controller.rb
index a8891d126bfd379895bc9eeea87b691d7ba926cc..cc00d8a6bc3ae1b08d2b4093a853f27414806b73 100644
--- a/app/controllers/api/v1/instances/peers_controller.rb
+++ b/app/controllers/api/v1/instances/peers_controller.rb
@@ -2,17 +2,20 @@
 
 class Api::V1::Instances::PeersController < Api::BaseController
   before_action :require_enabled_api!
+
   skip_before_action :set_cache_headers
+  skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
 
   respond_to :json
 
   def index
-    render_cached_json('api:v1:instances:peers:index', expires_in: 1.day) { Account.remote.domains }
+    expires_in 1.day, public: true
+    render_with_cache(expires_in: 1.day) { Account.remote.domains }
   end
 
   private
 
   def require_enabled_api!
-    head 404 unless Setting.peers_api_enabled
+    head 404 unless Setting.peers_api_enabled && !whitelist_mode?
   end
 end
diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb
index 8c83a180149cab1a7b85e62a1c2ce35fdd798fd1..c323b60b4292c89fab207db2882409f696f2a9af 100644
--- a/app/controllers/api/v1/instances_controller.rb
+++ b/app/controllers/api/v1/instances_controller.rb
@@ -2,11 +2,12 @@
 
 class Api::V1::InstancesController < Api::BaseController
   respond_to :json
+
   skip_before_action :set_cache_headers
+  skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
 
   def show
-    render_cached_json('api:v1:instances', expires_in: 5.minutes) do
-      ActiveModelSerializers::SerializableResource.new({}, serializer: REST::InstanceSerializer)
-    end
+    expires_in 3.minutes, public: true
+    render_with_cache json: {}, serializer: REST::InstanceSerializer, root: 'instance'
   end
 end
diff --git a/app/controllers/api/v1/markers_controller.rb b/app/controllers/api/v1/markers_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..28c2ec79168478bbb7b441a00fb1924f620a2348
--- /dev/null
+++ b/app/controllers/api/v1/markers_controller.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+class Api::V1::MarkersController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:index]
+  before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, except: [:index]
+
+  before_action :require_user!
+
+  def index
+    @markers = current_user.markers.where(timeline: Array(params[:timeline])).each_with_object({}) { |marker, h| h[marker.timeline] = marker }
+    render json: serialize_map(@markers)
+  end
+
+  def create
+    Marker.transaction do
+      @markers = {}
+
+      resource_params.each_pair do |timeline, timeline_params|
+        @markers[timeline] = current_user.markers.find_or_initialize_by(timeline: timeline)
+        @markers[timeline].update!(timeline_params)
+      end
+    end
+
+    render json: serialize_map(@markers)
+  rescue ActiveRecord::StaleObjectError
+    render json: { error: 'Conflict during update, please try again' }, status: 409
+  end
+
+  private
+
+  def serialize_map(map)
+    serialized = {}
+
+    map.each_pair do |key, value|
+      serialized[key] = ActiveModelSerializers::SerializableResource.new(value, serializer: REST::MarkerSerializer).as_json
+    end
+
+    Oj.dump(serialized)
+  end
+
+  def resource_params
+    params.slice(*Marker::TIMELINES).permit(*Marker::TIMELINES.map { |timeline| { timeline.to_sym => [:last_read_id] } })
+  end
+end
diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb
index e182a9c6cf6245fb052b435416e2d667ba212390..1b0b4b05b2e72de95fd3b4836261d1aad25eda04 100644
--- a/app/controllers/api/v1/reports_controller.rb
+++ b/app/controllers/api/v1/reports_controller.rb
@@ -21,7 +21,7 @@ class Api::V1::ReportsController < Api::BaseController
   private
 
   def reported_status_ids
-    reported_account.statuses.find(status_ids).pluck(:id)
+    reported_account.statuses.with_discarded.find(status_ids).pluck(:id)
   end
 
   def status_ids
diff --git a/app/controllers/api/v1/search_controller.rb b/app/controllers/api/v1/search_controller.rb
deleted file mode 100644
index 6131cbbb69fb1250ea102112ac51d0ebe04d200a..0000000000000000000000000000000000000000
--- a/app/controllers/api/v1/search_controller.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-class Api::V1::SearchController < Api::BaseController
-  include Authorization
-
-  RESULTS_LIMIT = 20
-
-  before_action -> { doorkeeper_authorize! :read, :'read:search' }
-  before_action :require_user!
-
-  respond_to :json
-
-  def index
-    @search = Search.new(search_results)
-    render json: @search, serializer: REST::SearchSerializer
-  end
-
-  private
-
-  def search_results
-    SearchService.new.call(
-      params[:q],
-      current_account,
-      limit_param(RESULTS_LIMIT),
-      search_params.merge(resolve: truthy_param?(:resolve))
-    )
-  end
-
-  def search_params
-    params.permit(:type, :offset, :min_id, :max_id, :account_id)
-  end
-end
diff --git a/app/controllers/api/v1/statuses/reblogs_controller.rb b/app/controllers/api/v1/statuses/reblogs_controller.rb
index ed4f551003d670061f58f75382af1fb6aa8542d6..42381a37fdbe8ab35d38dc24932830372c4168c8 100644
--- a/app/controllers/api/v1/statuses/reblogs_controller.rb
+++ b/app/controllers/api/v1/statuses/reblogs_controller.rb
@@ -18,6 +18,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
     @reblogs_map = { @status.id => false }
 
     authorize status_for_destroy, :unreblog?
+    status_for_destroy.discard
     RemovalWorker.perform_async(status_for_destroy.id)
 
     render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, reblogs_map: @reblogs_map)
@@ -30,7 +31,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
   end
 
   def status_for_destroy
-    current_user.account.statuses.where(reblog_of_id: params[:status_id]).first!
+    @status_for_destroy ||= current_user.account.statuses.where(reblog_of_id: params[:status_id]).first!
   end
 
   def reblog_params
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index 82cc05685537d5dd0085d62b69a00bdbb42df052..cb454e286473707a8c779c058a534bbe4f6ab39f 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -5,8 +5,8 @@ class Api::V1::StatusesController < Api::BaseController
 
   before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :destroy]
   before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only:   [:create, :destroy]
-  before_action :require_user!, except:  [:show, :context, :card]
-  before_action :set_status, only:       [:show, :context, :card]
+  before_action :require_user!, except:  [:show, :context]
+  before_action :set_status, only:       [:show, :context]
 
   respond_to :json
 
@@ -33,16 +33,6 @@ class Api::V1::StatusesController < Api::BaseController
     render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
   end
 
-  def card
-    @card = @status.preview_cards.first
-
-    if @card.nil?
-      render_empty
-    else
-      render json: @card, serializer: REST::PreviewCardSerializer
-    end
-  end
-
   def create
     @status = PostStatusService.new.call(current_user.account,
                                          text: status_params[:status],
@@ -64,7 +54,8 @@ class Api::V1::StatusesController < Api::BaseController
     @status = Status.where(account_id: current_user.account).find(params[:id])
     authorize @status, :destroy?
 
-    RemovalWorker.perform_async(@status.id)
+    @status.discard
+    RemovalWorker.perform_async(@status.id, redraft: true)
 
     render json: @status, serializer: REST::StatusSerializer, source_requested: true
   end
diff --git a/app/controllers/api/v1/streaming_controller.rb b/app/controllers/api/v1/streaming_controller.rb
index 66b812e761f641f39301843a824b303ecf3268e3..ebb17608c107df1cef434cb42015cec6f53c3ac9 100644
--- a/app/controllers/api/v1/streaming_controller.rb
+++ b/app/controllers/api/v1/streaming_controller.rb
@@ -5,11 +5,17 @@ class Api::V1::StreamingController < Api::BaseController
 
   def index
     if Rails.configuration.x.streaming_api_base_url != request.host
-      uri = URI.parse(request.url)
-      uri.host = URI.parse(Rails.configuration.x.streaming_api_base_url).host
-      redirect_to uri.to_s, status: 301
+      redirect_to streaming_api_url, status: 301
     else
-      raise ActiveRecord::RecordNotFound
+      not_found
     end
   end
+
+  private
+
+  def streaming_api_url
+    Addressable::URI.parse(request.url).tap do |uri|
+      uri.host = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url).host
+    end.to_s
+  end
 end
diff --git a/app/controllers/api/v1/timelines/direct_controller.rb b/app/controllers/api/v1/timelines/direct_controller.rb
deleted file mode 100644
index d8a76d153a6c5167492af980a3e2562924d22131..0000000000000000000000000000000000000000
--- a/app/controllers/api/v1/timelines/direct_controller.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-# frozen_string_literal: true
-
-class Api::V1::Timelines::DirectController < Api::BaseController
-  before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:show]
-  before_action :require_user!, only: [:show]
-  after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
-
-  respond_to :json
-
-  def show
-    @statuses = load_statuses
-    render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
-  end
-
-  private
-
-  def load_statuses
-    cached_direct_statuses
-  end
-
-  def cached_direct_statuses
-    cache_collection direct_statuses, Status
-  end
-
-  def direct_statuses
-    direct_timeline_statuses
-  end
-
-  def direct_timeline_statuses
-    # this query requires built in pagination.
-    Status.as_direct_timeline(
-      current_account,
-      limit_param(DEFAULT_STATUSES_LIMIT),
-      params[:max_id],
-      params[:since_id],
-      true # returns array of cache_ids object
-    )
-  end
-
-  def insert_pagination_headers
-    set_pagination_headers(next_path, prev_path)
-  end
-
-  def pagination_params(core_params)
-    params.permit(:local, :limit).merge(core_params)
-  end
-
-  def next_path
-    api_v1_timelines_direct_url pagination_params(max_id: pagination_max_id)
-  end
-
-  def prev_path
-    api_v1_timelines_direct_url pagination_params(since_id: pagination_since_id)
-  end
-
-  def pagination_max_id
-    @statuses.last.id
-  end
-
-  def pagination_since_id
-    @statuses.first.id
-  end
-end
diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb
index fcd0757f1a87c0d6792cfb12de536d9ef46c4643..ff5ede1385234ea19196a9559fc6694017a06a53 100644
--- a/app/controllers/api/v1/timelines/home_controller.rb
+++ b/app/controllers/api/v1/timelines/home_controller.rb
@@ -13,7 +13,7 @@ class Api::V1::Timelines::HomeController < Api::BaseController
     render json: @statuses,
            each_serializer: REST::StatusSerializer,
            relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
-           status: regeneration_in_progress? ? 206 : 200
+           status: account_home_feed.regenerating? ? 206 : 200
   end
 
   private
@@ -62,8 +62,4 @@ class Api::V1::Timelines::HomeController < Api::BaseController
   def pagination_since_id
     @statuses.first.id
   end
-
-  def regeneration_in_progress?
-    Redis.current.exists("account:#{current_account.id}:regeneration")
-  end
 end
diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb
index aabe24324312421b5f9c0765b3a7c037907ab9df..ccc10f966ca64789600fa8397108568a6860d39e 100644
--- a/app/controllers/api/v1/timelines/public_controller.rb
+++ b/app/controllers/api/v1/timelines/public_controller.rb
@@ -1,6 +1,7 @@
 # frozen_string_literal: true
 
 class Api::V1::Timelines::PublicController < Api::BaseController
+  before_action :require_user!, only: [:show], if: :require_auth?
   after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
 
   respond_to :json
@@ -12,6 +13,10 @@ class Api::V1::Timelines::PublicController < Api::BaseController
 
   private
 
+  def require_auth?
+    !Setting.timeline_preview
+  end
+
   def load_statuses
     cached_public_statuses
   end
diff --git a/app/controllers/api/v1/trends_controller.rb b/app/controllers/api/v1/trends_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bcea9857e8fa719c0c4ee3c3ce3a92c336d02f94
--- /dev/null
+++ b/app/controllers/api/v1/trends_controller.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class Api::V1::TrendsController < Api::BaseController
+  before_action :set_tags
+
+  respond_to :json
+
+  def index
+    render json: @tags, each_serializer: REST::TagSerializer
+  end
+
+  private
+
+  def set_tags
+    @tags = TrendingTags.get(limit_param(10))
+  end
+end
diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb
index 9aa6edc6969eb2bbda1b7bbcea3ce00aa8062d58..cbd9b551d091ba52ad0ed8bd7c3629bb25d33d5e 100644
--- a/app/controllers/api/v2/search_controller.rb
+++ b/app/controllers/api/v2/search_controller.rb
@@ -1,8 +1,32 @@
 # frozen_string_literal: true
 
-class Api::V2::SearchController < Api::V1::SearchController
+class Api::V2::SearchController < Api::BaseController
+  include Authorization
+
+  RESULTS_LIMIT = 20
+
+  before_action -> { doorkeeper_authorize! :read, :'read:search' }
+  before_action :require_user!
+
+  respond_to :json
+
   def index
     @search = Search.new(search_results)
-    render json: @search, serializer: REST::V2::SearchSerializer
+    render json: @search, serializer: REST::SearchSerializer
+  end
+
+  private
+
+  def search_results
+    SearchService.new.call(
+      params[:q],
+      current_account,
+      limit_param(RESULTS_LIMIT),
+      search_params.merge(resolve: truthy_param?(:resolve), exclude_unreviewed: truthy_param?(:exclude_unreviewed))
+    )
+  end
+
+  def search_params
+    params.permit(:type, :offset, :min_id, :max_id, :account_id)
   end
 end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 6b8411402beebba71d71eb87d7fdf03065f25ebc..bd3d137743f89339f8d7629a634a093435094c8e 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -10,21 +10,29 @@ class ApplicationController < ActionController::Base
   include Localized
   include UserTrackingConcern
   include SessionTrackingConcern
+  include CacheConcern
+  include DomainControlHelper
 
   helper_method :current_account
   helper_method :current_session
   helper_method :current_theme
   helper_method :single_user_mode?
   helper_method :use_seamless_external_login?
+  helper_method :whitelist_mode?
 
   rescue_from ActionController::RoutingError, with: :not_found
-  rescue_from ActiveRecord::RecordNotFound, with: :not_found
   rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
   rescue_from ActionController::UnknownFormat, with: :not_acceptable
+  rescue_from ActionController::ParameterMissing, with: :bad_request
+  rescue_from ActiveRecord::RecordNotFound, with: :not_found
   rescue_from Mastodon::NotPermittedError, with: :forbidden
+  rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
+  rescue_from Mastodon::RaceConditionError, with: :service_unavailable
 
   before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
-  before_action :check_user_permissions, if: :user_signed_in?
+  before_action :require_functional!, if: :user_signed_in?
+
+  skip_before_action :verify_authenticity_token, only: :raise_not_found
 
   def raise_not_found
     raise ActionController::RoutingError, "No route matches #{params[:unmatched_route]}"
@@ -33,7 +41,15 @@ class ApplicationController < ActionController::Base
   private
 
   def https_enabled?
-    Rails.env.production?
+    Rails.env.production? && !request.path.start_with?('/health')
+  end
+
+  def authorized_fetch_mode?
+    ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode
+  end
+
+  def public_fetch_mode?
+    !authorized_fetch_mode?
   end
 
   def store_current_location
@@ -48,8 +64,8 @@ class ApplicationController < ActionController::Base
     forbidden unless current_user&.staff?
   end
 
-  def check_user_permissions
-    forbidden if current_user.disabled? || current_user.account.suspended?
+  def require_functional!
+    redirect_to edit_user_registration_path unless current_user.functional?
   end
 
   def after_sign_out_path_for(_resource_or_scope)
@@ -82,8 +98,20 @@ class ApplicationController < ActionController::Base
     respond_with_error(406)
   end
 
+  def bad_request
+    respond_with_error(400)
+  end
+
+  def internal_server_error
+    respond_with_error(500)
+  end
+
+  def service_unavailable
+    respond_with_error(503)
+  end
+
   def single_user_mode?
-    @single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists?
+    @single_user_mode ||= Rails.configuration.x.single_user_mode && Account.where('id > 0').exists?
   end
 
   def use_seamless_external_login?
@@ -107,51 +135,10 @@ class ApplicationController < ActionController::Base
     current_user.setting_theme
   end
 
-  def cache_collection(raw, klass)
-    return raw unless klass.respond_to?(:with_includes)
-
-    raw                    = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
-    cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
-    uncached_ids           = raw.map(&:id) - cached_keys_with_value.keys
-
-    klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
-
-    unless uncached_ids.empty?
-      uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item }
-
-      uncached.each_value do |item|
-        Rails.cache.write(item, item)
-      end
-    end
-
-    raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
-  end
-
   def respond_with_error(code)
     respond_to do |format|
       format.any  { head code }
       format.html { render "errors/#{code}", layout: 'error', status: code }
     end
   end
-
-  def render_cached_json(cache_key, **options)
-    options[:expires_in] ||= 3.minutes
-    cache_public           = options.key?(:public) ? options.delete(:public) : true
-    content_type           = options.delete(:content_type) || 'application/json'
-
-    data = Rails.cache.fetch(cache_key, { raw: true }.merge(options)) do
-      yield.to_json
-    end
-
-    expires_in options[:expires_in], public: cache_public
-    render json: data, content_type: content_type
-  end
-
-  def set_cache_headers
-    response.headers['Vary'] = 'Accept'
-  end
-
-  def mark_cacheable!
-    expires_in 0, public: true
-  end
 end
diff --git a/app/controllers/auth/challenges_controller.rb b/app/controllers/auth/challenges_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..060944240a221ae05facc18d4b25bc58470b1edb
--- /dev/null
+++ b/app/controllers/auth/challenges_controller.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class Auth::ChallengesController < ApplicationController
+  include ChallengableConcern
+
+  layout 'auth'
+
+  before_action :authenticate_user!
+
+  skip_before_action :require_functional!
+
+  def create
+    if challenge_passed?
+      session[:challenge_passed_at] = Time.now.utc
+      redirect_to challenge_params[:return_to]
+    else
+      @challenge = Form::Challenge.new(return_to: challenge_params[:return_to])
+      flash.now[:alert] = I18n.t('challenge.invalid_password')
+      render_challenge
+    end
+  end
+end
diff --git a/app/controllers/auth/confirmations_controller.rb b/app/controllers/auth/confirmations_controller.rb
index c28c7471c0d1636b5f14f59400c4d8746a8d262b..89852526998dbedcedbcee3ace41ef724f4eb7e5 100644
--- a/app/controllers/auth/confirmations_controller.rb
+++ b/app/controllers/auth/confirmations_controller.rb
@@ -4,32 +4,36 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
   layout 'auth'
 
   before_action :set_body_classes
-  before_action :set_user, only: [:finish_signup]
+  before_action :require_unconfirmed!
 
-  def finish_signup
-    return unless request.patch? && params[:user]
+  skip_before_action :require_functional!
 
-    if @user.update(user_params)
-      @user.skip_reconfirmation!
-      bypass_sign_in(@user)
-      redirect_to root_path, notice: I18n.t('devise.confirmations.send_instructions')
-    else
-      @show_errors = true
-    end
+  def new
+    super
+
+    resource.email = current_user.unconfirmed_email || current_user.email if user_signed_in?
   end
 
   private
 
-  def set_user
-    @user = current_user
+  def require_unconfirmed!
+    redirect_to edit_user_registration_path if user_signed_in? && current_user.confirmed? && current_user.unconfirmed_email.blank?
   end
 
   def set_body_classes
     @body_classes = 'lighter'
   end
 
-  def user_params
-    params.require(:user).permit(:email)
+  def after_resending_confirmation_instructions_path_for(_resource_name)
+    if user_signed_in?
+      if current_user.confirmed? && current_user.approved?
+        edit_user_registration_path
+      else
+        auth_setup_path
+      end
+    else
+      new_user_session_path
+    end
   end
 
   def after_confirmation_path_for(_resource_name, user)
diff --git a/app/controllers/auth/omniauth_callbacks_controller.rb b/app/controllers/auth/omniauth_callbacks_controller.rb
index bbf63bed304f9ea55e9ad8aee6a329a433420ae7..682c77016fd6cd07eb7cb6fd042ab9d7186c0aa7 100644
--- a/app/controllers/auth/omniauth_callbacks_controller.rb
+++ b/app/controllers/auth/omniauth_callbacks_controller.rb
@@ -27,7 +27,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
     if resource.email_verified?
       root_path
     else
-      finish_signup_path
+      auth_setup_path(missing_email: '1')
     end
   end
 end
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index 83797cf1f7600d1e09a57f67a492058ba1355e70..019caf9c1aac5fc329e4dec2c9654b0b1abd92e8 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -9,6 +9,9 @@ class Auth::RegistrationsController < Devise::RegistrationsController
   before_action :set_sessions, only: [:edit, :update]
   before_action :set_instance_presenter, only: [:new, :create, :update]
   before_action :set_body_classes, only: [:new, :create, :edit, :update]
+  before_action :require_not_suspended!, only: [:update]
+
+  skip_before_action :require_functional!, only: [:edit, :update]
 
   def new
     super(&:build_invite_request)
@@ -43,7 +46,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
   end
 
   def after_sign_up_path_for(_resource)
-    new_user_session_path
+    auth_setup_path
   end
 
   def after_sign_in_path_for(_resource)
@@ -102,4 +105,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
   def set_sessions
     @sessions = current_user.session_activations
   end
+
+  def require_not_suspended!
+    forbidden if current_account.suspended?
+  end
 end
diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb
index fb8615c3134dc5b6db94ddf648b7f7bda1717340..f48b17c79935ceb3bb9141c1bab52fc9aa296a0d 100644
--- a/app/controllers/auth/sessions_controller.rb
+++ b/app/controllers/auth/sessions_controller.rb
@@ -6,8 +6,10 @@ class Auth::SessionsController < Devise::SessionsController
   layout 'auth'
 
   skip_before_action :require_no_authentication, only: [:create]
-  skip_before_action :check_user_permissions, only: [:destroy]
+  skip_before_action :require_functional!
+
   prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
+
   before_action :set_instance_presenter, only: [:new]
   before_action :set_body_classes
 
@@ -29,6 +31,7 @@ class Auth::SessionsController < Devise::SessionsController
   def destroy
     tmp_stored_location = stored_location_for(:user)
     super
+    session.delete(:challenge_passed_at)
     flash.delete(:notice)
     store_location_for(:user, tmp_stored_location) if continue_after?
   end
@@ -38,12 +41,10 @@ class Auth::SessionsController < Devise::SessionsController
   def find_user
     if session[:otp_user_id]
       User.find(session[:otp_user_id])
-    elsif user_params[:email]
-      if use_seamless_external_login? && Devise.check_at_sign && user_params[:email].index('@').nil?
-        User.joins(:account).find_by(accounts: { username: user_params[:email] })
-      else
-        User.find_for_authentication(email: user_params[:email])
-      end
+    else
+      user   = User.authenticate_with_ldap(user_params) if Devise.ldap_authentication
+      user ||= User.authenticate_with_pam(user_params) if Devise.pam_authentication
+      user ||= User.find_for_authentication(email: user_params[:email])
     end
   end
 
@@ -70,13 +71,13 @@ class Auth::SessionsController < Devise::SessionsController
   end
 
   def two_factor_enabled?
-    find_user.try(:otp_required_for_login?)
+    find_user&.otp_required_for_login?
   end
 
   def valid_otp_attempt?(user)
     user.validate_and_consume_otp!(user_params[:otp_attempt]) ||
       user.invalidate_otp_backup_code!(user_params[:otp_attempt])
-  rescue OpenSSL::Cipher::CipherError => _error
+  rescue OpenSSL::Cipher::CipherError
     false
   end
 
@@ -85,7 +86,10 @@ class Auth::SessionsController < Devise::SessionsController
 
     if user_params[:otp_attempt].present? && session[:otp_user_id]
       authenticate_with_two_factor_via_otp(user)
-    elsif user&.valid_password?(user_params[:password])
+    elsif user.present? && (user.encrypted_password.blank? || user.valid_password?(user_params[:password]))
+      # If encrypted_password is blank, we got the user from LDAP or PAM,
+      # so credentials are already valid
+
       prompt_for_two_factor(user)
     end
   end
@@ -103,6 +107,7 @@ class Auth::SessionsController < Devise::SessionsController
 
   def prompt_for_two_factor(user)
     session[:otp_user_id] = user.id
+    @body_classes = 'lighter'
     render :two_factor
   end
 
diff --git a/app/controllers/auth/setup_controller.rb b/app/controllers/auth/setup_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..46c5f2958174c3b853c7c5886f27ff0e6c6c406e
--- /dev/null
+++ b/app/controllers/auth/setup_controller.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+class Auth::SetupController < ApplicationController
+  layout 'auth'
+
+  before_action :authenticate_user!
+  before_action :require_unconfirmed_or_pending!
+  before_action :set_body_classes
+  before_action :set_user
+
+  skip_before_action :require_functional!
+
+  def show
+    flash.now[:notice] = begin
+      if @user.pending?
+        I18n.t('devise.registrations.signed_up_but_pending')
+      else
+        I18n.t('devise.registrations.signed_up_but_unconfirmed')
+      end
+    end
+  end
+
+  def update
+    # This allows updating the e-mail without entering a password as is required
+    # on the account settings page; however, we only allow this for accounts
+    # that were not confirmed yet
+
+    if @user.update(user_params)
+      redirect_to auth_setup_path, notice: I18n.t('devise.confirmations.send_instructions')
+    else
+      render :show
+    end
+  end
+
+  helper_method :missing_email?
+
+  private
+
+  def require_unconfirmed_or_pending!
+    redirect_to root_path if current_user.confirmed? && current_user.approved?
+  end
+
+  def set_user
+    @user = current_user
+  end
+
+  def set_body_classes
+    @body_classes = 'lighter'
+  end
+
+  def user_params
+    params.require(:user).permit(:email)
+  end
+
+  def missing_email?
+    truthy_param?(:missing_email)
+  end
+end
diff --git a/app/controllers/concerns/account_controller_concern.rb b/app/controllers/concerns/account_controller_concern.rb
index 1c422096c63516647c650a5943a6293196859d46..11eac0eb6bfc3f66dbd2f631711465431e2a4d6a 100644
--- a/app/controllers/concerns/account_controller_concern.rb
+++ b/app/controllers/concerns/account_controller_concern.rb
@@ -3,24 +3,19 @@
 module AccountControllerConcern
   extend ActiveSupport::Concern
 
+  include AccountOwnedConcern
+
   FOLLOW_PER_PAGE = 12
 
   included do
     layout 'public'
 
-    before_action :set_account
-    before_action :check_account_approval
-    before_action :check_account_suspension
     before_action :set_instance_presenter
-    before_action :set_link_headers
+    before_action :set_link_headers, if: -> { request.format.nil? || request.format == :html }
   end
 
   private
 
-  def set_account
-    @account = Account.find_local!(username_param)
-  end
-
   def set_instance_presenter
     @instance_presenter = InstancePresenter.new
   end
@@ -29,27 +24,15 @@ module AccountControllerConcern
     response.headers['Link'] = LinkHeader.new(
       [
         webfinger_account_link,
-        atom_account_url_link,
         actor_url_link,
       ]
     )
   end
 
-  def username_param
-    params[:account_username]
-  end
-
   def webfinger_account_link
     [
       webfinger_account_url,
-      [%w(rel lrdd), %w(type application/xrd+xml)],
-    ]
-  end
-
-  def atom_account_url_link
-    [
-      account_url(@account, format: 'atom'),
-      [%w(rel alternate), %w(type application/atom+xml)],
+      [%w(rel lrdd), %w(type application/jrd+json)],
     ]
   end
 
@@ -63,15 +46,4 @@ module AccountControllerConcern
   def webfinger_account_url
     webfinger_url(resource: @account.to_webfinger_s)
   end
-
-  def check_account_approval
-    not_found if @account.user_pending?
-  end
-
-  def check_account_suspension
-    if @account.suspended?
-      expires_in(3.minutes, public: true)
-      gone
-    end
-  end
 end
diff --git a/app/controllers/concerns/account_owned_concern.rb b/app/controllers/concerns/account_owned_concern.rb
new file mode 100644
index 0000000000000000000000000000000000000000..460f71f65faf8da8f90d00ef4a36d4c9960ece43
--- /dev/null
+++ b/app/controllers/concerns/account_owned_concern.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module AccountOwnedConcern
+  extend ActiveSupport::Concern
+
+  included do
+    before_action :authenticate_user!, if: -> { whitelist_mode? && request.format != :json }
+    before_action :set_account, if: :account_required?
+    before_action :check_account_approval, if: :account_required?
+    before_action :check_account_suspension, if: :account_required?
+  end
+
+  private
+
+  def account_required?
+    true
+  end
+
+  def set_account
+    @account = Account.find_local!(username_param)
+  end
+
+  def username_param
+    params[:account_username]
+  end
+
+  def check_account_approval
+    not_found if @account.local? && @account.user_pending?
+  end
+
+  def check_account_suspension
+    expires_in(3.minutes, public: true) && gone if @account.suspended?
+  end
+end
diff --git a/app/controllers/concerns/cache_concern.rb b/app/controllers/concerns/cache_concern.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c7d25ae00c85e019093284d5ef288bdd8c9ef068
--- /dev/null
+++ b/app/controllers/concerns/cache_concern.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module CacheConcern
+  extend ActiveSupport::Concern
+
+  def render_with_cache(**options)
+    raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given?
+
+    key        = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
+    expires_in = options.delete(:expires_in) || 3.minutes
+    body       = Rails.cache.read(key, raw: true)
+
+    if body
+      render(options.except(:json, :serializer, :each_serializer, :adapter, :fields).merge(json: body))
+    else
+      if block_given?
+        options[:json] = yield
+      elsif options[:json].is_a?(Symbol)
+        options[:json] = send(options[:json])
+      end
+
+      render(options)
+      Rails.cache.write(key, response.body, expires_in: expires_in, raw: true)
+    end
+  end
+
+  def set_cache_headers
+    response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
+  end
+
+  def cache_collection(raw, klass)
+    return raw unless klass.respond_to?(:with_includes)
+
+    raw                    = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
+    cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
+    uncached_ids           = raw.map(&:id) - cached_keys_with_value.keys
+
+    klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
+
+    unless uncached_ids.empty?
+      uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item }
+
+      uncached.each_value do |item|
+        Rails.cache.write(item, item)
+      end
+    end
+
+    raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
+  end
+end
diff --git a/app/controllers/concerns/challengable_concern.rb b/app/controllers/concerns/challengable_concern.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b29d90b3cc32d62c0d66e49d139ff44d6aee4841
--- /dev/null
+++ b/app/controllers/concerns/challengable_concern.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+# This concern is inspired by "sudo mode" on GitHub. It
+# is a way to re-authenticate a user before allowing them
+# to see or perform an action.
+#
+# Add `before_action :require_challenge!` to actions you
+# want to protect.
+#
+# The user will be shown a page to enter the challenge (which
+# is either the password, or just the username when no
+# password exists). Upon passing, there is a grace period
+# during which no challenge will be asked from the user.
+#
+# Accessing challenge-protected resources during the grace
+# period will refresh the grace period.
+module ChallengableConcern
+  extend ActiveSupport::Concern
+
+  CHALLENGE_TIMEOUT = 1.hour.freeze
+
+  def require_challenge!
+    return if skip_challenge?
+
+    if challenge_passed_recently?
+      session[:challenge_passed_at] = Time.now.utc
+      return
+    end
+
+    @challenge = Form::Challenge.new(return_to: request.url)
+
+    if params.key?(:form_challenge)
+      if challenge_passed?
+        session[:challenge_passed_at] = Time.now.utc
+        return
+      else
+        flash.now[:alert] = I18n.t('challenge.invalid_password')
+        render_challenge
+      end
+    else
+      render_challenge
+    end
+  end
+
+  def render_challenge
+    @body_classes = 'lighter'
+    render template: 'auth/challenges/new', layout: 'auth'
+  end
+
+  def challenge_passed?
+    current_user.valid_password?(challenge_params[:current_password])
+  end
+
+  def skip_challenge?
+    current_user.encrypted_password.blank?
+  end
+
+  def challenge_passed_recently?
+    session[:challenge_passed_at].present? && session[:challenge_passed_at] >= CHALLENGE_TIMEOUT.ago
+  end
+
+  def challenge_params
+    params.require(:form_challenge).permit(:current_password, :return_to)
+  end
+end
diff --git a/app/controllers/concerns/export_controller_concern.rb b/app/controllers/concerns/export_controller_concern.rb
index e20b71a30394a540332fd6f3d4a9c1ec710d215f..bfe990c827d1bf9cf085e05553fc1b375a372b84 100644
--- a/app/controllers/concerns/export_controller_concern.rb
+++ b/app/controllers/concerns/export_controller_concern.rb
@@ -5,7 +5,10 @@ module ExportControllerConcern
 
   included do
     before_action :authenticate_user!
+    before_action :require_not_suspended!
     before_action :load_export
+
+    skip_before_action :require_functional!
   end
 
   private
@@ -27,4 +30,8 @@ module ExportControllerConcern
   def export_filename
     "#{controller_name}.csv"
   end
+
+  def require_not_suspended!
+    forbidden if current_account.suspended?
+  end
 end
diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb
index 90a57197c00bde2a1512e524887f69bba1b66640..ce353f1deefcccaf4df775ef3c52a56ab3e0b77f 100644
--- a/app/controllers/concerns/signature_verification.rb
+++ b/app/controllers/concerns/signature_verification.rb
@@ -5,12 +5,35 @@
 module SignatureVerification
   extend ActiveSupport::Concern
 
+  include DomainControlHelper
+
+  def require_signature!
+    render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account
+  end
+
   def signed_request?
     request.headers['Signature'].present?
   end
 
   def signature_verification_failure_reason
-    return @signature_verification_failure_reason if defined?(@signature_verification_failure_reason)
+    @signature_verification_failure_reason
+  end
+
+  def signature_verification_failure_code
+    @signature_verification_failure_code || 401
+  end
+
+  def signature_key_id
+    raw_signature    = request.headers['Signature']
+    signature_params = {}
+
+    raw_signature.split(',').each do |part|
+      parsed_parts = part.match(/([a-z]+)="([^"]+)"/i)
+      next if parsed_parts.nil? || parsed_parts.size != 3
+      signature_params[parsed_parts[1]] = parsed_parts[2]
+    end
+
+    signature_params['keyId']
   end
 
   def signed_request_account
@@ -123,6 +146,13 @@ module SignatureVerification
   end
 
   def account_from_key_id(key_id)
+    domain = key_id.start_with?('acct:') ? key_id.split('@').last : key_id
+
+    if domain_not_allowed?(domain)
+      @signature_verification_failure_code = 403
+      return
+    end
+
     if key_id.start_with?('acct:')
       stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, '')) }
     elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
@@ -137,7 +167,7 @@ module SignatureVerification
       .with_fallback { nil }
       .with_threshold(1)
       .with_cool_off_time(5.minutes.seconds)
-      .with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) }
+      .with_error_handler { |error, handle| error.is_a?(HTTP::Error) || error.is_a?(OpenSSL::SSL::SSLError) ? handle.call(error) : raise(error) }
       .run
   end
 
diff --git a/app/controllers/concerns/status_controller_concern.rb b/app/controllers/concerns/status_controller_concern.rb
new file mode 100644
index 0000000000000000000000000000000000000000..62a7cf5086a2186854ba814662d1e72de0f5eaf8
--- /dev/null
+++ b/app/controllers/concerns/status_controller_concern.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+module StatusControllerConcern
+  extend ActiveSupport::Concern
+
+  ANCESTORS_LIMIT         = 40
+  DESCENDANTS_LIMIT       = 60
+  DESCENDANTS_DEPTH_LIMIT = 20
+
+  def create_descendant_thread(starting_depth, statuses)
+    depth = starting_depth + statuses.size
+
+    if depth < DESCENDANTS_DEPTH_LIMIT
+      {
+        statuses: statuses,
+        starting_depth: starting_depth,
+      }
+    else
+      next_status = statuses.pop
+
+      {
+        statuses: statuses,
+        starting_depth: starting_depth,
+        next_status: next_status,
+      }
+    end
+  end
+
+  def set_ancestors
+    @ancestors     = @status.reply? ? cache_collection(@status.ancestors(ANCESTORS_LIMIT, current_account), Status) : []
+    @next_ancestor = @ancestors.size < ANCESTORS_LIMIT ? nil : @ancestors.shift
+  end
+
+  def set_descendants
+    @max_descendant_thread_id   = params[:max_descendant_thread_id]&.to_i
+    @since_descendant_thread_id = params[:since_descendant_thread_id]&.to_i
+
+    descendants = cache_collection(
+      @status.descendants(
+        DESCENDANTS_LIMIT,
+        current_account,
+        @max_descendant_thread_id,
+        @since_descendant_thread_id,
+        DESCENDANTS_DEPTH_LIMIT
+      ),
+      Status
+    )
+
+    @descendant_threads = []
+
+    if descendants.present?
+      statuses       = [descendants.first]
+      starting_depth = 0
+
+      descendants.drop(1).each_with_index do |descendant, index|
+        if descendants[index].id == descendant.in_reply_to_id
+          statuses << descendant
+        else
+          @descendant_threads << create_descendant_thread(starting_depth, statuses)
+
+          # The thread is broken, assume it's a reply to the root status
+          starting_depth = 0
+
+          # ... unless we can find its ancestor in one of the already-processed threads
+          @descendant_threads.reverse_each do |descendant_thread|
+            statuses = descendant_thread[:statuses]
+
+            index = statuses.find_index do |thread_status|
+              thread_status.id == descendant.in_reply_to_id
+            end
+
+            if index.present?
+              starting_depth = descendant_thread[:starting_depth] + index + 1
+              break
+            end
+          end
+
+          statuses = [descendant]
+        end
+      end
+
+      @descendant_threads << create_descendant_thread(starting_depth, statuses)
+    end
+
+    @max_descendant_thread_id = @descendant_threads.pop[:statuses].first.id if descendants.size >= DESCENDANTS_LIMIT
+  end
+end
diff --git a/app/controllers/custom_css_controller.rb b/app/controllers/custom_css_controller.rb
index 6e80feaf837fac3c21d8ef8fd2c0b6f31d0c5b12..0a667a6a66009c99c61a6f6b311a9309ceae4add 100644
--- a/app/controllers/custom_css_controller.rb
+++ b/app/controllers/custom_css_controller.rb
@@ -2,10 +2,12 @@
 
 class CustomCssController < ApplicationController
   skip_before_action :store_current_location
+  skip_before_action :require_functional!
 
   before_action :set_cache_headers
 
   def show
+    expires_in 3.minutes, public: true
     render plain: Setting.custom_css || '', content_type: 'text/css'
   end
 end
diff --git a/app/controllers/directories_controller.rb b/app/controllers/directories_controller.rb
index 5949076740fac499b0f3842bca6c3021b6c8d69f..750c835ddab471e1536327e6486ad19b1ddcd7e8 100644
--- a/app/controllers/directories_controller.rb
+++ b/app/controllers/directories_controller.rb
@@ -3,12 +3,14 @@
 class DirectoriesController < ApplicationController
   layout 'public'
 
-  before_action :check_enabled
+  before_action :authenticate_user!, if: :whitelist_mode?
+  before_action :require_enabled!
   before_action :set_instance_presenter
   before_action :set_tag, only: :show
-  before_action :set_tags
   before_action :set_accounts
 
+  skip_before_action :require_functional!
+
   def index
     render :index
   end
@@ -19,21 +21,18 @@ class DirectoriesController < ApplicationController
 
   private
 
-  def check_enabled
+  def require_enabled!
     return not_found unless Setting.profile_directory
   end
 
   def set_tag
-    @tag = Tag.discoverable.find_by!(name: params[:id].downcase)
-  end
-
-  def set_tags
-    @tags = Tag.discoverable.limit(30).reject { |tag| tag.cached_sample_accounts.empty? }
+    @tag = Tag.discoverable.find_normalized!(params[:id])
   end
 
   def set_accounts
-    @accounts = Account.discoverable.by_recent_status.page(params[:page]).per(40).tap do |query|
+    @accounts = Account.local.discoverable.by_recent_status.page(params[:page]).per(20).tap do |query|
       query.merge!(Account.tagged_with(@tag.id)) if @tag
+      query.merge!(Account.not_excluded_by_account(current_account)) if current_account
     end
   end
 
diff --git a/app/controllers/emojis_controller.rb b/app/controllers/emojis_controller.rb
index 3feb081325fb1f21c640a3cdcf9b11655c080fb3..41f1e1c5ca07b307bbbd1ab0e07bb76726f7f374 100644
--- a/app/controllers/emojis_controller.rb
+++ b/app/controllers/emojis_controller.rb
@@ -7,9 +7,8 @@ class EmojisController < ApplicationController
   def show
     respond_to do |format|
       format.json do
-        render_cached_json(['activitypub', 'emoji', @emoji], content_type: 'application/activity+json') do
-          ActiveModelSerializers::SerializableResource.new(@emoji, serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter)
-        end
+        expires_in 3.minutes, public: true
+        render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
       end
     end
   end
diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb
index 415abe10c100756003126a7c602f9f94ac058f24..705ff412269c6cf30d4ee2477d54b2ed33ffe348 100644
--- a/app/controllers/follower_accounts_controller.rb
+++ b/app/controllers/follower_accounts_controller.rb
@@ -2,13 +2,18 @@
 
 class FollowerAccountsController < ApplicationController
   include AccountControllerConcern
+  include SignatureVerification
 
+  before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
   before_action :set_cache_headers
 
+  skip_around_action :set_locale, if: -> { request.format == :json }
+  skip_before_action :require_functional!
+
   def index
     respond_to do |format|
       format.html do
-        mark_cacheable! unless user_signed_in?
+        expires_in 0, public: true unless user_signed_in?
 
         next if @account.user_hides_network?
 
@@ -17,9 +22,9 @@ class FollowerAccountsController < ApplicationController
       end
 
       format.json do
-        raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network?
+        raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
 
-        expires_in 3.minutes, public: true if params[:page].blank?
+        expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
 
         render json: collection_presenter,
                serializer: ActivityPub::CollectionSerializer,
@@ -35,12 +40,16 @@ class FollowerAccountsController < ApplicationController
     @follows ||= Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
   end
 
+  def page_requested?
+    params[:page].present?
+  end
+
   def page_url(page)
     account_followers_url(@account, page: page) unless page.nil?
   end
 
   def collection_presenter
-    if params[:page].present?
+    if page_requested?
       ActivityPub::CollectionPresenter.new(
         id: account_followers_url(@account, page: params.fetch(:page, 1)),
         type: :ordered,
diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb
index 948725664196e4bebd19bf7641945bf86a44c1f6..968de980d1393724b9cf315c481305b1586d6971 100644
--- a/app/controllers/following_accounts_controller.rb
+++ b/app/controllers/following_accounts_controller.rb
@@ -2,13 +2,18 @@
 
 class FollowingAccountsController < ApplicationController
   include AccountControllerConcern
+  include SignatureVerification
 
+  before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
   before_action :set_cache_headers
 
+  skip_around_action :set_locale, if: -> { request.format == :json }
+  skip_before_action :require_functional!
+
   def index
     respond_to do |format|
       format.html do
-        mark_cacheable! unless user_signed_in?
+        expires_in 0, public: true unless user_signed_in?
 
         next if @account.user_hides_network?
 
@@ -17,9 +22,9 @@ class FollowingAccountsController < ApplicationController
       end
 
       format.json do
-        raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network?
+        raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
 
-        expires_in 3.minutes, public: true if params[:page].blank?
+        expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
 
         render json: collection_presenter,
                serializer: ActivityPub::CollectionSerializer,
@@ -35,12 +40,16 @@ class FollowingAccountsController < ApplicationController
     @follows ||= Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
   end
 
+  def page_requested?
+    params[:page].present?
+  end
+
   def page_url(page)
     account_following_index_url(@account, page: page) unless page.nil?
   end
 
   def collection_presenter
-    if params[:page].present?
+    if page_requested?
       ActivityPub::CollectionPresenter.new(
         id: account_following_index_url(@account, page: params.fetch(:page, 1)),
         type: :ordered,
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 85622a7b59ecb52772d2875efed930faa20a9680..7c8a18d17cc48a452cccb5ba804887e3f390d06a 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -3,7 +3,6 @@
 class HomeController < ApplicationController
   before_action :authenticate_user!
   before_action :set_referrer_policy_header
-  before_action :set_initial_state_json
 
   def index
     @body_classes = 'app-body'
@@ -21,7 +20,7 @@ class HomeController < ApplicationController
       when 'statuses'
         status = Status.find_by(id: matches[2])
 
-        if status && (status.public_visibility? || status.unlisted_visibility?)
+        if status&.distributable?
           redirect_to(ActivityPub::TagManager.instance.url_for(status))
           return
         end
@@ -39,26 +38,11 @@ class HomeController < ApplicationController
     redirect_to(matches ? tag_path(CGI.unescape(matches[:tag])) : default_redirect_path)
   end
 
-  def set_initial_state_json
-    serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
-    @initial_state_json   = serializable_resource.to_json
-  end
-
-  def initial_state_params
-    {
-      settings: Web::Setting.find_by(user: current_user)&.data || {},
-      push_subscription: current_account.user.web_push_subscription(current_session),
-      current_account: current_account,
-      token: current_session.token,
-      admin: Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')),
-    }
-  end
-
   def default_redirect_path
-    if request.path.start_with?('/web')
+    if request.path.start_with?('/web') || whitelist_mode?
       new_user_session_path
     elsif single_user_mode?
-      short_account_path(Account.local.without_suspended.first)
+      short_account_path(Account.local.without_suspended.where('id > 0').first)
     else
       about_path
     end
diff --git a/app/controllers/instance_actors_controller.rb b/app/controllers/instance_actors_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6f02d6a358f00cea153da012ad0f7a11de9fafea
--- /dev/null
+++ b/app/controllers/instance_actors_controller.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class InstanceActorsController < ApplicationController
+  include AccountControllerConcern
+
+  skip_around_action :set_locale
+
+  def show
+    expires_in 10.minutes, public: true
+    render json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
+  end
+
+  private
+
+  def set_account
+    @account = Account.find(-99)
+  end
+
+  def restrict_fields_to
+    %i(id type preferred_username inbox public_key endpoints url manually_approves_followers)
+  end
+end
diff --git a/app/controllers/intents_controller.rb b/app/controllers/intents_controller.rb
index 9f41cf48ac3183fe49414c6aa96418608e5e86e0..ca89fc7fe65f0fc09b933efcf95aa2b9c8527800 100644
--- a/app/controllers/intents_controller.rb
+++ b/app/controllers/intents_controller.rb
@@ -2,6 +2,7 @@
 
 class IntentsController < ApplicationController
   before_action :check_uri
+
   rescue_from Addressable::URI::InvalidURIError, with: :handle_invalid_uri
 
   def show
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index de5280305e90cd00877ea029b03a8047653cfe09..8d92147e276b7a5674949e895680952dbbfb739c 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -43,7 +43,7 @@ class InvitesController < ApplicationController
   end
 
   def resource_params
-    params.require(:invite).permit(:max_uses, :expires_in, :autofollow)
+    params.require(:invite).permit(:max_uses, :expires_in, :autofollow, :comment)
   end
 
   def set_body_classes
diff --git a/app/controllers/manifests_controller.rb b/app/controllers/manifests_controller.rb
index 332d845d8252c831a08bdde32fc100829d6d93cd..960510f60161e816ebdf73c3f140a5353b9858e9 100644
--- a/app/controllers/manifests_controller.rb
+++ b/app/controllers/manifests_controller.rb
@@ -2,8 +2,10 @@
 
 class ManifestsController < ApplicationController
   skip_before_action :store_current_location
+  skip_before_action :require_functional!
 
   def show
-    render json: InstancePresenter.new, serializer: ManifestSerializer
+    expires_in 3.minutes, public: true
+    render json: InstancePresenter.new, serializer: ManifestSerializer, root: 'instance'
   end
 end
diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb
index d44b52d2624d7994569f4850d7372103f6c494d2..05cf09c28af82c8d70a953728324b5af4edd1282 100644
--- a/app/controllers/media_controller.rb
+++ b/app/controllers/media_controller.rb
@@ -4,7 +4,9 @@ class MediaController < ApplicationController
   include Authorization
 
   skip_before_action :store_current_location
+  skip_before_action :require_functional!
 
+  before_action :authenticate_user!, if: :whitelist_mode?
   before_action :set_media_attachment
   before_action :verify_permitted_status!
   before_action :check_playable, only: :player
@@ -31,7 +33,6 @@ class MediaController < ApplicationController
   def verify_permitted_status!
     authorize @media_attachment.status, :show?
   rescue Mastodon::NotPermittedError
-    # Reraise in order to get a 404 instead of a 403 error code
     raise ActiveRecord::RecordNotFound
   end
 
diff --git a/app/controllers/media_proxy_controller.rb b/app/controllers/media_proxy_controller.rb
index 8fc18dd0602a9067d16e25efa454578170038148..014b89de102c68649a94717b1d837caeb9d3e4b6 100644
--- a/app/controllers/media_proxy_controller.rb
+++ b/app/controllers/media_proxy_controller.rb
@@ -4,6 +4,13 @@ class MediaProxyController < ApplicationController
   include RoutingHelper
 
   skip_before_action :store_current_location
+  skip_before_action :require_functional!
+
+  before_action :authenticate_user!, if: :whitelist_mode?
+
+  rescue_from ActiveRecord::RecordInvalid, with: :not_found
+  rescue_from Mastodon::UnexpectedResponseError, with: :not_found
+  rescue_from HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, with: :internal_server_error
 
   def show
     RedisLock.acquire(lock_options) do |lock|
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
index f3d2353669464c0e91af032fe46168fdfc31a14f..fb8389034b9b20cffeab89b0579000db992bdf7e 100644
--- a/app/controllers/oauth/authorized_applications_controller.rb
+++ b/app/controllers/oauth/authorized_applications_controller.rb
@@ -7,6 +7,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
   before_action :authenticate_resource_owner!
   before_action :set_body_classes
 
+  skip_before_action :require_functional!
+
   include Localized
 
   def destroy
diff --git a/app/controllers/public_timelines_controller.rb b/app/controllers/public_timelines_controller.rb
index 53d4472d88da355af4d67d23501117dbfb0fee35..1332ba16c2bac4b75664c03d3d641907689a3d50 100644
--- a/app/controllers/public_timelines_controller.rb
+++ b/app/controllers/public_timelines_controller.rb
@@ -3,25 +3,17 @@
 class PublicTimelinesController < ApplicationController
   layout 'public'
 
-  before_action :check_enabled
+  before_action :authenticate_user!, if: :whitelist_mode?
+  before_action :require_enabled!
   before_action :set_body_classes
   before_action :set_instance_presenter
 
-  def show
-    respond_to do |format|
-      format.html do
-        @initial_state_json = ActiveModelSerializers::SerializableResource.new(
-          InitialStatePresenter.new(settings: { known_fediverse: Setting.show_known_fediverse_at_about_page }, token: current_session&.token),
-          serializer: InitialStateSerializer
-        ).to_json
-      end
-    end
-  end
+  def show; end
 
   private
 
-  def check_enabled
-    raise ActiveRecord::RecordNotFound unless Setting.timeline_preview
+  def require_enabled!
+    not_found unless Setting.timeline_preview
   end
 
   def set_body_classes
diff --git a/app/controllers/remote_follow_controller.rb b/app/controllers/remote_follow_controller.rb
index 8ba331cd16ca51e777c218b5f52b670c2aa2c866..db1604644f124cb5040f00f83399be9c1b1c0754 100644
--- a/app/controllers/remote_follow_controller.rb
+++ b/app/controllers/remote_follow_controller.rb
@@ -1,12 +1,14 @@
 # frozen_string_literal: true
 
 class RemoteFollowController < ApplicationController
+  include AccountOwnedConcern
+
   layout 'modal'
 
-  before_action :set_account
-  before_action :gone, if: :suspended_account?
   before_action :set_body_classes
 
+  skip_before_action :require_functional!
+
   def new
     @remote_follow = RemoteFollow.new(session_params)
   end
@@ -29,15 +31,7 @@ class RemoteFollowController < ApplicationController
   end
 
   def session_params
-    { acct: session[:remote_follow] }
-  end
-
-  def set_account
-    @account = Account.find_local!(params[:account_username])
-  end
-
-  def suspended_account?
-    @account.suspended?
+    { acct: session[:remote_follow] || current_account&.username }
   end
 
   def set_body_classes
diff --git a/app/controllers/remote_interaction_controller.rb b/app/controllers/remote_interaction_controller.rb
index cc6993c52b22236dd4b95a4aa698c5e5527e8eed..4073e7ac3faa94614526690233475c8fbe1a9506 100644
--- a/app/controllers/remote_interaction_controller.rb
+++ b/app/controllers/remote_interaction_controller.rb
@@ -5,10 +5,13 @@ class RemoteInteractionController < ApplicationController
 
   layout 'modal'
 
+  before_action :authenticate_user!, if: :whitelist_mode?
   before_action :set_interaction_type
   before_action :set_status
   before_action :set_body_classes
 
+  skip_before_action :require_functional!
+
   def new
     @remote_follow = RemoteFollow.new(session_params)
   end
@@ -31,14 +34,13 @@ class RemoteInteractionController < ApplicationController
   end
 
   def session_params
-    { acct: session[:remote_follow] }
+    { acct: session[:remote_follow] || current_account&.username }
   end
 
   def set_status
     @status = Status.find(params[:id])
     authorize @status, :show?
   rescue Mastodon::NotPermittedError
-    # Reraise in order to get a 404
     raise ActiveRecord::RecordNotFound
   end
 
diff --git a/app/controllers/remote_unfollows_controller.rb b/app/controllers/remote_unfollows_controller.rb
deleted file mode 100644
index af5943363ae57ff7701519e30518a08b6f38e4ca..0000000000000000000000000000000000000000
--- a/app/controllers/remote_unfollows_controller.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-class RemoteUnfollowsController < ApplicationController
-  layout 'modal'
-
-  before_action :authenticate_user!
-  before_action :set_body_classes
-
-  def create
-    @account = unfollow_attempt.try(:target_account)
-
-    if @account.nil?
-      render :error
-    else
-      render :success
-    end
-  rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
-    render :error
-  end
-
-  private
-
-  def unfollow_attempt
-    username, domain = acct_without_prefix.split('@')
-    UnfollowService.new.call(current_account, Account.find_remote!(username, domain))
-  end
-
-  def acct_without_prefix
-    acct_params.gsub(/\Aacct:/, '')
-  end
-
-  def acct_params
-    params.fetch(:acct, '')
-  end
-
-  def set_body_classes
-    @body_classes = 'modal-layout'
-  end
-end
diff --git a/app/controllers/settings/aliases_controller.rb b/app/controllers/settings/aliases_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b7c9a409d1f8d7fb18adadbfcce531118d9c8a31
--- /dev/null
+++ b/app/controllers/settings/aliases_controller.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class Settings::AliasesController < Settings::BaseController
+  layout 'admin'
+
+  before_action :authenticate_user!
+  before_action :set_aliases, except: :destroy
+  before_action :set_alias, only: :destroy
+
+  def index
+    @alias = current_account.aliases.build
+  end
+
+  def create
+    @alias = current_account.aliases.build(resource_params)
+
+    if @alias.save
+      ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
+      redirect_to settings_aliases_path, notice: I18n.t('aliases.created_msg')
+    else
+      render :index
+    end
+  end
+
+  def destroy
+    @alias.destroy!
+    redirect_to settings_aliases_path, notice: I18n.t('aliases.deleted_msg')
+  end
+
+  private
+
+  def resource_params
+    params.require(:account_alias).permit(:acct)
+  end
+
+  def set_alias
+    @alias = current_account.aliases.find(params[:id])
+  end
+
+  def set_aliases
+    @aliases = current_account.aliases.order(id: :desc).reject(&:new_record?)
+  end
+end
diff --git a/app/controllers/settings/deletes_controller.rb b/app/controllers/settings/deletes_controller.rb
index dd19aadf636b6fd35541efdc4552ccf0e6b66bd9..15a59c999dffe219c06b7e2099ee1fe4fb2875db 100644
--- a/app/controllers/settings/deletes_controller.rb
+++ b/app/controllers/settings/deletes_controller.rb
@@ -5,18 +5,20 @@ class Settings::DeletesController < Settings::BaseController
 
   before_action :check_enabled_deletion
   before_action :authenticate_user!
+  before_action :require_not_suspended!
+
+  skip_before_action :require_functional!
 
   def show
     @confirmation = Form::DeleteConfirmation.new
   end
 
   def destroy
-    if current_user.valid_password?(delete_params[:password])
-      Admin::SuspensionWorker.perform_async(current_user.account_id, true)
-      sign_out
+    if challenge_passed?
+      destroy_account!
       redirect_to new_user_session_path, notice: I18n.t('deletes.success_msg')
     else
-      redirect_to settings_delete_path, alert: I18n.t('deletes.bad_password_msg')
+      redirect_to settings_delete_path, alert: I18n.t('deletes.challenge_not_passed')
     end
   end
 
@@ -26,7 +28,25 @@ class Settings::DeletesController < Settings::BaseController
     redirect_to root_path unless Setting.open_deletion
   end
 
-  def delete_params
-    params.require(:form_delete_confirmation).permit(:password)
+  def resource_params
+    params.require(:form_delete_confirmation).permit(:password, :username)
+  end
+
+  def require_not_suspended!
+    forbidden if current_account.suspended?
+  end
+
+  def challenge_passed?
+    if current_user.encrypted_password.blank?
+      current_account.username == resource_params[:username]
+    else
+      current_user.valid_password?(resource_params[:password])
+    end
+  end
+
+  def destroy_account!
+    current_account.suspend!
+    Admin::SuspensionWorker.perform_async(current_user.account_id, true)
+    sign_out
   end
 end
diff --git a/app/controllers/settings/exports_controller.rb b/app/controllers/settings/exports_controller.rb
index 3012fbf775600ad3a9dfb6149fab2e2eaf11ff04..0e93d07a9b2d509aa1738c855f4e2df49764af53 100644
--- a/app/controllers/settings/exports_controller.rb
+++ b/app/controllers/settings/exports_controller.rb
@@ -6,6 +6,9 @@ class Settings::ExportsController < Settings::BaseController
   layout 'admin'
 
   before_action :authenticate_user!
+  before_action :require_not_suspended!
+
+  skip_before_action :require_functional!
 
   def show
     @export  = Export.new(current_account)
@@ -34,4 +37,8 @@ class Settings::ExportsController < Settings::BaseController
   def lock_options
     { redis: Redis.current, key: "backup:#{current_user.id}" }
   end
+
+  def require_not_suspended!
+    forbidden if current_account.suspended?
+  end
 end
diff --git a/app/controllers/settings/migration/redirects_controller.rb b/app/controllers/settings/migration/redirects_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6e5b72ffba24806b9c6780bea095002f8a8c0584
--- /dev/null
+++ b/app/controllers/settings/migration/redirects_controller.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+class Settings::Migration::RedirectsController < Settings::BaseController
+  layout 'admin'
+
+  before_action :authenticate_user!
+  before_action :require_not_suspended!
+
+  skip_before_action :require_functional!
+
+  def new
+    @redirect = Form::Redirect.new
+  end
+
+  def create
+    @redirect = Form::Redirect.new(resource_params.merge(account: current_account))
+
+    if @redirect.valid_with_challenge?(current_user)
+      current_account.update!(moved_to_account: @redirect.target_account)
+      ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
+      redirect_to settings_migration_path, notice: I18n.t('migrations.moved_msg', acct: current_account.moved_to_account.acct)
+    else
+      render :new
+    end
+  end
+
+  def destroy
+    if current_account.moved_to_account_id.present?
+      current_account.update!(moved_to_account: nil)
+      ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
+    end
+
+    redirect_to settings_migration_path, notice: I18n.t('migrations.cancelled_msg')
+  end
+
+  private
+
+  def resource_params
+    params.require(:form_redirect).permit(:acct, :current_password, :current_username)
+  end
+
+  def require_not_suspended!
+    forbidden if current_account.suspended?
+  end
+end
diff --git a/app/controllers/settings/migrations_controller.rb b/app/controllers/settings/migrations_controller.rb
index 59eb48779c142bd274d138be83936661b74042e7..68304bb513f3a62afd7e13a4dfb862b11d6770a0 100644
--- a/app/controllers/settings/migrations_controller.rb
+++ b/app/controllers/settings/migrations_controller.rb
@@ -4,31 +4,48 @@ class Settings::MigrationsController < Settings::BaseController
   layout 'admin'
 
   before_action :authenticate_user!
+  before_action :require_not_suspended!
+  before_action :set_migrations
+  before_action :set_cooldown
+
+  skip_before_action :require_functional!
 
   def show
-    @migration = Form::Migration.new(account: current_account.moved_to_account)
+    @migration = current_account.migrations.build
   end
 
-  def update
-    @migration = Form::Migration.new(resource_params)
+  def create
+    @migration = current_account.migrations.build(resource_params)
 
-    if @migration.valid? && migration_account_changed?
-      current_account.update!(moved_to_account: @migration.account)
-      ActivityPub::UpdateDistributionWorker.perform_async(current_account.id)
-      redirect_to settings_migration_path, notice: I18n.t('migrations.updated_msg')
+    if @migration.save_with_challenge(current_user)
+      MoveService.new.call(@migration)
+      redirect_to settings_migration_path, notice: I18n.t('migrations.moved_msg', acct: current_account.moved_to_account.acct)
     else
       render :show
     end
   end
 
+  helper_method :on_cooldown?
+
   private
 
   def resource_params
-    params.require(:migration).permit(:acct)
+    params.require(:account_migration).permit(:acct, :current_password, :current_username)
+  end
+
+  def set_migrations
+    @migrations = current_account.migrations.includes(:target_account).order(id: :desc).reject(&:new_record?)
+  end
+
+  def set_cooldown
+    @cooldown = current_account.migrations.within_cooldown.first
+  end
+
+  def on_cooldown?
+    @cooldown.present?
   end
 
-  def migration_account_changed?
-    current_account.moved_to_account_id != @migration.account&.id &&
-      current_account.id != @migration.account&.id
+  def require_not_suspended!
+    forbidden if current_account.suspended?
   end
 end
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb
index be655cc287ec92ae747411bc7ac785e8fe413266..12b11c542c52f73be0c8a3c9bbfd60cf62da0964 100644
--- a/app/controllers/settings/preferences_controller.rb
+++ b/app/controllers/settings/preferences_controller.rb
@@ -55,7 +55,10 @@ class Settings::PreferencesController < Settings::BaseController
       :setting_aggregate_reblogs,
       :setting_show_application,
       :setting_advanced_layout,
-      notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account),
+      :setting_use_blurhash,
+      :setting_use_pending_items,
+      :setting_trends,
+      notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag),
       interactions: %i(must_be_follower must_be_following must_be_following_dm)
     )
   end
diff --git a/app/controllers/settings/sessions_controller.rb b/app/controllers/settings/sessions_controller.rb
index 84ebb21f2cce31ff3014c12df957fc540680a3ca..df5ace80368ccc1f0c210c1b7fb1ad388cf5dfed 100644
--- a/app/controllers/settings/sessions_controller.rb
+++ b/app/controllers/settings/sessions_controller.rb
@@ -4,6 +4,8 @@ class Settings::SessionsController < Settings::BaseController
   before_action :authenticate_user!
   before_action :set_session, only: :destroy
 
+  skip_before_action :require_functional!
+
   def destroy
     @session.destroy!
     flash[:notice] = I18n.t('sessions.revoke_success')
diff --git a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
index 02652a36c988bd67e6aecbeb4a0ac6971d0a71ce..ef4df33390aff8403b0b63f8bc2881ed28a0063a 100644
--- a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
+++ b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
@@ -3,23 +3,30 @@
 module Settings
   module TwoFactorAuthentication
     class ConfirmationsController < BaseController
+      include ChallengableConcern
+
       layout 'admin'
 
       before_action :authenticate_user!
+      before_action :require_challenge!
       before_action :ensure_otp_secret
 
+      skip_before_action :require_functional!
+
       def new
         prepare_two_factor_form
       end
 
       def create
-        if current_user.validate_and_consume_otp!(confirmation_params[:code])
+        if current_user.validate_and_consume_otp!(confirmation_params[:otp_attempt])
           flash.now[:notice] = I18n.t('two_factor_authentication.enabled_success')
 
           current_user.otp_required_for_login = true
           @recovery_codes = current_user.generate_otp_backup_codes!
           current_user.save!
 
+          UserMailer.two_factor_enabled(current_user).deliver_later!
+
           render 'settings/two_factor_authentication/recovery_codes/index'
         else
           flash.now[:alert] = I18n.t('two_factor_authentication.wrong_code')
@@ -31,7 +38,7 @@ module Settings
       private
 
       def confirmation_params
-        params.require(:form_two_factor_confirmation).permit(:code)
+        params.require(:form_two_factor_confirmation).permit(:otp_attempt)
       end
 
       def prepare_two_factor_form
diff --git a/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb b/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
index 874bf532ba567f2cfeac6f109003c51fc89a0e13..0c4f5bff762c591c8c4d14f56de5671edbffc0d8 100644
--- a/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
+++ b/app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
@@ -3,14 +3,22 @@
 module Settings
   module TwoFactorAuthentication
     class RecoveryCodesController < BaseController
+      include ChallengableConcern
+
       layout 'admin'
 
       before_action :authenticate_user!
+      before_action :require_challenge!, on: :create
+
+      skip_before_action :require_functional!
 
       def create
         @recovery_codes = current_user.generate_otp_backup_codes!
         current_user.save!
+
+        UserMailer.two_factor_recovery_codes_changed(current_user).deliver_later!
         flash.now[:notice] = I18n.t('two_factor_authentication.recovery_codes_regenerated')
+
         render :index
       end
     end
diff --git a/app/controllers/settings/two_factor_authentications_controller.rb b/app/controllers/settings/two_factor_authentications_controller.rb
index e12c4307468653c19c5a0c5e8c7cd5dc6d2792f5..9118a79332c4ef6b50d7c9345b9dc669e5e8bf41 100644
--- a/app/controllers/settings/two_factor_authentications_controller.rb
+++ b/app/controllers/settings/two_factor_authentications_controller.rb
@@ -2,10 +2,15 @@
 
 module Settings
   class TwoFactorAuthenticationsController < BaseController
+    include ChallengableConcern
+
     layout 'admin'
 
     before_action :authenticate_user!
     before_action :verify_otp_required, only: [:create]
+    before_action :require_challenge!, only: [:create]
+
+    skip_before_action :require_functional!
 
     def show
       @confirmation = Form::TwoFactorConfirmation.new
@@ -21,6 +26,7 @@ module Settings
       if acceptable_code?
         current_user.otp_required_for_login = false
         current_user.save!
+        UserMailer.two_factor_disabled(current_user).deliver_later!
         redirect_to settings_two_factor_authentication_path
       else
         flash.now[:alert] = I18n.t('two_factor_authentication.wrong_code')
@@ -32,7 +38,7 @@ module Settings
     private
 
     def confirmation_params
-      params.require(:form_two_factor_confirmation).permit(:code)
+      params.require(:form_two_factor_confirmation).permit(:otp_attempt)
     end
 
     def verify_otp_required
@@ -40,8 +46,8 @@ module Settings
     end
 
     def acceptable_code?
-      current_user.validate_and_consume_otp!(confirmation_params[:code]) ||
-        current_user.invalidate_otp_backup_code!(confirmation_params[:code])
+      current_user.validate_and_consume_otp!(confirmation_params[:otp_attempt]) ||
+        current_user.invalidate_otp_backup_code!(confirmation_params[:otp_attempt])
     end
   end
 end
diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb
index af605b98f701fce34dfbc5f15815d48134f5f888..6546b8497808c46e4ff95dbcf3a87ebb50d4f146 100644
--- a/app/controllers/shares_controller.rb
+++ b/app/controllers/shares_controller.rb
@@ -6,26 +6,10 @@ class SharesController < ApplicationController
   before_action :authenticate_user!
   before_action :set_body_classes
 
-  def show
-    serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
-    @initial_state_json   = serializable_resource.to_json
-  end
+  def show; end
 
   private
 
-  def initial_state_params
-    text = [params[:title], params[:text], params[:url]].compact.join(' ')
-
-    {
-      settings: Web::Setting.find_by(user: current_user)&.data || {},
-      push_subscription: current_account.user.web_push_subscription(current_session),
-      current_account: current_account,
-      token: current_session.token,
-      admin: Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')),
-      text: text,
-    }
-  end
-
   def set_body_classes
     @body_classes = 'modal-layout compose-standalone'
   end
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index ef26691b296c34895b76cb8ea63297428ae1fc20..57bbeca6434bed4e58b81dea78f8a7103448abce 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -1,24 +1,25 @@
 # frozen_string_literal: true
 
 class StatusesController < ApplicationController
+  include StatusControllerConcern
   include SignatureAuthentication
   include Authorization
-
-  ANCESTORS_LIMIT         = 40
-  DESCENDANTS_LIMIT       = 60
-  DESCENDANTS_DEPTH_LIMIT = 20
+  include AccountOwnedConcern
 
   layout 'public'
 
-  before_action :set_account
+  before_action :require_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? }
   before_action :set_status
   before_action :set_instance_presenter
   before_action :set_link_headers
-  before_action :check_account_suspension
-  before_action :redirect_to_original, only: [:show]
-  before_action :set_referrer_policy_header, only: [:show]
+  before_action :redirect_to_original, only: :show
+  before_action :set_referrer_policy_header, only: :show
   before_action :set_cache_headers
-  before_action :set_replies, only: [:replies]
+  before_action :set_body_classes
+  before_action :set_autoplay, only: :embed
+
+  skip_around_action :set_locale, if: -> { request.format == :json }
+  skip_before_action :require_functional!, only: [:show, :embed]
 
   content_security_policy only: :embed do |p|
     p.frame_ancestors(false)
@@ -28,27 +29,20 @@ class StatusesController < ApplicationController
     respond_to do |format|
       format.html do
         expires_in 10.seconds, public: true if current_account.nil?
-
-        @body_classes = 'with-modals'
-
         set_ancestors
         set_descendants
-
-        render 'stream_entries/show'
       end
 
       format.json do
-        render_cached_json(['activitypub', 'note', @status], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
-          ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter)
-        end
+        expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
+        render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
       end
     end
   end
 
   def activity
-    render_cached_json(['activitypub', 'activity', @status], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
-      ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter)
-    end
+    expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
+    render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
   end
 
   def embed
@@ -56,130 +50,24 @@ class StatusesController < ApplicationController
 
     expires_in 180, public: true
     response.headers['X-Frame-Options'] = 'ALLOWALL'
-    @autoplay = ActiveModel::Type::Boolean.new.cast(params[:autoplay])
-
-    render 'stream_entries/embed', layout: 'embedded'
-  end
 
-  def replies
-    render json: replies_collection_presenter,
-           serializer: ActivityPub::CollectionSerializer,
-           adapter: ActivityPub::Adapter,
-           content_type: 'application/activity+json',
-           skip_activities: true
+    render layout: 'embedded'
   end
 
   private
 
-  def replies_collection_presenter
-    page = ActivityPub::CollectionPresenter.new(
-      id: replies_account_status_url(@account, @status, page_params),
-      type: :unordered,
-      part_of: replies_account_status_url(@account, @status),
-      next: next_page,
-      items: @replies.map { |status| status.local ? status : status.id }
-    )
-    if page_requested?
-      page
-    else
-      ActivityPub::CollectionPresenter.new(
-        id: replies_account_status_url(@account, @status),
-        type: :unordered,
-        first: page
-      )
-    end
-  end
-
-  def create_descendant_thread(starting_depth, statuses)
-    depth = starting_depth + statuses.size
-    if depth < DESCENDANTS_DEPTH_LIMIT
-      { statuses: statuses, starting_depth: starting_depth }
-    else
-      next_status = statuses.pop
-      { statuses: statuses, starting_depth: starting_depth, next_status: next_status }
-    end
-  end
-
-  def set_account
-    @account = Account.find_local!(params[:account_username])
-  end
-
-  def set_ancestors
-    @ancestors     = @status.reply? ? cache_collection(@status.ancestors(ANCESTORS_LIMIT, current_account), Status) : []
-    @next_ancestor = @ancestors.size < ANCESTORS_LIMIT ? nil : @ancestors.shift
-  end
-
-  def set_descendants
-    @max_descendant_thread_id   = params[:max_descendant_thread_id]&.to_i
-    @since_descendant_thread_id = params[:since_descendant_thread_id]&.to_i
-
-    descendants = cache_collection(
-      @status.descendants(
-        DESCENDANTS_LIMIT,
-        current_account,
-        @max_descendant_thread_id,
-        @since_descendant_thread_id,
-        DESCENDANTS_DEPTH_LIMIT
-      ),
-      Status
-    )
-
-    @descendant_threads = []
-
-    if descendants.present?
-      statuses       = [descendants.first]
-      starting_depth = 0
-
-      descendants.drop(1).each_with_index do |descendant, index|
-        if descendants[index].id == descendant.in_reply_to_id
-          statuses << descendant
-        else
-          @descendant_threads << create_descendant_thread(starting_depth, statuses)
-
-          # The thread is broken, assume it's a reply to the root status
-          starting_depth = 0
-
-          # ... unless we can find its ancestor in one of the already-processed threads
-          @descendant_threads.reverse_each do |descendant_thread|
-            statuses = descendant_thread[:statuses]
-
-            index = statuses.find_index do |thread_status|
-              thread_status.id == descendant.in_reply_to_id
-            end
-
-            if index.present?
-              starting_depth = descendant_thread[:starting_depth] + index + 1
-              break
-            end
-          end
-
-          statuses = [descendant]
-        end
-      end
-
-      @descendant_threads << create_descendant_thread(starting_depth, statuses)
-    end
-
-    @max_descendant_thread_id = @descendant_threads.pop[:statuses].first.id if descendants.size >= DESCENDANTS_LIMIT
+  def set_body_classes
+    @body_classes = 'with-modals'
   end
 
   def set_link_headers
-    response.headers['Link'] = LinkHeader.new(
-      [
-        [account_stream_entry_url(@account, @status.stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]],
-        [ActivityPub::TagManager.instance.uri_for(@status), [%w(rel alternate), %w(type application/activity+json)]],
-      ]
-    )
+    response.headers['Link'] = LinkHeader.new([[ActivityPub::TagManager.instance.uri_for(@status), [%w(rel alternate), %w(type application/activity+json)]]])
   end
 
   def set_status
-    @status       = @account.statuses.find(params[:id])
-    @stream_entry = @status.stream_entry
-    @type         = @stream_entry.activity_type.downcase
-
+    @status = @account.statuses.find(params[:id])
     authorize @status, :show?
   rescue Mastodon::NotPermittedError
-    # Reraise in order to get a 404
     raise ActiveRecord::RecordNotFound
   end
 
@@ -187,39 +75,15 @@ class StatusesController < ApplicationController
     @instance_presenter = InstancePresenter.new
   end
 
-  def check_account_suspension
-    gone if @account.suspended?
-  end
-
   def redirect_to_original
-    redirect_to ::TagManager.instance.url_for(@status.reblog) if @status.reblog?
+    redirect_to ActivityPub::TagManager.instance.url_for(@status.reblog) if @status.reblog?
   end
 
   def set_referrer_policy_header
-    return if @status.public_visibility? || @status.unlisted_visibility?
-    response.headers['Referrer-Policy'] = 'origin'
-  end
-
-  def page_requested?
-    params[:page] == 'true'
-  end
-
-  def set_replies
-    @replies = page_params[:other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses
-    @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
-    @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
-  end
-
-  def next_page
-    last_reply = @replies.last
-    return if last_reply.nil?
-    same_account = last_reply.account_id == @account.id
-    return unless same_account || @replies.size == DESCENDANTS_LIMIT
-    same_account = false unless @replies.size == DESCENDANTS_LIMIT
-    replies_account_status_url(@account, @status, page: true, min_id: last_reply.id, other_accounts: !same_account)
+    response.headers['Referrer-Policy'] = 'origin' unless @status.distributable?
   end
 
-  def page_params
-    { page: true, other_accounts: params[:other_accounts], min_id: params[:min_id] }.compact
+  def set_autoplay
+    @autoplay = truthy_param?(:autoplay)
   end
 end
diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb
deleted file mode 100644
index 0f0be91342b0cb01c82b72e534dca53d2c31a71e..0000000000000000000000000000000000000000
--- a/app/controllers/stream_entries_controller.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-# frozen_string_literal: true
-
-class StreamEntriesController < ApplicationController
-  include Authorization
-  include SignatureVerification
-
-  layout 'public'
-
-  before_action :set_account
-  before_action :set_stream_entry
-  before_action :set_link_headers
-  before_action :check_account_suspension
-  before_action :set_cache_headers
-
-  def show
-    respond_to do |format|
-      format.html do
-        expires_in 5.minutes, public: true unless @stream_entry.hidden?
-
-        redirect_to short_account_status_url(params[:account_username], @stream_entry.activity)
-      end
-
-      format.atom do
-        expires_in 3.minutes, public: true unless @stream_entry.hidden? || @stream_entry.local_only?
-
-        render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.entry(@stream_entry, true))
-      end
-    end
-  end
-
-  def embed
-    redirect_to embed_short_account_status_url(@account, @stream_entry.activity), status: 301
-  end
-
-  private
-
-  def set_account
-    @account = Account.find_local!(params[:account_username])
-  end
-
-  def set_link_headers
-    response.headers['Link'] = LinkHeader.new(
-      [
-        [account_stream_entry_url(@account, @stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]],
-        [ActivityPub::TagManager.instance.uri_for(@stream_entry.activity), [%w(rel alternate), %w(type application/activity+json)]],
-      ]
-    )
-  end
-
-  def set_stream_entry
-    @stream_entry = @account.stream_entries.where(activity_type: 'Status').find(params[:id])
-    @type         = 'status'
-
-    raise ActiveRecord::RecordNotFound if @stream_entry.activity.nil?
-    authorize @stream_entry.activity, :show? if @stream_entry.hidden? || @stream_entry.local_only?
-  rescue Mastodon::NotPermittedError
-    # Reraise in order to get a 404
-    raise ActiveRecord::RecordNotFound
-  end
-
-  def check_account_suspension
-    gone if @account.suspended?
-  end
-end
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index 66b18490117833a176ded1f937776f226ec348b1..77d5661b8d596b94dc6a5d0012d966a0a106b368 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -1,25 +1,29 @@
 # frozen_string_literal: true
 
 class TagsController < ApplicationController
+  include SignatureVerification
+
   PAGE_SIZE = 20
 
   layout 'public'
 
+  before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
+  before_action :authenticate_user!, if: :whitelist_mode?
+  before_action :set_tag
   before_action :set_body_classes
   before_action :set_instance_presenter
 
-  def show
-    @tag = Tag.find_normalized!(params[:id])
+  skip_before_action :require_functional!
 
+  def show
     respond_to do |format|
       format.html do
-        @initial_state_json = ActiveModelSerializers::SerializableResource.new(
-          InitialStatePresenter.new(settings: {}, token: current_session&.token),
-          serializer: InitialStateSerializer
-        ).to_json
+        expires_in 0, public: true
       end
 
       format.rss do
+        expires_in 0, public: true
+
         @statuses = HashtagQueryService.new.call(@tag, params.slice(:any, :all, :none)).limit(PAGE_SIZE)
         @statuses = cache_collection(@statuses, Status)
 
@@ -27,19 +31,22 @@ class TagsController < ApplicationController
       end
 
       format.json do
+        expires_in 3.minutes, public: public_fetch_mode?
+
         @statuses = HashtagQueryService.new.call(@tag, params.slice(:any, :all, :none), current_account, params[:local]).paginate_by_max_id(PAGE_SIZE, params[:max_id])
         @statuses = cache_collection(@statuses, Status)
 
-        render json: collection_presenter,
-               serializer: ActivityPub::CollectionSerializer,
-               adapter: ActivityPub::Adapter,
-               content_type: 'application/activity+json'
+        render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
       end
     end
   end
 
   private
 
+  def set_tag
+    @tag = Tag.usable.find_normalized!(params[:id])
+  end
+
   def set_body_classes
     @body_classes = 'with-modals'
   end
diff --git a/app/controllers/well_known/host_meta_controller.rb b/app/controllers/well_known/host_meta_controller.rb
index 5fb70288a2a593862b775640bd62705ef538bd5e..2e9298c4ae641a9d5b0188b600ec1e92b03e06c4 100644
--- a/app/controllers/well_known/host_meta_controller.rb
+++ b/app/controllers/well_known/host_meta_controller.rb
@@ -13,7 +13,7 @@ module WellKnown
         format.xml { render content_type: 'application/xrd+xml' }
       end
 
-      expires_in(3.days, public: true)
+      expires_in 3.days, public: true
     end
   end
 end
diff --git a/app/controllers/well_known/keybase_proof_config_controller.rb b/app/controllers/well_known/keybase_proof_config_controller.rb
index eb41e586f84843d7557afcc50952d43285bd4e0d..e1d43ecbe9a1ad9c18c5944abbd8b81a2596bb05 100644
--- a/app/controllers/well_known/keybase_proof_config_controller.rb
+++ b/app/controllers/well_known/keybase_proof_config_controller.rb
@@ -3,7 +3,7 @@
 module WellKnown
   class KeybaseProofConfigController < ActionController::Base
     def show
-      render json: {}, serializer: ProofProvider::Keybase::ConfigSerializer
+      render json: {}, serializer: ProofProvider::Keybase::ConfigSerializer, root: 'keybase_config'
     end
   end
 end
diff --git a/app/controllers/well_known/nodeinfo_controller.rb b/app/controllers/well_known/nodeinfo_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..11a699ebc82715a68c98e495d09ee862d9b85f96
--- /dev/null
+++ b/app/controllers/well_known/nodeinfo_controller.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module WellKnown
+  class NodeInfoController < ActionController::Base
+    include CacheConcern
+
+    before_action { response.headers['Vary'] = 'Accept' }
+
+    def index
+      expires_in 3.days, public: true
+      render_with_cache json: {}, serializer: NodeInfo::DiscoverySerializer, adapter: NodeInfo::Adapter, expires_in: 3.days, root: 'nodeinfo'
+    end
+
+    def show
+      expires_in 30.minutes, public: true
+      render_with_cache json: {}, serializer: NodeInfo::Serializer, adapter: NodeInfo::Adapter, expires_in: 30.minutes, root: 'nodeinfo'
+    end
+  end
+end
diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb
index 28654b61d8cec6c4233a06b81e6aad7f8f7edc69..480e58f3f043fca439dbfaa762ffb09bd9ee96c9 100644
--- a/app/controllers/well_known/webfinger_controller.rb
+++ b/app/controllers/well_known/webfinger_controller.rb
@@ -5,34 +5,26 @@ module WellKnown
     include RoutingHelper
 
     before_action { response.headers['Vary'] = 'Accept' }
+    before_action :set_account
+    before_action :check_account_suspension
 
-    def show
-      @account = Account.find_local!(username_from_resource)
-
-      respond_to do |format|
-        format.any(:json, :html) do
-          render json: @account, serializer: WebfingerSerializer, content_type: 'application/jrd+json'
-        end
+    rescue_from ActiveRecord::RecordNotFound, ActionController::ParameterMissing, with: :not_found
 
-        format.xml do
-          render content_type: 'application/xrd+xml'
-        end
-      end
-
-      expires_in(3.days, public: true)
-    rescue ActiveRecord::RecordNotFound
-      head 404
+    def show
+      expires_in 3.days, public: true
+      render json: @account, serializer: WebfingerSerializer, content_type: 'application/jrd+json'
     end
 
     private
 
-    def username_from_resource
-      resource_user = resource_param
+    def set_account
+      @account = Account.find_local!(username_from_resource)
+    end
 
+    def username_from_resource
+      resource_user    = resource_param
       username, domain = resource_user.split('@')
-      if Rails.configuration.x.alternate_domains.include?(domain)
-        resource_user = "#{username}@#{Rails.configuration.x.local_domain}"
-      end
+      resource_user    = "#{username}@#{Rails.configuration.x.local_domain}" if Rails.configuration.x.alternate_domains.include?(domain)
 
       WebfingerResource.new(resource_user).username
     end
@@ -40,5 +32,17 @@ module WellKnown
     def resource_param
       params.require(:resource)
     end
+
+    def check_account_suspension
+      expires_in(3.minutes, public: true) && gone if @account.suspended?
+    end
+
+    def not_found
+      head 404
+    end
+
+    def gone
+      head 410
+    end
   end
 end
diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb
index e5fbb1500e997dc82084941380453e8a6b863113..1daa607745987d23dc5c93fd66fcee52ad6888c2 100644
--- a/app/helpers/admin/action_logs_helper.rb
+++ b/app/helpers/admin/action_logs_helper.rb
@@ -89,7 +89,7 @@ module Admin::ActionLogsHelper
     when 'DomainBlock', 'EmailDomainBlock'
       link_to record.domain, "https://#{record.domain}"
     when 'Status'
-      link_to record.account.acct, TagManager.instance.url_for(record)
+      link_to record.account.acct, ActivityPub::TagManager.instance.url_for(record)
     when 'AccountWarning'
       link_to record.target_account.acct, admin_account_path(record.target_account_id)
     end
diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb
index 0bda25974f0def86c916634bcd6c610fd3134f12..8af1683e76545b3ee27987d10d3d3678bd4722b0 100644
--- a/app/helpers/admin/filter_helper.rb
+++ b/app/helpers/admin/filter_helper.rb
@@ -5,15 +5,16 @@ module Admin::FilterHelper
   REPORT_FILTERS       = %i(resolved account_id target_account_id).freeze
   INVITE_FILTER        = %i(available expired).freeze
   CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
-  TAGS_FILTERS         = %i(hidden).freeze
+  TAGS_FILTERS         = %i(directory reviewed unreviewed pending_review popular active name).freeze
   INSTANCES_FILTERS    = %i(limited by_domain).freeze
   FOLLOWERS_FILTERS    = %i(relationship status by_domain activity order).freeze
 
   FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_FILTERS + INSTANCES_FILTERS + FOLLOWERS_FILTERS
 
   def filter_link_to(text, link_to_params, link_class_params = link_to_params)
-    new_url = filtered_url_for(link_to_params)
+    new_url   = filtered_url_for(link_to_params)
     new_class = filtered_url_for(link_class_params)
+
     link_to text, new_url, class: filter_link_class(new_class)
   end
 
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 9d113263dfda1f4bb5a1ac84384c620fe5adde24..defd97609ac5cd5599560b1ec44b3fece7bd6f4b 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -77,8 +77,12 @@ module ApplicationHelper
     content_tag(:i, nil, attributes.merge(class: class_names.join(' ')))
   end
 
-  def custom_emoji_tag(custom_emoji)
-    image_tag(custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:")
+  def custom_emoji_tag(custom_emoji, animate = true)
+    if animate
+      image_tag(custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:")
+    else
+      image_tag(custom_emoji.image.url(:static), class: 'emojione custom-emoji', alt: ":#{custom_emoji.shortcode}", 'data-original' => full_asset_url(custom_emoji.image.url), 'data-static' => full_asset_url(custom_emoji.image.url(:static)))
+    end
   end
 
   def opengraph(property, content)
@@ -122,4 +126,25 @@ module ApplicationHelper
     text = word_wrap(text, line_width: line_width - 2, break_sequence: break_sequence)
     text.split("\n").map { |line| '> ' + line }.join("\n")
   end
+
+  def render_initial_state
+    state_params = {
+      settings: {
+        known_fediverse: Setting.show_known_fediverse_at_about_page,
+      },
+
+      text: [params[:title], params[:text], params[:url]].compact.join(' '),
+    }
+
+    if user_signed_in?
+      state_params[:settings]          = state_params[:settings].merge(Web::Setting.find_by(user: current_user)&.data || {})
+      state_params[:push_subscription] = current_account.user.web_push_subscription(current_session)
+      state_params[:current_account]   = current_account
+      state_params[:token]             = current_session.token
+      state_params[:admin]             = Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, ''))
+    end
+
+    json = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(state_params), serializer: InitialStateSerializer).to_json
+    content_tag(:script, json_escape(json).html_safe, id: 'initial-state', type: 'application/json')
+  end
 end
diff --git a/app/helpers/domain_control_helper.rb b/app/helpers/domain_control_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..067b2c2cd68f723098832f13ce8d032e6451ed9b
--- /dev/null
+++ b/app/helpers/domain_control_helper.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module DomainControlHelper
+  def domain_not_allowed?(uri_or_domain)
+    return if uri_or_domain.blank?
+
+    domain = begin
+      if uri_or_domain.include?('://')
+        Addressable::URI.parse(uri_or_domain).domain
+      else
+        uri_or_domain
+      end
+    end
+
+    if whitelist_mode?
+      !DomainAllow.allowed?(domain)
+    else
+      DomainBlock.blocked?(domain)
+    end
+  end
+
+  def whitelist_mode?
+    Rails.configuration.x.whitelist_mode
+  end
+end
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb
index df60b7dd7ac32e8c6dc373644d0556ee90d7a336..b66e827fee2282928a576ac7cd06385d185c1863 100644
--- a/app/helpers/home_helper.rb
+++ b/app/helpers/home_helper.rb
@@ -21,7 +21,7 @@ module HomeHelper
                         end
                     end
                   else
-                    link_to(path || TagManager.instance.url_for(account), class: 'account__display-name') do
+                    link_to(path || ActivityPub::TagManager.instance.url_for(account), class: 'account__display-name') do
                       content_tag(:div, class: 'account__avatar-wrapper') do
                         content_tag(:div, '', class: 'account__avatar', style: "width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px; background-image: url(#{full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url)})")
                       end +
diff --git a/app/helpers/instance_helper.rb b/app/helpers/instance_helper.rb
index dd0b25f3ef9ec5096b47e6616e398e615ee68465..daacb535b6a570de65bf31ba16c4f0d242bd1775 100644
--- a/app/helpers/instance_helper.rb
+++ b/app/helpers/instance_helper.rb
@@ -8,4 +8,16 @@ module InstanceHelper
   def site_hostname
     @site_hostname ||= Addressable::URI.parse("//#{Rails.configuration.x.local_domain}").display_uri.host
   end
+
+  def description_for_sign_up
+    prefix = begin
+      if @invite.present?
+        I18n.t('auth.description.prefix_invited_by_user', name: @invite.user.account.username)
+      else
+        I18n.t('auth.description.prefix_sign_up')
+      end
+    end
+
+    safe_join([prefix, I18n.t('auth.description.suffix')], ' ')
+  end
 end
diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb
index 5b40112755a48aa8fb548621084b40967534483d..1c473efa3f54ca6c44c18a826ad96e5fd694e28e 100644
--- a/app/helpers/jsonld_helper.rb
+++ b/app/helpers/jsonld_helper.rb
@@ -16,13 +16,15 @@ module JsonLdHelper
   # The url attribute can be a string, an array of strings, or an array of objects.
   # The objects could include a mimeType. Not-included mimeType means it's text/html.
   def url_to_href(value, preferred_type = nil)
-    single_value = if value.is_a?(Array) && !value.first.is_a?(String)
-                     value.find { |link| preferred_type.nil? || ((link['mimeType'].presence || 'text/html') == preferred_type) }
-                   elsif value.is_a?(Array)
-                     value.first
-                   else
-                     value
-                   end
+    single_value = begin
+      if value.is_a?(Array) && !value.first.is_a?(String)
+        value.find { |link| preferred_type.nil? || ((link['mimeType'].presence || 'text/html') == preferred_type) }
+      elsif value.is_a?(Array)
+        value.first
+      else
+        value
+      end
+    end
 
     if single_value.nil? || single_value.is_a?(String)
       single_value
@@ -64,7 +66,9 @@ module JsonLdHelper
   def fetch_resource(uri, id, on_behalf_of = nil)
     unless id
       json = fetch_resource_without_id_validation(uri, on_behalf_of)
+
       return unless json
+
       uri = json['id']
     end
 
@@ -73,25 +77,20 @@ module JsonLdHelper
   end
 
   def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false)
+    on_behalf_of ||= Account.representative
+
     build_request(uri, on_behalf_of).perform do |response|
-      unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
-        raise Mastodon::UnexpectedResponseError, response
-      end
-      return body_to_json(response.body_with_limit) if response.code == 200
-    end
-    # If request failed, retry without doing it on behalf of a user
-    return if on_behalf_of.nil?
-    build_request(uri).perform do |response|
-      unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
-        raise Mastodon::UnexpectedResponseError, response
-      end
-      response.code == 200 ? body_to_json(response.body_with_limit) : nil
+      raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
+
+      body_to_json(response.body_with_limit) if response.code == 200
     end
   end
 
   def body_to_json(body, compare_id: nil)
     json = body.is_a?(String) ? Oj.load(body, mode: :strict) : body
+
     return if compare_id.present? && json['id'] != compare_id
+
     json
   rescue Oj::ParseError
     nil
@@ -105,35 +104,34 @@ module JsonLdHelper
     end
   end
 
-  private
-
   def response_successful?(response)
     (200...300).cover?(response.code)
   end
 
   def response_error_unsalvageable?(response)
-    (400...500).cover?(response.code) && response.code != 429
+    response.code == 501 || ((400...500).cover?(response.code) && ![401, 408, 429].include?(response.code))
   end
 
   def build_request(uri, on_behalf_of = nil)
-    request = Request.new(:get, uri)
-    request.on_behalf_of(on_behalf_of) if on_behalf_of
-    request.add_headers('Accept' => 'application/activity+json, application/ld+json')
-    request
+    Request.new(:get, uri).tap do |request|
+      request.on_behalf_of(on_behalf_of) if on_behalf_of
+      request.add_headers('Accept' => 'application/activity+json, application/ld+json')
+    end
   end
 
   def load_jsonld_context(url, _options = {}, &_block)
     json = Rails.cache.fetch("jsonld:context:#{url}", expires_in: 30.days, raw: true) do
       request = Request.new(:get, url)
       request.add_headers('Accept' => 'application/ld+json')
-
       request.perform do |res|
         raise JSON::LD::JsonLdError::LoadingDocumentFailed unless res.code == 200 && res.mime_type == 'application/ld+json'
+
         res.body_with_limit
       end
     end
 
-    doc = JSON::LD::API::RemoteDocument.new(url, json)
+    doc = JSON::LD::API::RemoteDocument.new(json, documentUrl: url)
+
     block_given? ? yield(doc) : doc
   end
 end
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index 92bc222ea9dfd96c023bcd196a7afa83da55d7e8..aa0a4d4674c9d8c39becbe6f1b5cfc54409aa1da 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -2,11 +2,11 @@
 
 module SettingsHelper
   HUMAN_LOCALES = {
-    en: 'English',
     ar: 'العربية',
     ast: 'Asturianu',
     bg: 'Български',
     bn: 'বাংলা',
+    br: 'Breton',
     ca: 'Català',
     co: 'Corsu',
     cs: 'Čeština',
@@ -14,8 +14,11 @@ module SettingsHelper
     da: 'Dansk',
     de: 'Deutsch',
     el: 'Ελληνικά',
+    en: 'English',
     eo: 'Esperanto',
+    'es-AR': 'Español (Argentina)',
     es: 'Español',
+    et: 'Eesti',
     eu: 'Euskara',
     fa: 'فارسی',
     fi: 'Suomi',
@@ -36,31 +39,34 @@ module SettingsHelper
     ko: '한국어',
     lt: 'Lietuvių',
     lv: 'Latviešu',
+    mk: 'Македонски',
     ml: 'മലയാളം',
     ms: 'Bahasa Melayu',
     nl: 'Nederlands',
+    nn: 'Nynorsk',
     no: 'Norsk',
     oc: 'Occitan',
     pl: 'Polski',
+    'pt-BR': 'Português (Brasil)',
+    'pt-PT': 'Português (Portugal)',
     pt: 'Português',
-    'pt-BR': 'Português do Brasil',
     ro: 'Română',
     ru: 'Русский',
     sk: 'Slovenčina',
     sl: 'Slovenščina',
     sq: 'Shqip',
-    sr: 'Српски',
     'sr-Latn': 'Srpski (latinica)',
+    sr: 'Српски',
     sv: 'Svenska',
     ta: 'தமிழ்',
     te: 'తెలుగు',
     th: 'ไทย',
     tr: 'Türkçe',
     uk: 'Українська',
-    zh: '中文',
     'zh-CN': '简体中文',
     'zh-HK': '繁體中文(香港)',
     'zh-TW': '繁體中文(臺灣)',
+    zh: '中文',
   }.freeze
 
   def human_locale(locale)
@@ -86,4 +92,12 @@ module SettingsHelper
       'desktop'
     end
   end
+
+  def compact_account_link_to(account)
+    return if account.nil?
+
+    link_to ActivityPub::TagManager.instance.url_for(account), class: 'name-tag', title: account.acct do
+      safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
+    end
+  end
 end
diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/statuses_helper.rb
similarity index 87%
rename from app/helpers/stream_entries_helper.rb
rename to app/helpers/statuses_helper.rb
index 02a860a74895399d509d9db05a8c093b5fce45bc..8380b3c4270d727419c8b51c572b1ed306cb9579 100644
--- a/app/helpers/stream_entries_helper.rb
+++ b/app/helpers/statuses_helper.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-module StreamEntriesHelper
+module StatusesHelper
   EMBEDDED_CONTROLLER = 'statuses'
   EMBEDDED_ACTION = 'embed'
 
@@ -34,6 +34,26 @@ module StreamEntriesHelper
     end
   end
 
+  def minimal_account_action_button(account)
+    if user_signed_in?
+      return if account.id == current_user.account_id
+
+      if current_account.following?(account) || current_account.requested?(account)
+        link_to account_unfollow_path(account), class: 'icon-button active', data: { method: :post }, title: t('accounts.unfollow') do
+          fa_icon('user-times fw')
+        end
+      elsif !(account.memorial? || account.moved?)
+        link_to account_follow_path(account), class: "icon-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post }, title: t('accounts.follow') do
+          fa_icon('user-plus fw')
+        end
+      end
+    elsif !(account.memorial? || account.moved?)
+      link_to account_remote_follow_path(account), class: 'icon-button modal-button', target: '_new', title: t('accounts.follow') do
+        fa_icon('user-plus fw')
+      end
+    end
+  end
+
   def svg_logo
     content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo'), 'viewBox' => '0 0 216.4144 232.00976')
   end
@@ -109,11 +129,13 @@ module StreamEntriesHelper
 
   def status_text_summary(status)
     return if status.spoiler_text.blank?
+
     I18n.t('statuses.content_warning', warning: status.spoiler_text)
   end
 
   def poll_summary(status)
     return unless status.preloadable_poll
+
     status.preloadable_poll.options.map { |o| "[ ] #{o}" }.join("\n")
   end
 
diff --git a/app/javascript/mastodon/actions/alerts.js b/app/javascript/mastodon/actions/alerts.js
index ef2500e7bdd84fb6a3cd353991a07ae1371f5bd7..cd36d8007bcceeae91a90c9a8abce257d6f3d858 100644
--- a/app/javascript/mastodon/actions/alerts.js
+++ b/app/javascript/mastodon/actions/alerts.js
@@ -3,6 +3,8 @@ import { defineMessages } from 'react-intl';
 const messages = defineMessages({
   unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' },
   unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' },
+  rateLimitedTitle: { id: 'alert.rate_limited.title', defaultMessage: 'Rate limited' },
+  rateLimitedMessage: { id: 'alert.rate_limited.message', defaultMessage: 'Please retry after {retry_time, time, medium}.' },
 });
 
 export const ALERT_SHOW    = 'ALERT_SHOW';
@@ -23,23 +25,29 @@ export function clearAlert() {
   };
 };
 
-export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage) {
+export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage, message_values = undefined) {
   return {
     type: ALERT_SHOW,
     title,
     message,
+    message_values,
   };
 };
 
 export function showAlertForError(error) {
   if (error.response) {
-    const { data, status, statusText } = error.response;
+    const { data, status, statusText, headers } = error.response;
 
     if (status === 404 || status === 410) {
       // Skip these errors as they are reflected in the UI
       return { type: ALERT_NOOP };
     }
 
+    if (status === 429 && headers['x-ratelimit-reset']) {
+      const reset_date = new Date(headers['x-ratelimit-reset']);
+      return showAlert(messages.rateLimitedTitle, messages.rateLimitedMessage, { 'retry_time': reset_date });
+    }
+
     let message = statusText;
     let title   = `${status}`;
 
diff --git a/app/javascript/mastodon/actions/app.js b/app/javascript/mastodon/actions/app.js
new file mode 100644
index 0000000000000000000000000000000000000000..414968f7de47cfef7cf1abe27742efc03ed92959
--- /dev/null
+++ b/app/javascript/mastodon/actions/app.js
@@ -0,0 +1,10 @@
+export const APP_FOCUS   = 'APP_FOCUS';
+export const APP_UNFOCUS = 'APP_UNFOCUS';
+
+export const focusApp = () => ({
+  type: APP_FOCUS,
+});
+
+export const unfocusApp = () => ({
+  type: APP_UNFOCUS,
+});
diff --git a/app/javascript/mastodon/actions/blocks.js b/app/javascript/mastodon/actions/blocks.js
index 7000f5a71cb66bfd38744b3ee98915300d407c2b..fd9881302a652fc3fb36c8a5dfaa7a831a23c15d 100644
--- a/app/javascript/mastodon/actions/blocks.js
+++ b/app/javascript/mastodon/actions/blocks.js
@@ -1,6 +1,7 @@
 import api, { getLinks } from '../api';
 import { fetchRelationships } from './accounts';
 import { importFetchedAccounts } from './importer';
+import { openModal } from './modal';
 
 export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST';
 export const BLOCKS_FETCH_SUCCESS = 'BLOCKS_FETCH_SUCCESS';
@@ -10,6 +11,8 @@ export const BLOCKS_EXPAND_REQUEST = 'BLOCKS_EXPAND_REQUEST';
 export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS';
 export const BLOCKS_EXPAND_FAIL    = 'BLOCKS_EXPAND_FAIL';
 
+export const BLOCKS_INIT_MODAL = 'BLOCKS_INIT_MODAL';
+
 export function fetchBlocks() {
   return (dispatch, getState) => {
     dispatch(fetchBlocksRequest());
@@ -83,3 +86,14 @@ export function expandBlocksFail(error) {
     error,
   };
 };
+
+export function initBlockModal(account) {
+  return dispatch => {
+    dispatch({
+      type: BLOCKS_INIT_MODAL,
+      account,
+    });
+
+    dispatch(openModal('BLOCK'));
+  };
+}
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index d27901c1bb4e321739684379530dac64bb31f94d..2dcb81035de9c483f89a5132bc0c78191e3f7588 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -11,7 +11,7 @@ import { showAlertForError } from './alerts';
 import { showAlert } from './alerts';
 import { defineMessages } from 'react-intl';
 
-let cancelFetchComposeSuggestionsAccounts;
+let cancelFetchComposeSuggestionsAccounts, cancelFetchComposeSuggestionsTags;
 
 export const COMPOSE_CHANGE          = 'COMPOSE_CHANGE';
 export const COMPOSE_SUBMIT_REQUEST  = 'COMPOSE_SUBMIT_REQUEST';
@@ -236,7 +236,7 @@ export function uploadCompose(files) {
             progress[i] = loaded;
             dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
           },
-        }).then(({ data }) => dispatch(uploadComposeSuccess(data)));
+        }).then(({ data }) => dispatch(uploadComposeSuccess(data, f)));
       }).catch(error => dispatch(uploadComposeFail(error)));
     };
   };
@@ -291,10 +291,11 @@ export function uploadComposeProgress(loaded, total) {
   };
 };
 
-export function uploadComposeSuccess(media) {
+export function uploadComposeSuccess(media, file) {
   return {
     type: COMPOSE_UPLOAD_SUCCESS,
     media: media,
+    file: file,
     skipLoading: true,
   };
 };
@@ -327,10 +328,12 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) =>
   if (cancelFetchComposeSuggestionsAccounts) {
     cancelFetchComposeSuggestionsAccounts();
   }
+
   api(getState).get('/api/v1/accounts/search', {
     cancelToken: new CancelToken(cancel => {
       cancelFetchComposeSuggestionsAccounts = cancel;
     }),
+
     params: {
       q: token.slice(1),
       resolve: false,
@@ -351,9 +354,33 @@ const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => {
   dispatch(readyComposeSuggestionsEmojis(token, results));
 };
 
-const fetchComposeSuggestionsTags = (dispatch, getState, token) => {
+const fetchComposeSuggestionsTags = throttle((dispatch, getState, token) => {
+  if (cancelFetchComposeSuggestionsTags) {
+    cancelFetchComposeSuggestionsTags();
+  }
+
   dispatch(updateSuggestionTags(token));
-};
+
+  api(getState).get('/api/v2/search', {
+    cancelToken: new CancelToken(cancel => {
+      cancelFetchComposeSuggestionsTags = cancel;
+    }),
+
+    params: {
+      type: 'hashtags',
+      q: token.slice(1),
+      resolve: false,
+      limit: 4,
+      exclude_unreviewed: true,
+    },
+  }).then(({ data }) => {
+    dispatch(readyComposeSuggestionsTags(token, data.hashtags));
+  }).catch(error => {
+    if (!isCancel(error)) {
+      dispatch(showAlertForError(error));
+    }
+  });
+}, 200, { leading: true, trailing: true });
 
 export function fetchComposeSuggestions(token) {
   return (dispatch, getState) => {
@@ -387,20 +414,26 @@ export function readyComposeSuggestionsAccounts(token, accounts) {
   };
 };
 
+export const readyComposeSuggestionsTags = (token, tags) => ({
+  type: COMPOSE_SUGGESTIONS_READY,
+  token,
+  tags,
+});
+
 export function selectComposeSuggestion(position, token, suggestion, path) {
   return (dispatch, getState) => {
     let completion, startPosition;
 
-    if (typeof suggestion === 'object' && suggestion.id) {
+    if (suggestion.type === 'emoji') {
       completion    = suggestion.native || suggestion.colons;
       startPosition = position - 1;
 
       dispatch(useEmoji(suggestion));
-    } else if (suggestion[0] === '#') {
-      completion    = suggestion;
+    } else if (suggestion.type === 'hashtag') {
+      completion    = `#${suggestion.name}`;
       startPosition = position - 1;
-    } else {
-      completion    = getState().getIn(['accounts', suggestion, 'acct']);
+    } else if (suggestion.type === 'account') {
+      completion    = getState().getIn(['accounts', suggestion.id, 'acct']);
       startPosition = position;
     }
 
diff --git a/app/javascript/mastodon/actions/conversations.js b/app/javascript/mastodon/actions/conversations.js
index c6e062ef73b2e5129aa9ebce8254c77f5d7b59a9..4ef654b1f902413a81a2ba0274e9252754e7836c 100644
--- a/app/javascript/mastodon/actions/conversations.js
+++ b/app/javascript/mastodon/actions/conversations.js
@@ -15,6 +15,10 @@ export const CONVERSATIONS_UPDATE        = 'CONVERSATIONS_UPDATE';
 
 export const CONVERSATIONS_READ = 'CONVERSATIONS_READ';
 
+export const CONVERSATIONS_DELETE_REQUEST = 'CONVERSATIONS_DELETE_REQUEST';
+export const CONVERSATIONS_DELETE_SUCCESS = 'CONVERSATIONS_DELETE_SUCCESS';
+export const CONVERSATIONS_DELETE_FAIL    = 'CONVERSATIONS_DELETE_FAIL';
+
 export const mountConversations = () => ({
   type: CONVERSATIONS_MOUNT,
 });
@@ -82,3 +86,27 @@ export const updateConversations = conversation => dispatch => {
     conversation,
   });
 };
+
+export const deleteConversation = conversationId => (dispatch, getState) => {
+  dispatch(deleteConversationRequest(conversationId));
+
+  api(getState).delete(`/api/v1/conversations/${conversationId}`)
+    .then(() => dispatch(deleteConversationSuccess(conversationId)))
+    .catch(error => dispatch(deleteConversationFail(conversationId, error)));
+};
+
+export const deleteConversationRequest = id => ({
+  type: CONVERSATIONS_DELETE_REQUEST,
+  id,
+});
+
+export const deleteConversationSuccess = id => ({
+  type: CONVERSATIONS_DELETE_SUCCESS,
+  id,
+});
+
+export const deleteConversationFail = (id, error) => ({
+  type: CONVERSATIONS_DELETE_FAIL,
+  id,
+  error,
+});
diff --git a/app/javascript/mastodon/actions/directory.js b/app/javascript/mastodon/actions/directory.js
new file mode 100644
index 0000000000000000000000000000000000000000..4b2b6dd56dce58aa588f71b75a449bd804415afe
--- /dev/null
+++ b/app/javascript/mastodon/actions/directory.js
@@ -0,0 +1,61 @@
+import api from '../api';
+import { importFetchedAccounts } from './importer';
+import { fetchRelationships } from './accounts';
+
+export const DIRECTORY_FETCH_REQUEST = 'DIRECTORY_FETCH_REQUEST';
+export const DIRECTORY_FETCH_SUCCESS = 'DIRECTORY_FETCH_SUCCESS';
+export const DIRECTORY_FETCH_FAIL    = 'DIRECTORY_FETCH_FAIL';
+
+export const DIRECTORY_EXPAND_REQUEST = 'DIRECTORY_EXPAND_REQUEST';
+export const DIRECTORY_EXPAND_SUCCESS = 'DIRECTORY_EXPAND_SUCCESS';
+export const DIRECTORY_EXPAND_FAIL    = 'DIRECTORY_EXPAND_FAIL';
+
+export const fetchDirectory = params => (dispatch, getState) => {
+  dispatch(fetchDirectoryRequest());
+
+  api(getState).get('/api/v1/directory', { params: { ...params, limit: 20 } }).then(({ data }) => {
+    dispatch(importFetchedAccounts(data));
+    dispatch(fetchDirectorySuccess(data));
+    dispatch(fetchRelationships(data.map(x => x.id)));
+  }).catch(error => dispatch(fetchDirectoryFail(error)));
+};
+
+export const fetchDirectoryRequest = () => ({
+  type: DIRECTORY_FETCH_REQUEST,
+});
+
+export const fetchDirectorySuccess = accounts => ({
+  type: DIRECTORY_FETCH_SUCCESS,
+  accounts,
+});
+
+export const fetchDirectoryFail = error => ({
+  type: DIRECTORY_FETCH_FAIL,
+  error,
+});
+
+export const expandDirectory = params => (dispatch, getState) => {
+  dispatch(expandDirectoryRequest());
+
+  const loadedItems = getState().getIn(['user_lists', 'directory', 'items']).size;
+
+  api(getState).get('/api/v1/directory', { params: { ...params, offset: loadedItems, limit: 20 } }).then(({ data }) => {
+    dispatch(importFetchedAccounts(data));
+    dispatch(expandDirectorySuccess(data));
+    dispatch(fetchRelationships(data.map(x => x.id)));
+  }).catch(error => dispatch(expandDirectoryFail(error)));
+};
+
+export const expandDirectoryRequest = () => ({
+  type: DIRECTORY_EXPAND_REQUEST,
+});
+
+export const expandDirectorySuccess = accounts => ({
+  type: DIRECTORY_EXPAND_SUCCESS,
+  accounts,
+});
+
+export const expandDirectoryFail = error => ({
+  type: DIRECTORY_EXPAND_FAIL,
+  error,
+});
diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js
index 5e7e78e698484674d3b7814d6d820f4839b0ad6e..f7108fdb9017ced70a54db2b4dd6513e15ba9a9e 100644
--- a/app/javascript/mastodon/actions/importer/normalizer.js
+++ b/app/javascript/mastodon/actions/importer/normalizer.js
@@ -73,8 +73,9 @@ export function normalizePoll(poll) {
 
   const emojiMap = makeEmojiMap(normalPoll);
 
-  normalPoll.options = poll.options.map(option => ({
+  normalPoll.options = poll.options.map((option, index) => ({
     ...option,
+    voted: poll.own_votes && poll.own_votes.includes(index),
     title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
   }));
 
diff --git a/app/javascript/mastodon/actions/markers.js b/app/javascript/mastodon/actions/markers.js
new file mode 100644
index 0000000000000000000000000000000000000000..c3a5fe86f1460c51d38562a6ddfff41163e1b98b
--- /dev/null
+++ b/app/javascript/mastodon/actions/markers.js
@@ -0,0 +1,30 @@
+export const submitMarkers = () => (dispatch, getState) => {
+  const accessToken = getState().getIn(['meta', 'access_token'], '');
+  const params      = {};
+
+  const lastHomeId         = getState().getIn(['timelines', 'home', 'items', 0]);
+  const lastNotificationId = getState().getIn(['notifications', 'items', 0, 'id']);
+
+  if (lastHomeId) {
+    params.home = {
+      last_read_id: lastHomeId,
+    };
+  }
+
+  if (lastNotificationId) {
+    params.notifications = {
+      last_read_id: lastNotificationId,
+    };
+  }
+
+  if (Object.keys(params).length === 0) {
+    return;
+  }
+
+  const client = new XMLHttpRequest();
+
+  client.open('POST', '/api/v1/markers', false);
+  client.setRequestHeader('Content-Type', 'application/json');
+  client.setRequestHeader('Authorization', `Bearer ${accessToken}`);
+  client.send(JSON.stringify(params));
+};
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index 56c952cb05866b3a0dbeaf97d4f06be7d4647842..58803d1ae5e442a2a59624ba4802c0eab540be44 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -12,6 +12,8 @@ import { defineMessages } from 'react-intl';
 import { List as ImmutableList } from 'immutable';
 import { unescapeHTML } from '../utils/html';
 import { getFiltersRegex } from '../selectors';
+import { usePendingItems as preferPendingItems } from 'mastodon/initial_state';
+import compareId from 'mastodon/compare_id';
 
 export const NOTIFICATIONS_UPDATE      = 'NOTIFICATIONS_UPDATE';
 export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
@@ -22,8 +24,12 @@ export const NOTIFICATIONS_EXPAND_FAIL    = 'NOTIFICATIONS_EXPAND_FAIL';
 
 export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';
 
-export const NOTIFICATIONS_CLEAR      = 'NOTIFICATIONS_CLEAR';
-export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
+export const NOTIFICATIONS_CLEAR        = 'NOTIFICATIONS_CLEAR';
+export const NOTIFICATIONS_SCROLL_TOP   = 'NOTIFICATIONS_SCROLL_TOP';
+export const NOTIFICATIONS_LOAD_PENDING = 'NOTIFICATIONS_LOAD_PENDING';
+
+export const NOTIFICATIONS_MOUNT   = 'NOTIFICATIONS_MOUNT';
+export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';
 
 defineMessages({
   mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
@@ -38,6 +44,10 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
   }
 };
 
+export const loadPending = () => ({
+  type: NOTIFICATIONS_LOAD_PENDING,
+});
+
 export function updateNotifications(notification, intlMessages, intlLocale) {
   return (dispatch, getState) => {
     const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true);
@@ -69,6 +79,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
       dispatch({
         type: NOTIFICATIONS_UPDATE,
         notification,
+        usePendingItems: preferPendingItems,
         meta: (playSound && !filtered) ? { sound: 'boop' } : undefined,
       });
 
@@ -122,10 +133,19 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
         : excludeTypesFromFilter(activeFilter),
     };
 
-    if (!maxId && notifications.get('items').size > 0) {
-      params.since_id = notifications.getIn(['items', 0, 'id']);
+    if (!params.max_id && (notifications.get('items', ImmutableList()).size + notifications.get('pendingItems', ImmutableList()).size) > 0) {
+      const a = notifications.getIn(['pendingItems', 0, 'id']);
+      const b = notifications.getIn(['items', 0, 'id']);
+
+      if (a && b && compareId(a, b) > 0) {
+        params.since_id = a;
+      } else {
+        params.since_id = b || a;
+      }
     }
 
+    const isLoadingRecent = !!params.since_id;
+
     dispatch(expandNotificationsRequest(isLoadingMore));
 
     api(getState).get('/api/v1/notifications', { params }).then(response => {
@@ -134,7 +154,7 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
       dispatch(importFetchedAccounts(response.data.map(item => item.account)));
       dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status)));
 
-      dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore));
+      dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent, isLoadingRecent && preferPendingItems));
       fetchRelatedRelationships(dispatch, response.data);
       done();
     }).catch(error => {
@@ -151,11 +171,13 @@ export function expandNotificationsRequest(isLoadingMore) {
   };
 };
 
-export function expandNotificationsSuccess(notifications, next, isLoadingMore) {
+export function expandNotificationsSuccess(notifications, next, isLoadingMore, isLoadingRecent, usePendingItems) {
   return {
     type: NOTIFICATIONS_EXPAND_SUCCESS,
     notifications,
     next,
+    isLoadingRecent: isLoadingRecent,
+    usePendingItems,
     skipLoading: !isLoadingMore,
   };
 };
@@ -196,3 +218,11 @@ export function setFilter (filterType) {
     dispatch(saveSettings());
   };
 };
+
+export const mountNotifications = () => ({
+  type: NOTIFICATIONS_MOUNT,
+});
+
+export const unmountNotifications = () => ({
+  type: NOTIFICATIONS_UNMOUNT,
+});
diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js
index 0974fdd15e6a5de30e318bade3ccbe4a73b58da4..a178faeadd9a128740d621676cb285017d4c6bba 100644
--- a/app/javascript/mastodon/actions/search.js
+++ b/app/javascript/mastodon/actions/search.js
@@ -10,6 +10,10 @@ export const SEARCH_FETCH_REQUEST = 'SEARCH_FETCH_REQUEST';
 export const SEARCH_FETCH_SUCCESS = 'SEARCH_FETCH_SUCCESS';
 export const SEARCH_FETCH_FAIL    = 'SEARCH_FETCH_FAIL';
 
+export const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST';
+export const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS';
+export const SEARCH_EXPAND_FAIL    = 'SEARCH_EXPAND_FAIL';
+
 export function changeSearch(value) {
   return {
     type: SEARCH_CHANGE,
@@ -77,8 +81,50 @@ export function fetchSearchFail(error) {
   };
 };
 
-export function showSearch() {
-  return {
-    type: SEARCH_SHOW,
-  };
+export const expandSearch = type => (dispatch, getState) => {
+  const value  = getState().getIn(['search', 'value']);
+  const offset = getState().getIn(['search', 'results', type]).size;
+
+  dispatch(expandSearchRequest());
+
+  api(getState).get('/api/v2/search', {
+    params: {
+      q: value,
+      type,
+      offset,
+    },
+  }).then(({ data }) => {
+    if (data.accounts) {
+      dispatch(importFetchedAccounts(data.accounts));
+    }
+
+    if (data.statuses) {
+      dispatch(importFetchedStatuses(data.statuses));
+    }
+
+    dispatch(expandSearchSuccess(data, value, type));
+    dispatch(fetchRelationships(data.accounts.map(item => item.id)));
+  }).catch(error => {
+    dispatch(expandSearchFail(error));
+  });
 };
+
+export const expandSearchRequest = () => ({
+  type: SEARCH_EXPAND_REQUEST,
+});
+
+export const expandSearchSuccess = (results, searchTerm, searchType) => ({
+  type: SEARCH_EXPAND_SUCCESS,
+  results,
+  searchTerm,
+  searchType,
+});
+
+export const expandSearchFail = error => ({
+  type: SEARCH_EXPAND_FAIL,
+  error,
+});
+
+export const showSearch = () => ({
+  type: SEARCH_SHOW,
+});
diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js
index 06c21b96b7f95e016bab3961a5a9dc3ec579ffc2..bc2ac5e82371a75e6bc2f5e76a8f2a4db24868c6 100644
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@ -1,6 +1,8 @@
 import { importFetchedStatus, importFetchedStatuses } from './importer';
-import api, { getLinks } from '../api';
+import api, { getLinks } from 'mastodon/api';
 import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+import compareId from 'mastodon/compare_id';
+import { usePendingItems as preferPendingItems } from 'mastodon/initial_state';
 
 export const TIMELINE_UPDATE  = 'TIMELINE_UPDATE';
 export const TIMELINE_DELETE  = 'TIMELINE_DELETE';
@@ -10,10 +12,15 @@ export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST';
 export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS';
 export const TIMELINE_EXPAND_FAIL    = 'TIMELINE_EXPAND_FAIL';
 
-export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
+export const TIMELINE_SCROLL_TOP   = 'TIMELINE_SCROLL_TOP';
+export const TIMELINE_LOAD_PENDING = 'TIMELINE_LOAD_PENDING';
+export const TIMELINE_DISCONNECT   = 'TIMELINE_DISCONNECT';
+export const TIMELINE_CONNECT      = 'TIMELINE_CONNECT';
 
-export const TIMELINE_CONNECT    = 'TIMELINE_CONNECT';
-export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
+export const loadPending = timeline => ({
+  type: TIMELINE_LOAD_PENDING,
+  timeline,
+});
 
 export function updateTimeline(timeline, status, accept) {
   return dispatch => {
@@ -27,6 +34,7 @@ export function updateTimeline(timeline, status, accept) {
       type: TIMELINE_UPDATE,
       timeline,
       status,
+      usePendingItems: preferPendingItems,
     });
   };
 };
@@ -71,8 +79,15 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
       return;
     }
 
-    if (!params.max_id && !params.pinned && timeline.get('items', ImmutableList()).size > 0) {
-      params.since_id = timeline.getIn(['items', 0]);
+    if (!params.max_id && !params.pinned && (timeline.get('items', ImmutableList()).size + timeline.get('pendingItems', ImmutableList()).size) > 0) {
+      const a = timeline.getIn(['pendingItems', 0]);
+      const b = timeline.getIn(['items', 0]);
+
+      if (a && b && compareId(a, b) > 0) {
+        params.since_id = a;
+      } else {
+        params.since_id = b || a;
+      }
     }
 
     const isLoadingRecent = !!params.since_id;
@@ -82,7 +97,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
     api(getState).get(path, { params }).then(response => {
       const next = getLinks(response).refs.find(link => link.rel === 'next');
       dispatch(importFetchedStatuses(response.data));
-      dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore));
+      dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
       done();
     }).catch(error => {
       dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
@@ -115,7 +130,7 @@ export function expandTimelineRequest(timeline, isLoadingMore) {
   };
 };
 
-export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore) {
+export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore, usePendingItems) {
   return {
     type: TIMELINE_EXPAND_SUCCESS,
     timeline,
@@ -123,6 +138,7 @@ export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadi
     next,
     partial,
     isLoadingRecent,
+    usePendingItems,
     skipLoading: !isLoadingMore,
   };
 };
@@ -151,9 +167,8 @@ export function connectTimeline(timeline) {
   };
 };
 
-export function disconnectTimeline(timeline) {
-  return {
-    type: TIMELINE_DISCONNECT,
-    timeline,
-  };
-};
+export const disconnectTimeline = timeline => ({
+  type: TIMELINE_DISCONNECT,
+  timeline,
+  usePendingItems: preferPendingItems,
+});
diff --git a/app/javascript/mastodon/actions/trends.js b/app/javascript/mastodon/actions/trends.js
new file mode 100644
index 0000000000000000000000000000000000000000..853e4f60aec711f0d3437163fa3e8a680e0869cb
--- /dev/null
+++ b/app/javascript/mastodon/actions/trends.js
@@ -0,0 +1,32 @@
+import api from '../api';
+
+export const TRENDS_FETCH_REQUEST = 'TRENDS_FETCH_REQUEST';
+export const TRENDS_FETCH_SUCCESS = 'TRENDS_FETCH_SUCCESS';
+export const TRENDS_FETCH_FAIL    = 'TRENDS_FETCH_FAIL';
+
+export const fetchTrends = () => (dispatch, getState) => {
+  dispatch(fetchTrendsRequest());
+
+  api(getState)
+    .get('/api/v1/trends')
+    .then(({ data }) => dispatch(fetchTrendsSuccess(data)))
+    .catch(err => dispatch(fetchTrendsFail(err)));
+};
+
+export const fetchTrendsRequest = () => ({
+  type: TRENDS_FETCH_REQUEST,
+  skipLoading: true,
+});
+
+export const fetchTrendsSuccess = trends => ({
+  type: TRENDS_FETCH_SUCCESS,
+  trends,
+  skipLoading: true,
+});
+
+export const fetchTrendsFail = error => ({
+  type: TRENDS_FETCH_FAIL,
+  error,
+  skipLoading: true,
+  skipAlert: true,
+});
diff --git a/app/javascript/mastodon/compare_id.js b/app/javascript/mastodon/compare_id.js
index aaff66481ac7fd1b8cf6f59a5bef1497e0482279..66cf51c4b67c11b53b93e4dff8c88f678d61ea33 100644
--- a/app/javascript/mastodon/compare_id.js
+++ b/app/javascript/mastodon/compare_id.js
@@ -1,10 +1,11 @@
-export default function compareId(id1, id2) {
+export default function compareId (id1, id2) {
   if (id1 === id2) {
     return 0;
   }
+
   if (id1.length === id2.length) {
     return id1 > id2 ? 1 : -1;
   } else {
     return id1.length > id2.length ? 1 : -1;
   }
-}
+};
diff --git a/app/javascript/mastodon/components/autosuggest_hashtag.js b/app/javascript/mastodon/components/autosuggest_hashtag.js
new file mode 100644
index 0000000000000000000000000000000000000000..e2f4e320dd1db663f8d416e7447216a19c0a56ca
--- /dev/null
+++ b/app/javascript/mastodon/components/autosuggest_hashtag.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { shortNumberFormat } from 'mastodon/utils/numbers';
+import { FormattedMessage } from 'react-intl';
+
+export default class AutosuggestHashtag extends React.PureComponent {
+
+  static propTypes = {
+    tag: PropTypes.shape({
+      name: PropTypes.string.isRequired,
+      url: PropTypes.string,
+      history: PropTypes.array,
+    }).isRequired,
+  };
+
+  render () {
+    const { tag } = this.props;
+    const weeklyUses = tag.history && shortNumberFormat(tag.history.reduce((total, day) => total + (day.uses * 1), 0));
+
+    return (
+      <div className='autosuggest-hashtag'>
+        <div className='autosuggest-hashtag__name'>#<strong>{tag.name}</strong></div>
+        {tag.history !== undefined && <div className='autosuggest-hashtag__uses'><FormattedMessage id='autosuggest_hashtag.per_week' defaultMessage='{count} per week' values={{ count: weeklyUses }} /></div>}
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/components/autosuggest_input.js b/app/javascript/mastodon/components/autosuggest_input.js
index c7d965b53ac5e284662512054f59ef57fbca7b8c..6d2035add0184c51dfa1a4522240e8fe4845c781 100644
--- a/app/javascript/mastodon/components/autosuggest_input.js
+++ b/app/javascript/mastodon/components/autosuggest_input.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
 import AutosuggestEmoji from './autosuggest_emoji';
+import AutosuggestHashtag from './autosuggest_hashtag';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import { isRtl } from '../rtl';
@@ -167,15 +168,15 @@ export default class AutosuggestInput extends ImmutablePureComponent {
     const { selectedSuggestion } = this.state;
     let inner, key;
 
-    if (typeof suggestion === 'object') {
+    if (suggestion.type === 'emoji') {
       inner = <AutosuggestEmoji emoji={suggestion} />;
       key   = suggestion.id;
-    } else if (suggestion[0] === '#') {
-      inner = suggestion;
-      key   = suggestion;
-    } else {
-      inner = <AutosuggestAccountContainer id={suggestion} />;
-      key   = suggestion;
+    } else if (suggestion.type ==='hashtag') {
+      inner = <AutosuggestHashtag tag={suggestion} />;
+      key   = suggestion.name;
+    } else if (suggestion.type === 'account') {
+      inner = <AutosuggestAccountContainer id={suggestion.id} />;
+      key   = suggestion.id;
     }
 
     return (
diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js
index b070fe3e5156d65353c46cce00cf8fb7f0509a3d..ac2a6366a07025a506efeab7af5bf64ba1fc6165 100644
--- a/app/javascript/mastodon/components/autosuggest_textarea.js
+++ b/app/javascript/mastodon/components/autosuggest_textarea.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
 import AutosuggestEmoji from './autosuggest_emoji';
+import AutosuggestHashtag from './autosuggest_hashtag';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import { isRtl } from '../rtl';
@@ -173,15 +174,15 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
     const { selectedSuggestion } = this.state;
     let inner, key;
 
-    if (typeof suggestion === 'object') {
+    if (suggestion.type === 'emoji') {
       inner = <AutosuggestEmoji emoji={suggestion} />;
       key   = suggestion.id;
-    } else if (suggestion[0] === '#') {
-      inner = suggestion;
-      key   = suggestion;
-    } else {
-      inner = <AutosuggestAccountContainer id={suggestion} />;
-      key   = suggestion;
+    } else if (suggestion.type === 'hashtag') {
+      inner = <AutosuggestHashtag tag={suggestion} />;
+      key   = suggestion.name;
+    } else if (suggestion.type === 'account') {
+      inner = <AutosuggestAccountContainer id={suggestion.id} />;
+      key   = suggestion.id;
     }
 
     return (
diff --git a/app/javascript/mastodon/components/avatar_composite.js b/app/javascript/mastodon/components/avatar_composite.js
index 4a9a73c512e2cc0dc18649e331ea245c6bf913ca..5d5b897492fec082ba26453f98153b293a096b12 100644
--- a/app/javascript/mastodon/components/avatar_composite.js
+++ b/app/javascript/mastodon/components/avatar_composite.js
@@ -35,35 +35,35 @@ export default class AvatarComposite extends React.PureComponent {
 
     if (size === 2) {
       if (index === 0) {
-        right = '2px';
+        right = '1px';
       } else {
-        left = '2px';
+        left = '1px';
       }
     } else if (size === 3) {
       if (index === 0) {
-        right = '2px';
+        right = '1px';
       } else if (index > 0) {
-        left = '2px';
+        left = '1px';
       }
 
       if (index === 1) {
-        bottom = '2px';
+        bottom = '1px';
       } else if (index > 1) {
-        top = '2px';
+        top = '1px';
       }
     } else if (size === 4) {
       if (index === 0 || index === 2) {
-        right = '2px';
+        right = '1px';
       }
 
       if (index === 1 || index === 3) {
-        left = '2px';
+        left = '1px';
       }
 
       if (index < 2) {
-        bottom = '2px';
+        bottom = '1px';
       } else {
-        top = '2px';
+        top = '1px';
       }
     }
 
@@ -88,7 +88,13 @@ export default class AvatarComposite extends React.PureComponent {
 
     return (
       <div className='account__avatar-composite' style={{ width: `${size}px`, height: `${size}px` }}>
-        {accounts.take(4).map((account, i) => this.renderItem(account, accounts.size, i))}
+        {accounts.take(4).map((account, i) => this.renderItem(account, Math.min(accounts.size, 4), i))}
+
+        {accounts.size > 4 && (
+          <span className='account__avatar-composite__label'>
+            +{accounts.size - 4}
+          </span>
+        )}
       </div>
     );
   }
diff --git a/app/javascript/mastodon/components/column.js b/app/javascript/mastodon/components/column.js
index d453874636ed50261fabca67f0a61e2907111b06..55e3bfd5e0b4312e4cf8ade94b8a0556971f4810 100644
--- a/app/javascript/mastodon/components/column.js
+++ b/app/javascript/mastodon/components/column.js
@@ -8,10 +8,11 @@ export default class Column extends React.PureComponent {
   static propTypes = {
     children: PropTypes.node,
     label: PropTypes.string,
+    bindToDocument: PropTypes.bool,
   };
 
   scrollTop () {
-    const scrollable = this.node.querySelector('.scrollable');
+    const scrollable = this.props.bindToDocument ? document.scrollingElement : this.node.querySelector('.scrollable');
 
     if (!scrollable) {
       return;
@@ -33,11 +34,19 @@ export default class Column extends React.PureComponent {
   }
 
   componentDidMount () {
-    this.node.addEventListener('wheel', this.handleWheel,  detectPassiveEvents.hasSupport ? { passive: true } : false);
+    if (this.props.bindToDocument) {
+      document.addEventListener('wheel', this.handleWheel,  detectPassiveEvents.hasSupport ? { passive: true } : false);
+    } else {
+      this.node.addEventListener('wheel', this.handleWheel,  detectPassiveEvents.hasSupport ? { passive: true } : false);
+    }
   }
 
   componentWillUnmount () {
-    this.node.removeEventListener('wheel', this.handleWheel);
+    if (this.props.bindToDocument) {
+      document.removeEventListener('wheel', this.handleWheel);
+    } else {
+      this.node.removeEventListener('wheel', this.handleWheel);
+    }
   }
 
   render () {
diff --git a/app/javascript/mastodon/components/column_back_button.js b/app/javascript/mastodon/components/column_back_button.js
index f41045787e47edbc70db7f9d4cafefb1b933b831..d97622705e588b8951cbf0197c714ae0a37fe07e 100644
--- a/app/javascript/mastodon/components/column_back_button.js
+++ b/app/javascript/mastodon/components/column_back_button.js
@@ -2,6 +2,7 @@ import React from 'react';
 import { FormattedMessage } from 'react-intl';
 import PropTypes from 'prop-types';
 import Icon from 'mastodon/components/icon';
+import { createPortal } from 'react-dom';
 
 export default class ColumnBackButton extends React.PureComponent {
 
@@ -9,6 +10,10 @@ export default class ColumnBackButton extends React.PureComponent {
     router: PropTypes.object,
   };
 
+  static propTypes = {
+    multiColumn: PropTypes.bool,
+  };
+
   handleClick = () => {
     if (window.history && window.history.length === 1) {
       this.context.router.history.push('/');
@@ -18,12 +23,32 @@ export default class ColumnBackButton extends React.PureComponent {
   }
 
   render () {
-    return (
+    const { multiColumn } = this.props;
+
+    const component = (
       <button onClick={this.handleClick} className='column-back-button'>
         <Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
         <FormattedMessage id='column_back_button.label' defaultMessage='Back' />
       </button>
     );
+
+    if (multiColumn) {
+      return component;
+    } else {
+      // The portal container and the component may be rendered to the DOM in
+      // the same React render pass, so the container might not be available at
+      // the time `render()` is called.
+      const container = document.getElementById('tabs-bar__portal');
+      if (container === null) {
+        // The container wasn't available, force a re-render so that the
+        // component can eventually be inserted in the container and not scroll
+        // with the rest of the area.
+        this.forceUpdate();
+        return component;
+      } else {
+        return createPortal(component, container);
+      }
+    }
   }
 
 }
diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js
index f33c689e7c6d1612767755a43201e62ed90906bc..0038995c8be0fb76fda78ea381a37c132bae57ca 100644
--- a/app/javascript/mastodon/components/column_header.js
+++ b/app/javascript/mastodon/components/column_header.js
@@ -1,5 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import { createPortal } from 'react-dom';
 import classNames from 'classnames';
 import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
 import Icon from 'mastodon/components/icon';
@@ -28,6 +29,7 @@ class ColumnHeader extends React.PureComponent {
     showBackButton: PropTypes.bool,
     children: PropTypes.node,
     pinned: PropTypes.bool,
+    placeholder: PropTypes.bool,
     onPin: PropTypes.func,
     onMove: PropTypes.func,
     onClick: PropTypes.func,
@@ -79,7 +81,7 @@ class ColumnHeader extends React.PureComponent {
   }
 
   render () {
-    const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage } } = this.props;
+    const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder } = this.props;
     const { collapsed, animating } = this.state;
 
     const wrapperClassName = classNames('column-header__wrapper', {
@@ -118,7 +120,7 @@ class ColumnHeader extends React.PureComponent {
           <button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><Icon id='chevron-right' /></button>
         </div>
       );
-    } else if (multiColumn) {
+    } else if (multiColumn && this.props.onPin) {
       pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
     }
 
@@ -140,13 +142,13 @@ class ColumnHeader extends React.PureComponent {
       collapsedContent.push(pinButton);
     }
 
-    if (children || multiColumn) {
+    if (children || (multiColumn && this.props.onPin)) {
       collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><Icon id='sliders' /></button>;
     }
 
     const hasTitle = icon && title;
 
-    return (
+    const component = (
       <div className={wrapperClassName}>
         <h1 className={buttonClassName}>
           {hasTitle && (
@@ -172,6 +174,24 @@ class ColumnHeader extends React.PureComponent {
         </div>
       </div>
     );
+
+    if (multiColumn || placeholder) {
+      return component;
+    } else {
+      // The portal container and the component may be rendered to the DOM in
+      // the same React render pass, so the container might not be available at
+      // the time `render()` is called.
+      const container = document.getElementById('tabs-bar__portal');
+      if (container === null) {
+        // The container wasn't available, force a re-render so that the
+        // component can eventually be inserted in the container and not scroll
+        // with the rest of the area.
+        this.forceUpdate();
+        return component;
+      } else {
+        return createPortal(component, container);
+      }
+    }
   }
 
 }
diff --git a/app/javascript/mastodon/components/error_boundary.js b/app/javascript/mastodon/components/error_boundary.js
index d1ca5bf756a3513be65736fadaae74e8671b557e..82543e1185ade5003dff9d1929545a0f1f6128ab 100644
--- a/app/javascript/mastodon/components/error_boundary.js
+++ b/app/javascript/mastodon/components/error_boundary.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import illustration from '../../images/elephant_ui_disappointed.svg';
+import { FormattedMessage } from 'react-intl';
+import { version, source_url } from 'mastodon/initial_state';
 
 export default class ErrorBoundary extends React.PureComponent {
 
@@ -12,26 +13,53 @@ export default class ErrorBoundary extends React.PureComponent {
     hasError: false,
     stackTrace: undefined,
     componentStack: undefined,
-  }
+  };
 
-  componentDidCatch(error, info) {
+  componentDidCatch (error, info) {
     this.setState({
       hasError: true,
       stackTrace: error.stack,
       componentStack: info && info.componentStack,
+      copied: false,
     });
   }
 
+  handleCopyStackTrace = () => {
+    const { stackTrace } = this.state;
+    const textarea = document.createElement('textarea');
+
+    textarea.textContent    = stackTrace;
+    textarea.style.position = 'fixed';
+
+    document.body.appendChild(textarea);
+
+    try {
+      textarea.select();
+      document.execCommand('copy');
+    } catch (e) {
+
+    } finally {
+      document.body.removeChild(textarea);
+    }
+
+    this.setState({ copied: true });
+    setTimeout(() => this.setState({ copied: false }), 700);
+  }
+
   render() {
-    const { hasError } = this.state;
+    const { hasError, copied } = this.state;
 
     if (!hasError) {
       return this.props.children;
     }
 
     return (
-      <div>
-        <img src={illustration} alt='' />
+      <div className='error-boundary'>
+        <div>
+          <p className='error-boundary__error'><FormattedMessage id='error.unexpected_crash.explanation' defaultMessage='Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.' /></p>
+          <p><FormattedMessage id='error.unexpected_crash.next_steps' defaultMessage='Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.' /></p>
+          <p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied && 'copied'}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
+        </div>
       </div>
     );
   }
diff --git a/app/javascript/mastodon/components/extended_video_player.js b/app/javascript/mastodon/components/extended_video_player.js
deleted file mode 100644
index 009c0d559ad67e60e0789c03aacf1be84ad69a2a..0000000000000000000000000000000000000000
--- a/app/javascript/mastodon/components/extended_video_player.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class ExtendedVideoPlayer extends React.PureComponent {
-
-  static propTypes = {
-    src: PropTypes.string.isRequired,
-    alt: PropTypes.string,
-    width: PropTypes.number,
-    height: PropTypes.number,
-    time: PropTypes.number,
-    controls: PropTypes.bool.isRequired,
-    muted: PropTypes.bool.isRequired,
-    onClick: PropTypes.func,
-  };
-
-  handleLoadedData = () => {
-    if (this.props.time) {
-      this.video.currentTime = this.props.time;
-    }
-  }
-
-  componentDidMount () {
-    this.video.addEventListener('loadeddata', this.handleLoadedData);
-  }
-
-  componentWillUnmount () {
-    this.video.removeEventListener('loadeddata', this.handleLoadedData);
-  }
-
-  setRef = (c) => {
-    this.video = c;
-  }
-
-  handleClick = e => {
-    e.stopPropagation();
-    const handler = this.props.onClick;
-    if (handler) handler();
-  }
-
-  render () {
-    const { src, muted, controls, alt } = this.props;
-
-    return (
-      <div className='extended-video-player'>
-        <video
-          ref={this.setRef}
-          src={src}
-          autoPlay
-          role='button'
-          tabIndex='0'
-          aria-label={alt}
-          title={alt}
-          muted={muted}
-          controls={controls}
-          loop={!controls}
-          onClick={this.handleClick}
-        />
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/mastodon/components/gifv.js b/app/javascript/mastodon/components/gifv.js
new file mode 100644
index 0000000000000000000000000000000000000000..83cfae49c494d752759db8b969d5b4ef2b4c3889
--- /dev/null
+++ b/app/javascript/mastodon/components/gifv.js
@@ -0,0 +1,75 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+export default class GIFV extends React.PureComponent {
+
+  static propTypes = {
+    src: PropTypes.string.isRequired,
+    alt: PropTypes.string,
+    width: PropTypes.number,
+    height: PropTypes.number,
+    onClick: PropTypes.func,
+  };
+
+  state = {
+    loading: true,
+  };
+
+  handleLoadedData = () => {
+    this.setState({ loading: false });
+  }
+
+  componentWillReceiveProps (nextProps) {
+    if (nextProps.src !== this.props.src) {
+      this.setState({ loading: true });
+    }
+  }
+
+  handleClick = e => {
+    const { onClick } = this.props;
+
+    if (onClick) {
+      e.stopPropagation();
+      onClick();
+    }
+  }
+
+  render () {
+    const { src, width, height, alt } = this.props;
+    const { loading } = this.state;
+
+    return (
+      <div className='gifv' style={{ position: 'relative' }}>
+        {loading && (
+          <canvas
+            width={width}
+            height={height}
+            role='button'
+            tabIndex='0'
+            aria-label={alt}
+            title={alt}
+            onClick={this.handleClick}
+          />
+        )}
+
+        <video
+          src={src}
+          width={width}
+          height={height}
+          role='button'
+          tabIndex='0'
+          aria-label={alt}
+          title={alt}
+          muted
+          loop
+          autoPlay
+          playsInline
+          onClick={this.handleClick}
+          onLoadedData={this.handleLoadedData}
+          style={{ position: loading ? 'absolute' : 'static', top: 0, left: 0 }}
+        />
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/components/hashtag.js b/app/javascript/mastodon/components/hashtag.js
index f091d7893e6631e0f6c12983f8a139854d78051a..62d61326261b3ac5efb1e949ec198a929003a46b 100644
--- a/app/javascript/mastodon/components/hashtag.js
+++ b/app/javascript/mastodon/components/hashtag.js
@@ -12,11 +12,11 @@ const Hashtag = ({ hashtag }) => (
         #<span>{hashtag.get('name')}</span>
       </Permalink>
 
-      <FormattedMessage id='trends.count_by_accounts' defaultMessage='{count} {rawCount, plural, one {person} other {people}} talking' values={{ rawCount: hashtag.getIn(['history', 0, 'accounts']), count: <strong>{shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))}</strong> }} />
+      <FormattedMessage id='trends.count_by_accounts' defaultMessage='{count} {rawCount, plural, one {person} other {people}} talking' values={{ rawCount: hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1, count: <strong>{shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1)}</strong> }} />
     </div>
 
     <div className='trends__item__current'>
-      {shortNumberFormat(hashtag.getIn(['history', 0, 'uses']))}
+      {shortNumberFormat(hashtag.getIn(['history', 0, 'uses']) * 1 + hashtag.getIn(['history', 1, 'uses']) * 1)}
     </div>
 
     <div className='trends__item__sparkline'>
diff --git a/app/javascript/mastodon/components/load_pending.js b/app/javascript/mastodon/components/load_pending.js
new file mode 100644
index 0000000000000000000000000000000000000000..7e27024036a69c70a25fa227bfabbac04bf38aab
--- /dev/null
+++ b/app/javascript/mastodon/components/load_pending.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import PropTypes from 'prop-types';
+
+export default class LoadPending extends React.PureComponent {
+
+  static propTypes = {
+    onClick: PropTypes.func,
+    count: PropTypes.number,
+  }
+
+  render() {
+    const { count } = this.props;
+
+    return (
+      <button className='load-more load-gap' onClick={this.props.onClick}>
+        <FormattedMessage id='load_pending' defaultMessage='{count, plural, one {# new item} other {# new items}}' values={{ count }} />
+      </button>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index 77bac61eec9e2a20b3ff71a710af2618c8b26626..e8dd79af9e09cbb5a81995af0ce33630074516fe 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -6,7 +6,7 @@ import IconButton from './icon_button';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { isIOS } from '../is_mobile';
 import classNames from 'classnames';
-import { autoPlayGif, displayMedia } from '../initial_state';
+import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state';
 import { decode } from 'blurhash';
 
 const messages = defineMessages({
@@ -81,6 +81,8 @@ class Item extends React.PureComponent {
   }
 
   _decode () {
+    if (!useBlurhash) return;
+
     const hash   = this.props.attachment.get('blurhash');
     const pixels = decode(hash, 32, 32);
 
@@ -157,7 +159,7 @@ class Item extends React.PureComponent {
     if (attachment.get('type') === 'unknown') {
       return (
         <div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
-          <a className='media-gallery__item-thumbnail' href={attachment.get('remote_url')} target='_blank' style={{ cursor: 'pointer' }} title={attachment.get('description')}>
+          <a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} target='_blank' style={{ cursor: 'pointer' }} title={attachment.get('description')}>
             <canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' />
           </a>
         </div>
@@ -313,15 +315,22 @@ class MediaGallery extends React.PureComponent {
       style.height = height;
     }
 
-    const size = media.take(4).size;
+    const size     = media.take(4).size;
+    const uncached = media.every(attachment => attachment.get('type') === 'unknown');
 
     if (this.isStandaloneEligible()) {
       children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} />;
     } else {
-      children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} displayWidth={width} visible={visible} />);
+      children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} displayWidth={width} visible={visible || uncached} />);
     }
 
-    if (visible) {
+    if (uncached) {
+      spoilerButton = (
+        <button type='button' disabled className='spoiler-button__overlay'>
+          <span className='spoiler-button__overlay__label'><FormattedMessage id='status.uncached_media_warning' defaultMessage='Not available' /></span>
+        </button>
+      );
+    } else if (visible) {
       spoilerButton = <IconButton title={intl.formatMessage(messages.toggle_visible)} icon='eye-slash' overlay onClick={this.handleOpen} />;
     } else {
       spoilerButton = (
@@ -333,7 +342,7 @@ class MediaGallery extends React.PureComponent {
 
     return (
       <div className='media-gallery' style={style} ref={this.handleRef}>
-        <div className={classNames('spoiler-button', { 'spoiler-button--minified': visible })}>
+        <div className={classNames('spoiler-button', { 'spoiler-button--minified': visible && !uncached, 'spoiler-button--click-thru': uncached })}>
           {spoilerButton}
         </div>
 
diff --git a/app/javascript/mastodon/components/missing_indicator.js b/app/javascript/mastodon/components/missing_indicator.js
index 70d8c3b98412821662a9b148e4e5360faeafbee3..7b0101bab852aa875b136c0fd38f5ca23eac5232 100644
--- a/app/javascript/mastodon/components/missing_indicator.js
+++ b/app/javascript/mastodon/components/missing_indicator.js
@@ -1,17 +1,24 @@
 import React from 'react';
+import PropTypes from 'prop-types';
 import { FormattedMessage } from 'react-intl';
+import illustration from 'mastodon/../images/elephant_ui_disappointed.svg';
+import classNames from 'classnames';
 
-const MissingIndicator = () => (
-  <div className='regeneration-indicator missing-indicator'>
-    <div>
-      <div className='regeneration-indicator__figure' />
+const MissingIndicator = ({ fullPage }) => (
+  <div className={classNames('regeneration-indicator', { 'regeneration-indicator--without-header': fullPage })}>
+    <div className='regeneration-indicator__figure'>
+      <img src={illustration} alt='' />
+    </div>
 
-      <div className='regeneration-indicator__label'>
-        <FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
-        <FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
-      </div>
+    <div className='regeneration-indicator__label'>
+      <FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
+      <FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
     </div>
   </div>
 );
 
+MissingIndicator.propTypes = {
+  fullPage: PropTypes.bool,
+};
+
 export default MissingIndicator;
diff --git a/app/javascript/mastodon/components/poll.js b/app/javascript/mastodon/components/poll.js
index 690f9ae5a076da1169b18314d9315adeb024357f..cdbcf8f709fdf0572dd1d70f6e68b06763f11a90 100644
--- a/app/javascript/mastodon/components/poll.js
+++ b/app/javascript/mastodon/components/poll.js
@@ -10,9 +10,11 @@ import spring from 'react-motion/lib/spring';
 import escapeTextContentForBrowser from 'escape-html';
 import emojify from 'mastodon/features/emoji/emoji';
 import RelativeTimestamp from './relative_timestamp';
+import Icon from 'mastodon/components/icon';
 
 const messages = defineMessages({
   closed: { id: 'poll.closed', defaultMessage: 'Closed' },
+  voted: { id: 'poll.voted', defaultMessage: 'You voted for this answer', description: 'Tooltip of the "voted" checkmark in polls' },
 });
 
 const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
@@ -32,8 +34,38 @@ class Poll extends ImmutablePureComponent {
 
   state = {
     selected: {},
+    expired: null,
   };
 
+  static getDerivedStateFromProps (props, state) {
+    const { poll, intl } = props;
+    const expired = poll.get('expired') || (new Date(poll.get('expires_at'))).getTime() < intl.now();
+    return (expired === state.expired) ? null : { expired };
+  }
+
+  componentDidMount () {
+    this._setupTimer();
+  }
+
+  componentDidUpdate () {
+    this._setupTimer();
+  }
+
+  componentWillUnmount () {
+    clearTimeout(this._timer);
+  }
+
+  _setupTimer () {
+    const { poll, intl } = this.props;
+    clearTimeout(this._timer);
+    if (!this.state.expired) {
+      const delay = (new Date(poll.get('expires_at'))).getTime() - intl.now();
+      this._timer = setTimeout(() => {
+        this.setState({ expired: true });
+      }, delay);
+    }
+  }
+
   handleOptionChange = e => {
     const { target: { value } } = e;
 
@@ -68,12 +100,13 @@ class Poll extends ImmutablePureComponent {
     this.props.dispatch(fetchPoll(this.props.poll.get('id')));
   };
 
-  renderOption (option, optionIndex) {
-    const { poll, disabled } = this.props;
-    const percent            = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100;
-    const leading            = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
-    const active             = !!this.state.selected[`${optionIndex}`];
-    const showResults        = poll.get('voted') || poll.get('expired');
+  renderOption (option, optionIndex, showResults) {
+    const { poll, disabled, intl } = this.props;
+    const pollVotesCount  = poll.get('voters_count') || poll.get('votes_count');
+    const percent         = pollVotesCount === 0 ? 0 : (option.get('votes_count') / pollVotesCount) * 100;
+    const leading         = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') >= other.get('votes_count'));
+    const active          = !!this.state.selected[`${optionIndex}`];
+    const voted           = option.get('voted') || (poll.get('own_votes') && poll.get('own_votes').includes(optionIndex));
 
     let titleEmojified = option.get('title_emojified');
     if (!titleEmojified) {
@@ -102,7 +135,10 @@ class Poll extends ImmutablePureComponent {
           />
 
           {!showResults && <span className={classNames('poll__input', { checkbox: poll.get('multiple'), active })} />}
-          {showResults && <span className='poll__number'>{Math.round(percent)}%</span>}
+          {showResults && <span className='poll__number'>
+            {!!voted && <Icon id='check' className='poll__vote__mark' title={intl.formatMessage(messages.voted)} />}
+            {Math.round(percent)}%
+          </span>}
 
           <span dangerouslySetInnerHTML={{ __html: titleEmojified }} />
         </label>
@@ -112,25 +148,34 @@ class Poll extends ImmutablePureComponent {
 
   render () {
     const { poll, intl } = this.props;
+    const { expired } = this.state;
 
     if (!poll) {
       return null;
     }
 
-    const timeRemaining = poll.get('expired') ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />;
-    const showResults   = poll.get('voted') || poll.get('expired');
+    const timeRemaining = expired ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />;
+    const showResults   = poll.get('voted') || expired;
     const disabled      = this.props.disabled || Object.entries(this.state.selected).every(item => !item);
 
+    let votesCount = null;
+
+    if (poll.get('voters_count') !== null && poll.get('voters_count') !== undefined) {
+      votesCount = <FormattedMessage id='poll.total_people' defaultMessage='{count, plural, one {# person} other {# people}}' values={{ count: poll.get('voters_count') }} />;
+    } else {
+      votesCount = <FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get('votes_count') }} />;
+    }
+
     return (
       <div className='poll'>
         <ul>
-          {poll.get('options').map((option, i) => this.renderOption(option, i))}
+          {poll.get('options').map((option, i) => this.renderOption(option, i, showResults))}
         </ul>
 
         <div className='poll__footer'>
           {!showResults && <button className='button button-secondary' disabled={disabled} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
           {showResults && !this.props.disabled && <span><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </span>}
-          <FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get('votes_count') }} />
+          {votesCount}
           {poll.get('expires_at') && <span> · {timeRemaining}</span>}
         </div>
       </div>
diff --git a/app/javascript/mastodon/components/radio_button.js b/app/javascript/mastodon/components/radio_button.js
new file mode 100644
index 0000000000000000000000000000000000000000..0496fa2868ac7bfdfa294206cda3572362f163e8
--- /dev/null
+++ b/app/javascript/mastodon/components/radio_button.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+
+export default class RadioButton extends React.PureComponent {
+
+  static propTypes = {
+    value: PropTypes.string.isRequired,
+    checked: PropTypes.bool,
+    name: PropTypes.string.isRequired,
+    onChange: PropTypes.func.isRequired,
+    label: PropTypes.node.isRequired,
+  };
+
+  render () {
+    const { name, value, checked, onChange, label } = this.props;
+
+    return (
+      <label className='radio-button'>
+        <input
+          name={name}
+          type='radio'
+          value={value}
+          checked={checked}
+          onChange={onChange}
+        />
+
+        <span className={classNames('radio-button__input', { checked })} />
+
+        <span>{label}</span>
+      </label>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/components/regeneration_indicator.js b/app/javascript/mastodon/components/regeneration_indicator.js
new file mode 100644
index 0000000000000000000000000000000000000000..faf88c6b5031ac6bcc45d39f05ece8f5f78b1a9f
--- /dev/null
+++ b/app/javascript/mastodon/components/regeneration_indicator.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+import illustration from 'mastodon/../images/elephant_ui_working.svg';
+
+const MissingIndicator = () => (
+  <div className='regeneration-indicator'>
+    <div className='regeneration-indicator__figure'>
+      <img src={illustration} alt='' />
+    </div>
+
+    <div className='regeneration-indicator__label'>
+      <FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading&hellip;' />
+      <FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
+    </div>
+  </div>
+);
+
+export default MissingIndicator;
diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js
index 0376cf85accef9390512dc9d67baf71623d255a0..421756803c96fb2b1da7ec382243169e9ae1af5d 100644
--- a/app/javascript/mastodon/components/scrollable_list.js
+++ b/app/javascript/mastodon/components/scrollable_list.js
@@ -3,6 +3,7 @@ import { ScrollContainer } from 'react-router-scroll-4';
 import PropTypes from 'prop-types';
 import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
 import LoadMore from './load_more';
+import LoadPending from './load_pending';
 import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
 import { throttle } from 'lodash';
 import { List as ImmutableList } from 'immutable';
@@ -21,6 +22,7 @@ export default class ScrollableList extends PureComponent {
   static propTypes = {
     scrollKey: PropTypes.string.isRequired,
     onLoadMore: PropTypes.func,
+    onLoadPending: PropTypes.func,
     onScrollToTop: PropTypes.func,
     onScroll: PropTypes.func,
     trackScroll: PropTypes.bool,
@@ -28,10 +30,12 @@ export default class ScrollableList extends PureComponent {
     isLoading: PropTypes.bool,
     showLoading: PropTypes.bool,
     hasMore: PropTypes.bool,
+    numPending: PropTypes.number,
     prepend: PropTypes.node,
     alwaysPrepend: PropTypes.bool,
     emptyMessage: PropTypes.node,
     children: PropTypes.node,
+    bindToDocument: PropTypes.bool,
   };
 
   static defaultProps = {
@@ -47,7 +51,9 @@ export default class ScrollableList extends PureComponent {
 
   handleScroll = throttle(() => {
     if (this.node) {
-      const { scrollTop, scrollHeight, clientHeight } = this.node;
+      const scrollTop = this.getScrollTop();
+      const scrollHeight = this.getScrollHeight();
+      const clientHeight = this.getClientHeight();
       const offset = scrollHeight - scrollTop - clientHeight;
 
       if (400 > offset && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading) {
@@ -77,9 +83,14 @@ export default class ScrollableList extends PureComponent {
   scrollToTopOnMouseIdle = false;
 
   setScrollTop = newScrollTop => {
-    if (this.node.scrollTop !== newScrollTop) {
+    if (this.getScrollTop() !== newScrollTop) {
       this.lastScrollWasSynthetic = true;
-      this.node.scrollTop = newScrollTop;
+
+      if (this.props.bindToDocument) {
+        document.scrollingElement.scrollTop = newScrollTop;
+      } else {
+        this.node.scrollTop = newScrollTop;
+      }
     }
   };
 
@@ -97,7 +108,7 @@ export default class ScrollableList extends PureComponent {
     this.clearMouseIdleTimer();
     this.mouseIdleTimer = setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY);
 
-    if (!this.mouseMovedRecently && this.node.scrollTop === 0) {
+    if (!this.mouseMovedRecently && this.getScrollTop() === 0) {
       // Only set if we just started moving and are scrolled to the top.
       this.scrollToTopOnMouseIdle = true;
     }
@@ -132,15 +143,27 @@ export default class ScrollableList extends PureComponent {
   }
 
   getScrollPosition = () => {
-    if (this.node && (this.node.scrollTop > 0 || this.mouseMovedRecently)) {
-      return { height: this.node.scrollHeight, top: this.node.scrollTop };
+    if (this.node && (this.getScrollTop() > 0 || this.mouseMovedRecently)) {
+      return { height: this.getScrollHeight(), top: this.getScrollTop() };
     } else {
       return null;
     }
   }
 
+  getScrollTop = () => {
+    return this.props.bindToDocument ? document.scrollingElement.scrollTop : this.node.scrollTop;
+  }
+
+  getScrollHeight = () => {
+    return this.props.bindToDocument ? document.scrollingElement.scrollHeight : this.node.scrollHeight;
+  }
+
+  getClientHeight = () => {
+    return this.props.bindToDocument ? document.scrollingElement.clientHeight : this.node.clientHeight;
+  }
+
   updateScrollBottom = (snapshot) => {
-    const newScrollTop = this.node.scrollHeight - snapshot;
+    const newScrollTop = this.getScrollHeight() - snapshot;
 
     this.setScrollTop(newScrollTop);
   }
@@ -149,9 +172,10 @@ export default class ScrollableList extends PureComponent {
     const someItemInserted = React.Children.count(prevProps.children) > 0 &&
       React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
       this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
+    const pendingChanged = (prevProps.numPending > 0) !== (this.props.numPending > 0);
 
-    if (someItemInserted && (this.node.scrollTop > 0 || this.mouseMovedRecently)) {
-      return this.node.scrollHeight - this.node.scrollTop;
+    if (pendingChanged || someItemInserted && (this.getScrollTop() > 0 || this.mouseMovedRecently)) {
+      return this.getScrollHeight() - this.getScrollTop();
     } else {
       return null;
     }
@@ -161,7 +185,7 @@ export default class ScrollableList extends PureComponent {
     // Reset the scroll position when a new child comes in in order not to
     // jerk the scrollbar around if you're already scrolled down the page.
     if (snapshot !== null) {
-      this.setScrollTop(this.node.scrollHeight - snapshot);
+      this.setScrollTop(this.getScrollHeight() - snapshot);
     }
   }
 
@@ -175,6 +199,7 @@ export default class ScrollableList extends PureComponent {
     this.clearMouseIdleTimer();
     this.detachScrollListener();
     this.detachIntersectionObserver();
+
     detachFullscreenListener(this.onFullScreenChange);
   }
 
@@ -194,13 +219,23 @@ export default class ScrollableList extends PureComponent {
   }
 
   attachScrollListener () {
-    this.node.addEventListener('scroll', this.handleScroll);
-    this.node.addEventListener('wheel', this.handleWheel);
+    if (this.props.bindToDocument) {
+      document.addEventListener('scroll', this.handleScroll);
+      document.addEventListener('wheel', this.handleWheel);
+    } else {
+      this.node.addEventListener('scroll', this.handleScroll);
+      this.node.addEventListener('wheel', this.handleWheel);
+    }
   }
 
   detachScrollListener () {
-    this.node.removeEventListener('scroll', this.handleScroll);
-    this.node.removeEventListener('wheel', this.handleWheel);
+    if (this.props.bindToDocument) {
+      document.removeEventListener('scroll', this.handleScroll);
+      document.removeEventListener('wheel', this.handleWheel);
+    } else {
+      this.node.removeEventListener('scroll', this.handleScroll);
+      this.node.removeEventListener('wheel', this.handleWheel);
+    }
   }
 
   getFirstChildKey (props) {
@@ -225,12 +260,25 @@ export default class ScrollableList extends PureComponent {
     this.props.onLoadMore();
   }
 
+  handleLoadPending = e => {
+    e.preventDefault();
+    this.props.onLoadPending();
+    // Prevent the weird scroll-jumping behavior, as we explicitly don't want to
+    // scroll to top, and we know the scroll height is going to change
+    this.scrollToTopOnMouseIdle = false;
+    this.lastScrollWasSynthetic = false;
+    this.clearMouseIdleTimer();
+    this.mouseIdleTimer = setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY);
+    this.mouseMovedRecently = true;
+  }
+
   render () {
-    const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, prepend, alwaysPrepend, emptyMessage, onLoadMore } = this.props;
+    const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, emptyMessage, onLoadMore } = this.props;
     const { fullscreen } = this.state;
     const childrenCount = React.Children.count(children);
 
     const loadMore     = (hasMore && onLoadMore) ? <LoadMore visible={!isLoading} onClick={this.handleLoadMore} /> : null;
+    const loadPending  = (numPending > 0) ? <LoadPending count={numPending} onClick={this.handleLoadPending} /> : null;
     let scrollableArea = null;
 
     if (showLoading) {
@@ -251,6 +299,8 @@ export default class ScrollableList extends PureComponent {
           <div role='feed' className='item-list'>
             {prepend}
 
+            {loadPending}
+
             {React.Children.map(this.props.children, (child, index) => (
               <IntersectionObserverArticleContainer
                 key={child.key}
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index 9b1035649d8b4ed6d0c8d6f7fb0f688933539cf7..b5606aca5a86578624b8e7a036443ee81382cefa 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -12,7 +12,7 @@ import AttachmentList from './attachment_list';
 import Card from '../features/status/components/card';
 import { injectIntl, FormattedMessage } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
-import { MediaGallery, Video } from '../features/ui/util/async-components';
+import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
 import { HotKeys } from 'react-hotkeys';
 import classNames from 'classnames';
 import Icon from 'mastodon/components/icon';
@@ -199,11 +199,15 @@ class Status extends ImmutablePureComponent {
   };
 
   renderLoadingMediaGallery () {
-    return <div className='media_gallery' style={{ height: '110px' }} />;
+    return <div className='media-gallery' style={{ height: '110px' }} />;
   }
 
   renderLoadingVideoPlayer () {
-    return <div className='media-spoiler-video' style={{ height: '110px' }} />;
+    return <div className='video-player' style={{ height: '110px' }} />;
+  }
+
+  renderLoadingAudioPlayer () {
+    return <div className='audio-player' style={{ height: '110px' }} />;
   }
 
   handleOpenVideo = (media, startTime) => {
@@ -278,12 +282,27 @@ class Status extends ImmutablePureComponent {
       return null;
     }
 
+    const handlers = this.props.muted ? {} : {
+      reply: this.handleHotkeyReply,
+      favourite: this.handleHotkeyFavourite,
+      boost: this.handleHotkeyBoost,
+      mention: this.handleHotkeyMention,
+      open: this.handleHotkeyOpen,
+      openProfile: this.handleHotkeyOpenProfile,
+      moveUp: this.handleHotkeyMoveUp,
+      moveDown: this.handleHotkeyMoveDown,
+      toggleHidden: this.handleHotkeyToggleHidden,
+      toggleSensitive: this.handleHotkeyToggleSensitive,
+    };
+
     if (hidden) {
       return (
-        <div ref={this.handleRef}>
-          {status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
-          {status.get('content')}
-        </div>
+        <HotKeys handlers={handlers}>
+          <div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex='0'>
+            {status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
+            {status.get('content')}
+          </div>
+        </HotKeys>
       );
     }
 
@@ -333,7 +352,23 @@ class Status extends ImmutablePureComponent {
             media={status.get('media_attachments')}
           />
         );
-      } else if (['video', 'audio'].includes(status.getIn(['media_attachments', 0, 'type']))) {
+      } else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
+        const attachment = status.getIn(['media_attachments', 0]);
+
+        media = (
+          <Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
+            {Component => (
+              <Component
+                src={attachment.get('url')}
+                alt={attachment.get('description')}
+                duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
+                peaks={[0]}
+                height={70}
+              />
+            )}
+          </Bundle>
+        );
+      } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
         const attachment = status.getIn(['media_attachments', 0]);
 
         media = (
@@ -394,19 +429,6 @@ class Status extends ImmutablePureComponent {
       statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
     }
 
-    const handlers = this.props.muted ? {} : {
-      reply: this.handleHotkeyReply,
-      favourite: this.handleHotkeyFavourite,
-      boost: this.handleHotkeyBoost,
-      mention: this.handleHotkeyMention,
-      open: this.handleHotkeyOpen,
-      openProfile: this.handleHotkeyOpenProfile,
-      moveUp: this.handleHotkeyMoveUp,
-      moveDown: this.handleHotkeyMoveDown,
-      toggleHidden: this.handleHotkeyToggleHidden,
-      toggleSensitive: this.handleHotkeyToggleSensitive,
-    };
-
     return (
       <HotKeys handlers={handlers}>
         <div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index ba6d3c296261d0e783dd7c7135b22a15e5a3cada..4ce9ec49f3374cc5780b472410735c9f8eb4123b 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -55,6 +55,7 @@ export default class StatusContent extends React.PureComponent {
         link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
       } else {
         link.setAttribute('title', link.href);
+        link.classList.add('unhandled-link');
       }
 
       link.setAttribute('target', '_blank');
@@ -111,7 +112,7 @@ export default class StatusContent extends React.PureComponent {
   }
 
   onHashtagClick = (hashtag, e) => {
-    hashtag = hashtag.replace(/^#/, '').toLowerCase();
+    hashtag = hashtag.replace(/^#/, '');
 
     if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       e.preventDefault();
@@ -215,22 +216,22 @@ export default class StatusContent extends React.PureComponent {
       return (
         <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
           <p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
-            <span dangerouslySetInnerHTML={spoilerContent} lang={status.get('language')} />
+            <span dangerouslySetInnerHTML={spoilerContent} />
             {' '}
             <button tabIndex='0' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick}>{toggleText}</button>
           </p>
 
           {mentionsPlaceholder}
 
-          <div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
+          <div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
 
           {!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
         </div>
       );
     } else if (this.props.onClick) {
       const output = [
-        <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
-          <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
+        <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content'>
+          <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
 
           {!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
         </div>,
@@ -244,7 +245,7 @@ export default class StatusContent extends React.PureComponent {
     } else {
       return (
         <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle}>
-          <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
+          <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
 
           {!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
         </div>
diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js
index 745e6422d3d86893a3dc5b92365f87b312f21a1b..e1b370c913c4bbd101783ebb8b212e94e4561dcc 100644
--- a/app/javascript/mastodon/components/status_list.js
+++ b/app/javascript/mastodon/components/status_list.js
@@ -1,12 +1,12 @@
 import { debounce } from 'lodash';
 import React from 'react';
-import { FormattedMessage } from 'react-intl';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import StatusContainer from '../containers/status_container';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import LoadGap from './load_gap';
 import ScrollableList from './scrollable_list';
+import RegenerationIndicator from 'mastodon/components/regeneration_indicator';
 
 export default class StatusList extends ImmutablePureComponent {
 
@@ -81,18 +81,7 @@ export default class StatusList extends ImmutablePureComponent {
     const { isLoading, isPartial } = other;
 
     if (isPartial) {
-      return (
-        <div className='regeneration-indicator'>
-          <div>
-            <div className='regeneration-indicator__figure' />
-
-            <div className='regeneration-indicator__label'>
-              <FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading&hellip;' />
-              <FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
-            </div>
-          </div>
-        </div>
-      );
+      return <RegenerationIndicator />;
     }
 
     let scrollableContent = (isLoading || statusIds.size > 0) ? (
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
index 542b682821ee5cf9b2c9ec8ce177b34c63cca6c9..3ac58cf7c5e6785e597d59c6ae2f9b1005f94acc 100644
--- a/app/javascript/mastodon/containers/mastodon.js
+++ b/app/javascript/mastodon/containers/mastodon.js
@@ -12,6 +12,8 @@ import { hydrateStore } from '../actions/store';
 import { connectUserStream } from '../actions/streaming';
 import { IntlProvider, addLocaleData } from 'react-intl';
 import { getLocale } from '../locales';
+import { previewState as previewMediaState } from 'mastodon/features/ui/components/media_modal';
+import { previewState as previewVideoState } from 'mastodon/features/ui/components/video_modal';
 import initialState from '../initial_state';
 import ErrorBoundary from '../components/error_boundary';
 
@@ -35,6 +37,10 @@ class MastodonMount extends React.PureComponent {
     showIntroduction: PropTypes.bool,
   };
 
+  shouldUpdateScroll (_, { location }) {
+    return location.state !== previewMediaState && location.state !== previewVideoState;
+  }
+
   render () {
     const { showIntroduction } = this.props;
 
@@ -44,7 +50,7 @@ class MastodonMount extends React.PureComponent {
 
     return (
       <BrowserRouter basename='/web'>
-        <ScrollContext>
+        <ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
           <Route path='/' component={UI} />
         </ScrollContext>
       </BrowserRouter>
diff --git a/app/javascript/mastodon/containers/media_container.js b/app/javascript/mastodon/containers/media_container.js
index 51d4f0fed7a2522bd6a3c38d4d98d9b91ed4b35d..ba55ecbc7ba8f42227066f48e74685c1e60915f4 100644
--- a/app/javascript/mastodon/containers/media_container.js
+++ b/app/javascript/mastodon/containers/media_container.js
@@ -2,19 +2,22 @@ import React, { PureComponent, Fragment } from 'react';
 import ReactDOM from 'react-dom';
 import PropTypes from 'prop-types';
 import { IntlProvider, addLocaleData } from 'react-intl';
-import { getLocale } from '../locales';
-import MediaGallery from '../components/media_gallery';
-import Video from '../features/video';
-import Card from '../features/status/components/card';
-import Poll from 'mastodon/components/poll';
-import ModalRoot from '../components/modal_root';
-import MediaModal from '../features/ui/components/media_modal';
 import { List as ImmutableList, fromJS } from 'immutable';
+import { getLocale } from 'mastodon/locales';
+import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
+import MediaGallery from 'mastodon/components/media_gallery';
+import Poll from 'mastodon/components/poll';
+import Hashtag from 'mastodon/components/hashtag';
+import ModalRoot from 'mastodon/components/modal_root';
+import MediaModal from 'mastodon/features/ui/components/media_modal';
+import Video from 'mastodon/features/video';
+import Card from 'mastodon/features/status/components/card';
+import Audio from 'mastodon/features/audio';
 
 const { localeData, messages } = getLocale();
 addLocaleData(localeData);
 
-const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll };
+const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio };
 
 export default class MediaContainer extends PureComponent {
 
@@ -31,6 +34,8 @@ export default class MediaContainer extends PureComponent {
 
   handleOpenMedia = (media, index) => {
     document.body.classList.add('with-modals--active');
+    document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
+
     this.setState({ media, index });
   }
 
@@ -38,11 +43,15 @@ export default class MediaContainer extends PureComponent {
     const media = ImmutableList([video]);
 
     document.body.classList.add('with-modals--active');
+    document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
+
     this.setState({ media, time });
   }
 
   handleCloseMedia = () => {
     document.body.classList.remove('with-modals--active');
+    document.documentElement.style.marginRight = 0;
+
     this.setState({ media: null, index: null, time: null });
   }
 
@@ -55,12 +64,13 @@ export default class MediaContainer extends PureComponent {
           {[].map.call(components, (component, i) => {
             const componentName = component.getAttribute('data-component');
             const Component = MEDIA_COMPONENTS[componentName];
-            const { media, card, poll, ...props } = JSON.parse(component.getAttribute('data-props'));
+            const { media, card, poll, hashtag, ...props } = JSON.parse(component.getAttribute('data-props'));
 
             Object.assign(props, {
-              ...(media ? { media: fromJS(media) } : {}),
-              ...(card  ? { card:  fromJS(card)  } : {}),
-              ...(poll  ? { poll:  fromJS(poll)  } : {}),
+              ...(media   ? { media:   fromJS(media)   } : {}),
+              ...(card    ? { card:    fromJS(card)    } : {}),
+              ...(poll    ? { poll:    fromJS(poll)    } : {}),
+              ...(hashtag ? { hashtag: fromJS(hashtag) } : {}),
 
               ...(componentName === 'Video' ? {
                 onOpenVideo: this.handleOpenVideo,
@@ -74,6 +84,7 @@ export default class MediaContainer extends PureComponent {
               component,
             );
           })}
+
           <ModalRoot onClose={this.handleCloseMedia}>
             {this.state.media && (
               <MediaModal
diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js
index 86324b846811df4cd8a4b6b1a32deebbf301c552..fb22676e045281485331a7db1f8c11461fc03de9 100644
--- a/app/javascript/mastodon/containers/status_container.js
+++ b/app/javascript/mastodon/containers/status_container.js
@@ -1,4 +1,3 @@
-import React from 'react';
 import { connect } from 'react-redux';
 import Status from '../components/status';
 import { makeGetStatus } from '../selectors';
@@ -15,7 +14,6 @@ import {
   pin,
   unpin,
 } from '../actions/interactions';
-import { blockAccount } from '../actions/accounts';
 import {
   muteStatus,
   unmuteStatus,
@@ -24,9 +22,10 @@ import {
   revealStatus,
 } from '../actions/statuses';
 import { initMuteModal } from '../actions/mutes';
+import { initBlockModal } from '../actions/blocks';
 import { initReport } from '../actions/reports';
 import { openModal } from '../actions/modal';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
 import { boostModal, deleteModal } from '../initial_state';
 import { showAlertForError } from '../actions/alerts';
 
@@ -35,10 +34,8 @@ const messages = defineMessages({
   deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
   redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
   redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
-  blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
   replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
   replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
-  blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
 });
 
 const makeMapStateToProps = () => {
@@ -56,6 +53,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
   onReply (status, router) {
     dispatch((_, getState) => {
       let state = getState();
+
       if (state.getIn(['compose', 'text']).trim().length !== 0) {
         dispatch(openModal('CONFIRM', {
           message: intl.formatMessage(messages.replyMessage),
@@ -77,7 +75,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
   },
 
   onReblog (status, e) {
-    if (e.shiftKey || !boostModal) {
+    if ((e && e.shiftKey) || !boostModal) {
       this.onModalReblog(status);
     } else {
       dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
@@ -137,16 +135,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 
   onBlock (status) {
     const account = status.get('account');
-    dispatch(openModal('CONFIRM', {
-      message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
-      confirm: intl.formatMessage(messages.blockConfirm),
-      onConfirm: () => dispatch(blockAccount(account.get('id'))),
-      secondary: intl.formatMessage(messages.blockAndReport),
-      onSecondary: () => {
-        dispatch(blockAccount(account.get('id')));
-        dispatch(initReport(account, status));
-      },
-    }));
+    dispatch(initBlockModal(account));
   },
 
   onReport (status) {
diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.js
index 2609b96ffca1b362b6741e6acea73073e57ef7b8..b6eec22439663a83e58efd0b40f4f71210fcda6d 100644
--- a/app/javascript/mastodon/features/account_gallery/components/media_item.js
+++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js
@@ -96,6 +96,12 @@ export default class MediaItem extends ImmutablePureComponent {
 
     if (attachment.get('type') === 'unknown') {
       // Skip
+    } else if (attachment.get('type') === 'audio') {
+      thumbnail = (
+        <span className='account-gallery__item__icons'>
+          <Icon id='music' />
+        </span>
+      );
     } else if (attachment.get('type') === 'image') {
       const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
       const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
@@ -113,6 +119,7 @@ export default class MediaItem extends ImmutablePureComponent {
       );
     } else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) {
       const autoPlay = !isIOS() && autoPlayGif;
+      const label    = attachment.get('type') === 'video' ? <Icon id='play' /> : 'GIF';
 
       thumbnail = (
         <div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
@@ -129,7 +136,7 @@ export default class MediaItem extends ImmutablePureComponent {
             muted
           />
 
-          <span className='media-gallery__gifv__label'>GIF</span>
+          <span className='media-gallery__gifv__label'>{label}</span>
         </div>
       );
     }
diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js
index 5d6a53e18eb9b6f39bfc5b41ef506ca6e5c458d4..de481075c3c13ca1ed53314c02c252ff3c353749 100644
--- a/app/javascript/mastodon/features/account_gallery/index.js
+++ b/app/javascript/mastodon/features/account_gallery/index.js
@@ -56,6 +56,7 @@ class AccountGallery extends ImmutablePureComponent {
     isLoading: PropTypes.bool,
     hasMore: PropTypes.bool,
     isAccount: PropTypes.bool,
+    multiColumn: PropTypes.bool,
   };
 
   state = {
@@ -101,6 +102,8 @@ class AccountGallery extends ImmutablePureComponent {
   handleOpenMedia = attachment => {
     if (attachment.get('type') === 'video') {
       this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status') }));
+    } else if (attachment.get('type') === 'audio') {
+      this.props.dispatch(openModal('AUDIO', { media: attachment, status: attachment.get('status') }));
     } else {
       const media = attachment.getIn(['status', 'media_attachments']);
       const index = media.findIndex(x => x.get('id') === attachment.get('id'));
@@ -116,7 +119,7 @@ class AccountGallery extends ImmutablePureComponent {
   }
 
   render () {
-    const { attachments, shouldUpdateScroll, isLoading, hasMore, isAccount } = this.props;
+    const { attachments, shouldUpdateScroll, isLoading, hasMore, isAccount, multiColumn } = this.props;
     const { width } = this.state;
 
     if (!isAccount) {
@@ -143,7 +146,7 @@ class AccountGallery extends ImmutablePureComponent {
 
     return (
       <Column>
-        <ColumnBackButton />
+        <ColumnBackButton multiColumn={multiColumn} />
 
         <ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={shouldUpdateScroll}>
           <div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
index 4d4ae6e8243e1024c41cba8375bc4ef3f4f8fb51..8728b48068a9baa4ddde47f01cbd4ed39fde8798 100644
--- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js
+++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
@@ -5,7 +5,6 @@ import Header from '../components/header';
 import {
   followAccount,
   unfollowAccount,
-  blockAccount,
   unblockAccount,
   unmuteAccount,
   pinAccount,
@@ -16,6 +15,7 @@ import {
   directCompose,
 } from '../../../actions/compose';
 import { initMuteModal } from '../../../actions/mutes';
+import { initBlockModal } from '../../../actions/blocks';
 import { initReport } from '../../../actions/reports';
 import { openModal } from '../../../actions/modal';
 import { blockDomain, unblockDomain } from '../../../actions/domain_blocks';
@@ -25,9 +25,7 @@ import { List as ImmutableList } from 'immutable';
 
 const messages = defineMessages({
   unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
-  blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
   blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
-  blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
 });
 
 const makeMapStateToProps = () => {
@@ -64,16 +62,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
     if (account.getIn(['relationship', 'blocking'])) {
       dispatch(unblockAccount(account.get('id')));
     } else {
-      dispatch(openModal('CONFIRM', {
-        message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
-        confirm: intl.formatMessage(messages.blockConfirm),
-        onConfirm: () => dispatch(blockAccount(account.get('id'))),
-        secondary: intl.formatMessage(messages.blockAndReport),
-        onSecondary: () => {
-          dispatch(blockAccount(account.get('id')));
-          dispatch(initReport(account));
-        },
-      }));
+      dispatch(initBlockModal(account));
     }
   },
 
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js
index 27581bfdc887a3aa3fc63ff69c390568b1e1434a..8d0cbe5a197b1f8331a85af1bd60191199932848 100644
--- a/app/javascript/mastodon/features/account_timeline/index.js
+++ b/app/javascript/mastodon/features/account_timeline/index.js
@@ -44,6 +44,7 @@ class AccountTimeline extends ImmutablePureComponent {
     withReplies: PropTypes.bool,
     blockedBy: PropTypes.bool,
     isAccount: PropTypes.bool,
+    multiColumn: PropTypes.bool,
   };
 
   componentWillMount () {
@@ -77,11 +78,12 @@ class AccountTimeline extends ImmutablePureComponent {
   }
 
   render () {
-    const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, isAccount } = this.props;
+    const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, isAccount, multiColumn } = this.props;
 
     if (!isAccount) {
       return (
         <Column>
+          <ColumnBackButton multiColumn={multiColumn} />
           <MissingIndicator />
         </Column>
       );
@@ -99,7 +101,7 @@ class AccountTimeline extends ImmutablePureComponent {
 
     return (
       <Column>
-        <ColumnBackButton />
+        <ColumnBackButton multiColumn={multiColumn} />
 
         <StatusList
           prepend={<HeaderContainer accountId={this.props.params.accountId} />}
@@ -112,6 +114,7 @@ class AccountTimeline extends ImmutablePureComponent {
           onLoadMore={this.handleLoadMore}
           shouldUpdateScroll={shouldUpdateScroll}
           emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
         />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/audio/index.js b/app/javascript/mastodon/features/audio/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..95e5675f3cf12e6adc72bdad9483fe4742bb6daf
--- /dev/null
+++ b/app/javascript/mastodon/features/audio/index.js
@@ -0,0 +1,226 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import WaveSurfer from 'wavesurfer.js';
+import { defineMessages, injectIntl } from 'react-intl';
+import { formatTime } from 'mastodon/features/video';
+import Icon from 'mastodon/components/icon';
+import classNames from 'classnames';
+import { throttle } from 'lodash';
+
+const messages = defineMessages({
+  play: { id: 'video.play', defaultMessage: 'Play' },
+  pause: { id: 'video.pause', defaultMessage: 'Pause' },
+  mute: { id: 'video.mute', defaultMessage: 'Mute sound' },
+  unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' },
+});
+
+export default @injectIntl
+class Audio extends React.PureComponent {
+
+  static propTypes = {
+    src: PropTypes.string.isRequired,
+    alt: PropTypes.string,
+    duration: PropTypes.number,
+    peaks: PropTypes.arrayOf(PropTypes.number),
+    height: PropTypes.number,
+    preload: PropTypes.bool,
+    editable: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+  };
+
+  state = {
+    currentTime: 0,
+    duration: null,
+    paused: true,
+    muted: false,
+    volume: 0.5,
+  };
+
+  // hard coded in components.scss
+  // any way to get ::before values programatically?
+
+  volWidth = 50;
+
+  volOffset = 70;
+
+  volHandleOffset = v => {
+    const offset = v * this.volWidth + this.volOffset;
+    return (offset > 110) ? 110 : offset;
+  }
+
+  setVolumeRef = c => {
+    this.volume = c;
+  }
+
+  setWaveformRef = c => {
+    this.waveform = c;
+  }
+
+  componentDidMount () {
+    if (this.waveform) {
+      this._updateWaveform();
+    }
+  }
+
+  componentDidUpdate (prevProps) {
+    if (this.waveform && prevProps.src !== this.props.src) {
+      this._updateWaveform();
+    }
+  }
+
+  componentWillUnmount () {
+    if (this.wavesurfer) {
+      this.wavesurfer.destroy();
+      this.wavesurfer = null;
+    }
+  }
+
+  _updateWaveform () {
+    const { src, height, duration, peaks, preload } = this.props;
+
+    const progressColor = window.getComputedStyle(document.querySelector('.audio-player__progress-placeholder')).getPropertyValue('background-color');
+    const waveColor     = window.getComputedStyle(document.querySelector('.audio-player__wave-placeholder')).getPropertyValue('background-color');
+
+    if (this.wavesurfer) {
+      this.wavesurfer.destroy();
+      this.loaded = false;
+    }
+
+    const wavesurfer = WaveSurfer.create({
+      container: this.waveform,
+      height,
+      barWidth: 3,
+      cursorWidth: 0,
+      progressColor,
+      waveColor,
+      backend: 'MediaElement',
+      interact: preload,
+    });
+
+    wavesurfer.setVolume(this.state.volume);
+
+    if (preload) {
+      wavesurfer.load(src);
+      this.loaded = true;
+    } else {
+      wavesurfer.load(src, peaks, 'none', duration);
+      this.loaded = false;
+    }
+
+    wavesurfer.on('ready', () => this.setState({ duration: Math.floor(wavesurfer.getDuration()) }));
+    wavesurfer.on('audioprocess', () => this.setState({ currentTime: Math.floor(wavesurfer.getCurrentTime()) }));
+    wavesurfer.on('pause', () => this.setState({ paused: true }));
+    wavesurfer.on('play', () => this.setState({ paused: false }));
+    wavesurfer.on('volume', volume => this.setState({ volume }));
+    wavesurfer.on('mute', muted => this.setState({ muted }));
+
+    this.wavesurfer = wavesurfer;
+  }
+
+  togglePlay = () => {
+    if (this.state.paused) {
+      if (!this.props.preload && !this.loaded) {
+        this.wavesurfer.createBackend();
+        this.wavesurfer.createPeakCache();
+        this.wavesurfer.load(this.props.src);
+        this.wavesurfer.toggleInteraction();
+        this.loaded = true;
+      }
+
+      this.wavesurfer.play();
+      this.setState({ paused: false });
+    } else {
+      this.wavesurfer.pause();
+      this.setState({ paused: true });
+    }
+  }
+
+  toggleMute = () => {
+    this.wavesurfer.setMute(!this.state.muted);
+  }
+
+  handleVolumeMouseDown = e => {
+    document.addEventListener('mousemove', this.handleMouseVolSlide, true);
+    document.addEventListener('mouseup', this.handleVolumeMouseUp, true);
+    document.addEventListener('touchmove', this.handleMouseVolSlide, true);
+    document.addEventListener('touchend', this.handleVolumeMouseUp, true);
+
+    this.handleMouseVolSlide(e);
+
+    e.preventDefault();
+    e.stopPropagation();
+  }
+
+  handleVolumeMouseUp = () => {
+    document.removeEventListener('mousemove', this.handleMouseVolSlide, true);
+    document.removeEventListener('mouseup', this.handleVolumeMouseUp, true);
+    document.removeEventListener('touchmove', this.handleMouseVolSlide, true);
+    document.removeEventListener('touchend', this.handleVolumeMouseUp, true);
+  }
+
+  handleMouseVolSlide = throttle(e => {
+    const rect = this.volume.getBoundingClientRect();
+    const x    = (e.clientX - rect.left) / this.volWidth; // x position within the element.
+
+    if(!isNaN(x)) {
+      let slideamt = x;
+
+      if (x > 1) {
+        slideamt = 1;
+      } else if(x < 0) {
+        slideamt = 0;
+      }
+
+      this.wavesurfer.setVolume(slideamt);
+    }
+  }, 60);
+
+  render () {
+    const { height, intl, alt, editable } = this.props;
+    const { paused, muted, volume, currentTime } = this.state;
+
+    const volumeWidth     = muted ? 0 : volume * this.volWidth;
+    const volumeHandleLoc = muted ? this.volHandleOffset(0) : this.volHandleOffset(volume);
+
+    return (
+      <div className={classNames('audio-player', { editable })}>
+        <div className='audio-player__progress-placeholder' style={{ display: 'none' }} />
+        <div className='audio-player__wave-placeholder' style={{ display: 'none' }} />
+
+        <div
+          className='audio-player__waveform'
+          aria-label={alt}
+          title={alt}
+          style={{ height }}
+          ref={this.setWaveformRef}
+        />
+
+        <div className='video-player__controls active'>
+          <div className='video-player__buttons-bar'>
+            <div className='video-player__buttons left'>
+              <button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
+              <button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
+
+              <div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
+                <div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
+
+                <span
+                  className={classNames('video-player__volume__handle')}
+                  tabIndex='0'
+                  style={{ left: `${volumeHandleLoc}px` }}
+                />
+              </div>
+
+              <span>
+                <span className='video-player__time-current'>{formatTime(currentTime)}</span>
+                <span className='video-player__time-sep'>/</span>
+                <span className='video-player__time-total'>{formatTime(this.state.duration || Math.floor(this.props.duration))}</span>
+              </span>
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/blocks/index.js b/app/javascript/mastodon/features/blocks/index.js
index 96a219c94720b2d34f635c1305e807aa7c2976eb..051431ed2933a138fe522c0c466701e48ec05020 100644
--- a/app/javascript/mastodon/features/blocks/index.js
+++ b/app/javascript/mastodon/features/blocks/index.js
@@ -32,6 +32,7 @@ class Blocks extends ImmutablePureComponent {
     accountIds: ImmutablePropTypes.list,
     hasMore: PropTypes.bool,
     intl: PropTypes.object.isRequired,
+    multiColumn: PropTypes.bool,
   };
 
   componentWillMount () {
@@ -43,7 +44,7 @@ class Blocks extends ImmutablePureComponent {
   }, 300, { leading: true });
 
   render () {
-    const { intl, accountIds, shouldUpdateScroll, hasMore } = this.props;
+    const { intl, accountIds, shouldUpdateScroll, hasMore, multiColumn } = this.props;
 
     if (!accountIds) {
       return (
@@ -56,7 +57,7 @@ class Blocks extends ImmutablePureComponent {
     const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
 
     return (
-      <Column icon='ban' heading={intl.formatMessage(messages.heading)}>
+      <Column bindToDocument={!multiColumn} icon='ban' heading={intl.formatMessage(messages.heading)}>
         <ColumnBackButtonSlim />
         <ScrollableList
           scrollKey='blocks'
@@ -64,6 +65,7 @@ class Blocks extends ImmutablePureComponent {
           hasMore={hasMore}
           shouldUpdateScroll={shouldUpdateScroll}
           emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
         >
           {accountIds.map(id =>
             <AccountContainer key={id} id={id} />
diff --git a/app/javascript/mastodon/features/community_timeline/components/column_settings.js b/app/javascript/mastodon/features/community_timeline/components/column_settings.js
index 8250190a7adf1ebbc6aa88a24d22de8392d7cd7a..0cb6db8836b9bda09fcd78e3f89c80b789170322 100644
--- a/app/javascript/mastodon/features/community_timeline/components/column_settings.js
+++ b/app/javascript/mastodon/features/community_timeline/components/column_settings.js
@@ -20,7 +20,7 @@ class ColumnSettings extends React.PureComponent {
     return (
       <div>
         <div className='column-settings__row'>
-          <SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media Only' />} />
+          <SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js
index 7d26c98b0d30de457d75ddc02bed8ea626c7d609..30153cc15376000459beefd13247f1e4d5ce52ff 100644
--- a/app/javascript/mastodon/features/community_timeline/index.js
+++ b/app/javascript/mastodon/features/community_timeline/index.js
@@ -18,9 +18,10 @@ const mapStateToProps = (state, { onlyMedia, columnId }) => {
   const uuid = columnId;
   const columns = state.getIn(['settings', 'columns']);
   const index = columns.findIndex(c => c.get('uuid') === uuid);
+  const timelineState = state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`]);
 
   return {
-    hasUnread: state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'unread']) > 0,
+    hasUnread: !!timelineState && (timelineState.get('unread') > 0 || timelineState.get('pendingItems').size > 0),
     onlyMedia: (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'community', 'other', 'onlyMedia']),
   };
 };
@@ -105,7 +106,7 @@ class CommunityTimeline extends React.PureComponent {
     const pinned = !!columnId;
 
     return (
-      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
         <ColumnHeader
           icon='users'
           active={hasUnread}
@@ -126,6 +127,7 @@ class CommunityTimeline extends React.PureComponent {
           onLoadMore={this.handleLoadMore}
           emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
           shouldUpdateScroll={shouldUpdateScroll}
+          bindToDocument={!multiColumn}
         />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/compose/components/action_bar.js b/app/javascript/mastodon/features/compose/components/action_bar.js
index d0303dbfbbf9007d0985bcdb16e7931fe19da9f1..dd2632796c033ca9d5497cf9518b11df16f584be 100644
--- a/app/javascript/mastodon/features/compose/components/action_bar.js
+++ b/app/javascript/mastodon/features/compose/components/action_bar.js
@@ -23,9 +23,14 @@ class ActionBar extends React.PureComponent {
 
   static propTypes = {
     account: ImmutablePropTypes.map.isRequired,
+    onLogout: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
   };
 
+  handleLogout = () => {
+    this.props.onLogout();
+  }
+
   render () {
     const { intl } = this.props;
 
@@ -44,7 +49,7 @@ class ActionBar extends React.PureComponent {
     menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
     menu.push({ text: intl.formatMessage(messages.filters), href: '/filters' });
     menu.push(null);
-    menu.push({ text: intl.formatMessage(messages.logout), href: '/auth/sign_out', target: null, method: 'delete' });
+    menu.push({ text: intl.formatMessage(messages.logout), action: this.handleLogout });
 
     return (
       <div className='compose__action-bar'>
diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
index c1429c756fdbefb2e802592c543fb418a5adb951..e57c3c20c33309348f6023a3d8ccc6b3b9238c55 100644
--- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
@@ -6,7 +6,7 @@ import Overlay from 'react-overlays/lib/Overlay';
 import classNames from 'classnames';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import detectPassiveEvents from 'detect-passive-events';
-import { buildCustomEmojis } from '../../emoji/emoji';
+import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji';
 
 const messages = defineMessages({
   emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
@@ -31,19 +31,6 @@ let EmojiPicker, Emoji; // load asynchronously
 const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`;
 const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
 
-const categoriesSort = [
-  'recent',
-  'custom',
-  'people',
-  'nature',
-  'foods',
-  'activity',
-  'places',
-  'objects',
-  'symbols',
-  'flags',
-];
-
 class ModifierPickerMenu extends React.PureComponent {
 
   static propTypes = {
@@ -241,8 +228,23 @@ class EmojiPickerMenu extends React.PureComponent {
     }
 
     const title = intl.formatMessage(messages.emoji);
+
     const { modifierOpen } = this.state;
 
+    const categoriesSort = [
+      'recent',
+      'people',
+      'nature',
+      'foods',
+      'activity',
+      'places',
+      'objects',
+      'symbols',
+      'flags',
+    ];
+
+    categoriesSort.splice(1, 0, ...Array.from(categoriesFromEmojis(custom_emojis)).sort());
+
     return (
       <div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
         <EmojiPicker
diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.js b/app/javascript/mastodon/features/compose/components/navigation_bar.js
index d8d49cb95cd5391409374d165f43b8f73abe1972..840d0a3da3abbc0b1c02fbb5db7c4841575ccaf1 100644
--- a/app/javascript/mastodon/features/compose/components/navigation_bar.js
+++ b/app/javascript/mastodon/features/compose/components/navigation_bar.js
@@ -12,6 +12,7 @@ export default class NavigationBar extends ImmutablePureComponent {
 
   static propTypes = {
     account: ImmutablePropTypes.map.isRequired,
+    onLogout: PropTypes.func.isRequired,
     onClose: PropTypes.func,
   };
 
@@ -33,7 +34,7 @@ export default class NavigationBar extends ImmutablePureComponent {
 
         <div className='navigation-bar__actions'>
           <IconButton className='close' title='' icon='close' onClick={this.props.onClose} />
-          <ActionBar account={this.props.account} />
+          <ActionBar account={this.props.account} onLogout={this.props.onLogout} />
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js
index 7f9edfeee30e75c2b1ad1fb49f390460c1900325..3e36a922be674fe738746941d38b4b7d9ce0d6de 100644
--- a/app/javascript/mastodon/features/compose/components/search.js
+++ b/app/javascript/mastodon/features/compose/components/search.js
@@ -60,12 +60,17 @@ class Search extends React.PureComponent {
     onShow: PropTypes.func.isRequired,
     openInRoute: PropTypes.bool,
     intl: PropTypes.object.isRequired,
+    singleColumn: PropTypes.bool,
   };
 
   state = {
     expanded: false,
   };
 
+  setRef = c => {
+    this.searchForm = c;
+  }
+
   handleChange = (e) => {
     this.props.onChange(e.target.value);
   }
@@ -95,6 +100,13 @@ class Search extends React.PureComponent {
   handleFocus = () => {
     this.setState({ expanded: true });
     this.props.onShow();
+
+    if (this.searchForm && !this.props.singleColumn) {
+      const { left, right } = this.searchForm.getBoundingClientRect();
+      if (left < 0 || right > (window.innerWidth || document.documentElement.clientWidth)) {
+        this.searchForm.scrollIntoView();
+      }
+    }
   }
 
   handleBlur = () => {
@@ -111,6 +123,7 @@ class Search extends React.PureComponent {
         <label>
           <span style={{ display: 'none' }}>{intl.formatMessage(messages.placeholder)}</span>
           <input
+            ref={this.setRef}
             className='search__input'
             type='text'
             placeholder={intl.formatMessage(messages.placeholder)}
diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js
index 2f338dd2469a49856d8c66b96e55e4591e46d7d6..c85031519b6164c072cf228c9c58bc8a23ef85ba 100644
--- a/app/javascript/mastodon/features/compose/components/search_results.js
+++ b/app/javascript/mastodon/features/compose/components/search_results.js
@@ -8,6 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import Hashtag from '../../../components/hashtag';
 import Icon from 'mastodon/components/icon';
 import { searchEnabled } from '../../../initial_state';
+import LoadMore from 'mastodon/components/load_more';
 
 const messages = defineMessages({
   dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
@@ -20,15 +21,24 @@ class SearchResults extends ImmutablePureComponent {
     results: ImmutablePropTypes.map.isRequired,
     suggestions: ImmutablePropTypes.list.isRequired,
     fetchSuggestions: PropTypes.func.isRequired,
+    expandSearch: PropTypes.func.isRequired,
     dismissSuggestion: PropTypes.func.isRequired,
     searchTerm: PropTypes.string,
     intl: PropTypes.object.isRequired,
   };
 
   componentDidMount () {
-    this.props.fetchSuggestions();
+    if (this.props.searchTerm === '') {
+      this.props.fetchSuggestions();
+    }
   }
 
+  handleLoadMoreAccounts = () => this.props.expandSearch('accounts');
+
+  handleLoadMoreStatuses = () => this.props.expandSearch('statuses');
+
+  handleLoadMoreHashtags = () => this.props.expandSearch('hashtags');
+
   render () {
     const { intl, results, suggestions, dismissSuggestion, searchTerm } = this.props;
 
@@ -65,6 +75,8 @@ class SearchResults extends ImmutablePureComponent {
           <h5><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5>
 
           {results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
+
+          {results.get('accounts').size >= 5 && <LoadMore visible onClick={this.handleLoadMoreAccounts} />}
         </div>
       );
     }
@@ -76,6 +88,18 @@ class SearchResults extends ImmutablePureComponent {
           <h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
 
           {results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
+
+          {results.get('statuses').size >= 5 && <LoadMore visible onClick={this.handleLoadMoreStatuses} />}
+        </div>
+      );
+    } else if(results.get('statuses') && results.get('statuses').size === 0 && !searchEnabled && !(searchTerm.startsWith('@') || searchTerm.startsWith('#') || searchTerm.includes(' '))) {
+      statuses = (
+        <div className='search-results__section'>
+          <h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
+
+          <div className='search-results__info'>
+            <FormattedMessage id='search_results.statuses_fts_disabled' defaultMessage='Searching toots by their content is not enabled on this Mastodon server.' />
+          </div>
         </div>
       );
     } else if(results.get('statuses') && results.get('statuses').size === 0 && !searchEnabled && !(searchTerm.startsWith('@') || searchTerm.startsWith('#') || searchTerm.includes(' '))) {
@@ -97,6 +121,8 @@ class SearchResults extends ImmutablePureComponent {
           <h5><Icon id='hashtag' fixedWidth /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5>
 
           {results.get('hashtags').map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
+
+          {results.get('hashtags').size >= 5 && <LoadMore visible onClick={this.handleLoadMoreHashtags} />}
         </div>
       );
     }
diff --git a/app/javascript/mastodon/features/compose/components/text_icon_button.js b/app/javascript/mastodon/features/compose/components/text_icon_button.js
index 9c8ffab1f93d84fc947d3321e40e4e94c0ce7311..f0b1335386ded402665a932603829dfa50b616b9 100644
--- a/app/javascript/mastodon/features/compose/components/text_icon_button.js
+++ b/app/javascript/mastodon/features/compose/components/text_icon_button.js
@@ -1,6 +1,12 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
+const iconStyle = {
+  height: null,
+  lineHeight: '27px',
+  width: `${18 * 1.28571429}px`,
+};
+
 export default class TextIconButton extends React.PureComponent {
 
   static propTypes = {
@@ -20,7 +26,14 @@ export default class TextIconButton extends React.PureComponent {
     const { label, title, active, ariaControls } = this.props;
 
     return (
-      <button title={title} aria-label={title} className={`text-icon-button ${active ? 'active' : ''}`} aria-expanded={active} onClick={this.handleClick} aria-controls={ariaControls}>
+      <button
+        title={title}
+        aria-label={title}
+        className={`text-icon-button ${active ? 'active' : ''}`}
+        aria-expanded={active}
+        onClick={this.handleClick}
+        aria-controls={ariaControls} style={iconStyle}
+      >
         {label}
       </button>
     );
diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js
index 629cbc36ac34cff657ac860c4db18007ea611219..b9f0fbe3abdf3c0722ba52e8f1f97c78f3262097 100644
--- a/app/javascript/mastodon/features/compose/components/upload.js
+++ b/app/javascript/mastodon/features/compose/components/upload.js
@@ -4,16 +4,11 @@ import PropTypes from 'prop-types';
 import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { FormattedMessage } from 'react-intl';
 import classNames from 'classnames';
 import Icon from 'mastodon/components/icon';
 
-const messages = defineMessages({
-  description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
-});
-
-export default @injectIntl
-class Upload extends ImmutablePureComponent {
+export default class Upload extends ImmutablePureComponent {
 
   static contextTypes = {
     router: PropTypes.object,
@@ -21,30 +16,10 @@ class Upload extends ImmutablePureComponent {
 
   static propTypes = {
     media: ImmutablePropTypes.map.isRequired,
-    intl: PropTypes.object.isRequired,
     onUndo: PropTypes.func.isRequired,
-    onDescriptionChange: PropTypes.func.isRequired,
     onOpenFocalPoint: PropTypes.func.isRequired,
-    onSubmit: PropTypes.func.isRequired,
-  };
-
-  state = {
-    hovered: false,
-    focused: false,
-    dirtyDescription: null,
   };
 
-  handleKeyDown = (e) => {
-    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
-      this.handleSubmit();
-    }
-  }
-
-  handleSubmit = () => {
-    this.handleInputBlur();
-    this.props.onSubmit(this.context.router.history);
-  }
-
   handleUndoClick = e => {
     e.stopPropagation();
     this.props.onUndo(this.props.media.get('id'));
@@ -55,69 +30,21 @@ class Upload extends ImmutablePureComponent {
     this.props.onOpenFocalPoint(this.props.media.get('id'));
   }
 
-  handleInputChange = e => {
-    this.setState({ dirtyDescription: e.target.value });
-  }
-
-  handleMouseEnter = () => {
-    this.setState({ hovered: true });
-  }
-
-  handleMouseLeave = () => {
-    this.setState({ hovered: false });
-  }
-
-  handleInputFocus = () => {
-    this.setState({ focused: true });
-  }
-
-  handleClick = () => {
-    this.setState({ focused: true });
-  }
-
-  handleInputBlur = () => {
-    const { dirtyDescription } = this.state;
-
-    this.setState({ focused: false, dirtyDescription: null });
-
-    if (dirtyDescription !== null) {
-      this.props.onDescriptionChange(this.props.media.get('id'), dirtyDescription);
-    }
-  }
-
   render () {
-    const { intl, media } = this.props;
-    const active          = this.state.hovered || this.state.focused;
-    const description     = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || '';
+    const { media } = this.props;
     const focusX = media.getIn(['meta', 'focus', 'x']);
     const focusY = media.getIn(['meta', 'focus', 'y']);
     const x = ((focusX /  2) + .5) * 100;
     const y = ((focusY / -2) + .5) * 100;
 
     return (
-      <div className='compose-form__upload' tabIndex='0' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} onClick={this.handleClick} role='button'>
+      <div className='compose-form__upload' tabIndex='0' role='button'>
         <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 })}>
+              <div className={classNames('compose-form__upload__actions', { active: true })}>
                 <button className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
-                {media.get('type') === 'image' && <button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='crosshairs' /> <FormattedMessage id='upload_form.focus' defaultMessage='Crop' /></button>}
-              </div>
-
-              <div className={classNames('compose-form__upload-description', { active })}>
-                <label>
-                  <span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span>
-
-                  <textarea
-                    placeholder={intl.formatMessage(messages.description)}
-                    value={description}
-                    maxLength={420}
-                    onFocus={this.handleInputFocus}
-                    onChange={this.handleInputChange}
-                    onBlur={this.handleInputBlur}
-                    onKeyDown={this.handleKeyDown}
-                  />
-                </label>
+                <button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>
               </div>
             </div>
           )}
diff --git a/app/javascript/mastodon/features/compose/components/upload_form.js b/app/javascript/mastodon/features/compose/components/upload_form.js
index 9ff2aa0fa631fdbcd64dfd3038d12e0166b08282..c6eac554e6cf6fbe7b13ee4631cf7fd82f3bf56a 100644
--- a/app/javascript/mastodon/features/compose/components/upload_form.js
+++ b/app/javascript/mastodon/features/compose/components/upload_form.js
@@ -4,6 +4,7 @@ import UploadProgressContainer from '../containers/upload_progress_container';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import UploadContainer from '../containers/upload_container';
 import SensitiveButtonContainer from '../containers/sensitive_button_container';
+import { FormattedMessage } from 'react-intl';
 
 export default class UploadForm extends ImmutablePureComponent {
 
@@ -16,7 +17,7 @@ export default class UploadForm extends ImmutablePureComponent {
 
     return (
       <div className='compose-form__upload-wrapper'>
-        <UploadProgressContainer />
+        <UploadProgressContainer icon='upload' message={<FormattedMessage id='upload_progress.label' defaultMessage='Uploading…' />} />
 
         <div className='compose-form__uploads-wrapper'>
           {mediaIds.map(id => (
diff --git a/app/javascript/mastodon/features/compose/components/upload_progress.js b/app/javascript/mastodon/features/compose/components/upload_progress.js
index cbe58f573926954c42db1193e6b3b442a31a2d7e..b0bfe0c9a142bb991bcb5558c4c82c66d1ab23c6 100644
--- a/app/javascript/mastodon/features/compose/components/upload_progress.js
+++ b/app/javascript/mastodon/features/compose/components/upload_progress.js
@@ -2,7 +2,6 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
-import { FormattedMessage } from 'react-intl';
 import Icon from 'mastodon/components/icon';
 
 export default class UploadProgress extends React.PureComponent {
@@ -10,10 +9,12 @@ export default class UploadProgress extends React.PureComponent {
   static propTypes = {
     active: PropTypes.bool,
     progress: PropTypes.number,
+    icon: PropTypes.string.isRequired,
+    message: PropTypes.node.isRequired,
   };
 
   render () {
-    const { active, progress } = this.props;
+    const { active, progress, icon, message } = this.props;
 
     if (!active) {
       return null;
@@ -22,11 +23,11 @@ export default class UploadProgress extends React.PureComponent {
     return (
       <div className='upload-progress'>
         <div className='upload-progress__icon'>
-          <Icon id='upload' />
+          <Icon id={icon} />
         </div>
 
         <div className='upload-progress__message'>
-          <FormattedMessage id='upload_progress.label' defaultMessage='Uploading...' />
+          {message}
 
           <div className='upload-progress__backdrop'>
             <Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}>
diff --git a/app/javascript/mastodon/features/compose/containers/navigation_container.js b/app/javascript/mastodon/features/compose/containers/navigation_container.js
index eb9f3ea45e6e4516c11d22aaa3a33832fc0549e9..8606a642e49781c3c1d8b799ad10ed6bc696686e 100644
--- a/app/javascript/mastodon/features/compose/containers/navigation_container.js
+++ b/app/javascript/mastodon/features/compose/containers/navigation_container.js
@@ -1,11 +1,29 @@
 import { connect }   from 'react-redux';
+import { defineMessages, injectIntl } from 'react-intl';
 import NavigationBar from '../components/navigation_bar';
+import { logOut } from 'mastodon/utils/log_out';
+import { openModal } from 'mastodon/actions/modal';
 import { me } from '../../../initial_state';
 
+const messages = defineMessages({
+  logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
+  logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
+});
+
 const mapStateToProps = state => {
   return {
     account: state.getIn(['accounts', me]),
   };
 };
 
-export default connect(mapStateToProps)(NavigationBar);
+const mapDispatchToProps = (dispatch, { intl }) => ({
+  onLogout () {
+    dispatch(openModal('CONFIRM', {
+      message: intl.formatMessage(messages.logoutMessage),
+      confirm: intl.formatMessage(messages.logoutConfirm),
+      onConfirm: () => logOut(),
+    }));
+  },
+});
+
+export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(NavigationBar));
diff --git a/app/javascript/mastodon/features/compose/containers/search_results_container.js b/app/javascript/mastodon/features/compose/containers/search_results_container.js
index e4d5f3420787c81184c529bc951e843243a46cfb..1f714ff8342cb267b07c22a163d1bca0d8bfcf0c 100644
--- a/app/javascript/mastodon/features/compose/containers/search_results_container.js
+++ b/app/javascript/mastodon/features/compose/containers/search_results_container.js
@@ -1,6 +1,7 @@
 import { connect } from 'react-redux';
 import SearchResults from '../components/search_results';
-import { fetchSuggestions, dismissSuggestion } from '../../../actions/suggestions';
+import { fetchSuggestions, dismissSuggestion } from 'mastodon/actions/suggestions';
+import { expandSearch } from 'mastodon/actions/search';
 
 const mapStateToProps = state => ({
   results: state.getIn(['search', 'results']),
@@ -10,6 +11,7 @@ const mapStateToProps = state => ({
 
 const mapDispatchToProps = dispatch => ({
   fetchSuggestions: () => dispatch(fetchSuggestions()),
+  expandSearch: type => dispatch(expandSearch(type)),
   dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))),
 });
 
diff --git a/app/javascript/mastodon/features/compose/containers/upload_container.js b/app/javascript/mastodon/features/compose/containers/upload_container.js
index b6d81f03ac6d9cf387f3e23d296f59f1d3c1b596..342b0c2a9cad55bed17ce03bfdf65c5bcc97c7a9 100644
--- a/app/javascript/mastodon/features/compose/containers/upload_container.js
+++ b/app/javascript/mastodon/features/compose/containers/upload_container.js
@@ -1,6 +1,6 @@
 import { connect } from 'react-redux';
 import Upload from '../components/upload';
-import { undoUploadCompose, changeUploadCompose } from '../../../actions/compose';
+import { undoUploadCompose } from '../../../actions/compose';
 import { openModal } from '../../../actions/modal';
 import { submitCompose } from '../../../actions/compose';
 
@@ -14,10 +14,6 @@ const mapDispatchToProps = dispatch => ({
     dispatch(undoUploadCompose(id));
   },
 
-  onDescriptionChange: (id, description) => {
-    dispatch(changeUploadCompose(id, { description }));
-  },
-
   onOpenFocalPoint: id => {
     dispatch(openModal('FOCAL_POINT', { id }));
   },
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index 0731abcf4d5a8c3245de61a5c78fb5ebc80fea6e..e2de8b0e6a20d1da4479c19a77dd201c434af682 100644
--- a/app/javascript/mastodon/features/compose/index.js
+++ b/app/javascript/mastodon/features/compose/index.js
@@ -12,9 +12,11 @@ import Motion from '../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import SearchResultsContainer from './containers/search_results_container';
 import { changeComposing } from '../../actions/compose';
+import { openModal } from 'mastodon/actions/modal';
 import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
 import { mascot } from '../../initial_state';
 import Icon from 'mastodon/components/icon';
+import { logOut } from 'mastodon/utils/log_out';
 
 const messages = defineMessages({
   start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@@ -25,6 +27,8 @@ const messages = defineMessages({
   preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
   logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
   compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' },
+  logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
+  logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
 });
 
 const mapStateToProps = (state, ownProps) => ({
@@ -61,6 +65,21 @@ class Compose extends React.PureComponent {
     }
   }
 
+  handleLogoutClick = e => {
+    const { dispatch, intl } = this.props;
+
+    e.preventDefault();
+    e.stopPropagation();
+
+    dispatch(openModal('CONFIRM', {
+      message: intl.formatMessage(messages.logoutMessage),
+      confirm: intl.formatMessage(messages.logoutConfirm),
+      onConfirm: () => logOut(),
+    }));
+
+    return false;
+  }
+
   onFocus = () => {
     this.props.dispatch(changeComposing(true));
   }
@@ -92,7 +111,7 @@ class Compose extends React.PureComponent {
             <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
           )}
           <a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a>
-          <a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><Icon id='sign-out' fixedWidth /></a>
+          <a href='/auth/sign_out' className='drawer__tab' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)} onClick={this.handleLogoutClick}><Icon id='sign-out' fixedWidth /></a>
         </nav>
       );
     }
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.js b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
index ffcd6d2811af8ac082a4cee79051e5073f2377c8..2cbaa0791be0fc55704e9eeb3d9eee32ca17d09e 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversation.js
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
@@ -2,9 +2,29 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
-import StatusContainer from '../../../containers/status_container';
+import StatusContent from 'mastodon/components/status_content';
+import AttachmentList from 'mastodon/components/attachment_list';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
+import AvatarComposite from 'mastodon/components/avatar_composite';
+import Permalink from 'mastodon/components/permalink';
+import IconButton from 'mastodon/components/icon_button';
+import RelativeTimestamp from 'mastodon/components/relative_timestamp';
+import { HotKeys } from 'react-hotkeys';
+import { autoPlayGif } from 'mastodon/initial_state';
 
-export default class Conversation extends ImmutablePureComponent {
+const messages = defineMessages({
+  more: { id: 'status.more', defaultMessage: 'More' },
+  open: { id: 'conversation.open', defaultMessage: 'View conversation' },
+  reply: { id: 'status.reply', defaultMessage: 'Reply' },
+  markAsRead: { id: 'conversation.mark_as_read', defaultMessage: 'Mark as read' },
+  delete: { id: 'conversation.delete', defaultMessage: 'Delete conversation' },
+  muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
+  unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
+});
+
+export default @injectIntl
+class Conversation extends ImmutablePureComponent {
 
   static contextTypes = {
     router: PropTypes.object,
@@ -13,25 +33,76 @@ export default class Conversation extends ImmutablePureComponent {
   static propTypes = {
     conversationId: PropTypes.string.isRequired,
     accounts: ImmutablePropTypes.list.isRequired,
-    lastStatusId: PropTypes.string,
+    lastStatus: ImmutablePropTypes.map,
     unread:PropTypes.bool.isRequired,
     onMoveUp: PropTypes.func,
     onMoveDown: PropTypes.func,
     markRead: PropTypes.func.isRequired,
+    delete: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
   };
 
+  _updateEmojis () {
+    const node = this.namesNode;
+
+    if (!node || autoPlayGif) {
+      return;
+    }
+
+    const emojis = node.querySelectorAll('.custom-emoji');
+
+    for (var i = 0; i < emojis.length; i++) {
+      let emoji = emojis[i];
+      if (emoji.classList.contains('status-emoji')) {
+        continue;
+      }
+      emoji.classList.add('status-emoji');
+
+      emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
+      emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
+    }
+  }
+
+  componentDidMount () {
+    this._updateEmojis();
+  }
+
+  componentDidUpdate () {
+    this._updateEmojis();
+  }
+
+  handleEmojiMouseEnter = ({ target }) => {
+    target.src = target.getAttribute('data-original');
+  }
+
+  handleEmojiMouseLeave = ({ target }) => {
+    target.src = target.getAttribute('data-static');
+  }
+
   handleClick = () => {
     if (!this.context.router) {
       return;
     }
 
-    const { lastStatusId, unread, markRead } = this.props;
+    const { lastStatus, unread, markRead } = this.props;
 
     if (unread) {
       markRead();
     }
 
-    this.context.router.history.push(`/statuses/${lastStatusId}`);
+    this.context.router.history.push(`/statuses/${lastStatus.get('id')}`);
+  }
+
+  handleMarkAsRead = () => {
+    this.props.markRead();
+  }
+
+  handleReply = () => {
+    this.props.reply(this.props.lastStatus, this.context.router.history);
+  }
+
+  handleDelete = () => {
+    this.props.delete();
   }
 
   handleHotkeyMoveUp = () => {
@@ -42,22 +113,92 @@ export default class Conversation extends ImmutablePureComponent {
     this.props.onMoveDown(this.props.conversationId);
   }
 
+  handleConversationMute = () => {
+    this.props.onMute(this.props.lastStatus);
+  }
+
+  handleShowMore = () => {
+    this.props.onToggleHidden(this.props.lastStatus);
+  }
+
+  setNamesRef = (c) => {
+    this.namesNode = c;
+  }
+
   render () {
-    const { accounts, lastStatusId, unread } = this.props;
+    const { accounts, lastStatus, unread, intl } = this.props;
 
-    if (lastStatusId === null) {
+    if (lastStatus === null) {
       return null;
     }
 
+    const menu = [
+      { text: intl.formatMessage(messages.open), action: this.handleClick },
+      null,
+    ];
+
+    menu.push({ text: intl.formatMessage(lastStatus.get('muted') ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMute });
+
+    if (unread) {
+      menu.push({ text: intl.formatMessage(messages.markAsRead), action: this.handleMarkAsRead });
+      menu.push(null);
+    }
+
+    menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDelete });
+
+    const names = accounts.map(a => <Permalink to={`/accounts/${a.get('id')}`} href={a.get('url')} key={a.get('id')} title={a.get('acct')}><bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi></Permalink>).reduce((prev, cur) => [prev, ', ', cur]);
+
+    const handlers = {
+      reply: this.handleReply,
+      open: this.handleClick,
+      moveUp: this.handleHotkeyMoveUp,
+      moveDown: this.handleHotkeyMoveDown,
+      toggleHidden: this.handleShowMore,
+    };
+
     return (
-      <StatusContainer
-        id={lastStatusId}
-        unread={unread}
-        otherAccounts={accounts}
-        onMoveUp={this.handleHotkeyMoveUp}
-        onMoveDown={this.handleHotkeyMoveDown}
-        onClick={this.handleClick}
-      />
+      <HotKeys handlers={handlers}>
+        <div className='conversation focusable muted' tabIndex='0'>
+          <div className='conversation__avatar'>
+            <AvatarComposite accounts={accounts} size={48} />
+          </div>
+
+          <div className='conversation__content'>
+            <div className='conversation__content__info'>
+              <div className='conversation__content__relative-time'>
+                <RelativeTimestamp timestamp={lastStatus.get('created_at')} />
+              </div>
+
+              <div className='conversation__content__names' ref={this.setNamesRef}>
+                <FormattedMessage id='conversation.with' defaultMessage='With {names}' values={{ names: <span>{names}</span> }} />
+              </div>
+            </div>
+
+            <StatusContent
+              status={lastStatus}
+              onClick={this.handleClick}
+              expanded={!lastStatus.get('hidden')}
+              onExpandedToggle={this.handleShowMore}
+              collapsable
+            />
+
+            {lastStatus.get('media_attachments').size > 0 && (
+              <AttachmentList
+                compact
+                media={lastStatus.get('media_attachments')}
+              />
+            )}
+
+            <div className='status__action-bar'>
+              <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReply} />
+
+              <div className='status__action-bar-dropdown'>
+                <DropdownMenuContainer status={lastStatus} items={menu} icon='ellipsis-h' size={18} direction='right' title={intl.formatMessage(messages.more)} />
+              </div>
+            </div>
+          </div>
+        </div>
+      </HotKeys>
     );
   }
 
diff --git a/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js b/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js
index bd6f6bfb0176ce45ff43f85c883f06bf4e03f6c4..94cef81a7d2d5cb718ad9589428b59e5016fec80 100644
--- a/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js
+++ b/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js
@@ -1,19 +1,74 @@
 import { connect } from 'react-redux';
 import Conversation from '../components/conversation';
-import { markConversationRead } from '../../../actions/conversations';
+import { markConversationRead, deleteConversation } from 'mastodon/actions/conversations';
+import { makeGetStatus } from 'mastodon/selectors';
+import { replyCompose } from 'mastodon/actions/compose';
+import { openModal } from 'mastodon/actions/modal';
+import { muteStatus, unmuteStatus, hideStatus, revealStatus } from 'mastodon/actions/statuses';
+import { defineMessages, injectIntl } from 'react-intl';
 
-const mapStateToProps = (state, { conversationId }) => {
-  const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId);
+const messages = defineMessages({
+  replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
+  replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
+});
+
+const mapStateToProps = () => {
+  const getStatus = makeGetStatus();
+
+  return (state, { conversationId }) => {
+    const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId);
+    const lastStatusId = conversation.get('last_status', null);
 
-  return {
-    accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)),
-    unread: conversation.get('unread'),
-    lastStatusId: conversation.get('last_status', null),
+    return {
+      accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)),
+      unread: conversation.get('unread'),
+      lastStatus: lastStatusId && getStatus(state, { id: lastStatusId }),
+    };
   };
 };
 
-const mapDispatchToProps = (dispatch, { conversationId }) => ({
-  markRead: () => dispatch(markConversationRead(conversationId)),
+const mapDispatchToProps = (dispatch, { intl, conversationId }) => ({
+
+  markRead () {
+    dispatch(markConversationRead(conversationId));
+  },
+
+  reply (status, router) {
+    dispatch((_, getState) => {
+      let state = getState();
+
+      if (state.getIn(['compose', 'text']).trim().length !== 0) {
+        dispatch(openModal('CONFIRM', {
+          message: intl.formatMessage(messages.replyMessage),
+          confirm: intl.formatMessage(messages.replyConfirm),
+          onConfirm: () => dispatch(replyCompose(status, router)),
+        }));
+      } else {
+        dispatch(replyCompose(status, router));
+      }
+    });
+  },
+
+  delete () {
+    dispatch(deleteConversation(conversationId));
+  },
+
+  onMute (status) {
+    if (status.get('muted')) {
+      dispatch(unmuteStatus(status.get('id')));
+    } else {
+      dispatch(muteStatus(status.get('id')));
+    }
+  },
+
+  onToggleHidden (status) {
+    if (status.get('hidden')) {
+      dispatch(revealStatus(status.get('id')));
+    } else {
+      dispatch(hideStatus(status.get('id')));
+    }
+  },
+
 });
 
-export default connect(mapStateToProps, mapDispatchToProps)(Conversation);
+export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(Conversation));
diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.js
index d202f3bfd904d86255010f995615399fe4978f41..5ce795760076f02c3734f3cc2f85b025ce4cd8b9 100644
--- a/app/javascript/mastodon/features/direct_timeline/index.js
+++ b/app/javascript/mastodon/features/direct_timeline/index.js
@@ -75,7 +75,7 @@ class DirectTimeline extends React.PureComponent {
     const pinned = !!columnId;
 
     return (
-      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
         <ColumnHeader
           icon='envelope'
           active={hasUnread}
diff --git a/app/javascript/mastodon/features/directory/components/account_card.js b/app/javascript/mastodon/features/directory/components/account_card.js
new file mode 100644
index 0000000000000000000000000000000000000000..50ad744501d4de44e11c831329b13912fecbdefb
--- /dev/null
+++ b/app/javascript/mastodon/features/directory/components/account_card.js
@@ -0,0 +1,190 @@
+import React from 'react';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { makeGetAccount } from 'mastodon/selectors';
+import Avatar from 'mastodon/components/avatar';
+import DisplayName from 'mastodon/components/display_name';
+import Permalink from 'mastodon/components/permalink';
+import RelativeTimestamp from 'mastodon/components/relative_timestamp';
+import IconButton from 'mastodon/components/icon_button';
+import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
+import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';
+import { shortNumberFormat } from 'mastodon/utils/numbers';
+import { followAccount, unfollowAccount, blockAccount, unblockAccount, unmuteAccount } from 'mastodon/actions/accounts';
+import { openModal } from 'mastodon/actions/modal';
+import { initMuteModal } from 'mastodon/actions/mutes';
+
+const messages = defineMessages({
+  follow: { id: 'account.follow', defaultMessage: 'Follow' },
+  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
+  requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
+  unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
+  unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
+});
+
+const makeMapStateToProps = () => {
+  const getAccount = makeGetAccount();
+
+  const mapStateToProps = (state, { id }) => ({
+    account: getAccount(state, id),
+  });
+
+  return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch, { intl }) => ({
+
+  onFollow (account) {
+    if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
+      if (unfollowModal) {
+        dispatch(openModal('CONFIRM', {
+          message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
+          confirm: intl.formatMessage(messages.unfollowConfirm),
+          onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
+        }));
+      } else {
+        dispatch(unfollowAccount(account.get('id')));
+      }
+    } else {
+      dispatch(followAccount(account.get('id')));
+    }
+  },
+
+  onBlock (account) {
+    if (account.getIn(['relationship', 'blocking'])) {
+      dispatch(unblockAccount(account.get('id')));
+    } else {
+      dispatch(blockAccount(account.get('id')));
+    }
+  },
+
+  onMute (account) {
+    if (account.getIn(['relationship', 'muting'])) {
+      dispatch(unmuteAccount(account.get('id')));
+    } else {
+      dispatch(initMuteModal(account));
+    }
+  },
+
+});
+
+export default @injectIntl
+@connect(makeMapStateToProps, mapDispatchToProps)
+class AccountCard extends ImmutablePureComponent {
+
+  static propTypes = {
+    account: ImmutablePropTypes.map.isRequired,
+    intl: PropTypes.object.isRequired,
+    onFollow: PropTypes.func.isRequired,
+    onBlock: PropTypes.func.isRequired,
+    onMute: PropTypes.func.isRequired,
+  };
+
+  _updateEmojis () {
+    const node = this.node;
+
+    if (!node || autoPlayGif) {
+      return;
+    }
+
+    const emojis = node.querySelectorAll('.custom-emoji');
+
+    for (var i = 0; i < emojis.length; i++) {
+      let emoji = emojis[i];
+      if (emoji.classList.contains('status-emoji')) {
+        continue;
+      }
+      emoji.classList.add('status-emoji');
+
+      emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
+      emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
+    }
+  }
+
+  componentDidMount () {
+    this._updateEmojis();
+  }
+
+  componentDidUpdate () {
+    this._updateEmojis();
+  }
+
+  handleEmojiMouseEnter = ({ target }) => {
+    target.src = target.getAttribute('data-original');
+  }
+
+  handleEmojiMouseLeave = ({ target }) => {
+    target.src = target.getAttribute('data-static');
+  }
+
+  handleFollow = () => {
+    this.props.onFollow(this.props.account);
+  }
+
+  handleBlock = () => {
+    this.props.onBlock(this.props.account);
+  }
+
+  handleMute = () => {
+    this.props.onMute(this.props.account);
+  }
+
+  setRef = (c) => {
+    this.node = c;
+  }
+
+  render () {
+    const { account, intl } = this.props;
+
+    let buttons;
+
+    if (account.get('id') !== me && account.get('relationship', null) !== null) {
+      const following = account.getIn(['relationship', 'following']);
+      const requested = account.getIn(['relationship', 'requested']);
+      const blocking  = account.getIn(['relationship', 'blocking']);
+      const muting    = account.getIn(['relationship', 'muting']);
+
+      if (requested) {
+        buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />;
+      } else if (blocking) {
+        buttons = <IconButton active icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
+      } else if (muting) {
+        buttons = <IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />;
+      } else if (!account.get('moved') || following) {
+        buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
+      }
+    }
+
+    return (
+      <div className='directory__card'>
+        <div className='directory__card__img'>
+          <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' />
+        </div>
+
+        <div className='directory__card__bar'>
+          <Permalink className='directory__card__bar__name' href={account.get('url')} to={`/accounts/${account.get('id')}`}>
+            <Avatar account={account} size={48} />
+            <DisplayName account={account} />
+          </Permalink>
+
+          <div className='directory__card__bar__relationship account__relationship'>
+            {buttons}
+          </div>
+        </div>
+
+        <div className='directory__card__extra' ref={this.setRef}>
+          <div className='account__header__content' dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }} />
+        </div>
+
+        <div className='directory__card__extra'>
+          <div className='accounts-table__count'>{shortNumberFormat(account.get('statuses_count'))} <small><FormattedMessage id='account.posts' defaultMessage='Toots' /></small></div>
+          <div className='accounts-table__count'>{shortNumberFormat(account.get('followers_count'))} <small><FormattedMessage id='account.followers' defaultMessage='Followers' /></small></div>
+          <div className='accounts-table__count'>{account.get('last_status_at') === null ? <FormattedMessage id='account.never_active' defaultMessage='Never' /> : <RelativeTimestamp timestamp={account.get('last_status_at')} />} <small><FormattedMessage id='account.last_status' defaultMessage='Last active' /></small></div>
+        </div>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/directory/index.js b/app/javascript/mastodon/features/directory/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..2f91e759b554e5ba2c6c999b04746bb506f8cd37
--- /dev/null
+++ b/app/javascript/mastodon/features/directory/index.js
@@ -0,0 +1,171 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { defineMessages, injectIntl } from 'react-intl';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import Column from 'mastodon/components/column';
+import ColumnHeader from 'mastodon/components/column_header';
+import { addColumn, removeColumn, moveColumn, changeColumnParams } from 'mastodon/actions/columns';
+import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory';
+import { List as ImmutableList } from 'immutable';
+import AccountCard from './components/account_card';
+import RadioButton from 'mastodon/components/radio_button';
+import classNames from 'classnames';
+import LoadMore from 'mastodon/components/load_more';
+import { ScrollContainer } from 'react-router-scroll-4';
+
+const messages = defineMessages({
+  title: { id: 'column.directory', defaultMessage: 'Browse profiles' },
+  recentlyActive: { id: 'directory.recently_active', defaultMessage: 'Recently active' },
+  newArrivals: { id: 'directory.new_arrivals', defaultMessage: 'New arrivals' },
+  local: { id: 'directory.local', defaultMessage: 'From {domain} only' },
+  federated: { id: 'directory.federated', defaultMessage: 'From known fediverse' },
+});
+
+const mapStateToProps = state => ({
+  accountIds: state.getIn(['user_lists', 'directory', 'items'], ImmutableList()),
+  isLoading: state.getIn(['user_lists', 'directory', 'isLoading'], true),
+  domain: state.getIn(['meta', 'domain']),
+});
+
+export default @connect(mapStateToProps)
+@injectIntl
+class Directory extends React.PureComponent {
+
+  static contextTypes = {
+    router: PropTypes.object,
+  };
+
+  static propTypes = {
+    isLoading: PropTypes.bool,
+    accountIds: ImmutablePropTypes.list.isRequired,
+    dispatch: PropTypes.func.isRequired,
+    shouldUpdateScroll: PropTypes.func,
+    columnId: PropTypes.string,
+    intl: PropTypes.object.isRequired,
+    multiColumn: PropTypes.bool,
+    domain: PropTypes.string.isRequired,
+    params: PropTypes.shape({
+      order: PropTypes.string,
+      local: PropTypes.bool,
+    }),
+  };
+
+  state = {
+    order: null,
+    local: null,
+  };
+
+  handlePin = () => {
+    const { columnId, dispatch } = this.props;
+
+    if (columnId) {
+      dispatch(removeColumn(columnId));
+    } else {
+      dispatch(addColumn('DIRECTORY', this.getParams(this.props, this.state)));
+    }
+  }
+
+  getParams = (props, state) => ({
+    order: state.order === null ? (props.params.order || 'active') : state.order,
+    local: state.local === null ? (props.params.local || false) : state.local,
+  });
+
+  handleMove = dir => {
+    const { columnId, dispatch } = this.props;
+    dispatch(moveColumn(columnId, dir));
+  }
+
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  }
+
+  componentDidMount () {
+    const { dispatch } = this.props;
+    dispatch(fetchDirectory(this.getParams(this.props, this.state)));
+  }
+
+  componentDidUpdate (prevProps, prevState) {
+    const { dispatch } = this.props;
+    const paramsOld = this.getParams(prevProps, prevState);
+    const paramsNew = this.getParams(this.props, this.state);
+
+    if (paramsOld.order !== paramsNew.order || paramsOld.local !== paramsNew.local) {
+      dispatch(fetchDirectory(paramsNew));
+    }
+  }
+
+  setRef = c => {
+    this.column = c;
+  }
+
+  handleChangeOrder = e => {
+    const { dispatch, columnId } = this.props;
+
+    if (columnId) {
+      dispatch(changeColumnParams(columnId, ['order'], e.target.value));
+    } else {
+      this.setState({ order: e.target.value });
+    }
+  }
+
+  handleChangeLocal = e => {
+    const { dispatch, columnId } = this.props;
+
+    if (columnId) {
+      dispatch(changeColumnParams(columnId, ['local'], e.target.value === '1'));
+    } else {
+      this.setState({ local: e.target.value === '1' });
+    }
+  }
+
+  handleLoadMore = () => {
+    const { dispatch } = this.props;
+    dispatch(expandDirectory(this.getParams(this.props, this.state)));
+  }
+
+  render () {
+    const { isLoading, accountIds, intl, columnId, multiColumn, domain, shouldUpdateScroll } = this.props;
+    const { order, local }  = this.getParams(this.props, this.state);
+    const pinned = !!columnId;
+
+    const scrollableArea = (
+      <div className='scrollable' style={{ background: 'transparent' }}>
+        <div className='filter-form'>
+          <div className='filter-form__column' role='group'>
+            <RadioButton name='order' value='active' label={intl.formatMessage(messages.recentlyActive)} checked={order === 'active'} onChange={this.handleChangeOrder} />
+            <RadioButton name='order' value='new' label={intl.formatMessage(messages.newArrivals)} checked={order === 'new'} onChange={this.handleChangeOrder} />
+          </div>
+
+          <div className='filter-form__column' role='group'>
+            <RadioButton name='local' value='1' label={intl.formatMessage(messages.local, { domain })} checked={local} onChange={this.handleChangeLocal} />
+            <RadioButton name='local' value='0' label={intl.formatMessage(messages.federated)} checked={!local} onChange={this.handleChangeLocal} />
+          </div>
+        </div>
+
+        <div className={classNames('directory__list', { loading: isLoading })}>
+          {accountIds.map(accountId => <AccountCard id={accountId} key={accountId} />)}
+        </div>
+
+        <LoadMore onClick={this.handleLoadMore} visible={!isLoading} />
+      </div>
+    );
+
+    return (
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
+        <ColumnHeader
+          icon='address-book-o'
+          title={intl.formatMessage(messages.title)}
+          onPin={this.handlePin}
+          onMove={this.handleMove}
+          onClick={this.handleHeaderClick}
+          pinned={pinned}
+          multiColumn={multiColumn}
+        />
+
+        {multiColumn && !pinned ? <ScrollContainer scrollKey='directory' shouldUpdateScroll={shouldUpdateScroll}>{scrollableArea}</ScrollContainer> : scrollableArea}
+      </Column>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.js
index 7c075f5a5c5109cb8a268126cc1b863dd06ae5ef..482245c867cc9fdaccdb4775848af5f61c3b45d8 100644
--- a/app/javascript/mastodon/features/domain_blocks/index.js
+++ b/app/javascript/mastodon/features/domain_blocks/index.js
@@ -33,6 +33,7 @@ class Blocks extends ImmutablePureComponent {
     hasMore: PropTypes.bool,
     domains: ImmutablePropTypes.orderedSet,
     intl: PropTypes.object.isRequired,
+    multiColumn: PropTypes.bool,
   };
 
   componentWillMount () {
@@ -44,7 +45,7 @@ class Blocks extends ImmutablePureComponent {
   }, 300, { leading: true });
 
   render () {
-    const { intl, domains, shouldUpdateScroll, hasMore } = this.props;
+    const { intl, domains, shouldUpdateScroll, hasMore, multiColumn } = this.props;
 
     if (!domains) {
       return (
@@ -57,7 +58,7 @@ class Blocks extends ImmutablePureComponent {
     const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
 
     return (
-      <Column icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
+      <Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
         <ColumnBackButtonSlim />
         <ScrollableList
           scrollKey='domain_blocks'
@@ -65,6 +66,7 @@ class Blocks extends ImmutablePureComponent {
           hasMore={hasMore}
           shouldUpdateScroll={shouldUpdateScroll}
           emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
         >
           {domains.map(domain =>
             <DomainContainer key={domain} domain={domain} />
diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js
index fa91b50149fadcd4204b1024026fda6271eef34b..cd10e20b7ea8f00117813682c24ba4e06702d1a3 100644
--- a/app/javascript/mastodon/features/emoji/emoji.js
+++ b/app/javascript/mastodon/features/emoji/emoji.js
@@ -92,8 +92,11 @@ export const buildCustomEmojis = (customEmojis) => {
       keywords: [name],
       imageUrl: url,
       custom: true,
+      customCategory: emoji.get('category'),
     });
   });
 
   return emojis;
 };
+
+export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set(['custom']));
diff --git a/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js b/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
index 164fdcc0b8a69f599a63d9128095f9b9c89c095e..e4519a13e62266127c91e848e7d0c1fb90c83e4d 100644
--- a/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
+++ b/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
@@ -74,7 +74,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
       return [emojisList['-1']];
     }
 
-    let values = value.toLowerCase().split(/[\s|,|\-|_]+/),
+    let values = value.toLowerCase().split(/[\s|,\-_]+/),
       allResults = [];
 
     if (values.length > 2) {
diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js
index fa9401b90ea16ff523129b50ce998ead6db4b988..db8a3f815f17295d1db6a3e8cbc9965b141a23b5 100644
--- a/app/javascript/mastodon/features/favourited_statuses/index.js
+++ b/app/javascript/mastodon/features/favourited_statuses/index.js
@@ -74,7 +74,7 @@ class Favourites extends ImmutablePureComponent {
     const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite toots yet. When you favourite one, it will show up here." />;
 
     return (
-      <Column ref={this.setRef} label={intl.formatMessage(messages.heading)}>
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>
         <ColumnHeader
           icon='star'
           title={intl.formatMessage(messages.heading)}
@@ -95,6 +95,7 @@ class Favourites extends ImmutablePureComponent {
           onLoadMore={this.handleLoadMore}
           shouldUpdateScroll={shouldUpdateScroll}
           emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
         />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.js
index d1ac229a279148e4a60a1e0fcc978220d92b634f..249e6a04424b471cec3a78f915068dc72e4c48a4 100644
--- a/app/javascript/mastodon/features/favourites/index.js
+++ b/app/javascript/mastodon/features/favourites/index.js
@@ -5,17 +5,23 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import LoadingIndicator from '../../components/loading_indicator';
 import { fetchFavourites } from '../../actions/interactions';
-import { FormattedMessage } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import AccountContainer from '../../containers/account_container';
 import Column from '../ui/components/column';
-import ColumnBackButton from '../../components/column_back_button';
 import ScrollableList from '../../components/scrollable_list';
+import Icon from 'mastodon/components/icon';
+import ColumnHeader from '../../components/column_header';
+
+const messages = defineMessages({
+  refresh: { id: 'refresh', defaultMessage: 'Refresh' },
+});
 
 const mapStateToProps = (state, props) => ({
   accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
 });
 
 export default @connect(mapStateToProps)
+@injectIntl
 class Favourites extends ImmutablePureComponent {
 
   static propTypes = {
@@ -23,10 +29,14 @@ class Favourites extends ImmutablePureComponent {
     dispatch: PropTypes.func.isRequired,
     shouldUpdateScroll: PropTypes.func,
     accountIds: ImmutablePropTypes.list,
+    multiColumn: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
   };
 
   componentWillMount () {
-    this.props.dispatch(fetchFavourites(this.props.params.statusId));
+    if (!this.props.accountIds) {
+      this.props.dispatch(fetchFavourites(this.props.params.statusId));
+    }
   }
 
   componentWillReceiveProps (nextProps) {
@@ -35,8 +45,12 @@ class Favourites extends ImmutablePureComponent {
     }
   }
 
+  handleRefresh = () => {
+    this.props.dispatch(fetchFavourites(this.props.params.statusId));
+  }
+
   render () {
-    const { shouldUpdateScroll, accountIds } = this.props;
+    const { intl, shouldUpdateScroll, accountIds, multiColumn } = this.props;
 
     if (!accountIds) {
       return (
@@ -49,13 +63,20 @@ class Favourites extends ImmutablePureComponent {
     const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favourited this toot yet. When someone does, they will show up here.' />;
 
     return (
-      <Column>
-        <ColumnBackButton />
+      <Column bindToDocument={!multiColumn}>
+        <ColumnHeader
+          showBackButton
+          multiColumn={multiColumn}
+          extraButton={(
+            <button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
+          )}
+        />
 
         <ScrollableList
           scrollKey='favourites'
           shouldUpdateScroll={shouldUpdateScroll}
           emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
         >
           {accountIds.map(id =>
             <AccountContainer key={id} id={id} withNote={false} />
diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js
index 44624cb40665155a5330922c45b8c520962d5950..57ef441454707f191b61331b20765310b4f72788 100644
--- a/app/javascript/mastodon/features/follow_requests/index.js
+++ b/app/javascript/mastodon/features/follow_requests/index.js
@@ -32,6 +32,7 @@ class FollowRequests extends ImmutablePureComponent {
     hasMore: PropTypes.bool,
     accountIds: ImmutablePropTypes.list,
     intl: PropTypes.object.isRequired,
+    multiColumn: PropTypes.bool,
   };
 
   componentWillMount () {
@@ -43,7 +44,7 @@ class FollowRequests extends ImmutablePureComponent {
   }, 300, { leading: true });
 
   render () {
-    const { intl, shouldUpdateScroll, accountIds, hasMore } = this.props;
+    const { intl, shouldUpdateScroll, accountIds, hasMore, multiColumn } = this.props;
 
     if (!accountIds) {
       return (
@@ -56,7 +57,7 @@ class FollowRequests extends ImmutablePureComponent {
     const emptyMessage = <FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />;
 
     return (
-      <Column icon='user-plus' heading={intl.formatMessage(messages.heading)}>
+      <Column bindToDocument={!multiColumn} icon='user-plus' heading={intl.formatMessage(messages.heading)}>
         <ColumnBackButtonSlim />
         <ScrollableList
           scrollKey='follow_requests'
@@ -64,6 +65,7 @@ class FollowRequests extends ImmutablePureComponent {
           hasMore={hasMore}
           shouldUpdateScroll={shouldUpdateScroll}
           emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
         >
           {accountIds.map(id =>
             <AccountAuthorizeContainer key={id} id={id} />
diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.js
index e3387e1be8351241ae77caa383756b0821d1f623..9e635d2501cfe3a951ceea46b2d59073a163e3f1 100644
--- a/app/javascript/mastodon/features/followers/index.js
+++ b/app/javascript/mastodon/features/followers/index.js
@@ -36,11 +36,14 @@ class Followers extends ImmutablePureComponent {
     hasMore: PropTypes.bool,
     blockedBy: PropTypes.bool,
     isAccount: PropTypes.bool,
+    multiColumn: PropTypes.bool,
   };
 
   componentWillMount () {
-    this.props.dispatch(fetchAccount(this.props.params.accountId));
-    this.props.dispatch(fetchFollowers(this.props.params.accountId));
+    if (!this.props.accountIds) {
+      this.props.dispatch(fetchAccount(this.props.params.accountId));
+      this.props.dispatch(fetchFollowers(this.props.params.accountId));
+    }
   }
 
   componentWillReceiveProps (nextProps) {
@@ -55,7 +58,7 @@ class Followers extends ImmutablePureComponent {
   }, 300, { leading: true });
 
   render () {
-    const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount } = this.props;
+    const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount, multiColumn } = this.props;
 
     if (!isAccount) {
       return (
@@ -77,7 +80,7 @@ class Followers extends ImmutablePureComponent {
 
     return (
       <Column>
-        <ColumnBackButton />
+        <ColumnBackButton multiColumn={multiColumn} />
 
         <ScrollableList
           scrollKey='followers'
@@ -87,6 +90,7 @@ class Followers extends ImmutablePureComponent {
           prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
           alwaysPrepend
           emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
         >
           {blockedBy ? [] : accountIds.map(id =>
             <AccountContainer key={id} id={id} withNote={false} />
diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js
index 3bf89fb2bab7927f1f64e8c0644845c3cec9746f..284ae2c11ea86fbcac08686ab53f434c4f216229 100644
--- a/app/javascript/mastodon/features/following/index.js
+++ b/app/javascript/mastodon/features/following/index.js
@@ -36,11 +36,14 @@ class Following extends ImmutablePureComponent {
     hasMore: PropTypes.bool,
     blockedBy: PropTypes.bool,
     isAccount: PropTypes.bool,
+    multiColumn: PropTypes.bool,
   };
 
   componentWillMount () {
-    this.props.dispatch(fetchAccount(this.props.params.accountId));
-    this.props.dispatch(fetchFollowing(this.props.params.accountId));
+    if (!this.props.accountIds) {
+      this.props.dispatch(fetchAccount(this.props.params.accountId));
+      this.props.dispatch(fetchFollowing(this.props.params.accountId));
+    }
   }
 
   componentWillReceiveProps (nextProps) {
@@ -55,7 +58,7 @@ class Following extends ImmutablePureComponent {
   }, 300, { leading: true });
 
   render () {
-    const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount } = this.props;
+    const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount, multiColumn } = this.props;
 
     if (!isAccount) {
       return (
@@ -77,7 +80,7 @@ class Following extends ImmutablePureComponent {
 
     return (
       <Column>
-        <ColumnBackButton />
+        <ColumnBackButton multiColumn={multiColumn} />
 
         <ScrollableList
           scrollKey='following'
@@ -87,6 +90,7 @@ class Following extends ImmutablePureComponent {
           prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
           alwaysPrepend
           emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
         >
           {blockedBy ? [] : accountIds.map(id =>
             <AccountContainer key={id} id={id} withNote={false} />
diff --git a/app/javascript/mastodon/features/generic_not_found/index.js b/app/javascript/mastodon/features/generic_not_found/index.js
index 0290be47f9a4f0376d2a8d13f006f28421087ea9..41cd61a5f8b610f2b00260443dc372e01b2f7372 100644
--- a/app/javascript/mastodon/features/generic_not_found/index.js
+++ b/app/javascript/mastodon/features/generic_not_found/index.js
@@ -4,7 +4,7 @@ import MissingIndicator from '../../components/missing_indicator';
 
 const GenericNotFound = () => (
   <Column>
-    <MissingIndicator />
+    <MissingIndicator fullPage />
   </Column>
 );
 
diff --git a/app/javascript/mastodon/features/getting_started/components/trends.js b/app/javascript/mastodon/features/getting_started/components/trends.js
new file mode 100644
index 0000000000000000000000000000000000000000..3b9a3075fbefeaba80ba820f27847eac4d08a563
--- /dev/null
+++ b/app/javascript/mastodon/features/getting_started/components/trends.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import Hashtag from 'mastodon/components/hashtag';
+import { FormattedMessage } from 'react-intl';
+
+export default class Trends extends ImmutablePureComponent {
+
+  static defaultProps = {
+    loading: false,
+  };
+
+  static propTypes = {
+    trends: ImmutablePropTypes.list,
+    fetchTrends: PropTypes.func.isRequired,
+  };
+
+  componentDidMount () {
+    this.props.fetchTrends();
+    this.refreshInterval = setInterval(() => this.props.fetchTrends(), 900 * 1000);
+  }
+
+  componentWillUnmount () {
+    if (this.refreshInterval) {
+      clearInterval(this.refreshInterval);
+    }
+  }
+
+  render () {
+    const { trends } = this.props;
+
+    if (!trends || trends.isEmpty()) {
+      return null;
+    }
+
+    return (
+      <div className='getting-started__trends'>
+        <h4><FormattedMessage id='trends.trending_now' defaultMessage='Trending now' /></h4>
+
+        {trends.take(3).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/getting_started/containers/trends_container.js b/app/javascript/mastodon/features/getting_started/containers/trends_container.js
new file mode 100644
index 0000000000000000000000000000000000000000..1df3fb4fe20d616b412e228f345d039f0bbbf410
--- /dev/null
+++ b/app/javascript/mastodon/features/getting_started/containers/trends_container.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux';
+import { fetchTrends } from '../../../actions/trends';
+import Trends from '../components/trends';
+
+const mapStateToProps = state => ({
+  trends: state.getIn(['trends', 'items']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  fetchTrends: () => dispatch(fetchTrends()),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Trends);
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
index fc7840ec190d1f635f0895d173ffd008367ca8f4..67ec7665b670c1fc36d64ce5999d75918d6f8245 100644
--- a/app/javascript/mastodon/features/getting_started/index.js
+++ b/app/javascript/mastodon/features/getting_started/index.js
@@ -7,12 +7,13 @@ import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
-import { me, profile_directory } from '../../initial_state';
+import { me, profile_directory, showTrends } from '../../initial_state';
 import { fetchFollowRequests } from 'mastodon/actions/accounts';
 import { List as ImmutableList } from 'immutable';
 import NavigationBar from '../compose/components/navigation_bar';
 import Icon from 'mastodon/components/icon';
 import LinkFooter from 'mastodon/features/ui/components/link_footer';
+import TrendsContainer from './containers/trends_container';
 
 const messages = defineMessages({
   home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
@@ -76,16 +77,14 @@ class GettingStarted extends ImmutablePureComponent {
   };
 
   componentDidMount () {
-    const { myAccount, fetchFollowRequests, multiColumn } = this.props;
+    const { fetchFollowRequests, multiColumn } = this.props;
 
     if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) {
       this.context.router.history.replace('/timelines/home');
       return;
     }
 
-    if (myAccount.get('locked')) {
-      fetchFollowRequests();
-    }
+    fetchFollowRequests();
   }
 
   render () {
@@ -106,7 +105,7 @@ class GettingStarted extends ImmutablePureComponent {
 
       if (profile_directory) {
         navItems.push(
-          <ColumnLink key={i++} icon='address-book' text={intl.formatMessage(messages.profile_directory)} href='/explore' />
+          <ColumnLink key={i++} icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />
         );
 
         height += 48;
@@ -119,7 +118,7 @@ class GettingStarted extends ImmutablePureComponent {
       height += 34;
     } else if (profile_directory) {
       navItems.push(
-        <ColumnLink key={i++} icon='address-book' text={intl.formatMessage(messages.profile_directory)} href='/explore' />
+        <ColumnLink key={i++} icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />
       );
 
       height += 48;
@@ -133,7 +132,7 @@ class GettingStarted extends ImmutablePureComponent {
 
     height += 48*3;
 
-    if (myAccount.get('locked')) {
+    if (myAccount.get('locked') || unreadFollowRequests > 0) {
       navItems.push(<ColumnLink key={i++} icon='user-plus' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
       height += 48;
     }
@@ -148,7 +147,7 @@ class GettingStarted extends ImmutablePureComponent {
     }
 
     return (
-      <Column label={intl.formatMessage(messages.menu)}>
+      <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.menu)}>
         {multiColumn && <div className='column-header__wrapper'>
           <h1 className='column-header'>
             <button>
@@ -168,6 +167,8 @@ class GettingStarted extends ImmutablePureComponent {
 
           <LinkFooter withHotkeys={multiColumn} />
         </div>
+
+        {multiColumn && showTrends && <TrendsContainer />}
       </Column>
     );
   }
diff --git a/app/javascript/mastodon/features/hashtag_timeline/containers/column_settings_container.js b/app/javascript/mastodon/features/hashtag_timeline/containers/column_settings_container.js
index c5098052ce23f1232a35947f5a41c70933e42ef0..5914bbeaf7880d31da7e761f10db6f935eab8a1b 100644
--- a/app/javascript/mastodon/features/hashtag_timeline/containers/column_settings_container.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/containers/column_settings_container.js
@@ -20,7 +20,7 @@ const mapDispatchToProps = (dispatch, { columnId }) => ({
   },
 
   onLoad (value) {
-    return api().get('/api/v2/search', { params: { q: value } }).then(response => {
+    return api().get('/api/v2/search', { params: { q: value, type: 'hashtags' } }).then(response => {
       return (response.data.hashtags || []).map((tag) => {
         return { value: tag.name, label: `#${tag.name}` };
       });
diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js
index 0d3c97a6489cc9d4caa56bb09e98ed0a71c3ef36..28200e6c21f0f61a529c03090b2343449fd869a6 100644
--- a/app/javascript/mastodon/features/hashtag_timeline/index.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/index.js
@@ -135,7 +135,7 @@ class HashtagTimeline extends React.PureComponent {
     const pinned = !!columnId;
 
     return (
-      <Column ref={this.setRef} label={`#${id}`}>
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}>
         <ColumnHeader
           icon='hashtag'
           active={hasUnread}
@@ -157,6 +157,7 @@ class HashtagTimeline extends React.PureComponent {
           onLoadMore={this.handleLoadMore}
           emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
           shouldUpdateScroll={shouldUpdateScroll}
+          bindToDocument={!multiColumn}
         />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js
index 097f91c16ca34472cd47c3fe91de9d627b915fdb..1cafb88eda51b76d844d79ca5ad26a36b7a800d8 100644
--- a/app/javascript/mastodon/features/home_timeline/index.js
+++ b/app/javascript/mastodon/features/home_timeline/index.js
@@ -98,7 +98,7 @@ class HomeTimeline extends React.PureComponent {
     const pinned = !!columnId;
 
     return (
-      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
         <ColumnHeader
           icon='home'
           active={hasUnread}
@@ -119,6 +119,7 @@ class HomeTimeline extends React.PureComponent {
           timelineId='home'
           emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Visit {public} or use search to get started and meet other users.' values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />}
           shouldUpdateScroll={shouldUpdateScroll}
+          bindToDocument={!multiColumn}
         />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.js b/app/javascript/mastodon/features/keyboard_shortcuts/index.js
index 01b45652c5339aaf62dae187d3d54858fd9f71c1..90dc87cbb0b673eb8a13b332ff726bd9ad06f516 100644
--- a/app/javascript/mastodon/features/keyboard_shortcuts/index.js
+++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.js
@@ -18,10 +18,10 @@ class KeyboardShortcuts extends ImmutablePureComponent {
   };
 
   render () {
-    const { intl } = this.props;
+    const { intl, multiColumn } = this.props;
 
     return (
-      <Column icon='question' heading={intl.formatMessage(messages.heading)}>
+      <Column bindToDocument={!multiColumn} icon='question' heading={intl.formatMessage(messages.heading)}>
         <ColumnBackButtonSlim />
         <div className='keyboard-shortcuts scrollable optionally-scrollable'>
           <table>
diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js
index 0db6d2228299e56eefcdccd73db7a4d7d3bc4948..f3205b2bff196466c48c5982dd6bc30367563cc8 100644
--- a/app/javascript/mastodon/features/list_timeline/index.js
+++ b/app/javascript/mastodon/features/list_timeline/index.js
@@ -148,14 +148,14 @@ class ListTimeline extends React.PureComponent {
     } else if (list === false) {
       return (
         <Column>
-          <ColumnBackButton />
+          <ColumnBackButton multiColumn={multiColumn} />
           <MissingIndicator />
         </Column>
       );
     }
 
     return (
-      <Column ref={this.setRef} label={title}>
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={title}>
         <ColumnHeader
           icon='list-ul'
           active={hasUnread}
@@ -184,6 +184,7 @@ class ListTimeline extends React.PureComponent {
           onLoadMore={this.handleLoadMore}
           emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
           shouldUpdateScroll={shouldUpdateScroll}
+          bindToDocument={!multiColumn}
         />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/lists/index.js b/app/javascript/mastodon/features/lists/index.js
index 015e21b6876ce5115cd47e4eb3c0799c3ae1b136..7f7f5009ca93890e332542be80198b77917e5167 100644
--- a/app/javascript/mastodon/features/lists/index.js
+++ b/app/javascript/mastodon/features/lists/index.js
@@ -40,6 +40,7 @@ class Lists extends ImmutablePureComponent {
     dispatch: PropTypes.func.isRequired,
     lists: ImmutablePropTypes.list,
     intl: PropTypes.object.isRequired,
+    multiColumn: PropTypes.bool,
   };
 
   componentWillMount () {
@@ -47,7 +48,7 @@ class Lists extends ImmutablePureComponent {
   }
 
   render () {
-    const { intl, shouldUpdateScroll, lists } = this.props;
+    const { intl, shouldUpdateScroll, lists, multiColumn } = this.props;
 
     if (!lists) {
       return (
@@ -60,7 +61,7 @@ class Lists extends ImmutablePureComponent {
     const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />;
 
     return (
-      <Column icon='list-ul' heading={intl.formatMessage(messages.heading)}>
+      <Column bindToDocument={!multiColumn} icon='list-ul' heading={intl.formatMessage(messages.heading)}>
         <ColumnBackButtonSlim />
 
         <NewListForm />
@@ -70,6 +71,7 @@ class Lists extends ImmutablePureComponent {
           shouldUpdateScroll={shouldUpdateScroll}
           emptyMessage={emptyMessage}
           prepend={<ColumnSubheading text={intl.formatMessage(messages.subheading)} />}
+          bindToDocument={!multiColumn}
         >
           {lists.map(list =>
             <ColumnLink key={list.get('id')} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />
diff --git a/app/javascript/mastodon/features/mutes/index.js b/app/javascript/mastodon/features/mutes/index.js
index 4ed29a1ce6717907902022d515357c7c832cc03f..91dd268c1630497857cca86f6cc3d9ae8aee5614 100644
--- a/app/javascript/mastodon/features/mutes/index.js
+++ b/app/javascript/mastodon/features/mutes/index.js
@@ -32,6 +32,7 @@ class Mutes extends ImmutablePureComponent {
     hasMore: PropTypes.bool,
     accountIds: ImmutablePropTypes.list,
     intl: PropTypes.object.isRequired,
+    multiColumn: PropTypes.bool,
   };
 
   componentWillMount () {
@@ -43,7 +44,7 @@ class Mutes extends ImmutablePureComponent {
   }, 300, { leading: true });
 
   render () {
-    const { intl, shouldUpdateScroll, hasMore, accountIds } = this.props;
+    const { intl, shouldUpdateScroll, hasMore, accountIds, multiColumn } = this.props;
 
     if (!accountIds) {
       return (
@@ -56,7 +57,7 @@ class Mutes extends ImmutablePureComponent {
     const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
 
     return (
-      <Column icon='volume-off' heading={intl.formatMessage(messages.heading)}>
+      <Column bindToDocument={!multiColumn} icon='volume-off' heading={intl.formatMessage(messages.heading)}>
         <ColumnBackButtonSlim />
         <ScrollableList
           scrollKey='mutes'
@@ -64,6 +65,7 @@ class Mutes extends ImmutablePureComponent {
           hasMore={hasMore}
           shouldUpdateScroll={shouldUpdateScroll}
           emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
         >
           {accountIds.map(id =>
             <AccountContainer key={id} id={id} />
diff --git a/app/javascript/mastodon/features/notifications/components/filter_bar.js b/app/javascript/mastodon/features/notifications/components/filter_bar.js
index 3f3e6ab7d234dfde1db3d585220fc2617b83aa84..2fd28d832678d3425477b6549904504ae5ae4991 100644
--- a/app/javascript/mastodon/features/notifications/components/filter_bar.js
+++ b/app/javascript/mastodon/features/notifications/components/filter_bar.js
@@ -64,7 +64,7 @@ class FilterBar extends React.PureComponent {
           onClick={this.onClick('mention')}
           title={intl.formatMessage(tooltips.mentions)}
         >
-          <Icon id='at' fixedWidth />
+          <Icon id='reply-all' fixedWidth />
         </button>
         <button
           className={selectedFilter === 'favourite' ? 'active' : ''}
diff --git a/app/javascript/mastodon/features/notifications/components/setting_toggle.js b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
index 7aec16d2ea71ed267e21e94c3c2648ab8d65eb27..e6f593ef89f606375cb88e0ca82f26045ffcb232 100644
--- a/app/javascript/mastodon/features/notifications/components/setting_toggle.js
+++ b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
@@ -11,6 +11,7 @@ export default class SettingToggle extends React.PureComponent {
     settingPath: PropTypes.array.isRequired,
     label: PropTypes.node.isRequired,
     onChange: PropTypes.func.isRequired,
+    defaultValue: PropTypes.bool,
   }
 
   onChange = ({ target }) => {
@@ -18,12 +19,12 @@ export default class SettingToggle extends React.PureComponent {
   }
 
   render () {
-    const { prefix, settings, settingPath, label } = this.props;
+    const { prefix, settings, settingPath, label, defaultValue } = this.props;
     const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-');
 
     return (
       <div className='setting-toggle'>
-        <Toggle id={id} checked={settings.getIn(settingPath)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
+        <Toggle id={id} checked={settings.getIn(settingPath, defaultValue)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
         <label htmlFor={id} className='setting-toggle__label'>{label}</label>
       </div>
     );
diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js
index 006c4565750282b2fb8db24617edf5fddda9aab2..d16a0f33a4e1fb984ac649738533d14a43af3090 100644
--- a/app/javascript/mastodon/features/notifications/index.js
+++ b/app/javascript/mastodon/features/notifications/index.js
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import Column from '../../components/column';
 import ColumnHeader from '../../components/column_header';
-import { expandNotifications, scrollTopNotifications } from '../../actions/notifications';
+import { expandNotifications, scrollTopNotifications, loadPending, mountNotifications, unmountNotifications } from '../../actions/notifications';
 import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
 import NotificationContainer from './containers/notification_container';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
@@ -39,8 +39,9 @@ const mapStateToProps = state => ({
   showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
   notifications: getNotifications(state),
   isLoading: state.getIn(['notifications', 'isLoading'], true),
-  isUnread: state.getIn(['notifications', 'unread']) > 0,
+  isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0,
   hasMore: state.getIn(['notifications', 'hasMore']),
+  numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
 });
 
 export default @connect(mapStateToProps)
@@ -58,17 +59,23 @@ class Notifications extends React.PureComponent {
     isUnread: PropTypes.bool,
     multiColumn: PropTypes.bool,
     hasMore: PropTypes.bool,
+    numPending: PropTypes.number,
   };
 
   static defaultProps = {
     trackScroll: true,
   };
 
+  componentWillMount() {
+    this.props.dispatch(mountNotifications());
+  }
+
   componentWillUnmount () {
     this.handleLoadOlder.cancel();
     this.handleScrollToTop.cancel();
     this.handleScroll.cancel();
     this.props.dispatch(scrollTopNotifications(false));
+    this.props.dispatch(unmountNotifications());
   }
 
   handleLoadGap = (maxId) => {
@@ -80,6 +87,10 @@ class Notifications extends React.PureComponent {
     this.props.dispatch(expandNotifications({ maxId: last && last.get('id') }));
   }, 300, { leading: true });
 
+  handleLoadPending = () => {
+    this.props.dispatch(loadPending());
+  };
+
   handleScrollToTop = debounce(() => {
     this.props.dispatch(scrollTopNotifications(true));
   }, 100);
@@ -136,7 +147,7 @@ class Notifications extends React.PureComponent {
   }
 
   render () {
-    const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, showFilterBar } = this.props;
+    const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar } = this.props;
     const pinned = !!columnId;
     const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;
 
@@ -178,18 +189,21 @@ class Notifications extends React.PureComponent {
         isLoading={isLoading}
         showLoading={isLoading && notifications.size === 0}
         hasMore={hasMore}
+        numPending={numPending}
         emptyMessage={emptyMessage}
         onLoadMore={this.handleLoadOlder}
+        onLoadPending={this.handleLoadPending}
         onScrollToTop={this.handleScrollToTop}
         onScroll={this.handleScroll}
         shouldUpdateScroll={shouldUpdateScroll}
+        bindToDocument={!multiColumn}
       >
         {scrollableContent}
       </ScrollableList>
     );
 
     return (
-      <Column ref={this.setColumnRef} label={intl.formatMessage(messages.title)}>
+      <Column bindToDocument={!multiColumn} ref={this.setColumnRef} label={intl.formatMessage(messages.title)}>
         <ColumnHeader
           icon='bell'
           active={isUnread}
diff --git a/app/javascript/mastodon/features/pinned_statuses/index.js b/app/javascript/mastodon/features/pinned_statuses/index.js
index 98cdbda3c492c0c6d3005ff532270fa38d98d5fb..ad5c9cafc367fceec9ff966a8bdad7921c45f85a 100644
--- a/app/javascript/mastodon/features/pinned_statuses/index.js
+++ b/app/javascript/mastodon/features/pinned_statuses/index.js
@@ -28,6 +28,7 @@ class PinnedStatuses extends ImmutablePureComponent {
     statusIds: ImmutablePropTypes.list.isRequired,
     intl: PropTypes.object.isRequired,
     hasMore: PropTypes.bool.isRequired,
+    multiColumn: PropTypes.bool,
   };
 
   componentWillMount () {
@@ -43,16 +44,17 @@ class PinnedStatuses extends ImmutablePureComponent {
   }
 
   render () {
-    const { intl, shouldUpdateScroll, statusIds, hasMore } = this.props;
+    const { intl, shouldUpdateScroll, statusIds, hasMore, multiColumn } = this.props;
 
     return (
-      <Column icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}>
+      <Column bindToDocument={!multiColumn} icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}>
         <ColumnBackButtonSlim />
         <StatusList
           statusIds={statusIds}
           scrollKey='pinned_statuses'
           hasMore={hasMore}
           shouldUpdateScroll={shouldUpdateScroll}
+          bindToDocument={!multiColumn}
         />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js
index 2b7d9c56f99519b08a92e751d05a1eccdc33c330..e7825e2366493bea810c6fb375bfca110b8135d9 100644
--- a/app/javascript/mastodon/features/public_timeline/index.js
+++ b/app/javascript/mastodon/features/public_timeline/index.js
@@ -105,7 +105,7 @@ class PublicTimeline extends React.PureComponent {
     const pinned = !!columnId;
 
     return (
-      <Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
+      <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
         <ColumnHeader
           icon='globe'
           active={hasUnread}
@@ -126,6 +126,7 @@ class PublicTimeline extends React.PureComponent {
           scrollKey={`public_timeline-${columnId}`}
           emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />}
           shouldUpdateScroll={shouldUpdateScroll}
+          bindToDocument={!multiColumn}
         />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/reblogs/index.js b/app/javascript/mastodon/features/reblogs/index.js
index c05d21c740451c9dd6753deea26ce0c373446150..9179e51dbb2437c7aacaa2b0a04c3f30ae504f9f 100644
--- a/app/javascript/mastodon/features/reblogs/index.js
+++ b/app/javascript/mastodon/features/reblogs/index.js
@@ -5,17 +5,23 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import LoadingIndicator from '../../components/loading_indicator';
 import { fetchReblogs } from '../../actions/interactions';
-import { FormattedMessage } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import AccountContainer from '../../containers/account_container';
 import Column from '../ui/components/column';
-import ColumnBackButton from '../../components/column_back_button';
 import ScrollableList from '../../components/scrollable_list';
+import Icon from 'mastodon/components/icon';
+import ColumnHeader from '../../components/column_header';
+
+const messages = defineMessages({
+  refresh: { id: 'refresh', defaultMessage: 'Refresh' },
+});
 
 const mapStateToProps = (state, props) => ({
   accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]),
 });
 
 export default @connect(mapStateToProps)
+@injectIntl
 class Reblogs extends ImmutablePureComponent {
 
   static propTypes = {
@@ -23,10 +29,14 @@ class Reblogs extends ImmutablePureComponent {
     dispatch: PropTypes.func.isRequired,
     shouldUpdateScroll: PropTypes.func,
     accountIds: ImmutablePropTypes.list,
+    multiColumn: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
   };
 
   componentWillMount () {
-    this.props.dispatch(fetchReblogs(this.props.params.statusId));
+    if (!this.props.accountIds) {
+      this.props.dispatch(fetchReblogs(this.props.params.statusId));
+    }
   }
 
   componentWillReceiveProps(nextProps) {
@@ -35,8 +45,12 @@ class Reblogs extends ImmutablePureComponent {
     }
   }
 
+  handleRefresh = () => {
+    this.props.dispatch(fetchReblogs(this.props.params.statusId));
+  }
+
   render () {
-    const { shouldUpdateScroll, accountIds } = this.props;
+    const { intl, shouldUpdateScroll, accountIds, multiColumn } = this.props;
 
     if (!accountIds) {
       return (
@@ -49,13 +63,20 @@ class Reblogs extends ImmutablePureComponent {
     const emptyMessage = <FormattedMessage id='status.reblogs.empty' defaultMessage='No one has boosted this toot yet. When someone does, they will show up here.' />;
 
     return (
-      <Column>
-        <ColumnBackButton />
+      <Column bindToDocument={!multiColumn}>
+        <ColumnHeader
+          showBackButton
+          multiColumn={multiColumn}
+          extraButton={(
+            <button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
+          )}
+        />
 
         <ScrollableList
           scrollKey='reblogs'
           shouldUpdateScroll={shouldUpdateScroll}
           emptyMessage={emptyMessage}
+          bindToDocument={!multiColumn}
         >
           {accountIds.map(id =>
             <AccountContainer key={id} id={id} withNote={false} />
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index 59be364888c31d337f82080177d41ee4bf0759c3..a0838740a7dbe8feda2e5ea7100179707ad6d340 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -10,6 +10,7 @@ import { defineMessages, injectIntl, FormattedDate, FormattedNumber } from 'reac
 import Card from './card';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import Video from '../../video';
+import Audio from '../../audio';
 import scheduleIdleTask from '../../ui/util/schedule_idle_task';
 import classNames from 'classnames';
 import Icon from 'mastodon/components/icon';
@@ -114,7 +115,19 @@ export default class DetailedStatus extends ImmutablePureComponent {
     }
 
     if (status.get('media_attachments').size > 0) {
-      if (['video', 'audio'].includes(status.getIn(['media_attachments', 0, 'type']))) {
+      if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
+        const attachment = status.getIn(['media_attachments', 0]);
+
+        media = (
+          <Audio
+            src={attachment.get('url')}
+            alt={attachment.get('description')}
+            duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
+            height={110}
+            preload
+          />
+        );
+      } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
         const attachment = status.getIn(['media_attachments', 0]);
 
         media = (
diff --git a/app/javascript/mastodon/features/status/containers/detailed_status_container.js b/app/javascript/mastodon/features/status/containers/detailed_status_container.js
index 61e0c428a108c49443428f14d49b063dd655e08e..333c295dc6c11915dd220e69064f536a99a57388 100644
--- a/app/javascript/mastodon/features/status/containers/detailed_status_container.js
+++ b/app/javascript/mastodon/features/status/containers/detailed_status_container.js
@@ -1,4 +1,3 @@
-import React from 'react';
 import { connect } from 'react-redux';
 import DetailedStatus from '../components/detailed_status';
 import { makeGetStatus } from '../../../selectors';
@@ -15,7 +14,6 @@ import {
   pin,
   unpin,
 } from '../../../actions/interactions';
-import { blockAccount } from '../../../actions/accounts';
 import {
   muteStatus,
   unmuteStatus,
@@ -24,9 +22,10 @@ import {
   revealStatus,
 } from '../../../actions/statuses';
 import { initMuteModal } from '../../../actions/mutes';
+import { initBlockModal } from '../../../actions/blocks';
 import { initReport } from '../../../actions/reports';
 import { openModal } from '../../../actions/modal';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
 import { boostModal, deleteModal } from '../../../initial_state';
 import { showAlertForError } from '../../../actions/alerts';
 
@@ -35,10 +34,8 @@ const messages = defineMessages({
   deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
   redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
   redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
-  blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
   replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
   replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
-  blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
 });
 
 const makeMapStateToProps = () => {
@@ -138,16 +135,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 
   onBlock (status) {
     const account = status.get('account');
-    dispatch(openModal('CONFIRM', {
-      message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
-      confirm: intl.formatMessage(messages.blockConfirm),
-      onConfirm: () => dispatch(blockAccount(account.get('id'))),
-      secondary: intl.formatMessage(messages.blockAndReport),
-      onSecondary: () => {
-        dispatch(blockAccount(account.get('id')));
-        dispatch(initReport(account, status));
-      },
-    }));
+    dispatch(initBlockModal(account));
   },
 
   onReport (status) {
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index 0422111ae38e1a1e76a361e26f216dae888fa3b1..029057d4076e1279d17aa91cc09b7e2c9f1c75b4 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -23,7 +23,6 @@ import {
   mentionCompose,
   directCompose,
 } from '../../actions/compose';
-import { blockAccount } from '../../actions/accounts';
 import {
   muteStatus,
   unmuteStatus,
@@ -32,6 +31,7 @@ import {
   revealStatus,
 } from '../../actions/statuses';
 import { initMuteModal } from '../../actions/mutes';
+import { initBlockModal } from '../../actions/blocks';
 import { initReport } from '../../actions/reports';
 import { makeGetStatus } from '../../selectors';
 import { ScrollContainer } from 'react-router-scroll-4';
@@ -39,7 +39,7 @@ import ColumnBackButton from '../../components/column_back_button';
 import ColumnHeader from '../../components/column_header';
 import StatusContainer from '../../containers/status_container';
 import { openModal } from '../../actions/modal';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { HotKeys } from 'react-hotkeys';
 import { boostModal, deleteModal } from '../../initial_state';
@@ -52,13 +52,11 @@ const messages = defineMessages({
   deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
   redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
   redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
-  blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
   revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
   hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
   detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
   replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
   replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
-  blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
 });
 
 const makeMapStateToProps = () => {
@@ -84,28 +82,38 @@ const makeMapStateToProps = () => {
   const getDescendantsIds = createSelector([
     (_, { id }) => id,
     state => state.getIn(['contexts', 'replies']),
-  ], (statusId, contextReplies) => {
-    let descendantsIds = Immutable.List();
-    descendantsIds = descendantsIds.withMutations(mutable => {
-      const ids = [statusId];
+    state => state.get('statuses'),
+  ], (statusId, contextReplies, statuses) => {
+    let descendantsIds = [];
+    const ids = [statusId];
 
-      while (ids.length > 0) {
-        let id        = ids.shift();
-        const replies = contextReplies.get(id);
+    while (ids.length > 0) {
+      let id        = ids.shift();
+      const replies = contextReplies.get(id);
 
-        if (statusId !== id) {
-          mutable.push(id);
-        }
+      if (statusId !== id) {
+        descendantsIds.push(id);
+      }
 
-        if (replies) {
-          replies.reverse().forEach(reply => {
-            ids.unshift(reply);
-          });
-        }
+      if (replies) {
+        replies.reverse().forEach(reply => {
+          ids.unshift(reply);
+        });
       }
-    });
+    }
 
-    return descendantsIds;
+    let insertAt = descendantsIds.findIndex((id) => statuses.get(id).get('in_reply_to_account_id') !== statuses.get(id).get('account'));
+    if (insertAt !== -1) {
+      descendantsIds.forEach((id, idx) => {
+        if (idx > insertAt && statuses.get(id).get('in_reply_to_account_id') === statuses.get(id).get('account')) {
+          descendantsIds.splice(idx, 1);
+          descendantsIds.splice(insertAt, 0, id);
+          insertAt += 1;
+        }
+      });
+    }
+
+    return Immutable.List(descendantsIds);
   });
 
   const mapStateToProps = (state, props) => {
@@ -146,6 +154,7 @@ class Status extends ImmutablePureComponent {
     descendantsIds: ImmutablePropTypes.list,
     intl: PropTypes.object.isRequired,
     askReplyConfirmation: PropTypes.bool,
+    multiColumn: PropTypes.bool,
     domain: PropTypes.string.isRequired,
   };
 
@@ -285,19 +294,9 @@ class Status extends ImmutablePureComponent {
   }
 
   handleBlockClick = (status) => {
-    const { dispatch, intl } = this.props;
+    const { dispatch } = this.props;
     const account = status.get('account');
-
-    dispatch(openModal('CONFIRM', {
-      message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
-      confirm: intl.formatMessage(messages.blockConfirm),
-      onConfirm: () => dispatch(blockAccount(account.get('id'))),
-      secondary: intl.formatMessage(messages.blockAndReport),
-      onSecondary: () => {
-        dispatch(blockAccount(account.get('id')));
-        dispatch(initReport(account, status));
-      },
-    }));
+    dispatch(initBlockModal(account));
   }
 
   handleReport = (status) => {
@@ -437,13 +436,13 @@ class Status extends ImmutablePureComponent {
 
   render () {
     let ancestors, descendants;
-    const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl, domain } = this.props;
+    const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl, domain, multiColumn } = this.props;
     const { fullscreen } = this.state;
 
     if (status === null) {
       return (
         <Column>
-          <ColumnBackButton />
+          <ColumnBackButton multiColumn={multiColumn} />
           <MissingIndicator />
         </Column>
       );
@@ -470,9 +469,10 @@ class Status extends ImmutablePureComponent {
     };
 
     return (
-      <Column label={intl.formatMessage(messages.detailedStatus)}>
+      <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.detailedStatus)}>
         <ColumnHeader
           showBackButton
+          multiColumn={multiColumn}
           extraButton={(
             <button className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button>
           )}
diff --git a/app/javascript/mastodon/features/ui/components/audio_modal.js b/app/javascript/mastodon/features/ui/components/audio_modal.js
new file mode 100644
index 0000000000000000000000000000000000000000..2300453d7c51181428bab2f50212f74488192b11
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/audio_modal.js
@@ -0,0 +1,76 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import Audio from 'mastodon/features/audio';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { FormattedMessage } from 'react-intl';
+import { previewState } from './video_modal';
+import classNames from 'classnames';
+import Icon from 'mastodon/components/icon';
+
+export default class AudioModal extends ImmutablePureComponent {
+
+  static propTypes = {
+    media: ImmutablePropTypes.map.isRequired,
+    status: ImmutablePropTypes.map,
+    onClose: PropTypes.func.isRequired,
+  };
+
+  static contextTypes = {
+    router: PropTypes.object,
+  };
+
+  componentDidMount () {
+    if (this.context.router) {
+      const history = this.context.router.history;
+
+      history.push(history.location.pathname, previewState);
+
+      this.unlistenHistory = history.listen(() => {
+        this.props.onClose();
+      });
+    }
+  }
+
+  componentWillUnmount () {
+    if (this.context.router) {
+      this.unlistenHistory();
+
+      if (this.context.router.history.location.state === previewState) {
+        this.context.router.history.goBack();
+      }
+    }
+  }
+
+  handleStatusClick = e => {
+    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+      e.preventDefault();
+      this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
+    }
+  }
+
+  render () {
+    const { media, status } = this.props;
+
+    return (
+      <div className='modal-root__modal audio-modal'>
+        <div className='audio-modal__container'>
+          <Audio
+            src={media.get('url')}
+            alt={media.get('description')}
+            duration={media.getIn(['meta', 'original', 'duration'], 0)}
+            height={135}
+            preload
+          />
+        </div>
+
+        {status && (
+          <div className={classNames('media-modal__meta')}>
+            <a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
+          </div>
+        )}
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/ui/components/block_modal.js b/app/javascript/mastodon/features/ui/components/block_modal.js
new file mode 100644
index 0000000000000000000000000000000000000000..a07baeaa64a450768dc2ede4e01744550a3d3a2e
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/block_modal.js
@@ -0,0 +1,103 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import { injectIntl, FormattedMessage } from 'react-intl';
+import { makeGetAccount } from '../../../selectors';
+import Button from '../../../components/button';
+import { closeModal } from '../../../actions/modal';
+import { blockAccount } from '../../../actions/accounts';
+import { initReport } from '../../../actions/reports';
+
+
+const makeMapStateToProps = () => {
+  const getAccount = makeGetAccount();
+
+  const mapStateToProps = state => ({
+    account: getAccount(state, state.getIn(['blocks', 'new', 'account_id'])),
+  });
+
+  return mapStateToProps;
+};
+
+const mapDispatchToProps = dispatch => {
+  return {
+    onConfirm(account) {
+      dispatch(blockAccount(account.get('id')));
+    },
+
+    onBlockAndReport(account) {
+      dispatch(blockAccount(account.get('id')));
+      dispatch(initReport(account));
+    },
+
+    onClose() {
+      dispatch(closeModal());
+    },
+  };
+};
+
+export default @connect(makeMapStateToProps, mapDispatchToProps)
+@injectIntl
+class BlockModal extends React.PureComponent {
+
+  static propTypes = {
+    account: PropTypes.object.isRequired,
+    onClose: PropTypes.func.isRequired,
+    onBlockAndReport: PropTypes.func.isRequired,
+    onConfirm: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  componentDidMount() {
+    this.button.focus();
+  }
+
+  handleClick = () => {
+    this.props.onClose();
+    this.props.onConfirm(this.props.account);
+  }
+
+  handleSecondary = () => {
+    this.props.onClose();
+    this.props.onBlockAndReport(this.props.account);
+  }
+
+  handleCancel = () => {
+    this.props.onClose();
+  }
+
+  setRef = (c) => {
+    this.button = c;
+  }
+
+  render () {
+    const { account } = this.props;
+
+    return (
+      <div className='modal-root__modal block-modal'>
+        <div className='block-modal__container'>
+          <p>
+            <FormattedMessage
+              id='confirmations.block.message'
+              defaultMessage='Are you sure you want to block {name}?'
+              values={{ name: <strong>@{account.get('acct')}</strong> }}
+            />
+          </p>
+        </div>
+
+        <div className='block-modal__action-bar'>
+          <Button onClick={this.handleCancel} className='block-modal__cancel-button'>
+            <FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
+          </Button>
+          <Button onClick={this.handleSecondary} className='confirmation-modal__secondary-button'>
+            <FormattedMessage id='confirmations.block.block_and_report' defaultMessage='Block & Report' />
+          </Button>
+          <Button onClick={this.handleClick} ref={this.setRef}>
+            <FormattedMessage id='confirmations.block.confirm' defaultMessage='Block' />
+          </Button>
+        </div>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/ui/components/column_loading.js b/app/javascript/mastodon/features/ui/components/column_loading.js
index 9503a7a1ac60da8b1c7425258020f9c07f241ce6..0cdfd05d80e2a4248b555093c1593f6fe551d648 100644
--- a/app/javascript/mastodon/features/ui/components/column_loading.js
+++ b/app/javascript/mastodon/features/ui/components/column_loading.js
@@ -21,7 +21,7 @@ export default class ColumnLoading extends ImmutablePureComponent {
     let { title, icon } = this.props;
     return (
       <Column>
-        <ColumnHeader icon={icon} title={title} multiColumn={false} focusable={false} />
+        <ColumnHeader icon={icon} title={title} multiColumn={false} focusable={false} placeholder />
         <div className='scrollable' />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js
index 042e44e43e9f46cb42518941a7544a25b41688bb..8a4e89b3de40c6c734515dffd0173f7eebddbc0c 100644
--- a/app/javascript/mastodon/features/ui/components/columns_area.js
+++ b/app/javascript/mastodon/features/ui/components/columns_area.js
@@ -12,7 +12,18 @@ import BundleContainer from '../containers/bundle_container';
 import ColumnLoading from './column_loading';
 import DrawerLoading from './drawer_loading';
 import BundleColumnError from './bundle_column_error';
-import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, ListTimeline } from '../../ui/util/async-components';
+import {
+  Compose,
+  Notifications,
+  HomeTimeline,
+  CommunityTimeline,
+  PublicTimeline,
+  HashtagTimeline,
+  DirectTimeline,
+  FavouritedStatuses,
+  ListTimeline,
+  Directory,
+} from '../../ui/util/async-components';
 import Icon from 'mastodon/components/icon';
 import ComposePanel from './compose_panel';
 import NavigationPanel from './navigation_panel';
@@ -30,6 +41,7 @@ const componentMap = {
   'DIRECT': DirectTimeline,
   'FAVOURITES': FavouritedStatuses,
   'LIST': ListTimeline,
+  'DIRECTORY': Directory,
 };
 
 const messages = defineMessages({
diff --git a/app/javascript/mastodon/features/ui/components/document_title.js b/app/javascript/mastodon/features/ui/components/document_title.js
new file mode 100644
index 0000000000000000000000000000000000000000..cd081b20c7e2c7c2ad30659ae3ce2d38cfe0b355
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/document_title.js
@@ -0,0 +1,41 @@
+import { PureComponent } from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import { title } from 'mastodon/initial_state';
+
+const mapStateToProps = state => ({
+  unread: state.getIn(['missed_updates', 'unread']),
+});
+
+export default @connect(mapStateToProps)
+class DocumentTitle extends PureComponent {
+
+  static propTypes = {
+    unread: PropTypes.number.isRequired,
+  };
+
+  componentDidMount () {
+    this._sideEffects();
+  }
+
+  componentDidUpdate() {
+    this._sideEffects();
+  }
+
+  _sideEffects () {
+    const { unread } = this.props;
+
+    if (unread > 99) {
+      document.title = `(*) ${title}`;
+    } else if (unread > 0) {
+      document.title = `(${unread}) ${title}`;
+    } else {
+      document.title = title;
+    }
+  }
+
+  render () {
+    return null;
+  }
+
+}
diff --git a/app/javascript/mastodon/features/ui/components/embed_modal.js b/app/javascript/mastodon/features/ui/components/embed_modal.js
index 982781db073d69319032ed809f6b01089e784c52..4679c9650fdc439bc3a2f49eea90e7ed8ba5f7b1 100644
--- a/app/javascript/mastodon/features/ui/components/embed_modal.js
+++ b/app/javascript/mastodon/features/ui/components/embed_modal.js
@@ -1,8 +1,13 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import ImmutablePureComponent from 'react-immutable-pure-component';
-import { FormattedMessage, injectIntl } from 'react-intl';
-import api from '../../../api';
+import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
+import api from 'mastodon/api';
+import IconButton from 'mastodon/components/icon_button';
+
+const messages = defineMessages({
+  close: { id: 'lightbox.close', defaultMessage: 'Close' },
+});
 
 export default @injectIntl
 class EmbedModal extends ImmutablePureComponent {
@@ -50,13 +55,17 @@ class EmbedModal extends ImmutablePureComponent {
   }
 
   render () {
+    const { intl, onClose } = this.props;
     const { oembed } = this.state;
 
     return (
-      <div className='modal-root__modal embed-modal'>
-        <h4><FormattedMessage id='status.embed' defaultMessage='Embed' /></h4>
+      <div className='modal-root__modal report-modal embed-modal'>
+        <div className='report-modal__target'>
+          <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
+          <FormattedMessage id='status.embed' defaultMessage='Embed' />
+        </div>
 
-        <div className='embed-modal__container'>
+        <div className='report-modal__container embed-modal__container' style={{ display: 'block' }}>
           <p className='hint'>
             <FormattedMessage id='embed.instructions' defaultMessage='Embed this status on your website by copying the code below.' />
           </p>
diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.js b/app/javascript/mastodon/features/ui/components/focal_point_modal.js
index 7488a3598bc143608c9985b7b4329fc600b07187..3694ab9046a9123d2e9f0aee0e6d8153a4d7e33a 100644
--- a/app/javascript/mastodon/features/ui/components/focal_point_modal.js
+++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.js
@@ -1,11 +1,28 @@
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { connect } from 'react-redux';
-import ImageLoader from './image_loader';
 import classNames from 'classnames';
 import { changeUploadCompose } from '../../../actions/compose';
 import { getPointerPosition } from '../../video';
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
+import IconButton from 'mastodon/components/icon_button';
+import Button from 'mastodon/components/button';
+import Video from 'mastodon/features/video';
+import Audio from 'mastodon/features/audio';
+import Textarea from 'react-textarea-autosize';
+import UploadProgress from 'mastodon/features/compose/components/upload_progress';
+import CharacterCounter from 'mastodon/features/compose/components/character_counter';
+import { length } from 'stringz';
+import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
+import GIFV from 'mastodon/components/gifv';
+
+const messages = defineMessages({
+  close: { id: 'lightbox.close', defaultMessage: 'Close' },
+  apply: { id: 'upload_modal.apply', defaultMessage: 'Apply' },
+  placeholder: { id: 'upload_modal.description_placeholder', defaultMessage: 'A quick brown fox jumps over the lazy dog' },
+});
 
 const mapStateToProps = (state, { id }) => ({
   media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
@@ -13,17 +30,56 @@ const mapStateToProps = (state, { id }) => ({
 
 const mapDispatchToProps = (dispatch, { id }) => ({
 
-  onSave: (x, y) => {
-    dispatch(changeUploadCompose(id, { focus: `${x.toFixed(2)},${y.toFixed(2)}` }));
+  onSave: (description, x, y) => {
+    dispatch(changeUploadCompose(id, { description, focus: `${x.toFixed(2)},${y.toFixed(2)}` }));
   },
 
 });
 
+const removeExtraLineBreaks = str => str.replace(/\n\n/g, '******')
+  .replace(/\n/g, ' ')
+  .replace(/\*\*\*\*\*\*/g, '\n\n');
+
+const assetHost = process.env.CDN_HOST || '';
+
+class ImageLoader extends React.PureComponent {
+
+  static propTypes = {
+    src: PropTypes.string.isRequired,
+    width: PropTypes.number,
+    height: PropTypes.number,
+  };
+
+  state = {
+    loading: true,
+  };
+
+  componentDidMount() {
+    const image = new Image();
+    image.addEventListener('load', () => this.setState({ loading: false }));
+    image.src = this.props.src;
+  }
+
+  render () {
+    const { loading } = this.state;
+
+    if (loading) {
+      return <canvas width={this.props.width} height={this.props.height} />;
+    } else {
+      return <img {...this.props} alt='' />;
+    }
+  }
+
+}
+
 export default @connect(mapStateToProps, mapDispatchToProps)
+@injectIntl
 class FocalPointModal extends ImmutablePureComponent {
 
   static propTypes = {
     media: ImmutablePropTypes.map.isRequired,
+    onClose: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
   };
 
   state = {
@@ -32,6 +88,10 @@ class FocalPointModal extends ImmutablePureComponent {
     focusX: 0,
     focusY: 0,
     dragging: false,
+    description: '',
+    dirty: false,
+    progress: 0,
+    loading: true,
   };
 
   componentWillMount () {
@@ -57,6 +117,14 @@ class FocalPointModal extends ImmutablePureComponent {
     this.setState({ dragging: true });
   }
 
+  handleTouchStart = e => {
+    document.addEventListener('touchmove', this.handleMouseMove);
+    document.addEventListener('touchend', this.handleTouchEnd);
+
+    this.updatePosition(e);
+    this.setState({ dragging: true });
+  }
+
   handleMouseMove = e => {
     this.updatePosition(e);
   }
@@ -66,7 +134,13 @@ class FocalPointModal extends ImmutablePureComponent {
     document.removeEventListener('mouseup', this.handleMouseUp);
 
     this.setState({ dragging: false });
-    this.props.onSave(this.state.focusX, this.state.focusY);
+  }
+
+  handleTouchEnd = () => {
+    document.removeEventListener('touchmove', this.handleMouseMove);
+    document.removeEventListener('touchend', this.handleTouchEnd);
+
+    this.setState({ dragging: false });
   }
 
   updatePosition = e => {
@@ -74,46 +148,166 @@ class FocalPointModal extends ImmutablePureComponent {
     const focusX   = (x - .5) *  2;
     const focusY   = (y - .5) * -2;
 
-    this.setState({ x, y, focusX, focusY });
+    this.setState({ x, y, focusX, focusY, dirty: true });
   }
 
   updatePositionFromMedia = media => {
-    const focusX = media.getIn(['meta', 'focus', 'x']);
-    const focusY = media.getIn(['meta', 'focus', 'y']);
+    const focusX      = media.getIn(['meta', 'focus', 'x']);
+    const focusY      = media.getIn(['meta', 'focus', 'y']);
+    const description = media.get('description') || '';
 
     if (focusX && focusY) {
       const x = (focusX /  2) + .5;
       const y = (focusY / -2) + .5;
 
-      this.setState({ x, y, focusX, focusY });
+      this.setState({
+        x,
+        y,
+        focusX,
+        focusY,
+        description,
+        dirty: false,
+      });
     } else {
-      this.setState({ x: 0.5, y: 0.5, focusX: 0, focusY: 0 });
+      this.setState({
+        x: 0.5,
+        y: 0.5,
+        focusX: 0,
+        focusY: 0,
+        description,
+        dirty: false,
+      });
     }
   }
 
+  handleChange = e => {
+    this.setState({ description: e.target.value, dirty: true });
+  }
+
+  handleSubmit = () => {
+    this.props.onSave(this.state.description, this.state.focusX, this.state.focusY);
+    this.props.onClose();
+  }
+
   setRef = c => {
     this.node = c;
   }
 
-  render () {
+  handleTextDetection = () => {
     const { media } = this.props;
-    const { x, y, dragging } = this.state;
+
+    this.setState({ detecting: true });
+
+    fetchTesseract().then(({ TesseractWorker }) => {
+      const worker = new TesseractWorker({
+        workerPath: `${assetHost}/packs/ocr/worker.min.js`,
+        corePath: `${assetHost}/packs/ocr/tesseract-core.wasm.js`,
+        langPath: `${assetHost}/ocr/lang-data`,
+      });
+
+      let media_url = media.get('file');
+
+      if (window.URL && URL.createObjectURL) {
+        try {
+          media_url = URL.createObjectURL(media.get('file'));
+        } catch (error) {
+          console.error(error);
+        }
+      }
+
+      worker.recognize(media_url)
+        .progress(({ progress }) => this.setState({ progress }))
+        .finally(() => worker.terminate())
+        .then(({ text }) => this.setState({ description: removeExtraLineBreaks(text), dirty: true, detecting: false }))
+        .catch(() => this.setState({ detecting: false }));
+    }).catch(() => this.setState({ detecting: false }));
+  }
+
+  render () {
+    const { media, intl, onClose } = this.props;
+    const { x, y, dragging, description, dirty, detecting, progress } = this.state;
 
     const width  = media.getIn(['meta', 'original', 'width']) || null;
     const height = media.getIn(['meta', 'original', 'height']) || null;
+    const focals = ['image', 'gifv'].includes(media.get('type'));
+
+    const previewRatio  = 16/9;
+    const previewWidth  = 200;
+    const previewHeight = previewWidth / previewRatio;
 
     return (
-      <div className='modal-root__modal video-modal focal-point-modal'>
-        <div className={classNames('focal-point', { dragging })} ref={this.setRef}>
-          <ImageLoader
-            previewSrc={media.get('preview_url')}
-            src={media.get('url')}
-            width={width}
-            height={height}
-          />
-
-          <div className='focal-point__reticle' style={{ top: `${y * 100}%`, left: `${x * 100}%` }} />
-          <div className='focal-point__overlay' onMouseDown={this.handleMouseDown} />
+      <div className='modal-root__modal report-modal' style={{ maxWidth: 960 }}>
+        <div className='report-modal__target'>
+          <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
+          <FormattedMessage id='upload_modal.edit_media' defaultMessage='Edit media' />
+        </div>
+
+        <div className='report-modal__container'>
+          <div className='report-modal__comment'>
+            {focals && <p><FormattedMessage id='upload_modal.hint' defaultMessage='Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.' /></p>}
+
+            <label className='setting-text-label' htmlFor='upload-modal__description'><FormattedMessage id='upload_form.description' defaultMessage='Describe for the visually impaired' /></label>
+
+            <div className='setting-text__wrapper'>
+              <Textarea
+                id='upload-modal__description'
+                className='setting-text light'
+                value={detecting ? '…' : description}
+                onChange={this.handleChange}
+                disabled={detecting}
+                autoFocus
+              />
+
+              <div className='setting-text__modifiers'>
+                <UploadProgress progress={progress * 100} active={detecting} icon='file-text-o' message={<FormattedMessage id='upload_modal.analyzing_picture' defaultMessage='Analyzing picture…' />} />
+              </div>
+            </div>
+
+            <div className='setting-text__toolbar'>
+              <button disabled={detecting || media.get('type') !== 'image'} className='link-button' onClick={this.handleTextDetection}><FormattedMessage id='upload_modal.detect_text' defaultMessage='Detect text from picture' /></button>
+              <CharacterCounter max={1500} text={detecting ? '' : description} />
+            </div>
+
+            <Button disabled={!dirty || detecting || length(description) > 1500} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} />
+          </div>
+
+          <div className='focal-point-modal__content'>
+            {focals && (
+              <div className={classNames('focal-point', { dragging })} ref={this.setRef} onMouseDown={this.handleMouseDown} onTouchStart={this.handleTouchStart}>
+                {media.get('type') === 'image' && <ImageLoader src={media.get('url')} width={width} height={height} alt='' />}
+                {media.get('type') === 'gifv' && <GIFV src={media.get('url')} width={width} height={height} />}
+
+                <div className='focal-point__preview'>
+                  <strong><FormattedMessage id='upload_modal.preview_label' defaultMessage='Preview ({ratio})' values={{ ratio: '16:9' }} /></strong>
+                  <div style={{ width: previewWidth, height: previewHeight, backgroundImage: `url(${media.get('preview_url')})`, backgroundSize: 'cover', backgroundPosition: `${x * 100}% ${y * 100}%` }} />
+                </div>
+
+                <div className='focal-point__reticle' style={{ top: `${y * 100}%`, left: `${x * 100}%` }} />
+                <div className='focal-point__overlay' />
+              </div>
+            )}
+
+            {media.get('type') === 'video' && (
+              <Video
+                preview={media.get('preview_url')}
+                blurhash={media.get('blurhash')}
+                src={media.get('url')}
+                detailed
+                inline
+                editable
+              />
+            )}
+
+            {media.get('type') === 'audio' && (
+              <Audio
+                src={media.get('url')}
+                duration={media.getIn(['meta', 'original', 'duration'], 0)}
+                height={150}
+                preload
+                editable
+              />
+            )}
+          </div>
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/features/ui/components/link_footer.js b/app/javascript/mastodon/features/ui/components/link_footer.js
index b481983dc88649fbb01580d9546ba979e4d41c17..2b9bd3875e259d9f0713a1cdc0a087e03786eb84 100644
--- a/app/javascript/mastodon/features/ui/components/link_footer.js
+++ b/app/javascript/mastodon/features/ui/components/link_footer.js
@@ -1,35 +1,72 @@
+import { connect } from 'react-redux';
 import React from 'react';
 import PropTypes from 'prop-types';
-import { FormattedMessage } from 'react-intl';
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
 import { Link } from 'react-router-dom';
 import { invitesEnabled, version, repository, source_url } from 'mastodon/initial_state';
+import { logOut } from 'mastodon/utils/log_out';
+import { openModal } from 'mastodon/actions/modal';
 
-const LinkFooter = ({ withHotkeys }) => (
-  <div className='getting-started__footer'>
-    <ul>
-      {invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
-      {withHotkeys && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
-      <li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
-      <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>
-      <li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
-      <li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
-      <li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
-      <li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
-      <li><a href='/auth/sign_out' data-method='delete'><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>
-    </ul>
-
-    <p>
-      <FormattedMessage
-        id='getting_started.open_source_notice'
-        defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
-        values={{ github: <span><a href={source_url} rel='noopener' target='_blank'>{repository}</a> (v{version})</span> }}
-      />
-    </p>
-  </div>
-);
-
-LinkFooter.propTypes = {
-  withHotkeys: PropTypes.bool,
-};
+const messages = defineMessages({
+  logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
+  logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
+});
+
+const mapDispatchToProps = (dispatch, { intl }) => ({
+  onLogout () {
+    dispatch(openModal('CONFIRM', {
+      message: intl.formatMessage(messages.logoutMessage),
+      confirm: intl.formatMessage(messages.logoutConfirm),
+      onConfirm: () => logOut(),
+    }));
+  },
+});
+
+export default @injectIntl
+@connect(null, mapDispatchToProps)
+class LinkFooter extends React.PureComponent {
+
+  static propTypes = {
+    withHotkeys: PropTypes.bool,
+    onLogout: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  handleLogoutClick = e => {
+    e.preventDefault();
+    e.stopPropagation();
+
+    this.props.onLogout();
 
-export default LinkFooter;
+    return false;
+  }
+
+  render () {
+    const { withHotkeys } = this.props;
+
+    return (
+      <div className='getting-started__footer'>
+        <ul>
+          {invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
+          {withHotkeys && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
+          <li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
+          <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>
+          <li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
+          <li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
+          <li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
+          <li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
+          <li><a href='/auth/sign_out' onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>
+        </ul>
+
+        <p>
+          <FormattedMessage
+            id='getting_started.open_source_notice'
+            defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
+            values={{ github: <span><a href={source_url} rel='noopener' target='_blank'>{repository}</a> (v{version})</span> }}
+          />
+        </p>
+      </div>
+    );
+  }
+
+};
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js
index da2ac5f26d5a4670bb0bd08f27e399af87b52463..a785551c0fd7ea3d18b731150111c4a8677d8743 100644
--- a/app/javascript/mastodon/features/ui/components/media_modal.js
+++ b/app/javascript/mastodon/features/ui/components/media_modal.js
@@ -3,13 +3,13 @@ import ReactSwipeableViews from 'react-swipeable-views';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import Video from 'mastodon/features/video';
-import ExtendedVideoPlayer from 'mastodon/components/extended_video_player';
 import classNames from 'classnames';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import IconButton from 'mastodon/components/icon_button';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import ImageLoader from './image_loader';
 import Icon from 'mastodon/components/icon';
+import GIFV from 'mastodon/components/gifv';
 
 const messages = defineMessages({
   close: { id: 'lightbox.close', defaultMessage: 'Close' },
@@ -169,10 +169,8 @@ class MediaModal extends ImmutablePureComponent {
         );
       } else if (image.get('type') === 'gifv') {
         return (
-          <ExtendedVideoPlayer
+          <GIFV
             src={image.get('url')}
-            muted
-            controls={false}
             width={width}
             height={height}
             key={image.get('preview_url')}
@@ -228,7 +226,7 @@ class MediaModal extends ImmutablePureComponent {
 
           {status && (
             <div className={classNames('media-modal__meta', { 'media-modal__meta--shifted': media.size > 1 })}>
-              <a href={status.get('url')} onClick={this.handleStatusClick}><FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
+              <a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
             </div>
           )}
 
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js
index cc2ab6c8ce9c34c89cc6744b0d94619e0de8afff..5cf70a0cc2d7b880e660e1f1c2ea9125960f237b 100644
--- a/app/javascript/mastodon/features/ui/components/modal_root.js
+++ b/app/javascript/mastodon/features/ui/components/modal_root.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import Base from '../../../components/modal_root';
+import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
+import Base from 'mastodon/components/modal_root';
 import BundleContainer from '../containers/bundle_container';
 import BundleModalError from './bundle_modal_error';
 import ModalLoading from './modal_loading';
@@ -8,10 +9,12 @@ import ActionsModal from './actions_modal';
 import MediaModal from './media_modal';
 import VideoModal from './video_modal';
 import BoostModal from './boost_modal';
+import AudioModal from './audio_modal';
 import ConfirmationModal from './confirmation_modal';
 import FocalPointModal from './focal_point_modal';
 import {
   MuteModal,
+  BlockModal,
   ReportModal,
   EmbedModal,
   ListEditor,
@@ -21,9 +24,11 @@ import {
 const MODAL_COMPONENTS = {
   'MEDIA': () => Promise.resolve({ default: MediaModal }),
   'VIDEO': () => Promise.resolve({ default: VideoModal }),
+  'AUDIO': () => Promise.resolve({ default: AudioModal }),
   'BOOST': () => Promise.resolve({ default: BoostModal }),
   'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
   'MUTE': MuteModal,
+  'BLOCK': BlockModal,
   'REPORT': ReportModal,
   'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
   'EMBED': EmbedModal,
@@ -47,8 +52,10 @@ export default class ModalRoot extends React.PureComponent {
   componentDidUpdate (prevProps, prevState, { visible }) {
     if (visible) {
       document.body.classList.add('with-modals--active');
+      document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
     } else {
       document.body.classList.remove('with-modals--active');
+      document.documentElement.style.marginRight = 0;
     }
   }
 
diff --git a/app/javascript/mastodon/features/ui/components/mute_modal.js b/app/javascript/mastodon/features/ui/components/mute_modal.js
index ac356b42a7b26f19c0e90428debbd38a0567c7b5..852830c3c2c93fd7c87de88e029db557bda9b3e1 100644
--- a/app/javascript/mastodon/features/ui/components/mute_modal.js
+++ b/app/javascript/mastodon/features/ui/components/mute_modal.js
@@ -11,7 +11,6 @@ import { toggleHideNotifications } from '../../../actions/mutes';
 
 const mapStateToProps = state => {
   return {
-    isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
     account: state.getIn(['mutes', 'new', 'account']),
     notifications: state.getIn(['mutes', 'new', 'notifications']),
   };
@@ -38,7 +37,6 @@ export default @connect(mapStateToProps, mapDispatchToProps)
 class MuteModal extends React.PureComponent {
 
   static propTypes = {
-    isSubmitting: PropTypes.bool.isRequired,
     account: PropTypes.object.isRequired,
     notifications: PropTypes.bool.isRequired,
     onClose: PropTypes.func.isRequired,
@@ -81,11 +79,16 @@ class MuteModal extends React.PureComponent {
               values={{ name: <strong>@{account.get('acct')}</strong> }}
             />
           </p>
-          <div>
-            <label htmlFor='mute-modal__hide-notifications-checkbox'>
+          <p className='mute-modal__explanation'>
+            <FormattedMessage
+              id='confirmations.mute.explanation'
+              defaultMessage='This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.'
+            />
+          </p>
+          <div className='setting-toggle'>
+            <Toggle id='mute-modal__hide-notifications-checkbox' checked={notifications} onChange={this.toggleNotifications} />
+            <label className='setting-toggle__label' htmlFor='mute-modal__hide-notifications-checkbox'>
               <FormattedMessage id='mute_modal.hide_notifications' defaultMessage='Hide notifications from this user?' />
-              {' '}
-              <Toggle id='mute-modal__hide-notifications-checkbox' checked={notifications} onChange={this.toggleNotifications} />
             </label>
           </div>
         </div>
diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.js
index ef3ad2e092f86e07936985041471a08906620e03..51e3ec037d1f161bd5950e12475be561276de99b 100644
--- a/app/javascript/mastodon/features/ui/components/navigation_panel.js
+++ b/app/javascript/mastodon/features/ui/components/navigation_panel.js
@@ -2,10 +2,11 @@ import React from 'react';
 import { NavLink, withRouter } from 'react-router-dom';
 import { FormattedMessage } from 'react-intl';
 import Icon from 'mastodon/components/icon';
-import { profile_directory } from 'mastodon/initial_state';
+import { profile_directory, showTrends } from 'mastodon/initial_state';
 import NotificationsCounterIcon from './notifications_counter_icon';
 import FollowRequestsNavLink from './follow_requests_nav_link';
 import ListPanel from './list_panel';
+import TrendsContainer from 'mastodon/features/getting_started/containers/trends_container';
 
 const NavigationPanel = () => (
   <div className='navigation-panel'>
@@ -17,6 +18,7 @@ const NavigationPanel = () => (
     <NavLink className='column-link column-link--transparent' to='/timelines/direct'><Icon className='column-link__icon' id='envelope' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink>
     <NavLink className='column-link column-link--transparent' to='/favourites'><Icon className='column-link__icon' id='star' fixedWidth /><FormattedMessage id='navigation_bar.favourites' defaultMessage='Favourites' /></NavLink>
     <NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' id='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink>
+    {profile_directory && <NavLink className='column-link column-link--transparent' to='/directory'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></NavLink>}
 
     <ListPanel />
 
@@ -24,7 +26,9 @@ const NavigationPanel = () => (
 
     <a className='column-link column-link--transparent' href='/settings/preferences'><Icon className='column-link__icon' id='cog' fixedWidth /><FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' /></a>
     <a className='column-link column-link--transparent' href='/relationships'><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a>
-    {!!profile_directory && <a className='column-link column-link--transparent' href='/explore'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='navigation_bar.profile_directory' defaultMessage='Profile directory' /></a>}
+
+    {showTrends && <div className='flex-spacer' />}
+    {showTrends && <TrendsContainer />}
   </div>
 );
 
diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js
index 29583d3d798aaead2bde744233257f58f4c6aeed..1911da8ba3f58e5188d4f233aad1ff71d0dfae0d 100644
--- a/app/javascript/mastodon/features/ui/components/tabs_bar.js
+++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js
@@ -73,9 +73,13 @@ class TabsBar extends React.PureComponent {
     const { intl: { formatMessage } } = this.props;
 
     return (
-      <nav className='tabs-bar' ref={this.setRef}>
-        {links.map(link => React.cloneElement(link, { key: link.props.to, onClick: this.handleClick, 'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }) }))}
-      </nav>
+      <div className='tabs-bar__wrapper'>
+        <nav className='tabs-bar' ref={this.setRef}>
+          {links.map(link => React.cloneElement(link, { key: link.props.to, onClick: this.handleClick, 'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }) }))}
+        </nav>
+
+        <div id='tabs-bar__portal' />
+      </div>
     );
   }
 
diff --git a/app/javascript/mastodon/features/ui/components/video_modal.js b/app/javascript/mastodon/features/ui/components/video_modal.js
index 213d31316c81af9df6f6a33d799ef851036985b2..f37fc796f83bceb000a9fa6d1d68043213952391 100644
--- a/app/javascript/mastodon/features/ui/components/video_modal.js
+++ b/app/javascript/mastodon/features/ui/components/video_modal.js
@@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
 import Video from 'mastodon/features/video';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { FormattedMessage } from 'react-intl';
+import classNames from 'classnames';
+import Icon from 'mastodon/components/icon';
 
 export const previewState = 'previewVideoModal';
 
@@ -52,22 +54,25 @@ export default class VideoModal extends ImmutablePureComponent {
   render () {
     const { media, status, time, onClose } = this.props;
 
-    const link = status && <a href={status.get('url')} onClick={this.handleStatusClick}><FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>;
-
     return (
       <div className='modal-root__modal video-modal'>
-        <div>
+        <div className='video-modal__container'>
           <Video
             preview={media.get('preview_url')}
             blurhash={media.get('blurhash')}
             src={media.get('url')}
             startTime={time}
             onCloseVideo={onClose}
-            link={link}
             detailed
             alt={media.get('description')}
           />
         </div>
+
+        {status && (
+          <div className={classNames('media-modal__meta')}>
+            <a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
+          </div>
+        )}
       </div>
     );
   }
diff --git a/app/javascript/mastodon/features/ui/containers/notifications_container.js b/app/javascript/mastodon/features/ui/containers/notifications_container.js
index b60a0216f628e22ec047b572e104bba2a275c697..3819da3d850c1fa3a93a176f4132fa0ab359db03 100644
--- a/app/javascript/mastodon/features/ui/containers/notifications_container.js
+++ b/app/javascript/mastodon/features/ui/containers/notifications_container.js
@@ -11,7 +11,7 @@ const mapStateToProps = (state, { intl }) => {
     const value = notification[key];
 
     if (typeof value === 'object') {
-      notification[key] = intl.formatMessage(value);
+      notification[key] = intl.formatMessage(value, notification[`${key}_values`]);
     }
   }));
 
diff --git a/app/javascript/mastodon/features/ui/containers/status_list_container.js b/app/javascript/mastodon/features/ui/containers/status_list_container.js
index 3df5b7beacc07b7547e8b0b15c314d886ca60df9..7b8eb652b4af87511bab867f7c2396ddcb7f9b28 100644
--- a/app/javascript/mastodon/features/ui/containers/status_list_container.js
+++ b/app/javascript/mastodon/features/ui/containers/status_list_container.js
@@ -1,6 +1,6 @@
 import { connect } from 'react-redux';
 import StatusList from '../../../components/status_list';
-import { scrollTopTimeline } from '../../../actions/timelines';
+import { scrollTopTimeline, loadPending } from '../../../actions/timelines';
 import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 import { createSelector } from 'reselect';
 import { debounce } from 'lodash';
@@ -37,6 +37,7 @@ const makeMapStateToProps = () => {
     isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
     isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
     hasMore:   state.getIn(['timelines', timelineId, 'hasMore']),
+    numPending: state.getIn(['timelines', timelineId, 'pendingItems'], ImmutableList()).size,
   });
 
   return mapStateToProps;
@@ -52,6 +53,8 @@ const mapDispatchToProps = (dispatch, { timelineId }) => ({
     dispatch(scrollTopTimeline(timelineId, false));
   }, 100),
 
+  onLoadPending: () => dispatch(loadPending(timelineId)),
+
 });
 
 export default connect(makeMapStateToProps, mapDispatchToProps)(StatusList);
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 791133afd32ed905705ca2cdd108c538acc82a45..791ff9a2e5ae6efb3c2b1ba1b7a2c2331c2ea740 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -15,9 +15,12 @@ import { expandHomeTimeline } from '../../actions/timelines';
 import { expandNotifications } from '../../actions/notifications';
 import { fetchFilters } from '../../actions/filters';
 import { clearHeight } from '../../actions/height_cache';
+import { focusApp, unfocusApp } from 'mastodon/actions/app';
+import { submitMarkers } from 'mastodon/actions/markers';
 import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
 import UploadArea from './components/upload_area';
 import ColumnsAreaContainer from './containers/columns_area_container';
+import DocumentTitle from './components/document_title';
 import {
   Compose,
   Status,
@@ -45,6 +48,7 @@ import {
   PinnedStatuses,
   Lists,
   Search,
+  Directory,
 } from './util/async-components';
 import { me, forceSingleColumn } from '../../initial_state';
 import { previewState as previewMediaState } from './components/media_modal';
@@ -62,6 +66,7 @@ const mapStateToProps = state => ({
   isComposing: state.getIn(['compose', 'is_composing']),
   hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
   hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
+  canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
   dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
 });
 
@@ -110,12 +115,25 @@ class SwitchingColumnsArea extends React.PureComponent {
 
   componentWillMount () {
     window.addEventListener('resize', this.handleResize, { passive: true });
+
+    if (this.state.mobile || forceSingleColumn) {
+      document.body.classList.toggle('layout-single-column', true);
+      document.body.classList.toggle('layout-multiple-columns', false);
+    } else {
+      document.body.classList.toggle('layout-single-column', false);
+      document.body.classList.toggle('layout-multiple-columns', true);
+    }
   }
 
-  componentDidUpdate (prevProps) {
+  componentDidUpdate (prevProps, prevState) {
     if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
       this.node.handleChildrenContentChange();
     }
+
+    if (prevState.mobile !== this.state.mobile && !forceSingleColumn) {
+      document.body.classList.toggle('layout-single-column', this.state.mobile);
+      document.body.classList.toggle('layout-multiple-columns', !this.state.mobile);
+    }
   }
 
   componentWillUnmount () {
@@ -126,14 +144,24 @@ class SwitchingColumnsArea extends React.PureComponent {
     return location.state !== previewMediaState && location.state !== previewVideoState;
   }
 
-  handleResize = debounce(() => {
+  handleLayoutChange = debounce(() => {
     // The cached heights are no longer accurate, invalidate
     this.props.onLayoutChange();
-
-    this.setState({ mobile: isMobile(window.innerWidth) });
   }, 500, {
     trailing: true,
-  });
+  })
+
+  handleResize = () => {
+    const mobile = isMobile(window.innerWidth);
+
+    if (mobile !== this.state.mobile) {
+      this.handleLayoutChange.cancel();
+      this.props.onLayoutChange();
+      this.setState({ mobile });
+    } else {
+      this.handleLayoutChange();
+    }
+  }
 
   setRef = c => {
     this.node = c.getWrappedInstance();
@@ -163,6 +191,7 @@ class SwitchingColumnsArea extends React.PureComponent {
           <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 
           <WrappedRoute path='/search' component={Search} content={children} />
+          <WrappedRoute path='/directory' component={Directory} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
 
           <WrappedRoute path='/statuses/new' component={Compose} content={children} />
           <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
@@ -204,6 +233,7 @@ class UI extends React.PureComponent {
     isComposing: PropTypes.bool,
     hasComposingText: PropTypes.bool,
     hasMediaAttachments: PropTypes.bool,
+    canUploadMore: PropTypes.bool,
     location: PropTypes.object,
     intl: PropTypes.object.isRequired,
     dropdownMenuIsOpen: PropTypes.bool,
@@ -213,8 +243,10 @@ class UI extends React.PureComponent {
     draggingOver: false,
   };
 
-  handleBeforeUnload = (e) => {
-    const { intl, isComposing, hasComposingText, hasMediaAttachments } = this.props;
+  handleBeforeUnload = e => {
+    const { intl, dispatch, isComposing, hasComposingText, hasMediaAttachments } = this.props;
+
+    dispatch(submitMarkers());
 
     if (isComposing && (hasComposingText || hasMediaAttachments)) {
       // Setting returnValue to any string causes confirmation dialog.
@@ -224,6 +256,14 @@ class UI extends React.PureComponent {
     }
   }
 
+  handleWindowFocus = () => {
+    this.props.dispatch(focusApp());
+  }
+
+  handleWindowBlur = () => {
+    this.props.dispatch(unfocusApp());
+  }
+
   handleLayoutChange = () => {
     // The cached heights are no longer accurate, invalidate
     this.props.dispatch(clearHeight());
@@ -240,13 +280,14 @@ class UI extends React.PureComponent {
       this.dragTargets.push(e.target);
     }
 
-    if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files')) {
+    if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore) {
       this.setState({ draggingOver: true });
     }
   }
 
   handleDragOver = (e) => {
     if (this.dataTransferIsText(e.dataTransfer)) return false;
+
     e.preventDefault();
     e.stopPropagation();
 
@@ -261,12 +302,13 @@ class UI extends React.PureComponent {
 
   handleDrop = (e) => {
     if (this.dataTransferIsText(e.dataTransfer)) return;
+
     e.preventDefault();
 
     this.setState({ draggingOver: false });
     this.dragTargets = [];
 
-    if (e.dataTransfer && e.dataTransfer.files.length >= 1) {
+    if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore) {
       this.props.dispatch(uploadCompose(e.dataTransfer.files));
     }
   }
@@ -285,7 +327,7 @@ class UI extends React.PureComponent {
   }
 
   dataTransferIsText = (dataTransfer) => {
-    return (dataTransfer && Array.from(dataTransfer.types).includes('text/plain') && dataTransfer.items.length === 1);
+    return (dataTransfer && Array.from(dataTransfer.types).filter((type) => type === 'text/plain').length === 1);
   }
 
   closeUploadModal = () => {
@@ -301,6 +343,8 @@ class UI extends React.PureComponent {
   }
 
   componentWillMount () {
+    window.addEventListener('focus', this.handleWindowFocus, false);
+    window.addEventListener('blur', this.handleWindowBlur, false);
     window.addEventListener('beforeunload', this.handleBeforeUnload, false);
 
     document.addEventListener('dragenter', this.handleDragEnter, false);
@@ -330,7 +374,10 @@ class UI extends React.PureComponent {
   }
 
   componentWillUnmount () {
+    window.removeEventListener('focus', this.handleWindowFocus);
+    window.removeEventListener('blur', this.handleWindowBlur);
     window.removeEventListener('beforeunload', this.handleBeforeUnload);
+
     document.removeEventListener('dragenter', this.handleDragEnter);
     document.removeEventListener('dragover', this.handleDragOver);
     document.removeEventListener('drop', this.handleDrop);
@@ -489,6 +536,7 @@ class UI extends React.PureComponent {
           <LoadingBarContainer className='loading-bar' />
           <ModalContainer />
           <UploadArea active={draggingOver} onClose={this.closeUploadModal} />
+          <DocumentTitle />
         </div>
       </HotKeys>
     );
diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js
index 6e8ed163a539a5c2bddb5b5d43213b17faf7ea9d..bb0fcb8593468acb8c7183726e4797d0ceb6d78f 100644
--- a/app/javascript/mastodon/features/ui/util/async-components.js
+++ b/app/javascript/mastodon/features/ui/util/async-components.js
@@ -106,6 +106,10 @@ export function MuteModal () {
   return import(/* webpackChunkName: "modals/mute_modal" */'../components/mute_modal');
 }
 
+export function BlockModal () {
+  return import(/* webpackChunkName: "modals/block_modal" */'../components/block_modal');
+}
+
 export function ReportModal () {
   return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal');
 }
@@ -133,3 +137,15 @@ export function ListAdder () {
 export function Search () {
   return import(/*webpackChunkName: "features/search" */'../../search');
 }
+
+export function Tesseract () {
+  return import(/*webpackChunkName: "tesseract" */'tesseract.js');
+}
+
+export function Audio () {
+  return import(/* webpackChunkName: "features/audio" */'../../audio');
+}
+
+export function Directory () {
+  return import(/* webpackChunkName: "features/directory" */'../../directory');
+}
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index b0c4085277fc13fc572a953502edbab8406fc4e7..5fe4e956f879f628773f399d80a8475c23b50f30 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/features/video/index.js
@@ -5,7 +5,7 @@ import { fromJS, is } from 'immutable';
 import { throttle } from 'lodash';
 import classNames from 'classnames';
 import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
-import { displayMedia } from '../../initial_state';
+import { displayMedia, useBlurhash } from '../../initial_state';
 import Icon from 'mastodon/components/icon';
 import { decode } from 'blurhash';
 
@@ -21,7 +21,7 @@ const messages = defineMessages({
   exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' },
 });
 
-const formatTime = secondsNum => {
+export const formatTime = secondsNum => {
   let hours   = Math.floor(secondsNum / 3600);
   let minutes = Math.floor((secondsNum - (hours * 3600)) / 60);
   let seconds = secondsNum - (hours * 3600) - (minutes * 60);
@@ -101,6 +101,7 @@ class Video extends React.PureComponent {
     onCloseVideo: PropTypes.func,
     detailed: PropTypes.bool,
     inline: PropTypes.bool,
+    editable: PropTypes.bool,
     cacheWidth: PropTypes.func,
     visible: PropTypes.bool,
     onToggleVisibility: PropTypes.func,
@@ -298,6 +299,8 @@ class Video extends React.PureComponent {
   }
 
   _decode () {
+    if (!useBlurhash) return;
+
     const hash   = this.props.blurhash;
     const pixels = decode(hash, 32, 32);
 
@@ -373,7 +376,7 @@ class Video extends React.PureComponent {
   }
 
   render () {
-    const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, link } = this.props;
+    const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, link, editable } = this.props;
     const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
     const progress = (currentTime / duration) * 100;
 
@@ -411,7 +414,7 @@ class Video extends React.PureComponent {
     return (
       <div
         role='menuitem'
-        className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen })}
+        className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen, editable })}
         style={playerStyle}
         ref={this.setPlayerRef}
         onMouseEnter={this.handleMouseEnter}
@@ -421,7 +424,7 @@ class Video extends React.PureComponent {
       >
         <canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': revealed })} />
 
-        {revealed && <video
+        {(revealed || editable) && <video
           ref={this.setVideoRef}
           src={src}
           poster={preview}
@@ -443,7 +446,7 @@ class Video extends React.PureComponent {
           onVolumeChange={this.handleVolumeChange}
         />}
 
-        <div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed })}>
+        <div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
           <button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
             <span className='spoiler-button__overlay__label'>{warning}</span>
           </button>
@@ -487,7 +490,7 @@ class Video extends React.PureComponent {
             </div>
 
             <div className='video-player__buttons right'>
-              {!onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
+              {(!onCloseVideo && !editable) && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
               {(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
               {onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
               <button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 125508c2345cad89188c36e552113b228837a897..56fb58546690992c51b27af59a3e50f8ce117034 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -20,5 +20,9 @@ export const mascot = getMeta('mascot');
 export const profile_directory = getMeta('profile_directory');
 export const isStaff = getMeta('is_staff');
 export const forceSingleColumn = !getMeta('advanced_layout');
+export const useBlurhash = getMeta('use_blurhash');
+export const usePendingItems = getMeta('use_pending_items');
+export const showTrends = getMeta('trends');
+export const title = getMeta('title');
 
 export default initialState;
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index de289b474ccd269f7dd657112f464a0f809c7a01..4dd9df55bc8d384251f4a772812172c026d7f37a 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -4,10 +4,11 @@
   "account.block": "حظر @{name}",
   "account.block_domain": "إخفاء كل شيئ قادم من اسم النطاق {domain}",
   "account.blocked": "محظور",
+  "account.cancel_follow_request": "إلغاء طلب المتابَعة",
   "account.direct": "رسالة خاصة إلى @{name}",
   "account.domain_blocked": "النطاق مخفي",
-  "account.edit_profile": "تعديل الملف الشخصي",
-  "account.endorse": "خاصّية على الملف الشخصي",
+  "account.edit_profile": "تعديل الملف التعريفي",
+  "account.endorse": "أوصِ به على صفحتك",
   "account.follow": "تابِع",
   "account.followers": "متابعون",
   "account.followers.empty": "لا أحد يتبع هذا الحساب بعد.",
@@ -15,28 +16,33 @@
   "account.follows.empty": "هذا الحساب لا يتبع أحدًا بعد.",
   "account.follows_you": "يتابعك",
   "account.hide_reblogs": "إخفاء ترقيات @{name}",
+  "account.last_status": "آخر نشاط",
   "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": "أكتم @{name}",
   "account.mute_notifications": "كتم الإخطارات من @{name}",
   "account.muted": "مكتوم",
-  "account.posts": "التبويقات",
+  "account.never_active": "أبدا",
+  "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.unblock": "إلغاء الحظر عن @{name}",
   "account.unblock_domain": "فك الخْفى عن {domain}",
-  "account.unendorse": "إزالة ترويجه مِن الملف الشخصي",
+  "account.unendorse": "أزل ترويجه مِن الملف التعريفي",
   "account.unfollow": "إلغاء المتابعة",
   "account.unmute": "إلغاء الكتم عن @{name}",
   "account.unmute_notifications": "إلغاء كتم إخطارات @{name}",
+  "alert.rate_limited.message": "يرجى إعادة المحاولة بعد {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "لقد طرأ هناك خطأ غير متوقّع.",
   "alert.unexpected.title": "المعذرة!",
+  "autosuggest_hashtag.per_week": "{count} في الأسبوع",
   "boost_modal.combo": "يمكنك/ي ضغط {combo} لتخطّي هذه في المرّة القادمة",
   "bundle_column_error.body": "لقد وقع هناك خطأ أثناء عملية تحميل هذا العنصر.",
   "bundle_column_error.retry": "إعادة المحاولة",
@@ -47,6 +53,7 @@
   "column.blocks": "الحسابات المحجوبة",
   "column.community": "الخيط العام المحلي",
   "column.direct": "الرسائل المباشرة",
+  "column.directory": "استعرض الملفات التعريفية",
   "column.domain_blocks": "النطاقات المخفية",
   "column.favourites": "المفضلة",
   "column.follow_requests": "طلبات المتابعة",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "هل تود حقا حذف هذه القائمة ؟",
   "confirmations.domain_block.confirm": "إخفاء اسم النطاق كاملا",
   "confirmations.domain_block.message": "متأكد من أنك تود حظر اسم النطاق {domain} بالكامل ؟ في غالب الأحيان يُستَحسَن كتم أو حظر بعض الحسابات بدلا من حظر نطاق بالكامل.\nلن تتمكن مِن رؤية محتوى هذا النطاق لا على خيوطك العمومية و لا في إشعاراتك. سوف يتم كذلك إزالة كافة متابعيك المنتمين إلى هذا النطاق.",
+  "confirmations.logout.confirm": "خروج",
+  "confirmations.logout.message": "متأكد من أنك تريد الخروج؟",
   "confirmations.mute.confirm": "أكتم",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "هل أنت متأكد أنك تريد كتم {name} ؟",
   "confirmations.redraft.confirm": "إزالة و إعادة الصياغة",
   "confirmations.redraft.message": "هل أنت متأكد من أنك تريد حذف هذا المنشور و إعادة صياغته ؟ سوف تفقد جميع الإعجابات و الترقيات أما الردود المتصلة به فستُصبِح يتيمة.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "الرد في الحين سوف يُعيد كتابة الرسالة التي أنت بصدد كتابتها. متأكد من أنك تريد المواصلة؟",
   "confirmations.unfollow.confirm": "إلغاء المتابعة",
   "confirmations.unfollow.message": "متأكد من أنك تريد إلغاء متابعة {name} ؟",
+  "conversation.delete": "احذف المحادثة",
+  "conversation.mark_as_read": "اعتبرها كمقروءة",
+  "conversation.open": "اعرض المحادثة",
+  "conversation.with": "بـ {names}",
+  "directory.federated": "مِن الفديفرس المعروف",
+  "directory.local": "مِن {domain} فقط",
+  "directory.new_arrivals": "الوافدون الجُدد",
+  "directory.recently_active": "نشط مؤخرا",
   "embed.instructions": "يمكنكم إدماج هذا المنشور على موقعكم الإلكتروني عن طريق نسخ الشفرة أدناه.",
   "embed.preview": "هكذا ما سوف يبدو عليه:",
   "emoji_button.activity": "الأنشطة",
@@ -116,9 +134,9 @@
   "emoji_button.search": "ابحث...",
   "emoji_button.search_results": "نتائج البحث",
   "emoji_button.symbols": "رموز",
-  "emoji_button.travel": "أماكن و أسفار",
+  "emoji_button.travel": "الأماكن والسفر",
   "empty_column.account_timeline": "ليس هناك تبويقات!",
-  "empty_column.account_unavailable": "الملف الشخصي غير متوفر",
+  "empty_column.account_unavailable": "الملف التعريفي غير متوفر",
   "empty_column.blocks": "لم تقم بحظر أي مستخدِم بعد.",
   "empty_column.community": "الخط العام المحلي فارغ. أكتب شيئا ما للعامة كبداية!",
   "empty_column.direct": "لم تتلق أية رسالة خاصة مباشِرة بعد. سوف يتم عرض الرسائل المباشرة هنا إن قمت بإرسال واحدة أو تلقيت البعض منها.",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "لم تقم بكتم أي مستخدم بعد.",
   "empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.",
   "empty_column.public": "لا يوجد أي شيء هنا! قم بنشر شيء ما للعامة، أو اتبع المستخدمين الآخرين المتواجدين على الخوادم الأخرى لملء خيط المحادثات",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "الإبلاغ عن خلل",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -142,7 +164,7 @@
   "follow_request.authorize": "ترخيص",
   "follow_request.reject": "رفض",
   "getting_started.developers": "المُطوِّرون",
-  "getting_started.directory": "دليل المستخدِمين والمستخدِمات",
+  "getting_started.directory": "دليل الصفحات التعريفية",
   "getting_started.documentation": "الدليل",
   "getting_started.heading": "استعدّ للبدء",
   "getting_started.invite": "دعوة أشخاص",
@@ -158,9 +180,9 @@
   "hashtag.column_settings.tag_mode.any": "أي كان مِن هذه",
   "hashtag.column_settings.tag_mode.none": "لا شيء مِن هذه",
   "hashtag.column_settings.tag_toggle": "إدراج الوسوم الإضافية لهذا العمود",
-  "home.column_settings.basic": "أساسية",
+  "home.column_settings.basic": "الأساسية",
   "home.column_settings.show_reblogs": "عرض الترقيات",
-  "home.column_settings.show_replies": "عرض الردود",
+  "home.column_settings.show_replies": "اعرض الردود",
   "intervals.full.days": "{number, plural, one {# يوم} other {# أيام}}",
   "intervals.full.hours": "{number, plural, one {# ساعة} other {# ساعات}}",
   "intervals.full.minutes": "{number, plural, one {# دقيقة} other {# دقائق}}",
@@ -180,7 +202,7 @@
   "introduction.interactions.reply.text": "يمكنكم الرد على تبويقاتكم و تبويقات الآخرين على شكل سلسلة محادثة.",
   "introduction.welcome.action": "هيا بنا!",
   "introduction.welcome.headline": "الخطوات الأولى",
-  "introduction.welcome.text": "مرحبا بكم على الفديفرس! بعد لحظات قليلة ، سيكون بمقدوركم بث رسائل والتحدث إلى أصدقائكم عبر تشكيلة واسعة من الخوادم المختلفة. هذا الخادم ، {domain} ، يستضيف ملفكم الشخصي ، لذا يجب تذكر اسمه جيدا.",
+  "introduction.welcome.text": "مرحبا بكم على الفديفرس! بعد لحظات قليلة ، سيكون بمقدوركم بث رسائل والتحدث إلى أصدقائكم عبر تشكيلة واسعة من الخوادم المختلفة. هذا الخادم ، {domain} ، يستضيف صفحتكم التعريفية ، لذا يجب تذكر اسمه جيدا.",
   "keyboard_shortcuts.back": "للعودة",
   "keyboard_shortcuts.blocked": "لفتح قائمة المستخدمين المحظورين",
   "keyboard_shortcuts.boost": "للترقية",
@@ -200,10 +222,10 @@
   "keyboard_shortcuts.local": "لفتح الخيط العام المحلي",
   "keyboard_shortcuts.mention": "لذِكر الناشر",
   "keyboard_shortcuts.muted": "لفتح قائمة المستخدِمين المكتومين",
-  "keyboard_shortcuts.my_profile": "لفتح ملفك الشخصي",
+  "keyboard_shortcuts.my_profile": "لفتح ملفك التعريفي",
   "keyboard_shortcuts.notifications": "لفتح عمود الإشعارات",
   "keyboard_shortcuts.pinned": "لفتح قائمة التبويقات المدبسة",
-  "keyboard_shortcuts.profile": "لفتح رابط الناشر",
+  "keyboard_shortcuts.profile": "لفتح الملف التعريفي للناشر",
   "keyboard_shortcuts.reply": "للردّ",
   "keyboard_shortcuts.requests": "لفتح قائمة طلبات المتابعة",
   "keyboard_shortcuts.search": "للتركيز على البحث",
@@ -220,15 +242,16 @@
   "lists.account.add": "أضف إلى القائمة",
   "lists.account.remove": "احذف من القائمة",
   "lists.delete": "احذف القائمة",
-  "lists.edit": "تعديل القائمة",
+  "lists.edit": "عدّل القائمة",
   "lists.edit.submit": "تعديل العنوان",
   "lists.new.create": "إنشاء قائمة",
   "lists.new.title_placeholder": "عنوان القائمة الجديدة",
   "lists.search": "إبحث في قائمة الحسابات التي تُتابِعها",
   "lists.subheading": "قوائمك",
+  "load_pending": "{count, plural, one {# عنصر جديد} other {# عناصر جديدة}}",
   "loading_indicator.label": "تحميل...",
   "media_gallery.toggle_visible": "عرض / إخفاء",
-  "missing_indicator.label": "تعذر العثور عليه",
+  "missing_indicator.label": "غير موجود",
   "missing_indicator.sublabel": "تعذر العثور على هذا المورد",
   "mute_modal.hide_notifications": "هل تود إخفاء الإخطارات القادمة من هذا المستخدم ؟",
   "navigation_bar.apps": "تطبيقات الأجهزة المحمولة",
@@ -238,7 +261,7 @@
   "navigation_bar.direct": "الرسائل المباشِرة",
   "navigation_bar.discover": "اكتشف",
   "navigation_bar.domain_blocks": "النطاقات المخفية",
-  "navigation_bar.edit_profile": "تعديل الملف الشخصي",
+  "navigation_bar.edit_profile": "عدّل الملف التعريفي",
   "navigation_bar.favourites": "المفضلة",
   "navigation_bar.filters": "الكلمات المكتومة",
   "navigation_bar.follow_requests": "طلبات المتابعة",
@@ -251,21 +274,20 @@
   "navigation_bar.personal": "شخصي",
   "navigation_bar.pins": "التبويقات المثبتة",
   "navigation_bar.preferences": "التفضيلات",
-  "navigation_bar.profile_directory": "دليل المستخدِمين",
   "navigation_bar.public_timeline": "الخيط العام الموحد",
   "navigation_bar.security": "الأمان",
   "notification.favourite": "أُعجِب {name} بمنشورك",
   "notification.follow": "{name} يتابعك",
   "notification.mention": "{name} ذكرك",
-  "notification.poll": "A poll you have voted in has ended",
+  "notification.poll": "لقد إنتها تصويت شاركت فيه",
   "notification.reblog": "{name} قام بترقية تبويقك",
   "notifications.clear": "امسح الإخطارات",
   "notifications.clear_confirmation": "أمتأكد من أنك تود مسح جل الإخطارات الخاصة بك و المتلقاة إلى حد الآن ؟",
   "notifications.column_settings.alert": "إشعارات سطح المكتب",
   "notifications.column_settings.favourite": "المُفَضَّلة:",
-  "notifications.column_settings.filter_bar.advanced": "عرض كافة الفئات",
+  "notifications.column_settings.filter_bar.advanced": "اعرض كافة الفئات",
   "notifications.column_settings.filter_bar.category": "شريط الفلترة السريعة",
-  "notifications.column_settings.filter_bar.show": "عرض",
+  "notifications.column_settings.filter_bar.show": "اعرض",
   "notifications.column_settings.follow": "متابعُون جُدُد:",
   "notifications.column_settings.mention": "الإشارات:",
   "notifications.column_settings.poll": "نتائج استطلاع الرأي:",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} إشعارات",
   "poll.closed": "انتهى",
   "poll.refresh": "تحديث",
+  "poll.total_people": "{count, plural, one {# شخص} other {# أشخاص}}",
   "poll.total_votes": "{count, plural, one {# صوت} other {# أصوات}}",
   "poll.vote": "صَوّت",
+  "poll.voted": "لقد صوّتت على هذه الإجابة",
   "poll_button.add_poll": "إضافة استطلاع للرأي",
   "poll_button.remove_poll": "إزالة استطلاع الرأي",
   "privacy.change": "اضبط خصوصية المنشور",
@@ -295,6 +319,7 @@
   "privacy.public.short": "للعامة",
   "privacy.unlisted.long": "لا تقم بإدراجه على الخيوط العامة",
   "privacy.unlisted.short": "غير مدرج",
+  "refresh": "أنعِش",
   "regeneration_indicator.label": "جارٍ التحميل…",
   "regeneration_indicator.sublabel": "جارٍ تجهيز تغذية صفحتك الرئيسية!",
   "relative_time.days": "{number}ÙŠ",
@@ -319,6 +344,7 @@
   "search_results.accounts": "أشخاص",
   "search_results.hashtags": "الوُسوم",
   "search_results.statuses": "التبويقات",
+  "search_results.statuses_fts_disabled": "البحث في التبويقات عن طريق المحتوى ليس مفعل في خادم ماستدون هذا.",
   "search_results.total": "{count, number} {count, plural, one {result} Ùˆ {results}}",
   "status.admin_account": "افتح الواجهة الإدارية لـ @{name}",
   "status.admin_status": "افتح هذا المنشور على واجهة الإشراف",
@@ -340,7 +366,7 @@
   "status.mute": "أكتم @{name}",
   "status.mute_conversation": "كتم المحادثة",
   "status.open": "وسع هذه المشاركة",
-  "status.pin": "تدبيس على الملف الشخصي",
+  "status.pin": "دبّسه على الصفحة التعريفية",
   "status.pinned": "تبويق مثبَّت",
   "status.read_more": "اقرأ المزيد",
   "status.reblog": "رَقِّي",
@@ -358,8 +384,9 @@
   "status.show_more": "أظهر المزيد",
   "status.show_more_all": "توسيع الكل",
   "status.show_thread": "الكشف عن المحادثة",
+  "status.uncached_media_warning": "غير متوفر",
   "status.unmute_conversation": "فك الكتم عن المحادثة",
-  "status.unpin": "فك التدبيس من الملف الشخصي",
+  "status.unpin": "فك التدبيس من الصفحة التعريفية",
   "suggestions.dismiss": "إلغاء الاقتراح",
   "suggestions.header": "يمكن أن يهمك…",
   "tabs_bar.federated_timeline": "الموحَّد",
@@ -368,19 +395,27 @@
   "tabs_bar.notifications": "الإخطارات",
   "tabs_bar.search": "البحث",
   "time_remaining.days": "{number, plural, one {# يوم} other {# أيام}} متبقية",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
+  "time_remaining.hours": "{number, plural, one {# ساعة} other {# ساعات}} متبقية",
+  "time_remaining.minutes": "{number, plural, one {# دقيقة} other {# دقائق}} متبقية",
   "time_remaining.moments": "لحظات متبقية",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.seconds": "{number, plural, one {# ثانية} other {# ثوانٍ}} متبقية",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} آخرون {people}} يتحدثون",
+  "trends.trending_now": "المتداولة الآن",
   "ui.beforeunload": "سوف تفقد مسودتك إن تركت ماستدون.",
   "upload_area.title": "اسحب ثم أفلت للرفع",
   "upload_button.label": "إضافة وسائط ({formats})",
   "upload_error.limit": "لقد تم بلوغ الحد الأقصى المسموح به لإرسال الملفات.",
   "upload_error.poll": "لا يمكن إدراج ملفات في استطلاعات الرأي.",
   "upload_form.description": "وصف للمعاقين بصريا",
-  "upload_form.focus": "قص",
+  "upload_form.edit": "تعديل",
   "upload_form.undo": "حذف",
+  "upload_modal.analyzing_picture": "جارٍ فحص الصورة…",
+  "upload_modal.apply": "طبّق",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "اكتشف النص مِن الصورة",
+  "upload_modal.edit_media": "تعديل الوسائط",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "معاينة ({ratio})",
   "upload_progress.label": "يرفع...",
   "video.close": "إغلاق الفيديو",
   "video.exit_fullscreen": "الخروج من وضع الشاشة المليئة",
diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json
index d4706768c97298de043d055920b155035ee10f30..b2c98bb157731bb4532c1318f4a298705157bae4 100644
--- a/app/javascript/mastodon/locales/ast.json
+++ b/app/javascript/mastodon/locales/ast.json
@@ -4,6 +4,7 @@
   "account.block": "Bloquiar a @{name}",
   "account.block_domain": "Anubrir tolo de {domain}",
   "account.blocked": "Blocked",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Unviar un mensaxe direutu a @{name}",
   "account.domain_blocked": "Dominiu anubríu",
   "account.edit_profile": "Editar el perfil",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Esti usuariu entá nun sigue a naide.",
   "account.follows_you": "Síguete",
   "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Media",
@@ -23,10 +25,11 @@
   "account.mute": "Silenciar a @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.muted": "Muted",
+  "account.never_active": "Never",
   "account.posts": "Toots",
   "account.posts_with_replies": "Toots y rempuestes",
   "account.report": "Report @{name}",
-  "account.requested": "Awaiting approval. Click to cancel follow request",
+  "account.requested": "Awaiting approval",
   "account.share": "Share @{name}'s profile",
   "account.show_reblogs": "Show boosts from @{name}",
   "account.unblock": "Desbloquiar a @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Unfollow",
   "account.unmute": "Unmute @{name}",
   "account.unmute_notifications": "Unmute notifications from @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Asocedió un fallu inesperáu.",
   "alert.unexpected.title": "¡Ups!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Pues primir {combo} pa saltar esto la próxima vegada",
   "bundle_column_error.body": "Something went wrong while loading this component.",
   "bundle_column_error.retry": "Try again",
@@ -47,6 +53,7 @@
   "column.blocks": "Usuarios bloquiaos",
   "column.community": "Llinia temporal llocal",
   "column.direct": "Mensaxes direutos",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Dominios anubríos",
   "column.favourites": "Favoritos",
   "column.follow_requests": "Solicitúes de siguimientu",
@@ -64,7 +71,7 @@
   "column_header.show_settings": "Show settings",
   "column_header.unpin": "Desfixar",
   "column_subheading.settings": "Axustes",
-  "community.column_settings.media_only": "Media Only",
+  "community.column_settings.media_only": "Media only",
   "compose_form.direct_message_warning": "Esti toot namái va unviase a los usuarios mentaos.",
   "compose_form.direct_message_warning_learn_more": "Learn more",
   "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "¿De xuru que quies desaniciar dafechu esta llista?",
   "confirmations.domain_block.confirm": "Anubrir tol dominiu",
   "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Mute",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "¿De xuru que quies silenciar a {name}?",
   "confirmations.redraft.confirm": "Desaniciar y reeditar",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "¿De xuru que quies dexar de siguir a {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Empotra esti estáu nun sitiu web copiando'l códigu d'embaxo.",
   "embed.preview": "Asina ye como va vese:",
   "emoji_button.activity": "Actividaes",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Entá nun silenciesti a dengún usuariu.",
   "empty_column.notifications": "Entá nun tienes dengún avisu. Interactua con otros p'aniciar la conversación.",
   "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Títulu nuevu de la llista",
   "lists.search": "Guetar ente la xente que sigues",
   "lists.subheading": "Les tos llistes",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Cargando...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Nun s'alcontró",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Toots fixaos",
   "navigation_bar.preferences": "Preferencies",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Llinia temporal federada",
   "navigation_bar.security": "Seguranza",
   "notification.favourite": "{name} favourited your status",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} avisos",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Do not show in public timelines",
   "privacy.unlisted.short": "Unlisted",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Cargando…",
   "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Xente",
   "search_results.hashtags": "Etiquetes",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Amosar más",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Desfixar del perfil",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "El borrador va perdese si coles de Mastodon.",
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Add media",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Descripción pa discapacitaos visuales",
-  "upload_form.focus": "Crop",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Desaniciar",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Xubiendo...",
   "video.close": "Zarrar el videu",
   "video.exit_fullscreen": "Colar de la pantalla completa",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index f534aa4baf0942a632aaffd24bfd7814cfe0019f..12b03796eb7371a3f321c02cb8b0193274ac6420 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -1,11 +1,12 @@
 {
-  "account.add_or_remove_from_list": "Add or Remove from lists",
-  "account.badges.bot": "Bot",
+  "account.add_or_remove_from_list": "Добави или премахни от списъците",
+  "account.badges.bot": "бот",
   "account.block": "Блокирай",
-  "account.block_domain": "Hide everything from {domain}",
-  "account.blocked": "Blocked",
+  "account.block_domain": "скрий всичко от (домейн)",
+  "account.blocked": "Блокирани",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Direct Message @{name}",
-  "account.domain_blocked": "Domain hidden",
+  "account.domain_blocked": "Скрит домейн",
   "account.edit_profile": "Редактирай профила си",
   "account.endorse": "Feature on profile",
   "account.follow": "Последвай",
@@ -15,6 +16,7 @@
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Твой последовател",
   "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Media",
@@ -23,6 +25,7 @@
   "account.mute": "Mute @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.muted": "Muted",
+  "account.never_active": "Never",
   "account.posts": "Публикации",
   "account.posts_with_replies": "Toots with replies",
   "account.report": "Report @{name}",
@@ -35,11 +38,14 @@
   "account.unfollow": "Не следвай",
   "account.unmute": "Unmute @{name}",
   "account.unmute_notifications": "Unmute notifications from @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "bundle_column_error.body": "Something went wrong while loading this component.",
-  "bundle_column_error.retry": "Try again",
+  "bundle_column_error.retry": "Опитай отново",
   "bundle_column_error.title": "Network error",
   "bundle_modal_error.close": "Close",
   "bundle_modal_error.message": "Something went wrong while loading this component.",
@@ -47,11 +53,12 @@
   "column.blocks": "Blocked users",
   "column.community": "Local timeline",
   "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
   "column.home": "Начало",
-  "column.lists": "Lists",
+  "column.lists": "Списъци",
   "column.mutes": "Muted users",
   "column.notifications": "Известия",
   "column.pins": "Pinned toot",
@@ -64,7 +71,7 @@
   "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.settings": "Settings",
-  "community.column_settings.media_only": "Media Only",
+  "community.column_settings.media_only": "Media only",
   "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.",
   "compose_form.direct_message_warning_learn_more": "Learn more",
   "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
   "confirmations.domain_block.confirm": "Hide entire domain",
   "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Mute",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "embed.preview": "Here is what it will look like:",
   "emoji_button.activity": "Activity",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
   "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "New list title",
   "lists.search": "Search among people you follow",
   "lists.subheading": "Your lists",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Зареждане...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Pinned toots",
   "navigation_bar.preferences": "Предпочитания",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Публичен канал",
   "navigation_bar.security": "Security",
   "notification.favourite": "{name} хареса твоята публикация",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Do not show in public timelines",
   "privacy.unlisted.short": "Unlisted",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Loading…",
   "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "People",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Show more",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,15 +400,23 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Добави медия",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
-  "upload_form.focus": "Crop",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Отмяна",
-  "upload_progress.label": "Uploading...",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
+  "upload_progress.label": "Uploading…",
   "video.close": "Close video",
   "video.exit_fullscreen": "Exit full screen",
   "video.expand": "Expand video",
diff --git a/app/javascript/mastodon/locales/bn.json b/app/javascript/mastodon/locales/bn.json
index fe81b9a3335c835eb8e7c79983d6799c50710b6a..67501844d1eee52857d93038faa6c21c828fa7ce 100644
--- a/app/javascript/mastodon/locales/bn.json
+++ b/app/javascript/mastodon/locales/bn.json
@@ -1,13 +1,14 @@
 {
   "account.add_or_remove_from_list": "তালিকাতে আরো যুক্ত বা মুছে ফেলুন",
   "account.badges.bot": "রোবট",
-  "account.block": "@{name} বন্ধ করুন",
+  "account.block": "@{name} কে বন্ধ করুন",
   "account.block_domain": "{domain} থেকে সব সরিয়ে ফেলুন",
   "account.blocked": "বন্ধ করা হয়েছে",
-  "account.direct": "@{name}কে সরকারি লিখুন",
+  "account.cancel_follow_request": "Cancel follow request",
+  "account.direct": "@{name} এর কাছে সরকারি লেখা পাঠাতে",
   "account.domain_blocked": "ওয়েবসাইট সরিয়ে ফেলা হয়েছে",
-  "account.edit_profile": "নিজের পাতা সম্পাদনা করুন",
-  "account.endorse": "নিজের পাতায় দেখান",
+  "account.edit_profile": "নিজের পাতা সম্পাদনা করতে",
+  "account.endorse": "আপনার নিজের পাতায় দেখাতে",
   "account.follow": "অনুসরণ করুন",
   "account.followers": "অনুসরণকারক",
   "account.followers.empty": "এই ব্যবহারকারীকে কেও এখনো অনুসরণ করে না।",
@@ -15,38 +16,44 @@
   "account.follows.empty": "এই ব্যবহারকারী কাওকে এখনো অনুসরণ করেন না।",
   "account.follows_you": "আপনাকে অনুসরণ করে",
   "account.hide_reblogs": "@{name}র সমর্থনগুলি সরিয়ে ফেলুন",
+  "account.last_status": "Last active",
   "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": "@{name} সব কার্যক্রম আপনার সময়রেখা থেকে সরিয়ে ফেলতে",
   "account.mute_notifications": "@{name}র প্রজ্ঞাপন আপনার কাছ থেকে সরিয়ে ফেলুন",
   "account.muted": "সরানো আছে",
+  "account.never_active": "Never",
   "account.posts": "টুট",
   "account.posts_with_replies": "টুট এবং মতামত",
-  "account.report": "@{name}কে রিপোর্ট করে দিন",
+  "account.report": "@{name} কে রিপোর্ট করতে",
   "account.requested": "অনুমতির অপেক্ষায় আছে। অনুসরণ করার অনুরোধ বাতিল করতে এখানে ক্লিক করুন",
   "account.share": "@{name}র পাতা অন্যদের দেখান",
   "account.show_reblogs": "@{name}র সমর্থনগুলো দেখুন",
   "account.unblock": "@{name}র কার্যকলাপ আবার দেখুন",
   "account.unblock_domain": "{domain}থেকে আবার দেখুন",
-  "account.unendorse": "নিজের পাতায় এটা দেখতে চান না",
-  "account.unfollow": "অনুসরণ বন্ধ করুন",
+  "account.unendorse": "আপনার নিজের পাতায় এটা না দেখাতে",
+  "account.unfollow": "অনুসরণ না করতে",
   "account.unmute": "@{name}র কার্যকলাপ আবার দেখুন",
   "account.unmute_notifications": "@{name}র প্রজ্ঞাপন দেওয়ার অনুমতি দিন",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "অপ্রত্যাশিত একটি সমস্যা হয়েছে।",
   "alert.unexpected.title": "ওহো!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "পরেরবার আপনি {combo} চাপ দিলে এটার শেষে চলে যেতে পারবেন",
   "bundle_column_error.body": "এই অংশটি দেখতে যেয়ে কোনো সমস্যা হয়েছে।",
   "bundle_column_error.retry": "আবার চেষ্টা করুন",
   "bundle_column_error.title": "নেটওয়ার্কের সমস্যা হচ্ছে",
   "bundle_modal_error.close": "বন্ধ করুন",
-  "bundle_modal_error.message": "এই অংশটি দেখতে যেয়ে কোনো সমস্যা হয়েছে।",
+  "bundle_modal_error.message": "এই অংশটি দেখাতে যেয়ে কোনো সমস্যা হয়েছে।",
   "bundle_modal_error.retry": "আবার চেষ্টা করুন",
   "column.blocks": "যাদের বন্ধ করে রাখা হয়েছে",
   "column.community": "স্থানীয় সময়সারি",
   "column.direct": "সরাসরি লেখা",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "সরিয়ে ফেলা ওয়েবসাইট",
   "column.favourites": "পছন্দের গুলো",
   "column.follow_requests": "অনুসরণের অনুমতি চেয়েছে যারা",
@@ -77,12 +84,12 @@
   "compose_form.poll.remove_option": "এই বিকল্পটি মুছে ফেলুন",
   "compose_form.publish": "টুট",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.hide": "Mark media as sensitive",
+  "compose_form.sensitive.hide": "এই ছবি বা ভিডিওটি সংবেদনশীল হিসেবে চিহ্নিত করতে",
   "compose_form.sensitive.marked": "এই ছবি বা ভিডিওটি সংবেদনশীল হিসেবে চিহ্নিত করা হয়েছে",
   "compose_form.sensitive.unmarked": "এই ছবি বা ভিডিওটি সংবেদনশীল হিসেবে চিহ্নিত করা হয়নি",
   "compose_form.spoiler.marked": "লেখাটি সাবধানতার পেছনে লুকানো আছে",
   "compose_form.spoiler.unmarked": "লেখাটি লুকানো নেই",
-  "compose_form.spoiler_placeholder": "আপনার সাবধানতা এখানে লিখুন",
+  "compose_form.spoiler_placeholder": "আপনার লেখা দেখার সাবধানবাণী লিখুন",
   "confirmation_modal.cancel": "বাতিল করুন",
   "confirmations.block.block_and_report": "বন্ধ করুন এবং রিপোর্ট করুন",
   "confirmations.block.confirm": "বন্ধ করুন",
@@ -93,14 +100,25 @@
   "confirmations.delete_list.message": "আপনি কি নিশ্চিত যে আপনি এই তালিকাটি স্থায়িভাবে মুছে ফেলতে চান ?",
   "confirmations.domain_block.confirm": "এই ওয়েবসাইট থেকে সব সরান",
   "confirmations.domain_block.message": "আপনি কি সত্যি সত্যি নিশ্চিত যে {domain} ওয়েবসাইট থেকে সব সরাতে চান ? সাধারণত কিছু লক্ষ্যবস্তু বন্ধ এবং সরানোযা যথেষ্ট। নিশ্চিত করলে ওই ওয়েবসাইট থেকে কোনোকিছু কোনখানে দেখবেন না। যারা আপনাকে অনুসরণ করে ওই ওয়েবসাইট থেকে তাদেরকেও মুছে ফেলা হবে।",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "সরিয়ে ফেলুন",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "আপনি কি নিশ্চিত {name} সরিয়ে ফেলতে চান ?",
   "confirmations.redraft.confirm": "মুছে ফেলুন এবং আবার সম্পাদন করুন",
   "confirmations.redraft.message": "আপনি কি নিশ্চিত এটি মুছে ফেলে  এবং আবার সম্পাদন করতে চান ? এটাতে যা পছন্দিত, সমর্থন বা মতামত আছে সেগুলো নতুন লেখার সাথে যুক্ত থাকবে না।",
   "confirmations.reply.confirm": "মতামত",
   "confirmations.reply.message": "এখন মতামত লিখতে গেলে আপনার এখন যেটা লিখছেন সেটা মুছে যাবে। আপনি নি নিশ্চিত এটা করতে চান ?",
-  "confirmations.unfollow.confirm": "অনুসরণ বন্ধ করুন",
+  "confirmations.unfollow.confirm": "অনুসরণ করা বাতিল করতে",
   "confirmations.unfollow.message": "আপনি কি নিশ্চিত {name} কে আর অনুসরণ করতে চান না ?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "এই লেখাটি আপনার ওয়েবসাইটে যুক্ত করতে নিচের কোডটি বেবহার করুন।",
   "embed.preview": "সেটা দেখতে এরকম হবে:",
   "emoji_button.activity": "কার্যকলাপ",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "আপনি এখনো কোনো ব্যবহারকারীকে সরাননি।",
   "empty_column.notifications": "আপনার এখনো কোনো প্রজ্ঞাপন নেই। কথোপকথন শুরু করতে,  অন্যদের সাথে মেলামেশা করতে পারেন।",
   "empty_column.public": "এখানে এখনো কিছু নেই! প্রকাশ্য ভাবে কিছু লিখুন বা অন্য সার্ভার থেকে কাওকে অনুসরণ করে এই জায়গা ভরে ফেলুন",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -142,11 +164,11 @@
   "follow_request.authorize": "অনুমতি দিন",
   "follow_request.reject": "প্রত্যাখ্যান করুন",
   "getting_started.developers": "তৈরিকারকদের জন্য",
-  "getting_started.directory": "নিজস্ব পাতার তালিকা",
+  "getting_started.directory": "নিজস্ব-পাতাগুলির তালিকা",
   "getting_started.documentation": "নথিপত্র",
   "getting_started.heading": "শুরু করা",
   "getting_started.invite": "অন্যদের আমন্ত্রণ করুন",
-  "getting_started.open_source_notice": "মাস্টাডন একটি মুক্ত সফটওয়্যার। আপনি তৈরিতে সাহায্য করতে পারেন অথবা সমস্যা রিপোর্ট করতে পারেন গিটহাবে {github}।",
+  "getting_started.open_source_notice": "মাস্টাডন একটি মুক্ত সফটওয়্যার। তৈরিতে সাহায্য করতে বা কোনো সমস্যা সম্পর্কে জানাতে আমাদের গিটহাবে যেতে পারেন {github}।",
   "getting_started.security": "নিরাপত্তা",
   "getting_started.terms": "ব্যবহারের নিয়মাবলী",
   "hashtag.column_header.tag_mode.all": "এবং {additional}",
@@ -157,7 +179,7 @@
   "hashtag.column_settings.tag_mode.all": "এগুলো সব",
   "hashtag.column_settings.tag_mode.any": "এর ভেতরে যেকোনোটা",
   "hashtag.column_settings.tag_mode.none": "এগুলোর একটাও না",
-  "hashtag.column_settings.tag_toggle": "আরো ট্যাগ এই কলামে যুক্ত করুন",
+  "hashtag.column_settings.tag_toggle": "আরো ট্যাগ এই কলামে যুক্ত করতে",
   "home.column_settings.basic": "সাধারণ",
   "home.column_settings.show_reblogs": "সমর্থনগুলো দেখান",
   "home.column_settings.show_replies": "মতামত দেখান",
@@ -200,7 +222,7 @@
   "keyboard_shortcuts.local": "স্থানীয় সময়রেখাতে যেতে",
   "keyboard_shortcuts.mention": "লেখককে উল্লেখ করতে",
   "keyboard_shortcuts.muted": "বন্ধ করা ব্যবহারকারীদের তালিকা খুলতে",
-  "keyboard_shortcuts.my_profile": "নিজের পাতা দেখতে",
+  "keyboard_shortcuts.my_profile": "আপনার নিজের পাতা দেখতে",
   "keyboard_shortcuts.notifications": "প্রজ্ঞাপনের কলাম খুলতে",
   "keyboard_shortcuts.pinned": "পিন দেওয়া টুটের তালিকা খুলতে",
   "keyboard_shortcuts.profile": "লেখকের পাতা দেখতে",
@@ -209,14 +231,14 @@
   "keyboard_shortcuts.search": "খোঁজার অংশে ফোকাস করতে",
   "keyboard_shortcuts.start": "\"প্রথম শুরুর\" কলাম বের করতে",
   "keyboard_shortcuts.toggle_hidden": "CW লেখা দেখতে বা লুকাতে",
-  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.toggle_sensitivity": "ভিডিও/ছবি দেখতে বা বন্ধ করতে",
   "keyboard_shortcuts.toot": "নতুন একটা টুট লেখা শুরু করতে",
   "keyboard_shortcuts.unfocus": "লেখা বা খোঁজার জায়গায় ফোকাস না করতে",
   "keyboard_shortcuts.up": "তালিকার উপরের দিকে যেতে",
   "lightbox.close": "বন্ধ",
   "lightbox.next": "পরবর্তী",
   "lightbox.previous": "পূর্ববর্তী",
-  "lightbox.view_context": "View context",
+  "lightbox.view_context": "প্রসঙ্গটি দেখতে",
   "lists.account.add": "তালিকাতে যুক্ত করতে",
   "lists.account.remove": "তালিকা থেকে বাদ দিতে",
   "lists.delete": "তালিকা মুছে ফেলতে",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "তালিকার নতুন শিরোনাম দিতে",
   "lists.search": "যাদের অনুসরণ করেন তাদের ভেতরে খুঁজুন",
   "lists.subheading": "আপনার তালিকা",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "আসছে...",
   "media_gallery.toggle_visible": "দৃশ্যতার অবস্থা বদলান",
   "missing_indicator.label": "খুঁজে পাওয়া যায়নি",
@@ -235,14 +258,14 @@
   "navigation_bar.blocks": "বন্ধ করা ব্যবহারকারী",
   "navigation_bar.community_timeline": "স্থানীয় সময়রেখা",
   "navigation_bar.compose": "নতুন টুট লিখুন",
-  "navigation_bar.direct": "সরাসরি লেখা",
+  "navigation_bar.direct": "সরাসরি লেখাগুলি",
   "navigation_bar.discover": "ঘুরে দেখুন",
   "navigation_bar.domain_blocks": "বন্ধ করা ওয়েবসাইট",
-  "navigation_bar.edit_profile": "নিজের পাতা সম্পাদনা করুন",
+  "navigation_bar.edit_profile": "নিজের পাতা সম্পাদনা করতে",
   "navigation_bar.favourites": "পছন্দের",
   "navigation_bar.filters": "বন্ধ করা শব্দ",
   "navigation_bar.follow_requests": "অনুসরণের অনুরোধগুলি",
-  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.follows_and_followers": "যাদেরকে অনুসরণ করেন এবং যারা তাকে অনুসরণ করে",
   "navigation_bar.info": "এই সার্ভার সম্পর্কে",
   "navigation_bar.keyboard_shortcuts": "হটকীগুলি",
   "navigation_bar.lists": "তালিকাগুলো",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "নিজস্ব",
   "navigation_bar.pins": "পিন দেওয়া টুট",
   "navigation_bar.preferences": "পছন্দসমূহ",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "যুক্তবিশ্বের সময়রেখা",
   "navigation_bar.security": "নিরাপত্তা",
   "notification.favourite": "{name} আপনার কার্যক্রম পছন্দ করেছেন",
@@ -261,18 +283,18 @@
   "notification.reblog": "{name} আপনার কার্যক্রমে সমর্থন দেখিয়েছেন",
   "notifications.clear": "প্রজ্ঞাপনগুলো মুছে ফেলতে",
   "notifications.clear_confirmation": "আপনি কি নির্চিত প্রজ্ঞাপনগুলো মুছে ফেলতে চান ?",
-  "notifications.column_settings.alert": "কম্পিউটারে প্রজ্ঞাপন",
+  "notifications.column_settings.alert": "কম্পিউটারে প্রজ্ঞাপনগুলি",
   "notifications.column_settings.favourite": "পছন্দের:",
-  "notifications.column_settings.filter_bar.advanced": "সব শ্রেণীগুলো দেখতে",
-  "notifications.column_settings.filter_bar.category": "দ্রুত ছাঁকনি বার",
-  "notifications.column_settings.filter_bar.show": "দেখতে",
+  "notifications.column_settings.filter_bar.advanced": "সব শ্রেণীগুলো দেখানো",
+  "notifications.column_settings.filter_bar.category": "সংক্ষিপ্ত ছাঁকনি অংশ",
+  "notifications.column_settings.filter_bar.show": "দেখানো",
   "notifications.column_settings.follow": "নতুন অনুসরণকারীরা:",
   "notifications.column_settings.mention": "প্রজ্ঞাপনগুলো:",
   "notifications.column_settings.poll": "নির্বাচনের ফলাফল:",
-  "notifications.column_settings.push": "পুশ প্রজ্ঞাপন",
+  "notifications.column_settings.push": "পুশ প্রজ্ঞাপনগুলি",
   "notifications.column_settings.reblog": "সমর্থনগুলো:",
-  "notifications.column_settings.show": "কলামে দেখান",
-  "notifications.column_settings.sound": "শব্দ বাজাতে",
+  "notifications.column_settings.show": "কলামে দেখানো",
+  "notifications.column_settings.sound": "শব্দ বাজানো",
   "notifications.filter.all": "সব",
   "notifications.filter.boosts": "সমর্থনগুলো",
   "notifications.filter.favourites": "পছন্দের গুলো",
@@ -281,9 +303,11 @@
   "notifications.filter.polls": "নির্বাচনের ফলাফল",
   "notifications.group": "{count} প্রজ্ঞাপন",
   "poll.closed": "বন্ধ",
-  "poll.refresh": "আবার সতেজ করতে",
+  "poll.refresh": "বদলেছে কিনা দেখতে",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# ভোট} other {# ভোট}}",
   "poll.vote": "ভোট",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "একটা নির্বাচন যোগ করতে",
   "poll_button.remove_poll": "নির্বাচন বাদ দিতে",
   "privacy.change": "লেখার গোপনীয়তা অবস্থা ঠিক করতে",
@@ -295,6 +319,7 @@
   "privacy.public.short": "সর্বজনীন প্রকাশ্য",
   "privacy.unlisted.long": "সর্বজনীন প্রকাশ্য সময়রেখাতে না দেখাতে",
   "privacy.unlisted.short": "প্রকাশ্য নয়",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "আসছে…",
   "regeneration_indicator.sublabel": "আপনার বাড়ির-সময়রেখা প্রস্তূত করা হচ্ছে!",
   "relative_time.days": "{number} দিন",
@@ -319,6 +344,7 @@
   "search_results.accounts": "মানুষ",
   "search_results.hashtags": "হ্যাশট্যাগগুলি",
   "search_results.statuses": "টুট",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {ফলাফল} other {ফলাফল}}",
   "status.admin_account": "@{name} র জন্য পরিচালনার ইন্টারফেসে ঢুকুন",
   "status.admin_status": "যায় লেখাটি পরিচালনার ইন্টারফেসে খুলুন",
@@ -328,7 +354,7 @@
   "status.copy": "লেখাটির লিংক কপি করতে",
   "status.delete": "মুছে ফেলতে",
   "status.detailed_status": "বিস্তারিত কথোপকথনের হিসেবে দেখতে",
-  "status.direct": "@{name} কে সরাসরি পাঠান",
+  "status.direct": "@{name} কে সরাসরি লেখা পাঠাতে",
   "status.embed": "এমবেড করতে",
   "status.favourite": "পছন্দের করতে",
   "status.filtered": "ছাঁকনিদিত",
@@ -350,7 +376,7 @@
   "status.redraft": "মুছে আবার নতুন করে লিখতে",
   "status.reply": "মতামত জানাতে",
   "status.replyAll": "লেখাযুক্ত সবার কাছে মতামত জানাতে",
-  "status.report": "@{name}কে রিপোর্ট করতে",
+  "status.report": "@{name} কে রিপোর্ট করতে",
   "status.sensitive_warning": "সংবেদনশীল কিছু",
   "status.share": "অন্যদের জানান",
   "status.show_less": "কম দেখতে",
@@ -358,9 +384,10 @@
   "status.show_more": "আরো দেখাতে",
   "status.show_more_all": "সবগুলোতে আরো দেখতে",
   "status.show_thread": "আলোচনা দেখতে",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "আলোচনার প্রজ্ঞাপন চালু করতে",
   "status.unpin": "নিজের পাতা থেকে পিন করে রাখাটির পিন খুলতে",
-  "suggestions.dismiss": "সাহায্যের জন্য পরামর্শগুলো সরাতে",
+  "suggestions.dismiss": "সাহায্যের পরামর্শগুলো সরাতে",
   "suggestions.header": "আপনি হয়তোবা এগুলোতে আগ্রহী হতে পারেন…",
   "tabs_bar.federated_timeline": "যুক্তবিশ্ব",
   "tabs_bar.home": "বাড়ি",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "সময় বাকি আছে",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} বাকি আছে",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} কথা বলছে",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "যে পর্যন্ত এটা লেখা হয়েছে, মাস্টাডন থেকে চলে গেলে এটা মুছে যাবে।",
   "upload_area.title": "টেনে এখানে ছেড়ে দিলে এখানে যুক্ত করা যাবে",
-  "upload_button.label": "ছবি বা ভিডিও যুক্ত করতে (এসব ধরণের JPEG, PNG, GIF, WebM, MP4, MOV)",
+  "upload_button.label": "ছবি বা ভিডিও যুক্ত করতে (এসব ধরণের: JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "যা যুক্ত করতে চাচ্ছেন সেটি বেশি বড়, এখানকার সর্বাধিকের মেমোরির উপরে চলে গেছে।",
   "upload_error.poll": "নির্বাচনক্ষেত্রে কোনো ফাইল যুক্ত করা যাবেনা।",
   "upload_form.description": "যারা দেখতে পায়না তাদের জন্য এটা বর্ণনা করতে",
-  "upload_form.focus": "সাধারণ দেখাটি পরিবর্তন করতে",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "মুছে ফেলতে",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "যুক্ত করতে পাঠানো হচ্ছে...",
   "video.close": "ভিডিওটি বন্ধ করতে",
   "video.exit_fullscreen": "পূর্ণ পর্দা থেকে বাইরে বের হতে",
diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json
new file mode 100644
index 0000000000000000000000000000000000000000..db07f1ababed1973e5754c9e3595031409886abc
--- /dev/null
+++ b/app/javascript/mastodon/locales/br.json
@@ -0,0 +1,423 @@
+{
+  "account.add_or_remove_from_list": "Ouzhpenn pe lemel ag ar listennadoù",
+  "account.badges.bot": "Robot",
+  "account.block": "Stankañ @{name}",
+  "account.block_domain": "Kuzh kement tra a {domain}",
+  "account.blocked": "Stanket",
+  "account.cancel_follow_request": "Nullañ ar pedad heuliañ",
+  "account.direct": "Kas ur c'hemennad da @{name}",
+  "account.domain_blocked": "Domani kuzhet",
+  "account.edit_profile": "Aozañ ar profil",
+  "account.endorse": "Lakaat war-wel war ar profil",
+  "account.follow": "Heuliañ",
+  "account.followers": "Heilour·ezed·ion",
+  "account.followers.empty": "Den na heul an implijour-mañ c'hoazh.",
+  "account.follows": "Koumanantoù",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
+  "account.follows_you": "Ho heul",
+  "account.hide_reblogs": "Kuzh toudoù skignet gant @{name}",
+  "account.last_status": "Oberiantiz zivezhañ",
+  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
+  "account.media": "Media",
+  "account.mention": "Menegiñ @{name}",
+  "account.moved_to": "Dilojet en·he deus {name} da:",
+  "account.mute": "Kuzhat @{name}",
+  "account.mute_notifications": "Kuzh kemennoù a @{name}",
+  "account.muted": "Kuzhet",
+  "account.never_active": "Birviken",
+  "account.posts": "Toudoù",
+  "account.posts_with_replies": "Toudoù ha respontoù",
+  "account.report": "Disklêriañ @{name}",
+  "account.requested": "Awaiting approval",
+  "account.share": "Skignañ profil @{name}",
+  "account.show_reblogs": "Diskouez toudoù a @{name}",
+  "account.unblock": "Distankañ @{name}",
+  "account.unblock_domain": "Diguzh {domain}",
+  "account.unendorse": "Don't feature on profile",
+  "account.unfollow": "Diheuliañ",
+  "account.unmute": "Diguzhat @{name}",
+  "account.unmute_notifications": "Diguzhat kemennoù a @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
+  "alert.unexpected.message": "Ur fazi dic'hortozet zo degouezhet.",
+  "alert.unexpected.title": "C'hem !",
+  "autosuggest_hashtag.per_week": "{count} bep sizhun",
+  "boost_modal.combo": "You can press {combo} to skip this next time",
+  "bundle_column_error.body": "Something went wrong while loading this component.",
+  "bundle_column_error.retry": "Klask endro",
+  "bundle_column_error.title": "Fazi rouedad",
+  "bundle_modal_error.close": "Serriñ",
+  "bundle_modal_error.message": "Something went wrong while loading this component.",
+  "bundle_modal_error.retry": "Klask endro",
+  "column.blocks": "Implijour·ezed·ion stanket",
+  "column.community": "Red-amzer lec'hel",
+  "column.direct": "Kemennadoù prevez",
+  "column.directory": "Mont a-dreuz ar profiloù",
+  "column.domain_blocks": "Domani kuzhet",
+  "column.favourites": "Favourites",
+  "column.follow_requests": "Follow requests",
+  "column.home": "Home",
+  "column.lists": "Lists",
+  "column.mutes": "Muted users",
+  "column.notifications": "Notifications",
+  "column.pins": "Pinned toot",
+  "column.public": "Federated timeline",
+  "column_back_button.label": "Back",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
+  "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
+  "column_header.unpin": "Unpin",
+  "column_subheading.settings": "Settings",
+  "community.column_settings.media_only": "Media only",
+  "compose_form.direct_message_warning": "This toot will only be sent to all the mentioned users.",
+  "compose_form.direct_message_warning_learn_more": "Learn more",
+  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+  "compose_form.lock_disclaimer.lock": "locked",
+  "compose_form.placeholder": "What is on your mind?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
+  "compose_form.publish": "Toot",
+  "compose_form.publish_loud": "{publish}!",
+  "compose_form.sensitive.hide": "Mark media as sensitive",
+  "compose_form.sensitive.marked": "Media is marked as sensitive",
+  "compose_form.sensitive.unmarked": "Media is not marked as sensitive",
+  "compose_form.spoiler.marked": "Text is hidden behind warning",
+  "compose_form.spoiler.unmarked": "Text is not hidden",
+  "compose_form.spoiler_placeholder": "Write your warning here",
+  "confirmation_modal.cancel": "Cancel",
+  "confirmations.block.block_and_report": "Block & Report",
+  "confirmations.block.confirm": "Block",
+  "confirmations.block.message": "Are you sure you want to block {name}?",
+  "confirmations.delete.confirm": "Delete",
+  "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.delete_list.confirm": "Delete",
+  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
+  "confirmations.mute.confirm": "Mute",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
+  "confirmations.mute.message": "Are you sure you want to mute {name}?",
+  "confirmations.redraft.confirm": "Delete & redraft",
+  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
+  "confirmations.reply.confirm": "Reply",
+  "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+  "confirmations.unfollow.confirm": "Unfollow",
+  "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
+  "embed.instructions": "Embed this status on your website by copying the code below.",
+  "embed.preview": "Here is what it will look like:",
+  "emoji_button.activity": "Activity",
+  "emoji_button.custom": "Custom",
+  "emoji_button.flags": "Flags",
+  "emoji_button.food": "Food & Drink",
+  "emoji_button.label": "Insert emoji",
+  "emoji_button.nature": "Nature",
+  "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Objects",
+  "emoji_button.people": "People",
+  "emoji_button.recent": "Frequently used",
+  "emoji_button.search": "Search...",
+  "emoji_button.search_results": "Search results",
+  "emoji_button.symbols": "Symbols",
+  "emoji_button.travel": "Travel & Places",
+  "empty_column.account_timeline": "No toots here!",
+  "empty_column.account_unavailable": "Profile unavailable",
+  "empty_column.blocks": "You haven't blocked any users yet.",
+  "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
+  "empty_column.hashtag": "There is nothing in this hashtag yet.",
+  "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
+  "empty_column.home.public_timeline": "the public timeline",
+  "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
+  "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+  "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
+  "follow_request.authorize": "Authorize",
+  "follow_request.reject": "Reject",
+  "getting_started.developers": "Developers",
+  "getting_started.directory": "Profile directory",
+  "getting_started.documentation": "Documentation",
+  "getting_started.heading": "Getting started",
+  "getting_started.invite": "Invite people",
+  "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
+  "getting_started.security": "Security",
+  "getting_started.terms": "Terms of service",
+  "hashtag.column_header.tag_mode.all": "and {additional}",
+  "hashtag.column_header.tag_mode.any": "or {additional}",
+  "hashtag.column_header.tag_mode.none": "without {additional}",
+  "hashtag.column_settings.select.no_options_message": "No suggestions found",
+  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
+  "hashtag.column_settings.tag_mode.all": "All of these",
+  "hashtag.column_settings.tag_mode.any": "Any of these",
+  "hashtag.column_settings.tag_mode.none": "None of these",
+  "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
+  "home.column_settings.basic": "Basic",
+  "home.column_settings.show_reblogs": "Show boosts",
+  "home.column_settings.show_replies": "Show replies",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "introduction.federation.action": "Next",
+  "introduction.federation.federated.headline": "Federated",
+  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
+  "introduction.federation.home.headline": "Home",
+  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
+  "introduction.federation.local.headline": "Local",
+  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
+  "introduction.interactions.action": "Finish toot-orial!",
+  "introduction.interactions.favourite.headline": "Favourite",
+  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
+  "introduction.interactions.reblog.headline": "Boost",
+  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
+  "introduction.interactions.reply.headline": "Reply",
+  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
+  "introduction.welcome.action": "Let's go!",
+  "introduction.welcome.headline": "First steps",
+  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
+  "keyboard_shortcuts.back": "to navigate back",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
+  "keyboard_shortcuts.boost": "to boost",
+  "keyboard_shortcuts.column": "to focus a status in one of the columns",
+  "keyboard_shortcuts.compose": "to focus the compose textarea",
+  "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
+  "keyboard_shortcuts.down": "to move down in the list",
+  "keyboard_shortcuts.enter": "to open status",
+  "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
+  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
+  "keyboard_shortcuts.hotkey": "Hotkey",
+  "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
+  "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
+  "keyboard_shortcuts.profile": "to open author's profile",
+  "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
+  "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
+  "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
+  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.toot": "to start a brand new toot",
+  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
+  "keyboard_shortcuts.up": "to move up in the list",
+  "lightbox.close": "Close",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
+  "lightbox.view_context": "View context",
+  "lists.account.add": "Add to list",
+  "lists.account.remove": "Remove from list",
+  "lists.delete": "Delete list",
+  "lists.edit": "Edit list",
+  "lists.edit.submit": "Change title",
+  "lists.new.create": "Add list",
+  "lists.new.title_placeholder": "New list title",
+  "lists.search": "Search among people you follow",
+  "lists.subheading": "Your lists",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
+  "loading_indicator.label": "Loading...",
+  "media_gallery.toggle_visible": "Toggle visibility",
+  "missing_indicator.label": "Not found",
+  "missing_indicator.sublabel": "This resource could not be found",
+  "mute_modal.hide_notifications": "Hide notifications from this user?",
+  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.blocks": "Blocked users",
+  "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.compose": "Compose new toot",
+  "navigation_bar.direct": "Direct messages",
+  "navigation_bar.discover": "Discover",
+  "navigation_bar.domain_blocks": "Hidden domains",
+  "navigation_bar.edit_profile": "Edit profile",
+  "navigation_bar.favourites": "Favourites",
+  "navigation_bar.filters": "Muted words",
+  "navigation_bar.follow_requests": "Follow requests",
+  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.info": "About this server",
+  "navigation_bar.keyboard_shortcuts": "Hotkeys",
+  "navigation_bar.lists": "Lists",
+  "navigation_bar.logout": "Logout",
+  "navigation_bar.mutes": "Muted users",
+  "navigation_bar.personal": "Personal",
+  "navigation_bar.pins": "Pinned toots",
+  "navigation_bar.preferences": "Preferences",
+  "navigation_bar.public_timeline": "Federated timeline",
+  "navigation_bar.security": "Security",
+  "notification.favourite": "{name} favourited your status",
+  "notification.follow": "{name} followed you",
+  "notification.mention": "{name} mentioned you",
+  "notification.poll": "A poll you have voted in has ended",
+  "notification.reblog": "{name} boosted your status",
+  "notifications.clear": "Clear notifications",
+  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+  "notifications.column_settings.alert": "Desktop notifications",
+  "notifications.column_settings.favourite": "Favourites:",
+  "notifications.column_settings.filter_bar.advanced": "Display all categories",
+  "notifications.column_settings.filter_bar.category": "Quick filter bar",
+  "notifications.column_settings.filter_bar.show": "Show",
+  "notifications.column_settings.follow": "New followers:",
+  "notifications.column_settings.mention": "Mentions:",
+  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.push": "Push notifications",
+  "notifications.column_settings.reblog": "Boosts:",
+  "notifications.column_settings.show": "Show in column",
+  "notifications.column_settings.sound": "Play sound",
+  "notifications.filter.all": "All",
+  "notifications.filter.boosts": "Boosts",
+  "notifications.filter.favourites": "Favourites",
+  "notifications.filter.follows": "Follows",
+  "notifications.filter.mentions": "Mentions",
+  "notifications.filter.polls": "Poll results",
+  "notifications.group": "{count} notifications",
+  "poll.closed": "Closed",
+  "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
+  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
+  "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
+  "privacy.change": "Adjust status privacy",
+  "privacy.direct.long": "Post to mentioned users only",
+  "privacy.direct.short": "Direct",
+  "privacy.private.long": "Post to followers only",
+  "privacy.private.short": "Followers-only",
+  "privacy.public.long": "Post to public timelines",
+  "privacy.public.short": "Public",
+  "privacy.unlisted.long": "Do not show in public timelines",
+  "privacy.unlisted.short": "Unlisted",
+  "refresh": "Refresh",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
+  "relative_time.days": "{number}d",
+  "relative_time.hours": "{number}h",
+  "relative_time.just_now": "now",
+  "relative_time.minutes": "{number}m",
+  "relative_time.seconds": "{number}s",
+  "reply_indicator.cancel": "Cancel",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:",
+  "report.placeholder": "Additional comments",
+  "report.submit": "Submit",
+  "report.target": "Report {target}",
+  "search.placeholder": "Search",
+  "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+  "search_popout.tips.hashtag": "hashtag",
+  "search_popout.tips.status": "status",
+  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
+  "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
+  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+  "status.admin_account": "Open moderation interface for @{name}",
+  "status.admin_status": "Open this status in the moderation interface",
+  "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
+  "status.cannot_reblog": "This post cannot be boosted",
+  "status.copy": "Copy link to status",
+  "status.delete": "Delete",
+  "status.detailed_status": "Detailed conversation view",
+  "status.direct": "Direct message @{name}",
+  "status.embed": "Embed",
+  "status.favourite": "Favourite",
+  "status.filtered": "Filtered",
+  "status.load_more": "Load more",
+  "status.media_hidden": "Media hidden",
+  "status.mention": "Mention @{name}",
+  "status.more": "More",
+  "status.mute": "Mute @{name}",
+  "status.mute_conversation": "Mute conversation",
+  "status.open": "Expand this status",
+  "status.pin": "Pin on profile",
+  "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
+  "status.reblog": "Boost",
+  "status.reblog_private": "Boost to original audience",
+  "status.reblogged_by": "{name} boosted",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
+  "status.redraft": "Delete & re-draft",
+  "status.reply": "Reply",
+  "status.replyAll": "Reply to thread",
+  "status.report": "Report @{name}",
+  "status.sensitive_warning": "Sensitive content",
+  "status.share": "Share",
+  "status.show_less": "Show less",
+  "status.show_less_all": "Show less for all",
+  "status.show_more": "Show more",
+  "status.show_more_all": "Show more for all",
+  "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
+  "status.unmute_conversation": "Unmute conversation",
+  "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
+  "tabs_bar.federated_timeline": "Federated",
+  "tabs_bar.home": "Home",
+  "tabs_bar.local_timeline": "Local",
+  "tabs_bar.notifications": "Notifications",
+  "tabs_bar.search": "Search",
+  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
+  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
+  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
+  "time_remaining.moments": "Moments remaining",
+  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
+  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
+  "upload_area.title": "Drag & drop to upload",
+  "upload_button.label": "Add media ({formats})",
+  "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.edit": "Edit",
+  "upload_form.undo": "Delete",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
+  "upload_progress.label": "Uploading…",
+  "video.close": "Close video",
+  "video.exit_fullscreen": "Exit full screen",
+  "video.expand": "Expand video",
+  "video.fullscreen": "Full screen",
+  "video.hide": "Hide video",
+  "video.mute": "Mute sound",
+  "video.pause": "Pause",
+  "video.play": "Play",
+  "video.unmute": "Unmute sound"
+}
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index 5f76bed2b46496aa272c6d12c550aff1f29028a3..e6d1bba1d3e3dc50ec28816a6daceae502474946 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -4,6 +4,7 @@
   "account.block": "Bloqueja @{name}",
   "account.block_domain": "Amaga-ho tot de {domain}",
   "account.blocked": "Bloquejat",
+  "account.cancel_follow_request": "Anul·la la sol·licitud de seguiment",
   "account.direct": "Missatge directe @{name}",
   "account.domain_blocked": "Domini ocult",
   "account.edit_profile": "Editar el perfil",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Aquest usuari encara no segueix a ningú.",
   "account.follows_you": "Et segueix",
   "account.hide_reblogs": "Amaga els impulsos de @{name}",
+  "account.last_status": "Darrer actiu",
   "account.link_verified_on": "La propietat d'aquest enllaç es va verificar el dia {date}",
   "account.locked_info": "Aquest estat de privadesa del compte està definit com a bloquejat. El propietari revisa manualment qui pot seguir-lo.",
   "account.media": "Mèdia",
@@ -23,6 +25,7 @@
   "account.mute": "Silencia @{name}",
   "account.mute_notifications": "Notificacions desactivades de @{name}",
   "account.muted": "Silenciat",
+  "account.never_active": "Mai",
   "account.posts": "Toots",
   "account.posts_with_replies": "Toots i respostes",
   "account.report": "Informe @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Deixa de seguir",
   "account.unmute": "Treure silenci de @{name}",
   "account.unmute_notifications": "Activar notificacions de @{name}",
+  "alert.rate_limited.message": "Si us plau torna-ho a provar després de {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Límit de freqüència",
   "alert.unexpected.message": "S'ha produït un error inesperat.",
   "alert.unexpected.title": "Vaja!",
+  "autosuggest_hashtag.per_week": "{count} per setmana",
   "boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop",
   "bundle_column_error.body": "S'ha produït un error en carregar aquest component.",
   "bundle_column_error.retry": "Torna-ho a provar",
@@ -47,6 +53,7 @@
   "column.blocks": "Usuaris bloquejats",
   "column.community": "Línia de temps local",
   "column.direct": "Missatges directes",
+  "column.directory": "Navega els perfils",
   "column.domain_blocks": "Dominis ocults",
   "column.favourites": "Favorits",
   "column.follow_requests": "Peticions per seguir-te",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Estàs segur que vols suprimir permanentment aquesta llista?",
   "confirmations.domain_block.confirm": "Amaga tot el domini",
   "confirmations.domain_block.message": "Estàs segur, realment segur que vols bloquejar totalment {domain}? En la majoria dels casos bloquejar o silenciar uns pocs objectius és suficient i preferible. No veuràs contingut d’aquest domini en cap de les línies públiques ni en les notificacions. Els teus seguidors d’aquest domini seran eliminats.",
+  "confirmations.logout.confirm": "Tancar sessió",
+  "confirmations.logout.message": "Segur que vols tancar la sessió?",
   "confirmations.mute.confirm": "Silencia",
+  "confirmations.mute.explanation": "Això amagarà les seves publicacions i les que els mencionen però encara els permetrà veure les teves i seguir-te.",
   "confirmations.mute.message": "Estàs segur que vols silenciar {name}?",
   "confirmations.redraft.confirm": "Esborrar i refer",
   "confirmations.redraft.message": "Estàs segur que vols esborrar aquest toot i tornar a redactar-lo? Perderàs totes els impulsos i favorits, i les respostes al toot original es quedaran orfes.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Responen ara es sobreescriurà el missatge que estàs editant. Estàs segur que vols continuar?",
   "confirmations.unfollow.confirm": "Deixa de seguir",
   "confirmations.unfollow.message": "Estàs segur que vols deixar de seguir {name}?",
+  "conversation.delete": "Elimina la conversa",
+  "conversation.mark_as_read": "Marca com a llegida",
+  "conversation.open": "Veure conversa",
+  "conversation.with": "Amb {names}",
+  "directory.federated": "Del fedivers conegut",
+  "directory.local": "Només de {domain}",
+  "directory.new_arrivals": "Arribades noves",
+  "directory.recently_active": "Recentment actius",
   "embed.instructions": "Incrusta aquest toot al lloc web copiant el codi a continuació.",
   "embed.preview": "Aquí tenim quin aspecte tindrá:",
   "emoji_button.activity": "Activitat",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Encara no has silenciat cap usuari.",
   "empty_column.notifications": "Encara no tens notificacions. Interactua amb altres per iniciar la conversa.",
   "empty_column.public": "No hi ha res aquí! Escriu públicament alguna cosa o manualment segueix usuaris d'altres servidors per omplir-ho",
+  "error.unexpected_crash.explanation": "A causa d'un bug en el nostre codi o un problema de compatibilitat del navegador, aquesta pàgina no podria ser mostrada correctament.",
+  "error.unexpected_crash.next_steps": "Prova recarregant la pàgina. Si això no ajuda encara pots ser capaç d'utilitzar Mastodont a través d'un navegador diferent o app nativa.",
+  "errors.unexpected_crash.copy_stacktrace": "Còpia stacktrace al porta-retalls",
+  "errors.unexpected_crash.report_issue": "Informa d'un problema",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Nova llista",
   "lists.search": "Cercar entre les persones que segueixes",
   "lists.subheading": "Les teves llistes",
+  "load_pending": "{count, plural, one {# element nou} other {# elements nous}}",
   "loading_indicator.label": "Carregant...",
   "media_gallery.toggle_visible": "Alternar visibilitat",
   "missing_indicator.label": "No trobat",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Toots fixats",
   "navigation_bar.preferences": "Preferències",
-  "navigation_bar.profile_directory": "Directori de perfils",
   "navigation_bar.public_timeline": "Línia de temps federada",
   "navigation_bar.security": "Seguretat",
   "notification.favourite": "{name} ha afavorit el teu estat",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notificacions",
   "poll.closed": "Finalitzada",
   "poll.refresh": "Actualitza",
+  "poll.total_people": "{count, plural, one {# persona} other {# persones}}",
   "poll.total_votes": "{count, plural, one {# vot} other {# vots}}",
   "poll.vote": "Vota",
+  "poll.voted": "Vas votar per aquesta resposta",
   "poll_button.add_poll": "Afegeix una enquesta",
   "poll_button.remove_poll": "Elimina l'enquesta",
   "privacy.change": "Ajusta l'estat de privacitat",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Públic",
   "privacy.unlisted.long": "No publicar en línies de temps públiques",
   "privacy.unlisted.short": "No llistat",
+  "refresh": "Actualitza",
   "regeneration_indicator.label": "Carregant…",
   "regeneration_indicator.sublabel": "S'està preparant la línia de temps Inici!",
   "relative_time.days": "fa {number} dies",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Gent",
   "search_results.hashtags": "Etiquetes",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "La cerca de toots pel seu contingut no està habilitada en aquest servidor Mastodon.",
   "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
   "status.admin_account": "Obre l'interfície de moderació per a @{name}",
   "status.admin_status": "Obre aquest toot a la interfície de moderació",
@@ -358,6 +384,7 @@
   "status.show_more": "Mostra més",
   "status.show_more_all": "Mostra més per a tot",
   "status.show_thread": "Mostra el fil",
+  "status.uncached_media_warning": "No està disponible",
   "status.unmute_conversation": "Activar conversació",
   "status.unpin": "Deslliga del perfil",
   "suggestions.dismiss": "Descartar suggeriment",
@@ -372,15 +399,23 @@
   "time_remaining.minutes": "{number, plural, one {# minut} other {# minuts}} restants",
   "time_remaining.moments": "Moments restants",
   "time_remaining.seconds": "{number, plural, one {# segon} other {# segons}} restants",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {persona} other {gent}} talking",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {persona} other {persones}} parlant-hi",
+  "trends.trending_now": "Ara en tendència",
   "ui.beforeunload": "El teu esborrany es perdrà si surts de Mastodon.",
   "upload_area.title": "Arrossega i deixa anar per a carregar",
   "upload_button.label": "Afegir multimèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "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.description": "Descriure els problemes visuals",
-  "upload_form.focus": "Modificar la previsualització",
+  "upload_form.edit": "Edita",
   "upload_form.undo": "Esborra",
+  "upload_modal.analyzing_picture": "Analitzant imatge…",
+  "upload_modal.apply": "Aplica",
+  "upload_modal.description_placeholder": "Uns salts ràpids de guineu marró sobre el gos gandul",
+  "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.",
+  "upload_modal.preview_label": "Previsualitza ({ratio})",
   "upload_progress.label": "Pujant...",
   "video.close": "Tancar el vídeo",
   "video.exit_fullscreen": "Sortir de pantalla completa",
diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json
index 369b8ce80ad214df08c172b8de649a1336774003..8321ffe5a365427718ce2ed08942f4fdd7cb62f3 100644
--- a/app/javascript/mastodon/locales/co.json
+++ b/app/javascript/mastodon/locales/co.json
@@ -1,9 +1,10 @@
 {
-  "account.add_or_remove_from_list": "Aghjustà o toglie da e liste",
+  "account.add_or_remove_from_list": "Aghjunghje o toglie da e liste",
   "account.badges.bot": "Bot",
   "account.block": "Bluccà @{name}",
   "account.block_domain": "Piattà tuttu da {domain}",
   "account.blocked": "Bluccatu",
+  "account.cancel_follow_request": "Annullà a dumanda d'abbunamentu",
   "account.direct": "Missaghju direttu @{name}",
   "account.domain_blocked": "Duminiu piattatu",
   "account.edit_profile": "Mudificà u prufile",
@@ -15,6 +16,7 @@
   "account.follows.empty": "St'utilizatore ùn seguita nisunu.",
   "account.follows_you": "Vi seguita",
   "account.hide_reblogs": "Piattà spartere da @{name}",
+  "account.last_status": "Ultima attività",
   "account.link_verified_on": "A prupietà di stu ligame hè stata verificata u {date}",
   "account.locked_info": "U statutu di vita privata di u contu hè chjosu. U pruprietariu esamina manualmente e dumande d'abbunamentu.",
   "account.media": "Media",
@@ -23,6 +25,7 @@
   "account.mute": "Piattà @{name}",
   "account.mute_notifications": "Piattà nutificazione da @{name}",
   "account.muted": "Piattatu",
+  "account.never_active": "Mai",
   "account.posts": "Statuti",
   "account.posts_with_replies": "Statuti è risposte",
   "account.report": "Palisà @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Ùn siguità più",
   "account.unmute": "Ùn piattà più @{name}",
   "account.unmute_notifications": "Ùn piattà più nutificazione da @{name}",
+  "alert.rate_limited.message": "Pruvate ancu dop'à {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Ghjettu limitatu",
   "alert.unexpected.message": "Un prublemu inaspettatu hè accadutu.",
   "alert.unexpected.title": "Uups!",
+  "autosuggest_hashtag.per_week": "{count} per settimana",
   "boost_modal.combo": "Pudete appughjà nant'à {combo} per saltà quessa a prussima volta",
   "bundle_column_error.body": "C'hè statu un prublemu caricandu st'elementu.",
   "bundle_column_error.retry": "Pruvà torna",
@@ -47,6 +53,7 @@
   "column.blocks": "Utilizatori bluccati",
   "column.community": "Linea pubblica lucale",
   "column.direct": "Missaghji diretti",
+  "column.directory": "Percorre i prufili",
   "column.domain_blocks": "Duminii piattati",
   "column.favourites": "Favuriti",
   "column.follow_requests": "Dumande d'abbunamentu",
@@ -71,7 +78,7 @@
   "compose_form.lock_disclaimer": "U vostru contu ùn hè micca {locked}. Tuttu u mondu pò seguitavi è vede i vostri statuti privati.",
   "compose_form.lock_disclaimer.lock": "privatu",
   "compose_form.placeholder": "À chè pensate?",
-  "compose_form.poll.add_option": "Aghjustà una scelta",
+  "compose_form.poll.add_option": "Aghjunghje scelta",
   "compose_form.poll.duration": "Durata di u scandagliu",
   "compose_form.poll.option_placeholder": "Scelta {number}",
   "compose_form.poll.remove_option": "Toglie sta scelta",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Site sicuru·a che vulete supprime sta lista?",
   "confirmations.domain_block.confirm": "Piattà tuttu u duminiu",
   "confirmations.domain_block.message": "Site sicuru·a che vulete piattà tuttu à {domain}? Saria forse abbastanza di bluccà ò piattà alcuni conti da quallà. Ùn viderete più nunda da quallà indè e linee pubbliche o e nutificazione. I vostri abbunati da stu duminiu saranu tolti.",
+  "confirmations.logout.confirm": "Scunnettassi",
+  "confirmations.logout.message": "Site sicuru·a che vulete scunnettà vi?",
   "confirmations.mute.confirm": "Piattà",
+  "confirmations.mute.explanation": "Quessu hà da piattà i statuti da sta persona è i posti chì a mintuvanu, mà ellu·a puderà sempre vede i vostri statuti è siguità vi.",
   "confirmations.mute.message": "Site sicuru·a che vulete piattà @{name}?",
   "confirmations.redraft.confirm": "Sguassà è riscrive",
   "confirmations.redraft.message": "Site sicuru·a chè vulete sguassà stu statutu è riscrivelu? I favuriti è spartere saranu persi, è e risposte diventeranu orfane.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Risponde avà sguasserà u missaghju chì scrivite. Site sicuru·a chì vulete cuntinuà?",
   "confirmations.unfollow.confirm": "Disabbunassi",
   "confirmations.unfollow.message": "Site sicuru·a ch'ùn vulete più siguità @{name}?",
+  "conversation.delete": "Sguassà a cunversazione",
+  "conversation.mark_as_read": "Marcà cum'è lettu",
+  "conversation.open": "Vede a cunversazione",
+  "conversation.with": "Cù {names}",
+  "directory.federated": "Da u fediverse cunisciutu",
+  "directory.local": "Solu da {domain}",
+  "directory.new_arrivals": "Ultimi arrivi",
+  "directory.recently_active": "Attività ricente",
   "embed.instructions": "Integrà stu statutu à u vostru situ cù u codice quì sottu.",
   "embed.preview": "Assumiglierà à qualcosa cusì:",
   "emoji_button.activity": "Attività",
@@ -123,8 +141,8 @@
   "empty_column.community": "Ùn c'hè nunda indè a linea lucale. Scrivete puru qualcosa!",
   "empty_column.direct": "Ùn avete ancu nisun missaghju direttu. S'è voi mandate o ricevete unu, u vidarete quì.",
   "empty_column.domain_blocks": "Ùn c'hè manc'un duminiu bluccatu avà.",
-  "empty_column.favourited_statuses": "Ùn avete manc'unu statutu favuritu. Quandu aghjusterate unu à i vostri favuriti, sarà mustratu quì.",
-  "empty_column.favourites": "Nisunu hà aghjustatu stu statutu à i so favuriti. Quandu qualch'unu farà quessa, u so contu sarà mustratu quì.",
+  "empty_column.favourited_statuses": "Ùn avete manc'unu statutu favuritu. Quandu aghjunghjerate unu à i vostri favuriti, sarà mustratu quì.",
+  "empty_column.favourites": "Nisunu hà aghjuntu stu statutu à i so favuriti. Quandu qualch'unu farà quessa, u so contu sarà mustratu quì.",
   "empty_column.follow_requests": "Ùn avete manc'una dumanda d'abbunamentu. Quandu averete una, sarà mustrata quì.",
   "empty_column.hashtag": "Ùn c'hè ancu nunda quì.",
   "empty_column.home": "A vostr'accolta hè viota! Pudete andà nant'à {public} o pruvà a ricerca per truvà parsone da siguità.",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Per avà ùn avete manc'un utilizatore piattatu.",
   "empty_column.notifications": "Ùn avete ancu nisuna nutificazione. Interact with others to start the conversation.",
   "empty_column.public": "Ùn c'hè nunda quì! Scrivete qualcosa in pubblicu o seguitate utilizatori d'altri servori per empie a linea pubblica",
+  "error.unexpected_crash.explanation": "In ragione d'un bug indè u nostru codice o un prublemu di cumpatibilità cù quessu navigatore, sta pagina ùn hè micca pussuta esse affissata currettamente.",
+  "error.unexpected_crash.next_steps": "Pruvate d'attualizà sta pagina. S'ellu persiste u prublemu, pudete forse sempre accede à Mastodon dapoi un'alltru navigatore o applicazione.",
+  "errors.unexpected_crash.copy_stacktrace": "Cupià stacktrace nant'à u fermacarta",
+  "errors.unexpected_crash.report_issue": "Palisà prublemu",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -222,10 +244,11 @@
   "lists.delete": "Supprime a lista",
   "lists.edit": "Mudificà a lista",
   "lists.edit.submit": "Cambià u titulu",
-  "lists.new.create": "Aghjustà una lista",
+  "lists.new.create": "Aghjunghje",
   "lists.new.title_placeholder": "Titulu di a lista",
   "lists.search": "Circà indè i vostr'abbunamenti",
   "lists.subheading": "E vo liste",
+  "load_pending": "{count, plural, one {# entrata nova} other {# entrate nove}}",
   "loading_indicator.label": "Caricamentu...",
   "media_gallery.toggle_visible": "Cambià a visibilità",
   "missing_indicator.label": "Micca trovu",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Persunale",
   "navigation_bar.pins": "Statuti puntarulati",
   "navigation_bar.preferences": "Preferenze",
-  "navigation_bar.profile_directory": "Annuariu di i prufili",
   "navigation_bar.public_timeline": "Linea pubblica glubale",
   "navigation_bar.security": "Sicurità",
   "notification.favourite": "{name} hà aghjuntu u vostru statutu à i so favuriti",
@@ -282,9 +304,11 @@
   "notifications.group": "{count} nutificazione",
   "poll.closed": "Chjosu",
   "poll.refresh": "Attualizà",
+  "poll.total_people": "{count, plural, one {# persona} other {# persone}}",
   "poll.total_votes": "{count, plural, one {# votu} other {# voti}}",
   "poll.vote": "Vutà",
-  "poll_button.add_poll": "Aghjustà un scandagliu",
+  "poll.voted": "Avete vutatu per sta risposta",
+  "poll_button.add_poll": "Aghjunghje",
   "poll_button.remove_poll": "Toglie u scandagliu",
   "privacy.change": "Mudificà a cunfidenzialità di u statutu",
   "privacy.direct.long": "Mandà solu à quelli chì so mintuvati",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Pubblicu",
   "privacy.unlisted.long": "Ùn mette micca nant'à e linee pubbliche",
   "privacy.unlisted.short": "Micca listatu",
+  "refresh": "Attualizà",
   "regeneration_indicator.label": "Caricamentu…",
   "regeneration_indicator.sublabel": "Priparazione di a vostra pagina d'accolta!",
   "relative_time.days": "{number}ghj",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Ghjente",
   "search_results.hashtags": "Hashtag",
   "search_results.statuses": "Statuti",
+  "search_results.statuses_fts_disabled": "A ricerca di i cuntinuti di i statuti ùn hè micca attivata nant'à stu servore Mastodon.",
   "search_results.total": "{count, number} {count, plural, one {risultatu} other {risultati}}",
   "status.admin_account": "Apre l'interfaccia di muderazione per @{name}",
   "status.admin_status": "Apre stu statutu in l'interfaccia di muderazione",
@@ -358,6 +384,7 @@
   "status.show_more": "Slibrà",
   "status.show_more_all": "Slibrà tuttu",
   "status.show_thread": "Vede u filu",
+  "status.uncached_media_warning": "Micca dispunibule",
   "status.unmute_conversation": "Ùn piattà più a cunversazione",
   "status.unpin": "Spuntarulà da u prufile",
   "suggestions.dismiss": "Righjittà a pruposta",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Ci fermanu qualchi mumentu",
   "time_remaining.seconds": "{number, plural, one {# siconda ferma} other {# siconde fermanu}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} parlanu",
+  "trends.trending_now": "Tindenze d'avà",
   "ui.beforeunload": "A bruttacopia sarà persa s'ellu hè chjosu Mastodon.",
   "upload_area.title": "Drag & drop per caricà un fugliale",
   "upload_button.label": "Aghjunghje un media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Limita di caricamentu di fugliali trapassata.",
   "upload_error.poll": "Ùn si pò micca caricà fugliali cù i scandagli.",
   "upload_form.description": "Discrive per i malvistosi",
-  "upload_form.focus": "Cambià a vista",
+  "upload_form.edit": "Mudificà",
   "upload_form.undo": "Sguassà",
+  "upload_modal.analyzing_picture": "Analisi di u ritrattu…",
+  "upload_modal.apply": "Affettà",
+  "upload_modal.description_placeholder": "Chì tempi brevi ziu, quandu solfeghji",
+  "upload_modal.detect_text": "Ditettà testu da u ritrattu",
+  "upload_modal.edit_media": "Cambià media",
+  "upload_modal.hint": "Cliccate o sguillate u chjerchju nant'à a vista per sceglie u puntu fucale chì sarà sempre in vista indè tutte e miniature.",
+  "upload_modal.preview_label": "Vista ({ratio})",
   "upload_progress.label": "Caricamentu...",
   "video.close": "Chjudà a video",
   "video.exit_fullscreen": "Caccià u pienu screnu",
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index fa94081d4c23372fe0d36887ec292c5173a8437f..ed0588e7c7a0af87949f58a10c936623fe626af4 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -4,6 +4,7 @@
   "account.block": "Zablokovat uživatele @{name}",
   "account.block_domain": "Skrýt vše z {domain}",
   "account.blocked": "Blokován/a",
+  "account.cancel_follow_request": "Zrušit požadavek o sledování",
   "account.direct": "Poslat přímou zprávu uživateli @{name}",
   "account.domain_blocked": "Doména skryta",
   "account.edit_profile": "Upravit profil",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Tento uživatel ještě nikoho nesleduje.",
   "account.follows_you": "Sleduje vás",
   "account.hide_reblogs": "Skrýt boosty od uživatele @{name}",
+  "account.last_status": "Naposledy aktivní",
   "account.link_verified_on": "Vlastnictví tohoto odkazu bylo zkontrolováno {date}",
   "account.locked_info": "Stav soukromí tohoto účtu je nastaven na zamčeno. Jeho vlastník ručně posuzuje, kdo ho může sledovat.",
   "account.media": "Média",
@@ -23,6 +25,7 @@
   "account.mute": "Skrýt uživatele @{name}",
   "account.mute_notifications": "Skrýt oznámení od uživatele @{name}",
   "account.muted": "Skryt/a",
+  "account.never_active": "Nikdy",
   "account.posts": "Tooty",
   "account.posts_with_replies": "Tooty a odpovědi",
   "account.report": "Nahlásit uživatele @{name}",
@@ -35,9 +38,12 @@
   "account.unfollow": "Přestat sledovat",
   "account.unmute": "Odkrýt uživatele @{name}",
   "account.unmute_notifications": "Odkrýt oznámení od uživatele @{name}",
+  "alert.rate_limited.message": "Prosím zkuste to znovu za {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rychlost omezena",
   "alert.unexpected.message": "Objevila se neočekávaná chyba.",
   "alert.unexpected.title": "Jejda!",
-  "boost_modal.combo": "Příště můžete pro přeskočení kliknout na {combo}",
+  "autosuggest_hashtag.per_week": "{count} za týden",
+  "boost_modal.combo": "Příště můžete pro přeskočení stisknout {combo}",
   "bundle_column_error.body": "Při načítání tohoto komponentu se něco pokazilo.",
   "bundle_column_error.retry": "Zkuste to znovu",
   "bundle_column_error.title": "Chyba sítě",
@@ -47,6 +53,7 @@
   "column.blocks": "Blokovaní uživatelé",
   "column.community": "Místní časová osa",
   "column.direct": "Přímé zprávy",
+  "column.directory": "Prozkoumat profily",
   "column.domain_blocks": "Skryté domény",
   "column.favourites": "Oblíbené",
   "column.follow_requests": "Požadavky o sledování",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Jste si jistý/á, že chcete tento seznam navždy smazat?",
   "confirmations.domain_block.confirm": "Skrýt celou doménu",
   "confirmations.domain_block.message": "Jste si opravdu, opravdu jistý/á, že chcete blokovat celou doménu {domain}? Ve většině případů stačí zablokovat nebo skrýt pár konkrétních uživatelů, což se doporučuje. Z této domény neuvidíte obsah v žádné veřejné časové ose ani v oznámeních. Vaši sledující z této domény budou odstraněni.",
+  "confirmations.logout.confirm": "Odhlásit",
+  "confirmations.logout.message": "Jste si jistý/á, že se chcete odhlásit?",
   "confirmations.mute.confirm": "Skrýt",
+  "confirmations.mute.explanation": "Tohle skryje jeho příspěvky a příspěvky, které ho zmiňují, ale uživatel pořád bude moci vidět vaše příspěvky a sledovat vás.",
   "confirmations.mute.message": "Jste si jistý/á, že chcete skrýt uživatele {name}?",
   "confirmations.redraft.confirm": "Smazat a přepsat",
   "confirmations.redraft.message": "Jste si jistý/á, že chcete smazat a přepsat tento toot? Oblíbení a boosty budou ztraceny a odpovědi na původní příspěvek budou opuštěny.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Odpovězením nyní přepíšete zprávu, kterou aktuálně píšete. Jste si jistý/á, že chcete pokračovat?",
   "confirmations.unfollow.confirm": "Přestat sledovat",
   "confirmations.unfollow.message": "jste si jistý/á, že chcete přestat sledovat uživatele {name}?",
+  "conversation.delete": "Smazat konverzaci",
+  "conversation.mark_as_read": "Označit jako přečtenou",
+  "conversation.open": "Zobrazit konverzaci",
+  "conversation.with": "S {names}",
+  "directory.federated": "Ze známého fedivesmíru",
+  "directory.local": "Pouze z {domain}",
+  "directory.new_arrivals": "Nově příchozí",
+  "directory.recently_active": "Nedávno aktivní",
   "embed.instructions": "Pro přidání tootu na vaši webovou stránku zkopírujte níže uvedený kód.",
   "embed.preview": "Takhle to bude vypadat:",
   "emoji_button.activity": "Aktivita",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Ještě jste neskryl/a žádné uživatele.",
   "empty_column.notifications": "Ještě nemáte žádná oznámení. Začněte konverzaci komunikováním s ostatními.",
   "empty_column.public": "Tady nic není! Napište něco veřejně, nebo začněte ručně sledovat uživatele z jiných serverů, aby tu něco přibylo",
+  "error.unexpected_crash.explanation": "Kvůli chybě v našem kódu nebo problému s kompatibilitou prohlížeče nemohla být tato stránka načtena správně.",
+  "error.unexpected_crash.next_steps": "Zkuste obnovit stránku. Pokud to nepomůže, budete možná moci dále používat Mastodon pomocí jiného prohlížeče nebo nativní aplikace.",
+  "errors.unexpected_crash.copy_stacktrace": "Zkopírovat stacktrace do schránky",
+  "errors.unexpected_crash.report_issue": "Nahlásit problém",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -147,7 +169,7 @@
   "getting_started.heading": "Začínáme",
   "getting_started.invite": "Pozvat lidi",
   "getting_started.open_source_notice": "Mastodon je otevřený software. Na GitHubu k němu můžete přispět nebo nahlásit chyby: {github}.",
-  "getting_started.security": "Zabezpečení",
+  "getting_started.security": "Nastavení účtu",
   "getting_started.terms": "Podmínky používání",
   "hashtag.column_header.tag_mode.all": "a {additional}",
   "hashtag.column_header.tag_mode.any": "nebo {additional}",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Název nového seznamu",
   "lists.search": "Hledejte mezi lidmi, které sledujete",
   "lists.subheading": "Vaše seznamy",
+  "load_pending": "{count, plural, one {# nová položka} few {# nové položky} many {# nových položek} other {# nových položek}}",
   "loading_indicator.label": "Načítám…",
   "media_gallery.toggle_visible": "Přepínat viditelnost",
   "missing_indicator.label": "Nenalezeno",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Osobní",
   "navigation_bar.pins": "Připnuté tooty",
   "navigation_bar.preferences": "Předvolby",
-  "navigation_bar.profile_directory": "Adresář profilů",
   "navigation_bar.public_timeline": "Federovaná časová osa",
   "navigation_bar.security": "Zabezpečení",
   "notification.favourite": "{name} si oblíbil/a váš toot",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} oznámení",
   "poll.closed": "Uzavřena",
   "poll.refresh": "Obnovit",
+  "poll.total_people": "{count, plural, one {# člověk} few {# lidé} many {# lidí} other {# lidí}}",
   "poll.total_votes": "{count, plural, one {# hlas} few {# hlasy} many {# hlasu} other {# hlasů}}",
   "poll.vote": "Hlasovat",
+  "poll.voted": "Pro tuto odpověď jste hlasoval/a",
   "poll_button.add_poll": "Přidat anketu",
   "poll_button.remove_poll": "Odstranit anketu",
   "privacy.change": "Změnit soukromí tootu",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Veřejný",
   "privacy.unlisted.long": "Neodeslat na veřejné časové osy",
   "privacy.unlisted.short": "Neuvedený",
+  "refresh": "Obnovit",
   "regeneration_indicator.label": "Načítám…",
   "regeneration_indicator.sublabel": "Váš domovský proud se připravuje!",
   "relative_time.days": "{number} d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Lidé",
   "search_results.hashtags": "Hashtagy",
   "search_results.statuses": "Tooty",
+  "search_results.statuses_fts_disabled": "Vyhledávání tootů podle jejich obsahu není na tomto serveru Mastodon povoleno.",
   "search_results.total": "{count, number} {count, plural, one {výsledek} few {výsledky} many {výsledku} other {výsledků}}",
   "status.admin_account": "Otevřít moderátorské rozhraní pro uživatele @{name}",
   "status.admin_status": "Otevřít tento toot v moderátorském rozhraní",
@@ -358,6 +384,7 @@
   "status.show_more": "Zobrazit více",
   "status.show_more_all": "Zobrazit více pro všechny",
   "status.show_thread": "Zobrazit vlákno",
+  "status.uncached_media_warning": "Nedostupné",
   "status.unmute_conversation": "Odkrýt konverzaci",
   "status.unpin": "Odepnout z profilu",
   "suggestions.dismiss": "Odmítnout návrh",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Zbývá několik sekund",
   "time_remaining.seconds": "{number, plural, one {Zbývá # sekunda} few {Zbývají # sekundy} many {Zbývá # sekundy} other {Zbývá # sekund}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {člověk} few {lidé} many {lidí} other {lidí}} hovoří",
+  "trends.trending_now": "Aktuální trendy",
   "ui.beforeunload": "Váš koncept se ztratí, pokud Mastodon opustíte.",
   "upload_area.title": "Přetažením nahrajete",
   "upload_button.label": "Přidat média (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Byl překročen limit nahraných souborů.",
   "upload_error.poll": "Nahrávání souborů není povoleno u anket.",
   "upload_form.description": "Popis pro zrakově postižené",
-  "upload_form.focus": "Změnit náhled",
+  "upload_form.edit": "Upravit",
   "upload_form.undo": "Smazat",
+  "upload_modal.analyzing_picture": "Analyzuji obrázek…",
+  "upload_modal.apply": "Použít",
+  "upload_modal.description_placeholder": "Příliš žluťoučký kůň úpěl ďábelské ódy",
+  "upload_modal.detect_text": "Detekovat text z obrázku",
+  "upload_modal.edit_media": "Upravit média",
+  "upload_modal.hint": "Kliknutím na nebo přetáhnutím kruhu na náhledu vyberte bod soustředění, který bude vždy zobrazen na všech náhledech.",
+  "upload_modal.preview_label": "Náhled ({ratio})",
   "upload_progress.label": "Nahrávám…",
   "video.close": "Zavřít video",
   "video.exit_fullscreen": "Ukončit celou obrazovku",
diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json
index f16d7056a28bd5732c11d4784cbcd55362b45d49..aeb919f5fb8da086f24b75e7fda755e746c37f9d 100644
--- a/app/javascript/mastodon/locales/cy.json
+++ b/app/javascript/mastodon/locales/cy.json
@@ -4,6 +4,7 @@
   "account.block": "Blocio @{name}",
   "account.block_domain": "Cuddio popeth rhag {domain}",
   "account.blocked": "Blociwyd",
+  "account.cancel_follow_request": "Canslo cais dilyn",
   "account.direct": "Neges breifat @{name}",
   "account.domain_blocked": "Parth wedi ei guddio",
   "account.edit_profile": "Golygu proffil",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Nid yw'r defnyddiwr hwn yn dilyn unrhyw un eto.",
   "account.follows_you": "Yn eich dilyn chi",
   "account.hide_reblogs": "Cuddio bwstiau o @{name}",
+  "account.last_status": "Gweithredol olaf",
   "account.link_verified_on": "Gwiriwyd perchnogaeth y ddolen yma ar {date}",
   "account.locked_info": "Mae'r statws preifatrwydd cyfrif hwn wedi'i osod i gloi. Mae'r perchennog yn adolygu'r sawl sy'n gallu eu dilyn.",
   "account.media": "Cyfryngau",
@@ -23,6 +25,7 @@
   "account.mute": "Tawelu @{name}",
   "account.mute_notifications": "Cuddio hysbysiadau o @{name}",
   "account.muted": "Distewyd",
+  "account.never_active": "Byth",
   "account.posts": "Tŵtiau",
   "account.posts_with_replies": "Tŵtiau ac atebion",
   "account.report": "Adrodd @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Dad-ddilyn",
   "account.unmute": "Dad-dawelu @{name}",
   "account.unmute_notifications": "Dad-dawelu hysbysiadau o @{name}",
+  "alert.rate_limited.message": "Ceisiwch eto ar ôl {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Cyfradd gyfyngedig",
   "alert.unexpected.message": "Digwyddodd gwall annisgwyl.",
   "alert.unexpected.title": "Wps!",
+  "autosuggest_hashtag.per_week": "{count} yr wythnos",
   "boost_modal.combo": "Mae modd gwasgu {combo} er mwyn sgipio hyn tro nesa",
   "bundle_column_error.body": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
   "bundle_column_error.retry": "Ceisiwch eto",
@@ -47,6 +53,7 @@
   "column.blocks": "Defnyddwyr a flociwyd",
   "column.community": "Ffrwd lleol",
   "column.direct": "Negeseuon preifat",
+  "column.directory": "Pori proffiliau",
   "column.domain_blocks": "Parthau cuddiedig",
   "column.favourites": "Ffefrynnau",
   "column.follow_requests": "Ceisiadau dilyn",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Ydych chi'n sicr eich bod eisiau dileu y rhestr hwn am byth?",
   "confirmations.domain_block.confirm": "Cuddio parth cyfan",
   "confirmations.domain_block.message": "A ydych yn hollol, hollol sicr eich bod am flocio y {domain} cyfan? Yn y nifer helaeth o achosion mae blocio neu tawelu ambell gyfrif yn ddigonol ac yn well. Ni fyddwch yn gweld cynnwys o'r parth hwnnw mewn unrhyw ffrydiau cyhoeddus na chwaith yn eich hysbysiadau. Bydd hyn yn cael gwared o'ch dilynwyr o'r parth hwnnw.",
+  "confirmations.logout.confirm": "Allgofnodi",
+  "confirmations.logout.message": "Ydych chi'n siŵr eich bod am allgofnodi?",
   "confirmations.mute.confirm": "Tawelu",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Ydych chi'n sicr eich bod am ddistewi {name}?",
   "confirmations.redraft.confirm": "Dileu & ailddrafftio",
   "confirmations.redraft.message": "Ydych chi'n siwr eich bod eisiau dileu y tŵt hwn a'i ailddrafftio? Bydd ffefrynnau a bwstiau'n cael ei colli, a bydd ymatebion i'r tŵt gwreiddiol yn cael eu hamddifadu.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Bydd ateb nawr yn cymryd lle y neges yr ydych yn cyfansoddi ar hyn o bryd. Ydych chi'n sicr yr ydych am barhau?",
   "confirmations.unfollow.confirm": "Dad-ddilynwch",
   "confirmations.unfollow.message": "Ydych chi'n sicr eich bod am ddad-ddilyn {name}?",
+  "conversation.delete": "Dileu sgwrs",
+  "conversation.mark_as_read": "Nodi fel wedi'i ddarllen",
+  "conversation.open": "Gweld sgwrs",
+  "conversation.with": "Gyda {names}",
+  "directory.federated": "O ffedysawd hysbys",
+  "directory.local": "O {domain} yn unig",
+  "directory.new_arrivals": "Newydd-ddyfodiaid",
+  "directory.recently_active": "Yn weithredol yn ddiweddar",
   "embed.instructions": "Mewnblannwch y tŵt hwn ar eich gwefan drwy gopïo'r côd isod.",
   "embed.preview": "Dyma sut olwg fydd arno:",
   "emoji_button.activity": "Gweithgarwch",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Nid ydych wedi tawelu unrhyw ddefnyddwyr eto.",
   "empty_column.notifications": "Nid oes gennych unrhyw hysbysiadau eto. Rhyngweithiwch ac eraill i ddechrau'r sgwrs.",
   "empty_column.public": "Does dim byd yma! Ysgrifennwch rhywbeth yn gyhoeddus, neu dilynwch ddefnyddwyr o achosion eraill i'w lenwi",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Teitl rhestr newydd",
   "lists.search": "Chwilio ymysg pobl yr ydych yn ei ddilyn",
   "lists.subheading": "Eich rhestrau",
+  "load_pending": "{count, plural, one {# eitem newydd} other {# eitemau newydd}}",
   "loading_indicator.label": "Llwytho...",
   "media_gallery.toggle_visible": "Toglo gwelededd",
   "missing_indicator.label": "Heb ei ganfod",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personol",
   "navigation_bar.pins": "Tŵtiau wedi eu pinio",
   "navigation_bar.preferences": "Dewisiadau",
-  "navigation_bar.profile_directory": "Cyfeiriadur Proffil",
   "navigation_bar.public_timeline": "Ffrwd y ffederasiwn",
   "navigation_bar.security": "Diogelwch",
   "notification.favourite": "hoffodd {name} eich tŵt",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} o hysbysiadau",
   "poll.closed": "Ar gau",
   "poll.refresh": "Adnewyddu",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# bleidlais} other {# o bleidleisiau}}",
   "poll.vote": "Pleidleisio",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Ychwanegu pleidlais",
   "poll_button.remove_poll": "Tynnu pleidlais",
   "privacy.change": "Addasu preifatrwdd y tŵt",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Cyhoeddus",
   "privacy.unlisted.long": "Peidio a chyhoeddi i ffrydiau cyhoeddus",
   "privacy.unlisted.short": "Heb ei restru",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Llwytho…",
   "regeneration_indicator.sublabel": "Mae eich ffrwd cartref yn cael ei baratoi!",
   "relative_time.days": "{number}dydd",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Pobl",
   "search_results.hashtags": "Hanshnodau",
   "search_results.statuses": "Tŵtiau",
+  "search_results.statuses_fts_disabled": "Nid yw chwilio Tŵtiau yn ôl eu cynnwys wedi'i alluogi ar y gweinydd Mastodon hwn.",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "status.admin_account": "Agor rhyngwyneb goruwchwylio ar gyfer @{name}",
   "status.admin_status": "Agor y tŵt yn y rhyngwyneb goruwchwylio",
@@ -358,6 +384,7 @@
   "status.show_more": "Dangos mwy",
   "status.show_more_all": "Dangos mwy i bawb",
   "status.show_thread": "Dangos edefyn",
+  "status.uncached_media_warning": "Dim ar gael",
   "status.unmute_conversation": "Dad-dawelu sgwrs",
   "status.unpin": "Dadbinio o'r proffil",
   "suggestions.dismiss": "Diswyddo",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Munudau ar ôl",
   "time_remaining.seconds": "{number, plural, one {# eiliad} other {# o eiliadau}} ar ôl",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} yn siarad",
+  "trends.trending_now": "Yn tueddu nawr",
   "ui.beforeunload": "Mi fyddwch yn colli eich drafft os gadewch Mastodon.",
   "upload_area.title": "Llusgwch & gollwing i uwchlwytho",
   "upload_button.label": "Ychwanegwch gyfryngau (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Wedi mynd heibio'r uchafswm terfyn uwchlwytho.",
   "upload_error.poll": "Nid oes modd uwchlwytho ffeiliau â phleidleisiau.",
   "upload_form.description": "Disgrifio i'r rheini a nam ar ei golwg",
-  "upload_form.focus": "Newid rhagolwg",
+  "upload_form.edit": "Golygu",
   "upload_form.undo": "Dileu",
+  "upload_modal.analyzing_picture": "Dadansoddi llun…",
+  "upload_modal.apply": "Gweithredu",
+  "upload_modal.description_placeholder": "Mae ei phen bach llawn jocs, 'run peth a fy nghot golff, rhai dyddiau",
+  "upload_modal.detect_text": "Canfod testun o'r llun",
+  "upload_modal.edit_media": "Golygu cyfryngau",
+  "upload_modal.hint": "Cliciwch neu llusgwch y cylch ar y rhagolwg i ddewis y canolbwynt a fydd bob amser i'w weld ar bob mân-lunau.",
+  "upload_modal.preview_label": "Rhagolwg ({ratio})",
   "upload_progress.label": "Uwchlwytho...",
   "video.close": "Cau fideo",
   "video.exit_fullscreen": "Gadael sgrîn llawn",
diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json
index c478f295338b376053e39ce4b2e9f931e1fd29e2..64088fd6e8e01df82f864774a7dab7d17ec439fd 100644
--- a/app/javascript/mastodon/locales/da.json
+++ b/app/javascript/mastodon/locales/da.json
@@ -4,6 +4,7 @@
   "account.block": "Bloker @{name}",
   "account.block_domain": "Skjul alt fra {domain}",
   "account.blocked": "Blokeret",
+  "account.cancel_follow_request": "Annullér følgeranmodning",
   "account.direct": "Send en direkte besked til @{name}",
   "account.domain_blocked": "Domænet er blevet skjult",
   "account.edit_profile": "Rediger profil",
@@ -15,14 +16,16 @@
   "account.follows.empty": "Denne bruger følger endnu ikke nogen.",
   "account.follows_you": "Følger dig",
   "account.hide_reblogs": "Skjul fremhævelserne fra @{name}",
+  "account.last_status": "Sidst aktiv",
   "account.link_verified_on": "Ejerskabet af dette link blev tjekket den %{date}",
-  "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
+  "account.locked_info": "Denne kontos privatlivsstatus er sat til låst. Ejeren bedømmer manuelt, hvem der kan følge dem.",
   "account.media": "Medie",
   "account.mention": "Nævn @{name}",
   "account.moved_to": "{name} er flyttet til:",
   "account.mute": "Dæmp @{name}",
   "account.mute_notifications": "Dæmp notifikationer fra @{name}",
   "account.muted": "Dæmpet",
+  "account.never_active": "Aldrig",
   "account.posts": "Trut",
   "account.posts_with_replies": "Trut og svar",
   "account.report": "Rapporter @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Følg ikke længere",
   "account.unmute": "Fjern dæmpningen af @{name}",
   "account.unmute_notifications": "Fjern dæmpningen af notifikationer fra @{name}",
+  "alert.rate_limited.message": "Prøv venligst igen efter {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Der opstod en uventet fejl.",
   "alert.unexpected.title": "Ups!",
+  "autosuggest_hashtag.per_week": "{count} per uge",
   "boost_modal.combo": "Du kan trykke {combo} for at springe dette over næste gang",
   "bundle_column_error.body": "Noget gik galt under indlæsningen af dette komponent.",
   "bundle_column_error.retry": "Prøv igen",
@@ -47,6 +53,7 @@
   "column.blocks": "Blokerede brugere",
   "column.community": "Lokal tidslinje",
   "column.direct": "Direkte beskeder",
+  "column.directory": "Gennemse profiler",
   "column.domain_blocks": "Skjulte domæner",
   "column.favourites": "Favoritter",
   "column.follow_requests": "Anmodning om at følge",
@@ -71,20 +78,20 @@
   "compose_form.lock_disclaimer": "Din konto er ikke {locked}. Alle kan følge dig for at se dine følger-kun indlæg.",
   "compose_form.lock_disclaimer.lock": "låst",
   "compose_form.placeholder": "Hvad har du på hjertet?",
-  "compose_form.poll.add_option": "Add a choice",
-  "compose_form.poll.duration": "Poll duration",
-  "compose_form.poll.option_placeholder": "Choice {number}",
-  "compose_form.poll.remove_option": "Remove this choice",
+  "compose_form.poll.add_option": "Tilføj valgmulighed",
+  "compose_form.poll.duration": "Afstemningens varighed",
+  "compose_form.poll.option_placeholder": "Valgmulighed {number}",
+  "compose_form.poll.remove_option": "Fjern denne valgmulighed",
   "compose_form.publish": "Trut",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.hide": "Mark media as sensitive",
+  "compose_form.sensitive.hide": "Markér medie som følsomt",
   "compose_form.sensitive.marked": "Medie er markeret som værende følsomt",
   "compose_form.sensitive.unmarked": "Mediet er ikke markeret som værende følsomt",
   "compose_form.spoiler.marked": "Teksten er skjult bag en advarsel",
   "compose_form.spoiler.unmarked": "Teksten er ikke skjult",
   "compose_form.spoiler_placeholder": "Skriv din advarsel her",
   "confirmation_modal.cancel": "Annuller",
-  "confirmations.block.block_and_report": "Block & Report",
+  "confirmations.block.block_and_report": "Blokér og anmeld",
   "confirmations.block.confirm": "Bloker",
   "confirmations.block.message": "Er du sikker på, du vil blokere {name}?",
   "confirmations.delete.confirm": "Slet",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Er du sikker på, du vil slette denne liste?",
   "confirmations.domain_block.confirm": "Skjul helt domæne",
   "confirmations.domain_block.message": "Er du helt sikker på du vil blokere hele {domain} domænet? I de fleste tilfælde vil få specifikke blokeringer eller dæmpninger være nok og at fortrække. Du vil ikke se indhold fra det domæne hverken på offentlige tidslinjer eller i dine notifikationer. Dine følgere fra det domæne vil blive fjernet.",
+  "confirmations.logout.confirm": "Log ud",
+  "confirmations.logout.message": "Er du sikker på du vil logge ud?",
   "confirmations.mute.confirm": "Dæmp",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Er du sikker på, du vil dæmpe {name}?",
   "confirmations.redraft.confirm": "Slet & omskriv",
   "confirmations.redraft.message": "Er du sikker på, du vil slette denne status og omskrive den? Favoritter og fremhævelser vil gå tabt og svar til det oprindelige opslag vil blive forældreløse.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Hvis du svarer nu vil du overskrive den besked du er ved at skrive. Er du sikker på, du vil fortsætte?",
   "confirmations.unfollow.confirm": "Følg ikke længere",
   "confirmations.unfollow.message": "Er du sikker på, du ikke længere vil følge {name}?",
+  "conversation.delete": "Slet samtale",
+  "conversation.mark_as_read": "Marker som læst",
+  "conversation.open": "Vis samtale",
+  "conversation.with": "Med {names}",
+  "directory.federated": "Fra kendt fedivers",
+  "directory.local": "Kun fra {domain}",
+  "directory.new_arrivals": "Nye ankomster",
+  "directory.recently_active": "Senest aktiv",
   "embed.instructions": "Indlejre denne status på din side ved at kopiere nedenstående kode.",
   "embed.preview": "Det kommer til at se således ud:",
   "emoji_button.activity": "Aktivitet",
@@ -111,14 +129,14 @@
   "emoji_button.nature": "Natur",
   "emoji_button.not_found": "Ingen emojos!! (╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "Objekter",
-  "emoji_button.people": "Mennesker",
+  "emoji_button.people": "Personer",
   "emoji_button.recent": "Oftest brugt",
   "emoji_button.search": "Søg...",
   "emoji_button.search_results": "Søgeresultater",
   "emoji_button.symbols": "Symboler",
   "emoji_button.travel": "Rejser & steder",
   "empty_column.account_timeline": "Ingen bidrag her!",
-  "empty_column.account_unavailable": "Profile unavailable",
+  "empty_column.account_unavailable": "Profil utilgængelig",
   "empty_column.blocks": "Du har ikke blokeret nogen endnu.",
   "empty_column.community": "Den lokale tidslinje er tom. Skriv noget offentligt for at starte lavinen!",
   "empty_column.direct": "Du har endnu ingen direkte beskeder. NÃ¥r du sender eller modtager en, vil den vises her.",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Du har endnu ikke dæmpet nogen som helst bruger.",
   "empty_column.notifications": "Du har endnu ingen notifikationer. Tag ud og bland dig med folkemængden for at starte samtalen.",
   "empty_column.public": "Der er ikke noget at se her! Skriv noget offentligt eller start ud med manuelt at følge brugere fra andre server for at udfylde tomrummet",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -142,7 +164,7 @@
   "follow_request.authorize": "Godkend",
   "follow_request.reject": "Afvis",
   "getting_started.developers": "Udviklere",
-  "getting_started.directory": "Profile directory",
+  "getting_started.directory": "Profilliste",
   "getting_started.documentation": "Dokumentation",
   "getting_started.heading": "Kom igang",
   "getting_started.invite": "Inviter folk",
@@ -152,35 +174,35 @@
   "hashtag.column_header.tag_mode.all": "og {additional}",
   "hashtag.column_header.tag_mode.any": "eller {additional}",
   "hashtag.column_header.tag_mode.none": "uden {additional}",
-  "hashtag.column_settings.select.no_options_message": "No suggestions found",
-  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
-  "hashtag.column_settings.tag_mode.all": "All of these",
-  "hashtag.column_settings.tag_mode.any": "Any of these",
-  "hashtag.column_settings.tag_mode.none": "None of these",
+  "hashtag.column_settings.select.no_options_message": "Ingen forslag fundet",
+  "hashtag.column_settings.select.placeholder": "Indtast hashtags…",
+  "hashtag.column_settings.tag_mode.all": "Alle disse",
+  "hashtag.column_settings.tag_mode.any": "Nogle af disse",
+  "hashtag.column_settings.tag_mode.none": "Ingen af disse",
   "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
   "home.column_settings.basic": "Grundlæggende",
   "home.column_settings.show_reblogs": "Vis fremhævelser",
   "home.column_settings.show_replies": "Vis svar",
-  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
-  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
-  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "intervals.full.days": "{number, plural, one {# dag} other {# dage}}",
+  "intervals.full.hours": "{number, plural, one {# time} other {# timer}}",
+  "intervals.full.minutes": "{number, plural, one {# minut} other {# minutter}}",
   "introduction.federation.action": "Næste",
-  "introduction.federation.federated.headline": "Federated",
+  "introduction.federation.federated.headline": "Fælles",
   "introduction.federation.federated.text": "Offentlige bidrag fra andre servere af fediversen vil komme til syne i den federated timeline.",
-  "introduction.federation.home.headline": "Home",
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
-  "introduction.federation.local.headline": "Local",
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
+  "introduction.federation.home.headline": "Hjem",
+  "introduction.federation.home.text": "Statusser fra personer du følger vil blive vist i dit hjemmefeed. Du kan følge alle på enhver server!",
+  "introduction.federation.local.headline": "Lokal",
+  "introduction.federation.local.text": "Offentlige statusser fra personer på samme server som dig vil blive vist i det lokale feed.",
   "introduction.interactions.action": "Slut tutorial!",
   "introduction.interactions.favourite.headline": "Favorisere",
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
+  "introduction.interactions.favourite.text": "Du kan gemme en status til senere (og vise forfatteren at du kunne lide den) ved at favorisere den.",
   "introduction.interactions.reblog.headline": "Boost",
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
+  "introduction.interactions.reblog.text": "Du kan delete andres statusser med dine følgere ved at booste dem.",
   "introduction.interactions.reply.headline": "Svar",
   "introduction.interactions.reply.text": "Du kan svare andres og din egen bidrag, hvilke vil kæde dem sammen i en konversation.",
   "introduction.welcome.action": "Læd os gå!",
   "introduction.welcome.headline": "Første skridt",
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
+  "introduction.welcome.text": "Velkommen til fediverset! Om få øjeblikke vil du kunne dele statusser og tale med dine venner på en bred vifte af servere. Men denne server, {domain}, er speciel. Det er på denne server at din profil har hjemme så husk dens navn.",
   "keyboard_shortcuts.back": "for at navigere dig tilbage",
   "keyboard_shortcuts.blocked": "for at åbne listen over blokerede brugere",
   "keyboard_shortcuts.boost": "for at fremhæve",
@@ -209,23 +231,24 @@
   "keyboard_shortcuts.search": "for at fokusere søgningen",
   "keyboard_shortcuts.start": "for at åbne \"kom igen\" kolonnen",
   "keyboard_shortcuts.toggle_hidden": "for at vise/skjule tekst bag CW",
-  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.toggle_sensitivity": "for at vise/skjule medier",
   "keyboard_shortcuts.toot": "for at påbegynde et helt nyt trut",
   "keyboard_shortcuts.unfocus": "for at fjerne fokus fra skriveområde/søgning",
   "keyboard_shortcuts.up": "for at bevæge dig op ad listen",
   "lightbox.close": "Luk",
   "lightbox.next": "Næste",
   "lightbox.previous": "Forrige",
-  "lightbox.view_context": "View context",
+  "lightbox.view_context": "Vis kontekst",
   "lists.account.add": "Tilføj til liste",
   "lists.account.remove": "Fjern fra liste",
   "lists.delete": "Slet liste",
   "lists.edit": "Rediger liste",
-  "lists.edit.submit": "Change title",
+  "lists.edit.submit": "Skift titel",
   "lists.new.create": "Tilføj liste",
   "lists.new.title_placeholder": "Ny liste titel",
   "lists.search": "Søg iblandt folk du følger",
   "lists.subheading": "Dine lister",
+  "load_pending": "{count, plural, one {# nyt punkt} other {# nye punkter}}",
   "loading_indicator.label": "Indlæser...",
   "media_gallery.toggle_visible": "Ændre synlighed",
   "missing_indicator.label": "Ikke fundet",
@@ -242,7 +265,7 @@
   "navigation_bar.favourites": "Favoritter",
   "navigation_bar.filters": "Dæmpede ord",
   "navigation_bar.follow_requests": "Følgeanmodninger",
-  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.follows_and_followers": "Følger og følgere",
   "navigation_bar.info": "Om denne instans",
   "navigation_bar.keyboard_shortcuts": "Hurtigtast",
   "navigation_bar.lists": "Lister",
@@ -251,50 +274,52 @@
   "navigation_bar.personal": "Personligt",
   "navigation_bar.pins": "Fastgjorte trut",
   "navigation_bar.preferences": "Præferencer",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Fælles tidslinje",
   "navigation_bar.security": "Sikkerhed",
   "notification.favourite": "{name} favoriserede din status",
   "notification.follow": "{name} fulgte dig",
   "notification.mention": "{name} nævnte dig",
-  "notification.poll": "A poll you have voted in has ended",
-  "notification.reblog": "{name} fremhævede din status",
+  "notification.poll": "En afstemning, du stemte i, er slut",
+  "notification.reblog": "{name} boostede din status",
   "notifications.clear": "Ryd notifikationer",
   "notifications.clear_confirmation": "Er du sikker på, du vil rydde alle dine notifikationer permanent?",
-  "notifications.column_settings.alert": "Skrivebords notifikationer",
+  "notifications.column_settings.alert": "Skrivebordsnotifikationer",
   "notifications.column_settings.favourite": "Favoritter:",
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
-  "notifications.column_settings.filter_bar.show": "Show",
+  "notifications.column_settings.filter_bar.advanced": "Vis alle kategorier",
+  "notifications.column_settings.filter_bar.category": "Hurtigfilter",
+  "notifications.column_settings.filter_bar.show": "Vis",
   "notifications.column_settings.follow": "Nye følgere:",
-  "notifications.column_settings.mention": "Omtale:",
-  "notifications.column_settings.poll": "Poll results:",
-  "notifications.column_settings.push": "Push notifikationer",
-  "notifications.column_settings.reblog": "Fremhævelser:",
+  "notifications.column_settings.mention": "Statusser der nævner dig:",
+  "notifications.column_settings.poll": "Afstemningsresultat:",
+  "notifications.column_settings.push": "Pushnotifikationer",
+  "notifications.column_settings.reblog": "Boosts:",
   "notifications.column_settings.show": "Vis i kolonne",
   "notifications.column_settings.sound": "Afspil lyd",
   "notifications.filter.all": "Alle",
   "notifications.filter.boosts": "Boosts",
   "notifications.filter.favourites": "Favoritter",
   "notifications.filter.follows": "Følger",
-  "notifications.filter.mentions": "Mentions",
-  "notifications.filter.polls": "Poll results",
+  "notifications.filter.mentions": "Statusser der nævner dig",
+  "notifications.filter.polls": "Afstemningsresultat",
   "notifications.group": "{count} notifikationer",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
-  "poll_button.add_poll": "Add a poll",
-  "poll_button.remove_poll": "Remove poll",
-  "privacy.change": "Ændre status privatliv",
-  "privacy.direct.long": "Post til kun de nævnte brugere",
+  "poll.closed": "Lukket",
+  "poll.refresh": "Opdatér",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
+  "poll.total_votes": "{count, plural, one {# stemme} other {# stemmer}}",
+  "poll.vote": "Stem",
+  "poll.voted": "You voted for this answer",
+  "poll_button.add_poll": "Tilføj en afstemning",
+  "poll_button.remove_poll": "Fjern afstemning",
+  "privacy.change": "Skift status visningsindstillinger",
+  "privacy.direct.long": "Udgiv kun til nævnte brugere",
   "privacy.direct.short": "Direkte",
-  "privacy.private.long": "Post kun til følgere",
+  "privacy.private.long": "Udgiv kun til følgere",
   "privacy.private.short": "Kun for følgere",
-  "privacy.public.long": "Post til offentlige tidslinjer",
+  "privacy.public.long": "Udgiv på offentlige tidslinjer",
   "privacy.public.short": "Offentligt",
-  "privacy.unlisted.long": "Post ikke til offentlige tidslinjer",
+  "privacy.unlisted.long": "Udgiv ikke på offentlige tidslinjer",
   "privacy.unlisted.short": "Ikke listet",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Indlæser…",
   "regeneration_indicator.sublabel": "Din startside er ved at blive forberedt!",
   "relative_time.days": "{number}d",
@@ -305,31 +330,32 @@
   "reply_indicator.cancel": "Annuller",
   "report.forward": "Videresend til {target}",
   "report.forward_hint": "Kontoen er fra en anden server. Vil du også sende en anonym kopi af anmeldelsen dertil?",
-  "report.hint": "Anmeldelsen vil blive sendt til moderatorene af din instans. Du kan give en forklaring for hvorfor du anmelder denne konto nedenfor:",
+  "report.hint": "Anmeldelsen vil blive sendt til moderatorene af din instans. Du kan give en forklaring på hvorfor du anmelder denne konto nedenfor:",
   "report.placeholder": "Yderligere kommentarer",
   "report.submit": "Indsend",
   "report.target": "Anmelder {target}",
   "search.placeholder": "Søg",
   "search_popout.search_format": "Avanceret søgeformat",
-  "search_popout.tips.full_text": "Simpel tekst returnerer statusser du har skrevet, favoriseret, fremhævet, eller er blevet nævnt i, lige så vel som matchende brugernavne, visningsnavne, og hashtags.",
-  "search_popout.tips.hashtag": "emnetag",
+  "search_popout.tips.full_text": "Simpel tekst returnerer statusser du har skrevet, favoriseret, boostet, eller er blevet nævnt i såvel som matchende brugernavne, profilnavne, og hashtags.",
+  "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
-  "search_popout.tips.text": "Simpelt tekst returnerer passende visningsnavne, brugernavne og hashtags",
+  "search_popout.tips.text": "Simpel tekst returnerer matchende profilnavne, brugernavne og hashtags",
   "search_popout.tips.user": "bruger",
-  "search_results.accounts": "Folk",
-  "search_results.hashtags": "Emnetags",
+  "search_results.accounts": "Personer",
+  "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Trut",
-  "search_results.total": "{count, number} {count, plural, et {result} andre {results}}",
-  "status.admin_account": "Open moderation interface for @{name}",
-  "status.admin_status": "Open this status in the moderation interface",
+  "search_results.statuses_fts_disabled": "Denne Mastodonserver har ikke aktiveret for søgning af statusser via deres indhold.",
+  "search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}",
+  "status.admin_account": "Ã…ben modereringsvisning for @{name}",
+  "status.admin_status": "Ã…ben denne status i modereringsvisningen",
   "status.block": "Bloker @{name}",
-  "status.cancel_reblog_private": "Fremhæv ikke længere",
-  "status.cannot_reblog": "Denne post kan ikke fremhæves",
-  "status.copy": "Copy link to status",
+  "status.cancel_reblog_private": "Fjern boost",
+  "status.cannot_reblog": "Denne post kan ikke boostes",
+  "status.copy": "Kopiér link til status",
   "status.delete": "Slet",
   "status.detailed_status": "Detaljeret visning af samtale",
   "status.direct": "Send direkte besked til @{name}",
-  "status.embed": "Indlejre",
+  "status.embed": "Integrér",
   "status.favourite": "Favorit",
   "status.filtered": "Filtreret",
   "status.load_more": "Indlæs mere",
@@ -343,13 +369,13 @@
   "status.pin": "Fastgør til profil",
   "status.pinned": "Fastgjort trut",
   "status.read_more": "Læs mere",
-  "status.reblog": "Fremhæv",
-  "status.reblog_private": "Fremhæv til oprindeligt publikum",
-  "status.reblogged_by": "{name} fremhævede",
-  "status.reblogs.empty": "Der er endnu ingen der har fremhævet dette trut. Når der er nogen der gør, vil det blive vist her.",
+  "status.reblog": "Boost",
+  "status.reblog_private": "Boost til det oprindelige publikum",
+  "status.reblogged_by": "{name} boostede",
+  "status.reblogs.empty": "Der er endnu ingen der har boostet dette trut. Når der er nogen der gør, vil det blive vist her.",
   "status.redraft": "Slet og omskriv",
-  "status.reply": "Svar",
-  "status.replyAll": "Svar samtale",
+  "status.reply": "Besvar",
+  "status.replyAll": "Besvar samtale",
   "status.report": "Anmeld @{name}",
   "status.sensitive_warning": "Følsomt indhold",
   "status.share": "Del",
@@ -357,33 +383,42 @@
   "status.show_less_all": "Vis mindre for alle",
   "status.show_more": "Vis mere",
   "status.show_more_all": "Vis mere for alle",
-  "status.show_thread": "Show thread",
-  "status.unmute_conversation": "Fjern dæmpningen fra samtale",
-  "status.unpin": "Fjern som fastgjort fra profil",
-  "suggestions.dismiss": "Dismiss suggestion",
-  "suggestions.header": "You might be interested in…",
+  "status.show_thread": "Vis tråd",
+  "status.uncached_media_warning": "Ikke tilgængelig",
+  "status.unmute_conversation": "Genaktivér samtale",
+  "status.unpin": "Frigør fra profil",
+  "suggestions.dismiss": "Afvis foreslag",
+  "suggestions.header": "Du er måske interesseret i…",
   "tabs_bar.federated_timeline": "Fælles",
   "tabs_bar.home": "Hjem",
   "tabs_bar.local_timeline": "Lokal",
   "tabs_bar.notifications": "Notifikationer",
   "tabs_bar.search": "Søg",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} snakker",
+  "time_remaining.days": "{number, plural, one {# dag} other {# dage}} tilbage",
+  "time_remaining.hours": "{number, plural, one {# time} other {# timer}} tilbage",
+  "time_remaining.minutes": "{number, plural, one {# minut} other {# minutter}} tilbage",
+  "time_remaining.moments": "Få øjeblikke tilbage",
+  "time_remaining.seconds": "{number, plural, one {# sekund} other {# sekunder}} tilbage",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {personer}} snakker",
+  "trends.trending_now": "Hot lige nu",
   "ui.beforeunload": "Din kladde vil gå tabt hvis du forlader Mastodon.",
   "upload_area.title": "Træk og slip for at uploade",
   "upload_button.label": "Tilføj medie (JPEG, PNG, GIF, WebM, MP4, MOV)",
-  "upload_error.limit": "File upload limit exceeded.",
-  "upload_error.poll": "File upload not allowed with polls.",
-  "upload_form.description": "Beskriv for de svagtseende",
-  "upload_form.focus": "Beskær",
+  "upload_error.limit": "Uploadgrænse overskredet.",
+  "upload_error.poll": "Filupload ikke tilladt sammen med afstemninger.",
+  "upload_form.description": "Beskriv for svagtseende",
+  "upload_form.edit": "Redigér",
   "upload_form.undo": "Slet",
+  "upload_modal.analyzing_picture": "Analyserer billede…",
+  "upload_modal.apply": "Anvend",
+  "upload_modal.description_placeholder": "En hurtig brun ræv hopper over den dovne hund",
+  "upload_modal.detect_text": "Find tekst i billede på automatisk vis",
+  "upload_modal.edit_media": "Redigér medie",
+  "upload_modal.hint": "Klik eller træk cirklen på billedet for at vælge et fokuspunkt.",
+  "upload_modal.preview_label": "Forhåndsvisning ({ratio})",
   "upload_progress.label": "Uploader...",
   "video.close": "Luk video",
-  "video.exit_fullscreen": "Gå ud af fuldskærm",
+  "video.exit_fullscreen": "Forlad fuldskærm",
   "video.expand": "Udvid video",
   "video.fullscreen": "Fuldskærm",
   "video.hide": "Skjul video",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index f84b420fe3a4c2f9867a132cfca71b4d7c1a3ce2..077cb729a99f1fbed5b388fbc8d5fd03917b345d 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -4,17 +4,19 @@
   "account.block": "@{name} blockieren",
   "account.block_domain": "Alles von {domain} verstecken",
   "account.blocked": "Blockiert",
+  "account.cancel_follow_request": "Folgeanfrage abbrechen",
   "account.direct": "Direktnachricht an @{name}",
   "account.domain_blocked": "Domain versteckt",
   "account.edit_profile": "Profil bearbeiten",
   "account.endorse": "Auf Profil hervorheben",
   "account.follow": "Folgen",
-  "account.followers": "Folger_innen",
+  "account.followers": "Folgende",
   "account.followers.empty": "Diesem Profil folgt noch niemand.",
   "account.follows": "Folgt",
   "account.follows.empty": "Dieses Profil folgt noch niemandem.",
   "account.follows_you": "Folgt dir",
   "account.hide_reblogs": "Geteilte Beiträge von @{name} verbergen",
+  "account.last_status": "Zuletzt aktiv",
   "account.link_verified_on": "Besitz dieses Links wurde geprüft am {date}",
   "account.locked_info": "Der Privatsphärenstatus dieses Accounts wurde auf gesperrt gesetzt. Die Person bestimmt manuell wer ihm/ihr folgen darf.",
   "account.media": "Medien",
@@ -23,6 +25,7 @@
   "account.mute": "@{name} stummschalten",
   "account.mute_notifications": "Benachrichtigungen von @{name} verbergen",
   "account.muted": "Stummgeschaltet",
+  "account.never_active": "Nie",
   "account.posts": "Beiträge",
   "account.posts_with_replies": "Beiträge und Antworten",
   "account.report": "@{name} melden",
@@ -35,8 +38,11 @@
   "account.unfollow": "Entfolgen",
   "account.unmute": "@{name} nicht mehr stummschalten",
   "account.unmute_notifications": "Benachrichtigungen von @{name} einschalten",
+  "alert.rate_limited.message": "Bitte versuche es nach {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Anfragelimit überschritten",
   "alert.unexpected.message": "Ein unerwarteter Fehler ist aufgetreten.",
   "alert.unexpected.title": "Hoppla!",
+  "autosuggest_hashtag.per_week": "{count} pro Woche",
   "boost_modal.combo": "Drücke {combo}, um dieses Fenster zu überspringen",
   "bundle_column_error.body": "Etwas ist beim Laden schiefgelaufen.",
   "bundle_column_error.retry": "Erneut versuchen",
@@ -47,6 +53,7 @@
   "column.blocks": "Blockierte Profile",
   "column.community": "Lokale Zeitleiste",
   "column.direct": "Direktnachrichten",
+  "column.directory": "Profile durchsuchen",
   "column.domain_blocks": "Versteckte Domains",
   "column.favourites": "Favoriten",
   "column.follow_requests": "Folgeanfragen",
@@ -92,8 +99,11 @@
   "confirmations.delete_list.confirm": "Löschen",
   "confirmations.delete_list.message": "Bist du dir sicher, dass du diese Liste permanent löschen möchtest?",
   "confirmations.domain_block.confirm": "Die ganze Domain verbergen",
-  "confirmations.domain_block.message": "Bist du dir wirklich sicher, dass du die ganze Domain {domain} blockieren willst? In den meisten Fällen reichen ein paar gezielte Blockierungen oder Stummschaltungen aus. Nach der Blockierung wirst du nichts mehr von dieser Domain in öffentlichen Zeitleisten oder Benachrichtigungen sehen. Deine Folger_innen von dieser Domain werden auch entfernt.",
+  "confirmations.domain_block.message": "Bist du dir wirklich sicher, dass du die ganze Domain {domain} blockieren willst? In den meisten Fällen reichen ein paar gezielte Blockierungen oder Stummschaltungen aus. Du wirst den Inhalt von dieser Domain nicht in irgendwelchen öffentlichen Timelines oder den Benachrichtigungen finden. Deine Folgenden von dieser Domain werden entfernt.",
+  "confirmations.logout.confirm": "Abmelden",
+  "confirmations.logout.message": "Bist du sicher, dass du dich abmelden möchtest?",
   "confirmations.mute.confirm": "Stummschalten",
+  "confirmations.mute.explanation": "Dies wird Beiträge von dieser Person und Beiträge, die diese Person erwähnen, ausblenden, aber es wird der Person trotzdem erlauben, deine Beiträge zu sehen und dir zu folgen.",
   "confirmations.mute.message": "Bist du dir sicher, dass du {name} stummschalten möchtest?",
   "confirmations.redraft.confirm": "Löschen und neu erstellen",
   "confirmations.redraft.message": "Bist du dir sicher, dass du diesen Beitrag löschen und neu erstellen möchtest? Favorisierungen, geteilte Beiträge und Antworten werden verloren gehen.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Wenn du jetzt antwortest wird es die gesamte Nachricht verwerfen, die du gerade schreibst. Möchtest du wirklich fortfahren?",
   "confirmations.unfollow.confirm": "Entfolgen",
   "confirmations.unfollow.message": "Bist du dir sicher, dass du {name} entfolgen möchtest?",
+  "conversation.delete": "Unterhaltung löschen",
+  "conversation.mark_as_read": "Als gelesen markieren",
+  "conversation.open": "Unterhaltung anzeigen",
+  "conversation.with": "Mit {names}",
+  "directory.federated": "Aus dem Fediverse",
+  "directory.local": "Nur von {domain}",
+  "directory.new_arrivals": "Neue Benutzer",
+  "directory.recently_active": "Kürzlich aktiv",
   "embed.instructions": "Du kannst diesen Beitrag auf deiner Webseite einbetten, indem du den folgenden Code einfügst.",
   "embed.preview": "So wird es aussehen:",
   "emoji_button.activity": "Aktivitäten",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Du hast keine Profile stummgeschaltet.",
   "empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um ins Gespräch zu kommen.",
   "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Profilen von anderen Servern, um die Zeitleiste aufzufüllen",
+  "error.unexpected_crash.explanation": "Aufgrund eines Fehlers in unserem Code oder einer Browsereinkompatibilität konnte diese Seite nicht korrekt angezeigt werden.",
+  "error.unexpected_crash.next_steps": "Versuche die Seite zu aktualisieren. Wenn das nicht hilft, kannst du Mastodon über einen anderen Browser oder eine native App verwenden.",
+  "errors.unexpected_crash.copy_stacktrace": "Fehlerlog in die Zwischenablage kopieren",
+  "errors.unexpected_crash.report_issue": "Problem melden",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -173,7 +195,7 @@
   "introduction.federation.local.text": "Öffentliche Beiträge von Leuten auf demselben Server wie du erscheinen in der lokalen Zeitleiste.",
   "introduction.interactions.action": "Tutorial beenden!",
   "introduction.interactions.favourite.headline": "Favorisieren",
-  "introduction.interactions.favourite.text": "Du kannst Beitrage für später speichern und ihre Autor_innen wissen lassen, dass sie dir gefallen haben, indem du sie favorisierst.",
+  "introduction.interactions.favourite.text": "Du kannst Beitrage für später speichern und ihre Autoren wissen lassen, dass sie dir gefallen haben, indem du sie favorisierst.",
   "introduction.interactions.reblog.headline": "Teilen",
   "introduction.interactions.reblog.text": "Du kannst Beiträge anderer mit deinen Followern teilen, indem du sie teilst.",
   "introduction.interactions.reply.headline": "Antworten",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Neuer Titel der Liste",
   "lists.search": "Suche nach Leuten denen du folgst",
   "lists.subheading": "Deine Listen",
+  "load_pending": "{count, plural, one {# neuer Beitrag} other {# neue Beiträge}}",
   "loading_indicator.label": "Wird geladen …",
   "media_gallery.toggle_visible": "Sichtbarkeit umschalten",
   "missing_indicator.label": "Nicht gefunden",
@@ -242,7 +265,7 @@
   "navigation_bar.favourites": "Favoriten",
   "navigation_bar.filters": "Stummgeschaltene Wörter",
   "navigation_bar.follow_requests": "Folgeanfragen",
-  "navigation_bar.follows_and_followers": "Folger_innen und Gefolgte",
+  "navigation_bar.follows_and_followers": "Folgende und Gefolgte",
   "navigation_bar.info": "Über diesen Server",
   "navigation_bar.keyboard_shortcuts": "Tastenkombinationen",
   "navigation_bar.lists": "Listen",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Persönlich",
   "navigation_bar.pins": "Angeheftete Beiträge",
   "navigation_bar.preferences": "Einstellungen",
-  "navigation_bar.profile_directory": "Profilverzeichnis",
   "navigation_bar.public_timeline": "Föderierte Zeitleiste",
   "navigation_bar.security": "Sicherheit",
   "notification.favourite": "{name} hat deinen Beitrag favorisiert",
@@ -266,7 +288,7 @@
   "notifications.column_settings.filter_bar.advanced": "Zeige alle Kategorien an",
   "notifications.column_settings.filter_bar.category": "Schnellfilterleiste",
   "notifications.column_settings.filter_bar.show": "Anzeigen",
-  "notifications.column_settings.follow": "Neue Folger_innen:",
+  "notifications.column_settings.follow": "Neue Folgende:",
   "notifications.column_settings.mention": "Erwähnungen:",
   "notifications.column_settings.poll": "Ergebnisse von Umfragen:",
   "notifications.column_settings.push": "Push-Benachrichtigungen",
@@ -276,25 +298,28 @@
   "notifications.filter.all": "Alle",
   "notifications.filter.boosts": "Geteilte Beiträge",
   "notifications.filter.favourites": "Favorisierungen",
-  "notifications.filter.follows": "Folger_innen",
+  "notifications.filter.follows": "Folgt",
   "notifications.filter.mentions": "Erwähnungen",
   "notifications.filter.polls": "Ergebnisse der Umfrage",
   "notifications.group": "{count} Benachrichtigungen",
   "poll.closed": "Geschlossen",
   "poll.refresh": "Aktualisieren",
+  "poll.total_people": "{count, plural, one {# Person} other {# Personen}}",
   "poll.total_votes": "{count, plural, one {# Stimme} other {# Stimmen}}",
   "poll.vote": "Abstimmen",
+  "poll.voted": "Du hast dafür gestimmt",
   "poll_button.add_poll": "Eine Umfrage erstellen",
   "poll_button.remove_poll": "Umfrage entfernen",
   "privacy.change": "Sichtbarkeit des Beitrags anpassen",
   "privacy.direct.long": "Wird an erwähnte Profile gesendet",
   "privacy.direct.short": "Direktnachricht",
-  "privacy.private.long": "Wird nur für deine Folger_innen sichtbar sein",
-  "privacy.private.short": "Nur für Folger_innen",
+  "privacy.private.long": "Wird nur für deine Folgende sichtbar sein",
+  "privacy.private.short": "Nur für Folgende",
   "privacy.public.long": "Wird in öffentlichen Zeitleisten erscheinen",
   "privacy.public.short": "Öffentlich",
   "privacy.unlisted.long": "Wird in öffentlichen Zeitleisten nicht gezeigt",
   "privacy.unlisted.short": "Nicht gelistet",
+  "refresh": "Aktualisieren",
   "regeneration_indicator.label": "Laden…",
   "regeneration_indicator.sublabel": "Deine Startseite wird gerade vorbereitet!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Personen",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Beiträge",
+  "search_results.statuses_fts_disabled": "Die Suche für Beiträge nach ihrem Inhalt ist auf diesem Mastodon-Server deaktiviert.",
   "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}",
   "status.admin_account": "Öffne Moderationsoberfläche für @{name}",
   "status.admin_status": "Öffne Beitrag in der Moderationsoberfläche",
@@ -358,6 +384,7 @@
   "status.show_more": "Mehr anzeigen",
   "status.show_more_all": "Alle Inhaltswarnungen aufklappen",
   "status.show_thread": "Zeige Konversation",
+  "status.uncached_media_warning": "Nicht verfügbar",
   "status.unmute_conversation": "Stummschaltung von Konversation aufheben",
   "status.unpin": "Vom Profil lösen",
   "suggestions.dismiss": "Empfehlung ausblenden",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Schließt in Kürze",
   "time_remaining.seconds": "{number, plural, one {# Sekunde} other {# Sekunden}} verbleibend",
   "trends.count_by_accounts": "{count} {rawCount, plural, eine {Person} other {Personen}} reden darüber",
+  "trends.trending_now": "In den Trends",
   "ui.beforeunload": "Dein Entwurf geht verloren, wenn du Mastodon verlässt.",
   "upload_area.title": "Zum Hochladen hereinziehen",
   "upload_button.label": "Mediendatei hinzufügen ({formats})",
   "upload_error.limit": "Dateiupload-Limit erreicht.",
   "upload_error.poll": "Dateiuploads sind in Kombination mit Umfragen nicht erlaubt.",
   "upload_form.description": "Für Menschen mit Sehbehinderung beschreiben",
-  "upload_form.focus": "Vorschaubild bearbeiten",
+  "upload_form.edit": "Bearbeiten",
   "upload_form.undo": "Löschen",
+  "upload_modal.analyzing_picture": "Analysiere Bild…",
+  "upload_modal.apply": "Übernehmen",
+  "upload_modal.description_placeholder": "Franz jagt im komplett verwahrlosten Taxi quer durch Bayern",
+  "upload_modal.detect_text": "Text aus Bild erkennen",
+  "upload_modal.edit_media": "Medien bearbeiten",
+  "upload_modal.hint": "Klicke oder ziehe den Kreis auf die Vorschau, um den Brennpunkt auszuwählen, der immer auf allen Vorschaubilder angezeigt wird.",
+  "upload_modal.preview_label": "Vorschau ({ratio})",
   "upload_progress.label": "Wird hochgeladen …",
   "video.close": "Video schließen",
   "video.exit_fullscreen": "Vollbild verlassen",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 236d573e87c22fccd409d7e833acdae66eca603b..62d822615e79993e6df0cc02a8c8114bf43f897b 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -8,6 +8,14 @@
       {
         "defaultMessage": "An unexpected error occurred.",
         "id": "alert.unexpected.message"
+      },
+      {
+        "defaultMessage": "Rate limited",
+        "id": "alert.rate_limited.title"
+      },
+      {
+        "defaultMessage": "Please retry after {retry_time, time, medium}.",
+        "id": "alert.rate_limited.message"
       }
     ],
     "path": "app/javascript/mastodon/actions/alerts.json"
@@ -71,6 +79,15 @@
     ],
     "path": "app/javascript/mastodon/components/account.json"
   },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "{count} per week",
+        "id": "autosuggest_hashtag.per_week"
+      }
+    ],
+    "path": "app/javascript/mastodon/components/autosuggest_hashtag.json"
+  },
   {
     "descriptors": [
       {
@@ -131,6 +148,27 @@
     ],
     "path": "app/javascript/mastodon/components/domain.json"
   },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+        "id": "error.unexpected_crash.explanation"
+      },
+      {
+        "defaultMessage": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+        "id": "error.unexpected_crash.next_steps"
+      },
+      {
+        "defaultMessage": "Report issue",
+        "id": "errors.unexpected_crash.report_issue"
+      },
+      {
+        "defaultMessage": "Copy stacktrace to clipboard",
+        "id": "errors.unexpected_crash.copy_stacktrace"
+      }
+    ],
+    "path": "app/javascript/mastodon/components/error_boundary.json"
+  },
   {
     "descriptors": [
       {
@@ -158,6 +196,15 @@
     ],
     "path": "app/javascript/mastodon/components/load_more.json"
   },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "{count, plural, one {# new item} other {# new items}}",
+        "id": "load_pending"
+      }
+    ],
+    "path": "app/javascript/mastodon/components/load_pending.json"
+  },
   {
     "descriptors": [
       {
@@ -173,6 +220,10 @@
         "defaultMessage": "Toggle visibility",
         "id": "media_gallery.toggle_visible"
       },
+      {
+        "defaultMessage": "Not available",
+        "id": "status.uncached_media_warning"
+      },
       {
         "defaultMessage": "Sensitive content",
         "id": "status.sensitive_warning"
@@ -203,6 +254,19 @@
         "defaultMessage": "Closed",
         "id": "poll.closed"
       },
+      {
+        "defaultMessage": "You voted for this answer",
+        "description": "Tooltip of the \"voted\" checkmark in polls",
+        "id": "poll.voted"
+      },
+      {
+        "defaultMessage": "{count, plural, one {# person} other {# people}}",
+        "id": "poll.total_people"
+      },
+      {
+        "defaultMessage": "{count, plural, one {# vote} other {# votes}}",
+        "id": "poll.total_votes"
+      },
       {
         "defaultMessage": "Vote",
         "id": "poll.vote"
@@ -210,13 +274,22 @@
       {
         "defaultMessage": "Refresh",
         "id": "poll.refresh"
+      }
+    ],
+    "path": "app/javascript/mastodon/components/poll.json"
+  },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "Loading…",
+        "id": "regeneration_indicator.label"
       },
       {
-        "defaultMessage": "{count, plural, one {# vote} other {# votes}}",
-        "id": "poll.total_votes"
+        "defaultMessage": "Your home feed is being prepared!",
+        "id": "regeneration_indicator.sublabel"
       }
     ],
-    "path": "app/javascript/mastodon/components/poll.json"
+    "path": "app/javascript/mastodon/components/regeneration_indicator.json"
   },
   {
     "descriptors": [
@@ -389,19 +462,6 @@
     ],
     "path": "app/javascript/mastodon/components/status_content.json"
   },
-  {
-    "descriptors": [
-      {
-        "defaultMessage": "Loading…",
-        "id": "regeneration_indicator.label"
-      },
-      {
-        "defaultMessage": "Your home feed is being prepared!",
-        "id": "regeneration_indicator.sublabel"
-      }
-    ],
-    "path": "app/javascript/mastodon/components/status_list.json"
-  },
   {
     "descriptors": [
       {
@@ -467,10 +527,6 @@
         "defaultMessage": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
         "id": "confirmations.redraft.message"
       },
-      {
-        "defaultMessage": "Block",
-        "id": "confirmations.block.confirm"
-      },
       {
         "defaultMessage": "Reply",
         "id": "confirmations.reply.confirm"
@@ -478,14 +534,6 @@
       {
         "defaultMessage": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
         "id": "confirmations.reply.message"
-      },
-      {
-        "defaultMessage": "Block & Report",
-        "id": "confirmations.block.block_and_report"
-      },
-      {
-        "defaultMessage": "Are you sure you want to block {name}?",
-        "id": "confirmations.block.message"
       }
     ],
     "path": "app/javascript/mastodon/containers/status_container.json"
@@ -522,26 +570,14 @@
         "defaultMessage": "Unfollow",
         "id": "confirmations.unfollow.confirm"
       },
-      {
-        "defaultMessage": "Block",
-        "id": "confirmations.block.confirm"
-      },
       {
         "defaultMessage": "Hide entire domain",
         "id": "confirmations.domain_block.confirm"
       },
-      {
-        "defaultMessage": "Block & Report",
-        "id": "confirmations.block.block_and_report"
-      },
       {
         "defaultMessage": "Are you sure you want to unfollow {name}?",
         "id": "confirmations.unfollow.message"
       },
-      {
-        "defaultMessage": "Are you sure you want to block {name}?",
-        "id": "confirmations.block.message"
-      },
       {
         "defaultMessage": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
         "id": "confirmations.domain_block.message"
@@ -572,6 +608,10 @@
         "defaultMessage": "Follow",
         "id": "account.follow"
       },
+      {
+        "defaultMessage": "Cancel follow request",
+        "id": "account.cancel_follow_request"
+      },
       {
         "defaultMessage": "Awaiting approval. Click to cancel follow request",
         "id": "account.requested"
@@ -723,6 +763,27 @@
     ],
     "path": "app/javascript/mastodon/features/account/components/header.json"
   },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "Play",
+        "id": "video.play"
+      },
+      {
+        "defaultMessage": "Pause",
+        "id": "video.pause"
+      },
+      {
+        "defaultMessage": "Mute sound",
+        "id": "video.mute"
+      },
+      {
+        "defaultMessage": "Unmute sound",
+        "id": "video.unmute"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/audio/index.json"
+  },
   {
     "descriptors": [
       {
@@ -739,7 +800,7 @@
   {
     "descriptors": [
       {
-        "defaultMessage": "Media Only",
+        "defaultMessage": "Media only",
         "id": "community.column_settings.media_only"
       }
     ],
@@ -799,6 +860,10 @@
       {
         "defaultMessage": "Muted words",
         "id": "navigation_bar.filters"
+      },
+      {
+        "defaultMessage": "Logout",
+        "id": "navigation_bar.logout"
       }
     ],
     "path": "app/javascript/mastodon/features/compose/components/action_bar.json"
@@ -1033,6 +1098,10 @@
         "defaultMessage": "Toots",
         "id": "search_results.statuses"
       },
+      {
+        "defaultMessage": "Searching toots by their content is not enabled on this Mastodon server.",
+        "id": "search_results.statuses_fts_disabled"
+      },
       {
         "defaultMessage": "Hashtags",
         "id": "search_results.hashtags"
@@ -1089,29 +1158,38 @@
   {
     "descriptors": [
       {
-        "defaultMessage": "Uploading...",
+        "defaultMessage": "Uploading…",
         "id": "upload_progress.label"
       }
     ],
-    "path": "app/javascript/mastodon/features/compose/components/upload_progress.json"
+    "path": "app/javascript/mastodon/features/compose/components/upload_form.json"
   },
   {
     "descriptors": [
-      {
-        "defaultMessage": "Describe for the visually impaired",
-        "id": "upload_form.description"
-      },
       {
         "defaultMessage": "Delete",
         "id": "upload_form.undo"
       },
       {
-        "defaultMessage": "Crop",
-        "id": "upload_form.focus"
+        "defaultMessage": "Edit",
+        "id": "upload_form.edit"
       }
     ],
     "path": "app/javascript/mastodon/features/compose/components/upload.json"
   },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "Are you sure you want to log out?",
+        "id": "confirmations.logout.message"
+      },
+      {
+        "defaultMessage": "Log out",
+        "id": "confirmations.logout.confirm"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/compose/containers/navigation_container.json"
+  },
   {
     "descriptors": [
       {
@@ -1200,10 +1278,68 @@
       {
         "defaultMessage": "Compose new toot",
         "id": "navigation_bar.compose"
+      },
+      {
+        "defaultMessage": "Are you sure you want to log out?",
+        "id": "confirmations.logout.message"
+      },
+      {
+        "defaultMessage": "Log out",
+        "id": "confirmations.logout.confirm"
       }
     ],
     "path": "app/javascript/mastodon/features/compose/index.json"
   },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "More",
+        "id": "status.more"
+      },
+      {
+        "defaultMessage": "View conversation",
+        "id": "conversation.open"
+      },
+      {
+        "defaultMessage": "Reply",
+        "id": "status.reply"
+      },
+      {
+        "defaultMessage": "Mark as read",
+        "id": "conversation.mark_as_read"
+      },
+      {
+        "defaultMessage": "Delete conversation",
+        "id": "conversation.delete"
+      },
+      {
+        "defaultMessage": "Mute conversation",
+        "id": "status.mute_conversation"
+      },
+      {
+        "defaultMessage": "Unmute conversation",
+        "id": "status.unmute_conversation"
+      },
+      {
+        "defaultMessage": "With {names}",
+        "id": "conversation.with"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/direct_timeline/components/conversation.json"
+  },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "Reply",
+        "id": "confirmations.reply.confirm"
+      },
+      {
+        "defaultMessage": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+        "id": "confirmations.reply.message"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/direct_timeline/containers/conversation_container.json"
+  },
   {
     "descriptors": [
       {
@@ -1217,6 +1353,76 @@
     ],
     "path": "app/javascript/mastodon/features/direct_timeline/index.json"
   },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "Follow",
+        "id": "account.follow"
+      },
+      {
+        "defaultMessage": "Unfollow",
+        "id": "account.unfollow"
+      },
+      {
+        "defaultMessage": "Awaiting approval",
+        "id": "account.requested"
+      },
+      {
+        "defaultMessage": "Unblock @{name}",
+        "id": "account.unblock"
+      },
+      {
+        "defaultMessage": "Unmute @{name}",
+        "id": "account.unmute"
+      },
+      {
+        "defaultMessage": "Are you sure you want to unfollow {name}?",
+        "id": "confirmations.unfollow.message"
+      },
+      {
+        "defaultMessage": "Toots",
+        "id": "account.posts"
+      },
+      {
+        "defaultMessage": "Followers",
+        "id": "account.followers"
+      },
+      {
+        "defaultMessage": "Never",
+        "id": "account.never_active"
+      },
+      {
+        "defaultMessage": "Last active",
+        "id": "account.last_status"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/directory/components/account_card.json"
+  },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "Browse profiles",
+        "id": "column.directory"
+      },
+      {
+        "defaultMessage": "Recently active",
+        "id": "directory.recently_active"
+      },
+      {
+        "defaultMessage": "New arrivals",
+        "id": "directory.new_arrivals"
+      },
+      {
+        "defaultMessage": "From {domain} only",
+        "id": "directory.local"
+      },
+      {
+        "defaultMessage": "From known fediverse",
+        "id": "directory.federated"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/directory/index.json"
+  },
   {
     "descriptors": [
       {
@@ -1249,6 +1455,10 @@
   },
   {
     "descriptors": [
+      {
+        "defaultMessage": "Refresh",
+        "id": "refresh"
+      },
       {
         "defaultMessage": "No one has favourited this toot yet. When someone does, they will show up here.",
         "id": "empty_column.favourites"
@@ -1308,6 +1518,15 @@
     ],
     "path": "app/javascript/mastodon/features/following/index.json"
   },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "Trending now",
+        "id": "trends.trending_now"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/getting_started/components/trends.json"
+  },
   {
     "descriptors": [
       {
@@ -1953,6 +2172,10 @@
   },
   {
     "descriptors": [
+      {
+        "defaultMessage": "Refresh",
+        "id": "refresh"
+      },
       {
         "defaultMessage": "No one has boosted this toot yet. When someone does, they will show up here.",
         "id": "status.reblogs.empty"
@@ -2080,10 +2303,6 @@
         "defaultMessage": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
         "id": "confirmations.redraft.message"
       },
-      {
-        "defaultMessage": "Block",
-        "id": "confirmations.block.confirm"
-      },
       {
         "defaultMessage": "Reply",
         "id": "confirmations.reply.confirm"
@@ -2091,14 +2310,6 @@
       {
         "defaultMessage": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
         "id": "confirmations.reply.message"
-      },
-      {
-        "defaultMessage": "Block & Report",
-        "id": "confirmations.block.block_and_report"
-      },
-      {
-        "defaultMessage": "Are you sure you want to block {name}?",
-        "id": "confirmations.block.message"
       }
     ],
     "path": "app/javascript/mastodon/features/status/containers/detailed_status_container.json"
@@ -2121,10 +2332,6 @@
         "defaultMessage": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
         "id": "confirmations.redraft.message"
       },
-      {
-        "defaultMessage": "Block",
-        "id": "confirmations.block.confirm"
-      },
       {
         "defaultMessage": "Show more for all",
         "id": "status.show_more_all"
@@ -2144,17 +2351,39 @@
       {
         "defaultMessage": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
         "id": "confirmations.reply.message"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/status/index.json"
+  },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "View context",
+        "id": "lightbox.view_context"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/ui/components/audio_modal.json"
+  },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "Are you sure you want to block {name}?",
+        "id": "confirmations.block.message"
+      },
+      {
+        "defaultMessage": "Cancel",
+        "id": "confirmation_modal.cancel"
       },
       {
         "defaultMessage": "Block & Report",
         "id": "confirmations.block.block_and_report"
       },
       {
-        "defaultMessage": "Are you sure you want to block {name}?",
-        "id": "confirmations.block.message"
+        "defaultMessage": "Block",
+        "id": "confirmations.block.confirm"
       }
     ],
-    "path": "app/javascript/mastodon/features/status/index.json"
+    "path": "app/javascript/mastodon/features/ui/components/block_modal.json"
   },
   {
     "descriptors": [
@@ -2227,6 +2456,10 @@
   },
   {
     "descriptors": [
+      {
+        "defaultMessage": "Close",
+        "id": "lightbox.close"
+      },
       {
         "defaultMessage": "Embed",
         "id": "status.embed"
@@ -2242,6 +2475,47 @@
     ],
     "path": "app/javascript/mastodon/features/ui/components/embed_modal.json"
   },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "Close",
+        "id": "lightbox.close"
+      },
+      {
+        "defaultMessage": "Apply",
+        "id": "upload_modal.apply"
+      },
+      {
+        "defaultMessage": "A quick brown fox jumps over the lazy dog",
+        "id": "upload_modal.description_placeholder"
+      },
+      {
+        "defaultMessage": "Edit media",
+        "id": "upload_modal.edit_media"
+      },
+      {
+        "defaultMessage": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+        "id": "upload_modal.hint"
+      },
+      {
+        "defaultMessage": "Describe for the visually impaired",
+        "id": "upload_form.description"
+      },
+      {
+        "defaultMessage": "Analyzing picture…",
+        "id": "upload_modal.analyzing_picture"
+      },
+      {
+        "defaultMessage": "Detect text from picture",
+        "id": "upload_modal.detect_text"
+      },
+      {
+        "defaultMessage": "Preview ({ratio})",
+        "id": "upload_modal.preview_label"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/ui/components/focal_point_modal.json"
+  },
   {
     "descriptors": [
       {
@@ -2253,6 +2527,14 @@
   },
   {
     "descriptors": [
+      {
+        "defaultMessage": "Are you sure you want to log out?",
+        "id": "confirmations.logout.message"
+      },
+      {
+        "defaultMessage": "Log out",
+        "id": "confirmations.logout.confirm"
+      },
       {
         "defaultMessage": "Invite people",
         "id": "getting_started.invite"
@@ -2323,6 +2605,10 @@
         "defaultMessage": "Are you sure you want to mute {name}?",
         "id": "confirmations.mute.message"
       },
+      {
+        "defaultMessage": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
+        "id": "confirmations.mute.explanation"
+      },
       {
         "defaultMessage": "Hide notifications from this user?",
         "id": "mute_modal.hide_notifications"
@@ -2368,6 +2654,10 @@
         "defaultMessage": "Lists",
         "id": "navigation_bar.lists"
       },
+      {
+        "defaultMessage": "Profile directory",
+        "id": "getting_started.directory"
+      },
       {
         "defaultMessage": "Preferences",
         "id": "navigation_bar.preferences"
@@ -2375,10 +2665,6 @@
       {
         "defaultMessage": "Follows and followers",
         "id": "navigation_bar.follows_and_followers"
-      },
-      {
-        "defaultMessage": "Profile directory",
-        "id": "navigation_bar.profile_directory"
       }
     ],
     "path": "app/javascript/mastodon/features/ui/components/navigation_panel.json"
diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json
index 50d7b1d7024e2987d29de4e295030655be5abc63..e395c8ba991168b92371cfdcf8c29a657e556218 100644
--- a/app/javascript/mastodon/locales/el.json
+++ b/app/javascript/mastodon/locales/el.json
@@ -1,9 +1,10 @@
 {
   "account.add_or_remove_from_list": "Προσθήκη ή Αφαίρεση από λίστες",
   "account.badges.bot": "Μποτ",
-  "account.block": "Αποκλισμός @{name}",
+  "account.block": "Αποκλεισμός @{name}",
   "account.block_domain": "Απόκρυψε τα πάντα από το {domain}",
   "account.blocked": "Αποκλεισμένος/η",
+  "account.cancel_follow_request": "Ακύρωση αιτήματος παρακολούθησης",
   "account.direct": "Προσωπικό μήνυμα προς @{name}",
   "account.domain_blocked": "Κρυμμένος τομέας",
   "account.edit_profile": "Επεξεργασία προφίλ",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Αυτός ο χρήστης δεν ακολουθεί κανέναν ακόμα.",
   "account.follows_you": "Σε ακολουθεί",
   "account.hide_reblogs": "Απόκρυψη προωθήσεων από @{name}",
+  "account.last_status": "Τελευταία δραστηριότητα",
   "account.link_verified_on": "Η ιδιοκτησία αυτού του συνδέσμου ελέχθηκε την {date}",
   "account.locked_info": "Η κατάσταση απορρήτου αυτού του λογαριασμού είναι κλειδωμένη. Ο ιδιοκτήτης επιβεβαιώνει χειροκίνητα ποιος μπορεί να τον ακολουθήσει.",
   "account.media": "Πολυμέσα",
@@ -23,6 +25,7 @@
   "account.mute": "Σώπασε @{name}",
   "account.mute_notifications": "Σώπασε τις ειδοποιήσεις από @{name}",
   "account.muted": "Αποσιωπημένος/η",
+  "account.never_active": "Ποτέ",
   "account.posts": "Τουτ",
   "account.posts_with_replies": "Τουτ και απαντήσεις",
   "account.report": "Κατάγγειλε @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Διακοπή παρακολούθησης",
   "account.unmute": "Διακοπή αποσιώπησης @{name}",
   "account.unmute_notifications": "Διακοπή αποσιώπησης ειδοποιήσεων του/της @{name}",
+  "alert.rate_limited.message": "Παρακαλούμε δοκίμασε ξανά αφού περάσει η {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Περιορισμός συχνότητας",
   "alert.unexpected.message": "Προέκυψε απροσδόκητο σφάλμα.",
   "alert.unexpected.title": "Εεπ!",
+  "autosuggest_hashtag.per_week": "{count} ανα εβδομάδα",
   "boost_modal.combo": "Μπορείς να πατήσεις {combo} για να το προσπεράσεις αυτό την επόμενη φορά",
   "bundle_column_error.body": "Κάτι πήγε στραβά ενώ φορτωνόταν αυτό το στοιχείο.",
   "bundle_column_error.retry": "Δοκίμασε ξανά",
@@ -47,6 +53,7 @@
   "column.blocks": "Αποκλεισμένοι χρήστες",
   "column.community": "Τοπική ροή",
   "column.direct": "Προσωπικά μηνύματα",
+  "column.directory": "Δες προφίλ",
   "column.domain_blocks": "Κρυμμένοι τομείς",
   "column.favourites": "Αγαπημένα",
   "column.follow_requests": "Αιτήματα ακολούθησης",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Σίγουρα θες να διαγράψεις οριστικά αυτή τη λίστα;",
   "confirmations.domain_block.confirm": "Απόκρυψη ολόκληρου του τομέα",
   "confirmations.domain_block.message": "Σίγουρα θες να μπλοκάρεις ολόκληρο το {domain}; Συνήθως μερικά εστιασμένα μπλοκ ή αποσιωπήσεις επαρκούν και προτιμούνται. Δεν θα βλέπεις περιεχόμενο από αυτό τον κόμβο σε καμία δημόσια ροή, ούτε στις ειδοποιήσεις σου. Όσους ακόλουθους έχεις αυτό αυτό τον κόμβο θα αφαιρεθούν.",
+  "confirmations.logout.confirm": "Αποσύνδεση",
+  "confirmations.logout.message": "Σίγουρα θέλεις να αποσυνδεθείς;",
   "confirmations.mute.confirm": "Αποσιώπηση",
+  "confirmations.mute.explanation": "Αυτό θα κρύψει τις δημοσιεύσεις τους και τις δημοσιεύσεις που τους αναφέρουν, αλλά θα συνεχίσουν να μπορούν να βλέπουν τις δημοσιεύσεις σου και να σε ακολουθούν.",
   "confirmations.mute.message": "Σίγουρα θες να αποσιωπήσεις {name};",
   "confirmations.redraft.confirm": "Διαγραφή & ξαναγράψιμο",
   "confirmations.redraft.message": "Σίγουρα θέλεις να σβήσεις αυτή την κατάσταση και να την ξαναγράψεις; Οι αναφορές και τα αγαπημένα της θα χαθούν ενώ οι απαντήσεις προς αυτή θα μείνουν ορφανές.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Απαντώντας τώρα θα αντικαταστήσεις το κείμενο που ήδη γράφεις. Σίγουρα θέλεις να συνεχίσεις;",
   "confirmations.unfollow.confirm": "Διακοπή παρακολούθησης",
   "confirmations.unfollow.message": "Σίγουρα θες να πάψεις να ακολουθείς τον/την {name};",
+  "conversation.delete": "Διαγραφή συζήτησης",
+  "conversation.mark_as_read": "Σήμανση ως αναγνωσμένο",
+  "conversation.open": "Προβολή συνομιλίας",
+  "conversation.with": "Με {names}",
+  "directory.federated": "Από το γνωστό fediverse",
+  "directory.local": "Μόνο από {domain}",
+  "directory.new_arrivals": "Νέες αφίξεις",
+  "directory.recently_active": "Πρόσφατα ενεργοί",
   "embed.instructions": "Ενσωματώστε αυτή την κατάσταση στην ιστοσελίδα σας αντιγράφοντας τον παρακάτω κώδικα.",
   "embed.preview": "Ορίστε πως θα φαίνεται:",
   "emoji_button.activity": "Δραστηριότητα",
@@ -113,7 +131,7 @@
   "emoji_button.objects": "Αντικείμενα",
   "emoji_button.people": "Άνθρωποι",
   "emoji_button.recent": "Δημοφιλή",
-  "emoji_button.search": "Αναζήτηση…",
+  "emoji_button.search": "Αναζήτηση...",
   "emoji_button.search_results": "Αποτελέσματα αναζήτησης",
   "emoji_button.symbols": "Σύμβολα",
   "emoji_button.travel": "Ταξίδια & Τοποθεσίες",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Δεν έχεις αποσιωπήσει κανένα χρήστη ακόμα.",
   "empty_column.notifications": "Δεν έχεις ειδοποιήσεις ακόμα. Αλληλεπίδρασε με άλλους χρήστες για να ξεκινήσεις την κουβέντα.",
   "empty_column.public": "Δεν υπάρχει τίποτα εδώ! Γράψε κάτι δημόσιο, ή ακολούθησε χειροκίνητα χρήστες από άλλους κόμβους για να τη γεμίσεις",
+  "error.unexpected_crash.explanation": "Είτε λόγω λάθους στον κώδικά μας ή λόγω ασυμβατότητας με τον browser, η σελίδα δε μπόρεσε να εμφανιστεί σωστά.",
+  "error.unexpected_crash.next_steps": "Δοκίμασε να ανανεώσεις τη σελίδα. Αν αυτό δε βοηθήσει, ίσως να μπορέσεις να χρησιμοποιήσεις το Mastodon μέσω διαφορετικού browser ή κάποιας εφαρμογής.",
+  "errors.unexpected_crash.copy_stacktrace": "Αντιγραφή μηνυμάτων κώδικα στο πρόχειρο",
+  "errors.unexpected_crash.report_issue": "Αναφορά προβλήματος",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -158,7 +180,7 @@
   "hashtag.column_settings.tag_mode.any": "Οποιοδήποτε από αυτά",
   "hashtag.column_settings.tag_mode.none": "Κανένα από αυτά",
   "hashtag.column_settings.tag_toggle": "Προσθήκη επιπλέον ταμπελών για την κολώνα",
-  "home.column_settings.basic": "Βασικά",
+  "home.column_settings.basic": "Βασικές ρυθμίσεις",
   "home.column_settings.show_reblogs": "Εμφάνιση προωθήσεων",
   "home.column_settings.show_replies": "Εμφάνιση απαντήσεων",
   "intervals.full.days": "{number, plural, one {# μέρα} other {# μέρες}}",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Τίτλος νέας λίστα",
   "lists.search": "Αναζήτησε μεταξύ των ανθρώπων που ακουλουθείς",
   "lists.subheading": "Οι λίστες σου",
+  "load_pending": "{count, plural, one {# νέο} other {# νέα}}",
   "loading_indicator.label": "Φορτώνει...",
   "media_gallery.toggle_visible": "Εναλλαγή ορατότητας",
   "missing_indicator.label": "Δε βρέθηκε",
@@ -242,7 +265,7 @@
   "navigation_bar.favourites": "Αγαπημένα",
   "navigation_bar.filters": "Αποσιωπημένες λέξεις",
   "navigation_bar.follow_requests": "Αιτήματα ακολούθησης",
-  "navigation_bar.follows_and_followers": "Ακολουθεί και ακολουθείται",
+  "navigation_bar.follows_and_followers": "Ακολουθείς και σε ακολουθούν",
   "navigation_bar.info": "Πληροφορίες κόμβου",
   "navigation_bar.keyboard_shortcuts": "Συντομεύσεις",
   "navigation_bar.lists": "Λίστες",
@@ -251,13 +274,12 @@
   "navigation_bar.personal": "Προσωπικά",
   "navigation_bar.pins": "Καρφιτσωμένα τουτ",
   "navigation_bar.preferences": "Προτιμήσεις",
-  "navigation_bar.profile_directory": "Κατάλογος λογαριασμών",
   "navigation_bar.public_timeline": "Ομοσπονδιακή ροή",
   "navigation_bar.security": "Ασφάλεια",
   "notification.favourite": "Ο/Η {name} σημείωσε ως αγαπημένη την κατάστασή σου",
   "notification.follow": "Ο/Η {name} σε ακολούθησε",
   "notification.mention": "Ο/Η {name} σε ανέφερε",
-  "notification.poll": "Έλαβε τέλος μια από τις ψηφοφορίες που συμμετείχες",
+  "notification.poll": "Τελείωσε μια από τις ψηφοφορίες που συμμετείχες",
   "notification.reblog": "Ο/Η {name} προώθησε την κατάστασή σου",
   "notifications.clear": "Καθαρισμός ειδοποιήσεων",
   "notifications.clear_confirmation": "Σίγουρα θέλεις να καθαρίσεις όλες τις ειδοποιήσεις σου;",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} ειδοποιήσεις",
   "poll.closed": "Κλειστή",
   "poll.refresh": "Ανανέωση",
+  "poll.total_people": "{count, plural, one {# άτομο} other {# άτομα}}",
   "poll.total_votes": "{count, plural, one {# ψήφος} other {# ψήφοι}}",
   "poll.vote": "Ψήφισε",
+  "poll.voted": "Ψηφίσατε αυτήν την απάντηση",
   "poll_button.add_poll": "Προσθήκη δημοσκόπησης",
   "poll_button.remove_poll": "Αφαίρεση δημοσκόπησης",
   "privacy.change": "Προσαρμογή ιδιωτικότητας δημοσίευσης",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Δημόσιο",
   "privacy.unlisted.long": "Μην δημοσιεύσεις στις δημόσιες ροές",
   "privacy.unlisted.short": "Μη καταχωρημένα",
+  "refresh": "Ανανέωση",
   "regeneration_indicator.label": "Φορτώνει…",
   "regeneration_indicator.sublabel": "Η αρχική σου ροή ετοιμάζεται!",
   "relative_time.days": "{number}η",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Άνθρωποι",
   "search_results.hashtags": "Ταμπέλες",
   "search_results.statuses": "Τουτ",
+  "search_results.statuses_fts_disabled": "Η αναζήτηση τουτ βάσει του περιεχόμενού τους δεν είναι ενεργοποιημένη σε αυτό τον κόμβο.",
   "search_results.total": "{count, number} {count, plural, zero {αποτελέσματα} one {αποτέλεσμα} other {αποτελέσματα}}",
   "status.admin_account": "Άνοιγμα λειτουργίας διαμεσολάβησης για τον/την @{name}",
   "status.admin_status": "Άνοιγμα αυτής της δημοσίευσης στη λειτουργία διαμεσολάβησης",
@@ -358,13 +384,14 @@
   "status.show_more": "Δείξε περισσότερα",
   "status.show_more_all": "Δείξε περισσότερα για όλα",
   "status.show_thread": "Εμφάνιση νήματος",
+  "status.uncached_media_warning": "Μη διαθέσιμα",
   "status.unmute_conversation": "Διέκοψε την αποσιώπηση της συζήτησης",
   "status.unpin": "Ξεκαρφίτσωσε από το προφίλ",
   "suggestions.dismiss": "Απόρριψη πρότασης",
   "suggestions.header": "Ίσως να ενδιαφέρεσαι για…",
   "tabs_bar.federated_timeline": "Ομοσπονδιακή",
   "tabs_bar.home": "Αρχική",
-  "tabs_bar.local_timeline": "Τοπικά",
+  "tabs_bar.local_timeline": "Τοπική",
   "tabs_bar.notifications": "Ειδοποιήσεις",
   "tabs_bar.search": "Αναζήτηση",
   "time_remaining.days": "απομένουν {number, plural, one {# ημέρα} other {# ημέρες}}",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Απομένουν στιγμές",
   "time_remaining.seconds": "απομένουν {number, plural, one {# δευτερόλεπτο} other {# δευτερόλεπτα}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} μιλάνε",
+  "trends.trending_now": "Δημοφιλή τώρα",
   "ui.beforeunload": "Το προσχέδιό σου θα χαθεί αν φύγεις από το Mastodon.",
   "upload_area.title": "Drag & drop για να ανεβάσεις",
-  "upload_button.label": "Πρόσθεσε πολυμέσα (JPEG, PNG, GIF, WebM, MP4, MOV)",
+  "upload_button.label": "Πρόσθεσε πολυμέσα ({formats})",
   "upload_error.limit": "Υπέρβαση ορίου μεγέθους ανεβασμένων αρχείων.",
   "upload_error.poll": "Στις δημοσκοπήσεις δεν επιτρέπεται η μεταφόρτωση αρχείου.",
   "upload_form.description": "Περιέγραψε για όσους & όσες έχουν προβλήματα όρασης",
-  "upload_form.focus": "Αλλαγή προεπισκόπησης",
+  "upload_form.edit": "Ενημέρωση",
   "upload_form.undo": "Διαγραφή",
+  "upload_modal.analyzing_picture": "Ανάλυση εικόνας…",
+  "upload_modal.apply": "Εφαρμογή",
+  "upload_modal.description_placeholder": "Λύκος μαύρος και ισχνός του πατέρα του καημός",
+  "upload_modal.detect_text": "Αναγνώριση κειμένου από την εικόνα",
+  "upload_modal.edit_media": "Επεξεργασία Πολυμέσων",
+  "upload_modal.hint": "Κάνε κλικ ή σείρε τον κύκλο στην προεπισκόπηση για να επιλέξεις το σημείο εστίασης που θα είναι πάντα εμφανές σε όλες τις μικρογραφίες.",
+  "upload_modal.preview_label": "Προεπισκόπηση ({ratio})",
   "upload_progress.label": "Ανεβαίνει...",
   "video.close": "Κλείσε το βίντεο",
   "video.exit_fullscreen": "Έξοδος από πλήρη οθόνη",
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index acb7393265a36218fd1a59c8ada74718583f5717..29070422dbdecc12ed99cabb9cc6afa79d8a4d2e 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -4,6 +4,7 @@
   "account.block": "Block @{name}",
   "account.block_domain": "Hide everything from {domain}",
   "account.blocked": "Blocked",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Direct message @{name}",
   "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Edit profile",
@@ -15,6 +16,7 @@
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Media",
@@ -23,6 +25,7 @@
   "account.mute": "Mute @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.muted": "Muted",
+  "account.never_active": "Never",
   "account.posts": "Toots",
   "account.posts_with_replies": "Toots and replies",
   "account.report": "Report @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Unfollow",
   "account.unmute": "Unmute @{name}",
   "account.unmute_notifications": "Unmute notifications from @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "bundle_column_error.body": "Something went wrong while loading this component.",
   "bundle_column_error.retry": "Try again",
@@ -47,6 +53,7 @@
   "column.blocks": "Blocked users",
   "column.community": "Local timeline",
   "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
   "confirmations.domain_block.confirm": "Hide entire domain",
   "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Mute",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "embed.preview": "Here is what it will look like:",
   "emoji_button.activity": "Activity",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
   "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -147,7 +169,7 @@
   "getting_started.heading": "Getting started",
   "getting_started.invite": "Invite people",
   "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
-  "getting_started.security": "Security",
+  "getting_started.security": "Account settings",
   "getting_started.terms": "Terms of service",
   "hashtag.column_header.tag_mode.all": "and {additional}",
   "hashtag.column_header.tag_mode.any": "or {additional}",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "New list title",
   "lists.search": "Search among people you follow",
   "lists.subheading": "Your lists",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Loading...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Pinned toots",
   "navigation_bar.preferences": "Preferences",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Federated timeline",
   "navigation_bar.security": "Security",
   "notification.favourite": "{name} favourited your status",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Do not post to public timelines",
   "privacy.unlisted.short": "Unlisted",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Loading…",
   "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "People",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Show more",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Add media ({formats})",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
-  "upload_form.focus": "Change preview",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Delete",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Uploading...",
   "video.close": "Close video",
   "video.exit_fullscreen": "Exit full screen",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index 709bd093da39258f9e7a7b97cb9c6e0b749bfc5f..0835ae5b07b6edd31039c163459ad10ce35b727c 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -4,6 +4,7 @@
   "account.block": "Bloki @{name}",
   "account.block_domain": "Kaŝi ĉion de {domain}",
   "account.blocked": "Blokita",
+  "account.cancel_follow_request": "Nuligi peton de sekvado",
   "account.direct": "Rekte mesaĝi @{name}",
   "account.domain_blocked": "Domajno kaŝita",
   "account.edit_profile": "Redakti profilon",
@@ -15,14 +16,16 @@
   "account.follows.empty": "Tiu uzanto ankoraÅ­ ne sekvas iun.",
   "account.follows_you": "Sekvas vin",
   "account.hide_reblogs": "Kaŝi diskonigojn de @{name}",
+  "account.last_status": "Laste aktiva",
   "account.link_verified_on": "La posedanto de tiu ligilo estis kontrolita je {date}",
   "account.locked_info": "La privateco de tiu konto estas elektita kiel fermita. La posedanto povas mane akcepti tiun, kiu povas sekvi rin.",
   "account.media": "Aŭdovidaĵoj",
   "account.mention": "Mencii @{name}",
   "account.moved_to": "{name} moviĝis al:",
   "account.mute": "Silentigi @{name}",
-  "account.mute_notifications": "Silentigi sciigojn el @{name}",
+  "account.mute_notifications": "Silentigi sciigojn de @{name}",
   "account.muted": "Silentigita",
+  "account.never_active": "Neniam",
   "account.posts": "Mesaĝoj",
   "account.posts_with_replies": "Kun respondoj",
   "account.report": "Signali @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Ne plu sekvi",
   "account.unmute": "Malsilentigi @{name}",
   "account.unmute_notifications": "Malsilentigi sciigojn de @{name}",
+  "alert.rate_limited.message": "Bonvolu reprovi post {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Mesaĝkvante limigita",
   "alert.unexpected.message": "Neatendita eraro okazis.",
   "alert.unexpected.title": "Ups!",
+  "autosuggest_hashtag.per_week": "{count} semajne",
   "boost_modal.combo": "Vi povas premi {combo} por preterpasi sekvafoje",
   "bundle_column_error.body": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",
   "bundle_column_error.retry": "Bonvolu reprovi",
@@ -47,6 +53,7 @@
   "column.blocks": "Blokitaj uzantoj",
   "column.community": "Loka tempolinio",
   "column.direct": "Rektaj mesaĝoj",
+  "column.directory": "Trarigardi profilojn",
   "column.domain_blocks": "Kaŝitaj domajnoj",
   "column.favourites": "Stelumoj",
   "column.follow_requests": "Petoj de sekvado",
@@ -71,20 +78,20 @@
   "compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn, kiuj estas nur por sekvantoj.",
   "compose_form.lock_disclaimer.lock": "ŝlosita",
   "compose_form.placeholder": "Pri kio vi pensas?",
-  "compose_form.poll.add_option": "Aldoni elekto",
-  "compose_form.poll.duration": "Balotenketo daÅ­ro",
-  "compose_form.poll.option_placeholder": "elekto {number}",
-  "compose_form.poll.remove_option": "Forigi ĉi tiu elekton",
+  "compose_form.poll.add_option": "Aldoni elekteblon",
+  "compose_form.poll.duration": "Balotenketa daÅ­ro",
+  "compose_form.poll.option_placeholder": "Elekteblo {number}",
+  "compose_form.poll.remove_option": "Forigi ĉi tiu elekteblon",
   "compose_form.publish": "Hup",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.hide": "Marki aŭdovidaĵojn kiel tiklaj",
+  "compose_form.sensitive.hide": "Marki la aŭdovidaĵojn kiel tiklaj",
   "compose_form.sensitive.marked": "Aŭdovidaĵo markita tikla",
   "compose_form.sensitive.unmarked": "Aŭdovidaĵo ne markita tikla",
   "compose_form.spoiler.marked": "Teksto kaŝita malantaŭ averto",
   "compose_form.spoiler.unmarked": "Teksto ne kaŝita",
   "compose_form.spoiler_placeholder": "Skribu vian averton ĉi tie",
   "confirmation_modal.cancel": "Nuligi",
-  "confirmations.block.block_and_report": "Bloki & Signali",
+  "confirmations.block.block_and_report": "Bloki kaj signali",
   "confirmations.block.confirm": "Bloki",
   "confirmations.block.message": "Ĉu vi certas, ke vi volas bloki {name}?",
   "confirmations.delete.confirm": "Forigi",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Ĉu vi certas, ke vi volas porĉiame forigi ĉi tiun liston?",
   "confirmations.domain_block.confirm": "Kaŝi la tutan domajnon",
   "confirmations.domain_block.message": "Ĉu vi vere, vere certas, ke vi volas tute bloki {domain}? Plej ofte, trafa blokado kaj silentigado sufiĉas kaj preferindas. Vi ne vidos enhavon de tiu domajno en publika tempolinio aŭ en viaj sciigoj. Viaj sekvantoj de tiu domajno estos forigitaj.",
+  "confirmations.logout.confirm": "Elsaluti",
+  "confirmations.logout.message": "Ĉu vi certas ke vi volas elsaluti?",
   "confirmations.mute.confirm": "Silentigi",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Ĉu vi certas, ke vi volas silentigi {name}?",
   "confirmations.redraft.confirm": "Forigi kaj reskribi",
   "confirmations.redraft.message": "Ĉu vi certas ke vi volas forigi tiun mesaĝon kaj reskribi ĝin? Ĉiuj diskonigoj kaj stelumoj estos perditaj, kaj respondoj al la originala mesaĝo estos senparentaj.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Respondi nun anstataŭigos la mesaĝon, kiun vi nun skribas. Ĉu vi certas, ke vi volas daŭrigi?",
   "confirmations.unfollow.confirm": "Ne plu sekvi",
   "confirmations.unfollow.message": "Ĉu vi certas, ke vi volas ĉesi sekvi {name}?",
+  "conversation.delete": "Forigi konversacion",
+  "conversation.mark_as_read": "Marki legita",
+  "conversation.open": "Vidi konversacion",
+  "conversation.with": "Kun {names}",
+  "directory.federated": "El konata fediverso",
+  "directory.local": "Nur de {domain}",
+  "directory.new_arrivals": "Novaj alvenoj",
+  "directory.recently_active": "Lastatempe aktiva",
   "embed.instructions": "Enkorpigu ĉi tiun mesaĝon en vian retejon per kopio de la suba kodo.",
   "embed.preview": "Äœi aperos tiel:",
   "emoji_button.activity": "Agadoj",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Vi ne ankoraÅ­ silentigis iun uzanton.",
   "empty_column.notifications": "Vi ankoraÅ­ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.",
   "empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj serviloj por plenigi la publikan tempolinion",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Raporti problemon",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -157,7 +179,7 @@
   "hashtag.column_settings.tag_mode.all": "Ĉiuj",
   "hashtag.column_settings.tag_mode.any": "Iu ajn",
   "hashtag.column_settings.tag_mode.none": "Neniu",
-  "hashtag.column_settings.tag_toggle": "Inkluzivi pluajn etikedojn por ĉi tiu kolumno",
+  "hashtag.column_settings.tag_toggle": "Aldoni pliajn etikedojn por ĉi tiu kolumno",
   "home.column_settings.basic": "Bazaj agordoj",
   "home.column_settings.show_reblogs": "Montri diskonigojn",
   "home.column_settings.show_replies": "Montri respondojn",
@@ -165,9 +187,9 @@
   "intervals.full.hours": "{number, plural, one {# horo} other {# horoj}}",
   "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutoj}}",
   "introduction.federation.action": "Sekva",
-  "introduction.federation.federated.headline": "Federacio",
+  "introduction.federation.federated.headline": "Fratara",
   "introduction.federation.federated.text": "Publikaj mesaĝoj el aliaj serviloj de la Fediverse aperos en la fratara tempolinio.",
-  "introduction.federation.home.headline": "Heimo",
+  "introduction.federation.home.headline": "Hejmo",
   "introduction.federation.home.text": "Mesaĝoj de homoj, kiujn vi sekvas, aperos en via hejma fluo. Vi povas sekvi iun ajn de ajna servilo!",
   "introduction.federation.local.headline": "Loka",
   "introduction.federation.local.text": "Publikaj mesaĝoj de homoj de via servilo aperos en la loka tempolinio.",
@@ -216,7 +238,7 @@
   "lightbox.close": "Fermi",
   "lightbox.next": "Sekva",
   "lightbox.previous": "AntaÅ­a",
-  "lightbox.view_context": "Vidi kontekston",
+  "lightbox.view_context": "Vidi kuntekston",
   "lists.account.add": "Aldoni al la listo",
   "lists.account.remove": "Forigi de la listo",
   "lists.delete": "Forigi la liston",
@@ -226,11 +248,12 @@
   "lists.new.title_placeholder": "Titolo de la nova listo",
   "lists.search": "Serĉi inter la homoj, kiujn vi sekvas",
   "lists.subheading": "Viaj listoj",
+  "load_pending": "{count,plural, one {# nova elemento} other {# novaj elementoj}}",
   "loading_indicator.label": "Ŝargado…",
   "media_gallery.toggle_visible": "Baskuligi videblecon",
   "missing_indicator.label": "Ne trovita",
   "missing_indicator.sublabel": "Ĉi tiu elemento ne estis trovita",
-  "mute_modal.hide_notifications": "Ĉu vi volas kaŝi la sciigojn el ĉi tiu uzanto?",
+  "mute_modal.hide_notifications": "Ĉu vi volas kaŝi la sciigojn de ĉi tiu uzanto?",
   "navigation_bar.apps": "Telefonaj aplikaĵoj",
   "navigation_bar.blocks": "Blokitaj uzantoj",
   "navigation_bar.community_timeline": "Loka tempolinio",
@@ -251,13 +274,12 @@
   "navigation_bar.personal": "Persone",
   "navigation_bar.pins": "Alpinglitaj mesaĝoj",
   "navigation_bar.preferences": "Preferoj",
-  "navigation_bar.profile_directory": "Profilujo",
   "navigation_bar.public_timeline": "Fratara tempolinio",
   "navigation_bar.security": "Sekureco",
   "notification.favourite": "{name} stelumis vian mesaĝon",
   "notification.follow": "{name} eksekvis vin",
   "notification.mention": "{name} menciis vin",
-  "notification.poll": "Balotenketo ke vi balotis estas finita",
+  "notification.poll": "Partoprenita balotenketo finiĝis",
   "notification.reblog": "{name} diskonigis vian mesaĝon",
   "notifications.clear": "Forviŝi sciigojn",
   "notifications.clear_confirmation": "Ĉu vi certas, ke vi volas porĉiame forviŝi ĉiujn viajn sciigojn?",
@@ -268,7 +290,7 @@
   "notifications.column_settings.filter_bar.show": "Montri",
   "notifications.column_settings.follow": "Novaj sekvantoj:",
   "notifications.column_settings.mention": "Mencioj:",
-  "notifications.column_settings.poll": "Balotenketo rezulto:",
+  "notifications.column_settings.poll": "Balotenketaj rezultoj:",
   "notifications.column_settings.push": "Puŝsciigoj",
   "notifications.column_settings.reblog": "Diskonigoj:",
   "notifications.column_settings.show": "Montri en kolumno",
@@ -278,12 +300,14 @@
   "notifications.filter.favourites": "Stelumoj",
   "notifications.filter.follows": "Sekvoj",
   "notifications.filter.mentions": "Mencioj",
-  "notifications.filter.polls": "Balotenketoj rezultoj",
+  "notifications.filter.polls": "Balotenketaj rezultoj",
   "notifications.group": "{count} sciigoj",
   "poll.closed": "Finita",
   "poll.refresh": "Aktualigi",
+  "poll.total_people": "{count, plural, one {# homo} other {# homoj}}",
   "poll.total_votes": "{count, plural, one {# voĉdono} other {# voĉdonoj}}",
   "poll.vote": "Voĉdoni",
+  "poll.voted": "Vi elektis por ĉi tiu respondo",
   "poll_button.add_poll": "Aldoni balotenketon",
   "poll_button.remove_poll": "Forigi balotenketon",
   "privacy.change": "Agordi mesaĝan privatecon",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Publika",
   "privacy.unlisted.long": "Ne afiŝi en publikaj tempolinioj",
   "privacy.unlisted.short": "Nelistigita",
+  "refresh": "Refreŝigu",
   "regeneration_indicator.label": "Ŝargado…",
   "regeneration_indicator.sublabel": "Via hejma fluo pretiĝas!",
   "relative_time.days": "{number}t",
@@ -319,11 +344,12 @@
   "search_results.accounts": "Homoj",
   "search_results.hashtags": "Kradvortoj",
   "search_results.statuses": "Mesaĝoj",
+  "search_results.statuses_fts_disabled": "Serĉi mesaĝojn laŭ enhavo ne estas ebligita en ĉi tiu Mastodon-servilo.",
   "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezultoj}}",
   "status.admin_account": "Malfermi la kontrolan interfacon por @{name}",
   "status.admin_status": "Malfermi ĉi tiun mesaĝon en la kontrola interfaco",
   "status.block": "Bloki @{name}",
-  "status.cancel_reblog_private": "Eksdiskonigi",
+  "status.cancel_reblog_private": "Ne plu diskonigi",
   "status.cannot_reblog": "Ĉi tiu mesaĝo ne diskonigeblas",
   "status.copy": "Kopii la ligilon al la mesaĝo",
   "status.delete": "Forigi",
@@ -358,6 +384,7 @@
   "status.show_more": "Grandigi",
   "status.show_more_all": "Grandigi ĉiujn",
   "status.show_thread": "Montri la fadenon",
+  "status.uncached_media_warning": "Nedisponebla",
   "status.unmute_conversation": "Malsilentigi la konversacion",
   "status.unpin": "Depingli de profilo",
   "suggestions.dismiss": "Forigi la proponon",
@@ -367,26 +394,34 @@
   "tabs_bar.local_timeline": "Loka tempolinio",
   "tabs_bar.notifications": "Sciigoj",
   "tabs_bar.search": "Serĉi",
-  "time_remaining.days": "{number, plural, one {# tago} other {# tagoj}} restanta",
-  "time_remaining.hours": "{number, plural, one {# horo} other {# horoj}} restanta",
-  "time_remaining.minutes": "{number, plural, one {# minuto} other {# minutoj}} restanta",
-  "time_remaining.moments": "Momento restanta",
-  "time_remaining.seconds": "{number, plural, one {# sekundo} other {# sekundoj}} restanta",
+  "time_remaining.days": "{number, plural, one {# tago} other {# tagoj}} restas",
+  "time_remaining.hours": "{number, plural, one {# horo} other {# horoj}} restas",
+  "time_remaining.minutes": "{number, plural, one {# minuto} other {# minutoj}} restas",
+  "time_remaining.moments": "Momenteto restas",
+  "time_remaining.seconds": "{number, plural, one {# sekundo} other {# sekundoj}} restas",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {persono} other {personoj}} parolas",
+  "trends.trending_now": "Nunaj furoraĵoj",
   "ui.beforeunload": "Via malneto perdiĝos se vi eliras de Mastodon.",
   "upload_area.title": "Altreni kaj lasi por alŝuti",
   "upload_button.label": "Aldoni aŭdovidaĵon (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Limo de dosiera alŝutado transpasita.",
-  "upload_error.poll": "Alŝuto de dosiero ne permisita kun balotenketo",
+  "upload_error.poll": "Alŝuto de dosiero ne permesita kun balotenketo.",
   "upload_form.description": "Priskribi por misvidantaj homoj",
-  "upload_form.focus": "Antaŭvido de ŝanĝo",
+  "upload_form.edit": "Redakti",
   "upload_form.undo": "Forigi",
+  "upload_modal.analyzing_picture": "Bilda analizado…",
+  "upload_modal.apply": "Apliki",
+  "upload_modal.description_placeholder": "Laŭ Ludoviko Zamenhof bongustas freŝa ĉeĥa manĝaĵo kun spicoj",
+  "upload_modal.detect_text": "Detekti tekston de la bildo",
+  "upload_modal.edit_media": "Redakti aŭdovidaĵon",
+  "upload_modal.hint": "Klaku aŭ trenu la cirklon en la antaŭvidilo por elekti la fokuspunkton kiu ĉiam videblos en ĉiuj etigitaj bildoj.",
+  "upload_modal.preview_label": "AntaÅ­vido ({ratio})",
   "upload_progress.label": "Alŝutado…",
-  "video.close": "Fermi videon",
+  "video.close": "Fermi la videon",
   "video.exit_fullscreen": "Eksigi plenekrana",
-  "video.expand": "Grandigi videon",
+  "video.expand": "Grandigi la videon",
   "video.fullscreen": "Igi plenekrana",
-  "video.hide": "Kaŝi videon",
+  "video.hide": "Kaŝi la videon",
   "video.mute": "Silentigi",
   "video.pause": "PaÅ­zi",
   "video.play": "Ekigi",
diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json
new file mode 100644
index 0000000000000000000000000000000000000000..d46063b182baa6a5c62e27ac16ff199e6c38a20a
--- /dev/null
+++ b/app/javascript/mastodon/locales/es-AR.json
@@ -0,0 +1,423 @@
+{
+  "account.add_or_remove_from_list": "Agregar o quitar de las listas",
+  "account.badges.bot": "Bot",
+  "account.block": "Bloquear a @{name}",
+  "account.block_domain": "Ocultar todo de {domain}",
+  "account.blocked": "Bloqueado",
+  "account.cancel_follow_request": "Cancelar la solicitud de seguimiento",
+  "account.direct": "Mensaje directo a @{name}",
+  "account.domain_blocked": "Dominio oculto",
+  "account.edit_profile": "Editar perfil",
+  "account.endorse": "Destacar en el perfil",
+  "account.follow": "Seguir",
+  "account.followers": "Seguidores",
+  "account.followers.empty": "Todavía nadie sigue a este usuario.",
+  "account.follows": "Sigue",
+  "account.follows.empty": "Todavía este usuario no sigue a nadie.",
+  "account.follows_you": "Te sigue",
+  "account.hide_reblogs": "Ocultar retoots de @{name}",
+  "account.last_status": "Última actividad",
+  "account.link_verified_on": "La propiedad de este enlace fue verificada el {date}",
+  "account.locked_info": "El estado de privacidad de esta cuenta está establecido como bloqueado. El propietario manualmente revisa quién puede seguirle.",
+  "account.media": "Medios",
+  "account.mention": "Mencionar a @{name}",
+  "account.moved_to": "{name} se ha muó a:",
+  "account.mute": "Silenciar a @{name}",
+  "account.mute_notifications": "Silenciar notificaciones de @{name}",
+  "account.muted": "Silenciado",
+  "account.never_active": "Nunca",
+  "account.posts": "Toots",
+  "account.posts_with_replies": "Toots con respuestas",
+  "account.report": "Denunciar a @{name}",
+  "account.requested": "Esperando aprobación. Hacé clic para cancelar la solicitud de seguimiento.",
+  "account.share": "Compartir el perfil de @{name}",
+  "account.show_reblogs": "Mostrar retoots de @{name}",
+  "account.unblock": "Desbloquear a @{name}",
+  "account.unblock_domain": "Mostrar {domain}",
+  "account.unendorse": "No destacar en el perfil",
+  "account.unfollow": "Dejar de seguir",
+  "account.unmute": "Dejar de silenciar a @{name}",
+  "account.unmute_notifications": "Dejar de silenciar las notificaciones de @{name}",
+  "alert.rate_limited.message": "Por favor, reintentá después de las {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Tarifa limitada",
+  "alert.unexpected.message": "Ocurrió un error inesperado.",
+  "alert.unexpected.title": "¡Epa!",
+  "autosuggest_hashtag.per_week": "{count} por semana",
+  "boost_modal.combo": "Podés hacer clic en {combo} para saltar esto la próxima vez",
+  "bundle_column_error.body": "Algo salió mal al cargar este componente.",
+  "bundle_column_error.retry": "Intentá de nuevo",
+  "bundle_column_error.title": "Error de red",
+  "bundle_modal_error.close": "Cerrar",
+  "bundle_modal_error.message": "Algo salió mal al cargar este componente.",
+  "bundle_modal_error.retry": "Intentá de nuevo",
+  "column.blocks": "Usuarios bloqueados",
+  "column.community": "Línea temporal local",
+  "column.direct": "Mensajes directos",
+  "column.directory": "Explorar perfiles",
+  "column.domain_blocks": "Dominios ocultos",
+  "column.favourites": "Favoritos",
+  "column.follow_requests": "Solicitudes de seguimiento",
+  "column.home": "Principal",
+  "column.lists": "Listas",
+  "column.mutes": "Usuarios silenciados",
+  "column.notifications": "Notificaciones",
+  "column.pins": "Toots fijados",
+  "column.public": "Línea temporal federada",
+  "column_back_button.label": "Volver",
+  "column_header.hide_settings": "Ocultar configuración",
+  "column_header.moveLeft_settings": "Mover columna a la izquierda",
+  "column_header.moveRight_settings": "Mover columna a la derecha",
+  "column_header.pin": "Fijar",
+  "column_header.show_settings": "Mostrar configuración",
+  "column_header.unpin": "Dejar de fijar",
+  "column_subheading.settings": "Configuración",
+  "community.column_settings.media_only": "Sólo medios",
+  "compose_form.direct_message_warning": "Este toot sólo será enviado a los usuarios mencionados.",
+  "compose_form.direct_message_warning_learn_more": "Aprendé más",
+  "compose_form.hashtag_warning": "Este toot no se mostrará bajo hashtags porque no es público. Sólo los toots públicos se pueden buscar por hashtag.",
+  "compose_form.lock_disclaimer": "Tu cuenta no está {locked}. Todos pueden seguirte para ver tus toots marcados como \"sólo para seguidores\".",
+  "compose_form.lock_disclaimer.lock": "bloqueada",
+  "compose_form.placeholder": "¿Qué onda?",
+  "compose_form.poll.add_option": "Agregá una opción",
+  "compose_form.poll.duration": "Duración de la encuesta",
+  "compose_form.poll.option_placeholder": "Opción {number}",
+  "compose_form.poll.remove_option": "Quitá esta opción",
+  "compose_form.publish": "Tootear",
+  "compose_form.publish_loud": "¡{publish}!",
+  "compose_form.sensitive.hide": "Marcar medio como sensible",
+  "compose_form.sensitive.marked": "El medio se marcó como sensible",
+  "compose_form.sensitive.unmarked": "El medio no está marcado como sensible",
+  "compose_form.spoiler.marked": "El texto está oculto detrás de la advertencia",
+  "compose_form.spoiler.unmarked": "El texto no está oculto",
+  "compose_form.spoiler_placeholder": "Escribí tu advertencia acá",
+  "confirmation_modal.cancel": "Cancelar",
+  "confirmations.block.block_and_report": "Bloquear y denunciar",
+  "confirmations.block.confirm": "Bloquear",
+  "confirmations.block.message": "¿Estás seguro que querés bloquear a {name}?",
+  "confirmations.delete.confirm": "Eliminar",
+  "confirmations.delete.message": "¿Estás seguro que querés eliminar este estado?",
+  "confirmations.delete_list.confirm": "Eliminar",
+  "confirmations.delete_list.message": "¿Estás seguro que querés eliminar permanentemente esta lista?",
+  "confirmations.domain_block.confirm": "Ocultar dominio entero",
+  "confirmations.domain_block.message": "¿Estás completamente seguro que querés bloquear el {domain} entero? En la mayoría de los casos, unos cuantos bloqueos y silenciados puntuales son suficientes y preferibles. No vas a ver contenido de ese dominio en ninguna de tus líneas temporales o en tus notificaciones. Tus seguidores de ese dominio serán quitados.",
+  "confirmations.logout.confirm": "Cerrar sesión",
+  "confirmations.logout.message": "¿Estás seguro que querés cerrar la sesión?",
+  "confirmations.mute.confirm": "Silenciar",
+  "confirmations.mute.explanation": "Esto ocultará mensajes de ellos y mensajes que los mencionen, pero todavía les permitirá ver tus mensajes o seguirte.",
+  "confirmations.mute.message": "¿Estás seguro que querés silenciar a {name}?",
+  "confirmations.redraft.confirm": "Eliminar toot original y editarlo",
+  "confirmations.redraft.message": "¿Estás seguro que querés eliminar este estado y volver a editarlo? Se perderán las veces marcadas como favoritos y los retoots, y las respuestas a la publicación original quedarán huérfanas.",
+  "confirmations.reply.confirm": "Responder",
+  "confirmations.reply.message": "Responder ahora sobreescribirá el mensaje que estás redactando actualmente. ¿Estás seguro que querés seguir?",
+  "confirmations.unfollow.confirm": "Dejar de seguir",
+  "confirmations.unfollow.message": "¿Estás seguro que querés dejar de seguir a {name}?",
+  "conversation.delete": "Eliminar conversación",
+  "conversation.mark_as_read": "Marcar como leído",
+  "conversation.open": "Ver conversación",
+  "conversation.with": "Con {names}",
+  "directory.federated": "Desde fediverso conocido",
+  "directory.local": "Sólo de {domain}",
+  "directory.new_arrivals": "Recién llegados",
+  "directory.recently_active": "Recientemente activo",
+  "embed.instructions": "Insertá este toot a tu sitio web copiando el código de abajo.",
+  "embed.preview": "Así es cómo se verá:",
+  "emoji_button.activity": "Actividad",
+  "emoji_button.custom": "Personalizado",
+  "emoji_button.flags": "Banderas",
+  "emoji_button.food": "Comida y bebida",
+  "emoji_button.label": "Insertar emoji",
+  "emoji_button.nature": "Naturaleza",
+  "emoji_button.not_found": "¡¡No emojos!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Objetos",
+  "emoji_button.people": "Gente",
+  "emoji_button.recent": "Usados frecuentemente",
+  "emoji_button.search": "Buscar…",
+  "emoji_button.search_results": "Resultados de búsqueda",
+  "emoji_button.symbols": "Símbolos",
+  "emoji_button.travel": "Viajes y lugares",
+  "empty_column.account_timeline": "¡No hay toots aquí!",
+  "empty_column.account_unavailable": "Perfil no disponible",
+  "empty_column.blocks": "Todavía no bloqueaste a ningún usuario.",
+  "empty_column.community": "La línea temporal local está vacía. ¡Escribí algo en modo público para que se empiece a correr la bola!",
+  "empty_column.direct": "Todavía no tenés ningún mensaje directo. Cuando enviés o recibás uno, se mostrará acá.",
+  "empty_column.domain_blocks": "Todavía no hay dominios ocultos.",
+  "empty_column.favourited_statuses": "Todavía no tenés toots favoritos. Cuando marqués uno como favorito, se mostrará acá.",
+  "empty_column.favourites": "Todavía nadie marcó este toot como favorito. Cuando alguien lo haga, se mostrará acá.",
+  "empty_column.follow_requests": "Todavía no tenés ninguna solicitud de seguimiento. Cuando recibás una, se mostrará acá.",
+  "empty_column.hashtag": "Todavía no hay nada con esta etiqueta.",
+  "empty_column.home": "¡Tu línea temporal principal está vacía! Visitá {public} o usá la búsqueda para comenzar y encontrar a otros usuarios.",
+  "empty_column.home.public_timeline": "la línea temporal pública",
+  "empty_column.list": "Todavía no hay nada en esta lista. Cuando miembros de esta lista envíen nuevos toots, se mostrarán acá.",
+  "empty_column.lists": "Todavía no tienes ninguna lista. Cuando creés una, se mostrará acá.",
+  "empty_column.mutes": "Todavía no silenciaste a ningún usuario.",
+  "empty_column.notifications": "Todavía no tenés ninguna notificación. Interactuá con otros para iniciar la conversación.",
+  "empty_column.public": "¡Naranja! Escribí algo públicamente, o seguí usuarios manualmente de otros servidores para ir llenando esta línea temporal.",
+  "error.unexpected_crash.explanation": "Debido a un error en nuestro código o a un problema de compatibilidad con el navegador web, esta página no se pudo mostrar correctamente.",
+  "error.unexpected_crash.next_steps": "Intentá recargar la página. Si eso no ayuda, podés usar Mastodon a través de un navegador web diferente o aplicación nativa.",
+  "errors.unexpected_crash.copy_stacktrace": "Copiar stacktrace al portapapeles",
+  "errors.unexpected_crash.report_issue": "Informar problema",
+  "follow_request.authorize": "Autorizar",
+  "follow_request.reject": "Rechazar",
+  "getting_started.developers": "Desarrolladores",
+  "getting_started.directory": "Directorio de perfiles",
+  "getting_started.documentation": "Documentación",
+  "getting_started.heading": "Introducción",
+  "getting_started.invite": "Invitar usuarios",
+  "getting_started.open_source_notice": "Mastodon es software libre. Podés contribuir o informar errores en {github}.",
+  "getting_started.security": "Seguridad",
+  "getting_started.terms": "Términos del servicio",
+  "hashtag.column_header.tag_mode.all": "y {additional}",
+  "hashtag.column_header.tag_mode.any": "o {additional}",
+  "hashtag.column_header.tag_mode.none": "sin {additional}",
+  "hashtag.column_settings.select.no_options_message": "No se encontraron sugerencias",
+  "hashtag.column_settings.select.placeholder": "Introducí etiquetas…",
+  "hashtag.column_settings.tag_mode.all": "Todas estas",
+  "hashtag.column_settings.tag_mode.any": "Cualquiera de estas",
+  "hashtag.column_settings.tag_mode.none": "Ninguna de estas",
+  "hashtag.column_settings.tag_toggle": "Incluir etiquetas adicionales para esta columna",
+  "home.column_settings.basic": "Básico",
+  "home.column_settings.show_reblogs": "Mostrar retoots",
+  "home.column_settings.show_replies": "Mostrar respuestas",
+  "intervals.full.days": "{number, plural, one {# día} other {# días}}",
+  "intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
+  "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}",
+  "introduction.federation.action": "Siguiente",
+  "introduction.federation.federated.headline": "Federado",
+  "introduction.federation.federated.text": "Los toots públicos de otros servidores del fediverso aparecerán en la línea temporal federada.",
+  "introduction.federation.home.headline": "Principal",
+  "introduction.federation.home.text": "Los toots de las personas que seguíss aparecerán en tu línea temporal principal. ¡Podés seguir a cualquiera en cualquier servidor!",
+  "introduction.federation.local.headline": "Local",
+  "introduction.federation.local.text": "Los toots públicos de las personas en el mismo servidor aparecerán en la línea temporal local.",
+  "introduction.interactions.action": "¡Terminar tutorial!",
+  "introduction.interactions.favourite.headline": "Favorito",
+  "introduction.interactions.favourite.text": "Podés guardar un toot para más tarde, y hacerle saber al autor que te gustó, marcándolo como favorito.",
+  "introduction.interactions.reblog.headline": "Retootear",
+  "introduction.interactions.reblog.text": "Podés compartir los toots de otras personas con tus seguidores retooteando los mismos.",
+  "introduction.interactions.reply.headline": "Responder",
+  "introduction.interactions.reply.text": "Podés responder a tus propios toots y los de otras personas, que se encadenarán juntos en una conversación.",
+  "introduction.welcome.action": "¡Dale!",
+  "introduction.welcome.headline": "Primeros pasos",
+  "introduction.welcome.text": "¡Bienvenido al fediverso! En unos pocos minutos, vas a poder transmitir mensajes y hablar con tus amigos a través de una amplia variedad de servidores. Pero este servidor, {domain}, es especial: aloja tu perfil, así que acordate de su nombre.",
+  "keyboard_shortcuts.back": "para volver",
+  "keyboard_shortcuts.blocked": "para abrir la lista de usuarios bloqueados",
+  "keyboard_shortcuts.boost": "para retootear",
+  "keyboard_shortcuts.column": "para enfocar un estado en una de las columnas",
+  "keyboard_shortcuts.compose": "para enfocar el área de texto de redacción",
+  "keyboard_shortcuts.description": "Descripción",
+  "keyboard_shortcuts.direct": "para abrir columna de mensajes directos",
+  "keyboard_shortcuts.down": "para bajar en la lista",
+  "keyboard_shortcuts.enter": "para abrir el estado",
+  "keyboard_shortcuts.favourite": "para marcar como favorito",
+  "keyboard_shortcuts.favourites": "para abrir la lista de favoritos",
+  "keyboard_shortcuts.federated": "para abrir la línea temporal federada",
+  "keyboard_shortcuts.heading": "Atajos de teclado",
+  "keyboard_shortcuts.home": "para abrir la línea temporal principal",
+  "keyboard_shortcuts.hotkey": "Combinación",
+  "keyboard_shortcuts.legend": "para mostrar este texto",
+  "keyboard_shortcuts.local": "para abrir la línea temporal local",
+  "keyboard_shortcuts.mention": "para mencionar al autor",
+  "keyboard_shortcuts.muted": "abrir la lista de usuarios silenciados",
+  "keyboard_shortcuts.my_profile": "para abrir tu perfil",
+  "keyboard_shortcuts.notifications": "para abrir la columna de notificaciones",
+  "keyboard_shortcuts.pinned": "para abrir lista de toots fijados",
+  "keyboard_shortcuts.profile": "para abrir el perfil del autor",
+  "keyboard_shortcuts.reply": "para responder",
+  "keyboard_shortcuts.requests": "para abrir la lista de solicitudes de seguimiento",
+  "keyboard_shortcuts.search": "para enfocar la búsqueda",
+  "keyboard_shortcuts.start": "para abrir la columna \"Introducción\"",
+  "keyboard_shortcuts.toggle_hidden": "para mostrar/ocultar el texto detrás de la advertencia de contenido",
+  "keyboard_shortcuts.toggle_sensitivity": "para mostrar/ocultar los medios",
+  "keyboard_shortcuts.toot": "para comenzar un toot nuevo",
+  "keyboard_shortcuts.unfocus": "para quitar el enfoque del área de texto de redacción o de búsqueda",
+  "keyboard_shortcuts.up": "para subir en la lista",
+  "lightbox.close": "Cerrar",
+  "lightbox.next": "Siguiente",
+  "lightbox.previous": "Anterior",
+  "lightbox.view_context": "Ver contexto",
+  "lists.account.add": "Agregar a lista",
+  "lists.account.remove": "Quitar de lista",
+  "lists.delete": "Eliminar lista",
+  "lists.edit": "Editar lista",
+  "lists.edit.submit": "Cambiar título",
+  "lists.new.create": "Agregar lista",
+  "lists.new.title_placeholder": "Nuevo título de lista",
+  "lists.search": "Buscar entre la gente que seguís",
+  "lists.subheading": "Tus listas",
+  "load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
+  "loading_indicator.label": "Cargando…",
+  "media_gallery.toggle_visible": "Cambiar visibilidad",
+  "missing_indicator.label": "No se encontró",
+  "missing_indicator.sublabel": "No se encontró este recurso",
+  "mute_modal.hide_notifications": "¿Querés ocultar las notificaciones de este usuario?",
+  "navigation_bar.apps": "Aplicaciones móviles",
+  "navigation_bar.blocks": "Usuarios bloqueados",
+  "navigation_bar.community_timeline": "Línea temporal local",
+  "navigation_bar.compose": "Redactar un nuevo toot",
+  "navigation_bar.direct": "Mensajes directos",
+  "navigation_bar.discover": "Descubrir",
+  "navigation_bar.domain_blocks": "Dominios ocultos",
+  "navigation_bar.edit_profile": "Editar perfil",
+  "navigation_bar.favourites": "Favoritos",
+  "navigation_bar.filters": "Palabras silenciadas",
+  "navigation_bar.follow_requests": "Solicitudes de seguimiento",
+  "navigation_bar.follows_and_followers": "Personas seguidas y seguidores",
+  "navigation_bar.info": "Acerca de este servidor",
+  "navigation_bar.keyboard_shortcuts": "Atajos",
+  "navigation_bar.lists": "Listas",
+  "navigation_bar.logout": "Cerrar sesión",
+  "navigation_bar.mutes": "Usuarios silenciados",
+  "navigation_bar.personal": "Personal",
+  "navigation_bar.pins": "Toots fijados",
+  "navigation_bar.preferences": "Configuración",
+  "navigation_bar.public_timeline": "Línea temporal federada",
+  "navigation_bar.security": "Seguridad",
+  "notification.favourite": "{name} marcó tu estado como favorito",
+  "notification.follow": "{name} te empezó a seguir",
+  "notification.mention": "{name} te mencionó",
+  "notification.poll": "Finalizó una encuesta en la que votaste",
+  "notification.reblog": "{name} retooteó tu estado",
+  "notifications.clear": "Limpiar notificaciones",
+  "notifications.clear_confirmation": "¿Estás seguro que querés limpiar todas tus notificaciones permanentemente?",
+  "notifications.column_settings.alert": "Notificaciones de escritorio",
+  "notifications.column_settings.favourite": "Favoritos:",
+  "notifications.column_settings.filter_bar.advanced": "Mostrar todas las categorías",
+  "notifications.column_settings.filter_bar.category": "Barra de filtrado rápido",
+  "notifications.column_settings.filter_bar.show": "Mostrar",
+  "notifications.column_settings.follow": "Nuevos seguidores:",
+  "notifications.column_settings.mention": "Menciones:",
+  "notifications.column_settings.poll": "Resultados de la encuesta:",
+  "notifications.column_settings.push": "Notificaciones push",
+  "notifications.column_settings.reblog": "Retoots:",
+  "notifications.column_settings.show": "Mostrar en columna",
+  "notifications.column_settings.sound": "Reproducir sonido",
+  "notifications.filter.all": "Todas",
+  "notifications.filter.boosts": "Retoots",
+  "notifications.filter.favourites": "Favoritos",
+  "notifications.filter.follows": "Seguidores",
+  "notifications.filter.mentions": "Menciones",
+  "notifications.filter.polls": "Resultados de la encuesta",
+  "notifications.group": "{count} notificaciones",
+  "poll.closed": "Cerrada",
+  "poll.refresh": "Refrescar",
+  "poll.total_people": "{count, plural, one {# persona} other {# personas}}",
+  "poll.total_votes": "{count, plural, one {# voto} other {# votos}}",
+  "poll.vote": "Votar",
+  "poll.voted": "Votaste esta opción",
+  "poll_button.add_poll": "Agregar una encuesta",
+  "poll_button.remove_poll": "Quitar encuesta",
+  "privacy.change": "Configurar privacidad de estado",
+  "privacy.direct.long": "Enviar toot sólo a los usuarios mencionados",
+  "privacy.direct.short": "Directo",
+  "privacy.private.long": "Enviar toot sólo a los seguidores",
+  "privacy.private.short": "Sólo a seguidores",
+  "privacy.public.long": "Enviar toot a las líneas temporales públicas",
+  "privacy.public.short": "Público",
+  "privacy.unlisted.long": "No enviar toot a las líneas temporales públicas",
+  "privacy.unlisted.short": "No listado",
+  "refresh": "Refrescar",
+  "regeneration_indicator.label": "Cargando…",
+  "regeneration_indicator.sublabel": "¡Se está preparando tu línea temporal principal!",
+  "relative_time.days": "{number}d",
+  "relative_time.hours": "{number}h",
+  "relative_time.just_now": "recién",
+  "relative_time.minutes": "{number}m",
+  "relative_time.seconds": "{number}s",
+  "reply_indicator.cancel": "Cancelar",
+  "report.forward": "Reenviar a {target}",
+  "report.forward_hint": "La cuenta es de otro servidor. ¿Querés enviar una copia anonimizada del informe también ahí?",
+  "report.hint": "La denuncia se enviará a los moderadores de tu servidor. Podés proporcionar una explicación de por qué estás denunciando esta cuenta, a continuación:",
+  "report.placeholder": "Comentarios adicionales",
+  "report.submit": "Enviar",
+  "report.target": "Denunciando a {target}",
+  "search.placeholder": "Buscar",
+  "search_popout.search_format": "Formato de búsqueda avanzada",
+  "search_popout.tips.full_text": "Las búsquedas de texto simple devuelven los estados que escribiste, los marcados como favoritos, los retooteados o en los que te mencionaron, así como nombres usuarios, nombres mostrados y etiquetas.",
+  "search_popout.tips.hashtag": "etiqueta",
+  "search_popout.tips.status": "estado",
+  "search_popout.tips.text": "Las búsquedas de texto simple devuelven nombres de usuarios, nombres mostrados y etiquetas que coincidan",
+  "search_popout.tips.user": "usuario",
+  "search_results.accounts": "Gente",
+  "search_results.hashtags": "Etiquetas",
+  "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "No se puede buscar toots por contenido en este servidor de Mastodon.",
+  "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
+  "status.admin_account": "Abrir interface de moderación para @{name}",
+  "status.admin_status": "Abrir este estado en la interface de moderación",
+  "status.block": "Bloquear a @{name}",
+  "status.cancel_reblog_private": "Quitar retoot",
+  "status.cannot_reblog": "No se puede retootear este toot",
+  "status.copy": "Copiar enlace al estado",
+  "status.delete": "Eliminar",
+  "status.detailed_status": "Vista de conversación detallada",
+  "status.direct": "Mensaje directo a @{name}",
+  "status.embed": "Insertar",
+  "status.favourite": "Favorito",
+  "status.filtered": "Filtrado",
+  "status.load_more": "Cargar más",
+  "status.media_hidden": "Medios ocultos",
+  "status.mention": "Mencionar a @{name}",
+  "status.more": "Más",
+  "status.mute": "Silenciar a @{name}",
+  "status.mute_conversation": "Silenciar conversación",
+  "status.open": "Expandir este estado",
+  "status.pin": "Pin en el perfil",
+  "status.pinned": "Toot fijado",
+  "status.read_more": "Leer más",
+  "status.reblog": "Retootear",
+  "status.reblog_private": "Retootear a la audiencia original",
+  "status.reblogged_by": "Retooteado por {name}",
+  "status.reblogs.empty": "Todavía nadie retooteó este toot. Cuando alguien lo haga, se mostrará acá.",
+  "status.redraft": "Eliminar toot original y editarlo",
+  "status.reply": "Responder",
+  "status.replyAll": "Responder al hilo",
+  "status.report": "Denunciar a @{name}",
+  "status.sensitive_warning": "Contenido sensible",
+  "status.share": "Compartir",
+  "status.show_less": "Mostrar menos",
+  "status.show_less_all": "Mostrar menos para todo",
+  "status.show_more": "Mostrar más",
+  "status.show_more_all": "Mostrar más para todo",
+  "status.show_thread": "Mostrar hilo",
+  "status.uncached_media_warning": "No disponible",
+  "status.unmute_conversation": "Dejar de silenciar conversación",
+  "status.unpin": "Desmarcar del perfil",
+  "suggestions.dismiss": "Descartar sugerencia",
+  "suggestions.header": "Es posible que te interese…",
+  "tabs_bar.federated_timeline": "Federado",
+  "tabs_bar.home": "Principal",
+  "tabs_bar.local_timeline": "Local",
+  "tabs_bar.notifications": "Notificaciones",
+  "tabs_bar.search": "Buscar",
+  "time_remaining.days": "{number, plural,one {queda # día} other {quedan # días}}",
+  "time_remaining.hours": "{number, plural,one {queda # hora} other {quedan # horas}}",
+  "time_remaining.minutes": "{number, plural,one {queda # minuto} other {quedan # minutos}}",
+  "time_remaining.moments": "Momentos restantes",
+  "time_remaining.seconds": "{number, plural,one {queda # segundo} other {quedan # segundos}}",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {persona} other {personas}} hablando",
+  "trends.trending_now": "Tendencia ahora",
+  "ui.beforeunload": "Tu borrador se perderá si abandonás Mastodon.",
+  "upload_area.title": "Para subir, arrastrá y soltá",
+  "upload_button.label": "Agregar medios ({formats})",
+  "upload_error.limit": "Se excedió el límite de subida de archivos.",
+  "upload_error.poll": "No se permite la subida de archivos en encuestas.",
+  "upload_form.description": "Agregar descripción para los usuarios con dificultades visuales",
+  "upload_form.edit": "Editar",
+  "upload_form.undo": "Eliminar",
+  "upload_modal.analyzing_picture": "Analizando imagen…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.description_placeholder": "El veloz murciélago hindú comía feliz cardillo y kiwi. La cigüeña tocaba el saxofón detrás del palenque de paja.",
+  "upload_modal.detect_text": "Detectar texto de la imagen",
+  "upload_modal.edit_media": "Editar medio",
+  "upload_modal.hint": "Hacé clic o arrastrá el círculo en la previsualización para elegir el punto focal que siempre estará a la vista en todas las miniaturas.",
+  "upload_modal.preview_label": "Previsualización ({ratio})",
+  "upload_progress.label": "Subiendo…",
+  "video.close": "Cerrar video",
+  "video.exit_fullscreen": "Salir de pantalla completa",
+  "video.expand": "Expandir vídeo",
+  "video.fullscreen": "Pantalla completa",
+  "video.hide": "Ocultar video",
+  "video.mute": "Silenciar sonido",
+  "video.pause": "Pausar",
+  "video.play": "Reproducir",
+  "video.unmute": "Dejar de silenciar sonido"
+}
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index 0f9d45bd66b8cda6bc7dae9a264df2481cd50947..d964716faf3f63731579952c3d18a1270fdc8e64 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -1,21 +1,23 @@
 {
-  "account.add_or_remove_from_list": "Agregar o eliminar de las listas",
+  "account.add_or_remove_from_list": "Agregar o eliminar de listas",
   "account.badges.bot": "Bot",
-  "account.block": "Bloquear",
+  "account.block": "Bloquear a @{name}",
   "account.block_domain": "Ocultar todo de {domain}",
   "account.blocked": "Bloqueado",
-  "account.direct": "Direct Message @{name}",
+  "account.cancel_follow_request": "Cancelar la solicitud de seguimiento",
+  "account.direct": "Mensaje directo a @{name}",
   "account.domain_blocked": "Dominio oculto",
   "account.edit_profile": "Editar perfil",
   "account.endorse": "Mostrar en perfil",
   "account.follow": "Seguir",
   "account.followers": "Seguidores",
-  "account.followers.empty": "Nadie sigue a este usuario todavía.",
+  "account.followers.empty": "Todavía nadie sigue a este usuario.",
   "account.follows": "Sigue",
   "account.follows.empty": "Este usuario todavía no sigue a nadie.",
   "account.follows_you": "Te sigue",
   "account.hide_reblogs": "Ocultar retoots de @{name}",
-  "account.link_verified_on": "El proprietario de este link fue verificado el {date}",
+  "account.last_status": "Última actividad",
+  "account.link_verified_on": "El proprietario de este link fue comprobado el {date}",
   "account.locked_info": "El estado de privacidad de esta cuenta està configurado como bloqueado. El proprietario debe revisar manualmente quien puede seguirle.",
   "account.media": "Multimedia",
   "account.mention": "Mencionar a @{name}",
@@ -23,6 +25,7 @@
   "account.mute": "Silenciar a @{name}",
   "account.mute_notifications": "Silenciar notificaciones de @{name}",
   "account.muted": "Silenciado",
+  "account.never_active": "Nunca",
   "account.posts": "Toots",
   "account.posts_with_replies": "Toots con respuestas",
   "account.report": "Reportar a @{name}",
@@ -35,9 +38,12 @@
   "account.unfollow": "Dejar de seguir",
   "account.unmute": "Dejar de silenciar a @{name}",
   "account.unmute_notifications": "Dejar de silenciar las notificaciones de @{name}",
+  "alert.rate_limited.message": "Por favor reintente después de {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Tarifa limitada",
   "alert.unexpected.message": "Hubo un error inesperado.",
   "alert.unexpected.title": "¡Ups!",
-  "boost_modal.combo": "Puedes presionar {combo} para saltear este aviso la próxima vez",
+  "autosuggest_hashtag.per_week": "{count} por semana",
+  "boost_modal.combo": "Puedes hacer clic en {combo} para saltar este aviso la próxima vez",
   "bundle_column_error.body": "Algo salió mal al cargar este componente.",
   "bundle_column_error.retry": "Inténtalo de nuevo",
   "bundle_column_error.title": "Error de red",
@@ -47,17 +53,18 @@
   "column.blocks": "Usuarios bloqueados",
   "column.community": "Línea de tiempo local",
   "column.direct": "Mensajes directos",
-  "column.domain_blocks": "Dominios ocultos",
+  "column.directory": "Buscar perfiles",
+  "column.domain_blocks": "Dominios ocultados",
   "column.favourites": "Favoritos",
   "column.follow_requests": "Solicitudes de seguimiento",
   "column.home": "Inicio",
   "column.lists": "Listas",
   "column.mutes": "Usuarios silenciados",
   "column.notifications": "Notificaciones",
-  "column.pins": "Toot fijado",
-  "column.public": "Historia federada",
+  "column.pins": "Toots fijados",
+  "column.public": "Línea de tiempo federada",
   "column_back_button.label": "Atrás",
-  "column_header.hide_settings": "Ocultar ajustes",
+  "column_header.hide_settings": "Ocultar configuración",
   "column_header.moveLeft_settings": "Mover columna a la izquierda",
   "column_header.moveRight_settings": "Mover columna a la derecha",
   "column_header.pin": "Fijar",
@@ -93,14 +100,25 @@
   "confirmations.delete_list.message": "¿Seguro que quieres borrar esta lista permanentemente?",
   "confirmations.domain_block.confirm": "Ocultar dominio entero",
   "confirmations.domain_block.message": "¿Seguro de que quieres bloquear al dominio {domain} entero? En general unos cuantos bloqueos y silenciados concretos es suficiente y preferible.",
+  "confirmations.logout.confirm": "Cerrar sesión",
+  "confirmations.logout.message": "¿Estás seguro de querer cerrar la sesión?",
   "confirmations.mute.confirm": "Silenciar",
+  "confirmations.mute.explanation": "Esto esconderá las publicaciones de ellos y en las que los has mencionado, pero les permitirá ver tus mensajes y seguirte.",
   "confirmations.mute.message": "¿Estás seguro de que quieres silenciar a {name}?",
   "confirmations.redraft.confirm": "Borrar y volver a borrador",
   "confirmations.redraft.message": "Estás seguro de que quieres borrar este estado y volverlo a borrador? Perderás todas las respuestas, impulsos y favoritos asociados a él, y las respuestas a la publicación original quedarán huérfanos.",
   "confirmations.reply.confirm": "Responder",
-  "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+  "confirmations.reply.message": "Responder sobrescribirá el mensaje que estás escribiendo. ¿Estás seguro de que deseas continuar?",
   "confirmations.unfollow.confirm": "Dejar de seguir",
   "confirmations.unfollow.message": "¿Estás seguro de que quieres dejar de seguir a {name}?",
+  "conversation.delete": "Borrar conversación",
+  "conversation.mark_as_read": "Marcar como leído",
+  "conversation.open": "Ver conversación",
+  "conversation.with": "Con {names}",
+  "directory.federated": "Desde el fediverso conocido",
+  "directory.local": "Sólo de {domain}",
+  "directory.new_arrivals": "Recién llegados",
+  "directory.recently_active": "Recientemente activo",
   "embed.instructions": "Añade este toot a tu sitio web con el siguiente código.",
   "embed.preview": "Así es como se verá:",
   "emoji_button.activity": "Actividad",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Aún no has silenciado a ningún usuario.",
   "empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.",
   "empty_column.public": "¡No hay nada aquí! Escribe algo públicamente, o sigue usuarios de otras instancias manualmente para llenarlo",
+  "error.unexpected_crash.explanation": "Debido a un error en nuestro código o a un problema de compatibilidad con el navegador, esta página no se ha podido mostrar correctamente.",
+  "error.unexpected_crash.next_steps": "Intenta actualizar la página. Si eso no ayuda, es posible que puedas usar Mastodon a través de otro navegador o aplicación nativa.",
+  "errors.unexpected_crash.copy_stacktrace": "Copiar el seguimiento de pila en el portapapeles",
+  "errors.unexpected_crash.report_issue": "Informar de un problema/error",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -154,33 +176,33 @@
   "hashtag.column_header.tag_mode.none": "sin {additional}",
   "hashtag.column_settings.select.no_options_message": "No se encontraron sugerencias",
   "hashtag.column_settings.select.placeholder": "Introduzca hashtags…",
-  "hashtag.column_settings.tag_mode.all": "All of these",
+  "hashtag.column_settings.tag_mode.all": "Cualquiera de estos",
   "hashtag.column_settings.tag_mode.any": "Cualquiera de estos",
   "hashtag.column_settings.tag_mode.none": "Ninguno de estos",
   "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
   "home.column_settings.basic": "Básico",
   "home.column_settings.show_reblogs": "Mostrar retoots",
   "home.column_settings.show_replies": "Mostrar respuestas",
-  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
-  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
-  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "intervals.full.days": "{number, plural, one {# día} other {# días}}",
+  "intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
+  "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}",
   "introduction.federation.action": "Siguiente",
   "introduction.federation.federated.headline": "Federado",
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
+  "introduction.federation.federated.text": "Los mensajes públicos de otros servidores del fediverso aparecerán en la cronología federada.",
   "introduction.federation.home.headline": "Inicio",
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
+  "introduction.federation.home.text": "Los posts de personas que sigues aparecerán en tu cronología. ¡Puedes seguir a cualquiera en cualquier servidor!",
   "introduction.federation.local.headline": "Local",
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
+  "introduction.federation.local.text": "Los posts públicos de personas en el mismo servidor que aparecerán en la cronología local.",
   "introduction.interactions.action": "¡Terminar tutorial!",
   "introduction.interactions.favourite.headline": "Favorito",
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
-  "introduction.interactions.reblog.headline": "Boost",
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
+  "introduction.interactions.favourite.text": "Puedes guardar un toot para más tarde, y hacer saber al autor que te gustó, dándole a favorito.",
+  "introduction.interactions.reblog.headline": "Retootear",
+  "introduction.interactions.reblog.text": "Puedes compartir los toots de otras personas con tus seguidores retooteando los mismos.",
   "introduction.interactions.reply.headline": "Responder",
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
+  "introduction.interactions.reply.text": "Puedes responder a tus propios toots y los de otras personas, que se encadenarán juntos en una conversación.",
   "introduction.welcome.action": "¡Vamos!",
   "introduction.welcome.headline": "Primeros pasos",
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
+  "introduction.welcome.text": "¡Bienvenido al fediverso! En unos momentos, podrás transmitir mensajes y hablar con tus amigos a través de una amplia variedad de servidores. Pero este servidor, {domain}, es especial, alberga tu perfil, así que recuerda su nombre.",
   "keyboard_shortcuts.back": "volver atrás",
   "keyboard_shortcuts.blocked": "abrir una lista de usuarios bloqueados",
   "keyboard_shortcuts.boost": "retootear",
@@ -189,7 +211,7 @@
   "keyboard_shortcuts.description": "Descripción",
   "keyboard_shortcuts.direct": "abrir la columna de mensajes directos",
   "keyboard_shortcuts.down": "mover hacia abajo en la lista",
-  "keyboard_shortcuts.enter": "to open status",
+  "keyboard_shortcuts.enter": "abrir estado",
   "keyboard_shortcuts.favourite": "añadir a favoritos",
   "keyboard_shortcuts.favourites": "abrir la lista de favoritos",
   "keyboard_shortcuts.federated": "abrir el timeline federado",
@@ -209,7 +231,7 @@
   "keyboard_shortcuts.search": "para poner el foco en la búsqueda",
   "keyboard_shortcuts.start": "abrir la columna \"comenzar\"",
   "keyboard_shortcuts.toggle_hidden": "mostrar/ocultar texto tras aviso de contenido (CW)",
-  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.toggle_sensitivity": "mostrar/ocultar medios",
   "keyboard_shortcuts.toot": "para comenzar un nuevo toot",
   "keyboard_shortcuts.unfocus": "para retirar el foco de la caja de redacción/búsqueda",
   "keyboard_shortcuts.up": "para ir hacia arriba en la lista",
@@ -221,11 +243,12 @@
   "lists.account.remove": "Quitar de lista",
   "lists.delete": "Borrar lista",
   "lists.edit": "Editar lista",
-  "lists.edit.submit": "Change title",
+  "lists.edit.submit": "Cambiar título",
   "lists.new.create": "Añadir lista",
   "lists.new.title_placeholder": "Título de la nueva lista",
   "lists.search": "Buscar entre la gente a la que sigues",
   "lists.subheading": "Tus listas",
+  "load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
   "loading_indicator.label": "Cargando…",
   "media_gallery.toggle_visible": "Cambiar visibilidad",
   "missing_indicator.label": "No encontrado",
@@ -242,7 +265,7 @@
   "navigation_bar.favourites": "Favoritos",
   "navigation_bar.filters": "Palabras silenciadas",
   "navigation_bar.follow_requests": "Solicitudes para seguirte",
-  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.follows_and_followers": "Siguiendo y seguidores",
   "navigation_bar.info": "Información adicional",
   "navigation_bar.keyboard_shortcuts": "Atajos",
   "navigation_bar.lists": "Listas",
@@ -251,41 +274,42 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Toots fijados",
   "navigation_bar.preferences": "Preferencias",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Historia federada",
   "navigation_bar.security": "Seguridad",
   "notification.favourite": "{name} marcó tu estado como favorito",
   "notification.follow": "{name} te empezó a seguir",
   "notification.mention": "{name} te ha mencionado",
-  "notification.poll": "A poll you have voted in has ended",
+  "notification.poll": "Una encuesta en la que has votado ha terminado",
   "notification.reblog": "{name} ha retooteado tu estado",
   "notifications.clear": "Limpiar notificaciones",
   "notifications.clear_confirmation": "¿Seguro que quieres limpiar permanentemente todas tus notificaciones?",
   "notifications.column_settings.alert": "Notificaciones de escritorio",
   "notifications.column_settings.favourite": "Favoritos:",
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
-  "notifications.column_settings.filter_bar.show": "Show",
+  "notifications.column_settings.filter_bar.advanced": "Mostrar todas las categorías",
+  "notifications.column_settings.filter_bar.category": "Barra de filtrado rápido",
+  "notifications.column_settings.filter_bar.show": "Mostrar",
   "notifications.column_settings.follow": "Nuevos seguidores:",
   "notifications.column_settings.mention": "Menciones:",
-  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.poll": "Resultados de la votación:",
   "notifications.column_settings.push": "Notificaciones push",
   "notifications.column_settings.reblog": "Retoots:",
   "notifications.column_settings.show": "Mostrar en columna",
   "notifications.column_settings.sound": "Reproducir sonido",
-  "notifications.filter.all": "All",
-  "notifications.filter.boosts": "Boosts",
-  "notifications.filter.favourites": "Favourites",
-  "notifications.filter.follows": "Follows",
-  "notifications.filter.mentions": "Mentions",
-  "notifications.filter.polls": "Poll results",
+  "notifications.filter.all": "Todos",
+  "notifications.filter.boosts": "Retoots",
+  "notifications.filter.favourites": "Favoritos",
+  "notifications.filter.follows": "Seguidores",
+  "notifications.filter.mentions": "Menciones",
+  "notifications.filter.polls": "Resultados de la votación",
   "notifications.group": "{count} notificaciones",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
-  "poll_button.add_poll": "Add a poll",
-  "poll_button.remove_poll": "Remove poll",
+  "poll.closed": "Cerrada",
+  "poll.refresh": "Actualizar",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
+  "poll.total_votes": "{count, plural, one {# voto} other {# votos}}",
+  "poll.vote": "Votar",
+  "poll.voted": "Has votado a favor de esta respuesta",
+  "poll_button.add_poll": "Añadir una encuesta",
+  "poll_button.remove_poll": "Eliminar encuesta",
   "privacy.change": "Ajustar privacidad",
   "privacy.direct.long": "Sólo mostrar a los usuarios mencionados",
   "privacy.direct.short": "Directo",
@@ -294,7 +318,8 @@
   "privacy.public.long": "Mostrar en la historia federada",
   "privacy.public.short": "Público",
   "privacy.unlisted.long": "No mostrar en la historia federada",
-  "privacy.unlisted.short": "Sin federar",
+  "privacy.unlisted.short": "No listado",
+  "refresh": "Actualizar",
   "regeneration_indicator.label": "Cargando…",
   "regeneration_indicator.sublabel": "¡Tu historia de inicio se está preparando!",
   "relative_time.days": "{number}d",
@@ -313,19 +338,20 @@
   "search_popout.search_format": "Formato de búsqueda avanzada",
   "search_popout.tips.full_text": "Búsquedas de texto recuperan posts que has escrito, marcado como favoritos, retooteado o en los que has sido mencionado, así como usuarios, nombres y hashtags.",
   "search_popout.tips.hashtag": "etiqueta",
-  "search_popout.tips.status": "status",
+  "search_popout.tips.status": "estado",
   "search_popout.tips.text": "El texto simple devuelve correspondencias de nombre, usuario y hashtag",
   "search_popout.tips.user": "usuario",
   "search_results.accounts": "Gente",
   "search_results.hashtags": "Etiquetas",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Buscar toots por su contenido no está disponible en este servidor de Mastodon.",
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
-  "status.admin_account": "Open moderation interface for @{name}",
-  "status.admin_status": "Open this status in the moderation interface",
-  "status.block": "Block @{name}",
+  "status.admin_account": "Abrir interfaz de moderación para @{name}",
+  "status.admin_status": "Abrir este estado en la interfaz de moderación",
+  "status.block": "Bloquear a @{name}",
   "status.cancel_reblog_private": "Des-impulsar",
   "status.cannot_reblog": "Este toot no puede retootearse",
-  "status.copy": "Copy link to status",
+  "status.copy": "Copiar enlace al estado",
   "status.delete": "Borrar",
   "status.detailed_status": "Vista de conversación detallada",
   "status.direct": "Mensaje directo a @{name}",
@@ -342,7 +368,7 @@
   "status.open": "Expandir estado",
   "status.pin": "Fijar",
   "status.pinned": "Toot fijado",
-  "status.read_more": "Read more",
+  "status.read_more": "Leer más",
   "status.reblog": "Retootear",
   "status.reblog_private": "Implusar a la audiencia original",
   "status.reblogged_by": "Retooteado por {name}",
@@ -357,30 +383,39 @@
   "status.show_less_all": "Mostrar menos para todo",
   "status.show_more": "Mostrar más",
   "status.show_more_all": "Mostrar más para todo",
-  "status.show_thread": "Show thread",
+  "status.show_thread": "Ver hilo",
+  "status.uncached_media_warning": "No disponible",
   "status.unmute_conversation": "Dejar de silenciar conversación",
   "status.unpin": "Dejar de fijar",
-  "suggestions.dismiss": "Dismiss suggestion",
-  "suggestions.header": "You might be interested in…",
+  "suggestions.dismiss": "Descartar sugerencia",
+  "suggestions.header": "Es posible que te interese…",
   "tabs_bar.federated_timeline": "Federado",
   "tabs_bar.home": "Inicio",
   "tabs_bar.local_timeline": "Local",
   "tabs_bar.notifications": "Notificaciones",
   "tabs_bar.search": "Buscar",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "time_remaining.days": "{number, plural, one {# día restante} other {# días restantes}}",
+  "time_remaining.hours": "{number, plural, one {# hora restante} other {# horas restantes}}",
+  "time_remaining.minutes": "{number, plural, one {# minuto restante} other {# minutos restantes}}",
+  "time_remaining.moments": "Momentos restantes",
+  "time_remaining.seconds": "{number, plural, one {# segundo restante} other {# segundos restantes}}",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {persona} other {personas}} hablando",
+  "trends.trending_now": "Tendencia ahora",
   "ui.beforeunload": "Tu borrador se perderá si sales de Mastodon.",
   "upload_area.title": "Arrastra y suelta para subir",
   "upload_button.label": "Subir multimedia (JPEG, PNG, GIF, WebM, MP4, MOV)",
-  "upload_error.limit": "File upload limit exceeded.",
-  "upload_error.poll": "File upload not allowed with polls.",
+  "upload_error.limit": "Límite de subida de archivos excedido.",
+  "upload_error.poll": "Subida de archivos no permitida con encuestas.",
   "upload_form.description": "Describir para los usuarios con dificultad visual",
-  "upload_form.focus": "Recortar",
+  "upload_form.edit": "Editar",
   "upload_form.undo": "Borrar",
+  "upload_modal.analyzing_picture": "Analizando imagen…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.description_placeholder": "Un rápido zorro marrón salta sobre el perro perezoso",
+  "upload_modal.detect_text": "Detectar texto de la imagen",
+  "upload_modal.edit_media": "Editar multimedia",
+  "upload_modal.hint": "Haga clic o arrastre el círculo en la vista previa para elegir el punto focal que siempre estará a la vista en todas las miniaturas.",
+  "upload_modal.preview_label": "Vista previa ({ratio})",
   "upload_progress.label": "Subiendo…",
   "video.close": "Cerrar video",
   "video.exit_fullscreen": "Salir de pantalla completa",
diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json
new file mode 100644
index 0000000000000000000000000000000000000000..db60dab545276725f6219f1f0d22686c606467ba
--- /dev/null
+++ b/app/javascript/mastodon/locales/et.json
@@ -0,0 +1,423 @@
+{
+  "account.add_or_remove_from_list": "Lisa või Eemalda nimekirjadest",
+  "account.badges.bot": "Robot",
+  "account.block": "Blokeeri @{name}",
+  "account.block_domain": "Peida kõik domeenist {domain}",
+  "account.blocked": "Blokeeritud",
+  "account.cancel_follow_request": "Cancel follow request",
+  "account.direct": "Otsesõnum @{name}",
+  "account.domain_blocked": "Domeen peidetud",
+  "account.edit_profile": "Muuda profiili",
+  "account.endorse": "Too profiilil esile",
+  "account.follow": "Jälgi",
+  "account.followers": "Jälgijad",
+  "account.followers.empty": "Keegi ei jälgi seda kasutajat veel.",
+  "account.follows": "Jälgib",
+  "account.follows.empty": "See kasutaja ei jälgi veel kedagi.",
+  "account.follows_you": "Jälgib sind",
+  "account.hide_reblogs": "Peida upitused kasutajalt @{name}",
+  "account.last_status": "Last active",
+  "account.link_verified_on": "Selle lingi autorsust kontrolliti {date}",
+  "account.locked_info": "Selle konto privaatsus on lukustatud. Omanik vaatab manuaalselt üle, kes teda jägida saab.",
+  "account.media": "Meedia",
+  "account.mention": "Maini @{name}",
+  "account.moved_to": "{name} on kolinud:",
+  "account.mute": "Vaigista @{name}",
+  "account.mute_notifications": "Vaigista teated kasutajalt @{name}",
+  "account.muted": "Vaigistatud",
+  "account.never_active": "Never",
+  "account.posts": "Tuututused",
+  "account.posts_with_replies": "Tuututused ja vastused",
+  "account.report": "Raporteeri @{name}",
+  "account.requested": "Ootab kinnitust. Klõpsa jälgimise soovi tühistamiseks",
+  "account.share": "Jaga @{name} profiili",
+  "account.show_reblogs": "Näita kasutaja @{name} upitusi",
+  "account.unblock": "Eemalda blokeering @{name}",
+  "account.unblock_domain": "Tee {domain} nähtavaks",
+  "account.unendorse": "Ära kuva profiilil",
+  "account.unfollow": "Ära jälgi",
+  "account.unmute": "Ära vaigista @{name}",
+  "account.unmute_notifications": "Ära vaigista teateid kasutajalt @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
+  "alert.unexpected.message": "Tekkis ootamatu viga.",
+  "alert.unexpected.title": "Oih!",
+  "autosuggest_hashtag.per_week": "{count} per week",
+  "boost_modal.combo": "Saad vajutada {combo}, et see järgmine kord vahele jätta",
+  "bundle_column_error.body": "Mindagi läks valesti selle komponendi laadimisel.",
+  "bundle_column_error.retry": "Proovi uuesti",
+  "bundle_column_error.title": "Võrgu viga",
+  "bundle_modal_error.close": "Sulge",
+  "bundle_modal_error.message": "Selle komponendi laadimisel läks midagi viltu.",
+  "bundle_modal_error.retry": "Proovi uuesti",
+  "column.blocks": "Blokeeritud kasutajad",
+  "column.community": "Kohalik ajajoon",
+  "column.direct": "Otsesõnumid",
+  "column.directory": "Browse profiles",
+  "column.domain_blocks": "Peidetud domeenid",
+  "column.favourites": "Lemmikud",
+  "column.follow_requests": "Jälgimistaotlused",
+  "column.home": "Kodu",
+  "column.lists": "Nimekirjad",
+  "column.mutes": "Vaigistatud kasutajad",
+  "column.notifications": "Teated",
+  "column.pins": "Kinnitatud upitused",
+  "column.public": "Föderatiivne ajajoon",
+  "column_back_button.label": "Tagasi",
+  "column_header.hide_settings": "Peida sätted",
+  "column_header.moveLeft_settings": "Liiguta tulp vasakule",
+  "column_header.moveRight_settings": "Liiguta tulp paremale",
+  "column_header.pin": "Kinnita",
+  "column_header.show_settings": "Näita sätteid",
+  "column_header.unpin": "Eemalda kinnitus",
+  "column_subheading.settings": "Sätted",
+  "community.column_settings.media_only": "Ainult meedia",
+  "compose_form.direct_message_warning": "See tuut saadetakse ainult mainitud kasutajatele.",
+  "compose_form.direct_message_warning_learn_more": "Vaata veel",
+  "compose_form.hashtag_warning": "Seda tuuti ei kuvata ühegi sildi all, sest see on kirjendamata. Ainult avalikud tuutid on sildi järgi otsitavad.",
+  "compose_form.lock_disclaimer": "Sinu konto ei ole {locked}. Igaüks saab sind jälgida ja näha su ainult-jälgijatele postitusi.",
+  "compose_form.lock_disclaimer.lock": "lukus",
+  "compose_form.placeholder": "Millest mõtled?",
+  "compose_form.poll.add_option": "Lisa valik",
+  "compose_form.poll.duration": "Küsitluse kestus",
+  "compose_form.poll.option_placeholder": "Valik {number}",
+  "compose_form.poll.remove_option": "Eemalda see valik",
+  "compose_form.publish": "Tuut",
+  "compose_form.publish_loud": "{publish}!",
+  "compose_form.sensitive.hide": "Märgista meedia tundlikuks",
+  "compose_form.sensitive.marked": "Meedia on sensitiivseks märgitud",
+  "compose_form.sensitive.unmarked": "Meedia ei ole sensitiivseks märgitud",
+  "compose_form.spoiler.marked": "Tekst on hoiatuse taha peidetud",
+  "compose_form.spoiler.unmarked": "Tekst ei ole peidetud",
+  "compose_form.spoiler_placeholder": "Kirjuta oma hoiatus siia",
+  "confirmation_modal.cancel": "Katkesta",
+  "confirmations.block.block_and_report": "Blokeeri & Teata",
+  "confirmations.block.confirm": "Blokeeri",
+  "confirmations.block.message": "Oled kindel, et soovid blokkida {name}?",
+  "confirmations.delete.confirm": "Kustuta",
+  "confirmations.delete.message": "Oled kindel, et soovid selle staatuse kustutada?",
+  "confirmations.delete_list.confirm": "Kustuta",
+  "confirmations.delete_list.message": "Oled kindel, et soovid selle nimekirja püsivalt kustutada?",
+  "confirmations.domain_block.confirm": "Peida terve domeen",
+  "confirmations.domain_block.message": "Oled ikka päris kindel, et soovid blokeerida terve  {domain}? Enamikul juhtudel piisab mõnest sihitud blokist või vaigistusest, mis on eelistatav. Sa ei näe selle domeeni sisu üheski avalikus ajajoones või teadetes. Sinu jälgijad sellest domeenist eemaldatakse.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
+  "confirmations.mute.confirm": "Vaigista",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
+  "confirmations.mute.message": "Oled kindel, et soovid {name} vaigistada?",
+  "confirmations.redraft.confirm": "Kustuta & taasalusta",
+  "confirmations.redraft.message": "Oled kindel, et soovid selle staatuse kustutada ja alustada uuesti? Lemmikud ja upitused lähevad kaotsi ja vastused originaaalpostitusele jäävad orvuks.",
+  "confirmations.reply.confirm": "Vasta",
+  "confirmations.reply.message": "Kohene vastamine kirjutab üle sõnumi, mida hetkel koostad. Oled kindel, et soovid jätkata?",
+  "confirmations.unfollow.confirm": "Ära jälgi",
+  "confirmations.unfollow.message": "Oled kindel, et ei soovi jälgida {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
+  "embed.instructions": "Manusta see staatus oma veebilehele, kopeerides alloleva koodi.",
+  "embed.preview": "Nii näeb see välja:",
+  "emoji_button.activity": "Tegevus",
+  "emoji_button.custom": "Mugandatud",
+  "emoji_button.flags": "Lipud",
+  "emoji_button.food": "Toit & Jook",
+  "emoji_button.label": "Sisesta emoji",
+  "emoji_button.nature": "Loodus",
+  "emoji_button.not_found": "Ei ole emojosi!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Objektid",
+  "emoji_button.people": "Inimesed",
+  "emoji_button.recent": "Tihti kasutatud",
+  "emoji_button.search": "Otsi...",
+  "emoji_button.search_results": "Otsitulemused",
+  "emoji_button.symbols": "Sümbolid",
+  "emoji_button.travel": "Reisimine & Kohad",
+  "empty_column.account_timeline": "Siin tuute ei ole!",
+  "empty_column.account_unavailable": "Profiil pole saadaval",
+  "empty_column.blocks": "Sa ei ole veel ühtegi kasutajat blokeerinud.",
+  "empty_column.community": "Kohalik ajajoon on tühi. Kirjuta midagi avalikult, et pall veerema saada!",
+  "empty_column.direct": "Sul ei veel otsesõnumeid. Kui saadad või võtad mõne vastu, ilmuvad nad siia.",
+  "empty_column.domain_blocks": "Siin ei ole veel peidetud domeene.",
+  "empty_column.favourited_statuses": "Sul pole veel lemmikuid tuute. Kui märgid mõne, näed neid siin.",
+  "empty_column.favourites": "Keegi pole veel seda tuuti lemmikuks märkinud. Kui seegi seda teeb, näed seda siin.",
+  "empty_column.follow_requests": "Sul pole veel ühtegi jälgimise taotlust. Kui saad mõne, näed seda siin.",
+  "empty_column.hashtag": "Selle sildiga pole veel midagi.",
+  "empty_column.home": "Sinu kodu ajajoon on tühi! Külasta {public} või kasuta otsingut alustamaks ja kohtamaks teisi kasutajaid.",
+  "empty_column.home.public_timeline": "avalik ajajoon",
+  "empty_column.list": "Siin nimstus pole veel midagi. Kui selle nimistu liikmed postitavad uusi staatusi, näed neid siin.",
+  "empty_column.lists": "Sul ei ole veel ühtegi nimekirja. Kui lood mõne, näed seda siin.",
+  "empty_column.mutes": "Sa pole veel ühtegi kasutajat vaigistanud.",
+  "empty_column.notifications": "Sul ei ole veel teateid. Suhtle teistega alustamaks vestlust.",
+  "empty_column.public": "Siin pole midagi! Kirjuta midagi avalikut või jälgi ise kasutajaid täitmaks seda ruumi",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
+  "follow_request.authorize": "Autoriseeri",
+  "follow_request.reject": "Hülga",
+  "getting_started.developers": "Arendajad",
+  "getting_started.directory": "Profiili kataloog",
+  "getting_started.documentation": "Dokumentatsioon",
+  "getting_started.heading": "Alustamine",
+  "getting_started.invite": "Kutsu inimesi",
+  "getting_started.open_source_notice": "Mastodon on avatud lähtekoodiga tarkvara. Saad panustada või teatada probleemidest GitHubis {github}.",
+  "getting_started.security": "Turvalisus",
+  "getting_started.terms": "Kasutustingimused",
+  "hashtag.column_header.tag_mode.all": "ja {additional}",
+  "hashtag.column_header.tag_mode.any": "või {additional}",
+  "hashtag.column_header.tag_mode.none": "ilma {additional}",
+  "hashtag.column_settings.select.no_options_message": "Soovitusi ei leitud",
+  "hashtag.column_settings.select.placeholder": "Sisesta sildid…",
+  "hashtag.column_settings.tag_mode.all": "Kõik need",
+  "hashtag.column_settings.tag_mode.any": "Mõni neist",
+  "hashtag.column_settings.tag_mode.none": "Mitte ükski neist",
+  "hashtag.column_settings.tag_toggle": "Kaasa lisamärked selle tulba jaoks",
+  "home.column_settings.basic": "Peamine",
+  "home.column_settings.show_reblogs": "Näita upitusi",
+  "home.column_settings.show_replies": "Näita vastuseid",
+  "intervals.full.days": "{number, plural, one {# päev} other {# päevad}}",
+  "intervals.full.hours": "{number, plural, one {# tund} other {# tundi}}",
+  "intervals.full.minutes": "{number, plural, one {# minut} other {# minutit}}",
+  "introduction.federation.action": "Järgmine",
+  "introduction.federation.federated.headline": "Föderatiivne",
+  "introduction.federation.federated.text": "Avalikud postitused teistest föderatsiooni serveritest kuvatakse föderatiivsel ajajoonel.",
+  "introduction.federation.home.headline": "Kodu",
+  "introduction.federation.home.text": "Inimest postitused keda jälgid kuvatakse sinu koduajajoonel. Saad jälgida igaüht igas serveris!",
+  "introduction.federation.local.headline": "Kohalik",
+  "introduction.federation.local.text": "Samas serveris olevate inimeste postitused kuvatakse kohalikul ajajoonel.",
+  "introduction.interactions.action": "Välju õpetusest!",
+  "introduction.interactions.favourite.headline": "Lemmik",
+  "introduction.interactions.favourite.text": "Saad tuuti salvestada ja anda autorile teada, et meeldis märkides selle lemmikuks.",
+  "introduction.interactions.reblog.headline": "Upita",
+  "introduction.interactions.reblog.text": "Saad jagada teiste inimeste tuute oma jälgijatega upitades neid.",
+  "introduction.interactions.reply.headline": "Vasta",
+  "introduction.interactions.reply.text": "Saad vastata teiste ja enda tuutidele, mis ühendab nad kokku aruteluks.",
+  "introduction.welcome.action": "Lähme!",
+  "introduction.welcome.headline": "Esimesed sammud",
+  "introduction.welcome.text": "Teretulemast fediversumisse! Mõne aja pärast saad avaldada sõnumeid ja rääkida oma sõpradega läbi laia valiku serverite. Aga see server, {domain}, on eriline—ta majutab sinu profiili. Seega jäta ta nimi meelde.",
+  "keyboard_shortcuts.back": "tagasiminekuks",
+  "keyboard_shortcuts.blocked": "avamaks blokeeritud kasutajate nimistut",
+  "keyboard_shortcuts.boost": "upitamiseks",
+  "keyboard_shortcuts.column": "fokuseerimaks staatust ühele tulpadest",
+  "keyboard_shortcuts.compose": "fokuseerimaks tekstikoostamise alale",
+  "keyboard_shortcuts.description": "Kirjeldus",
+  "keyboard_shortcuts.direct": "avamaks otsesõnumite tulpa",
+  "keyboard_shortcuts.down": "liikumaks nimstus alla",
+  "keyboard_shortcuts.enter": "staatuse avamiseks",
+  "keyboard_shortcuts.favourite": "lemmikuks märkimiseks",
+  "keyboard_shortcuts.favourites": "avamaks lemmikute nimistut",
+  "keyboard_shortcuts.federated": "avamaks föderatsiooni ajajoont",
+  "keyboard_shortcuts.heading": "Klaviatuuri kiirkäsud",
+  "keyboard_shortcuts.home": "avamaks kodu ajajoont",
+  "keyboard_shortcuts.hotkey": "Kiirklahv",
+  "keyboard_shortcuts.legend": "selle legendi kuvamiseks",
+  "keyboard_shortcuts.local": "avamaks kohalikku ajajoont",
+  "keyboard_shortcuts.mention": "autori mainimiseks",
+  "keyboard_shortcuts.muted": "avamaks vaigistatud kasutajate nimistut",
+  "keyboard_shortcuts.my_profile": "avamaks profiili",
+  "keyboard_shortcuts.notifications": "avamaks teadete tulpa",
+  "keyboard_shortcuts.pinned": "avamaks kinnitatud tuutide nimistut",
+  "keyboard_shortcuts.profile": "avamaks autori profiili",
+  "keyboard_shortcuts.reply": "vastamiseks",
+  "keyboard_shortcuts.requests": "avamaks jälgimistaotluste nimistut",
+  "keyboard_shortcuts.search": "otsingu fokuseerimiseks",
+  "keyboard_shortcuts.start": "avamaks \"Alusta\" tulpa",
+  "keyboard_shortcuts.toggle_hidden": "näitamaks/peitmaks teksti CW taga",
+  "keyboard_shortcuts.toggle_sensitivity": "et peita/näidata meediat",
+  "keyboard_shortcuts.toot": "alustamaks täiesti uut tuuti",
+  "keyboard_shortcuts.unfocus": "tekstiala/otsingu koostamise mittefokuseerimiseks",
+  "keyboard_shortcuts.up": "liikumaks nimistus üles",
+  "lightbox.close": "Sulge",
+  "lightbox.next": "Järgmine",
+  "lightbox.previous": "Eelmine",
+  "lightbox.view_context": "Vaata konteksti",
+  "lists.account.add": "Lisa nimistusse",
+  "lists.account.remove": "Eemalda nimistust",
+  "lists.delete": "Kustuta nimistu",
+  "lists.edit": "Muuda nimistut",
+  "lists.edit.submit": "Muuda pealkiri",
+  "lists.new.create": "Lisa nimistu",
+  "lists.new.title_placeholder": "Uus nimistu pealkiri",
+  "lists.search": "Otsi sinu poolt jälgitavate inimese hulgast",
+  "lists.subheading": "Sinu nimistud",
+  "load_pending": "{count, plural, one {# uus kirje} other {# uut kirjet}}",
+  "loading_indicator.label": "Laeb..",
+  "media_gallery.toggle_visible": "Lülita nähtavus",
+  "missing_indicator.label": "Ei leitud",
+  "missing_indicator.sublabel": "Seda ressurssi ei leitud",
+  "mute_modal.hide_notifications": "Kas peita teated sellelt kasutajalt?",
+  "navigation_bar.apps": "Mobiilrakendused",
+  "navigation_bar.blocks": "Blokeeritud kasutajad",
+  "navigation_bar.community_timeline": "Kohalik ajajoon",
+  "navigation_bar.compose": "Koosta uus tuut",
+  "navigation_bar.direct": "Otsesõnumid",
+  "navigation_bar.discover": "Avasta",
+  "navigation_bar.domain_blocks": "Peidetud domeenid",
+  "navigation_bar.edit_profile": "Muuda profiili",
+  "navigation_bar.favourites": "Lemmikud",
+  "navigation_bar.filters": "Vaigistatud sõnad",
+  "navigation_bar.follow_requests": "Jälgimistaotlused",
+  "navigation_bar.follows_and_followers": "Jälgitud ja jälgijad",
+  "navigation_bar.info": "Selle serveri kohta",
+  "navigation_bar.keyboard_shortcuts": "Kiirklahvid",
+  "navigation_bar.lists": "Nimistud",
+  "navigation_bar.logout": "Logi välja",
+  "navigation_bar.mutes": "Vaigistatud kasutajad",
+  "navigation_bar.personal": "Isiklik",
+  "navigation_bar.pins": "Kinnitatud tuutid",
+  "navigation_bar.preferences": "Eelistused",
+  "navigation_bar.public_timeline": "Föderatiivne ajajoon",
+  "navigation_bar.security": "Turvalisus",
+  "notification.favourite": "{name} märkis su staatuse lemmikuks",
+  "notification.follow": "{name} jälgib sind",
+  "notification.mention": "{name} mainis sind",
+  "notification.poll": "Küsitlus, milles osalesid, on lõppenud",
+  "notification.reblog": "{name} upitas su staatust",
+  "notifications.clear": "Puhasta teated",
+  "notifications.clear_confirmation": "Oled kindel, et soovid püsivalt kõik oma teated puhastada?",
+  "notifications.column_settings.alert": "Töölauateated",
+  "notifications.column_settings.favourite": "Lemmikud:",
+  "notifications.column_settings.filter_bar.advanced": "Kuva kõik kategooriad",
+  "notifications.column_settings.filter_bar.category": "Kiirfiltri riba",
+  "notifications.column_settings.filter_bar.show": "Kuva",
+  "notifications.column_settings.follow": "Uued jälgijad:",
+  "notifications.column_settings.mention": "Mainimised:",
+  "notifications.column_settings.poll": "Küsitluse tulemused:",
+  "notifications.column_settings.push": "Push teated",
+  "notifications.column_settings.reblog": "Upitused:",
+  "notifications.column_settings.show": "Kuva tulbas",
+  "notifications.column_settings.sound": "Mängi heli",
+  "notifications.filter.all": "Kõik",
+  "notifications.filter.boosts": "Upitused",
+  "notifications.filter.favourites": "Lemmikud",
+  "notifications.filter.follows": "Jälgib",
+  "notifications.filter.mentions": "Mainimised",
+  "notifications.filter.polls": "Küsitluse tulemused",
+  "notifications.group": "{count} teated",
+  "poll.closed": "Suletud",
+  "poll.refresh": "Värskenda",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
+  "poll.total_votes": "{count, plural, one {# hääl} other {# hääli}}",
+  "poll.vote": "Hääleta",
+  "poll.voted": "You voted for this answer",
+  "poll_button.add_poll": "Lisa küsitlus",
+  "poll_button.remove_poll": "Eemalda küsitlus",
+  "privacy.change": "Muuda staatuse privaatsust",
+  "privacy.direct.long": "Postita ainult mainitud kasutajatele",
+  "privacy.direct.short": "Otsene",
+  "privacy.private.long": "Postita ainult jälgijatele",
+  "privacy.private.short": "Ainult jälgijatele",
+  "privacy.public.long": "Postita avalikele ajajoontele",
+  "privacy.public.short": "Avalik",
+  "privacy.unlisted.long": "Ära postita avalikele ajajoontele",
+  "privacy.unlisted.short": "Määramata",
+  "refresh": "Refresh",
+  "regeneration_indicator.label": "Laeb…",
+  "regeneration_indicator.sublabel": "Sinu kodu voog on ettevalmistamisel!",
+  "relative_time.days": "{number}p",
+  "relative_time.hours": "{number}t",
+  "relative_time.just_now": "nüüd",
+  "relative_time.minutes": "{number}m",
+  "relative_time.seconds": "{number}s",
+  "reply_indicator.cancel": "Tühista",
+  "report.forward": "Edasta kasutajale {target}",
+  "report.forward_hint": "See kasutaja on teisest serverist. Kas saadan anonümiseeritud koopia sellest teatest sinna ka?",
+  "report.hint": "See teade saadetakse sinu serveri moderaatoritele. Te saate lisada selgituse selle kohta, miks selle kasutaja kohta teate esitasite, siin:",
+  "report.placeholder": "Lisaks kommentaarid",
+  "report.submit": "Saada",
+  "report.target": "Teatamine {target} kohta",
+  "search.placeholder": "Otsi",
+  "search_popout.search_format": "Täiustatud otsiformaat",
+  "search_popout.tips.full_text": "Lihtne tekst toob esile staatused mida olete kirjutanud, lisanud lemmikuks, upitanud või olete seal mainitud, ning lisaks veel kattuvad kasutajanimed, kuvanimed ja sildid.",
+  "search_popout.tips.hashtag": "silt",
+  "search_popout.tips.status": "staatus",
+  "search_popout.tips.text": "Lihtne tekst toob esile kattuvad kuvanimed, kasutajanimed ning sildid",
+  "search_popout.tips.user": "kasutaja",
+  "search_results.accounts": "Inimesed",
+  "search_results.hashtags": "Sildid",
+  "search_results.statuses": "Tuudid",
+  "search_results.statuses_fts_disabled": "Tuutsude otsimine nende sisu järgi ei ole sellel Mastodoni serveril sisse lülitatud.",
+  "search_results.total": "{count, number} {count, plural, one {tulemus} other {tulemust}}",
+  "status.admin_account": "Ava moderaatoriliides kasutajale @{name}",
+  "status.admin_status": "Ava see staatus moderaatoriliites",
+  "status.block": "Blokeeri @{name}",
+  "status.cancel_reblog_private": "Äraupita",
+  "status.cannot_reblog": "Seda postitust ei saa upitada",
+  "status.copy": "Kopeeri link staatusesse",
+  "status.delete": "Kustuta",
+  "status.detailed_status": "Detailne vestluskuva",
+  "status.direct": "Otsesõnum @{name}",
+  "status.embed": "Sängita",
+  "status.favourite": "Lemmik",
+  "status.filtered": "Filtreeritud",
+  "status.load_more": "Lae veel",
+  "status.media_hidden": "Meedia peidetud",
+  "status.mention": "Mainimine @{name}",
+  "status.more": "Veel",
+  "status.mute": "Vaigista @{name}",
+  "status.mute_conversation": "Vaigista vestlus",
+  "status.open": "Laienda see staatus",
+  "status.pin": "Kinnita profiilile",
+  "status.pinned": "Kinnitatud tuut",
+  "status.read_more": "Loe veel",
+  "status.reblog": "Upita",
+  "status.reblog_private": "Upita algsele publikule",
+  "status.reblogged_by": "{name} upitatud",
+  "status.reblogs.empty": "Keegi pole seda tuuti veel upitanud. Kui keegi upitab, näed seda siin.",
+  "status.redraft": "Kustuta & alga uuesti",
+  "status.reply": "Vasta",
+  "status.replyAll": "Vasta lõimele",
+  "status.report": "Raport @{name}",
+  "status.sensitive_warning": "Tundlik sisu",
+  "status.share": "Jaga",
+  "status.show_less": "Näita vähem",
+  "status.show_less_all": "Näita vähem kõigile",
+  "status.show_more": "Näita veel",
+  "status.show_more_all": "Näita enam kõigile",
+  "status.show_thread": "Kuva lõim",
+  "status.uncached_media_warning": "Not available",
+  "status.unmute_conversation": "Ära vaigista vestlust",
+  "status.unpin": "Kinnita profiililt lahti",
+  "suggestions.dismiss": "Eira soovitust",
+  "suggestions.header": "Sind võib huvitada…",
+  "tabs_bar.federated_timeline": "Föderatiivne",
+  "tabs_bar.home": "Kodu",
+  "tabs_bar.local_timeline": "Kohalik",
+  "tabs_bar.notifications": "Teated",
+  "tabs_bar.search": "Otsi",
+  "time_remaining.days": "{number, plural, one {# päev} other {# päeva}} left",
+  "time_remaining.hours": "{number, plural, one {# tund} other {# tundi}} left",
+  "time_remaining.minutes": "{number, plural, one {# minut} other {# minutit}} left",
+  "time_remaining.moments": "Hetked jäänud",
+  "time_remaining.seconds": "{number, plural, one {# sekund} other {# sekundit}} left",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {inimene} other {inimesed}} talking",
+  "trends.trending_now": "Trending now",
+  "ui.beforeunload": "Sinu mustand läheb kaotsi, kui lahkud Mastodonist.",
+  "upload_area.title": "Lohista & aseta üleslaadimiseks",
+  "upload_button.label": "Lisa meedia (JPEG, PNG, GIF, WebM, MP4, MOV)",
+  "upload_error.limit": "Faili üleslaadimise limiit ületatud.",
+  "upload_error.poll": "Küsitlustes pole faili üleslaadimine lubatud.",
+  "upload_form.description": "Kirjelda vaegnägijatele",
+  "upload_form.edit": "Edit",
+  "upload_form.undo": "Kustuta",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
+  "upload_progress.label": "Laeb üles....",
+  "video.close": "Sulge video",
+  "video.exit_fullscreen": "Välju täisekraanist",
+  "video.expand": "Suurenda video",
+  "video.fullscreen": "Täisekraan",
+  "video.hide": "Peida video",
+  "video.mute": "Vaigista heli",
+  "video.pause": "Paus",
+  "video.play": "Mängi",
+  "video.unmute": "Taasta heli"
+}
diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json
index 53d614eb91397649488b20a61ae901749b770540..1fb7faf9adf53a8a1ac081d14d1cdac7fb6039ec 100644
--- a/app/javascript/mastodon/locales/eu.json
+++ b/app/javascript/mastodon/locales/eu.json
@@ -4,6 +4,7 @@
   "account.block": "Blokeatu @{name}",
   "account.block_domain": "Ezkutatu {domain} domeinuko guztia",
   "account.blocked": "Blokeatuta",
+  "account.cancel_follow_request": "Ezeztatu jarraitzeko eskaria",
   "account.direct": "Mezu zuzena @{name}(r)i",
   "account.domain_blocked": "Ezkutatutako domeinua",
   "account.edit_profile": "Aldatu profila",
@@ -15,16 +16,18 @@
   "account.follows.empty": "Erabiltzaile honek ez du inor jarraitzen oraindik.",
   "account.follows_you": "Jarraitzen dizu",
   "account.hide_reblogs": "Ezkutatu @{name}(r)en bultzadak",
+  "account.last_status": "Azkenekoz aktiboa",
   "account.link_verified_on": "Esteka honen jabetzaren egiaztaketa data: {date}",
   "account.locked_info": "Kontu honen pribatutasun egoera blokeatuta gisa ezarri da. Jabeak eskuz erabakitzen du nork jarraitu diezaioken.",
   "account.media": "Multimedia",
   "account.mention": "Aipatu @{name}",
-  "account.moved_to": "{name} hona lekualdatu da:",
+  "account.moved_to": "{name} hona migratu da:",
   "account.mute": "Mututu @{name}",
   "account.mute_notifications": "Mututu @{name}(r)en jakinarazpenak",
   "account.muted": "Mutututa",
-  "account.posts": "Toot",
-  "account.posts_with_replies": "Toot eta erantzunak",
+  "account.never_active": "Inoiz ez",
+  "account.posts": "Tootak",
+  "account.posts_with_replies": "Toot-ak eta erantzunak",
   "account.report": "Salatu @{name}",
   "account.requested": "Onarpenaren zain. Klikatu jarraitzeko eskaera ezeztatzeko",
   "account.share": "@{name}(e)ren profila elkarbanatu",
@@ -35,8 +38,11 @@
   "account.unfollow": "Utzi jarraitzeari",
   "account.unmute": "Desmututu @{name}",
   "account.unmute_notifications": "Desmututu @{name}(r)en jakinarazpenak",
+  "alert.rate_limited.message": "Saiatu {retry_time, time, medium} barru.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Ustekabeko errore bat gertatu da.",
   "alert.unexpected.title": "Ene!",
+  "autosuggest_hashtag.per_week": "{count} asteko",
   "boost_modal.combo": "{combo} sakatu dezakezu hurrengoan hau saltatzeko",
   "bundle_column_error.body": "Zerbait okerra gertatu da osagai hau kargatzean.",
   "bundle_column_error.retry": "Saiatu berriro",
@@ -47,6 +53,7 @@
   "column.blocks": "Blokeatutako erabiltzaileak",
   "column.community": "Denbora-lerro lokala",
   "column.direct": "Mezu zuzenak",
+  "column.directory": "Arakatu profilak",
   "column.domain_blocks": "Ezkutatutako domeinuak",
   "column.favourites": "Gogokoak",
   "column.follow_requests": "Jarraitzeko eskariak",
@@ -54,7 +61,7 @@
   "column.lists": "Zerrendak",
   "column.mutes": "Mutututako erabiltzaileak",
   "column.notifications": "Jakinarazpenak",
-  "column.pins": "Pinned toot",
+  "column.pins": "Finkatutako toot-ak",
   "column.public": "Federatutako denbora-lerroa",
   "column_back_button.label": "Atzera",
   "column_header.hide_settings": "Ezkutatu ezarpenak",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Ziur behin betiko ezabatu nahi duzula zerrenda hau?",
   "confirmations.domain_block.confirm": "Ezkutatu domeinu osoa",
   "confirmations.domain_block.message": "Ziur, erabat ziur, {domain} domeinu osoa blokeatu nahi duzula? Gehienetan gutxi batzuk blokeatu edo mututzearekin nahikoa da. Ez duzu domeinu horretako edukirik ikusiko denbora lerroetan edo jakinarazpenetan. Domeinu horretako zure jarraitzaileak kenduko dira ere.",
+  "confirmations.logout.confirm": "Amaitu saioa",
+  "confirmations.logout.message": "Ziur saioa amaitu nahi duzula?",
   "confirmations.mute.confirm": "Mututu",
+  "confirmations.mute.explanation": "Honek horko mezuak eta aipamena egiten dietenak ezkutatuko ditu, baina beraiek zure mezuak ikusi ahal izango dituzte eta zuri jarraitu.",
   "confirmations.mute.message": "Ziur {name} mututu nahi duzula?",
   "confirmations.redraft.confirm": "Ezabatu eta berridatzi",
   "confirmations.redraft.message": "Ziur mezu hau ezabatu eta berridatzi nahi duzula? Gogokoak eta bultzadak galduko dira eta jaso dituen erantzunak umezurtz geratuko dira.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Orain erantzuteak idazten ari zaren mezua gainidatziko du. Ziur jarraitu nahi duzula?",
   "confirmations.unfollow.confirm": "Utzi jarraitzeari",
   "confirmations.unfollow.message": "Ziur {name} jarraitzeari utzi nahi diozula?",
+  "conversation.delete": "Ezabatu elkarrizketa",
+  "conversation.mark_as_read": "Markatu irakurrita bezala",
+  "conversation.open": "Ikusi elkarrizketa",
+  "conversation.with": "Hauekin: {names}",
+  "directory.federated": "Fedibertso ezagunekoak",
+  "directory.local": "{domain} domeinukoak soilik",
+  "directory.new_arrivals": "Iritsi berriak",
+  "directory.recently_active": "Duela gutxi aktibo",
   "embed.instructions": "Txertatu mezu hau zure webgunean beheko kodea kopatuz.",
   "embed.preview": "Hau da izango duen itxura:",
   "emoji_button.activity": "Jarduera",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Ez duzu erabiltzailerik mututu oraindik.",
   "empty_column.notifications": "Ez duzu jakinarazpenik oraindik. Jarri besteekin harremanetan elkarrizketa abiatzeko.",
   "empty_column.public": "Ez dago ezer hemen! Idatzi zerbait publikoki edo jarraitu eskuz beste zerbitzari batzuetako erabiltzaileak hau betetzen joateko",
+  "error.unexpected_crash.explanation": "Gure kodean arazoren bat dela eta, edo nabigatzailearekin bateragarritasun arazoren bat dela eta, orri hau ezin izan da ongi bistaratu.",
+  "error.unexpected_crash.next_steps": "Saiatu orria berritzen. Horrek ez badu laguntzen, agian Mastodon erabiltzeko aukera duzu oraindik ere beste nabigatzaile bat edo aplikazio natibo bat erabilita.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Eman arazoaren berri",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Zerrenda berriaren izena",
   "lists.search": "Bilatu jarraitzen dituzun pertsonen artean",
   "lists.subheading": "Zure zerrendak",
+  "load_pending": "{count, plural, one {eleentuberri #} other {# elementu berri}}",
   "loading_indicator.label": "Kargatzen...",
   "media_gallery.toggle_visible": "Txandakatu ikusgaitasuna",
   "missing_indicator.label": "Ez aurkitua",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Pertsonala",
   "navigation_bar.pins": "Finkatutako toot-ak",
   "navigation_bar.preferences": "Hobespenak",
-  "navigation_bar.profile_directory": "Profilen direktorioa",
   "navigation_bar.public_timeline": "Federatutako denbora-lerroa",
   "navigation_bar.security": "Segurtasuna",
   "notification.favourite": "{name}(e)k zure mezua gogoko du",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} jakinarazpen",
   "poll.closed": "Itxita",
   "poll.refresh": "Berritu",
+  "poll.total_people": "{count, plural, one {pertsona #} other {# pertsona}}",
   "poll.total_votes": "{count, plural, one {boto #} other {# boto}}",
   "poll.vote": "Bozkatu",
+  "poll.voted": "Erantzun honi eman diozu botoa",
   "poll_button.add_poll": "Gehitu inkesta bat",
   "poll_button.remove_poll": "Kendu inkesta",
   "privacy.change": "Doitu mezuaren pribatutasuna",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Publikoa",
   "privacy.unlisted.long": "Do not show in public timelines",
   "privacy.unlisted.short": "Zerrendatu gabea",
+  "refresh": "Berritu",
   "regeneration_indicator.label": "Kargatzen…",
   "regeneration_indicator.sublabel": "Zure hasiera-jarioa prestatzen ari da!",
   "relative_time.days": "{number}e",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Jendea",
   "search_results.hashtags": "Traolak",
   "search_results.statuses": "Toot-ak",
+  "search_results.statuses_fts_disabled": "Mastodon zerbitzari honek ez du Toot-en edukiaren bilaketa gaitu.",
   "search_results.total": "{count, number} {count, plural, one {emaitza} other {emaitzak}}",
   "status.admin_account": "Ireki @{name} erabiltzailearen moderazio interfazea",
   "status.admin_status": "Ireki mezu hau moderazio interfazean",
@@ -358,6 +384,7 @@
   "status.show_more": "Erakutsi gehiago",
   "status.show_more_all": "Erakutsi denetarik gehiago",
   "status.show_thread": "Erakutsi haria",
+  "status.uncached_media_warning": "Ez eskuragarri",
   "status.unmute_conversation": "Desmututu elkarrizketa",
   "status.unpin": "Desfinkatu profiletik",
   "suggestions.dismiss": "Errefusatu proposamena",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Amaitzekotan",
   "time_remaining.seconds": "{number, plural, one {segundo #} other {# segundo}} amaitzeko",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} hitz egiten",
+  "trends.trending_now": "Joera orain",
   "ui.beforeunload": "Zure zirriborroa galduko da Mastodon uzten baduzu.",
   "upload_area.title": "Arrastatu eta jaregin igotzeko",
   "upload_button.label": "Gehitu multimedia  (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Fitxategi igoera muga gaindituta.",
   "upload_error.poll": "Ez da inkestetan fitxategiak igotzea onartzen.",
   "upload_form.description": "Deskribatu ikusmen arazoak dituztenentzat",
-  "upload_form.focus": "Aldatu aurrebista",
+  "upload_form.edit": "Editatu",
   "upload_form.undo": "Ezabatu",
+  "upload_modal.analyzing_picture": "Irudia aztertzen…",
+  "upload_modal.apply": "Aplikatu",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Antzeman irudiko testua",
+  "upload_modal.edit_media": "Editatu multimedia",
+  "upload_modal.hint": "Sakatu eta jaregin aurrebistako zirkulua iruditxoetan beti ikusgai egongo den puntu fokala hautatzeko.",
+  "upload_modal.preview_label": "Aurreikusi({ratio})",
   "upload_progress.label": "Igotzen...",
   "video.close": "Itxi bideoa",
   "video.exit_fullscreen": "Irten pantaila osotik",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index ab408d53d187898e66ed60cca2aa6e54ff8167d9..fa3453f105d3d4dd341c7a763b178884f1917469 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -1,9 +1,10 @@
 {
-  "account.add_or_remove_from_list": "افزودن یا حذف از فهرست‌ها",
+  "account.add_or_remove_from_list": "افزودن یا برداشتن از فهرست",
   "account.badges.bot": "ربات",
   "account.block": "مسدودسازی @{name}",
   "account.block_domain": "پنهان‌سازی همه چیز از سرور {domain}",
-  "account.blocked": "مسدودشده",
+  "account.blocked": "مسدود شده",
+  "account.cancel_follow_request": "لغو درخواست پیگیری",
   "account.direct": "پیغام خصوصی به @{name}",
   "account.domain_blocked": "دامین پنهان‌شده",
   "account.edit_profile": "ویرایش نمایه",
@@ -15,7 +16,8 @@
   "account.follows.empty": "این کاربر هنوز هیچ کسی را پی نمی‌گیرد.",
   "account.follows_you": "پیگیر شماست",
   "account.hide_reblogs": "پنهان کردن بازبوق‌های @{name}",
-  "account.link_verified_on": "مالکیت این نشانی در تایخ {date} بررسی شد",
+  "account.last_status": "آخرین فعالیت",
+  "account.link_verified_on": "مالکیت این نشانی در تاریخ {date} بررسی شد",
   "account.locked_info": "این حساب خصوصی است. صاحب این حساب تصمیم می‌گیرد که چه کسی می‌تواند پیگیرش باشد.",
   "account.media": "عکس و ویدیو",
   "account.mention": "نام‌بردن از @{name}",
@@ -23,6 +25,7 @@
   "account.mute": "بی‌صدا کردن @{name}",
   "account.mute_notifications": "بی‌صداکردن اعلان‌ها از طرف @{name}",
   "account.muted": "بی‌صداشده",
+  "account.never_active": "هرگز",
   "account.posts": "نوشته‌ها",
   "account.posts_with_replies": "نوشته‌ها و پاسخ‌ها",
   "account.report": "گزارش @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "پایان پیگیری",
   "account.unmute": "باصدا کردن @{name}",
   "account.unmute_notifications": "باصداکردن اعلان‌ها از طرف @{name}",
+  "alert.rate_limited.message": "لطفاً پس از {retry_time, time, medium} دوباره تلاش کنید.",
+  "alert.rate_limited.title": "محدودیت تعداد",
   "alert.unexpected.message": "خطای پیش‌بینی‌نشده‌ای رخ داد.",
   "alert.unexpected.title": "ای وای!",
+  "autosuggest_hashtag.per_week": "{count} در هفته",
   "boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید",
   "bundle_column_error.body": "هنگام بازکردن این بخش خطایی رخ داد.",
   "bundle_column_error.retry": "تلاش دوباره",
@@ -47,6 +53,7 @@
   "column.blocks": "کاربران مسدودشده",
   "column.community": "نوشته‌های محلی",
   "column.direct": "پیغام‌های خصوصی",
+  "column.directory": "مرور نمایه‌ها",
   "column.domain_blocks": "دامین‌های پنهان‌شده",
   "column.favourites": "پسندیده‌ها",
   "column.follow_requests": "درخواست‌های پیگیری",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "آیا واقعاً می‌خواهید این فهرست را برای همیشه پاک کنید؟",
   "confirmations.domain_block.confirm": "پنهان‌سازی کل دامین",
   "confirmations.domain_block.message": "آیا جدی جدی می‌خواهید کل دامین {domain} را مسدود کنید؟ بیشتر وقت‌ها مسدودکردن یا بی‌صداکردن چند حساب کاربری خاص کافی است و توصیه می‌شود. پس از این کار شما هیچ نوشته‌ای را از این دامین در فهرست نوشته‌های عمومی یا اعلان‌هایتان نخواهید دید. پیگیران شما از این دامین هم حذف خواهد شد.",
+  "confirmations.logout.confirm": "خروج",
+  "confirmations.logout.message": "آیا مطمئنید که می‌خواهید خارج شوید؟",
   "confirmations.mute.confirm": "بی‌صدا کن",
+  "confirmations.mute.explanation": "این کار نوشته‌های آن‌ها و نوشته‌هایی را که از آن‌ها نام برده پنهان می‌کند، ولی آن‌ها همچنان اجازه دارند نوشته‌های شما را ببینند و شما را پی بگیرند.",
   "confirmations.mute.message": "آیا واقعاً می‌خواهید {name} را بی‌صدا کنید؟",
   "confirmations.redraft.confirm": "پاک‌کردن و بازنویسی",
   "confirmations.redraft.message": "آیا واقعاً می‌خواهید این نوشته را پاک کنید و آن را از نو بنویسید؟ با این کار بازبوق‌ها و پسندیده‌شدن‌های آن از دست می‌رود و پاسخ‌ها به آن بی‌مرجع می‌شود.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "اگر الان پاسخ دهید، چیزی که در حال نوشتنش بودید پاک خواهد شد. آیا همین را می‌خواهید؟",
   "confirmations.unfollow.confirm": "لغو پیگیری",
   "confirmations.unfollow.message": "آیا واقعاً می‌خواهید به پیگیری از {name} پایان دهید؟",
+  "conversation.delete": "حذف گفتگو",
+  "conversation.mark_as_read": "علامت‌گذاری به عنوان خوانده شده",
+  "conversation.open": "دیدن گفتگو",
+  "conversation.with": "با {names}",
+  "directory.federated": "از سرورهای همسایه",
+  "directory.local": "تنها از {domain}",
+  "directory.new_arrivals": "تازه‌واردان",
+  "directory.recently_active": "کاربران فعال اخیر",
   "embed.instructions": "برای جاگذاری این نوشته در سایت خودتان، کد زیر را کپی کنید.",
   "embed.preview": "نوشتهٔ جاگذاری‌شده این گونه به نظر خواهد رسید:",
   "emoji_button.activity": "فعالیت",
@@ -126,7 +144,7 @@
   "empty_column.favourited_statuses": "شما هنوز هیچ بوقی را نپسندیده‌اید. وقتی بوقی را بپسندید، این‌جا نمایش خواهد یافت.",
   "empty_column.favourites": "هنوز هیچ کسی این بوق را نپسندیده است. وقتی کسی آن را بپسندد، نامش این‌جا نمایش خواهد یافت.",
   "empty_column.follow_requests": "شما هنوز هیچ درخواست پیگیری‌ای ندارید. وقتی چنین درخواستی بگیرید، این‌جا نمایش خواهد یافت.",
-  "empty_column.hashtag": "هنوز هیچ چیزی با این هشتگ نیست.",
+  "empty_column.hashtag": "هنوز هیچ چیزی با این برچسب (هشتگ) نیست.",
   "empty_column.home": "شما هنوز پیگیر کسی نیستید. {public} را ببینید یا چیزی را جستجو کنید تا کاربران دیگر را ببینید.",
   "empty_column.home.public_timeline": "فهرست نوشته‌های همه‌جا",
   "empty_column.list": "در این فهرست هنوز چیزی نیست. وقتی اعضای این فهرست چیزی بنویسند، این‌جا ظاهر خواهد شد.",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "شما هنوز هیچ کاربری را بی‌صدا نکرده‌اید.",
   "empty_column.notifications": "هنوز هیچ اعلانی ندارید. به نوشته‌های دیگران واکنش نشان دهید تا گفتگو آغاز شود.",
   "empty_column.public": "این‌جا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران سرورهای دیگر را پی بگیرید تا این‌جا پر شود",
+  "error.unexpected_crash.explanation": "به خاطر اشکالی در کدهای ما یا ناسازگاری با مرورگر شما، این صفحه به درستی نمایش نیافت.",
+  "error.unexpected_crash.next_steps": "لطفاً صفحه را دوباره باز کنید. اگر آن هم کمکی نکرد، همچنان شاید بتوانید با ماستدون از راه یکی از اپ‌های آن کار کنید.",
+  "errors.unexpected_crash.copy_stacktrace": "کپی جزئیات اشکال",
+  "errors.unexpected_crash.report_issue": "گزارش اشکال",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "نام فهرست تازه",
   "lists.search": "بین کسانی که پی می‌گیرید بگردید",
   "lists.subheading": "فهرست‌های شما",
+  "load_pending": "{count, plural, one {# مورد تازه} other {# مورد تازه}}",
   "loading_indicator.label": "بارگیری...",
   "media_gallery.toggle_visible": "تغییر پیدایی",
   "missing_indicator.label": "پیدا نشد",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "شخصی",
   "navigation_bar.pins": "نوشته‌های ثابت",
   "navigation_bar.preferences": "ترجیحات",
-  "navigation_bar.profile_directory": "فهرست گزیدهٔ کاربران",
   "navigation_bar.public_timeline": "نوشته‌های همه‌جا",
   "navigation_bar.security": "امنیت",
   "notification.favourite": "‫{name}‬ نوشتهٔ شما را پسندید",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} اعلان",
   "poll.closed": "پایان‌یافته",
   "poll.refresh": "به‌روزرسانی",
+  "poll.total_people": "{count, plural, one {# نفر} other {# نفر}}",
   "poll.total_votes": "{count, plural, one {# رأی} other {# رأی}}",
   "poll.vote": "رأی",
+  "poll.voted": "شما به این گزینه رأی دادید",
   "poll_button.add_poll": "افزودن نظرسنجی",
   "poll_button.remove_poll": "حذف نظرسنجی",
   "privacy.change": "تنظیم حریم خصوصی نوشته‌ها",
@@ -295,6 +319,7 @@
   "privacy.public.short": "عمومی",
   "privacy.unlisted.long": "عمومی، ولی فهرست نکن",
   "privacy.unlisted.short": "فهرست‌نشده",
+  "refresh": "به‌روزرسانی",
   "regeneration_indicator.label": "در حال باز شدن…",
   "regeneration_indicator.sublabel": "این فهرست دارد آماده می‌شود!",
   "relative_time.days": "{number} روز",
@@ -312,13 +337,14 @@
   "search.placeholder": "جستجو",
   "search_popout.search_format": "راهنمای جستجوی پیشرفته",
   "search_popout.tips.full_text": "جستجوی متنی ساده می‌تواند بوق‌هایی که شما نوشته‌اید، پسندیده‌اید، بازبوقیده‌اید، یا در آن‌ها از شما نام برده شده است را پیدا کند. همچنین نام‌های کاربری، نام نمایش‌یافته، و هشتگ‌ها را هم شامل می‌شود.",
-  "search_popout.tips.hashtag": "هشتگ",
+  "search_popout.tips.hashtag": "برچسب",
   "search_popout.tips.status": "نوشته",
-  "search_popout.tips.text": "جستجوی متنی ساده برای نام‌ها، نام‌های کاربری، و هشتگ‌ها",
+  "search_popout.tips.text": "جستجوی متنی ساده برای نام‌ها، نام‌های کاربری، و برچسب‌ها",
   "search_popout.tips.user": "کاربر",
   "search_results.accounts": "افراد",
-  "search_results.hashtags": "هشتگ‌ها",
+  "search_results.hashtags": "برچسب‌ها",
   "search_results.statuses": "بوق‌ها",
+  "search_results.statuses_fts_disabled": "جستجوی محتوای بوق‌ها در این سرور ماستدون ممکن نیست.",
   "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
   "status.admin_account": "محیط مدیریت مربوط به @{name} را باز کن",
   "status.admin_status": "این نوشته را در محیط مدیریت باز کن",
@@ -358,6 +384,7 @@
   "status.show_more": "نمایش",
   "status.show_more_all": "نمایش بیشتر همه",
   "status.show_thread": "نمایش گفتگو",
+  "status.uncached_media_warning": "ناموجود",
   "status.unmute_conversation": "باصداکردن گفتگو",
   "status.unpin": "برداشتن نوشتهٔ ثابت نمایه",
   "suggestions.dismiss": "پیشنهاد را نادیده بگیر",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "زمان باقی‌مانده",
   "time_remaining.seconds": "{number, plural, one {# ثانیه} other {# ثانیه}} باقی مانده",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {نفر نوشته است} other {نفر نوشته‌اند}}",
+  "trends.trending_now": "پرطرفدار",
   "ui.beforeunload": "اگر از ماستدون خارج شوید پیش‌نویس شما پاک خواهد شد.",
   "upload_area.title": "برای بارگذاری به این‌جا بکشید",
   "upload_button.label": "افزودن عکس و ویدیو (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "از حد مجاز باگذاری فراتر رفتید.",
   "upload_error.poll": "باگذاری پرونده در نظرسنجی‌ها ممکن نیست.",
   "upload_form.description": "نوشتهٔ توضیحی برای کم‌بینایان و نابینایان",
-  "upload_form.focus": "تغییر پیش‌نمایش",
+  "upload_form.edit": "ویرایش",
   "upload_form.undo": "حذف",
+  "upload_modal.analyzing_picture": "در حال پردازش تصویر…",
+  "upload_modal.apply": "اجرا",
+  "upload_modal.description_placeholder": "مردی با بیل مادرزنش را کشت",
+  "upload_modal.detect_text": "پیدا کردن نوشته از درون تصویر",
+  "upload_modal.edit_media": "ویرایش تصویر",
+  "upload_modal.hint": "حتی اگر تصویر بریده یا کوچک شود، نقطهٔ کانونی آن همیشه دیده خواهد شد. نقطهٔ کانونی را با کلیک یا جابه‌جا کردن آن تنظیم کنید.",
+  "upload_modal.preview_label": "پیش‌نمایش ({ratio})",
   "upload_progress.label": "بارگذاری...",
   "video.close": "بستن ویدیو",
   "video.exit_fullscreen": "خروج از حالت تمام صفحه",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index 4817b16f85618b6c601391842a5d2b18dc7cc9a0..ca96d298abd397120eba70b66d541d13ba470ef2 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -4,17 +4,19 @@
   "account.block": "Estä @{name}",
   "account.block_domain": "Piilota kaikki sisältö verkkotunnuksesta {domain}",
   "account.blocked": "Estetty",
+  "account.cancel_follow_request": "Peruuta seurauspyyntö",
   "account.direct": "Viesti käyttäjälle @{name}",
   "account.domain_blocked": "Verkko-osoite piilotettu",
   "account.edit_profile": "Muokkaa",
   "account.endorse": "Suosittele profiilissasi",
   "account.follow": "Seuraa",
-  "account.followers": "Seuraajia",
+  "account.followers": "Seuraajaa",
   "account.followers.empty": "Tällä käyttäjällä ei ole vielä seuraajia.",
   "account.follows": "Seuraa",
   "account.follows.empty": "Tämä käyttäjä ei vielä seuraa ketään.",
   "account.follows_you": "Seuraa sinua",
   "account.hide_reblogs": "Piilota buustaukset käyttäjältä @{name}",
+  "account.last_status": "Aktiivinen viimeksi",
   "account.link_verified_on": "Tämän linkin omistaja tarkistettiin {date}",
   "account.locked_info": "Tämän tili on yksityinen. Käyttäjä vahvistaa itse kuka voi seurata häntä.",
   "account.media": "Media",
@@ -23,6 +25,7 @@
   "account.mute": "Mykistä @{name}",
   "account.mute_notifications": "Mykistä ilmoitukset käyttäjältä @{name}",
   "account.muted": "Mykistetty",
+  "account.never_active": "Ei koskaan",
   "account.posts": "Tuuttaukset",
   "account.posts_with_replies": "Tuuttaukset ja vastaukset",
   "account.report": "Raportoi @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Lakkaa seuraamasta",
   "account.unmute": "Poista käyttäjän @{name} mykistys",
   "account.unmute_notifications": "Poista mykistys käyttäjän @{name} ilmoituksilta",
+  "alert.rate_limited.message": "Yritä uudestaan {retry_time, time, medium} jälkeen.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Tapahtui odottamaton virhe.",
   "alert.unexpected.title": "Hups!",
+  "autosuggest_hashtag.per_week": "{count} viikossa",
   "boost_modal.combo": "Ensi kerralla voit ohittaa tämän painamalla {combo}",
   "bundle_column_error.body": "Jokin meni vikaan komponenttia ladattaessa.",
   "bundle_column_error.retry": "Yritä uudestaan",
@@ -47,6 +53,7 @@
   "column.blocks": "Estetyt käyttäjät",
   "column.community": "Paikallinen aikajana",
   "column.direct": "Viestit",
+  "column.directory": "Selaa profiileja",
   "column.domain_blocks": "Piilotetut verkkotunnukset",
   "column.favourites": "Suosikit",
   "column.follow_requests": "Seuraamispyynnöt",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Haluatko varmasti poistaa tämän listan kokonaan?",
   "confirmations.domain_block.confirm": "Piilota koko verkko-osoite",
   "confirmations.domain_block.message": "Haluatko aivan varmasti estää koko verkko-osoitteen {domain}? Useimmiten jokunen kohdistettu esto ja mykistys riittää, ja se on suositeltavampi tapa toimia.",
+  "confirmations.logout.confirm": "Kirjaudu ulos",
+  "confirmations.logout.message": "Oletko varma, että haluat kirjautua ulos?",
   "confirmations.mute.confirm": "Mykistä",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Haluatko varmasti mykistää käyttäjän {name}?",
   "confirmations.redraft.confirm": "Poista & palauta muokattavaksi",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Jos vastaat nyt, vastaus korvaa tällä hetkellä työstämäsi viestin. Oletko varma, että haluat jatkaa?",
   "confirmations.unfollow.confirm": "Lakkaa seuraamasta",
   "confirmations.unfollow.message": "Haluatko varmasti lakata seuraamasta käyttäjää {name}?",
+  "conversation.delete": "Poista keskustelu",
+  "conversation.mark_as_read": "Merkitse luetuksi",
+  "conversation.open": "Näytä keskustelu",
+  "conversation.with": "{names} kanssa",
+  "directory.federated": "Koko tunnettu fediverse",
+  "directory.local": "Vain palvelimelta {domain}",
+  "directory.new_arrivals": "Äskettäin saapuneet",
+  "directory.recently_active": "Hiljattain aktiiviset",
   "embed.instructions": "Upota statuspäivitys sivullesi kopioimalla alla oleva koodi.",
   "embed.preview": "Se tulee näyttämään tältä:",
   "emoji_button.activity": "Aktiviteetit",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Et ole mykistänyt vielä yhtään käyttäjää.",
   "empty_column.notifications": "Sinulle ei ole vielä ilmoituksia. Aloita keskustelu juttelemalla muille.",
   "empty_column.public": "Täällä ei ole mitään! Saat sisältöä, kun kirjoitat jotain julkisesti tai käyt seuraamassa muiden instanssien käyttäjiä",
+  "error.unexpected_crash.explanation": "Sivua ei voi näyttää oikein, johtuen bugista tai ongelmasta selaimen yhteensopivuudessa.",
+  "error.unexpected_crash.next_steps": "Kokeile päivittää sivu. Jos tämä ei auta, saatat yhä pystyä käyttämään Mastodonia toisen selaimen tai sovelluksen kautta.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Ilmoita ongelmasta",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -209,14 +231,14 @@
   "keyboard_shortcuts.search": "siirry hakukenttään",
   "keyboard_shortcuts.start": "avaa \"Aloitus\" -sarake",
   "keyboard_shortcuts.toggle_hidden": "näytä/piilota sisältövaroituksella merkitty teksti",
-  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.toggle_sensitivity": "näytä/piilota media",
   "keyboard_shortcuts.toot": "ala kirjoittaa uutta tuuttausta",
   "keyboard_shortcuts.unfocus": "siirry pois tekstikentästä tai hakukentästä",
   "keyboard_shortcuts.up": "siirry listassa ylöspäin",
   "lightbox.close": "Sulje",
   "lightbox.next": "Seuraava",
   "lightbox.previous": "Edellinen",
-  "lightbox.view_context": "View context",
+  "lightbox.view_context": "Näytä kontekstissa",
   "lists.account.add": "Lisää listaan",
   "lists.account.remove": "Poista listasta",
   "lists.delete": "Poista lista",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Uuden listan nimi",
   "lists.search": "Etsi seuraamistasi henkilöistä",
   "lists.subheading": "Omat listat",
+  "load_pending": "{count, plural, one {# uusi kappale} other {# uutta kappaletta}}",
   "loading_indicator.label": "Ladataan...",
   "media_gallery.toggle_visible": "Säädä näkyvyyttä",
   "missing_indicator.label": "Ei löytynyt",
@@ -236,56 +259,57 @@
   "navigation_bar.community_timeline": "Paikallinen aikajana",
   "navigation_bar.compose": "Kirjoita uusi tuuttaus",
   "navigation_bar.direct": "Viestit",
-  "navigation_bar.discover": "Discover",
+  "navigation_bar.discover": "Löydä uutta",
   "navigation_bar.domain_blocks": "Piilotetut verkkotunnukset",
   "navigation_bar.edit_profile": "Muokkaa profiilia",
   "navigation_bar.favourites": "Suosikit",
   "navigation_bar.filters": "Mykistetyt sanat",
   "navigation_bar.follow_requests": "Seuraamispyynnöt",
-  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.follows_and_followers": "Seurattavat ja seuraajat",
   "navigation_bar.info": "Tietoa tästä instanssista",
   "navigation_bar.keyboard_shortcuts": "Näppäinkomennot",
   "navigation_bar.lists": "Listat",
   "navigation_bar.logout": "Kirjaudu ulos",
   "navigation_bar.mutes": "Mykistetyt käyttäjät",
-  "navigation_bar.personal": "Personal",
+  "navigation_bar.personal": "Henkilökohtaiset",
   "navigation_bar.pins": "Kiinnitetyt tuuttaukset",
   "navigation_bar.preferences": "Asetukset",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Yleinen aikajana",
   "navigation_bar.security": "Tunnukset",
   "notification.favourite": "{name} tykkäsi tilastasi",
   "notification.follow": "{name} seurasi sinua",
   "notification.mention": "{name} mainitsi sinut",
-  "notification.poll": "A poll you have voted in has ended",
+  "notification.poll": "Kysely, johon osallistuit, on päättynyt",
   "notification.reblog": "{name} buustasi tilaasi",
   "notifications.clear": "Tyhjennä ilmoitukset",
   "notifications.clear_confirmation": "Haluatko varmasti poistaa kaikki ilmoitukset pysyvästi?",
   "notifications.column_settings.alert": "Työpöytäilmoitukset",
   "notifications.column_settings.favourite": "Tykkäykset:",
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
-  "notifications.column_settings.filter_bar.show": "Show",
+  "notifications.column_settings.filter_bar.advanced": "Näytä kaikki kategoriat",
+  "notifications.column_settings.filter_bar.category": "Pikasuodatuspalkki",
+  "notifications.column_settings.filter_bar.show": "Näytä",
   "notifications.column_settings.follow": "Uudet seuraajat:",
   "notifications.column_settings.mention": "Maininnat:",
-  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.poll": "Kyselyn tulokset:",
   "notifications.column_settings.push": "Push-ilmoitukset",
   "notifications.column_settings.reblog": "Buustit:",
   "notifications.column_settings.show": "Näytä sarakkeessa",
   "notifications.column_settings.sound": "Äänimerkki",
-  "notifications.filter.all": "All",
-  "notifications.filter.boosts": "Boosts",
-  "notifications.filter.favourites": "Favourites",
-  "notifications.filter.follows": "Follows",
-  "notifications.filter.mentions": "Mentions",
-  "notifications.filter.polls": "Poll results",
-  "notifications.group": "{count} notifications",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
-  "poll_button.add_poll": "Add a poll",
-  "poll_button.remove_poll": "Remove poll",
+  "notifications.filter.all": "Kaikki",
+  "notifications.filter.boosts": "Buustit",
+  "notifications.filter.favourites": "Suosikit",
+  "notifications.filter.follows": "Seuraa",
+  "notifications.filter.mentions": "Maininnat",
+  "notifications.filter.polls": "Kyselyn tulokset",
+  "notifications.group": "{count} ilmoitusta",
+  "poll.closed": "Suljettu",
+  "poll.refresh": "Päivitä",
+  "poll.total_people": "{count, plural, one {# henkilö} other {# henkilöä}}",
+  "poll.total_votes": "{count, plural, one {# ääni} other {# ääntä}}",
+  "poll.vote": "Äänestä",
+  "poll.voted": "Äänestit tätä vastausta",
+  "poll_button.add_poll": "Lisää kysely",
+  "poll_button.remove_poll": "Poista kysely",
   "privacy.change": "Säädä tuuttauksen näkyvyyttä",
   "privacy.direct.long": "Julkaise vain mainituille käyttäjille",
   "privacy.direct.short": "Suora viesti",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Julkinen",
   "privacy.unlisted.long": "Älä julkaise julkisilla aikajanoilla",
   "privacy.unlisted.short": "Listaamaton julkinen",
+  "refresh": "Päivitä",
   "regeneration_indicator.label": "Ladataan…",
   "regeneration_indicator.sublabel": "Kotinäkymääsi valmistellaan!",
   "relative_time.days": "{number} pv",
@@ -312,22 +337,23 @@
   "search.placeholder": "Hae",
   "search_popout.search_format": "Tarkennettu haku",
   "search_popout.tips.full_text": "Tekstihaku palauttaa tilapäivitykset, jotka olet kirjoittanut, lisännyt suosikkeihisi, boostannut tai joissa sinut mainitaan, sekä tekstin sisältävät käyttäjänimet, nimimerkit ja hastagit.",
-  "search_popout.tips.hashtag": "hashtag",
+  "search_popout.tips.hashtag": "hashtagit",
   "search_popout.tips.status": "tila",
   "search_popout.tips.text": "Tekstihaku palauttaa hakua vastaavat nimimerkit, käyttäjänimet ja hastagit",
   "search_popout.tips.user": "käyttäjä",
   "search_results.accounts": "Ihmiset",
   "search_results.hashtags": "Hashtagit",
   "search_results.statuses": "Tuuttaukset",
-  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
-  "status.admin_account": "Open moderation interface for @{name}",
-  "status.admin_status": "Open this status in the moderation interface",
+  "search_results.statuses_fts_disabled": "Tuuttausten haku sisällön perusteella ei ole käytössä tällä Mastodon-serverillä.",
+  "search_results.total": "{count, number} {count, plural, one {tulos} other {tulosta}}",
+  "status.admin_account": "Avaa moderaattorinäkymä tilistä @{name}",
+  "status.admin_status": "Avaa tilapäivitys moderaattorinäkymässä",
   "status.block": "Estä @{name}",
   "status.cancel_reblog_private": "Peru buustaus",
   "status.cannot_reblog": "Tätä julkaisua ei voi buustata",
-  "status.copy": "Copy link to status",
+  "status.copy": "Kopioi linkki tilapäivitykseen",
   "status.delete": "Poista",
-  "status.detailed_status": "Detailed conversation view",
+  "status.detailed_status": "Yksityiskohtainen keskustelunäkymä",
   "status.direct": "Viesti käyttäjälle @{name}",
   "status.embed": "Upota",
   "status.favourite": "Tykkää",
@@ -346,7 +372,7 @@
   "status.reblog": "Buustaa",
   "status.reblog_private": "Buustaa alkuperäiselle yleisölle",
   "status.reblogged_by": "{name} buustasi",
-  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
+  "status.reblogs.empty": "Kukaan ei ole vielä buustannut tätä tuuttausta. Kun joku tekee niin, näkyy kyseinen henkilö tässä.",
   "status.redraft": "Poista & palauta muokattavaksi",
   "status.reply": "Vastaa",
   "status.replyAll": "Vastaa ketjuun",
@@ -357,30 +383,39 @@
   "status.show_less_all": "Näytä vähemmän kaikista",
   "status.show_more": "Näytä lisää",
   "status.show_more_all": "Näytä lisää kaikista",
-  "status.show_thread": "Show thread",
+  "status.show_thread": "Näytä ketju",
+  "status.uncached_media_warning": "Ei saatavilla",
   "status.unmute_conversation": "Poista keskustelun mykistys",
   "status.unpin": "Irrota profiilista",
   "suggestions.dismiss": "Dismiss suggestion",
-  "suggestions.header": "You might be interested in…",
+  "suggestions.header": "Saatat olla kiinnostunut myös…",
   "tabs_bar.federated_timeline": "Yleinen",
   "tabs_bar.home": "Koti",
   "tabs_bar.local_timeline": "Paikallinen",
   "tabs_bar.notifications": "Ilmoitukset",
   "tabs_bar.search": "Hae",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "time_remaining.days": "{number, plural, one {# päivä} other {# päivää}} jäljellä",
+  "time_remaining.hours": "{number, plural, one {# tunti} other {# tuntia}} jäljellä",
+  "time_remaining.minutes": "{number, plural, one {# minuutti} other {# minuuttia}} jäljellä",
+  "time_remaining.moments": "Hetki jäljellä",
+  "time_remaining.seconds": "{number, plural, one {# sekunti} other {# sekuntia}} jäljellä",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {henkilö} other {henkilöä}} keskustelee",
+  "trends.trending_now": "Suosittua nyt",
   "ui.beforeunload": "Luonnos häviää, jos poistut Mastodonista.",
   "upload_area.title": "Lataa raahaamalla ja pudottamalla tähän",
   "upload_button.label": "Lisää mediaa",
-  "upload_error.limit": "File upload limit exceeded.",
-  "upload_error.poll": "File upload not allowed with polls.",
+  "upload_error.limit": "Tiedostolatauksien raja ylitetty.",
+  "upload_error.poll": "Tiedon lataaminen ei ole sallittua kyselyissä.",
   "upload_form.description": "Anna kuvaus näkörajoitteisia varten",
-  "upload_form.focus": "Rajaa",
+  "upload_form.edit": "Muokkaa",
   "upload_form.undo": "Peru",
+  "upload_modal.analyzing_picture": "Analysoidaan kuvaa…",
+  "upload_modal.apply": "Käytä",
+  "upload_modal.description_placeholder": "Eräänä jäätävänä ja pimeänä yönä gorilla ratkaisi sudokun kahdessa minuutissa",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Ladataan...",
   "video.close": "Sulje video",
   "video.exit_fullscreen": "Poistu koko näytön tilasta",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index 507e37df31ad954b9f830eb9b0be6e3f6ee25d82..9568b54f0f9b73a721dcacb8f0b4dd3db0353b67 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -4,10 +4,11 @@
   "account.block": "Bloquer @{name}",
   "account.block_domain": "Tout masquer venant de {domain}",
   "account.blocked": "Bloqué",
+  "account.cancel_follow_request": "Annuler la demande de suivi",
   "account.direct": "Envoyer un message direct à @{name}",
   "account.domain_blocked": "Domaine caché",
   "account.edit_profile": "Modifier le profil",
-  "account.endorse": "Figure sur le profil",
+  "account.endorse": "Recommander sur le profil",
   "account.follow": "Suivre",
   "account.followers": "Abonné⋅e⋅s",
   "account.followers.empty": "Personne ne suit cet utilisateur·rice pour l’instant.",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Cet·te utilisateur·rice ne suit personne pour l’instant.",
   "account.follows_you": "Vous suit",
   "account.hide_reblogs": "Masquer les partages de @{name}",
+  "account.last_status": "Dernière activité",
   "account.link_verified_on": "La propriété de ce lien a été vérifiée le {date}",
   "account.locked_info": "Ce compte est verrouillé. Son propriétaire approuve manuellement qui peut le ou la suivre.",
   "account.media": "Média",
@@ -23,6 +25,7 @@
   "account.mute": "Masquer @{name}",
   "account.mute_notifications": "Ignorer les notifications de @{name}",
   "account.muted": "Silencé",
+  "account.never_active": "Jamais",
   "account.posts": "Pouets",
   "account.posts_with_replies": "Pouets et réponses",
   "account.report": "Signaler @{name}",
@@ -31,13 +34,16 @@
   "account.show_reblogs": "Afficher les partages de @{name}",
   "account.unblock": "Débloquer @{name}",
   "account.unblock_domain": "Ne plus masquer {domain}",
-  "account.unendorse": "Ne figure pas sur le profil",
+  "account.unendorse": "Ne plus recommander sur le profil",
   "account.unfollow": "Ne plus suivre",
   "account.unmute": "Ne plus masquer @{name}",
   "account.unmute_notifications": "Réactiver les notifications de @{name}",
+  "alert.rate_limited.message": "Veuillez réessayer après {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Débit limité",
   "alert.unexpected.message": "Une erreur inattendue s’est produite.",
   "alert.unexpected.title": "Oups !",
-  "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois",
+  "autosuggest_hashtag.per_week": "{count} par semaine",
+  "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour passer ceci, la prochaine fois",
   "bundle_column_error.body": "Une erreur s’est produite lors du chargement de ce composant.",
   "bundle_column_error.retry": "Réessayer",
   "bundle_column_error.title": "Erreur réseau",
@@ -46,7 +52,8 @@
   "bundle_modal_error.retry": "Réessayer",
   "column.blocks": "Comptes bloqués",
   "column.community": "Fil public local",
-  "column.direct": "Messages directs",
+  "column.direct": "Messages privés",
+  "column.directory": "Parcourir les profils",
   "column.domain_blocks": "Domaines cachés",
   "column.favourites": "Favoris",
   "column.follow_requests": "Demandes de suivi",
@@ -62,11 +69,11 @@
   "column_header.moveRight_settings": "Déplacer la colonne vers la droite",
   "column_header.pin": "Épingler",
   "column_header.show_settings": "Afficher les paramètres",
-  "column_header.unpin": "Retirer",
+  "column_header.unpin": "Désépingler",
   "column_subheading.settings": "Paramètres",
   "community.column_settings.media_only": "Média uniquement",
   "compose_form.direct_message_warning": "Ce pouet sera uniquement envoyé aux personnes mentionnées. Cependant, l’administration de votre instance et des instances réceptrices pourront inspecter ce message.",
-  "compose_form.direct_message_warning_learn_more": "En savoir plus",
+  "compose_form.direct_message_warning_learn_more": "Plus d’informations",
   "compose_form.hashtag_warning": "Ce pouet ne sera pas listé dans les recherches par hashtag car sa visibilité est réglée sur \"non listé\". Seuls les pouets avec une visibilité \"publique\" peuvent être recherchés par hashtag.",
   "compose_form.lock_disclaimer": "Votre compte n’est pas {locked}. Tout le monde peut vous suivre et voir vos pouets privés.",
   "compose_form.lock_disclaimer.lock": "verrouillé",
@@ -79,7 +86,7 @@
   "compose_form.publish_loud": "{publish} !",
   "compose_form.sensitive.hide": "Marquer le média comme sensible",
   "compose_form.sensitive.marked": "Média marqué comme sensible",
-  "compose_form.sensitive.unmarked": "Média non marqué comme sensible",
+  "compose_form.sensitive.unmarked": "Le média n’est pas marqué comme sensible",
   "compose_form.spoiler.marked": "Le texte est caché derrière un avertissement",
   "compose_form.spoiler.unmarked": "Le texte n’est pas caché",
   "compose_form.spoiler_placeholder": "Écrivez ici votre avertissement",
@@ -93,25 +100,36 @@
   "confirmations.delete_list.message": "Êtes-vous sûr·e de vouloir supprimer définitivement cette liste ?",
   "confirmations.domain_block.confirm": "Masquer le domaine entier",
   "confirmations.domain_block.message": "Êtes-vous vraiment, vraiment sûr⋅e de vouloir bloquer {domain} en entier ? Dans la plupart des cas, quelques blocages ou masquages ciblés sont suffisants et préférables. Vous ne verrez plus de contenu provenant de ce domaine, ni dans fils publics, ni dans vos notifications. Vos abonné·e·s utilisant ce domaine seront retiré·e·s.",
+  "confirmations.logout.confirm": "Déconnexion",
+  "confirmations.logout.message": "Êtes-vous sûr de vouloir vous déconnecter ?",
   "confirmations.mute.confirm": "Masquer",
-  "confirmations.mute.message": "Confirmez-vous le masquage de {name} ?",
+  "confirmations.mute.explanation": "Cela masquera leurs messages et les messages les mentionnant, mais cela leur permettra quand même de voir vos messages et vous suivre.",
+  "confirmations.mute.message": "Êtes-vous sûr·e de vouloir masquer {name} ?",
   "confirmations.redraft.confirm": "Effacer et ré-écrire",
   "confirmations.redraft.message": "Êtes-vous sûr·e de vouloir effacer ce statut pour le ré-écrire ? Ses partages ainsi que ses mises en favori seront perdu·e·s et ses réponses seront orphelines.",
   "confirmations.reply.confirm": "Répondre",
-  "confirmations.reply.message": "Répondre maintenant écrasera le message que vous êtes en train de composer. Voulez-vous vraiment continuer ?",
+  "confirmations.reply.message": "Répondre maintenant écrasera le message que vous composez actuellement. Êtes-vous sûr de vouloir continuer ?",
   "confirmations.unfollow.confirm": "Ne plus suivre",
   "confirmations.unfollow.message": "Voulez-vous arrêter de suivre {name} ?",
+  "conversation.delete": "Supprimer la conversation",
+  "conversation.mark_as_read": "Marquer comme lu",
+  "conversation.open": "Afficher la conversation",
+  "conversation.with": "Avec {names}",
+  "directory.federated": "Du fédiverse connu",
+  "directory.local": "De {domain} seulement",
+  "directory.new_arrivals": "Nouveaux arrivants",
+  "directory.recently_active": "Récemment actif",
   "embed.instructions": "Intégrez ce statut à votre site en copiant le code ci-dessous.",
   "embed.preview": "Il apparaîtra comme cela :",
   "emoji_button.activity": "Activités",
-  "emoji_button.custom": "Personnalisés",
+  "emoji_button.custom": "Personnalisé",
   "emoji_button.flags": "Drapeaux",
   "emoji_button.food": "Nourriture & Boisson",
   "emoji_button.label": "Insérer un émoji",
   "emoji_button.nature": "Nature",
   "emoji_button.not_found": "Pas d’émoji !! (╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "Objets",
-  "emoji_button.people": "Personnages",
+  "emoji_button.people": "Personnes",
   "emoji_button.recent": "Fréquemment utilisés",
   "emoji_button.search": "Recherche…",
   "emoji_button.search_results": "Résultats de la recherche",
@@ -131,8 +149,13 @@
   "empty_column.home.public_timeline": "le fil public",
   "empty_column.list": "Il n’y a rien dans cette liste pour l’instant. Dès que des personnes de cette liste publieront de nouveaux statuts, ils apparaîtront ici.",
   "empty_column.lists": "Vous n’avez pas encore de liste. Lorsque vous en créerez une, elle apparaîtra ici.",
-  "empty_column.mutes": "Vous n’avez pas encore mis d'utilisateur·rice·s en silence.",
+  "empty_column.mutes": "Vous n’avez pas encore mis d’utilisateur·rice·s en silence.",
   "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres personnes pour débuter la conversation.",
+  "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des personnes d’autres instances pour le remplir",
+  "error.unexpected_crash.explanation": "En raison d’un bug dans notre code ou d’un problème de compatibilité avec le navigateur, cette page n’a pas pu être affichée correctement.",
+  "error.unexpected_crash.next_steps": "Essayez de rafraîchir la page. Si cela n’aide pas, vous pouvez toujours utiliser Mastodon via un autre navigateur ou une application native.",
+  "errors.unexpected_crash.copy_stacktrace": "Copier la trace-pile dans le presse-papier",
+  "errors.unexpected_crash.report_issue": "Signaler un bogue",
   "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des personnes d’autres instances pour remplir le fil public",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
@@ -153,12 +176,12 @@
   "hashtag.column_header.tag_mode.any": "ou {additional}",
   "hashtag.column_header.tag_mode.none": "sans {additional}",
   "hashtag.column_settings.select.no_options_message": "Aucune suggestion trouvée",
-  "hashtag.column_settings.select.placeholder": "Ajouter des hashtags…",
+  "hashtag.column_settings.select.placeholder": "Ajouter des mots-clés…",
   "hashtag.column_settings.tag_mode.all": "Tous ces éléments",
   "hashtag.column_settings.tag_mode.any": "Au moins un de ces éléments",
   "hashtag.column_settings.tag_mode.none": "Aucun de ces éléments",
-  "hashtag.column_settings.tag_toggle": "Inclure des tags additionnels dans cette colonne",
-  "home.column_settings.basic": "Basique",
+  "hashtag.column_settings.tag_toggle": "Inclure des mots-clés additionnels dans cette colonne",
+  "home.column_settings.basic": "Base",
   "home.column_settings.show_reblogs": "Afficher les partages",
   "home.column_settings.show_replies": "Afficher les réponses",
   "intervals.full.days": "{number, plural, one {# jour} other {# jours}}",
@@ -173,7 +196,7 @@
   "introduction.federation.local.text": "Les messages publics de personnes se trouvant sur le même serveur que vous apparaîtront sur le fil public local.",
   "introduction.interactions.action": "Finir le tutoriel !",
   "introduction.interactions.favourite.headline": "Favoris",
-  "introduction.interactions.favourite.text": "Vous pouvez garder un pouet pour plus tard, et faire savoir à son auteur·ice que vous l'avez aimé, en le favorisant.",
+  "introduction.interactions.favourite.text": "Vous pouvez garder un pouet pour plus tard et faire savoir à son auteur·ice que vous l’avez aimé, en le favorisant.",
   "introduction.interactions.reblog.headline": "Repartager",
   "introduction.interactions.reblog.text": "Vous pouvez partager les pouets d'autres personnes avec vos abonné·e·s en les repartageant.",
   "introduction.interactions.reply.headline": "Répondre",
@@ -185,7 +208,7 @@
   "keyboard_shortcuts.blocked": "pour ouvrir une liste d’utilisateur·rice·s bloqué·e·s",
   "keyboard_shortcuts.boost": "pour partager",
   "keyboard_shortcuts.column": "pour focaliser un statut dans l’une des colonnes",
-  "keyboard_shortcuts.compose": "pour centrer la zone de rédaction",
+  "keyboard_shortcuts.compose": "pour focaliser la zone de rédaction",
   "keyboard_shortcuts.description": "Description",
   "keyboard_shortcuts.direct": "pour ouvrir une colonne des messages directs",
   "keyboard_shortcuts.down": "pour descendre dans la liste",
@@ -226,6 +249,7 @@
   "lists.new.title_placeholder": "Titre de la nouvelle liste",
   "lists.search": "Rechercher parmi les gens que vous suivez",
   "lists.subheading": "Vos listes",
+  "load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}",
   "loading_indicator.label": "Chargement…",
   "media_gallery.toggle_visible": "Modifier la visibilité",
   "missing_indicator.label": "Non trouvé",
@@ -243,7 +267,7 @@
   "navigation_bar.filters": "Mots silenciés",
   "navigation_bar.follow_requests": "Demandes de suivi",
   "navigation_bar.follows_and_followers": "Abonnements et abonné⋅e·s",
-  "navigation_bar.info": "Plus d’informations",
+  "navigation_bar.info": "À propos de ce serveur",
   "navigation_bar.keyboard_shortcuts": "Raccourcis clavier",
   "navigation_bar.lists": "Listes",
   "navigation_bar.logout": "Déconnexion",
@@ -251,7 +275,6 @@
   "navigation_bar.personal": "Personnel",
   "navigation_bar.pins": "Pouets épinglés",
   "navigation_bar.preferences": "Préférences",
-  "navigation_bar.profile_directory": "Annuaire des profils",
   "navigation_bar.public_timeline": "Fil public global",
   "navigation_bar.security": "Sécurité",
   "notification.favourite": "{name} a ajouté à ses favoris :",
@@ -282,8 +305,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "Fermé",
   "poll.refresh": "Actualiser",
+  "poll.total_people": "{count, plural, one {# personne} other {# personnes}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Voter",
+  "poll.voted": "Vous avez voté pour cette réponse",
   "poll_button.add_poll": "Ajouter un sondage",
   "poll_button.remove_poll": "Supprimer le sondage",
   "privacy.change": "Ajuster la confidentialité du message",
@@ -295,6 +320,7 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Ne pas afficher dans les fils publics",
   "privacy.unlisted.short": "Non listé",
+  "refresh": "Actualiser",
   "regeneration_indicator.label": "Chargement…",
   "regeneration_indicator.sublabel": "Le flux de votre page principale est en cours de préparation !",
   "relative_time.days": "{number} j",
@@ -319,9 +345,10 @@
   "search_results.accounts": "Comptes",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Pouets",
+  "search_results.statuses_fts_disabled": "La recherche de pouets par leur contenu n'est pas activée sur ce serveur Mastodon.",
   "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
-  "status.admin_account": "Ouvrir l'interface de modération pour @{name}",
-  "status.admin_status": "Ouvrir ce statut dans l'interface de modération",
+  "status.admin_account": "Ouvrir l’interface de modération pour @{name}",
+  "status.admin_status": "Ouvrir ce statut dans l’interface de modération",
   "status.block": "Bloquer @{name}",
   "status.cancel_reblog_private": "Dé-booster",
   "status.cannot_reblog": "Cette publication ne peut être boostée",
@@ -358,6 +385,7 @@
   "status.show_more": "Déplier",
   "status.show_more_all": "Tout déplier",
   "status.show_thread": "Lire le fil",
+  "status.uncached_media_warning": "Indisponible",
   "status.unmute_conversation": "Ne plus masquer la conversation",
   "status.unpin": "Retirer du profil",
   "suggestions.dismiss": "Rejeter la suggestion",
@@ -373,14 +401,22 @@
   "time_remaining.moments": "Encore quelques instants",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} restantes",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {personne} other {personnes}} discutent",
+  "trends.trending_now": "Tendance en ce moment",
   "ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.",
   "upload_area.title": "Glissez et déposez pour envoyer",
-  "upload_button.label": "Joindre un média (JPEG, PNG, GIF, WebM, MP4, MOV)",
+  "upload_button.label": "Joindre un média ({formats})",
   "upload_error.limit": "Taille maximale d'envoi de fichier dépassée.",
-  "upload_error.poll": "L'envoi de fichiers n'est pas autorisé avec les sondages.",
+  "upload_error.poll": "L’envoi de fichiers n’est pas autorisé avec les sondages.",
   "upload_form.description": "Décrire pour les malvoyant·e·s",
-  "upload_form.focus": "Modifier l’aperçu",
+  "upload_form.edit": "Modifier",
   "upload_form.undo": "Supprimer",
+  "upload_modal.analyzing_picture": "Analyse de l’image en cours…",
+  "upload_modal.apply": "Appliquer",
+  "upload_modal.description_placeholder": "Buvez de ce whisky que le patron juge fameux",
+  "upload_modal.detect_text": "Détecter le texte de l’image",
+  "upload_modal.edit_media": "Modifier le média",
+  "upload_modal.hint": "Cliquez ou faites glisser le cercle sur l’aperçu pour choisir le point focal qui sera toujours visible sur toutes les miniatures.",
+  "upload_modal.preview_label": "Aperçu ({ratio})",
   "upload_progress.label": "Envoi en cours…",
   "video.close": "Fermer la vidéo",
   "video.exit_fullscreen": "Quitter le plein écran",
diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json
new file mode 100644
index 0000000000000000000000000000000000000000..2dd0dbbad97700e96e9027190825dd74d3b3e719
--- /dev/null
+++ b/app/javascript/mastodon/locales/ga.json
@@ -0,0 +1,423 @@
+{
+  "account.add_or_remove_from_list": "Add or Remove from lists",
+  "account.badges.bot": "Bot",
+  "account.block": "Block @{name}",
+  "account.block_domain": "Hide everything from {domain}",
+  "account.blocked": "Blocked",
+  "account.cancel_follow_request": "Cancel follow request",
+  "account.direct": "Direct message @{name}",
+  "account.domain_blocked": "Domain hidden",
+  "account.edit_profile": "Edit profile",
+  "account.endorse": "Feature on profile",
+  "account.follow": "Follow",
+  "account.followers": "Followers",
+  "account.followers.empty": "No one follows this user yet.",
+  "account.follows": "Follows",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
+  "account.follows_you": "Follows you",
+  "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.last_status": "Last active",
+  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
+  "account.media": "Media",
+  "account.mention": "Mention @{name}",
+  "account.moved_to": "{name} has moved to:",
+  "account.mute": "Mute @{name}",
+  "account.mute_notifications": "Mute notifications from @{name}",
+  "account.muted": "Muted",
+  "account.never_active": "Never",
+  "account.posts": "Toots",
+  "account.posts_with_replies": "Toots and replies",
+  "account.report": "Report @{name}",
+  "account.requested": "Awaiting approval",
+  "account.share": "Share @{name}'s profile",
+  "account.show_reblogs": "Show boosts from @{name}",
+  "account.unblock": "Unblock @{name}",
+  "account.unblock_domain": "Unhide {domain}",
+  "account.unendorse": "Don't feature on profile",
+  "account.unfollow": "Unfollow",
+  "account.unmute": "Unmute @{name}",
+  "account.unmute_notifications": "Unmute notifications from @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
+  "alert.unexpected.message": "An unexpected error occurred.",
+  "alert.unexpected.title": "Oops!",
+  "autosuggest_hashtag.per_week": "{count} per week",
+  "boost_modal.combo": "You can press {combo} to skip this next time",
+  "bundle_column_error.body": "Something went wrong while loading this component.",
+  "bundle_column_error.retry": "Try again",
+  "bundle_column_error.title": "Network error",
+  "bundle_modal_error.close": "Close",
+  "bundle_modal_error.message": "Something went wrong while loading this component.",
+  "bundle_modal_error.retry": "Try again",
+  "column.blocks": "Blocked users",
+  "column.community": "Local timeline",
+  "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
+  "column.domain_blocks": "Hidden domains",
+  "column.favourites": "Favourites",
+  "column.follow_requests": "Follow requests",
+  "column.home": "Home",
+  "column.lists": "Lists",
+  "column.mutes": "Muted users",
+  "column.notifications": "Notifications",
+  "column.pins": "Pinned toot",
+  "column.public": "Federated timeline",
+  "column_back_button.label": "Back",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
+  "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
+  "column_header.unpin": "Unpin",
+  "column_subheading.settings": "Settings",
+  "community.column_settings.media_only": "Media only",
+  "compose_form.direct_message_warning": "This toot will only be sent to all the mentioned users.",
+  "compose_form.direct_message_warning_learn_more": "Learn more",
+  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+  "compose_form.lock_disclaimer.lock": "locked",
+  "compose_form.placeholder": "What is on your mind?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
+  "compose_form.publish": "Toot",
+  "compose_form.publish_loud": "{publish}!",
+  "compose_form.sensitive.hide": "Mark media as sensitive",
+  "compose_form.sensitive.marked": "Media is marked as sensitive",
+  "compose_form.sensitive.unmarked": "Media is not marked as sensitive",
+  "compose_form.spoiler.marked": "Text is hidden behind warning",
+  "compose_form.spoiler.unmarked": "Text is not hidden",
+  "compose_form.spoiler_placeholder": "Write your warning here",
+  "confirmation_modal.cancel": "Cancel",
+  "confirmations.block.block_and_report": "Block & Report",
+  "confirmations.block.confirm": "Block",
+  "confirmations.block.message": "Are you sure you want to block {name}?",
+  "confirmations.delete.confirm": "Delete",
+  "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.delete_list.confirm": "Delete",
+  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
+  "confirmations.mute.confirm": "Mute",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
+  "confirmations.mute.message": "Are you sure you want to mute {name}?",
+  "confirmations.redraft.confirm": "Delete & redraft",
+  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
+  "confirmations.reply.confirm": "Reply",
+  "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+  "confirmations.unfollow.confirm": "Unfollow",
+  "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
+  "embed.instructions": "Embed this status on your website by copying the code below.",
+  "embed.preview": "Here is what it will look like:",
+  "emoji_button.activity": "Activity",
+  "emoji_button.custom": "Custom",
+  "emoji_button.flags": "Flags",
+  "emoji_button.food": "Food & Drink",
+  "emoji_button.label": "Insert emoji",
+  "emoji_button.nature": "Nature",
+  "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Objects",
+  "emoji_button.people": "People",
+  "emoji_button.recent": "Frequently used",
+  "emoji_button.search": "Search...",
+  "emoji_button.search_results": "Search results",
+  "emoji_button.symbols": "Symbols",
+  "emoji_button.travel": "Travel & Places",
+  "empty_column.account_timeline": "No toots here!",
+  "empty_column.account_unavailable": "Profile unavailable",
+  "empty_column.blocks": "You haven't blocked any users yet.",
+  "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
+  "empty_column.hashtag": "There is nothing in this hashtag yet.",
+  "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
+  "empty_column.home.public_timeline": "the public timeline",
+  "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
+  "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+  "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
+  "follow_request.authorize": "Authorize",
+  "follow_request.reject": "Reject",
+  "getting_started.developers": "Developers",
+  "getting_started.directory": "Profile directory",
+  "getting_started.documentation": "Documentation",
+  "getting_started.heading": "Getting started",
+  "getting_started.invite": "Invite people",
+  "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
+  "getting_started.security": "Security",
+  "getting_started.terms": "Terms of service",
+  "hashtag.column_header.tag_mode.all": "and {additional}",
+  "hashtag.column_header.tag_mode.any": "or {additional}",
+  "hashtag.column_header.tag_mode.none": "without {additional}",
+  "hashtag.column_settings.select.no_options_message": "No suggestions found",
+  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
+  "hashtag.column_settings.tag_mode.all": "All of these",
+  "hashtag.column_settings.tag_mode.any": "Any of these",
+  "hashtag.column_settings.tag_mode.none": "None of these",
+  "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
+  "home.column_settings.basic": "Basic",
+  "home.column_settings.show_reblogs": "Show boosts",
+  "home.column_settings.show_replies": "Show replies",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "introduction.federation.action": "Next",
+  "introduction.federation.federated.headline": "Federated",
+  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
+  "introduction.federation.home.headline": "Home",
+  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
+  "introduction.federation.local.headline": "Local",
+  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
+  "introduction.interactions.action": "Finish toot-orial!",
+  "introduction.interactions.favourite.headline": "Favourite",
+  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
+  "introduction.interactions.reblog.headline": "Boost",
+  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
+  "introduction.interactions.reply.headline": "Reply",
+  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
+  "introduction.welcome.action": "Let's go!",
+  "introduction.welcome.headline": "First steps",
+  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
+  "keyboard_shortcuts.back": "to navigate back",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
+  "keyboard_shortcuts.boost": "to boost",
+  "keyboard_shortcuts.column": "to focus a status in one of the columns",
+  "keyboard_shortcuts.compose": "to focus the compose textarea",
+  "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
+  "keyboard_shortcuts.down": "to move down in the list",
+  "keyboard_shortcuts.enter": "to open status",
+  "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
+  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
+  "keyboard_shortcuts.hotkey": "Hotkey",
+  "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
+  "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
+  "keyboard_shortcuts.profile": "to open author's profile",
+  "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
+  "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
+  "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
+  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.toot": "to start a brand new toot",
+  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
+  "keyboard_shortcuts.up": "to move up in the list",
+  "lightbox.close": "Close",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
+  "lightbox.view_context": "View context",
+  "lists.account.add": "Add to list",
+  "lists.account.remove": "Remove from list",
+  "lists.delete": "Delete list",
+  "lists.edit": "Edit list",
+  "lists.edit.submit": "Change title",
+  "lists.new.create": "Add list",
+  "lists.new.title_placeholder": "New list title",
+  "lists.search": "Search among people you follow",
+  "lists.subheading": "Your lists",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
+  "loading_indicator.label": "Loading...",
+  "media_gallery.toggle_visible": "Toggle visibility",
+  "missing_indicator.label": "Not found",
+  "missing_indicator.sublabel": "This resource could not be found",
+  "mute_modal.hide_notifications": "Hide notifications from this user?",
+  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.blocks": "Blocked users",
+  "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.compose": "Compose new toot",
+  "navigation_bar.direct": "Direct messages",
+  "navigation_bar.discover": "Discover",
+  "navigation_bar.domain_blocks": "Hidden domains",
+  "navigation_bar.edit_profile": "Edit profile",
+  "navigation_bar.favourites": "Favourites",
+  "navigation_bar.filters": "Muted words",
+  "navigation_bar.follow_requests": "Follow requests",
+  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.info": "About this server",
+  "navigation_bar.keyboard_shortcuts": "Hotkeys",
+  "navigation_bar.lists": "Lists",
+  "navigation_bar.logout": "Logout",
+  "navigation_bar.mutes": "Muted users",
+  "navigation_bar.personal": "Personal",
+  "navigation_bar.pins": "Pinned toots",
+  "navigation_bar.preferences": "Preferences",
+  "navigation_bar.public_timeline": "Federated timeline",
+  "navigation_bar.security": "Security",
+  "notification.favourite": "{name} favourited your status",
+  "notification.follow": "{name} followed you",
+  "notification.mention": "{name} mentioned you",
+  "notification.poll": "A poll you have voted in has ended",
+  "notification.reblog": "{name} boosted your status",
+  "notifications.clear": "Clear notifications",
+  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+  "notifications.column_settings.alert": "Desktop notifications",
+  "notifications.column_settings.favourite": "Favourites:",
+  "notifications.column_settings.filter_bar.advanced": "Display all categories",
+  "notifications.column_settings.filter_bar.category": "Quick filter bar",
+  "notifications.column_settings.filter_bar.show": "Show",
+  "notifications.column_settings.follow": "New followers:",
+  "notifications.column_settings.mention": "Mentions:",
+  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.push": "Push notifications",
+  "notifications.column_settings.reblog": "Boosts:",
+  "notifications.column_settings.show": "Show in column",
+  "notifications.column_settings.sound": "Play sound",
+  "notifications.filter.all": "All",
+  "notifications.filter.boosts": "Boosts",
+  "notifications.filter.favourites": "Favourites",
+  "notifications.filter.follows": "Follows",
+  "notifications.filter.mentions": "Mentions",
+  "notifications.filter.polls": "Poll results",
+  "notifications.group": "{count} notifications",
+  "poll.closed": "Closed",
+  "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
+  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
+  "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
+  "privacy.change": "Adjust status privacy",
+  "privacy.direct.long": "Post to mentioned users only",
+  "privacy.direct.short": "Direct",
+  "privacy.private.long": "Post to followers only",
+  "privacy.private.short": "Followers-only",
+  "privacy.public.long": "Post to public timelines",
+  "privacy.public.short": "Public",
+  "privacy.unlisted.long": "Do not show in public timelines",
+  "privacy.unlisted.short": "Unlisted",
+  "refresh": "Refresh",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
+  "relative_time.days": "{number}d",
+  "relative_time.hours": "{number}h",
+  "relative_time.just_now": "now",
+  "relative_time.minutes": "{number}m",
+  "relative_time.seconds": "{number}s",
+  "reply_indicator.cancel": "Cancel",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:",
+  "report.placeholder": "Additional comments",
+  "report.submit": "Submit",
+  "report.target": "Report {target}",
+  "search.placeholder": "Search",
+  "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+  "search_popout.tips.hashtag": "hashtag",
+  "search_popout.tips.status": "status",
+  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
+  "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
+  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+  "status.admin_account": "Open moderation interface for @{name}",
+  "status.admin_status": "Open this status in the moderation interface",
+  "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
+  "status.cannot_reblog": "This post cannot be boosted",
+  "status.copy": "Copy link to status",
+  "status.delete": "Delete",
+  "status.detailed_status": "Detailed conversation view",
+  "status.direct": "Direct message @{name}",
+  "status.embed": "Embed",
+  "status.favourite": "Favourite",
+  "status.filtered": "Filtered",
+  "status.load_more": "Load more",
+  "status.media_hidden": "Media hidden",
+  "status.mention": "Mention @{name}",
+  "status.more": "More",
+  "status.mute": "Mute @{name}",
+  "status.mute_conversation": "Mute conversation",
+  "status.open": "Expand this status",
+  "status.pin": "Pin on profile",
+  "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
+  "status.reblog": "Boost",
+  "status.reblog_private": "Boost to original audience",
+  "status.reblogged_by": "{name} boosted",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
+  "status.redraft": "Delete & re-draft",
+  "status.reply": "Reply",
+  "status.replyAll": "Reply to thread",
+  "status.report": "Report @{name}",
+  "status.sensitive_warning": "Sensitive content",
+  "status.share": "Share",
+  "status.show_less": "Show less",
+  "status.show_less_all": "Show less for all",
+  "status.show_more": "Show more",
+  "status.show_more_all": "Show more for all",
+  "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
+  "status.unmute_conversation": "Unmute conversation",
+  "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
+  "tabs_bar.federated_timeline": "Federated",
+  "tabs_bar.home": "Home",
+  "tabs_bar.local_timeline": "Local",
+  "tabs_bar.notifications": "Notifications",
+  "tabs_bar.search": "Search",
+  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
+  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
+  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
+  "time_remaining.moments": "Moments remaining",
+  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
+  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
+  "upload_area.title": "Drag & drop to upload",
+  "upload_button.label": "Add media ({formats})",
+  "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.edit": "Edit",
+  "upload_form.undo": "Delete",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
+  "upload_progress.label": "Uploading...",
+  "video.close": "Close video",
+  "video.exit_fullscreen": "Exit full screen",
+  "video.expand": "Expand video",
+  "video.fullscreen": "Full screen",
+  "video.hide": "Hide video",
+  "video.mute": "Mute sound",
+  "video.pause": "Pause",
+  "video.play": "Play",
+  "video.unmute": "Unmute sound"
+}
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index 8fbdabd0d116e1b6cf2758301fd30d340e7995e2..bee0384c33f438f428511fbd01c1b8934cd3adeb 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -4,6 +4,7 @@
   "account.block": "Bloquear @{name}",
   "account.block_domain": "Ocultar calquer contido de {domain}",
   "account.blocked": "Bloqueada",
+  "account.cancel_follow_request": "Cancelar petición de seguemento",
   "account.direct": "Mensaxe directa @{name}",
   "account.domain_blocked": "Dominio agochado",
   "account.edit_profile": "Editar perfil",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Esta usuaria aínda non segue a ninguén.",
   "account.follows_you": "Séguete",
   "account.hide_reblogs": "Ocultar repeticións de @{name}",
+  "account.last_status": "Último activo",
   "account.link_verified_on": "A propiedade de esta ligazón foi comprobada en {date}",
   "account.locked_info": "O estado da intimidade de esta conta estableceuse en pechado. A persoa dona da conta revisa quen pode seguila.",
   "account.media": "Medios",
@@ -23,6 +25,7 @@
   "account.mute": "Acalar @{name}",
   "account.mute_notifications": "Acalar as notificacións de @{name}",
   "account.muted": "Acalada",
+  "account.never_active": "Nunca",
   "account.posts": "Toots",
   "account.posts_with_replies": "Toots e respostas",
   "account.report": "Informar sobre @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Non seguir",
   "account.unmute": "Non acalar @{name}",
   "account.unmute_notifications": "Desbloquear as notificacións de @{name}",
+  "alert.rate_limited.message": "Por favor inténteo tras {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Taxa limitada",
   "alert.unexpected.message": "Aconteceu un fallo non agardado.",
   "alert.unexpected.title": "Vaia!",
+  "autosuggest_hashtag.per_week": "{count} por semana",
   "boost_modal.combo": "Pulse {combo} para saltar esto a próxima vez",
   "bundle_column_error.body": "Houbo un fallo mentras se cargaba este compoñente.",
   "bundle_column_error.retry": "Inténteo de novo",
@@ -47,6 +53,7 @@
   "column.blocks": "Usuarias bloqueadas",
   "column.community": "Liña temporal local",
   "column.direct": "Mensaxes directas",
+  "column.directory": "Ver perfiles",
   "column.domain_blocks": "Dominios agochados",
   "column.favourites": "Favoritas",
   "column.follow_requests": "Peticións de seguimento",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Estás seguro de que queres eliminar permanentemente esta lista?",
   "confirmations.domain_block.confirm": "Agochar un dominio completo",
   "confirmations.domain_block.message": "Realmente está segura de que quere bloquear por completo o dominio {domain}? Normalmente é suficiente, e preferible, bloquear de xeito selectivo varios elementos. Non verá contidos de ese dominio en ningunha liña temporal ou nas notificacións. As súas seguidoras en ese dominio serán eliminadas.",
+  "confirmations.logout.confirm": "Desconectar",
+  "confirmations.logout.message": "Seguro que desexa desconectar?",
   "confirmations.mute.confirm": "Acalar",
+  "confirmations.mute.explanation": "Esto ocultará as publicacións delas e as que as mencionen, pero poderán seguir lendo as túas publicacións e seguirte.",
   "confirmations.mute.message": "Está segura de que quere acalar a {name}?",
   "confirmations.redraft.confirm": "Eliminar e reescribir",
   "confirmations.redraft.message": "Está segura de querer eliminar este estado e voltalo a escribir? Perderá réplicas e favoritas, e as respostas ao orixinal quedarán orfas.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Respostando agora sobreescribirá a mensaxe que está a compoñer. Segura de querer proceder?",
   "confirmations.unfollow.confirm": "Deixar de seguir",
   "confirmations.unfollow.message": "Quere deixar de seguir a {name}?",
+  "conversation.delete": "Eliminar conversa",
+  "conversation.mark_as_read": "Marcar como lido",
+  "conversation.open": "Ver conversa",
+  "conversation.with": "Con {names}",
+  "directory.federated": "Desde o fediverso coñecido",
+  "directory.local": "Só desde {domain}",
+  "directory.new_arrivals": "Novas achegas",
+  "directory.recently_active": "Activo recentemente",
   "embed.instructions": "Copie o código inferior para incrustar no seu sitio web este estado.",
   "embed.preview": "Así será mostrado:",
   "emoji_button.activity": "Actividade",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Non acalou ningunha usuaria polo de agora.",
   "empty_column.notifications": "Aínda non ten notificacións. Interactúe con outras para iniciar unha conversa.",
   "empty_column.public": "Nada por aquí! Escriba algo de xeito público, ou siga manualmente usuarias de outros servidores para ir enchéndoa",
+  "error.unexpected_crash.explanation": "Non se mostra correctamente a páxina debido a un fallo no código ou problema de compatibilidade do navegador.",
+  "error.unexpected_crash.next_steps": "Intenta actualizar a páxina. Se esto non axuda podes tamén utilizar Mastodon en outro navegador ou app nativa.",
+  "errors.unexpected_crash.copy_stacktrace": "Copiar trazas ao portaretallos",
+  "errors.unexpected_crash.report_issue": "Informar de problema",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Novo título da lista",
   "lists.search": "Procurar entre a xente que segues",
   "lists.subheading": "As túas listas",
+  "load_pending": "{count, plural, one {# novo elemento} other {# novos elementos}}",
   "loading_indicator.label": "Cargando...",
   "media_gallery.toggle_visible": "Ocultar",
   "missing_indicator.label": "Non atopado",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Persoal",
   "navigation_bar.pins": "Mensaxes fixadas",
   "navigation_bar.preferences": "Preferencias",
-  "navigation_bar.profile_directory": "Directorio de perfil",
   "navigation_bar.public_timeline": "Liña temporal federada",
   "navigation_bar.security": "Seguridade",
   "notification.favourite": "{name} marcou como favorito o seu estado",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notificacións",
   "poll.closed": "Pechado",
   "poll.refresh": "Actualizar",
+  "poll.total_people": "{count, plural,one {# persoa}other {# persoas}}",
   "poll.total_votes": "{count, plural, one {# voto} outros {# votos}}",
   "poll.vote": "Votar",
+  "poll.voted": "Votou por esta opción",
   "poll_button.add_poll": "Engadir sondaxe",
   "poll_button.remove_poll": "Eliminar sondaxe",
   "privacy.change": "Axustar a intimidade do estado",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Pública",
   "privacy.unlisted.long": "Non publicar en liñas temporais públicas",
   "privacy.unlisted.short": "Non listada",
+  "refresh": "Actualizar",
   "regeneration_indicator.label": "Cargando…",
   "regeneration_indicator.sublabel": "Estase a preparar a súa liña temporal de inicio!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Xente",
   "search_results.hashtags": "Etiquetas",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Non está activada neste servidor Mastodon a busca de toots polo seu contido.",
   "search_results.total": "{count, number} {count,plural,one {result} outros {results}}",
   "status.admin_account": "Abrir interface de moderación para @{name}",
   "status.admin_status": "Abrir este estado na interface de moderación",
@@ -358,6 +384,7 @@
   "status.show_more": "Mostrar máis",
   "status.show_more_all": "Mostrar máis para todas",
   "status.show_thread": "Mostrar fío",
+  "status.uncached_media_warning": "Non dispoñible",
   "status.unmute_conversation": "Non acalar a conversa",
   "status.unpin": "Despegar do perfil",
   "suggestions.dismiss": "Rexeitar suxestión",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Está rematando",
   "time_remaining.seconds": "{number, plural, one {# segundo} other {# segundos}} restantes",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} outras {people}} conversando",
+  "trends.trending_now": "Tendencias actuais",
   "ui.beforeunload": "O borrador perderase se sae de Mastodon.",
   "upload_area.title": "Arrastre e solte para subir",
-  "upload_button.label": "Engadir medios (JPEG, PNG, GIF, WebM, MP4, MOV)",
+  "upload_button.label": "Engadir medios ({formats})",
   "upload_error.limit": "Excedeu o límite de subida de ficheiros.",
   "upload_error.poll": "Non se poden subir ficheiros nas sondaxes.",
   "upload_form.description": "Describa para deficientes visuais",
-  "upload_form.focus": "Cambiar vista previa",
+  "upload_form.edit": "Editar",
   "upload_form.undo": "Eliminar",
+  "upload_modal.analyzing_picture": "Analizando imaxe…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.description_placeholder": "Un raposo moi feitiño salta sobre o can preguiceiro",
+  "upload_modal.detect_text": "Detectar texto na imaxe",
+  "upload_modal.edit_media": "Editar medios",
+  "upload_modal.hint": "Prema ou arrastre o círculo na vista previa para escolle o punto focal que se verá na vista de todas as miniaturas.",
+  "upload_modal.preview_label": "Vista previa ({ratio})",
   "upload_progress.label": "Subindo...",
   "video.close": "Pechar video",
   "video.exit_fullscreen": "Saír da pantalla completa",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index 5b1e17e991f9ab4bb2fee23deebced8a6746f03d..81912477d9a5cd05002c51c86f3fb6e75f8af570 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -1,21 +1,23 @@
 {
-  "account.add_or_remove_from_list": "Add or Remove from lists",
-  "account.badges.bot": "Bot",
+  "account.add_or_remove_from_list": "הוסף או הסר מהרשימות",
+  "account.badges.bot": "בוט",
   "account.block": "חסימת @{name}",
   "account.block_domain": "להסתיר הכל מהקהילה {domain}",
-  "account.blocked": "Blocked",
+  "account.blocked": "חסום",
+  "account.cancel_follow_request": "בטל בקשת מעקב",
   "account.direct": "Direct Message @{name}",
-  "account.domain_blocked": "Domain hidden",
+  "account.domain_blocked": "הדומיין חסוי",
   "account.edit_profile": "עריכת פרופיל",
-  "account.endorse": "Feature on profile",
+  "account.endorse": "הצג בפרופיל",
   "account.follow": "מעקב",
   "account.followers": "עוקבים",
-  "account.followers.empty": "No one follows this user yet.",
+  "account.followers.empty": "אף אחד לא עוקב אחר המשתמש הזה עדיין.",
   "account.follows": "נעקבים",
-  "account.follows.empty": "This user doesn't follow anyone yet.",
+  "account.follows.empty": "משתמש זה לא עוקב אחר אף אחד עדיין.",
   "account.follows_you": "במעקב אחריך",
   "account.hide_reblogs": "להסתיר הידהודים מאת @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.last_status": "פעילות אחרונה",
+  "account.link_verified_on": "בעלות על הקישור הזה נבדקה לאחרונה ב{date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "מדיה",
   "account.mention": "אזכור של @{name}",
@@ -23,6 +25,7 @@
   "account.mute": "להשתיק את @{name}",
   "account.mute_notifications": "להסתיר התראות מאת @{name}",
   "account.muted": "Muted",
+  "account.never_active": "Never",
   "account.posts": "הודעות",
   "account.posts_with_replies": "Toots with replies",
   "account.report": "לדווח על @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "הפסקת מעקב",
   "account.unmute": "הפסקת השתקת @{name}",
   "account.unmute_notifications": "להפסיק הסתרת הודעות מעם @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "אירעה שגיאה בלתי צפויה.",
   "alert.unexpected.title": "אופס!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה",
   "bundle_column_error.body": "משהו השתבש בעת הצגת הרכיב הזה.",
   "bundle_column_error.retry": "לנסות שוב",
@@ -47,6 +53,7 @@
   "column.blocks": "חסימות",
   "column.community": "ציר זמן מקומי",
   "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "חיבובים",
   "column.follow_requests": "בקשות מעקב",
@@ -64,7 +71,7 @@
   "column_header.show_settings": "הצגת העדפות",
   "column_header.unpin": "שחרור קיבוע",
   "column_subheading.settings": "אפשרויות",
-  "community.column_settings.media_only": "Media Only",
+  "community.column_settings.media_only": "Media only",
   "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.",
   "compose_form.direct_message_warning_learn_more": "Learn more",
   "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
   "confirmations.domain_block.confirm": "הסתר קהילה שלמה",
   "confirmations.domain_block.message": "באמת באמת לחסום את כל קהילת {domain}? ברב המקרים השתקות נבחרות של מספר משתמשים מסויימים צריכה להספיק.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "להשתיק",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "להשתיק את {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "להפסיק מעקב",
   "confirmations.unfollow.message": "להפסיק מעקב אחרי {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "ניתן להטמיע את ההודעה באתרך ע\"י העתקת הקוד שלהלן.",
   "embed.preview": "דוגמא כיצד זה יראה:",
   "emoji_button.activity": "פעילות",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב.",
   "empty_column.public": "אין פה כלום! כדי למלא את הטור הזה אפשר לכתוב משהו, או להתחיל לעקוב אחרי אנשים מקהילות אחרות",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "New list title",
   "lists.search": "Search among people you follow",
   "lists.subheading": "Your lists",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "טוען...",
   "media_gallery.toggle_visible": "נראה\\בלתי נראה",
   "missing_indicator.label": "לא נמצא",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "חיצרוצים מקובעים",
   "navigation_bar.preferences": "העדפות",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "ציר זמן בין-קהילתי",
   "navigation_bar.security": "Security",
   "notification.favourite": "חצרוצך חובב על ידי {name}",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "שינוי פרטיות ההודעה",
@@ -295,6 +319,7 @@
   "privacy.public.short": "פומבי",
   "privacy.unlisted.long": "לא יופיע בפידים הציבוריים המשותפים",
   "privacy.unlisted.short": "לא לפיד הכללי",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Loading…",
   "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "People",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "הראה יותר",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "הסרת השתקת שיחה",
   "status.unpin": "לשחרר מקיבוע באודות",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "הטיוטא תאבד אם תעזבו את מסטודון.",
   "upload_area.title": "ניתן להעלות על ידי Drag & drop",
   "upload_button.label": "הוספת מדיה",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "תיאור לכבדי ראיה",
-  "upload_form.focus": "Crop",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "ביטול",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "עולה...",
   "video.close": "סגירת וידאו",
   "video.exit_fullscreen": "יציאה ממסך מלא",
diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json
index 7a71d84c9da96de9665a9ff2ff0f16121cd30389..325b64dc4220ae521d8d2b17be81b02733b953b1 100644
--- a/app/javascript/mastodon/locales/hi.json
+++ b/app/javascript/mastodon/locales/hi.json
@@ -4,6 +4,7 @@
   "account.block": "Block @{name}",
   "account.block_domain": "Hide everything from {domain}",
   "account.blocked": "Blocked",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Direct message @{name}",
   "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Edit profile",
@@ -15,6 +16,7 @@
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Media",
@@ -23,6 +25,7 @@
   "account.mute": "Mute @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.muted": "Muted",
+  "account.never_active": "Never",
   "account.posts": "Toots",
   "account.posts_with_replies": "Toots and replies",
   "account.report": "Report @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Unfollow",
   "account.unmute": "Unmute @{name}",
   "account.unmute_notifications": "Unmute notifications from @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "bundle_column_error.body": "Something went wrong while loading this component.",
   "bundle_column_error.retry": "Try again",
@@ -47,6 +53,7 @@
   "column.blocks": "Blocked users",
   "column.community": "Local timeline",
   "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
   "confirmations.domain_block.confirm": "Hide entire domain",
   "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Mute",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "embed.preview": "Here is what it will look like:",
   "emoji_button.activity": "Activity",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
   "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "New list title",
   "lists.search": "Search among people you follow",
   "lists.subheading": "Your lists",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Loading...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Pinned toots",
   "navigation_bar.preferences": "Preferences",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Federated timeline",
   "navigation_bar.security": "Security",
   "notification.favourite": "{name} favourited your status",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Do not show in public timelines",
   "privacy.unlisted.short": "Unlisted",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Loading…",
   "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "People",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Show more",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
-  "upload_form.focus": "Crop",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Delete",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Uploading...",
   "video.close": "Close video",
   "video.exit_fullscreen": "Exit full screen",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index 8f02973d770e12894e93437e6b6492332312e036..fd32fcd3804190e4f82989dc6ed02ec5cdf03970 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -4,6 +4,7 @@
   "account.block": "Blokiraj @{name}",
   "account.block_domain": "Sakrij sve sa {domain}",
   "account.blocked": "Blocked",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Direct Message @{name}",
   "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Uredi profil",
@@ -15,6 +16,7 @@
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "te slijedi",
   "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Media",
@@ -23,6 +25,7 @@
   "account.mute": "Utišaj @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.muted": "Muted",
+  "account.never_active": "Never",
   "account.posts": "Postovi",
   "account.posts_with_replies": "Toots with replies",
   "account.report": "Prijavi @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Prestani slijediti",
   "account.unmute": "Poništi utišavanje @{name}",
   "account.unmute_notifications": "Unmute notifications from @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put",
   "bundle_column_error.body": "Something went wrong while loading this component.",
   "bundle_column_error.retry": "Try again",
@@ -47,6 +53,7 @@
   "column.blocks": "Blokirani korisnici",
   "column.community": "Lokalni timeline",
   "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoriti",
   "column.follow_requests": "Zahtjevi za slijeđenje",
@@ -64,7 +71,7 @@
   "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.settings": "Postavke",
-  "community.column_settings.media_only": "Media Only",
+  "community.column_settings.media_only": "Media only",
   "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.",
   "compose_form.direct_message_warning_learn_more": "Learn more",
   "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
   "confirmations.domain_block.confirm": "Sakrij cijelu domenu",
   "confirmations.domain_block.message": "Jesi li zaista, zaista siguran da želiš potpuno blokirati {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Utišaj",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Jesi li siguran da želiš utišati {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "embed.preview": "Here is what it will look like:",
   "emoji_button.activity": "Aktivnost",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Još nemaš notifikacija. Komuniciraj sa drugima kako bi započeo razgovor.",
   "empty_column.public": "Ovdje nema ništa! Napiši nešto javno, ili ručno slijedi korisnike sa drugih instanci kako bi popunio",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "New list title",
   "lists.search": "Search among people you follow",
   "lists.subheading": "Your lists",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Učitavam...",
   "media_gallery.toggle_visible": "Preklopi vidljivost",
   "missing_indicator.label": "Nije nađen",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Pinned toots",
   "navigation_bar.preferences": "Postavke",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Federalni timeline",
   "navigation_bar.security": "Security",
   "notification.favourite": "{name} je lajkao tvoj status",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Podesi status privatnosti",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Javno",
   "privacy.unlisted.long": "Ne prikazuj u javnim timelineovima",
   "privacy.unlisted.short": "Unlisted",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Loading…",
   "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "People",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Pokaži više",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Poništi utišavanje razgovora",
   "status.unpin": "Unpin from profile",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
   "upload_area.title": "Povuci i spusti kako bi uploadao",
   "upload_button.label": "Dodaj media",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
-  "upload_form.focus": "Crop",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Poništi",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Uploadam...",
   "video.close": "Close video",
   "video.exit_fullscreen": "Exit full screen",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index 3f1f4a6e9f11b46f1dde1c28a808de05323b5700..0eadb290348a5a445b29215a2dbd504ec2aa87d6 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -4,6 +4,7 @@
   "account.block": "@{name} letiltása",
   "account.block_domain": "Minden elrejtése innen: {domain}",
   "account.blocked": "Letiltva",
+  "account.cancel_follow_request": "Követési kérelem törlése",
   "account.direct": "Közvetlen üzenet @{name} számára",
   "account.domain_blocked": "Rejtett domain",
   "account.edit_profile": "Profil szerkesztése",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Ez a felhasználó még senkit sem követ.",
   "account.follows_you": "Követ téged",
   "account.hide_reblogs": "@{name} megtolásainak némítása",
+  "account.last_status": "Utoljára aktív",
   "account.link_verified_on": "A linket ellenőriztük: {date}",
   "account.locked_info": "Ez a fiók zárt. A tulaj engedélyezi, ki követheti őt.",
   "account.media": "Média",
@@ -23,6 +25,7 @@
   "account.mute": "@{name} némítása",
   "account.mute_notifications": "@{name} értesítéseinek némítása",
   "account.muted": "Némítva",
+  "account.never_active": "Soha",
   "account.posts": "Tülkölés",
   "account.posts_with_replies": "Tülkölés válaszokkal",
   "account.report": "@{name} jelentése",
@@ -35,8 +38,11 @@
   "account.unfollow": "Követés vége",
   "account.unmute": "@{name} némítás feloldása",
   "account.unmute_notifications": "@{name} némított értesítéseinek feloldása",
+  "alert.rate_limited.message": "Kérlek, próbáld újra {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Forgalomkorlátozás",
   "alert.unexpected.message": "Váratlan hiba történt.",
   "alert.unexpected.title": "Hoppá!",
+  "autosuggest_hashtag.per_week": "{count}/hét",
   "boost_modal.combo": "Hogy átugord ezt következő alkalommal, használd {combo}",
   "bundle_column_error.body": "Hiba történt a komponens betöltése közben.",
   "bundle_column_error.retry": "Próbáld újra",
@@ -47,6 +53,7 @@
   "column.blocks": "Letiltott felhasználók",
   "column.community": "Helyi idővonal",
   "column.direct": "Közvetlen üzenetek",
+  "column.directory": "Profilok böngészése",
   "column.domain_blocks": "Rejtett domainek",
   "column.favourites": "Kedvencek",
   "column.follow_requests": "Követési kérelmek",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Biztos, hogy véglegesen törölni szeretnéd ezt a listát?",
   "confirmations.domain_block.confirm": "Teljes domain elrejtése",
   "confirmations.domain_block.message": "Egészen biztos, hogy le szeretnéd tiltani a teljes {domain}-t? A legtöbb esetben néhány célzott tiltás vagy némítás elegendő és kívánatosabb megoldás. Semmilyen tartalmat nem fogsz látni ebből a domainből se idővonalakon, se értesítésekben. Az ebben a domainben lévő követőidet is eltávolítjuk.",
+  "confirmations.logout.confirm": "Kijelentkezés",
+  "confirmations.logout.message": "Biztosan ki akar jelentkezni?",
   "confirmations.mute.confirm": "Némítás",
+  "confirmations.mute.explanation": "Ez elrejti a tőlük érkező posztokat, valamint az őket megemlítőket, de ők továbbra is láthatják a te posztjaidat és követhetnek is téged.",
   "confirmations.mute.message": "Biztos, hogy némítani szeretnéd {name}?",
   "confirmations.redraft.confirm": "Törlés és újraírás",
   "confirmations.redraft.message": "Biztos, hogy ezt a tülköt szeretnéd törölni és újraírni? Minden megtolást és kedvencnek jelölést elvesztesz, az eredetire adott válaszok pedig elárvulnak.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Ha most válaszolsz, ez felülírja a most szerkesztés alatt álló üzenetet. Mégis ezt szeretnéd?",
   "confirmations.unfollow.confirm": "Követés visszavonása",
   "confirmations.unfollow.message": "Biztos, hogy vissza szeretnéd vonni {name} követését?",
+  "conversation.delete": "Beszélgetés törlése",
+  "conversation.mark_as_read": "Megjelölés olvasottként",
+  "conversation.open": "Beszélgetés megtekintése",
+  "conversation.with": "{names}-el/al",
+  "directory.federated": "Az ismert fediverzumból",
+  "directory.local": "Csak {domain}-ból/ből",
+  "directory.new_arrivals": "Új csatlakozók",
+  "directory.recently_active": "Nemrég aktív",
   "embed.instructions": "Ágyazd be ezt a tülköt a weboldaladba az alábbi kód kimásolásával.",
   "embed.preview": "Így fog kinézni:",
   "emoji_button.activity": "Aktivitás",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Még egy felhasználót sem némítottál le.",
   "empty_column.notifications": "Jelenleg nincsenek értesítéseid. Lépj kapcsolatba másokkal, hogy elindítsd a beszélgetést.",
   "empty_column.public": "Jelenleg itt nincs semmi! Írj valamit nyilvánosan vagy kövess más szervereken levő felhasználókat, hogy megtöltsd",
+  "error.unexpected_crash.explanation": "Egy hiba vagy böngésző inkompatibilitás miatt ez az oldal nem jeleníthető meg rendesen.",
+  "error.unexpected_crash.next_steps": "Próbáld frissíteni az oldalt. Ha ez nem segít, egy másik böngészőn vagy appon keresztül még mindig használhatod a Mastodont.",
+  "errors.unexpected_crash.copy_stacktrace": "Stacktrace vágólapra másolása",
+  "errors.unexpected_crash.report_issue": "Probléma bejelentése",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Új lista címe",
   "lists.search": "Keresés a követett személyek között",
   "lists.subheading": "Listáid",
+  "load_pending": "{count, plural, one {# új elem} other {# új elem}}",
   "loading_indicator.label": "Betöltés...",
   "media_gallery.toggle_visible": "Láthatóság állítása",
   "missing_indicator.label": "Nincs találat",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Személyes",
   "navigation_bar.pins": "Kitűzött tülkök",
   "navigation_bar.preferences": "Beállítások",
-  "navigation_bar.profile_directory": "Profilok",
   "navigation_bar.public_timeline": "Föderációs idővonal",
   "navigation_bar.security": "Biztonság",
   "notification.favourite": "{name} kedvencnek jelölte egy tülködet",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} értesítés",
   "poll.closed": "Lezárva",
   "poll.refresh": "Frissítés",
+  "poll.total_people": "{count, plural, one {# személy} other {# személy}}",
   "poll.total_votes": "{count, plural, one {# szavazat} other {# szavazat}}",
   "poll.vote": "Szavazás",
+  "poll.voted": "Erre a válaszra szavaztál",
   "poll_button.add_poll": "Új szavazás",
   "poll_button.remove_poll": "Szavazás törlése",
   "privacy.change": "Tülk láthatóságának módosítása",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Nyilvános",
   "privacy.unlisted.long": "Ne mutassuk nyilvános idővonalon",
   "privacy.unlisted.short": "Listázatlan",
+  "refresh": "Frissítés",
   "regeneration_indicator.label": "Töltődik…",
   "regeneration_indicator.sublabel": "A saját idővonalad épp készül!",
   "relative_time.days": "{number}nap",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Emberek",
   "search_results.hashtags": "Hashtagek",
   "search_results.statuses": "Tülkök",
+  "search_results.statuses_fts_disabled": "Ezen a szerveren nem engedélyezett a tülkök tartalom szerinti keresése.",
   "search_results.total": "{count, number} {count, plural, one {találat} other {találat}}",
   "status.admin_account": "Moderáció megnyitása @{name} felhasználóhoz",
   "status.admin_status": "Tülk megnyitása moderációra",
@@ -358,6 +384,7 @@
   "status.show_more": "Többet",
   "status.show_more_all": "Többet mindenhol",
   "status.show_thread": "Szál mutatása",
+  "status.uncached_media_warning": "Nem elérhető",
   "status.unmute_conversation": "Beszélgetés némításának kikapcsolása",
   "status.unpin": "Kitűzés eltávolítása a profilodról",
   "suggestions.dismiss": "Javaslat elvetése",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Pillanatok vannak hátra",
   "time_remaining.seconds": "{number, plural, one {# másodperc} other {# másodperc}} van hátra",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {résztvevő} other {résztvevő}} beszélget",
+  "trends.trending_now": "Most trendi",
   "ui.beforeunload": "A piszkozatod el fog veszni, ha elhagyod a Mastodon-t.",
   "upload_area.title": "Húzd ide a feltöltéshez",
   "upload_button.label": "Média hozzáadása (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Túllépted a fájl feltöltési limitet.",
   "upload_error.poll": "Szavazásnál nem lehet fájlt feltölteni.",
   "upload_form.description": "Leírás látáskorlátozottak számára",
-  "upload_form.focus": "Előnézet megváltoztatása",
+  "upload_form.edit": "Szerkesztés",
   "upload_form.undo": "Mégsem",
+  "upload_modal.analyzing_picture": "Kép elemzése…",
+  "upload_modal.apply": "Alkalmazás",
+  "upload_modal.description_placeholder": "A gyors, barna róka átugrik a lusta kutya fölött",
+  "upload_modal.detect_text": "Szöveg felismerése a képről",
+  "upload_modal.edit_media": "Média szerkesztése",
+  "upload_modal.hint": "Kattints vagy húzd a kört az előnézetben arra a fókuszpontra, mely minden megjelenített bélyegképen látható kell, legyen.",
+  "upload_modal.preview_label": "Előnézet ({ratio})",
   "upload_progress.label": "Feltöltés...",
   "video.close": "Videó bezárása",
   "video.exit_fullscreen": "Kilépés teljes képernyőből",
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index 483becbcf8cb2d66fb8152791781be980a03490f..e667e5b4e8c4618b9520b2854991afaeef5d1343 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -1,20 +1,22 @@
 {
   "account.add_or_remove_from_list": "Add or Remove from lists",
-  "account.badges.bot": "Bot",
+  "account.badges.bot": "Ô²Õ¸Õ¿",
   "account.block": "Ô±Ö€Õ£Õ¥Õ¬Õ¡ÖƒÕ¡Õ¯Õ¥Õ¬ @{name}ÖŠÕ«Õ¶",
   "account.block_domain": "Թաքցնել ամենը հետեւյալ տիրույթից՝ {domain}",
   "account.blocked": "Blocked",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Direct Message @{name}",
   "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Ô½Õ´Õ¢Õ¡Õ£Ö€Õ¥Õ¬ Õ¡Õ¶Õ±Õ¶Õ¡Õ¯Õ¡Õ¶ Õ§Õ»Õ¨",
   "account.endorse": "Feature on profile",
   "account.follow": "Õ€Õ¥Õ¿Õ¥Ö‚Õ¥Õ¬",
-  "account.followers": "Õ€Õ¥Õ¿Õ¥Ö‚Õ¾Õ¸Õ²Õ¶Õ¥Ö€",
+  "account.followers": "Õ€Õ¥Õ¿Õ¥Ö‚Õ¸Õ²Õ¶Õ¥Ö€",
   "account.followers.empty": "No one follows this user yet.",
   "account.follows": "Õ€Õ¥Õ¿Õ¥Ö‚Õ¸Ö‚Õ´ Õ§",
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Õ€Õ¥Õ¿Õ¥Ö‚Õ¸Ö‚Õ´ Õ§ Ö„Õ¥Õ¦",
   "account.hide_reblogs": "Թաքցնել @{name}֊ի տարածածները",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Õ„Õ¥Õ¤Õ«Õ¡",
@@ -23,6 +25,7 @@
   "account.mute": "Լռեցնել @{name}֊ին",
   "account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից",
   "account.muted": "Muted",
+  "account.never_active": "Never",
   "account.posts": "Ô³Ö€Õ¡Õ¼Õ¸Ö‚Õ´Õ¶Õ¥Ö€",
   "account.posts_with_replies": "Toots with replies",
   "account.report": "Բողոքել @{name}֊ից",
@@ -35,8 +38,11 @@
   "account.unfollow": "Õ‰Õ°Õ¥Õ¿Õ¥Ö‚Õ¥Õ¬",
   "account.unmute": "Ապալռեցնել @{name}֊ին",
   "account.unmute_notifications": "Միացնել ծանուցումները @{name}֊ից",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
-  "alert.unexpected.title": "Oops!",
+  "alert.unexpected.title": "Վա՜յ",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Կարող ես սեղմել {combo}՝ սա հաջորդ անգամ բաց թողնելու համար",
   "bundle_column_error.body": "Այս բաղադրիչը բեռնելու ընթացքում ինչ֊որ բան խափանվեց։",
   "bundle_column_error.retry": "Ô¿Ö€Õ¯Õ«Õ¶ ÖƒÕ¸Ö€Õ±Õ¥Õ¬",
@@ -47,6 +53,7 @@
   "column.blocks": "Ô±Ö€Õ£Õ¥Õ¬Õ¡ÖƒÕ¡Õ¯Õ¾Õ¡Õ® Ö…Õ£Õ¿Õ¡Õ¿Õ¥Ö€Õ¥Ö€",
   "column.community": "Տեղական հոսք",
   "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Õ€Õ¡Õ¾Õ¡Õ¶Õ¡Õ®Õ¶Õ¥Ö€",
   "column.follow_requests": "Հետեւելու հայցեր",
@@ -64,7 +71,7 @@
   "column_header.show_settings": "Ցուցադրել կարգավորումները",
   "column_header.unpin": "Õ€Õ¡Õ¶Õ¥Õ¬",
   "column_subheading.settings": "Ô¿Õ¡Ö€Õ£Õ¡Õ¾Õ¸Ö€Õ¸Ö‚Õ´Õ¶Õ¥Ö€",
-  "community.column_settings.media_only": "Media Only",
+  "community.column_settings.media_only": "Media only",
   "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.",
   "compose_form.direct_message_warning_learn_more": "Learn more",
   "compose_form.hashtag_warning": "Ô±ÕµÕ½ Õ©Õ¸Ö‚Õ©Õ¨ Õ¹Õ« Õ°Õ¡Õ·Õ¾Õ¡Õ¼Õ¾Õ« Õ¸Ö€Õ¥Ö‚Õ§ ÕºÕ«Õ¿Õ¡Õ¯Õ« Õ¿Õ¡Õ¯, Ö„Õ¡Õ¶Õ¦Õ« Õ¡ÕµÕ¶ Õ®Õ¡Õ®Õ¸Ö‚Õ¯ Õ§Ö‰ Õ„Õ«Õ¡ÕµÕ¶ Õ°Ö€Õ¡ÕºÕ¡Ö€Õ¡Õ¯Õ¡ÕµÕ«Õ¶ Õ©Õ©Õ¥Ö€Õ¨ Õ°Õ¶Õ¡Ö€Õ¡Õ¾Õ¸Ö€ Õ§ Õ¸Ö€Õ¸Õ¶Õ¥Õ¬ ÕºÕ«Õ¿Õ¡Õ¯Õ¶Õ¥Ö€Õ¸Õ¾Ö‰",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Վստա՞հ ես, որ ուզում ես մշտապես ջնջել այս ցանկը։",
   "confirmations.domain_block.confirm": "Թաքցնել ամբողջ տիրույթը",
   "confirmations.domain_block.message": "Հաստատ֊հաստա՞տ վստահ ես, որ ուզում ես արգելափակել ամբողջ {domain} տիրույթը։ Սովորաբար մի երկու թիրախավորված արգելափակում կամ լռեցում բավական է ու նախընտրելի։",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Լռեցնել",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Վստա՞հ ես, որ ուզում ես {name}֊ին լռեցնել։",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Ô±ÕºÕ¡Õ°Õ¥Õ¿Õ¥Ö‚Õ¥Õ¬",
   "confirmations.unfollow.message": "ÕŽÕ½Õ¿Õ¡ÕžÕ° Õ¥Õ½, Õ¸Ö€ Õ¸Ö‚Õ¦Õ¸Ö‚Õ´ Õ¥Õ½ Õ¡ÕµÕ¬Õ¥Ö‚Õ½ Õ¹Õ°Õ¥Õ¿Õ¥Ö‚Õ¥Õ¬ {name}ÖŠÕ«Õ¶Ö‰",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Ô±ÕµÕ½ Õ©Õ¸Ö‚Õ©Õ¨ Ö„Õ¸ Õ¯Õ¡ÕµÖ„Õ¸Ö‚Õ´ Õ¶Õ¥Ö€Õ¤Õ¶Õ¥Õ¬Õ¸Ö‚ Õ°Õ¡Õ´Õ¡Ö€ Õ¯Õ¡Ö€Õ¸Õ² Õ¥Õ½ ÕºÕ¡Õ¿Õ³Õ¥Õ¶Õ¥Õ¬ Õ¶Õ¥Ö€Ö„Õ¸Õ°Õ«Õ·ÕµÕ¡Õ¬ Õ¯Õ¸Õ¤Õ¨Ö‰",
   "embed.preview": "Ահա, թե ինչ տեսք կունենա այն՝",
   "emoji_button.activity": "Ô¶Õ¢Õ¡Õ²Õ´Õ¸Ö‚Õ¶Ö„Õ¶Õ¥Ö€",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Ոչ մի ծանուցում դեռ չունես։ Բզիր մյուսներին՝ խոսակցությունը սկսելու համար։",
   "empty_column.public": "Այստեղ բան չկա՛։ Հրապարակային մի բան գրիր կամ հետեւիր այլ հանգույցներից էակների՝ այն լցնելու համար։",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Նոր ցանկի վերնագիր",
   "lists.search": "Փնտրել քո հետեւած մարդկանց մեջ",
   "lists.subheading": "Քո ցանկերը",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Բեռնվում է…",
   "media_gallery.toggle_visible": "Ցուցադրել/թաքցնել",
   "missing_indicator.label": "Չգտնվեց",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Ô±Õ¶Õ±Õ¶Õ¡Õ¯Õ¡Õ¶",
   "navigation_bar.pins": "Ամրացված թթեր",
   "navigation_bar.preferences": "Õ†Õ¡Õ­Õ¡ÕºÕ¡Õ¿Õ¾Õ¸Ö‚Õ©ÕµÕ¸Ö‚Õ¶Õ¶Õ¥Ö€",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Ô´Õ¡Õ·Õ¶Õ¡ÕµÕ«Õ¶ Õ°Õ¸Õ½Ö„",
   "navigation_bar.security": "Ô±Õ¶Õ¾Õ¿Õ¡Õ¶Õ£Õ¸Ö‚Õ©ÕµÕ¸Ö‚Õ¶",
   "notification.favourite": "{name} հավանեց թութդ",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Ô¿Õ¡Ö€Õ£Õ¡Õ¾Õ¸Ö€Õ¥Õ¬ Õ©Õ©Õ« Õ£Õ¡Õ²Õ¿Õ¶Õ«Õ¸Ö‚Õ©ÕµÕ¸Ö‚Õ¶Õ¨",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Õ€Ö€Õ¡ÕºÕ¡Ö€Õ¡Õ¯Õ¡ÕµÕ«Õ¶",
   "privacy.unlisted.long": "Õ‰Õ©Õ©Õ¥Õ¬ Õ°Ö€Õ¡ÕºÕ¡Ö€Õ¡Õ¯Õ¡ÕµÕ«Õ¶ Õ°Õ¸Õ½Ö„Õ¥Ö€Õ¸Ö‚Õ´",
   "privacy.unlisted.short": "Ô¾Õ¡Õ®Õ¸Ö‚Õ¯",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Loading…",
   "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}Ö…Ö€",
@@ -316,9 +341,10 @@
   "search_popout.tips.status": "Õ©Õ¸Ö‚Õ©",
   "search_popout.tips.text": "Õ€Õ¡Õ½Õ¡Ö€Õ¡Õ¯ Õ¿Õ¥Ö„Õ½Õ¿Õ¨ Õ¯Õ¾Õ¥Ö€Õ¡Õ¤Õ¡Ö€Õ±Õ¶Õ« Õ°Õ¡Õ´Õ¨Õ¶Õ¯Õ¶Õ¸Õ² Õ¡Õ¶Õ¸Ö‚Õ¶Õ¶Õ¥Ö€, Ö…Õ£Õ¿Õ¡Õ¶Õ¸Ö‚Õ¶Õ¶Õ¥Ö€ Õ¸Ö‚ ÕºÕ«Õ¿Õ¡Õ¯Õ¶Õ¥Ö€",
   "search_popout.tips.user": "Ö…Õ£Õ¿Õ¡Õ¿Õ¥Ö€",
-  "search_results.accounts": "People",
+  "search_results.accounts": "Õ„Õ¡Ö€Õ¤Õ«Õ¯",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {Õ¡Ö€Õ¤ÕµÕ¸Ö‚Õ¶Ö„} other {Õ¡Ö€Õ¤ÕµÕ¸Ö‚Õ¶Ö„}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Ô±Õ¾Õ¥Õ¬Õ«Õ¶",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Ապալռեցնել խոսակցությունը",
   "status.unpin": "Հանել անձնական էջից",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -366,21 +393,29 @@
   "tabs_bar.home": "Õ€Õ«Õ´Õ¶Õ¡Õ¯Õ¡Õ¶",
   "tabs_bar.local_timeline": "Տեղական",
   "tabs_bar.notifications": "Ծանուցումներ",
-  "tabs_bar.search": "Search",
+  "tabs_bar.search": "Õ“Õ¶Õ¿Ö€Õ¥Õ¬",
   "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
   "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
   "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Õ”Õ¸ Õ½Õ¥Ö‚Õ¡Õ£Õ«Ö€Õ¨ Õ¯Õ¯Õ¸Ö€Õ«, Õ¥Õ©Õ¥ Õ¬Ö„Õ¥Õ½ Õ„Õ¡Õ½Õ¿Õ¸Õ¤Õ¸Õ¶Õ¨Ö‰",
   "upload_area.title": "Քաշիր ու նետիր՝ վերբեռնելու համար",
   "upload_button.label": "Ավելացնել մեդիա",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Նկարագրություն ավելացրու տեսողական խնդիրներ ունեցողների համար",
-  "upload_form.focus": "Crop",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Õ€Õ¥Õ¿Õ¡Ö€Õ¯Õ¥Õ¬",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Վերբեռնվում է…",
   "video.close": "Õ“Õ¡Õ¯Õ¥Õ¬  Õ¿Õ¥Õ½Õ¡Õ£Ö€Õ¸Ö‚Õ©ÕµÕ¸Ö‚Õ¶Õ¨",
   "video.exit_fullscreen": "Ô±Õ¶Õ»Õ¡Õ¿Õ¥Õ¬ Õ¬Õ«Õ¡Õ§Õ¯Ö€Õ¡Õ¶ Õ¤Õ«Õ¿Õ¸Ö‚Õ´Õ¨",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index 1f5c45af6904f5a62d3856af5522f2bb0ecc4527..900f95d422b1b7944d157f2eb235194ebef8d7e5 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -1,29 +1,32 @@
 {
-  "account.add_or_remove_from_list": "Add or Remove from lists",
+  "account.add_or_remove_from_list": "Tambah atau Hapus dari daftar",
   "account.badges.bot": "Bot",
   "account.block": "Blokir @{name}",
   "account.block_domain": "Sembunyikan segalanya dari {domain}",
   "account.blocked": "Terblokir",
+  "account.cancel_follow_request": "Batalkan permintaan ikuti",
   "account.direct": "Direct Message @{name}",
   "account.domain_blocked": "Domain disembunyikan",
   "account.edit_profile": "Ubah profil",
-  "account.endorse": "Feature on profile",
+  "account.endorse": "Tampilkan di profil",
   "account.follow": "Ikuti",
   "account.followers": "Pengikut",
-  "account.followers.empty": "No one follows this user yet.",
+  "account.followers.empty": "Tidak ada satupun yang mengkuti pengguna ini saat ini.",
   "account.follows": "Mengikuti",
-  "account.follows.empty": "This user doesn't follow anyone yet.",
+  "account.follows.empty": "Pengguna ini belum mengikuti siapapun.",
   "account.follows_you": "Mengikuti anda",
   "account.hide_reblogs": "Sembunyikan boosts dari @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
-  "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
+  "account.last_status": "Terakhir aktif",
+  "account.link_verified_on": "Kepemilikan tautan ini telah dicek pada {date}",
+  "account.locked_info": "Status privasi akun ini disetel untuk dikunci. Pemilik secara manual meninjau siapa yang dapat mengikuti mereka.",
   "account.media": "Media",
   "account.mention": "Balasan @{name}",
   "account.moved_to": "{name} telah pindah ke:",
   "account.mute": "Bisukan @{name}",
   "account.mute_notifications": "Sembunyikan notifikasi dari @{name}",
   "account.muted": "Dibisukan",
-  "account.posts": "Toots",
+  "account.never_active": "Tak pernah",
+  "account.posts": "Toot",
   "account.posts_with_replies": "Postingan dengan balasan",
   "account.report": "Laporkan @{name}",
   "account.requested": "Menunggu persetujuan. Klik untuk membatalkan permintaan",
@@ -31,23 +34,27 @@
   "account.show_reblogs": "Tampilkan boost dari @{name}",
   "account.unblock": "Hapus blokir @{name}",
   "account.unblock_domain": "Tampilkan {domain}",
-  "account.unendorse": "Don't feature on profile",
+  "account.unendorse": "Jangan tampilkan di profil",
   "account.unfollow": "Berhenti mengikuti",
   "account.unmute": "Berhenti membisukan @{name}",
   "account.unmute_notifications": "Munculkan notifikasi dari @{name}",
-  "alert.unexpected.message": "An unexpected error occurred.",
+  "alert.rate_limited.message": "Tolong ulangi setelah {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
+  "alert.unexpected.message": "Terjadi kesalahan yang tidak terduga.",
   "alert.unexpected.title": "Oops!",
+  "autosuggest_hashtag.per_week": "{count} per minggu",
   "boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini",
   "bundle_column_error.body": "Kesalahan terjadi saat memuat komponen ini.",
   "bundle_column_error.retry": "Coba lagi",
-  "bundle_column_error.title": "Network error",
+  "bundle_column_error.title": "Kesalahan jaringan",
   "bundle_modal_error.close": "Tutup",
   "bundle_modal_error.message": "Kesalahan terjadi saat memuat komponen ini.",
   "bundle_modal_error.retry": "Coba lagi",
   "column.blocks": "Pengguna diblokir",
   "column.community": "Linimasa Lokal",
-  "column.direct": "Direct messages",
-  "column.domain_blocks": "Hidden domains",
+  "column.direct": "Pesan langsung",
+  "column.directory": "Jelajahi profil",
+  "column.domain_blocks": "Topik tersembunyi",
   "column.favourites": "Favorit",
   "column.follow_requests": "Permintaan mengikuti",
   "column.home": "Beranda",
@@ -64,43 +71,54 @@
   "column_header.show_settings": "Tampilkan pengaturan",
   "column_header.unpin": "Lepaskan",
   "column_subheading.settings": "Pengaturan",
-  "community.column_settings.media_only": "Media Only",
+  "community.column_settings.media_only": "Hanya media",
   "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.",
-  "compose_form.direct_message_warning_learn_more": "Learn more",
+  "compose_form.direct_message_warning_learn_more": "Pelajari selengkapnya",
   "compose_form.hashtag_warning": "Toot ini tidak akan ada dalam daftar tagar manapun karena telah di set sebagai tidak terdaftar. Hanya postingan publik yang bisa dicari dengan tagar.",
   "compose_form.lock_disclaimer": "Akun anda tidak {locked}. Semua orang dapat mengikuti anda untuk melihat postingan khusus untuk pengikut anda.",
   "compose_form.lock_disclaimer.lock": "terkunci",
   "compose_form.placeholder": "Apa yang ada di pikiran anda?",
-  "compose_form.poll.add_option": "Add a choice",
-  "compose_form.poll.duration": "Poll duration",
-  "compose_form.poll.option_placeholder": "Choice {number}",
-  "compose_form.poll.remove_option": "Remove this choice",
+  "compose_form.poll.add_option": "Tambahkan pilihan",
+  "compose_form.poll.duration": "Durasi jajak pendapat",
+  "compose_form.poll.option_placeholder": "Pilihan {number}",
+  "compose_form.poll.remove_option": "Hapus opsi ini",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.hide": "Mark media as sensitive",
+  "compose_form.sensitive.hide": "Tandai sebagai media sensitif",
   "compose_form.sensitive.marked": "Sumber ini telah ditandai sebagai sumber sensitif.",
   "compose_form.sensitive.unmarked": "Sumber ini tidak ditandai sebagai sumber sensitif",
   "compose_form.spoiler.marked": "Teks disembunyikan dibalik peringatan",
   "compose_form.spoiler.unmarked": "Teks tidak tersembunyi",
   "compose_form.spoiler_placeholder": "Peringatan konten",
   "confirmation_modal.cancel": "Batal",
-  "confirmations.block.block_and_report": "Block & Report",
+  "confirmations.block.block_and_report": "Blokir & Laporkan",
   "confirmations.block.confirm": "Blokir",
   "confirmations.block.message": "Apa anda yakin ingin memblokir {name}?",
   "confirmations.delete.confirm": "Hapus",
   "confirmations.delete.message": "Apa anda yakin untuk menghapus status ini?",
-  "confirmations.delete_list.confirm": "Delete",
+  "confirmations.delete_list.confirm": "Hapus",
   "confirmations.delete_list.message": "Apakah anda yakin untuk menghapus daftar ini secara permanen?",
   "confirmations.domain_block.confirm": "Sembunyikan keseluruhan domain",
   "confirmations.domain_block.message": "Apakah anda benar benar yakin untuk memblokir keseluruhan {domain}? Dalam kasus tertentu beberapa pemblokiran atau penyembunyian lebih baik.",
+  "confirmations.logout.confirm": "Keluar",
+  "confirmations.logout.message": "Apakah anda yakin ingin keluar?",
   "confirmations.mute.confirm": "Bisukan",
+  "confirmations.mute.explanation": "Ini akan menyembunyikan pos dari mereka dan pos yang menyebut mereka, tapi ini tetap mengizinkan mereka melihat posmu dan mengikutimu.",
   "confirmations.mute.message": "Apa anda yakin ingin membisukan {name}?",
-  "confirmations.redraft.confirm": "Delete & redraft",
+  "confirmations.redraft.confirm": "Hapus dan konsep ulang",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
-  "confirmations.reply.confirm": "Reply",
-  "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+  "confirmations.reply.confirm": "Balas",
+  "confirmations.reply.message": "Membalas sekarang akan menimpa pesan yang sedang Anda buat. Anda yakin ingin melanjutkan?",
   "confirmations.unfollow.confirm": "Berhenti mengikuti",
   "confirmations.unfollow.message": "Apakah anda ingin berhenti mengikuti {name}?",
+  "conversation.delete": "Hapus percakapan",
+  "conversation.mark_as_read": "Tandai sudah dibaca",
+  "conversation.open": "Lihat percakapan",
+  "conversation.with": "Dengan {names}",
+  "directory.federated": "Dari fediverse yang dikenal",
+  "directory.local": "Dari {domain} saja",
+  "directory.new_arrivals": "Yang baru datang",
+  "directory.recently_active": "Baru-baru ini aktif",
   "embed.instructions": "Sematkan status ini di website anda dengan menyalin kode di bawah ini.",
   "embed.preview": "Seperti ini nantinya:",
   "emoji_button.activity": "Aktivitas",
@@ -117,23 +135,27 @@
   "emoji_button.search_results": "Hasil pencarian",
   "emoji_button.symbols": "Simbol",
   "emoji_button.travel": "Tempat Wisata",
-  "empty_column.account_timeline": "No toots here!",
-  "empty_column.account_unavailable": "Profile unavailable",
-  "empty_column.blocks": "You haven't blocked any users yet.",
+  "empty_column.account_timeline": "Tidak ada toot di sini!",
+  "empty_column.account_unavailable": "Profil tidak tersedia",
+  "empty_column.blocks": "Anda belum memblokir siapapun.",
   "empty_column.community": "Linimasa lokal masih kosong. Tulis sesuatu secara publik dan buat roda berputar!",
-  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
-  "empty_column.domain_blocks": "There are no hidden domains yet.",
-  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
-  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
-  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
+  "empty_column.direct": "Anda belum memiliki pesan langsung. Ketika Anda mengirim atau menerimanya, maka akan muncul di sini.",
+  "empty_column.domain_blocks": "Tidak ada topik tersembunyi.",
+  "empty_column.favourited_statuses": "Anda belum memiliki toot favorit. Ketika Anda mengirim atau menerimanya, maka akan muncul di sini.",
+  "empty_column.favourites": "Tidak ada seorangpun yang memfavoritkan toot ini. Ketika seseorang melakukannya, maka akan muncul disini.",
+  "empty_column.follow_requests": "Anda belum memiliki permintaan mengikuti. Ketika Anda menerimanya, maka akan muncul disini.",
   "empty_column.hashtag": "Tidak ada apapun dalam hashtag ini.",
   "empty_column.home": "Linimasa anda kosong! Kunjungi {public} atau gunakan pencarian untuk memulai dan bertemu pengguna lain.",
   "empty_column.home.public_timeline": "linimasa publik",
   "empty_column.list": "Tidak ada postingan di list ini. Ketika anggota dari list ini memposting status baru, status tersebut akan tampil disini.",
-  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
-  "empty_column.mutes": "You haven't muted any users yet.",
+  "empty_column.lists": "Anda belum memiliki daftar. Ketika Anda membuatnya, maka akan muncul disini.",
+  "empty_column.mutes": "Anda belum membisukan siapapun.",
   "empty_column.notifications": "Anda tidak memiliki notifikasi apapun. Berinteraksi dengan orang lain untuk memulai percakapan.",
   "empty_column.public": "Tidak ada apapun disini! Tulis sesuatu, atau ikuti pengguna lain dari server lain untuk mengisi ini",
+  "error.unexpected_crash.explanation": "Karena kutu pada kode kami atau isu kompatibilitas peramban, halaman tak dapat ditampilkan dengan benar.",
+  "error.unexpected_crash.next_steps": "Coba segarkan halaman. Jika tak membantu, Anda masih bisa memakai Mastodon dengan peramban berbeda atau aplikasi native.",
+  "errors.unexpected_crash.copy_stacktrace": "Salin stacktrace ke papan klip",
+  "errors.unexpected_crash.report_issue": "Laporkan masalah",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -141,149 +163,151 @@
   "federation.local_only.short": "Local-only",
   "follow_request.authorize": "Izinkan",
   "follow_request.reject": "Tolak",
-  "getting_started.developers": "Developers",
-  "getting_started.directory": "Profile directory",
-  "getting_started.documentation": "Documentation",
+  "getting_started.developers": "Pengembang",
+  "getting_started.directory": "Direktori profil",
+  "getting_started.documentation": "Dokumentasi",
   "getting_started.heading": "Mulai",
-  "getting_started.invite": "Invite people",
+  "getting_started.invite": "Undang orang",
   "getting_started.open_source_notice": "Mastodon adalah perangkat lunak yang bersifat terbuka. Anda dapat berkontribusi atau melaporkan permasalahan/bug di Github {github}.",
-  "getting_started.security": "Security",
-  "getting_started.terms": "Terms of service",
-  "hashtag.column_header.tag_mode.all": "and {additional}",
-  "hashtag.column_header.tag_mode.any": "or {additional}",
-  "hashtag.column_header.tag_mode.none": "without {additional}",
-  "hashtag.column_settings.select.no_options_message": "No suggestions found",
-  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
-  "hashtag.column_settings.tag_mode.all": "All of these",
-  "hashtag.column_settings.tag_mode.any": "Any of these",
-  "hashtag.column_settings.tag_mode.none": "None of these",
+  "getting_started.security": "Keamanan",
+  "getting_started.terms": "Ketentuan layanan",
+  "hashtag.column_header.tag_mode.all": "dan {additional}",
+  "hashtag.column_header.tag_mode.any": "atau {additional}",
+  "hashtag.column_header.tag_mode.none": "tanpa {additional}",
+  "hashtag.column_settings.select.no_options_message": "Tidak ada saran yang ditemukan",
+  "hashtag.column_settings.select.placeholder": "Masukkan tagar…",
+  "hashtag.column_settings.tag_mode.all": "Semua ini",
+  "hashtag.column_settings.tag_mode.any": "Semua ini",
+  "hashtag.column_settings.tag_mode.none": "Tak satu pun",
   "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
   "home.column_settings.basic": "Dasar",
   "home.column_settings.show_reblogs": "Tampilkan boost",
   "home.column_settings.show_replies": "Tampilkan balasan",
-  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
-  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
-  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
-  "introduction.federation.action": "Next",
-  "introduction.federation.federated.headline": "Federated",
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
-  "introduction.federation.home.headline": "Home",
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
-  "introduction.federation.local.headline": "Local",
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
+  "intervals.full.days": "{number, plural, other {# hari}}",
+  "intervals.full.hours": "{number, plural, other {# jam}}",
+  "intervals.full.minutes": "{number, plural, other {# menit}}",
+  "introduction.federation.action": "Selanjutnya",
+  "introduction.federation.federated.headline": "Gabungan",
+  "introduction.federation.federated.text": "Pos publik dari server fediverse lain akan muncul di linimasa gabungan.",
+  "introduction.federation.home.headline": "Beranda",
+  "introduction.federation.home.text": "Pos dari orang yang Anda ikuti akan muncul di beranda. Anda dapat mengikuti siapa pun dari server mana pun!",
+  "introduction.federation.local.headline": "Lokal",
+  "introduction.federation.local.text": "Pos publik dari orang yang ada di server sama denganmu akan muncul di linimasa lokal.",
   "introduction.interactions.action": "Finish toot-orial!",
-  "introduction.interactions.favourite.headline": "Favourite",
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
+  "introduction.interactions.favourite.headline": "Favorit",
+  "introduction.interactions.favourite.text": "Anda dapat menyimpan toot untuk dibaca nanti, biarkan penulis tahu Anda menyukainya, dengan memfavoritkannya.",
   "introduction.interactions.reblog.headline": "Boost",
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
-  "introduction.interactions.reply.headline": "Reply",
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
-  "introduction.welcome.action": "Let's go!",
-  "introduction.welcome.headline": "First steps",
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
+  "introduction.interactions.reblog.text": "Anda dapat membagikan toot orang lain kepada pengikut Anda dengan mem-boost-nya.",
+  "introduction.interactions.reply.headline": "Balas",
+  "introduction.interactions.reply.text": "Anda dapat membalas toot Anda dan orang lain, yang akan menjalin dalam satu percakapan.",
+  "introduction.welcome.action": "Ayo!",
+  "introduction.welcome.headline": "Langkah pertama",
+  "introduction.welcome.text": "Selamat datang di fediverse! Beberapa saat lagi, Anda dapat menyiarkan pesan dan berbincang dengan teman lintas server. Namun server ini, {domain}, spesial--ia menyimpan profil Anda, jadi ingatlah namanya.",
   "keyboard_shortcuts.back": "untuk kembali",
-  "keyboard_shortcuts.blocked": "to open blocked users list",
+  "keyboard_shortcuts.blocked": "buka daftar pengguna terblokir",
   "keyboard_shortcuts.boost": "untuk menyebarkan",
   "keyboard_shortcuts.column": "untuk fokus kepada sebuah status di sebuah kolom",
   "keyboard_shortcuts.compose": "untuk fokus ke area penulisan",
   "keyboard_shortcuts.description": "Deskripsi",
-  "keyboard_shortcuts.direct": "to open direct messages column",
+  "keyboard_shortcuts.direct": "buka kolom pesan langsung",
   "keyboard_shortcuts.down": "untuk pindah ke bawah dalam sebuah daftar",
   "keyboard_shortcuts.enter": "untuk membuka status",
   "keyboard_shortcuts.favourite": "untuk memfavoritkan",
-  "keyboard_shortcuts.favourites": "to open favourites list",
-  "keyboard_shortcuts.federated": "to open federated timeline",
+  "keyboard_shortcuts.favourites": "buka daftar favorit",
+  "keyboard_shortcuts.federated": "buka linimasa gabungan",
   "keyboard_shortcuts.heading": "Pintasan keyboard",
-  "keyboard_shortcuts.home": "to open home timeline",
-  "keyboard_shortcuts.hotkey": "Hotkey",
-  "keyboard_shortcuts.legend": "to display this legend",
-  "keyboard_shortcuts.local": "to open local timeline",
-  "keyboard_shortcuts.mention": "to mention author",
-  "keyboard_shortcuts.muted": "to open muted users list",
-  "keyboard_shortcuts.my_profile": "to open your profile",
-  "keyboard_shortcuts.notifications": "to open notifications column",
-  "keyboard_shortcuts.pinned": "to open pinned toots list",
-  "keyboard_shortcuts.profile": "to open author's profile",
-  "keyboard_shortcuts.reply": "to reply",
-  "keyboard_shortcuts.requests": "to open follow requests list",
+  "keyboard_shortcuts.home": "buka linimasa beranda",
+  "keyboard_shortcuts.hotkey": "Pintasan",
+  "keyboard_shortcuts.legend": "tampilkan legenda ini",
+  "keyboard_shortcuts.local": "buka linimasa lokal",
+  "keyboard_shortcuts.mention": "sebut pencipta",
+  "keyboard_shortcuts.muted": "buka daftar pengguna terbisukan",
+  "keyboard_shortcuts.my_profile": "buka profil Anda",
+  "keyboard_shortcuts.notifications": "buka kolom notifikasi",
+  "keyboard_shortcuts.pinned": "buka daftar toot tersemat",
+  "keyboard_shortcuts.profile": "buka profil pencipta",
+  "keyboard_shortcuts.reply": "balas",
+  "keyboard_shortcuts.requests": "buka daftar permintaan ikuti",
   "keyboard_shortcuts.search": "untuk fokus mencari",
-  "keyboard_shortcuts.start": "to open \"get started\" column",
-  "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
-  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
-  "keyboard_shortcuts.toot": "to start a brand new toot",
+  "keyboard_shortcuts.start": "buka kolom \"memulai\"",
+  "keyboard_shortcuts.toggle_hidden": "tampilkan/sembunyikan teks di belakang CW",
+  "keyboard_shortcuts.toggle_sensitivity": "tampilkan/sembunyikan media",
+  "keyboard_shortcuts.toot": "mulai toot baru",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
   "keyboard_shortcuts.up": "to move up in the list",
   "lightbox.close": "Tutup",
-  "lightbox.next": "Next",
-  "lightbox.previous": "Previous",
-  "lightbox.view_context": "View context",
-  "lists.account.add": "Add to list",
-  "lists.account.remove": "Remove from list",
-  "lists.delete": "Delete list",
-  "lists.edit": "Edit list",
-  "lists.edit.submit": "Change title",
-  "lists.new.create": "Add list",
-  "lists.new.title_placeholder": "New list title",
-  "lists.search": "Search among people you follow",
-  "lists.subheading": "Your lists",
+  "lightbox.next": "Selanjutnya",
+  "lightbox.previous": "Sebelumnya",
+  "lightbox.view_context": "Lihat konteks",
+  "lists.account.add": "Tambah ke daftar",
+  "lists.account.remove": "Hapus dari daftar",
+  "lists.delete": "Hapus daftar",
+  "lists.edit": "Sunting daftar",
+  "lists.edit.submit": "Ubah judul",
+  "lists.new.create": "Tambah daftar",
+  "lists.new.title_placeholder": "Judul daftar baru",
+  "lists.search": "Cari di antara orang yang Anda ikuti",
+  "lists.subheading": "Daftar Anda",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Tunggu sebentar...",
   "media_gallery.toggle_visible": "Tampil/Sembunyikan",
   "missing_indicator.label": "Tidak ditemukan",
-  "missing_indicator.sublabel": "This resource could not be found",
-  "mute_modal.hide_notifications": "Hide notifications from this user?",
-  "navigation_bar.apps": "Mobile apps",
+  "missing_indicator.sublabel": "Sumber daya tak bisa ditemukan",
+  "mute_modal.hide_notifications": "Sembunyikan notifikasi dari pengguna ini?",
+  "navigation_bar.apps": "Aplikasi mobile",
   "navigation_bar.blocks": "Pengguna diblokir",
   "navigation_bar.community_timeline": "Linimasa lokal",
-  "navigation_bar.compose": "Compose new toot",
-  "navigation_bar.direct": "Direct messages",
-  "navigation_bar.discover": "Discover",
-  "navigation_bar.domain_blocks": "Hidden domains",
+  "navigation_bar.compose": "Tulis toot baru",
+  "navigation_bar.direct": "Pesan langsung",
+  "navigation_bar.discover": "Temukan",
+  "navigation_bar.domain_blocks": "Domain tersembunyi",
   "navigation_bar.edit_profile": "Ubah profil",
   "navigation_bar.favourites": "Favorit",
-  "navigation_bar.filters": "Muted words",
+  "navigation_bar.filters": "Kata yang dibisukan",
   "navigation_bar.follow_requests": "Permintaan mengikuti",
   "navigation_bar.follows_and_followers": "Follows and followers",
   "navigation_bar.info": "Informasi selengkapnya",
   "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
-  "navigation_bar.lists": "Lists",
+  "navigation_bar.lists": "Daftar",
   "navigation_bar.logout": "Keluar",
   "navigation_bar.mutes": "Pengguna dibisukan",
   "navigation_bar.personal": "Personal",
-  "navigation_bar.pins": "Pinned toots",
+  "navigation_bar.pins": "Toot tersemat",
   "navigation_bar.preferences": "Pengaturan",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Linimasa gabungan",
-  "navigation_bar.security": "Security",
+  "navigation_bar.security": "Keamanan",
   "notification.favourite": "{name} menyukai status anda",
   "notification.follow": "{name} mengikuti anda",
-  "notification.mention": "{name} mentioned you",
-  "notification.poll": "A poll you have voted in has ended",
+  "notification.mention": "{name} menyebut Anda",
+  "notification.poll": "Japat yang Anda ikuti telah berakhir",
   "notification.reblog": "{name} mem-boost status anda",
   "notifications.clear": "Hapus notifikasi",
   "notifications.clear_confirmation": "Apa anda yakin hendak menghapus semua notifikasi anda?",
   "notifications.column_settings.alert": "Notifikasi desktop",
   "notifications.column_settings.favourite": "Favorit:",
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
-  "notifications.column_settings.filter_bar.show": "Show",
+  "notifications.column_settings.filter_bar.advanced": "Tampilkan semua kategori",
+  "notifications.column_settings.filter_bar.category": "Bilah penyaring cepat",
+  "notifications.column_settings.filter_bar.show": "Tampilkan",
   "notifications.column_settings.follow": "Pengikut baru:",
   "notifications.column_settings.mention": "Balasan:",
-  "notifications.column_settings.poll": "Poll results:",
-  "notifications.column_settings.push": "Push notifications",
+  "notifications.column_settings.poll": "Hasil japat:",
+  "notifications.column_settings.push": "Notifikasi dorong",
   "notifications.column_settings.reblog": "Boost:",
   "notifications.column_settings.show": "Tampilkan dalam kolom",
   "notifications.column_settings.sound": "Mainkan suara",
-  "notifications.filter.all": "All",
-  "notifications.filter.boosts": "Boosts",
-  "notifications.filter.favourites": "Favourites",
-  "notifications.filter.follows": "Follows",
-  "notifications.filter.mentions": "Mentions",
-  "notifications.filter.polls": "Poll results",
-  "notifications.group": "{count} notifications",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
+  "notifications.filter.all": "Semua",
+  "notifications.filter.boosts": "Boost",
+  "notifications.filter.favourites": "Favorit",
+  "notifications.filter.follows": "Diikuti",
+  "notifications.filter.mentions": "Sebutan",
+  "notifications.filter.polls": "Hasil japat",
+  "notifications.group": "{count} notifikasi",
+  "poll.closed": "Ditutup",
+  "poll.refresh": "Segarkan",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Tentukan privasi status",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Publik",
   "privacy.unlisted.long": "Tidak ditampilkan di linimasa publik",
   "privacy.unlisted.short": "Tak Terdaftar",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Loading…",
   "regeneration_indicator.sublabel": "Linimasa anda sedang disiapkan!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "People",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {hasil} other {hasil}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Tampilkan semua",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Naskah anda akan hilang jika anda keluar dari Mastodon.",
   "upload_area.title": "Seret & lepaskan untuk mengunggah",
   "upload_button.label": "Tambahkan media",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Deskripsikan untuk mereka yang tidak bisa melihat dengan jelas",
-  "upload_form.focus": "Potong",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Undo",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Mengunggah...",
   "video.close": "Close video",
   "video.exit_fullscreen": "Keluar dari layar penuh",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index 2d73f9226bd45531aac217f9d5311213a7c83896..06db699cb248a6720df8b6c84ba6da0232e949ca 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -4,6 +4,7 @@
   "account.block": "Blokusar @{name}",
   "account.block_domain": "Hide everything from {domain}",
   "account.blocked": "Blocked",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Direct Message @{name}",
   "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Modifikar profilo",
@@ -15,6 +16,7 @@
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Sequas tu",
   "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Media",
@@ -23,6 +25,7 @@
   "account.mute": "Celar @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.muted": "Muted",
+  "account.never_active": "Never",
   "account.posts": "Mesaji",
   "account.posts_with_replies": "Toots with replies",
   "account.report": "Denuncar @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Ne plus sequar",
   "account.unmute": "Ne plus celar @{name}",
   "account.unmute_notifications": "Unmute notifications from @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Tu povas presar sur {combo} por omisar co en la venonta foyo",
   "bundle_column_error.body": "Something went wrong while loading this component.",
   "bundle_column_error.retry": "Try again",
@@ -47,6 +53,7 @@
   "column.blocks": "Blokusita uzeri",
   "column.community": "Lokala tempolineo",
   "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favorati",
   "column.follow_requests": "Demandi di sequado",
@@ -64,7 +71,7 @@
   "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.settings": "Settings",
-  "community.column_settings.media_only": "Media Only",
+  "community.column_settings.media_only": "Media only",
   "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.",
   "compose_form.direct_message_warning_learn_more": "Learn more",
   "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
   "confirmations.domain_block.confirm": "Hide entire domain",
   "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Mute",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "embed.preview": "Here is what it will look like:",
   "emoji_button.activity": "Activity",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Tu havas ankore nula savigo. Komunikez kun altri por debutar la konverso.",
   "empty_column.public": "Esas nulo hike! Skribez ulo publike, o manuale sequez uzeri de altra instaluri por plenigar ol.",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "New list title",
   "lists.search": "Search among people you follow",
   "lists.subheading": "Your lists",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Kargante...",
   "media_gallery.toggle_visible": "Chanjar videbleso",
   "missing_indicator.label": "Ne trovita",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Pinned toots",
   "navigation_bar.preferences": "Preferi",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Federata tempolineo",
   "navigation_bar.security": "Security",
   "notification.favourite": "{name} favorizis tua mesajo",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Aranjar privateso di mesaji",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Publike",
   "privacy.unlisted.long": "Ne montrar en publika tempolinei",
   "privacy.unlisted.short": "Ne enlistigota",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Loading…",
   "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "People",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezulti}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Montrar plue",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
   "upload_area.title": "Tranar faligar por kargar",
   "upload_button.label": "Adjuntar kontenajo",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
-  "upload_form.focus": "Crop",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Desfacar",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Kargante...",
   "video.close": "Close video",
   "video.exit_fullscreen": "Exit full screen",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index 9f803e3553b422d8becb4688cb414096bc41c858..c5b1d724b7ee220a944d5ba67ef7ad23c0a062b8 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -4,7 +4,8 @@
   "account.block": "Blocca @{name}",
   "account.block_domain": "Nascondi tutto da {domain}",
   "account.blocked": "Bloccato",
-  "account.direct": "Invia messaggio diretto a @{name}",
+  "account.cancel_follow_request": "Annulla richiesta di seguito",
+  "account.direct": "Invia messaggio privato a @{name}",
   "account.domain_blocked": "Dominio nascosto",
   "account.edit_profile": "Modifica profilo",
   "account.endorse": "Metti in evidenza sul profilo",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Questo utente non segue ancora nessuno.",
   "account.follows_you": "Ti segue",
   "account.hide_reblogs": "Nascondi condivisioni da @{name}",
+  "account.last_status": "Ultima attività",
   "account.link_verified_on": "La proprietà di questo link è stata controllata il {date}",
   "account.locked_info": "Il livello di privacy di questo account è impostato a \"bloccato\". Il proprietario esamina manualmente le richieste di seguirlo.",
   "account.media": "Media",
@@ -23,6 +25,7 @@
   "account.mute": "Silenzia @{name}",
   "account.mute_notifications": "Silenzia notifiche da @{name}",
   "account.muted": "Silenziato",
+  "account.never_active": "Mai",
   "account.posts": "Toot",
   "account.posts_with_replies": "Toot e risposte",
   "account.report": "Segnala @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Non seguire",
   "account.unmute": "Non silenziare @{name}",
   "account.unmute_notifications": "Non silenziare più le notifiche da @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Si è verificato un errore inatteso.",
   "alert.unexpected.title": "Oops!",
+  "autosuggest_hashtag.per_week": "{count} per settimana",
   "boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta",
   "bundle_column_error.body": "E' avvenuto un errore durante il caricamento di questo componente.",
   "bundle_column_error.retry": "Riprova",
@@ -47,6 +53,7 @@
   "column.blocks": "Utenti bloccati",
   "column.community": "Timeline locale",
   "column.direct": "Messaggi diretti",
+  "column.directory": "Sfoglia profili",
   "column.domain_blocks": "Domini nascosti",
   "column.favourites": "Apprezzati",
   "column.follow_requests": "Richieste di amicizia",
@@ -66,7 +73,7 @@
   "column_subheading.settings": "Impostazioni",
   "community.column_settings.media_only": "Solo media",
   "compose_form.direct_message_warning": "Questo toot sarà mandato solo a tutti gli utenti menzionati.",
-  "compose_form.direct_message_warning_learn_more": "Per saperne di piu'",
+  "compose_form.direct_message_warning_learn_more": "Per saperne di più",
   "compose_form.hashtag_warning": "Questo toot non è listato, quindi non sarà trovato nelle ricerche per hashtag. Solo i toot pubblici possono essere cercati per hashtag.",
   "compose_form.lock_disclaimer": "Il tuo account non è {bloccato}. Chiunque può decidere di seguirti per vedere i tuoi post per soli seguaci.",
   "compose_form.lock_disclaimer.lock": "bloccato",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Sei sicuro di voler cancellare definitivamente questa lista?",
   "confirmations.domain_block.confirm": "Nascondi intero dominio",
   "confirmations.domain_block.message": "Sei davvero sicuro che vuoi bloccare l'intero {domain}? Nella maggior parte dei casi, pochi blocchi o silenziamenti mirati sono sufficienti e preferibili. Non vedrai nessun contenuto di quel dominio né nelle timeline pubbliche né nelle notifiche. I tuoi seguaci di quel dominio saranno eliminati.",
+  "confirmations.logout.confirm": "Esci",
+  "confirmations.logout.message": "Sei sicuro di voler uscire?",
   "confirmations.mute.confirm": "Silenzia",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Sei sicuro di voler silenziare {name}?",
   "confirmations.redraft.confirm": "Cancella e riscrivi",
   "confirmations.redraft.message": "Sei sicuro di voler cancellare questo stato e riscriverlo? Perderai tutte le risposte, condivisioni e preferiti.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Se rispondi ora, il messaggio che stai componendo sarà sovrascritto. Sei sicuro di voler continuare?",
   "confirmations.unfollow.confirm": "Smetti di seguire",
   "confirmations.unfollow.message": "Sei sicuro che non vuoi più seguire {name}?",
+  "conversation.delete": "Elimina conversazione",
+  "conversation.mark_as_read": "Segna come letto",
+  "conversation.open": "Visualizza conversazione",
+  "conversation.with": "Con {names}",
+  "directory.federated": "Da un fediverso noto",
+  "directory.local": "Solo da {domain}",
+  "directory.new_arrivals": "Nuovi arrivi",
+  "directory.recently_active": "Attivo di recente",
   "embed.instructions": "Inserisci questo status nel tuo sito copiando il codice qui sotto.",
   "embed.preview": "Ecco come apparirà:",
   "emoji_button.activity": "Attività",
@@ -121,7 +139,7 @@
   "empty_column.account_unavailable": "Profilo non disponibile",
   "empty_column.blocks": "Non hai ancora bloccato nessun utente.",
   "empty_column.community": "La timeline locale è vuota. Condividi qualcosa pubblicamente per dare inizio alla festa!",
-  "empty_column.direct": "Non hai ancora nessun messaggio diretto. Quando ne manderai o riceverai qualcuno, apparirà qui.",
+  "empty_column.direct": "Non hai ancora nessun messaggio privato. Quando ne manderai o riceverai qualcuno, apparirà qui.",
   "empty_column.domain_blocks": "Non vi sono domini nascosti.",
   "empty_column.favourited_statuses": "Non hai ancora segnato nessun toot come apprezzato. Quando lo farai, comparirà qui.",
   "empty_column.favourites": "Nessuno ha ancora segnato questo toot come apprezzato. Quando qualcuno lo farà, apparirà qui.",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Non hai ancora silenziato nessun utente.",
   "empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.",
   "empty_column.public": "Qui non c'è nulla! Scrivi qualcosa pubblicamente, o aggiungi utenti da altri server per riempire questo spazio",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -142,7 +164,7 @@
   "follow_request.authorize": "Autorizza",
   "follow_request.reject": "Rifiuta",
   "getting_started.developers": "Sviluppatori",
-  "getting_started.directory": "Directory del profilo",
+  "getting_started.directory": "Directory dei profili",
   "getting_started.documentation": "Documentazione",
   "getting_started.heading": "Come iniziare",
   "getting_started.invite": "Invita qualcuno",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Titolo della nuova lista",
   "lists.search": "Cerca tra le persone che segui",
   "lists.subheading": "Le tue liste",
+  "load_pending": "{count, plural, one {# nuovo oggetto} other {# nuovi oggetti}}",
   "loading_indicator.label": "Caricamento...",
   "media_gallery.toggle_visible": "Imposta visibilità",
   "missing_indicator.label": "Non trovato",
@@ -242,7 +265,7 @@
   "navigation_bar.favourites": "Apprezzati",
   "navigation_bar.filters": "Parole silenziate",
   "navigation_bar.follow_requests": "Richieste di amicizia",
-  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.follows_and_followers": "Seguiti e seguaci",
   "navigation_bar.info": "Informazioni su questo server",
   "navigation_bar.keyboard_shortcuts": "Tasti di scelta rapida",
   "navigation_bar.lists": "Liste",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personale",
   "navigation_bar.pins": "Toot fissati in cima",
   "navigation_bar.preferences": "Impostazioni",
-  "navigation_bar.profile_directory": "Directory dei profili",
   "navigation_bar.public_timeline": "Timeline federata",
   "navigation_bar.security": "Sicurezza",
   "notification.favourite": "{name} ha apprezzato il tuo post",
@@ -282,19 +304,22 @@
   "notifications.group": "{count} notifiche",
   "poll.closed": "Chiuso",
   "poll.refresh": "Aggiorna",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# voto} other {# voti}}",
   "poll.vote": "Vota",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Aggiungi un sondaggio",
   "poll_button.remove_poll": "Rimuovi sondaggio",
   "privacy.change": "Modifica privacy del post",
   "privacy.direct.long": "Invia solo a utenti menzionati",
-  "privacy.direct.short": "Diretto",
+  "privacy.direct.short": "Diretto in privato",
   "privacy.private.long": "Invia solo ai seguaci",
   "privacy.private.short": "Privato",
   "privacy.public.long": "Invia alla timeline pubblica",
   "privacy.public.short": "Pubblico",
   "privacy.unlisted.long": "Non mostrare sulla timeline pubblica",
   "privacy.unlisted.short": "Non elencato",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Caricamento in corso…",
   "regeneration_indicator.sublabel": "Stiamo preparando il tuo home feed!",
   "relative_time.days": "{number}g",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Gente",
   "search_results.hashtags": "Hashtag",
   "search_results.statuses": "Toot",
+  "search_results.statuses_fts_disabled": "La ricerca di toot per il loro contenuto non è abilitata su questo server Mastodon.",
   "search_results.total": "{count} {count, plural, one {risultato} other {risultati}}",
   "status.admin_account": "Apri interfaccia di moderazione per @{name}",
   "status.admin_status": "Apri questo status nell'interfaccia di moderazione",
@@ -328,7 +354,7 @@
   "status.copy": "Copia link allo status",
   "status.delete": "Elimina",
   "status.detailed_status": "Vista conversazione dettagliata",
-  "status.direct": "Messaggio diretto @{name}",
+  "status.direct": "Messaggio privato @{name}",
   "status.embed": "Incorpora",
   "status.favourite": "Apprezzato",
   "status.filtered": "Filtrato",
@@ -358,6 +384,7 @@
   "status.show_more": "Mostra di più",
   "status.show_more_all": "Mostra di più per tutti",
   "status.show_thread": "Mostra thread",
+  "status.uncached_media_warning": "Non disponibile",
   "status.unmute_conversation": "Annulla silenzia conversazione",
   "status.unpin": "Non fissare in cima al profilo",
   "suggestions.dismiss": "Elimina suggerimento",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Restano pochi istanti",
   "time_remaining.seconds": "{number, plural, one {# secondo} other {# secondi}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {persona ne sta} other {persone ne stanno}} parlando",
+  "trends.trending_now": "Di tendenza ora",
   "ui.beforeunload": "La bozza andrà persa se esci da Mastodon.",
   "upload_area.title": "Trascina per caricare",
   "upload_button.label": "Aggiungi file multimediale",
   "upload_error.limit": "Limite al caricamento di file superato.",
   "upload_error.poll": "Caricamento file non consentito nei sondaggi.",
   "upload_form.description": "Descrizione per utenti con disabilità visive",
-  "upload_form.focus": "Modifica anteprima",
+  "upload_form.edit": "Modifica",
   "upload_form.undo": "Cancella",
+  "upload_modal.analyzing_picture": "Analisi immagine…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "Ma la volpe col suo balzo ha raggiunto il quieto Fido",
+  "upload_modal.detect_text": "Rileva testo dall'immagine",
+  "upload_modal.edit_media": "Modifica media",
+  "upload_modal.hint": "Clicca o trascina il cerchio sull'anteprima per scegliere il punto focale che sarà sempre visualizzato su tutte le miniature.",
+  "upload_modal.preview_label": "Anteprima ({ratio})",
   "upload_progress.label": "Sto caricando...",
   "video.close": "Chiudi video",
   "video.exit_fullscreen": "Esci da modalità a schermo intero",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index c9dd98e7613fa746ade0663dff94b93e2f8885dc..f6026824c12d4f031489a3e64f3a6ecc9dab4ba3 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -4,6 +4,7 @@
   "account.block": "@{name}さんをブロック",
   "account.block_domain": "{domain}全体を非表示",
   "account.blocked": "ブロック済み",
+  "account.cancel_follow_request": "フォローリクエストを取り消す",
   "account.direct": "@{name}さんにダイレクトメッセージ",
   "account.domain_blocked": "ドメイン非表示中",
   "account.edit_profile": "プロフィール編集",
@@ -15,6 +16,7 @@
   "account.follows.empty": "まだ誰もフォローしていません。",
   "account.follows_you": "フォローされています",
   "account.hide_reblogs": "@{name}さんからのブーストを非表示",
+  "account.last_status": "最後の活動",
   "account.link_verified_on": "このリンクの所有権は{date}に確認されました",
   "account.locked_info": "このアカウントは承認制アカウントです。相手が承認するまでフォローは完了しません。",
   "account.media": "メディア",
@@ -23,6 +25,7 @@
   "account.mute": "@{name}さんをミュート",
   "account.mute_notifications": "@{name}さんからの通知を受け取らない",
   "account.muted": "ミュート済み",
+  "account.never_active": "活動なし",
   "account.posts": "投稿",
   "account.posts_with_replies": "投稿と返信",
   "account.report": "@{name}さんを通報",
@@ -35,8 +38,11 @@
   "account.unfollow": "フォロー解除",
   "account.unmute": "@{name}さんのミュートを解除",
   "account.unmute_notifications": "@{name}さんからの通知を受け取るようにする",
+  "alert.rate_limited.message": "{retry_time, time, medium} 以降に再度実行してください。",
+  "alert.rate_limited.title": "制限に達しました",
   "alert.unexpected.message": "不明なエラーが発生しました。",
   "alert.unexpected.title": "エラー!",
+  "autosuggest_hashtag.per_week": "{count} 回 / 週",
   "boost_modal.combo": "次からは{combo}を押せばスキップできます",
   "bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。",
   "bundle_column_error.retry": "再試行",
@@ -47,6 +53,7 @@
   "column.blocks": "ブロックしたユーザー",
   "column.community": "ローカルタイムライン",
   "column.direct": "ダイレクトメッセージ",
+  "column.directory": "ディレクトリ",
   "column.domain_blocks": "非表示にしたドメイン",
   "column.favourites": "お気に入り",
   "column.follow_requests": "フォローリクエスト",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "本当にこのリストを完全に削除しますか?",
   "confirmations.domain_block.confirm": "ドメイン全体を非表示",
   "confirmations.domain_block.message": "本当に{domain}全体を非表示にしますか? 多くの場合は個別にブロックやミュートするだけで充分であり、また好ましいです。公開タイムラインにそのドメインのコンテンツが表示されなくなり、通知も届かなくなります。そのドメインのフォロワーはアンフォローされます。",
+  "confirmations.logout.confirm": "ログアウト",
+  "confirmations.logout.message": "本当にログアウトしますか?",
   "confirmations.mute.confirm": "ミュート",
+  "confirmations.mute.explanation": "これにより相手のトゥートと返信は見えなくなりますが、引き続きあなたをフォローしトゥートを見ることはできます。",
   "confirmations.mute.message": "本当に{name}さんをミュートしますか?",
   "confirmations.redraft.confirm": "削除して下書きに戻す",
   "confirmations.redraft.message": "本当にこのトゥートを削除して下書きに戻しますか? このトゥートへのお気に入り登録やブーストは失われ、返信は孤立することになります。",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "今返信すると現在作成中のメッセージが上書きされます。本当に実行しますか?",
   "confirmations.unfollow.confirm": "フォロー解除",
   "confirmations.unfollow.message": "本当に{name}さんのフォローを解除しますか?",
+  "conversation.delete": "会話を削除",
+  "conversation.mark_as_read": "既読にする",
+  "conversation.open": "会話を表示",
+  "conversation.with": "{names}",
+  "directory.federated": "既知の連合より",
+  "directory.local": "{domain} のみ",
+  "directory.new_arrivals": "新着順",
+  "directory.recently_active": "最近の活動順",
   "embed.instructions": "下記のコードをコピーしてウェブサイトに埋め込みます。",
   "embed.preview": "表示例:",
   "emoji_button.activity": "活動",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "まだ誰もミュートしていません。",
   "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
   "empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のサーバーのユーザーをフォローしたりしていっぱいにしましょう",
+  "error.unexpected_crash.explanation": "不具合かブラウザの互換性問題のため、このページを正しく表示できませんでした。",
+  "error.unexpected_crash.next_steps": "ページの再読み込みをお試しください。それでも解決しない場合、別のブラウザかアプリを使えば使用できることがあります。",
+  "errors.unexpected_crash.copy_stacktrace": "スタックトレースをクリップボードにコピー",
+  "errors.unexpected_crash.report_issue": "問題を報告",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -147,7 +169,7 @@
   "getting_started.heading": "スタート",
   "getting_started.invite": "招待",
   "getting_started.open_source_notice": "Mastodonはオープンソースソフトウェアです。誰でもGitHub ( {github} ) から開発に参加したり、問題を報告したりできます。",
-  "getting_started.security": "セキュリティ",
+  "getting_started.security": "アカウント設定",
   "getting_started.terms": "プライバシーポリシー",
   "hashtag.column_header.tag_mode.all": "と {additional}",
   "hashtag.column_header.tag_mode.any": "か {additional}",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "新規リスト名",
   "lists.search": "フォローしている人の中から検索",
   "lists.subheading": "あなたのリスト",
+  "load_pending": "{count} 件の新着",
   "loading_indicator.label": "読み込み中...",
   "media_gallery.toggle_visible": "表示切り替え",
   "missing_indicator.label": "見つかりません",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "個人用",
   "navigation_bar.pins": "固定したトゥート",
   "navigation_bar.preferences": "ユーザー設定",
-  "navigation_bar.profile_directory": "ディレクトリ",
   "navigation_bar.public_timeline": "連合タイムライン",
   "navigation_bar.security": "セキュリティ",
   "notification.favourite": "{name}さんがあなたのトゥートをお気に入りに登録しました",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} 件の通知",
   "poll.closed": "終了",
   "poll.refresh": "æ›´æ–°",
+  "poll.total_people": "{count}人",
   "poll.total_votes": "{count}票",
   "poll.vote": "投票",
+  "poll.voted": "この項目に投票しました",
   "poll_button.add_poll": "アンケートを追加",
   "poll_button.remove_poll": "アンケートを削除",
   "privacy.change": "公開範囲を変更",
@@ -295,6 +319,7 @@
   "privacy.public.short": "公開",
   "privacy.unlisted.long": "公開TLで表示しない",
   "privacy.unlisted.short": "未収載",
+  "refresh": "æ›´æ–°",
   "regeneration_indicator.label": "読み込み中…",
   "regeneration_indicator.sublabel": "ホームタイムラインは準備中です!",
   "relative_time.days": "{number}日前",
@@ -319,6 +344,7 @@
   "search_results.accounts": "人々",
   "search_results.hashtags": "ハッシュタグ",
   "search_results.statuses": "トゥート",
+  "search_results.statuses_fts_disabled": "このサーバーではトゥート本文の検索は利用できません。",
   "search_results.total": "{count, number}件の結果",
   "status.admin_account": "@{name} のモデレーション画面を開く",
   "status.admin_status": "このトゥートをモデレーション画面で開く",
@@ -358,6 +384,7 @@
   "status.show_more": "もっと見る",
   "status.show_more_all": "全て見る",
   "status.show_thread": "スレッドを表示",
+  "status.uncached_media_warning": "利用できません",
   "status.unmute_conversation": "会話のミュートを解除",
   "status.unpin": "プロフィールへの固定を解除",
   "suggestions.dismiss": "隠す",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "まもなく終了",
   "time_remaining.seconds": "残り{number}秒",
   "trends.count_by_accounts": "{count}人がトゥート",
+  "trends.trending_now": "トレンドタグ",
   "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。",
   "upload_area.title": "ドラッグ&ドロップでアップロード",
   "upload_button.label": "メディアを追加 ({formats})",
   "upload_error.limit": "アップロードできる上限を超えています。",
   "upload_error.poll": "アンケートではファイルをアップロードできません。",
   "upload_form.description": "視覚障害者のための説明",
-  "upload_form.focus": "プレビューを変更",
+  "upload_form.edit": "編集",
   "upload_form.undo": "削除",
+  "upload_modal.analyzing_picture": "画像を解析中…",
+  "upload_modal.apply": "適用",
+  "upload_modal.description_placeholder": "あのイーハトーヴォのすきとおった風",
+  "upload_modal.detect_text": "画像からテキストを検出",
+  "upload_modal.edit_media": "メディアを編集",
+  "upload_modal.hint": "サムネイルの焦点にしたい場所をクリックするか円形の枠をその場所にドラッグしてください。",
+  "upload_modal.preview_label": "プレビュー ({ratio})",
   "upload_progress.label": "アップロード中...",
   "video.close": "動画を閉じる",
   "video.exit_fullscreen": "全画面を終了する",
diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json
index b18fa7210d7ba7bf29e14cb3b71fa9cc2d7b9978..c378bc9c88916e28c398a8c659d611f095eed645 100644
--- a/app/javascript/mastodon/locales/ka.json
+++ b/app/javascript/mastodon/locales/ka.json
@@ -4,6 +4,7 @@
   "account.block": "დაბლოკე @{name}",
   "account.block_domain": "დაიმალოს ყველაფერი დომენიდან {domain}",
   "account.blocked": "დაიბლოკა",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "პირდაპირი წერილი @{name}-ს",
   "account.domain_blocked": "დომენი დამალულია",
   "account.edit_profile": "პროფილის ცვლილება",
@@ -15,6 +16,7 @@
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "მოგყვებათ",
   "account.hide_reblogs": "დაიმალოს ბუსტები @{name}-სგან",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "მედია",
@@ -23,6 +25,7 @@
   "account.mute": "გააჩუმე @{name}",
   "account.mute_notifications": "გააჩუმე შეტყობინებები @{name}-სგან",
   "account.muted": "გაჩუმებული",
+  "account.never_active": "Never",
   "account.posts": "ტუტები",
   "account.posts_with_replies": "ტუტები და პასუხები",
   "account.report": "დაარეპორტე @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "ნუღარ მიჰყვები",
   "account.unmute": "ნუღარ აჩუმებ @{name}-ს",
   "account.unmute_notifications": "ნუღარ აჩუმებ შეტყობინებებს @{name}-სგან",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "წარმოიშვა მოულოდნელი შეცდომა.",
   "alert.unexpected.title": "უპს!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "შეგიძლიათ დააჭიროთ {combo}-ს რათა შემდეგ ჯერზე გამოტოვოთ ეს",
   "bundle_column_error.body": "ამ კომპონენტის ჩატვირთვისას რაღაც აირია.",
   "bundle_column_error.retry": "სცადეთ კიდევ ერთხელ",
@@ -47,6 +53,7 @@
   "column.blocks": "დაბლოკილი მომხმარებლები",
   "column.community": "ლოკალური თაიმლაინი",
   "column.direct": "პირდაპირი წერილები",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "დამალული დომენები",
   "column.favourites": "ფავორიტები",
   "column.follow_requests": "დადევნების მოთხოვნები",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "დარწმუნებული ხართ, გსურთ სამუდამოდ გააუქმოთ ეს სია?",
   "confirmations.domain_block.confirm": "მთელი დომენის დამალვა",
   "confirmations.domain_block.message": "ნაღდად, ნაღდად, დარწმუნებული ხართ, გსურთ დაბლოკოთ მთელი {domain}? უმეტეს შემთხვევაში რამდენიმე გამიზნული ბლოკი ან გაჩუმება საკმარისი და უკეთესია. კონტენტს ამ დომენიდან ვერ იხილავთ ვერც ერთ ღია თაიმლაინზე ან თქვენს შეტყობინებებში. ამ დომენიდან არსებული მიმდევრები ამოიშლება.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "გაჩუმება",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "დარწმუნებული ხართ, გსურთ გააჩუმოთ {name}?",
   "confirmations.redraft.confirm": "გაუქმება და გადანაწილება",
   "confirmations.redraft.message": "დარწმუნებული ხართ, გსურთ გააუქმოთ ეს სტატუსი და გადაანაწილოთ? დაკარგავთ ყველა პასუხს, ბუსტს და მასზედ არსებულ ფავორიტს.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "ნუღარ მიჰყვები",
   "confirmations.unfollow.message": "დარწმუნებული ხართ, აღარ გსურთ მიჰყვებოდეთ {name}-ს?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "ეს სტატუსი ჩასვით თქვენს ვებ-საიტზე შემდეგი კოდის კოპირებით.",
   "embed.preview": "ესაა თუ როგორც გამოჩნდება:",
   "emoji_button.activity": "აქტივობა",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "ჯერ შეტყობინებები არ გაქვთ. საუბრის დასაწყებად იურთიერთქმედეთ სხვებთან.",
   "empty_column.public": "აქ არაფერია! შესავსებად, დაწერეთ რაიმე ღიად ან ხელით გაჰყევით მომხმარებლებს სხვა ინსტანციებისგან",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "ახალი სიის სათაური",
   "lists.search": "ძებნა ადამიანებს შორის რომელთაც მიჰყვებით",
   "lists.subheading": "თქვენი სიები",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "იტვირთება...",
   "media_gallery.toggle_visible": "ხილვადობის ჩართვა",
   "missing_indicator.label": "არაა ნაპოვნი",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "პირადი",
   "navigation_bar.pins": "აპინული ტუტები",
   "navigation_bar.preferences": "პრეფერენსიები",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "ფედერალური თაიმლაინი",
   "navigation_bar.security": "უსაფრთხოება",
   "notification.favourite": "{name}-მა თქვენი სტატუსი აქცია ფავორიტად",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} შეტყობინება",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "სტატუსის კონფიდენციალურობის მითითება",
@@ -295,6 +319,7 @@
   "privacy.public.short": "საჯარო",
   "privacy.unlisted.long": "არ დაიპოსტოს საჯარო თაიმლაინებზე",
   "privacy.unlisted.short": "ჩამოუთვლელი",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "იტვირთება…",
   "regeneration_indicator.sublabel": "თქვენი სახლის ლენტა მზადდება!",
   "relative_time.days": "{number}დღ",
@@ -319,6 +344,7 @@
   "search_results.accounts": "ხალხი",
   "search_results.hashtags": "ჰეშტეგები",
   "search_results.statuses": "ტუტები",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "აჩვენე მეტი",
   "status.show_more_all": "აჩვენე მეტი ყველაზე",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "საუბარზე გაჩუმების მოშორება",
   "status.unpin": "პროფილიდან პინის მოშორება",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} საუბრობს",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "თქვენი დრაფტი გაუქმდება თუ დატოვებთ მასტოდონს.",
   "upload_area.title": "გადმოწიეთ და ჩააგდეთ ასატვირთათ",
   "upload_button.label": "მედიის დამატება",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "აღწერილობა ვიზუალურად უფასურისთვის",
-  "upload_form.focus": "კროპი",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "გაუქმება",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "იტვირთება...",
   "video.close": "ვიდეოს დახურვა",
   "video.exit_fullscreen": "სრულ ეკრანზე ჩვენების გათიშვა",
diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json
index f551783a7de4af7022b3dfcf076b01720c40fc3f..be0a5050397af653ffc7d9a3d66f29bae882d38b 100644
--- a/app/javascript/mastodon/locales/kk.json
+++ b/app/javascript/mastodon/locales/kk.json
@@ -4,6 +4,7 @@
   "account.block": "Бұғаттау @{name}",
   "account.block_domain": "Домендегі барлығын бұғатта {domain}",
   "account.blocked": "Бұғатталды",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Жеке хат @{name}",
   "account.domain_blocked": "Домен жабық",
   "account.edit_profile": "Профильді өңдеу",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Ешкімге жазылмапты.",
   "account.follows_you": "Сізге жазылыпты",
   "account.hide_reblogs": "@{name} атты қолданушының әрекеттерін жасыру",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Сілтеме меншігі расталған күн {date}",
   "account.locked_info": "Бұл қолданушы өзі туралы мәліметтерді жасырған. Тек жазылғандар ғана көре алады.",
   "account.media": "Медиа",
@@ -23,6 +25,7 @@
   "account.mute": "Үнсіз қылу @{name}",
   "account.mute_notifications": "@{name} туралы ескертпелерді жасыру",
   "account.muted": "Үнсіз",
+  "account.never_active": "Never",
   "account.posts": "Жазбалар",
   "account.posts_with_replies": "Жазбалар мен жауаптар",
   "account.report": "Шағымдану @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Оқымау",
   "account.unmute": "@{name} ескертпелерін қосу",
   "account.unmute_notifications": "@{name} ескертпелерін көрсету",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Бір нәрсе дұрыс болмады.",
   "alert.unexpected.title": "Өй!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Келесіде өткізіп жіберу үшін басыңыз {combo}",
   "bundle_column_error.body": "Бұл компонентті жүктеген кезде бір қате пайда болды.",
   "bundle_column_error.retry": "Қайтадан көріңіз",
@@ -47,6 +53,7 @@
   "column.blocks": "Бұғатталғандар",
   "column.community": "Жергілікті желі",
   "column.direct": "Жеке хаттар",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Жасырылған домендер",
   "column.favourites": "Таңдаулылар",
   "column.follow_requests": "Жазылу сұранымдары",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Бұл тізімді жоясыз ба шынымен?",
   "confirmations.domain_block.confirm": "Бұл доменді бұғатта",
   "confirmations.domain_block.message": "Бұл домендегі {domain} жазбаларды шынымен бұғаттайсыз ба? Кейде үнсіз қылып тастау да жеткілікті.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Үнсіз қылу",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "{name} атты қолданушы үнсіз болсын ба?",
   "confirmations.redraft.confirm": "Өшіруді құптау",
   "confirmations.redraft.message": "Бұл жазбаны өшіріп, нобайларға жібереміз бе? Барлық жауаптар мен лайктарды жоғалтасыз.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Жауабыңыз жазып жатқан жазбаңыздың үстіне кетеді. Жалғастырамыз ба?",
   "confirmations.unfollow.confirm": "Оқымау",
   "confirmations.unfollow.message": "\"{name} атты қолданушыға енді жазылғыңыз келмей ме?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Төмендегі кодты көшіріп алу арқылы жазбаны басқа сайттарға да орналастыра аласыз.",
   "embed.preview": "Былай көрінетін болады:",
   "emoji_button.activity": "Белсенділік",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Әзірше ешқандай үнсізге қойылған қолданушы жоқ.",
   "empty_column.notifications": "Әзірше ешқандай ескертпе жоқ. Басқалармен араласуды бастаңыз және пікірталастарға қатысыңыз.",
   "empty_column.public": "Ештеңе жоқ бұл жерде! Өзіңіз бастап жазып көріңіз немесе басқаларға жазылыңыз",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Жаңа тізім аты",
   "lists.search": "Сіз іздеген адамдар арасында іздеу",
   "lists.subheading": "Тізімдеріңіз",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Жүктеу...",
   "media_gallery.toggle_visible": "Көрінуді қосу",
   "missing_indicator.label": "Табылмады",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Жеке",
   "navigation_bar.pins": "Жабыстырылғандар",
   "navigation_bar.preferences": "Басымдықтар",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Жаһандық желі",
   "navigation_bar.security": "Қауіпсіздік",
   "notification.favourite": "{name} жазбаңызды таңдаулыға қосты",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} ескертпе",
   "poll.closed": "Жабық",
   "poll.refresh": "Жаңарту",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# дауыс} other {# дауыс}}",
   "poll.vote": "Дауыс беру",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Сауалнама қосу",
   "poll_button.remove_poll": "Сауалнаманы өшіру",
   "privacy.change": "Құпиялылықты реттеу",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Ашық",
   "privacy.unlisted.long": "Do not show in public timelines",
   "privacy.unlisted.short": "Тізімсіз",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Жүктеу…",
   "regeneration_indicator.sublabel": "Жергілікті желі құрылуда!",
   "relative_time.days": "{number}күн",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Адамдар",
   "search_results.hashtags": "Хэштегтер",
   "search_results.statuses": "Жазбалар",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "status.admin_account": "@{name} үшін модерация интерфейсін аш",
   "status.admin_status": "Бұл жазбаны модерация интерфейсінде аш",
@@ -358,6 +384,7 @@
   "status.show_more": "Толығырақ",
   "status.show_more_all": "Бәрін толығымен",
   "status.show_thread": "Желіні көрсет",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Пікірталасты үнсіз қылмау",
   "status.unpin": "Профильден алып тастау",
   "suggestions.dismiss": "Өткізіп жіберу",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Қалған уақыт",
   "time_remaining.seconds": "{number, plural, one {# секунд} other {# секунд}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} жазған екен",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Mastodon желісінен шықсаңыз, нобайыңыз сақталмайды.",
   "upload_area.title": "Жүктеу үшін сүйреп әкеліңіз",
   "upload_button.label": "Медиа қосу (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Файл жүктеу лимитінен асып кеттіңіз.",
   "upload_error.poll": "Сауалнамамен бірге файл жүктеуге болмайды.",
   "upload_form.description": "Көру қабілеті нашар адамдар үшін сипаттаңыз",
-  "upload_form.focus": "Превьюді өзгерту",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Өшіру",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Жүктеп жатыр...",
   "video.close": "Видеоны жабу",
   "video.exit_fullscreen": "Толық экраннан шық",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index f5977d0c669164524db5cb23b38b883418ae61a9..2640b43b415dbdf8323b65feae11678735ac8032 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -4,6 +4,7 @@
   "account.block": "@{name}을 차단",
   "account.block_domain": "{domain} 전체를 숨김",
   "account.blocked": "차단 됨",
+  "account.cancel_follow_request": "팔로우 요청 취소",
   "account.direct": "@{name}으로부터의 다이렉트 메시지",
   "account.domain_blocked": "도메인 숨겨짐",
   "account.edit_profile": "프로필 편집",
@@ -15,6 +16,7 @@
   "account.follows.empty": "이 유저는 아직 아무도 팔로우 하고 있지 않습니다.",
   "account.follows_you": "날 팔로우합니다",
   "account.hide_reblogs": "@{name}의 부스트를 숨기기",
+  "account.last_status": "마지막 활동",
   "account.link_verified_on": "{date}에 이 링크의 소유권이 확인 됨",
   "account.locked_info": "이 계정의 프라이버시 설정은 잠금으로 설정되어 있습니다. 계정 소유자가 수동으로 팔로어를 승인합니다.",
   "account.media": "미디어",
@@ -23,6 +25,7 @@
   "account.mute": "@{name} 뮤트",
   "account.mute_notifications": "@{name}의 알림을 뮤트",
   "account.muted": "뮤트 됨",
+  "account.never_active": "없음",
   "account.posts": "툿",
   "account.posts_with_replies": "툿과 답장",
   "account.report": "@{name} ì‹ ê³ ",
@@ -35,8 +38,11 @@
   "account.unfollow": "팔로우 해제",
   "account.unmute": "뮤트 해제",
   "account.unmute_notifications": "@{name}의 알림 뮤트 해제",
+  "alert.rate_limited.message": "{retry_time, time, medium}에 다시 시도해 주세요.",
+  "alert.rate_limited.title": "빈도 제한",
   "alert.unexpected.message": "예측하지 못한 에러가 발생했습니다.",
   "alert.unexpected.title": "ì•—!",
+  "autosuggest_hashtag.per_week": "주간 {count}회",
   "boost_modal.combo": "{combo}를 누르면 다음부터 이 과정을 건너뛸 수 있습니다",
   "bundle_column_error.body": "컴포넌트를 불러오는 과정에서 문제가 발생했습니다.",
   "bundle_column_error.retry": "다시 시도",
@@ -47,6 +53,7 @@
   "column.blocks": "차단 중인 사용자",
   "column.community": "로컬 타임라인",
   "column.direct": "다이렉트 메시지",
+  "column.directory": "프로필 둘러보기",
   "column.domain_blocks": "숨겨진 도메인",
   "column.favourites": "즐겨찾기",
   "column.follow_requests": "팔로우 요청",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "정말로 이 리스트를 삭제하시겠습니까?",
   "confirmations.domain_block.confirm": "도메인 전체를 숨김",
   "confirmations.domain_block.message": "정말로 {domain} 전체를 숨기시겠습니까? 대부분의 경우 개별 차단이나 뮤트로 충분합니다. 모든 공개 타임라인과 알림에서 해당 도메인에서 작성된 컨텐츠를 보지 못합니다. 해당 도메인 팔로워와의 관계가 사라집니다.",
+  "confirmations.logout.confirm": "로그아웃",
+  "confirmations.logout.message": "정말로 로그아웃 하시겠습니까?",
   "confirmations.mute.confirm": "뮤트",
+  "confirmations.mute.explanation": "이 동작은 그의 게시물, 그를 멘션하는 게시물을 숨깁니다, 하지만 여전히 그가 당신의 게시물을 보고 팔로우 할 수 있습니다.",
   "confirmations.mute.message": "정말로 {name}를 뮤트하시겠습니까?",
   "confirmations.redraft.confirm": "삭제하고 다시 쓰기",
   "confirmations.redraft.message": "정말로 이 포스트를 삭제하고 다시 쓰시겠습니까? 해당 포스트에 대한 부스트와 즐겨찾기를 잃게 되고 원본에 대한 답장은 연결 되지 않습니다.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "답글을 달기 위해 현재 작성 중인 메시지가 덮어 씌워집니다. 진행하시겠습니까?",
   "confirmations.unfollow.confirm": "언팔로우",
   "confirmations.unfollow.message": "정말로 {name}를 언팔로우하시겠습니까?",
+  "conversation.delete": "대화 삭제",
+  "conversation.mark_as_read": "읽은 상태로 표시",
+  "conversation.open": "대화 보기",
+  "conversation.with": "{names} 님과",
+  "directory.federated": "알려진 연합우주로부터",
+  "directory.local": "{domain}에서만",
+  "directory.new_arrivals": "새로운 사람들",
+  "directory.recently_active": "최근 활동",
   "embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 공유하세요.",
   "embed.preview": "다음과 같이 표시됩니다:",
   "emoji_button.activity": "활동",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "아직 아무도 뮤트하지 않았습니다.",
   "empty_column.notifications": "아직 알림이 없습니다. 다른 사람과 대화를 시작해 보세요.",
   "empty_column.public": "여기엔 아직 아무 것도 없습니다! 공개적으로 무언가 포스팅하거나, 다른 서버의 유저를 팔로우 해서 채워보세요",
+  "error.unexpected_crash.explanation": "버그 혹은 브라우저 호환성 문제로 이 페이지를 올바르게 표시할 수 없습니다.",
+  "error.unexpected_crash.next_steps": "페이지를 새로고침 해보세요. 그래도 해결되지 않는 경우, 다른 브라우저나 네이티브 앱으로도 마스토돈을 이용하실 수 있습니다.",
+  "errors.unexpected_crash.copy_stacktrace": "에러 내용을 클립보드에 복사",
+  "errors.unexpected_crash.report_issue": "문제 신고",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "새 리스트의 이름",
   "lists.search": "팔로우 중인 사람들 중에서 찾기",
   "lists.subheading": "당신의 리스트",
+  "load_pending": "{count}개의 새 항목",
   "loading_indicator.label": "불러오는 중...",
   "media_gallery.toggle_visible": "표시 전환",
   "missing_indicator.label": "찾을 수 없습니다",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "개인용",
   "navigation_bar.pins": "고정된 툿",
   "navigation_bar.preferences": "사용자 설정",
-  "navigation_bar.profile_directory": "프로필 디렉토리",
   "navigation_bar.public_timeline": "연합 타임라인",
   "navigation_bar.security": "보안",
   "notification.favourite": "{name}님이 즐겨찾기 했습니다",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} 개의 알림",
   "poll.closed": "마감됨",
   "poll.refresh": "새로고침",
-  "poll.total_votes": "{count} 명 참여",
+  "poll.total_people": "{count}명",
+  "poll.total_votes": "{count} 표",
   "poll.vote": "투표",
+  "poll.voted": "이 답변에 투표했습니다",
   "poll_button.add_poll": "투표 추가",
   "poll_button.remove_poll": "투표 삭제",
   "privacy.change": "포스트의 프라이버시 설정을 변경",
@@ -295,6 +319,7 @@
   "privacy.public.short": "공개",
   "privacy.unlisted.long": "공개 타임라인에 표시하지 않음",
   "privacy.unlisted.short": "타임라인에 비표시",
+  "refresh": "새로고침",
   "regeneration_indicator.label": "불러오는 중…",
   "regeneration_indicator.sublabel": "당신의 홈 피드가 준비되는 중입니다!",
   "relative_time.days": "{number}일 전",
@@ -319,6 +344,7 @@
   "search_results.accounts": "사람",
   "search_results.hashtags": "해시태그",
   "search_results.statuses": "툿",
+  "search_results.statuses_fts_disabled": "이 마스토돈 서버에선 툿의 내용을 통한 검색이 활성화 되어 있지 않습니다.",
   "search_results.total": "{count, number}건의 결과",
   "status.admin_account": "@{name}에 대한 모더레이션 인터페이스 열기",
   "status.admin_status": "모더레이션 인터페이스에서 이 게시물 열기",
@@ -331,7 +357,7 @@
   "status.direct": "@{name}에게 다이렉트 메시지",
   "status.embed": "공유하기",
   "status.favourite": "즐겨찾기",
-  "status.filtered": "필터링 됨",
+  "status.filtered": "필터로 걸러짐",
   "status.load_more": "더 보기",
   "status.local_only": "This post is only visible by other users of your instance",
   "status.media_hidden": "미디어 숨겨짐",
@@ -358,6 +384,7 @@
   "status.show_more": "더 보기",
   "status.show_more_all": "모두 펼치기",
   "status.show_thread": "글타래 보기",
+  "status.uncached_media_warning": "사용할 수 없음",
   "status.unmute_conversation": "이 대화의 뮤트 해제하기",
   "status.unpin": "고정 해제",
   "suggestions.dismiss": "추천 지우기",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "남은 시간",
   "time_remaining.seconds": "{number} 초 남음",
   "trends.count_by_accounts": "{count} 명의 사람들이 말하고 있습니다",
+  "trends.trending_now": "지금 유행중",
   "ui.beforeunload": "지금 나가면 저장되지 않은 항목을 잃게 됩니다.",
   "upload_area.title": "드래그 & 드롭으로 업로드",
   "upload_button.label": "미디어 추가 (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "파일 업로드 제한에 도달했습니다.",
   "upload_error.poll": "파일 업로드는 투표와 함께 첨부할 수 없습니다.",
   "upload_form.description": "시각장애인을 위한 설명",
-  "upload_form.focus": "미리보기 변경",
+  "upload_form.edit": "편집",
   "upload_form.undo": "삭제",
+  "upload_modal.analyzing_picture": "이미지 분석 중…",
+  "upload_modal.apply": "적용",
+  "upload_modal.description_placeholder": "다람쥐 헌 쳇바퀴 타고파",
+  "upload_modal.detect_text": "이미지에서 텍스트 추출",
+  "upload_modal.edit_media": "미디어 편집",
+  "upload_modal.hint": "미리보기를 클릭하거나 드래그 해서 포컬 포인트를 맞추세요. 이 점은 썸네일에 항상 보여질 부분을 나타냅니다.",
+  "upload_modal.preview_label": "미리보기 ({ratio})",
   "upload_progress.label": "업로드 중...",
   "video.close": "동영상 닫기",
   "video.exit_fullscreen": "전체화면 나가기",
diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json
index 7a71d84c9da96de9665a9ff2ff0f16121cd30389..611a5eb53cf3b0288fd322003f0e907bbc289665 100644
--- a/app/javascript/mastodon/locales/lt.json
+++ b/app/javascript/mastodon/locales/lt.json
@@ -4,6 +4,7 @@
   "account.block": "Block @{name}",
   "account.block_domain": "Hide everything from {domain}",
   "account.blocked": "Blocked",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Direct message @{name}",
   "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Edit profile",
@@ -15,6 +16,7 @@
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Media",
@@ -23,10 +25,11 @@
   "account.mute": "Mute @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.muted": "Muted",
+  "account.never_active": "Never",
   "account.posts": "Toots",
   "account.posts_with_replies": "Toots and replies",
   "account.report": "Report @{name}",
-  "account.requested": "Awaiting approval. Click to cancel follow request",
+  "account.requested": "Awaiting approval",
   "account.share": "Share @{name}'s profile",
   "account.show_reblogs": "Show boosts from @{name}",
   "account.unblock": "Unblock @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Unfollow",
   "account.unmute": "Unmute @{name}",
   "account.unmute_notifications": "Unmute notifications from @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "bundle_column_error.body": "Something went wrong while loading this component.",
   "bundle_column_error.retry": "Try again",
@@ -47,6 +53,7 @@
   "column.blocks": "Blocked users",
   "column.community": "Local timeline",
   "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
@@ -64,7 +71,7 @@
   "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.settings": "Settings",
-  "community.column_settings.media_only": "Media Only",
+  "community.column_settings.media_only": "Media only",
   "compose_form.direct_message_warning": "This toot will only be sent to all the mentioned users.",
   "compose_form.direct_message_warning_learn_more": "Learn more",
   "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
   "confirmations.domain_block.confirm": "Hide entire domain",
   "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Mute",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "embed.preview": "Here is what it will look like:",
   "emoji_button.activity": "Activity",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
   "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "New list title",
   "lists.search": "Search among people you follow",
   "lists.subheading": "Your lists",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Loading...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Pinned toots",
   "navigation_bar.preferences": "Preferences",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Federated timeline",
   "navigation_bar.security": "Security",
   "notification.favourite": "{name} favourited your status",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Do not show in public timelines",
   "privacy.unlisted.short": "Unlisted",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Loading…",
   "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "People",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Show more",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,15 +400,23 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
   "upload_area.title": "Drag & drop to upload",
-  "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
+  "upload_button.label": "Add media ({formats})",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
-  "upload_form.focus": "Crop",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Delete",
-  "upload_progress.label": "Uploading...",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
+  "upload_progress.label": "Uploading…",
   "video.close": "Close video",
   "video.exit_fullscreen": "Exit full screen",
   "video.expand": "Expand video",
diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json
index ccc5d4e57917f781d65c137574f383747fbd0d69..fc76339c54f534877d939069a95c88fa4c697cfc 100644
--- a/app/javascript/mastodon/locales/lv.json
+++ b/app/javascript/mastodon/locales/lv.json
@@ -4,6 +4,7 @@
   "account.block": "Bloķēt @{name}",
   "account.block_domain": "Slēpt visu no {domain}",
   "account.blocked": "Bloķēts",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Privātā ziņa @{name}",
   "account.domain_blocked": "Domēns ir paslēpts",
   "account.edit_profile": "Labot profilu",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Šis lietotājs pagaidām nevienam neseko.",
   "account.follows_you": "Seko tev",
   "account.hide_reblogs": "Paslēpt paceltos ierakstus no lietotāja @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Šīs saites piederība ir pārbaudīta {date}",
   "account.locked_info": "Šī konta privātuma status ir iestatīts slēgts. Īpašnieks izskatīs un izvēlēsies kas viņam drīkst sekot.",
   "account.media": "Mēdiji",
@@ -23,6 +25,7 @@
   "account.mute": "Apklusināt @{name}",
   "account.mute_notifications": "Nerādīt paziņojumus no @{name}",
   "account.muted": "Apklusināts",
+  "account.never_active": "Never",
   "account.posts": "Ieraksti",
   "account.posts_with_replies": "Ieraksti un atbildes",
   "account.report": "Ziņot par lietotāju @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Nesekot",
   "account.unmute": "Noņemt apklusinājumu no lietotāja @{name}",
   "account.unmute_notifications": "Rādīt paziņojumus no lietotāja @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Negaidīta kļūda.",
   "alert.unexpected.title": "Ups!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Nospied {combo} lai izlaistu šo nākamreiz",
   "bundle_column_error.body": "Kaut kas nogāja greizi ielādējot šo komponenti.",
   "bundle_column_error.retry": "Mēģini vēlreiz",
@@ -47,6 +53,7 @@
   "column.blocks": "Bloķētie lietotāji",
   "column.community": "Lokālā laika līnija",
   "column.direct": "Privātās ziņas",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Paslēptie domēni",
   "column.favourites": "Favorīti",
   "column.follow_requests": "Sekotāju pieprasījumi",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Vai tiešam vēlies neatgriezeniski dzēst šo sarakstu?",
   "confirmations.domain_block.confirm": "Paslēpt visu domēnu",
   "confirmations.domain_block.message": "Vai tu tiešām, tiešam vēlies bloķēt visu domēnu {domain}? Lielākajā daļā gadījumu pietiek ja nobloķē vai apklusini kādu. Tu neredzēsi saturu vai paziņojumus no šī domēna nevienā laika līnijā. Tavi sekotāji no šī domēna tiks noņemti.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Apklusināt",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Vai Tu tiešām velies apklusināt {name}?",
   "confirmations.redraft.confirm": "Dzēst un pārrakstīt",
   "confirmations.redraft.message": "Vai tiešām vēlies dzēst un pārrakstīt šo ierakstu? Favorīti un paceltie ieraksti tiks dzēsti, kā arī atbildes tiks atsaistītas no šī ieraksta.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Atbildot tagad tava ziņa ko šobrīd raksti tiks pārrakstīta. Vai tiešām vēlies turpināt?",
   "confirmations.unfollow.confirm": "Nesekot",
   "confirmations.unfollow.message": "Vai tiešam vairs nevēlies sekot lietotājam {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Iegul šo ziņojumu savā mājaslapā kopējot kodu zemāk.",
   "embed.preview": "Tas izskatīsies šādi:",
   "emoji_button.activity": "Aktivitāte",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Tu neesi nevienu apklusinājis.",
   "empty_column.notifications": "Tev nav paziņojumu. Iesaisties sarunās ar citiem.",
   "empty_column.public": "Šeit nekā nav, tukšums! Ieraksti kaut ko publiski, vai uzmeklē un seko kādam no citas instances",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "New list title",
   "lists.search": "Search among people you follow",
   "lists.subheading": "Your lists",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Loading...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Pinned toots",
   "navigation_bar.preferences": "Preferences",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Federated timeline",
   "navigation_bar.security": "Security",
   "notification.favourite": "{name} favourited your status",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Do not show in public timelines",
   "privacy.unlisted.short": "Unlisted",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Loading…",
   "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "People",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Show more",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,15 +400,23 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
   "upload_area.title": "Drag & drop to upload",
-  "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
+  "upload_button.label": "Add media ({formats})",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
-  "upload_form.focus": "Crop",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Delete",
-  "upload_progress.label": "Uploading...",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
+  "upload_progress.label": "Uploading…",
   "video.close": "Close video",
   "video.exit_fullscreen": "Exit full screen",
   "video.expand": "Expand video",
diff --git a/app/javascript/mastodon/locales/mk.json b/app/javascript/mastodon/locales/mk.json
new file mode 100644
index 0000000000000000000000000000000000000000..9a4a417af4165e335790bfa7f9172e2bfcbc6f60
--- /dev/null
+++ b/app/javascript/mastodon/locales/mk.json
@@ -0,0 +1,423 @@
+{
+  "account.add_or_remove_from_list": "Додади или одстрани од листа",
+  "account.badges.bot": "Бот",
+  "account.block": "Блокирај @{name}",
+  "account.block_domain": "Сокријај се од {domain}",
+  "account.blocked": "Блокиран",
+  "account.cancel_follow_request": "Одкажи барање за следење",
+  "account.direct": "Директна порана @{name}",
+  "account.domain_blocked": "Скриен домен",
+  "account.edit_profile": "Измени профил",
+  "account.endorse": "Карактеристики на профилот",
+  "account.follow": "Следи",
+  "account.followers": "Следбеници",
+  "account.followers.empty": "Никој не го следи овој корисник сеуште.",
+  "account.follows": "Следи",
+  "account.follows.empty": "Корисникот не следи никој сеуште.",
+  "account.follows_you": "Те следи тебе",
+  "account.hide_reblogs": "Сокриј буст од @{name}",
+  "account.last_status": "Последно активен",
+  "account.link_verified_on": "Сопстевноста на овај линк беше проверен на {date}",
+  "account.locked_info": "Статусот на приватност на овај корисник е сетиран како заклучен. Корисникот одлучува кој можи да го следи него.",
+  "account.media": "Медија",
+  "account.mention": "Спомни @{name}",
+  "account.moved_to": "{name} се пресели во:",
+  "account.mute": "Зачути го @{name}",
+  "account.mute_notifications": "Исклучи известувања од @{name}",
+  "account.muted": "Зачутено",
+  "account.never_active": "Никогаш",
+  "account.posts": "Тутови",
+  "account.posts_with_replies": "Тутови и реплики",
+  "account.report": "Пријави @{name}",
+  "account.requested": "Се чека одобрување. Кликни за да одкажиш барање за следење",
+  "account.share": "Сподели @{name} профил",
+  "account.show_reblogs": "Прикажи бустови од @{name}",
+  "account.unblock": "Одблокирај @{name}",
+  "account.unblock_domain": "Прикажи {domain}",
+  "account.unendorse": "Don't feature on profile",
+  "account.unfollow": "Одследи",
+  "account.unmute": "Зачути го @{name}",
+  "account.unmute_notifications": "Исклучи известувања од @{name}",
+  "alert.rate_limited.message": "Обидете се повторно после {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
+  "alert.unexpected.message": "Неочекувана грешка.",
+  "alert.unexpected.title": "Упс!",
+  "autosuggest_hashtag.per_week": "{count} неделно",
+  "boost_modal.combo": "Кликни {combo} за да го прескокниш ова нареден пат",
+  "bundle_column_error.body": "Се случи проблем при вчитувањето.",
+  "bundle_column_error.retry": "Обидете се повторно",
+  "bundle_column_error.title": "Мрежна грешка",
+  "bundle_modal_error.close": "Затвори",
+  "bundle_modal_error.message": "Настана грешка при прикажувањето на оваа веб-страница.",
+  "bundle_modal_error.retry": "Обидете се повторно",
+  "column.blocks": "Блокирани корисници",
+  "column.community": "Local timeline",
+  "column.direct": "Директна порака",
+  "column.directory": "Browse profiles",
+  "column.domain_blocks": "Hidden domains",
+  "column.favourites": "Favourites",
+  "column.follow_requests": "Follow requests",
+  "column.home": "Дома",
+  "column.lists": "Листа",
+  "column.mutes": "Muted users",
+  "column.notifications": "Известувања",
+  "column.pins": "Pinned toot",
+  "column.public": "Federated timeline",
+  "column_back_button.label": "Назад",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
+  "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
+  "column_header.unpin": "Unpin",
+  "column_subheading.settings": "Settings",
+  "community.column_settings.media_only": "Media only",
+  "compose_form.direct_message_warning": "This toot will only be sent to all the mentioned users.",
+  "compose_form.direct_message_warning_learn_more": "Learn more",
+  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+  "compose_form.lock_disclaimer.lock": "locked",
+  "compose_form.placeholder": "What is on your mind?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
+  "compose_form.publish": "Toot",
+  "compose_form.publish_loud": "{publish}!",
+  "compose_form.sensitive.hide": "Mark media as sensitive",
+  "compose_form.sensitive.marked": "Media is marked as sensitive",
+  "compose_form.sensitive.unmarked": "Media is not marked as sensitive",
+  "compose_form.spoiler.marked": "Text is hidden behind warning",
+  "compose_form.spoiler.unmarked": "Text is not hidden",
+  "compose_form.spoiler_placeholder": "Write your warning here",
+  "confirmation_modal.cancel": "Cancel",
+  "confirmations.block.block_and_report": "Block & Report",
+  "confirmations.block.confirm": "Блокирај",
+  "confirmations.block.message": "Are you sure you want to block {name}?",
+  "confirmations.delete.confirm": "Delete",
+  "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.delete_list.confirm": "Delete",
+  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
+  "confirmations.mute.confirm": "Mute",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
+  "confirmations.mute.message": "Are you sure you want to mute {name}?",
+  "confirmations.redraft.confirm": "Delete & redraft",
+  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
+  "confirmations.reply.confirm": "Reply",
+  "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+  "confirmations.unfollow.confirm": "Unfollow",
+  "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
+  "embed.instructions": "Embed this status on your website by copying the code below.",
+  "embed.preview": "Here is what it will look like:",
+  "emoji_button.activity": "Activity",
+  "emoji_button.custom": "Custom",
+  "emoji_button.flags": "Flags",
+  "emoji_button.food": "Food & Drink",
+  "emoji_button.label": "Insert emoji",
+  "emoji_button.nature": "Nature",
+  "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Objects",
+  "emoji_button.people": "People",
+  "emoji_button.recent": "Frequently used",
+  "emoji_button.search": "Search...",
+  "emoji_button.search_results": "Search results",
+  "emoji_button.symbols": "Symbols",
+  "emoji_button.travel": "Travel & Places",
+  "empty_column.account_timeline": "No toots here!",
+  "empty_column.account_unavailable": "Profile unavailable",
+  "empty_column.blocks": "You haven't blocked any users yet.",
+  "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no hidden domains yet.",
+  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
+  "empty_column.hashtag": "There is nothing in this hashtag yet.",
+  "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
+  "empty_column.home.public_timeline": "the public timeline",
+  "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
+  "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+  "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
+  "follow_request.authorize": "Authorize",
+  "follow_request.reject": "Reject",
+  "getting_started.developers": "Developers",
+  "getting_started.directory": "Profile directory",
+  "getting_started.documentation": "Documentation",
+  "getting_started.heading": "Getting started",
+  "getting_started.invite": "Invite people",
+  "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
+  "getting_started.security": "Security",
+  "getting_started.terms": "Terms of service",
+  "hashtag.column_header.tag_mode.all": "and {additional}",
+  "hashtag.column_header.tag_mode.any": "or {additional}",
+  "hashtag.column_header.tag_mode.none": "without {additional}",
+  "hashtag.column_settings.select.no_options_message": "No suggestions found",
+  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
+  "hashtag.column_settings.tag_mode.all": "All of these",
+  "hashtag.column_settings.tag_mode.any": "Any of these",
+  "hashtag.column_settings.tag_mode.none": "None of these",
+  "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
+  "home.column_settings.basic": "Basic",
+  "home.column_settings.show_reblogs": "Show boosts",
+  "home.column_settings.show_replies": "Show replies",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "introduction.federation.action": "Next",
+  "introduction.federation.federated.headline": "Federated",
+  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
+  "introduction.federation.home.headline": "Home",
+  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
+  "introduction.federation.local.headline": "Local",
+  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
+  "introduction.interactions.action": "Finish toot-orial!",
+  "introduction.interactions.favourite.headline": "Favourite",
+  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
+  "introduction.interactions.reblog.headline": "Boost",
+  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
+  "introduction.interactions.reply.headline": "Reply",
+  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
+  "introduction.welcome.action": "Let's go!",
+  "introduction.welcome.headline": "First steps",
+  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
+  "keyboard_shortcuts.back": "to navigate back",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
+  "keyboard_shortcuts.boost": "to boost",
+  "keyboard_shortcuts.column": "to focus a status in one of the columns",
+  "keyboard_shortcuts.compose": "to focus the compose textarea",
+  "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
+  "keyboard_shortcuts.down": "to move down in the list",
+  "keyboard_shortcuts.enter": "to open status",
+  "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
+  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
+  "keyboard_shortcuts.hotkey": "Hotkey",
+  "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
+  "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
+  "keyboard_shortcuts.profile": "to open author's profile",
+  "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
+  "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
+  "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
+  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.toot": "to start a brand new toot",
+  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
+  "keyboard_shortcuts.up": "to move up in the list",
+  "lightbox.close": "Close",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
+  "lightbox.view_context": "View context",
+  "lists.account.add": "Add to list",
+  "lists.account.remove": "Remove from list",
+  "lists.delete": "Delete list",
+  "lists.edit": "Edit list",
+  "lists.edit.submit": "Change title",
+  "lists.new.create": "Add list",
+  "lists.new.title_placeholder": "New list title",
+  "lists.search": "Search among people you follow",
+  "lists.subheading": "Your lists",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
+  "loading_indicator.label": "Loading...",
+  "media_gallery.toggle_visible": "Toggle visibility",
+  "missing_indicator.label": "Not found",
+  "missing_indicator.sublabel": "This resource could not be found",
+  "mute_modal.hide_notifications": "Hide notifications from this user?",
+  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.blocks": "Blocked users",
+  "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.compose": "Compose new toot",
+  "navigation_bar.direct": "Direct messages",
+  "navigation_bar.discover": "Discover",
+  "navigation_bar.domain_blocks": "Hidden domains",
+  "navigation_bar.edit_profile": "Edit profile",
+  "navigation_bar.favourites": "Favourites",
+  "navigation_bar.filters": "Muted words",
+  "navigation_bar.follow_requests": "Follow requests",
+  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.info": "About this server",
+  "navigation_bar.keyboard_shortcuts": "Hotkeys",
+  "navigation_bar.lists": "Lists",
+  "navigation_bar.logout": "Logout",
+  "navigation_bar.mutes": "Muted users",
+  "navigation_bar.personal": "Personal",
+  "navigation_bar.pins": "Pinned toots",
+  "navigation_bar.preferences": "Preferences",
+  "navigation_bar.public_timeline": "Federated timeline",
+  "navigation_bar.security": "Security",
+  "notification.favourite": "{name} favourited your status",
+  "notification.follow": "{name} followed you",
+  "notification.mention": "{name} mentioned you",
+  "notification.poll": "A poll you have voted in has ended",
+  "notification.reblog": "{name} boosted your status",
+  "notifications.clear": "Clear notifications",
+  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+  "notifications.column_settings.alert": "Desktop notifications",
+  "notifications.column_settings.favourite": "Favourites:",
+  "notifications.column_settings.filter_bar.advanced": "Display all categories",
+  "notifications.column_settings.filter_bar.category": "Quick filter bar",
+  "notifications.column_settings.filter_bar.show": "Show",
+  "notifications.column_settings.follow": "New followers:",
+  "notifications.column_settings.mention": "Mentions:",
+  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.push": "Push notifications",
+  "notifications.column_settings.reblog": "Boosts:",
+  "notifications.column_settings.show": "Show in column",
+  "notifications.column_settings.sound": "Play sound",
+  "notifications.filter.all": "All",
+  "notifications.filter.boosts": "Boosts",
+  "notifications.filter.favourites": "Favourites",
+  "notifications.filter.follows": "Follows",
+  "notifications.filter.mentions": "Mentions",
+  "notifications.filter.polls": "Poll results",
+  "notifications.group": "{count} notifications",
+  "poll.closed": "Closed",
+  "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
+  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
+  "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
+  "privacy.change": "Adjust status privacy",
+  "privacy.direct.long": "Post to mentioned users only",
+  "privacy.direct.short": "Direct",
+  "privacy.private.long": "Post to followers only",
+  "privacy.private.short": "Followers-only",
+  "privacy.public.long": "Post to public timelines",
+  "privacy.public.short": "Public",
+  "privacy.unlisted.long": "Do not show in public timelines",
+  "privacy.unlisted.short": "Unlisted",
+  "refresh": "Refresh",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
+  "relative_time.days": "{number}d",
+  "relative_time.hours": "{number}h",
+  "relative_time.just_now": "now",
+  "relative_time.minutes": "{number}m",
+  "relative_time.seconds": "{number}s",
+  "reply_indicator.cancel": "Cancel",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:",
+  "report.placeholder": "Additional comments",
+  "report.submit": "Submit",
+  "report.target": "Report {target}",
+  "search.placeholder": "Search",
+  "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+  "search_popout.tips.hashtag": "hashtag",
+  "search_popout.tips.status": "status",
+  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
+  "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
+  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+  "status.admin_account": "Open moderation interface for @{name}",
+  "status.admin_status": "Open this status in the moderation interface",
+  "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
+  "status.cannot_reblog": "This post cannot be boosted",
+  "status.copy": "Copy link to status",
+  "status.delete": "Delete",
+  "status.detailed_status": "Detailed conversation view",
+  "status.direct": "Direct message @{name}",
+  "status.embed": "Embed",
+  "status.favourite": "Favourite",
+  "status.filtered": "Filtered",
+  "status.load_more": "Load more",
+  "status.media_hidden": "Media hidden",
+  "status.mention": "Mention @{name}",
+  "status.more": "More",
+  "status.mute": "Mute @{name}",
+  "status.mute_conversation": "Mute conversation",
+  "status.open": "Expand this status",
+  "status.pin": "Pin on profile",
+  "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
+  "status.reblog": "Boost",
+  "status.reblog_private": "Boost to original audience",
+  "status.reblogged_by": "{name} boosted",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
+  "status.redraft": "Delete & re-draft",
+  "status.reply": "Reply",
+  "status.replyAll": "Reply to thread",
+  "status.report": "Report @{name}",
+  "status.sensitive_warning": "Sensitive content",
+  "status.share": "Share",
+  "status.show_less": "Show less",
+  "status.show_less_all": "Show less for all",
+  "status.show_more": "Show more",
+  "status.show_more_all": "Show more for all",
+  "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
+  "status.unmute_conversation": "Unmute conversation",
+  "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
+  "tabs_bar.federated_timeline": "Federated",
+  "tabs_bar.home": "Home",
+  "tabs_bar.local_timeline": "Local",
+  "tabs_bar.notifications": "Notifications",
+  "tabs_bar.search": "Search",
+  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
+  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
+  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
+  "time_remaining.moments": "Moments remaining",
+  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
+  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
+  "upload_area.title": "Drag & drop to upload",
+  "upload_button.label": "Add media ({formats})",
+  "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.edit": "Edit",
+  "upload_form.undo": "Delete",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
+  "upload_progress.label": "Uploading…",
+  "video.close": "Close video",
+  "video.exit_fullscreen": "Exit full screen",
+  "video.expand": "Expand video",
+  "video.fullscreen": "Full screen",
+  "video.hide": "Hide video",
+  "video.mute": "Mute sound",
+  "video.pause": "Pause",
+  "video.play": "Play",
+  "video.unmute": "Unmute sound"
+}
diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json
index 098d6cca93b6c8bae6c8825e190004282c3aa9d2..6efd42955cfe20fae93a6c4a0f18b2bbbd374bbe 100644
--- a/app/javascript/mastodon/locales/ms.json
+++ b/app/javascript/mastodon/locales/ms.json
@@ -4,6 +4,7 @@
   "account.block": "Block @{name}",
   "account.block_domain": "Hide everything from {domain}",
   "account.blocked": "Blocked",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Direct message @{name}",
   "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Edit profile",
@@ -15,6 +16,7 @@
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Media",
@@ -23,10 +25,11 @@
   "account.mute": "Mute @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.muted": "Muted",
+  "account.never_active": "Never",
   "account.posts": "Toots",
   "account.posts_with_replies": "Toots and replies",
   "account.report": "Report @{name}",
-  "account.requested": "Awaiting approval. Click to cancel follow request",
+  "account.requested": "Awaiting approval",
   "account.share": "Share @{name}'s profile",
   "account.show_reblogs": "Show boosts from @{name}",
   "account.unblock": "Unblock @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Unfollow",
   "account.unmute": "Unmute @{name}",
   "account.unmute_notifications": "Unmute notifications from @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "You can press {combo} to skip this next time",
   "bundle_column_error.body": "Something went wrong while loading this component.",
   "bundle_column_error.retry": "Try again",
@@ -47,6 +53,7 @@
   "column.blocks": "Blocked users",
   "column.community": "Local timeline",
   "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
@@ -64,7 +71,7 @@
   "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
   "column_subheading.settings": "Settings",
-  "community.column_settings.media_only": "Media Only",
+  "community.column_settings.media_only": "Media only",
   "compose_form.direct_message_warning": "This toot will only be sent to all the mentioned users.",
   "compose_form.direct_message_warning_learn_more": "Learn more",
   "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
   "confirmations.domain_block.confirm": "Hide entire domain",
   "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Mute",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "embed.preview": "Here is what it will look like:",
   "emoji_button.activity": "Activity",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
   "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "New list title",
   "lists.search": "Search among people you follow",
   "lists.subheading": "Your lists",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Loading...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Pinned toots",
   "navigation_bar.preferences": "Preferences",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Federated timeline",
   "navigation_bar.security": "Security",
   "notification.favourite": "{name} favourited your status",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Do not show in public timelines",
   "privacy.unlisted.short": "Unlisted",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Loading…",
   "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "People",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Show more",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,15 +400,23 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
   "upload_area.title": "Drag & drop to upload",
-  "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
+  "upload_button.label": "Add media ({formats})",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Describe for the visually impaired",
-  "upload_form.focus": "Crop",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Delete",
-  "upload_progress.label": "Uploading...",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
+  "upload_progress.label": "Uploading…",
   "video.close": "Close video",
   "video.exit_fullscreen": "Exit full screen",
   "video.expand": "Expand video",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index ee7d9cf638e7b76f0c93e5b4c370a2f334d66469..ba649dde09fd6489233a0b3fb5f7375f021dbed0 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -4,6 +4,7 @@
   "account.block": "Blokkeer @{name}",
   "account.block_domain": "Verberg alles van {domain}",
   "account.blocked": "Geblokkeerd",
+  "account.cancel_follow_request": "Volgverzoek annuleren",
   "account.direct": "Direct Message @{name}",
   "account.domain_blocked": "Domein verborgen",
   "account.edit_profile": "Profiel bewerken",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Deze gebruiker volgt nog niemand.",
   "account.follows_you": "Volgt jou",
   "account.hide_reblogs": "Verberg boosts van @{name}",
+  "account.last_status": "Laatst actief",
   "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.media": "Media",
@@ -23,6 +25,7 @@
   "account.mute": "Negeer @{name}",
   "account.mute_notifications": "Negeer meldingen van @{name}",
   "account.muted": "Genegeerd",
+  "account.never_active": "Nooit",
   "account.posts": "Toots",
   "account.posts_with_replies": "Toots en reacties",
   "account.report": "Rapporteer @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Ontvolgen",
   "account.unmute": "@{name} niet langer negeren",
   "account.unmute_notifications": "@{name} meldingen niet langer negeren",
+  "alert.rate_limited.message": "Probeer het nog een keer na {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Beperkt te gebruiken",
   "alert.unexpected.message": "Er deed zich een onverwachte fout voor",
   "alert.unexpected.title": "Oeps!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan",
   "bundle_column_error.body": "Tijdens het laden van dit onderdeel is er iets fout gegaan.",
   "bundle_column_error.retry": "Opnieuw proberen",
@@ -47,6 +53,7 @@
   "column.blocks": "Geblokkeerde gebruikers",
   "column.community": "Lokale tijdlijn",
   "column.direct": "Directe berichten",
+  "column.directory": "Gebruikersgids",
   "column.domain_blocks": "Genegeerde servers",
   "column.favourites": "Favorieten",
   "column.follow_requests": "Volgverzoeken",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Weet je zeker dat je deze lijst definitief wilt verwijderen?",
   "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.logout.confirm": "Uitloggen",
+  "confirmations.logout.message": "Weet je zeker dat je wilt uitloggen?",
   "confirmations.mute.confirm": "Negeren",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "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 reacties op de originele toot zitten niet meer aan de nieuwe toot vast.",
@@ -101,6 +111,14 @@
   "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.unfollow.confirm": "Ontvolgen",
   "confirmations.unfollow.message": "Weet je het zeker dat je {name} wilt ontvolgen?",
+  "conversation.delete": "Gesprek verwijderen",
+  "conversation.mark_as_read": "Als gelezen markeren",
+  "conversation.open": "Gesprek tonen",
+  "conversation.with": "Met {names}",
+  "directory.federated": "Fediverse (wat bekend is)",
+  "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.preview": "Zo komt het eruit te zien:",
   "emoji_button.activity": "Activiteiten",
@@ -134,6 +152,10 @@
   "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",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Stacktrace naar klembord kopiëren",
+  "errors.unexpected_crash.report_issue": "Technisch probleem melden",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Naam nieuwe lijst",
   "lists.search": "Zoek naar mensen die je volgt",
   "lists.subheading": "Jouw lijsten",
+  "load_pending": "{count, plural, one {# nieuw item} other {# nieuwe items}}",
   "loading_indicator.label": "Laden…",
   "media_gallery.toggle_visible": "Media wel/niet tonen",
   "missing_indicator.label": "Niet gevonden",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Persoonlijk",
   "navigation_bar.pins": "Vastgezette toots",
   "navigation_bar.preferences": "Instellingen",
-  "navigation_bar.profile_directory": "Gebruikersgids",
   "navigation_bar.public_timeline": "Globale tijdlijn",
   "navigation_bar.security": "Beveiliging",
   "notification.favourite": "{name} voegde jouw toot als favoriet toe",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} meldingen",
   "poll.closed": "Gesloten",
   "poll.refresh": "Vernieuwen",
+  "poll.total_people": "{count, plural, one {# persoon} other {# mensen}}",
   "poll.total_votes": "{count, plural, one {# stem} other {# stemmen}}",
   "poll.vote": "Stemmen",
+  "poll.voted": "Je hebt hier op gestemd",
   "poll_button.add_poll": "Poll toevoegen",
   "poll_button.remove_poll": "Poll verwijderen",
   "privacy.change": "Zichtbaarheid toot aanpassen",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Openbaar",
   "privacy.unlisted.long": "Niet op openbare tijdlijnen tonen",
   "privacy.unlisted.short": "Minder openbaar",
+  "refresh": "Vernieuwen",
   "regeneration_indicator.label": "Aan het laden…",
   "regeneration_indicator.sublabel": "Jouw tijdlijn wordt aangemaakt!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Gebruikers",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Het zoeken in toots is op deze Mastodonserver 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",
@@ -358,6 +384,7 @@
   "status.show_more": "Meer tonen",
   "status.show_more_all": "Alles meer tonen",
   "status.show_thread": "Gesprek tonen",
+  "status.uncached_media_warning": "Niet beschikbaar",
   "status.unmute_conversation": "Gesprek niet langer negeren",
   "status.unpin": "Van profielpagina losmaken",
   "suggestions.dismiss": "Voorstel verwerpen",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Nog enkele ogenblikken resterend",
   "time_remaining.seconds": "{number, plural, one {# seconde} other {# seconden}} te gaan",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {persoon praat} other {mensen praten}} hierover",
+  "trends.trending_now": "Trends",
   "ui.beforeunload": "Je concept zal verloren gaan als je Mastodon verlaat.",
   "upload_area.title": "Hiernaar toe slepen om te uploaden",
   "upload_button.label": "Media toevoegen ({formats})",
   "upload_error.limit": "Uploadlimiet van bestand overschreden.",
   "upload_error.poll": "Het uploaden van bestanden is in polls niet toegestaan.",
   "upload_form.description": "Omschrijf dit voor mensen met een visuele beperking",
-  "upload_form.focus": "Voorvertoning aanpassen",
+  "upload_form.edit": "Bewerken",
   "upload_form.undo": "Verwijderen",
+  "upload_modal.analyzing_picture": "Afbeelding analyseren…",
+  "upload_modal.apply": "Toepassen",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Tekst in een afbeelding detecteren",
+  "upload_modal.edit_media": "Media bewerken",
+  "upload_modal.hint": "Klik of sleep de cirkel in de voorvertoning naar een centraal punt dat op elke thumbnail zichtbaar moet blijven.",
+  "upload_modal.preview_label": "Voorvertoning ({ratio})",
   "upload_progress.label": "Uploaden...",
   "video.close": "Video sluiten",
   "video.exit_fullscreen": "Volledig scherm sluiten",
diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json
new file mode 100644
index 0000000000000000000000000000000000000000..73a319179b6985d543b0904a45a3144db87a2c5c
--- /dev/null
+++ b/app/javascript/mastodon/locales/nn.json
@@ -0,0 +1,423 @@
+{
+  "account.add_or_remove_from_list": "Legg til eller ta vekk fra liste",
+  "account.badges.bot": "Robot",
+  "account.block": "Blokkér @{name}",
+  "account.block_domain": "Gøyme alt innhald for domenet {domain}",
+  "account.blocked": "Blokkert",
+  "account.cancel_follow_request": "Avslutt føljar-førespurnad",
+  "account.direct": "Direkte meld @{name}",
+  "account.domain_blocked": "Domenet er gøymt",
+  "account.edit_profile": "Rediger profil",
+  "account.endorse": "Framhev på profilen din",
+  "account.follow": "Følj",
+  "account.followers": "Føljare",
+  "account.followers.empty": "Er ikkje nokon som føljar denne brukaren ennå.",
+  "account.follows": "Føljingar",
+  "account.follows.empty": "Denne brukaren foljer ikkje nokon ennå.",
+  "account.follows_you": "Føljar deg",
+  "account.hide_reblogs": "Gøym fremhevingar for @{name}",
+  "account.last_status": "Sist aktiv",
+  "account.link_verified_on": "Eigerskap for denne linken er sist sjekket den {date}",
+  "account.locked_info": "Brukarens privat-status er satt til lukka. Eigaren må manuelt døme kvem som kan følje honom.",
+  "account.media": "Media",
+  "account.mention": "Nemne @{name}",
+  "account.moved_to": "{name} har flytta til:",
+  "account.mute": "MÃ¥lbind @{name}",
+  "account.mute_notifications": "Målbind varslingar ifrå @{name}",
+  "account.muted": "MÃ¥lbindt",
+  "account.never_active": "Aldri",
+  "account.posts": "Tutar",
+  "account.posts_with_replies": "Tutar og svar",
+  "account.report": "Rapporter @{name}",
+  "account.requested": "Venter på samtykke. Klikk for å avbryte føljar-førespurnad",
+  "account.share": "Del @{name} sin profil",
+  "account.show_reblogs": "Sjå framhevingar ifrå @{name}",
+  "account.unblock": "Avblokker @{name}",
+  "account.unblock_domain": "Vis {domain}",
+  "account.unendorse": "Ikkje framhev på profil",
+  "account.unfollow": "Avfølja",
+  "account.unmute": "Av-demp @{name}",
+  "account.unmute_notifications": "Av-demp notifikasjoner ifrå @{name}",
+  "alert.rate_limited.message": "Ver vennlig og prøv igjen {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Bregrensa rate",
+  "alert.unexpected.message": "Eit uforventa problem har hendt.",
+  "alert.unexpected.title": "Oops!",
+  "autosuggest_hashtag.per_week": "{count} per veke",
+  "boost_modal.combo": "Du kan trykke {combo} for å hoppe over dette neste gong",
+  "bundle_column_error.body": "Noko gikk gale mens komponent ble nedlasta.",
+  "bundle_column_error.retry": "Prøv igjen",
+  "bundle_column_error.title": "Tenarmaskin feil",
+  "bundle_modal_error.close": "Lukk",
+  "bundle_modal_error.message": "Noko gikk gale mens komponent var i ferd med å bli nedlasta.",
+  "bundle_modal_error.retry": "Prøv igjen",
+  "column.blocks": "Blokka brukare",
+  "column.community": "Lokal samtid",
+  "column.direct": "Direkte meldingar",
+  "column.directory": "Sjå gjennom profiler",
+  "column.domain_blocks": "Gøymte domener",
+  "column.favourites": "Favorittar",
+  "column.follow_requests": "Føljarførespurnad",
+  "column.home": "Heim",
+  "column.lists": "Lister",
+  "column.mutes": "MÃ¥lbindte brukare",
+  "column.notifications": "Varslingar",
+  "column.pins": "Festa tuter",
+  "column.public": "Federert samtid",
+  "column_back_button.label": "Tilbake",
+  "column_header.hide_settings": "Skjul innstillingar",
+  "column_header.moveLeft_settings": "Flytt feltet til venstre",
+  "column_header.moveRight_settings": "Flytt feltet til høgre",
+  "column_header.pin": "Fest",
+  "column_header.show_settings": "Vis innstillingar",
+  "column_header.unpin": "Løys",
+  "column_subheading.settings": "Innstillingar",
+  "community.column_settings.media_only": "Kun medie",
+  "compose_form.direct_message_warning": "Denne tuten vil kun verte synleg for nemnde brukarar.",
+  "compose_form.direct_message_warning_learn_more": "Lær meir",
+  "compose_form.hashtag_warning": "Denne tuten vill ikkje bli lista under nokon knagg ettersom den ikkje er opplista. Berre offentlege tutar kan ble søkt på ved emneknagg.",
+  "compose_form.lock_disclaimer": "Din brukar er ikkje {locked}. Alle kan følje deg for å sjå føljar-modus poster.",
+  "compose_form.lock_disclaimer.lock": "låst",
+  "compose_form.placeholder": "Kva har du på hjartet?",
+  "compose_form.poll.add_option": "Legg til eit punkt",
+  "compose_form.poll.duration": "Varigheit for spørring",
+  "compose_form.poll.option_placeholder": "Val {number}",
+  "compose_form.poll.remove_option": "Ta burt dette valet",
+  "compose_form.publish": "Tut",
+  "compose_form.publish_loud": "{publish}!",
+  "compose_form.sensitive.hide": "Merk media som sensitivt",
+  "compose_form.sensitive.marked": "Media er markert som sensitivt",
+  "compose_form.sensitive.unmarked": "Media er ikkje merka som sensitivt",
+  "compose_form.spoiler.marked": "Tekst er gøymt bak ein advarsel",
+  "compose_form.spoiler.unmarked": "Tekst er ikkje gøymt",
+  "compose_form.spoiler_placeholder": "Skriv varselen din her",
+  "confirmation_modal.cancel": "Avbrot",
+  "confirmations.block.block_and_report": "Blokk & rapportér",
+  "confirmations.block.confirm": "Blokkér",
+  "confirmations.block.message": "Er du sikker på at du vill blokke {name}?",
+  "confirmations.delete.confirm": "Slett",
+  "confirmations.delete.message": "Er du sikker på at du vill slette denne statusen?",
+  "confirmations.delete_list.confirm": "Slett",
+  "confirmations.delete_list.message": "Er du sikker på at du vill slette denne listen for alltid?",
+  "confirmations.domain_block.confirm": "Gøym heile domenet",
+  "confirmations.domain_block.message": "Er du ordentleg, ordentleg sikker på at du vill blokkere heile {domain}? I dei tilfeller er det bedre med ein målretta blokkering eller demping av individuelle brukare.",
+  "confirmations.logout.confirm": "Logg ut",
+  "confirmations.logout.message": "Er du sikker på at du vill logge ut?",
+  "confirmations.mute.confirm": "MÃ¥lbind",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
+  "confirmations.mute.message": "Er du sikker på at d vill målbinde {name}?",
+  "confirmations.redraft.confirm": "Slett & gjennopprett",
+  "confirmations.redraft.message": "Er du sikker på at du vill slette statusen og gjennoprette den? Favoritter og framhevinger vill bli borte, og svar til den originale posten vill bli einstøing.",
+  "confirmations.reply.confirm": "Svar",
+  "confirmations.reply.message": "Å svare nå vill overskrive meldingen du er i ferd med å skrive. Er du sikker på at du vill gå fram?",
+  "confirmations.unfollow.confirm": "Avfølj",
+  "confirmations.unfollow.message": "Er du sikker på at du vill avfølje {name}?",
+  "conversation.delete": "Slett samtale",
+  "conversation.mark_as_read": "Merk som lest",
+  "conversation.open": "Sjå samtale",
+  "conversation.with": "Med {names}",
+  "directory.federated": "Fra kjent fedivers",
+  "directory.local": "Fra berre {domain} domenet",
+  "directory.new_arrivals": "Nyankommne",
+  "directory.recently_active": "Nylig aktiv",
+  "embed.instructions": "Embed this status on your website by copying the code below.",
+  "embed.preview": "Dette er korleis den vil sjå ut:",
+  "emoji_button.activity": "Aktivitet",
+  "emoji_button.custom": "Custom",
+  "emoji_button.flags": "Flagg",
+  "emoji_button.food": "Mat & drikke",
+  "emoji_button.label": "Legg til smilefjes",
+  "emoji_button.nature": "Natur",
+  "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Objektar",
+  "emoji_button.people": "Folk",
+  "emoji_button.recent": "Ofte brukt",
+  "emoji_button.search": "Søk...",
+  "emoji_button.search_results": "Søke resultater",
+  "emoji_button.symbols": "Symbolar",
+  "emoji_button.travel": "Reise & steder",
+  "empty_column.account_timeline": "Ikkje nokon tutar her!",
+  "empty_column.account_unavailable": "Profil ikkje tilgjengelig",
+  "empty_column.blocks": "Du har ikkje blokkért nokon brukarar ennå.",
+  "empty_column.community": "Den lokale samtiden er tom. Skriv noko offentleg å få ballen til å rulle!",
+  "empty_column.direct": "Du har ikkje nokon direkte meldingar ennå. Når du sendar eller får ein, så vill den ende opp her.",
+  "empty_column.domain_blocks": "Der er ikkje nokon gøymte domener enno.",
+  "empty_column.favourited_statuses": "Du har ikkje favorisert nokon tutar enno. Når du favoriserer noko, så vill det ende opp her.",
+  "empty_column.favourites": "Ikkje nokon har favorisert denne tuten enno. Når nokon gjer det, så vill den ende opp her.",
+  "empty_column.follow_requests": "Du har ikkje nokon føljar førespurnad enno. Når du får ein, så vill den sjåast her.",
+  "empty_column.hashtag": "Det er ikkje noko i denne emneknaggen her enno.",
+  "empty_column.home": "Din heime-tidslinja er tom! Dra til {public} eller søk for å starte å møte andre brukare.",
+  "empty_column.home.public_timeline": "Den offentlege tidslinja",
+  "empty_column.list": "Det er ikkje noko i denne lista enno. Når medlemmar av denne lista poster statuser, så vill dei sjåast her.",
+  "empty_column.lists": "Du har ikkje nokon liste enno. Når du lagar ein, så vill den ende up her.",
+  "empty_column.mutes": "Du har ikkje dempet nokon brukare enno.",
+  "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
+  "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Rapportér problem",
+  "follow_request.authorize": "Autoriser",
+  "follow_request.reject": "Reject",
+  "getting_started.developers": "Utviklare",
+  "getting_started.directory": "Profile directory",
+  "getting_started.documentation": "Dokumentasjon",
+  "getting_started.heading": "Komme i gong",
+  "getting_started.invite": "Inviter folk",
+  "getting_started.open_source_notice": "Mastodon har åpen kilde kode. Du kan hjelpe til med problemar på GitHub gjennom {github}.",
+  "getting_started.security": "Brukar instillingar",
+  "getting_started.terms": "Brukarvillkår",
+  "hashtag.column_header.tag_mode.all": "og {additional}",
+  "hashtag.column_header.tag_mode.any": "eller {additional}",
+  "hashtag.column_header.tag_mode.none": "uten {additional}",
+  "hashtag.column_settings.select.no_options_message": "No suggestions found",
+  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
+  "hashtag.column_settings.tag_mode.all": "Alle disse",
+  "hashtag.column_settings.tag_mode.any": "Any of these",
+  "hashtag.column_settings.tag_mode.none": "None of these",
+  "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
+  "home.column_settings.basic": "Basic",
+  "home.column_settings.show_reblogs": "Vis fremhevingar",
+  "home.column_settings.show_replies": "Vis svar",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "introduction.federation.action": "Neste",
+  "introduction.federation.federated.headline": "Federert",
+  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
+  "introduction.federation.home.headline": "Heim",
+  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
+  "introduction.federation.local.headline": "Lokal",
+  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
+  "introduction.interactions.action": "Fullfør omvisning!",
+  "introduction.interactions.favourite.headline": "Lik",
+  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
+  "introduction.interactions.reblog.headline": "Fremhev",
+  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
+  "introduction.interactions.reply.headline": "Svar",
+  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
+  "introduction.welcome.action": "Let's go!",
+  "introduction.welcome.headline": "Første steg",
+  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
+  "keyboard_shortcuts.back": "to navigate back",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
+  "keyboard_shortcuts.boost": "to boost",
+  "keyboard_shortcuts.column": "to focus a status in one of the columns",
+  "keyboard_shortcuts.compose": "to focus the compose textarea",
+  "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
+  "keyboard_shortcuts.down": "to move down in the list",
+  "keyboard_shortcuts.enter": "to open status",
+  "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
+  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
+  "keyboard_shortcuts.hotkey": "Hotkey",
+  "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
+  "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.pinned": "to open pinned toots list",
+  "keyboard_shortcuts.profile": "to open author's profile",
+  "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
+  "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
+  "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
+  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.toot": "Start ein heilt ny tut",
+  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
+  "keyboard_shortcuts.up": "to move up in the list",
+  "lightbox.close": "Lukk",
+  "lightbox.next": "Neste",
+  "lightbox.previous": "Forrige",
+  "lightbox.view_context": "Sjå kontekst",
+  "lists.account.add": "Legg til i liste",
+  "lists.account.remove": "Ta burt fra liste",
+  "lists.delete": "Slett liste",
+  "lists.edit": "Rediger liste",
+  "lists.edit.submit": "Bytt tittel",
+  "lists.new.create": "Legg til liste",
+  "lists.new.title_placeholder": "Ny liste tittel",
+  "lists.search": "Søk gjennom folk du føljar",
+  "lists.subheading": "Dine lister",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
+  "loading_indicator.label": "Laster...",
+  "media_gallery.toggle_visible": "Toggle visibility",
+  "missing_indicator.label": "Ikkje funne",
+  "missing_indicator.sublabel": "Denne ressursen ble ikkje funne",
+  "mute_modal.hide_notifications": "Hide notifications from this user?",
+  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.blocks": "Blocked users",
+  "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.compose": "Compose new toot",
+  "navigation_bar.direct": "Direct messages",
+  "navigation_bar.discover": "Discover",
+  "navigation_bar.domain_blocks": "Hidden domains",
+  "navigation_bar.edit_profile": "Edit profile",
+  "navigation_bar.favourites": "Favourites",
+  "navigation_bar.filters": "Muted words",
+  "navigation_bar.follow_requests": "Follow requests",
+  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.info": "About this server",
+  "navigation_bar.keyboard_shortcuts": "Hotkeys",
+  "navigation_bar.lists": "Lister",
+  "navigation_bar.logout": "Logg ut",
+  "navigation_bar.mutes": "Dempa brukare",
+  "navigation_bar.personal": "Personlig",
+  "navigation_bar.pins": "Festa tuter",
+  "navigation_bar.preferences": "Preferanser",
+  "navigation_bar.public_timeline": "Federert tidslinje",
+  "navigation_bar.security": "Sikkerheit",
+  "notification.favourite": "{name} likte din status",
+  "notification.follow": "{name} fulgte deg",
+  "notification.mention": "{name} nevnte deg",
+  "notification.poll": "A poll you have voted in has ended",
+  "notification.reblog": "{name} boosted your status",
+  "notifications.clear": "Klarer notifikasjoner",
+  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+  "notifications.column_settings.alert": "Desktop notifications",
+  "notifications.column_settings.favourite": "Favourites:",
+  "notifications.column_settings.filter_bar.advanced": "Display all categories",
+  "notifications.column_settings.filter_bar.category": "Quick filter bar",
+  "notifications.column_settings.filter_bar.show": "Show",
+  "notifications.column_settings.follow": "Nye føljare:",
+  "notifications.column_settings.mention": "Mentions:",
+  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.push": "Push notifications",
+  "notifications.column_settings.reblog": "Framhevinger:",
+  "notifications.column_settings.show": "Show in column",
+  "notifications.column_settings.sound": "Play sound",
+  "notifications.filter.all": "All",
+  "notifications.filter.boosts": "Framhevinger",
+  "notifications.filter.favourites": "Favoritter",
+  "notifications.filter.follows": "Føljare",
+  "notifications.filter.mentions": "Mentions",
+  "notifications.filter.polls": "Poll results",
+  "notifications.group": "{count} notifications",
+  "poll.closed": "Closed",
+  "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
+  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
+  "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
+  "privacy.change": "Adjust status privacy",
+  "privacy.direct.long": "Post to mentioned users only",
+  "privacy.direct.short": "Direct",
+  "privacy.private.long": "Post to followers only",
+  "privacy.private.short": "Followers-only",
+  "privacy.public.long": "Post to public timelines",
+  "privacy.public.short": "Public",
+  "privacy.unlisted.long": "Do not show in public timelines",
+  "privacy.unlisted.short": "Unlisted",
+  "refresh": "Refresh",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
+  "relative_time.days": "{number}d",
+  "relative_time.hours": "{number}h",
+  "relative_time.just_now": "now",
+  "relative_time.minutes": "{number}m",
+  "relative_time.seconds": "{number}s",
+  "reply_indicator.cancel": "Cancel",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:",
+  "report.placeholder": "Additional comments",
+  "report.submit": "Submit",
+  "report.target": "Report {target}",
+  "search.placeholder": "Search",
+  "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+  "search_popout.tips.hashtag": "hashtag",
+  "search_popout.tips.status": "status",
+  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
+  "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
+  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+  "status.admin_account": "Open moderation interface for @{name}",
+  "status.admin_status": "Open this status in the moderation interface",
+  "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
+  "status.cannot_reblog": "This post cannot be boosted",
+  "status.copy": "Copy link to status",
+  "status.delete": "Delete",
+  "status.detailed_status": "Detailed conversation view",
+  "status.direct": "Direct message @{name}",
+  "status.embed": "Embed",
+  "status.favourite": "Favourite",
+  "status.filtered": "Filtered",
+  "status.load_more": "Load more",
+  "status.media_hidden": "Media hidden",
+  "status.mention": "Mention @{name}",
+  "status.more": "More",
+  "status.mute": "Mute @{name}",
+  "status.mute_conversation": "Mute conversation",
+  "status.open": "Expand this status",
+  "status.pin": "Pin on profile",
+  "status.pinned": "Pinned toot",
+  "status.read_more": "Read more",
+  "status.reblog": "Boost",
+  "status.reblog_private": "Boost to original audience",
+  "status.reblogged_by": "{name} boosted",
+  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
+  "status.redraft": "Delete & re-draft",
+  "status.reply": "Reply",
+  "status.replyAll": "Reply to thread",
+  "status.report": "Report @{name}",
+  "status.sensitive_warning": "Sensitive content",
+  "status.share": "Share",
+  "status.show_less": "Show less",
+  "status.show_less_all": "Show less for all",
+  "status.show_more": "Show more",
+  "status.show_more_all": "Show more for all",
+  "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
+  "status.unmute_conversation": "Unmute conversation",
+  "status.unpin": "Unpin from profile",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
+  "tabs_bar.federated_timeline": "Federated",
+  "tabs_bar.home": "Home",
+  "tabs_bar.local_timeline": "Local",
+  "tabs_bar.notifications": "Notifications",
+  "tabs_bar.search": "Search",
+  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
+  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
+  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
+  "time_remaining.moments": "Moments remaining",
+  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
+  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
+  "upload_area.title": "Drag & drop to upload",
+  "upload_button.label": "Add media ({formats})",
+  "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
+  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.edit": "Edit",
+  "upload_form.undo": "Delete",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
+  "upload_progress.label": "Uploading…",
+  "video.close": "Close video",
+  "video.exit_fullscreen": "Exit full screen",
+  "video.expand": "Expand video",
+  "video.fullscreen": "Full screen",
+  "video.hide": "Hide video",
+  "video.mute": "Mute sound",
+  "video.pause": "Pause",
+  "video.play": "Play",
+  "video.unmute": "Unmute sound"
+}
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index b9495f6b6985bd94ad2a719f42cdf3dc3a478b67..28f89863323581c959eafc17c18c5419134e5dd2 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -4,6 +4,7 @@
   "account.block": "Blokkér @{name}",
   "account.block_domain": "Skjul alt fra {domain}",
   "account.blocked": "Blocked",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Direct Message @{name}",
   "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Rediger profil",
@@ -15,6 +16,7 @@
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Følger deg",
   "account.hide_reblogs": "Skjul fremhevinger fra @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Media",
@@ -23,6 +25,7 @@
   "account.mute": "Demp @{name}",
   "account.mute_notifications": "Ignorer varsler fra @{name}",
   "account.muted": "Muted",
+  "account.never_active": "Never",
   "account.posts": "Innlegg",
   "account.posts_with_replies": "Toots with replies",
   "account.report": "Rapportér @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Avfølg",
   "account.unmute": "Avdemp @{name}",
   "account.unmute_notifications": "Vis varsler fra @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang",
   "bundle_column_error.body": "Noe gikk galt mens denne komponenten lastet.",
   "bundle_column_error.retry": "Prøv igjen",
@@ -47,6 +53,7 @@
   "column.blocks": "Blokkerte brukere",
   "column.community": "Lokal tidslinje",
   "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Likt",
   "column.follow_requests": "Følgeforespørsler",
@@ -64,7 +71,7 @@
   "column_header.show_settings": "Vis innstillinger",
   "column_header.unpin": "Løsne",
   "column_subheading.settings": "Innstillinger",
-  "community.column_settings.media_only": "Media Only",
+  "community.column_settings.media_only": "Media only",
   "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.",
   "compose_form.direct_message_warning_learn_more": "Learn more",
   "compose_form.hashtag_warning": "Denne tuten blir ikke listet under noen emneknagger da den er ulistet. Kun offentlige tuter kan søktes etter med emneknagg.",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Er du sikker på at du vil slette denne listen permanent?",
   "confirmations.domain_block.confirm": "Skjul alt fra domenet",
   "confirmations.domain_block.message": "Er du sikker på at du vil skjule hele domenet {domain}? I de fleste tilfeller er det bedre med målrettet blokkering eller demping.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Demp",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Er du sikker på at du vil dempe {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Slutt å følge",
   "confirmations.unfollow.message": "Er du sikker på at du vil slutte å følge {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Kopier koden under for å bygge inn denne statusen på hjemmesiden din.",
   "embed.preview": "Slik kommer det til å se ut:",
   "emoji_button.activity": "Aktivitet",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.",
   "empty_column.public": "Det er ingenting her! Skriv noe offentlig, eller følg brukere manuelt fra andre instanser for å fylle den opp",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Ny listetittel",
   "lists.search": "Søk blant personer du følger",
   "lists.subheading": "Dine lister",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Laster...",
   "media_gallery.toggle_visible": "Veksle synlighet",
   "missing_indicator.label": "Ikke funnet",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Festa tuter",
   "navigation_bar.preferences": "Preferanser",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Felles tidslinje",
   "navigation_bar.security": "Security",
   "notification.favourite": "{name} likte din status",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Justér synlighet",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Offentlig",
   "privacy.unlisted.long": "Ikke vis i offentlige tidslinjer",
   "privacy.unlisted.short": "Uoppført",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Laster…",
   "regeneration_indicator.sublabel": "Dine startside forberedes!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "People",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Vis mer",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Ikke demp samtale",
   "status.unpin": "Angre festing på profilen",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Din kladd vil bli forkastet om du forlater Mastodon.",
   "upload_area.title": "Dra og slipp for å laste opp",
   "upload_button.label": "Legg til media",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Beskriv for synshemmede",
-  "upload_form.focus": "Crop",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Angre",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Laster opp...",
   "video.close": "Lukk video",
   "video.exit_fullscreen": "Lukk fullskjerm",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index f5c150d376282298caf49439073ff6dfafcbd2bf..1a2479ecb2d9f4371db33c2d2ed986ac573780bd 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -4,6 +4,7 @@
   "account.block": "Blocar @{name}",
   "account.block_domain": "Tot amagar del domeni {domain}",
   "account.blocked": "Blocat",
+  "account.cancel_follow_request": "Anullar la demanda de seguiment",
   "account.direct": "Escriure un MP a @{name}",
   "account.domain_blocked": "Domeni amagat",
   "account.edit_profile": "Modificar lo perfil",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Aqueste utilizaire sèc pas degun pel moment.",
   "account.follows_you": "Vos sèc",
   "account.hide_reblogs": "Rescondre los partatges de @{name}",
+  "account.last_status": "Darrièra activitat",
   "account.link_verified_on": "La proprietat d’aqueste ligam foguèt verificada lo {date}",
   "account.locked_info": "L’estatut de privacitat del compte es configurat sus clavat. Lo proprietari causís qual pòt sègre son compte.",
   "account.media": "Mèdias",
@@ -23,6 +25,7 @@
   "account.mute": "Rescondre @{name}",
   "account.mute_notifications": "Rescondre las notificacions de @{name}",
   "account.muted": "Mes en silenci",
+  "account.never_active": "Jamai",
   "account.posts": "Tuts",
   "account.posts_with_replies": "Tuts e responsas",
   "account.report": "Senhalar @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Quitar de sègre",
   "account.unmute": "Quitar de rescondre @{name}",
   "account.unmute_notifications": "Mostrar las notificacions de @{name}",
+  "alert.rate_limited.message": "Mercés de tornar ensajar aprèp {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Taus limitat",
   "alert.unexpected.message": "Una error s’es producha.",
   "alert.unexpected.title": "Ops !",
+  "autosuggest_hashtag.per_week": "{count} per setmana",
   "boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven",
   "bundle_column_error.body": "Quicòm a fach mèuca pendent lo cargament d’aqueste compausant.",
   "bundle_column_error.retry": "Tornar ensajar",
@@ -47,6 +53,7 @@
   "column.blocks": "Personas blocadas",
   "column.community": "Flux public local",
   "column.direct": "Messatges dirèctes",
+  "column.directory": "Percórrer los perfils",
   "column.domain_blocks": "Domenis resconduts",
   "column.favourites": "Favorits",
   "column.follow_requests": "Demandas d’abonament",
@@ -77,7 +84,7 @@
   "compose_form.poll.remove_option": "Levar aquesta opcion",
   "compose_form.publish": "Tut",
   "compose_form.publish_loud": "{publish} !",
-  "compose_form.sensitive.hide": "Mark media as sensitive",
+  "compose_form.sensitive.hide": "Marcar coma sensible",
   "compose_form.sensitive.marked": "Lo mèdia es marcat coma sensible",
   "compose_form.sensitive.unmarked": "Lo mèdia es pas marcat coma sensible",
   "compose_form.spoiler.marked": "Lo tèxte es rescondut jos l’avertiment",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Volètz vertadièrament suprimir aquesta lista per totjorn ?",
   "confirmations.domain_block.confirm": "Amagar tot lo domeni",
   "confirmations.domain_block.message": "Volètz vertadièrament blocar complètament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.\nVeiretz pas cap de contengut d’aquel domeni dins cap de flux public o dins vòstras notificacions. Vòstres seguidors d’aquel domeni seràn levats.",
+  "confirmations.logout.confirm": "Desconnexion",
+  "confirmations.logout.message": "Volètz vertadièrament vos desconnectar ?",
   "confirmations.mute.confirm": "Rescondre",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Volètz vertadièrament rescondre {name} ?",
   "confirmations.redraft.confirm": "Escafar & tornar formular",
   "confirmations.redraft.message": "Volètz vertadièrament escafar aqueste estatut e lo reformular ? Totes sos partiments e favorits seràn perduts, e sas responsas seràn orfanèlas.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Respondre remplaçarà lo messatge que sètz a escriure. Volètz vertadièrament contunhar ?",
   "confirmations.unfollow.confirm": "Quitar de sègre",
   "confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?",
+  "conversation.delete": "Suprimir la conversacion",
+  "conversation.mark_as_read": "Marcar coma legida",
+  "conversation.open": "Veire la conversacion",
+  "conversation.with": "Amb {names}",
+  "directory.federated": "Del fediverse conegut",
+  "directory.local": "Solament de {domain}",
+  "directory.new_arrivals": "Nòus-venguts",
+  "directory.recently_active": "Actius fa res",
   "embed.instructions": "Embarcar aqueste estatut per lo far veire sus un site Internet en copiar lo còdi çai-jos.",
   "embed.preview": "Semblarà aquò :",
   "emoji_button.activity": "Activitats",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Encara avètz pas mes en silenci degun.",
   "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.",
   "empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autres servidors per garnir lo flux public",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Senhalar un problèma",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -209,14 +231,14 @@
   "keyboard_shortcuts.search": "anar a la recèrca",
   "keyboard_shortcuts.start": "dobrir la colomna « Per començar »",
   "keyboard_shortcuts.toggle_hidden": "mostrar/amagar lo tèxte dels avertiments",
-  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.toggle_sensitivity": "per mostrar/rescondre los mèdias",
   "keyboard_shortcuts.toot": "començar un estatut tot novèl",
   "keyboard_shortcuts.unfocus": "quitar lo camp tèxte/de recèrca",
   "keyboard_shortcuts.up": "far montar dins la lista",
   "lightbox.close": "Tampar",
   "lightbox.next": "Seguent",
   "lightbox.previous": "Precedent",
-  "lightbox.view_context": "View context",
+  "lightbox.view_context": "Veire lo contèxt",
   "lists.account.add": "Ajustar a la lista",
   "lists.account.remove": "Levar de la lista",
   "lists.delete": "Suprimir la lista",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Títol de la nòva lista",
   "lists.search": "Cercar demest lo monde que seguètz",
   "lists.subheading": "Vòstras listas",
+  "load_pending": "{count, plural, one {# nòu element} other {# nòu elements}}",
   "loading_indicator.label": "Cargament…",
   "media_gallery.toggle_visible": "Modificar la visibilitat",
   "missing_indicator.label": "Pas trobat",
@@ -242,7 +265,7 @@
   "navigation_bar.favourites": "Favorits",
   "navigation_bar.filters": "Mots ignorats",
   "navigation_bar.follow_requests": "Demandas d’abonament",
-  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.follows_and_followers": "Abonament e seguidors",
   "navigation_bar.info": "Tocant aqueste servidor",
   "navigation_bar.keyboard_shortcuts": "Acorchis clavièr",
   "navigation_bar.lists": "Listas",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Tuts penjats",
   "navigation_bar.preferences": "Preferéncias",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Flux public global",
   "navigation_bar.security": "Seguretat",
   "notification.favourite": "{name} a ajustat a sos favorits",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notificacions",
   "poll.closed": "Tampat",
   "poll.refresh": "Actualizar",
+  "poll.total_people": "{count, plural, one {# persona} other {# personas}}",
   "poll.total_votes": "{count, plural, one {# vòte} other {# vòtes}}",
   "poll.vote": "Votar",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Ajustar un sondatge",
   "poll_button.remove_poll": "Levar lo sondatge",
   "privacy.change": "Ajustar la confidencialitat del messatge",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Mostrar pas dins los fluxes publics",
   "privacy.unlisted.short": "Pas-listat",
+  "refresh": "Actualizar",
   "regeneration_indicator.label": "Cargament…",
   "regeneration_indicator.sublabel": "Sèm a preparar vòstre flux d’acuèlh !",
   "relative_time.days": "fa {number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Gents",
   "search_results.hashtags": "Etiquetas",
   "search_results.statuses": "Tuts",
+  "search_results.statuses_fts_disabled": "La recèrca de tuts per lor contengut es pas activada sus aqueste servidor Mastodon.",
   "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
   "status.admin_account": "Dobrir l’interfàcia de moderacion per @{name}",
   "status.admin_status": "Dobrir aqueste estatut dins l’interfàcia de moderacion",
@@ -358,6 +384,7 @@
   "status.show_more": "Desplegar",
   "status.show_more_all": "Los desplegar totes",
   "status.show_thread": "Mostrar lo fil",
+  "status.uncached_media_warning": "Pas disponible",
   "status.unmute_conversation": "Tornar mostrar la conversacion",
   "status.unpin": "Tirar del perfil",
   "suggestions.dismiss": "Regetar la suggestion",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments restants",
   "time_remaining.seconds": "demòra{number, plural, one { # segonda} other {n # segondas}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} ne charra other {people}} ne charran",
+  "trends.trending_now": "Tendéncia del moment",
   "ui.beforeunload": "Vòstre brolhon serà perdut se quitatz Mastodon.",
   "upload_area.title": "Lisatz e depausatz per mandar",
   "upload_button.label": "Ajustar un mèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Talha maximum pels mandadís subrepassada.",
   "upload_error.poll": "Lo mandadís de fichièr es pas autorizat pels sondatges.",
   "upload_form.description": "Descripcion pels mal vesents",
-  "upload_form.focus": "Modificar l’apercebut",
+  "upload_form.edit": "Modificar",
   "upload_form.undo": "Suprimir",
+  "upload_modal.analyzing_picture": "Analisi de l’imatge…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.description_placeholder": "Lo dròlle bilingüe manja un yaourt de ròcs exagonals e kiwis verds farà un an mai",
+  "upload_modal.detect_text": "Detectar lo tèxt de l’imatge",
+  "upload_modal.edit_media": "Modificar lo mèdia",
+  "upload_modal.hint": "Clicatz o lissatz lo cercle de l’apercebut per causir lo ponch que serà totjorn visible dins las vinhetas.",
+  "upload_modal.preview_label": "Apercebut ({ratio})",
   "upload_progress.label": "Mandadís…",
   "video.close": "Tampar la vidèo",
   "video.exit_fullscreen": "Sortir plen ecran",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index 3e170d1a171fcc5065bb3d307422b150415e263e..cf0c0188edc7c963737be4e1f5e16d1e85e83fe0 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -4,6 +4,7 @@
   "account.block": "Blokuj @{name}",
   "account.block_domain": "Blokuj wszystko z {domain}",
   "account.blocked": "Zablokowany(-a)",
+  "account.cancel_follow_request": "Zrezygnuj z prośby o możliwość śledzenia",
   "account.direct": "Wyślij wiadomość bezpośrednią do @{name}",
   "account.domain_blocked": "Ukryto domenÄ™",
   "account.edit_profile": "Edytuj profil",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Ten użytkownik nie śledzi jeszcze nikogo.",
   "account.follows_you": "Åšledzi CiÄ™",
   "account.hide_reblogs": "Ukryj podbicia od @{name}",
+  "account.last_status": "Ostatnia aktywność",
   "account.link_verified_on": "Własność tego odnośnika została potwierdzona {date}",
   "account.locked_info": "To konto jest prywatne. Właściciel ręcznie wybiera kto może go śledzić.",
   "account.media": "Zawartość multimedialna",
@@ -23,6 +25,7 @@
   "account.mute": "Wycisz @{name}",
   "account.mute_notifications": "Wycisz powiadomienia o @{name}",
   "account.muted": "Wyciszony",
+  "account.never_active": "Nigdy",
   "account.posts": "Wpisy",
   "account.posts_with_replies": "Wpisy i odpowiedzi",
   "account.report": "Zgłoś @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Przestań śledzić",
   "account.unmute": "Cofnij wyciszenie @{name}",
   "account.unmute_notifications": "Cofnij wyciszenie powiadomień od @{name}",
+  "alert.rate_limited.message": "Spróbuj ponownie po {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Wystąpił nieoczekiwany błąd.",
   "alert.unexpected.title": "O nie!",
+  "autosuggest_hashtag.per_week": "{count} co tydzień",
   "boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem",
   "bundle_column_error.body": "Coś poszło nie tak podczas ładowania tego składnika.",
   "bundle_column_error.retry": "Spróbuj ponownie",
@@ -47,6 +53,7 @@
   "column.blocks": "Zablokowani użytkownicy",
   "column.community": "Lokalna oÅ› czasu",
   "column.direct": "Wiadomości bezpośrednie",
+  "column.directory": "PrzeglÄ…daj profile",
   "column.domain_blocks": "Ukryte domeny",
   "column.favourites": "Ulubione",
   "column.follow_requests": "Prośby o śledzenie",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Czy na pewno chcesz bezpowrotnie usunąć tą listę?",
   "confirmations.domain_block.confirm": "Ukryj wszysyko z domeny",
   "confirmations.domain_block.message": "Czy na pewno chcesz zablokować całą domenę {domain}? Zwykle lepszym rozwiązaniem jest blokada lub wyciszenie kilku użytkowników.",
+  "confirmations.logout.confirm": "Wyloguj",
+  "confirmations.logout.message": "Czy na pewno chcesz się wylogować?",
   "confirmations.mute.confirm": "Wycisz",
+  "confirmations.mute.explanation": "To schowa ich i wspominające ich posty, ale wciąż pozwoli im widzieć twoje posty i śledzić cię.",
   "confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?",
   "confirmations.redraft.confirm": "Usuń i przeredaguj",
   "confirmations.redraft.message": "Czy na pewno chcesz usunąć i przeredagować ten wpis? Polubienia i podbicia zostaną utracone, a odpowiedzi do oryginalnego wpisu zostaną osierocone.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "W ten sposób utracisz wpis który obecnie tworzysz. Czy na pewno chcesz to zrobić?",
   "confirmations.unfollow.confirm": "Przestań śledzić",
   "confirmations.unfollow.message": "Czy na pewno zamierzasz przestać śledzić {name}?",
+  "conversation.delete": "Usuń rozmowę",
+  "conversation.mark_as_read": "Oznacz jako przeczytane",
+  "conversation.open": "Zobacz rozmowÄ™",
+  "conversation.with": "Z {names}",
+  "directory.federated": "Ze znanego fediwersum",
+  "directory.local": "Tylko z {domain}",
+  "directory.new_arrivals": "Nowości",
+  "directory.recently_active": "Ostatnio aktywne",
   "embed.instructions": "Osadź ten wpis na swojej stronie wklejając poniższy kod.",
   "embed.preview": "Tak będzie to wyglądać:",
   "emoji_button.activity": "Aktywność",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Nie wyciszyłeś(-aś) jeszcze żadnego użytkownika.",
   "empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.",
   "empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych serwerów, aby to wyświetlić",
+  "error.unexpected_crash.explanation": "W związku z błędem w naszym kodzie lub braku kompatybilności przeglądarki, ta strona nie może być poprawnie wyświetlona.",
+  "error.unexpected_crash.next_steps": "Spróbuj odświeżyć stronę. Jeśli to nie pomoże, wciąż jesteś w stanie używać Mastodona przez inną przeglądarkę lub natywną aplikację.",
+  "errors.unexpected_crash.copy_stacktrace": "Skopiuj ślad stosu do schowka",
+  "errors.unexpected_crash.report_issue": "Zgłoś problem",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Wprowadź tytuł listy",
   "lists.search": "Szukaj wśród osób które śledzisz",
   "lists.subheading": "Twoje listy",
+  "load_pending": "{count, plural, one {# nowy przedmiot} other {nowe przedmioty}}",
   "loading_indicator.label": "Ładowanie…",
   "media_gallery.toggle_visible": "Przełącz widoczność",
   "missing_indicator.label": "Nie znaleziono",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Osobiste",
   "navigation_bar.pins": "Przypięte wpisy",
   "navigation_bar.preferences": "Preferencje",
-  "navigation_bar.profile_directory": "Katalog profilów",
   "navigation_bar.public_timeline": "Globalna oÅ› czasu",
   "navigation_bar.security": "Bezpieczeństwo",
   "notification.favourite": "{name} dodał(a) Twój wpis do ulubionych",
@@ -282,8 +304,10 @@
   "notifications.group": "{count, number} {count, plural, one {powiadomienie} few {powiadomienia} many {powiadomień} more {powiadomień}}",
   "poll.closed": "Zamknięte",
   "poll.refresh": "Odśwież",
+  "poll.total_people": "{count, plural, one {# osoba} few {# osoby} many {# osób} other {# osób}}",
   "poll.total_votes": "{count, plural, one {# głos} few {# głosy} many {# głosów} other {# głosów}}",
   "poll.vote": "Zagłosuj",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Dodaj głosowanie",
   "poll_button.remove_poll": "Usuń głosowanie",
   "privacy.change": "Dostosuj widoczność wpisów",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Publiczny",
   "privacy.unlisted.long": "Niewidoczny na publicznych osiach czasu",
   "privacy.unlisted.short": "Niewidoczny",
+  "refresh": "Odśwież",
   "regeneration_indicator.label": "Ładuję…",
   "regeneration_indicator.sublabel": "Twoja oÅ› czasu jest przygotowywana!",
   "relative_time.days": "{number} dni",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Ludzie",
   "search_results.hashtags": "Hashtagi",
   "search_results.statuses": "Wpisy",
+  "search_results.statuses_fts_disabled": "Szukanie wpisów przy pomocy ich zawartości nie jest włączone na tym serwerze Mastodona.",
   "search_results.total": "{count, number} {count, plural, one {wynik} few {wyniki} many {wyników} more {wyników}}",
   "status.admin_account": "Otwórz interfejs moderacyjny dla @{name}",
   "status.admin_status": "Otwórz ten wpis w interfejsie moderacyjnym",
@@ -358,6 +384,7 @@
   "status.show_more": "Rozwiń",
   "status.show_more_all": "Rozwiń wszystkie",
   "status.show_thread": "Pokaż wątek",
+  "status.uncached_media_warning": "Niedostępne",
   "status.unmute_conversation": "Cofnij wyciszenie konwersacji",
   "status.unpin": "Odepnij z profilu",
   "suggestions.dismiss": "Odrzuć sugestię",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Pozostała chwila",
   "time_remaining.seconds": "{number, plural, one {Pozostała # sekunda} few {Pozostały # sekundy} many {Pozostało # sekund} other {Pozostało # sekund}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {osoba rozmawia} few {osoby rozmawiają} other {osób rozmawia}} o tym",
+  "trends.trending_now": "Popularne teraz",
   "ui.beforeunload": "Utracisz tworzony wpis, jeżeli opuścisz Mastodona.",
   "upload_area.title": "Przeciągnij i upuść aby wysłać",
   "upload_button.label": "Dodaj zawartość multimedialną (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Przekroczono limit plików do wysłania.",
   "upload_error.poll": "Dołączanie plików nie dozwolone z głosowaniami.",
   "upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących",
-  "upload_form.focus": "Dopasuj podglÄ…d",
+  "upload_form.edit": "Edytuj",
   "upload_form.undo": "Usuń",
+  "upload_modal.analyzing_picture": "Analizowanie obrazu…",
+  "upload_modal.apply": "Zastosuj",
+  "upload_modal.description_placeholder": "Pchnąć w tę łódź jeża lub ośm skrzyń fig",
+  "upload_modal.detect_text": "Wykryj tekst ze obrazu",
+  "upload_modal.edit_media": "Edytuj multimedia",
+  "upload_modal.hint": "Kliknij lub przeciągnij kółko na podglądzie by wybrać centralny punkt, który zawsze będzie na widoku na miniaturce.",
+  "upload_modal.preview_label": "PodglÄ…d ({ratio})",
   "upload_progress.label": "Wysyłanie…",
   "video.close": "Zamknij film",
   "video.exit_fullscreen": "Opuść tryb pełnoekranowy",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index a221584171e79d526e654c81d54c133bf14a547a..86322e1e12c94971a99f28939c268ed075b17153 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -4,6 +4,7 @@
   "account.block": "Bloquear @{name}",
   "account.block_domain": "Esconder tudo de {domain}",
   "account.blocked": "Bloqueado",
+  "account.cancel_follow_request": "Cancelar solicitação para seguir",
   "account.direct": "Direct Message @{name}",
   "account.domain_blocked": "Domínio escondido",
   "account.edit_profile": "Editar perfil",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Esse usuário não segue ninguém no momento.",
   "account.follows_you": "Segue você",
   "account.hide_reblogs": "Esconder compartilhamentos de @{name}",
+  "account.last_status": "Última atividade",
   "account.link_verified_on": "A posse desse link foi verificada em {date}",
   "account.locked_info": "Essa conta está trancada. Se você a seguir sua solicitação será revisada manualmente.",
   "account.media": "Mídia",
@@ -23,6 +25,7 @@
   "account.mute": "Silenciar @{name}",
   "account.mute_notifications": "Silenciar notificações de @{name}",
   "account.muted": "Silenciado",
+  "account.never_active": "Nunca",
   "account.posts": "Toots",
   "account.posts_with_replies": "Toots e respostas",
   "account.report": "Denunciar @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Deixar de seguir",
   "account.unmute": "Não silenciar @{name}",
   "account.unmute_notifications": "Retirar silêncio das notificações vindas de @{name}",
+  "alert.rate_limited.message": "Por favor tente novamente após {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Limite de tentativas",
   "alert.unexpected.message": "Um erro inesperado ocorreu.",
-  "alert.unexpected.title": "Oops!",
+  "alert.unexpected.title": "Eita!",
+  "autosuggest_hashtag.per_week": "{count} por semana",
   "boost_modal.combo": "Você pode pressionar {combo} para ignorar este diálogo na próxima vez",
   "bundle_column_error.body": "Algo de errado aconteceu enquanto este componente era carregado.",
   "bundle_column_error.retry": "Tente novamente",
@@ -47,6 +53,7 @@
   "column.blocks": "Usuários bloqueados",
   "column.community": "Local",
   "column.direct": "Mensagens diretas",
+  "column.directory": "Explorar perfis",
   "column.domain_blocks": "Domínios escondidos",
   "column.favourites": "Favoritos",
   "column.follow_requests": "Seguidores pendentes",
@@ -77,7 +84,7 @@
   "compose_form.poll.remove_option": "Remover essa opção",
   "compose_form.publish": "Publicar",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.hide": "Mark media as sensitive",
+  "compose_form.sensitive.hide": "Marcar mídia como sensível",
   "compose_form.sensitive.marked": "Mídia está marcada como sensível",
   "compose_form.sensitive.unmarked": "Mídia não está marcada como sensível",
   "compose_form.spoiler.marked": "O texto está escondido por um aviso de conteúdo",
@@ -89,11 +96,14 @@
   "confirmations.block.message": "Você tem certeza de que quer bloquear {name}?",
   "confirmations.delete.confirm": "Excluir",
   "confirmations.delete.message": "Você tem certeza de que quer excluir esta postagem?",
-  "confirmations.delete_list.confirm": "Delete",
+  "confirmations.delete_list.confirm": "Excluir",
   "confirmations.delete_list.message": "Você tem certeza que quer deletar permanentemente a lista?",
   "confirmations.domain_block.confirm": "Esconder o domínio inteiro",
   "confirmations.domain_block.message": "Você quer mesmo bloquear {domain} inteiro? Na maioria dos casos, silenciar ou bloquear alguns usuários é o suficiente e o recomendado. Você não vai ver conteúdo desse domínio em nenhuma das timelines públicas ou nas suas notificações. Seus seguidores desse domínio serão removidos.",
+  "confirmations.logout.confirm": "Sair",
+  "confirmations.logout.message": "Tem certeza que deseja encerrar a sessão?",
   "confirmations.mute.confirm": "Silenciar",
+  "confirmations.mute.explanation": "Isto irá esconder postagens e postagens que mencionam, mas ainda vai permitir que eles vejam suas publicações e sigam você.",
   "confirmations.mute.message": "Você tem certeza de que quer silenciar {name}?",
   "confirmations.redraft.confirm": "Apagar & usar como rascunho",
   "confirmations.redraft.message": "Você tem certeza que deseja apagar esse status e usá-lo como rascunho? Seus compartilhamentos e favoritos serão perdidos e as respostas ao toot original ficarão desconectadas.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Responder agora vai sobrescrever a mensagem que você está compondo. Você tem certeza que quer continuar?",
   "confirmations.unfollow.confirm": "Deixar de seguir",
   "confirmations.unfollow.message": "Você tem certeza de que quer deixar de seguir {name}?",
+  "conversation.delete": "Excluir conversa",
+  "conversation.mark_as_read": "Marcar como lida",
+  "conversation.open": "Ver conversa",
+  "conversation.with": "Com {names}",
+  "directory.federated": "De fediverso conhecido",
+  "directory.local": "De {domain} apenas",
+  "directory.new_arrivals": "Acabaram de chegar",
+  "directory.recently_active": "Reverta esta propriedade para seu valor padrão",
   "embed.instructions": "Incorpore esta postagem em seu site copiando o código abaixo.",
   "embed.preview": "Aqui está uma previsão de como ficará:",
   "emoji_button.activity": "Atividades",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Você ainda não silenciou nenhum usuário.",
   "empty_column.notifications": "Você ainda não possui notificações. Interaja com outros usuários para começar a conversar.",
   "empty_column.public": "Não há nada aqui! Escreva algo publicamente ou siga manualmente usuários de outras instâncias",
+  "error.unexpected_crash.explanation": "Devido a um bug em nosso código ou a um problema de compatibilidade do navegador, esta página não pode ser exibida corretamente.",
+  "error.unexpected_crash.next_steps": "Tente atualizar a página. Se isso não ajudar, você ainda pode usar Mastodon através de um navegador diferente ou aplicativo nativo.",
+  "errors.unexpected_crash.copy_stacktrace": "Copiar stacktrace para a área de transferência",
+  "errors.unexpected_crash.report_issue": "Reportar problema",
   "federation.change": "Ajustar federação do toot",
   "federation.federated.long": "Permitir que o toot chegue a outras instâncias",
   "federation.federated.short": "Federado",
@@ -163,11 +185,11 @@
   "home.column_settings.show_replies": "Mostrar as respostas",
   "intervals.full.days": "{number, plural, one {# dia} other {# dias}}",
   "intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
-  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}",
   "introduction.federation.action": "Próximo",
-  "introduction.federation.federated.headline": "Federated",
+  "introduction.federation.federated.headline": "Global",
   "introduction.federation.federated.text": "Posts públicos de outros servidores do fediverso vão aparecer na timeline global.",
-  "introduction.federation.home.headline": "Home",
+  "introduction.federation.home.headline": "Início",
   "introduction.federation.home.text": "Posts de pessoas que você segue vão aparecer na sua página inicial. Você pode seguir pessoas de qualquer servidor!",
   "introduction.federation.local.headline": "Local",
   "introduction.federation.local.text": "Posts públicos de pessoas no mesmo servidor que você vão aparecer na timeline local.",
@@ -209,23 +231,24 @@
   "keyboard_shortcuts.search": "para focar a pesquisa",
   "keyboard_shortcuts.start": "para abrir a coluna \"primeiros passos\"",
   "keyboard_shortcuts.toggle_hidden": "mostrar/esconder o texto com aviso de conteúdo",
-  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.toggle_sensitivity": "mostrar/esconder mídia",
   "keyboard_shortcuts.toot": "para compor um novo toot",
   "keyboard_shortcuts.unfocus": "para remover o foco da área de composição/pesquisa",
   "keyboard_shortcuts.up": "para mover para cima na lista",
   "lightbox.close": "Fechar",
   "lightbox.next": "Próximo",
   "lightbox.previous": "Anterior",
-  "lightbox.view_context": "View context",
+  "lightbox.view_context": "Ver contexto",
   "lists.account.add": "Adicionar a listas",
   "lists.account.remove": "Remover da lista",
-  "lists.delete": "Delete list",
+  "lists.delete": "Excluir lista",
   "lists.edit": "Editar lista",
   "lists.edit.submit": "Mudar o título",
   "lists.new.create": "Adicionar lista",
   "lists.new.title_placeholder": "Novo título da lista",
   "lists.search": "Procurar entre as pessoas que você segue",
   "lists.subheading": "Suas listas",
+  "load_pending": "{count, plural, one {# novo item} other {# novos items}}",
   "loading_indicator.label": "Carregando...",
   "media_gallery.toggle_visible": "Esconder/Mostrar",
   "missing_indicator.label": "Não encontrado",
@@ -242,7 +265,7 @@
   "navigation_bar.favourites": "Favoritos",
   "navigation_bar.filters": "Palavras silenciadas",
   "navigation_bar.follow_requests": "Seguidores pendentes",
-  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.follows_and_followers": "Seguindo e seguidores",
   "navigation_bar.info": "Mais informações",
   "navigation_bar.keyboard_shortcuts": "Atalhos de teclado",
   "navigation_bar.lists": "Listas",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Pessoal",
   "navigation_bar.pins": "Postagens fixadas",
   "navigation_bar.preferences": "Preferências",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Global",
   "navigation_bar.security": "Segurança",
   "notification.favourite": "{name} adicionou a sua postagem aos favoritos",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notificações",
   "poll.closed": "Fechada",
   "poll.refresh": "Atualizar",
+  "poll.total_people": "{count, plural, one {# pessoa} other {# pessoas}}",
   "poll.total_votes": "{count, plural, one {# voto} other {# votos}}",
   "poll.vote": "Votar",
+  "poll.voted": "Você votou nesta resposta",
   "poll_button.add_poll": "Adicionar uma enquete",
   "poll_button.remove_poll": "Remover enquete",
   "privacy.change": "Ajustar a privacidade da mensagem",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Pública",
   "privacy.unlisted.long": "Não publicar em feeds públicos",
   "privacy.unlisted.short": "Não listada",
+  "refresh": "Atualizar",
   "regeneration_indicator.label": "Carregando…",
   "regeneration_indicator.sublabel": "Sua página inicial está sendo preparada!",
   "relative_time.days": "{number}d",
@@ -319,10 +344,11 @@
   "search_results.accounts": "Pessoas",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Pesquisar toots por seu conteúdo não está habilitado neste servidor Mastodon.",
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "status.admin_account": "Abrir interface de moderação para @{name}",
   "status.admin_status": "Abrir esse status na interface de moderação",
-  "status.block": "Block @{name}",
+  "status.block": "Bloquear @{name}",
   "status.cancel_reblog_private": "Desfazer compartilhamento",
   "status.cannot_reblog": "Esta postagem não pode ser compartilhada",
   "status.copy": "Copiar o link para o status",
@@ -358,6 +384,7 @@
   "status.show_more": "Mostrar mais",
   "status.show_more_all": "Mostrar mais para todas as mensagens",
   "status.show_thread": "Mostrar sequência",
+  "status.uncached_media_warning": "Não disponível",
   "status.unmute_conversation": "Desativar silêncio desta conversa",
   "status.unpin": "Desafixar do perfil",
   "suggestions.dismiss": "Ignorar a sugestão",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Momentos restantes",
   "time_remaining.seconds": "{number, plural, one {# segundo restante} other {# segundos restantes}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {pessoa} other {pessoas}} falando sobre",
+  "trends.trending_now": "Em alta no momento",
   "ui.beforeunload": "Seu rascunho será perdido se você sair do Mastodon.",
   "upload_area.title": "Arraste e solte para enviar",
   "upload_button.label": "Adicionar mídia (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Limite de envio de arquivos excedido.",
   "upload_error.poll": "Envio de arquivos não é permitido com enquetes.",
   "upload_form.description": "Descreva a imagem para deficientes visuais",
-  "upload_form.focus": "Ajustar foco",
+  "upload_form.edit": "Editar",
   "upload_form.undo": "Remover",
+  "upload_modal.analyzing_picture": "Analisando imagem…",
+  "upload_modal.apply": "Aplicar",
+  "upload_modal.description_placeholder": "Grave e cabisbaixo, o filho justo zelava pela querida mãe doente",
+  "upload_modal.detect_text": "Detectar texto da imagem",
+  "upload_modal.edit_media": "Editar mídia",
+  "upload_modal.hint": "Clique ou arraste o círculo na visualização para escolher o ponto focal que sempre será visto em todas as miniaturas.",
+  "upload_modal.preview_label": "Prévia ({ratio})",
   "upload_progress.label": "Salvando...",
   "video.close": "Fechar vídeo",
   "video.exit_fullscreen": "Sair da tela cheia",
diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt-PT.json
similarity index 69%
rename from app/javascript/mastodon/locales/pt.json
rename to app/javascript/mastodon/locales/pt-PT.json
index ef7434196c9a8b1650f798444280585277c123a9..c3b1ea17136bda3623b3a57d21d586e404fd2725 100644
--- a/app/javascript/mastodon/locales/pt.json
+++ b/app/javascript/mastodon/locales/pt-PT.json
@@ -4,6 +4,7 @@
   "account.block": "Bloquear @{name}",
   "account.block_domain": "Esconder tudo do domínio {domain}",
   "account.blocked": "Bloqueado",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Mensagem directa @{name}",
   "account.domain_blocked": "Domínio escondido",
   "account.edit_profile": "Editar perfil",
@@ -15,14 +16,16 @@
   "account.follows.empty": "Este utilizador ainda não segue alguém.",
   "account.follows_you": "É teu seguidor",
   "account.hide_reblogs": "Esconder partilhas de @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "A posse deste link foi verificada em {date}",
   "account.locked_info": "O estatuto de privacidade desta conta é fechado. O dono revê manualmente que a pode seguir.",
-  "account.media": "Media",
+  "account.media": "Média",
   "account.mention": "Mencionar @{name}",
   "account.moved_to": "{name} mudou a sua conta para:",
   "account.mute": "Silenciar @{name}",
   "account.mute_notifications": "Silenciar notificações de @{name}",
   "account.muted": "Silenciada",
+  "account.never_active": "Never",
   "account.posts": "Publicações",
   "account.posts_with_replies": "Publicações e respostas",
   "account.report": "Denunciar @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Deixar de seguir",
   "account.unmute": "Não silenciar @{name}",
   "account.unmute_notifications": "Deixar de silenciar @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Ocorreu um erro inesperado.",
   "alert.unexpected.title": "Bolas!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
   "bundle_column_error.body": "Algo de errado aconteceu enquanto este componente era carregado.",
   "bundle_column_error.retry": "Tente de novo",
@@ -47,53 +53,57 @@
   "column.blocks": "Utilizadores Bloqueados",
   "column.community": "Cronologia local",
   "column.direct": "Mensagens directas",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Domínios escondidos",
   "column.favourites": "Favoritos",
-  "column.follow_requests": "Seguidores Pendentes",
+  "column.follow_requests": "Seguidores pendentes",
   "column.home": "Início",
   "column.lists": "Listas",
   "column.mutes": "Utilizadores silenciados",
   "column.notifications": "Notificações",
   "column.pins": "Publicações fixas",
-  "column.public": "Cronologia federativa",
+  "column.public": "Cronologia federada",
   "column_back_button.label": "Voltar",
-  "column_header.hide_settings": "Esconder preferências",
+  "column_header.hide_settings": "Esconder configurações",
   "column_header.moveLeft_settings": "Mover coluna para a esquerda",
   "column_header.moveRight_settings": "Mover coluna para a direita",
   "column_header.pin": "Fixar",
-  "column_header.show_settings": "Mostrar preferências",
+  "column_header.show_settings": "Mostrar configurações",
   "column_header.unpin": "Desafixar",
-  "column_subheading.settings": "Preferências",
-  "community.column_settings.media_only": "Somente media",
-  "compose_form.direct_message_warning": "Esta publicação só  será enviada para os utilizadores mencionados.",
-  "compose_form.direct_message_warning_learn_more": "Aprender mais",
-  "compose_form.hashtag_warning": "Esta pulbicacção não será listada em nenhuma hashtag por ser não listada. Somente publicações públicas podem ser pesquisadas por hashtag.",
+  "column_subheading.settings": "Configurações",
+  "community.column_settings.media_only": "Somente multimédia",
+  "compose_form.direct_message_warning": "Esta publicação será enviada apenas para os utilizadores mencionados.",
+  "compose_form.direct_message_warning_learn_more": "Conhecer mais",
+  "compose_form.hashtag_warning": "Este toot não será listado em nenhuma hashtag por ser não listado. Apenas toots públics podem ser pesquisados por hashtag.",
   "compose_form.lock_disclaimer": "A tua conta não está {locked}. Qualquer pessoa pode seguir-te e ver as publicações direcionadas apenas a seguidores.",
-  "compose_form.lock_disclaimer.lock": "fechada",
+  "compose_form.lock_disclaimer.lock": "bloqueado",
   "compose_form.placeholder": "Em que estás a pensar?",
-  "compose_form.poll.add_option": "Add a choice",
-  "compose_form.poll.duration": "Poll duration",
-  "compose_form.poll.option_placeholder": "Choice {number}",
-  "compose_form.poll.remove_option": "Remove this choice",
-  "compose_form.publish": "Publicar",
-  "compose_form.publish_loud": "{publicar}!",
-  "compose_form.sensitive.hide": "Mark media as sensitive",
-  "compose_form.sensitive.marked": "Media marcado como sensível",
-  "compose_form.sensitive.unmarked": "Media não está marcado como sensível",
+  "compose_form.poll.add_option": "Adicionar uma opção",
+  "compose_form.poll.duration": "Duração da votação",
+  "compose_form.poll.option_placeholder": "Opção {number}",
+  "compose_form.poll.remove_option": "Eliminar esta opção",
+  "compose_form.publish": "Toot",
+  "compose_form.publish_loud": "{publish}!",
+  "compose_form.sensitive.hide": "Marcar multimédia como sensível",
+  "compose_form.sensitive.marked": "Média marcada como sensível",
+  "compose_form.sensitive.unmarked": "Média não está marcada como sensível",
   "compose_form.spoiler.marked": "Texto escondido atrás de aviso",
   "compose_form.spoiler.unmarked": "O texto não está escondido",
   "compose_form.spoiler_placeholder": "Escreve o teu aviso aqui",
   "confirmation_modal.cancel": "Cancelar",
-  "confirmations.block.block_and_report": "Block & Report",
+  "confirmations.block.block_and_report": "Bloquear e denunciar",
   "confirmations.block.confirm": "Bloquear",
   "confirmations.block.message": "De certeza que queres bloquear {name}?",
   "confirmations.delete.confirm": "Eliminar",
   "confirmations.delete.message": "De certeza que queres eliminar esta publicação?",
-  "confirmations.delete_list.confirm": "Apagar",
-  "confirmations.delete_list.message": "Tens a certeza de que desejas apagar permanentemente esta lista?",
+  "confirmations.delete_list.confirm": "Eliminar",
+  "confirmations.delete_list.message": "Tens a certeza de que desejas eliminar permanentemente esta lista?",
   "confirmations.domain_block.confirm": "Esconder tudo deste domínio",
-  "confirmations.domain_block.message": "De certeza que queres bloquear completamente o domínio {domain}? Na maioria dos casos, silenciar ou bloquear alguns utilizadores é o suficiente e o recomendado. Não irás ver conteúdo daquele domínio em cronologia alguma, nem nas tuas notificações. Os teus seguidores daquele domínio serão removidos.",
+  "confirmations.domain_block.message": "De certeza que queres bloquear completamente o domínio {domain}? Na maioria dos casos, silenciar ou bloquear alguns utilizadores é suficiente e é o recomendado. Não irás ver conteúdo daquele domínio em cronologia alguma nem nas tuas notificações. Os teus seguidores daquele domínio serão removidos.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Silenciar",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "De certeza que queres silenciar {name}?",
   "confirmations.redraft.confirm": "Apagar & redigir",
   "confirmations.redraft.message": "Tens a certeza que queres apagar e redigir esta publicação?  Os favoritos e as partilhas perder-se-ão e as respostas à publicação original ficarão órfãs.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Responder agora irá reescrever a mensagem que estás a compor actualmente. Tens a certeza que queres continuar?",
   "confirmations.unfollow.confirm": "Deixar de seguir",
   "confirmations.unfollow.message": "De certeza que queres deixar de seguir {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Publica esta publicação no teu site copiando o código abaixo.",
   "embed.preview": "Podes ver aqui como irá ficar:",
   "emoji_button.activity": "Actividade",
@@ -109,23 +127,23 @@
   "emoji_button.food": "Comida & Bebida",
   "emoji_button.label": "Inserir Emoji",
   "emoji_button.nature": "Natureza",
-  "emoji_button.not_found": "Não tem emojos!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.not_found": "Não tem emojis!! (╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "Objectos",
   "emoji_button.people": "Pessoas",
-  "emoji_button.recent": "Regularmente utilizados",
-  "emoji_button.search": "Procurar...",
+  "emoji_button.recent": "Utilizados regularmente",
+  "emoji_button.search": "Pesquisar...",
   "emoji_button.search_results": "Resultados da pesquisa",
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viagens & Lugares",
-  "empty_column.account_timeline": "Sem publicações!",
-  "empty_column.account_unavailable": "Profile unavailable",
+  "empty_column.account_timeline": "Sem toots por aqui!",
+  "empty_column.account_unavailable": "Perfil indisponível",
   "empty_column.blocks": "Ainda não bloqueaste qualquer utilizador.",
-  "empty_column.community": "Ainda não existe conteúdo local para mostrar!",
+  "empty_column.community": "A timeline local está vazia. Escreve algo publicamente para começar!",
   "empty_column.direct": "Ainda não tens qualquer mensagem directa. Quando enviares ou receberes alguma, ela irá aparecer aqui.",
   "empty_column.domain_blocks": "Ainda não há qualquer domínio escondido.",
-  "empty_column.favourited_statuses": "Ainda não tens quaisquer publicações favoritas. Quando tiveres alguma, ela irá aparecer aqui.",
-  "empty_column.favourites": "Ainda ninguém favorizou esta publicação. Quando alguém o fizer, ela irá aparecer aqui.",
-  "empty_column.follow_requests": "Ainda não tens pedido de seguimento algum. Quando receberes algum, ele irá aparecer aqui.",
+  "empty_column.favourited_statuses": "Ainda não tens quaisquer toots favoritos. Quando tiveres algum, ele irá aparecer aqui.",
+  "empty_column.favourites": "Ainda ninguém marcou este toot como favorito. Quando alguém o fizer, ele irá aparecer aqui.",
+  "empty_column.follow_requests": "Ainda não tens nenhum pedido de seguimento. Quando receberes algum, ele irá aparecer aqui.",
   "empty_column.hashtag": "Não foram encontradas publicações com essa hashtag.",
   "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
   "empty_column.home.public_timeline": "Cronologia pública",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Ainda não silenciaste qualquer utilizador.",
   "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
   "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para veres aqui os conteúdos públicos",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Ajustar federação do toot",
   "federation.federated.long": "Permitir que o toot chegue a outras instâncias",
   "federation.federated.short": "Federado",
@@ -143,10 +165,10 @@
   "follow_request.reject": "Rejeitar",
   "getting_started.developers": "Responsáveis pelo desenvolvimento",
   "getting_started.directory": "Directório de perfil",
-  "getting_started.documentation": "Documentation",
+  "getting_started.documentation": "Documentação",
   "getting_started.heading": "Primeiros passos",
   "getting_started.invite": "Convidar pessoas",
-  "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}.",
+  "getting_started.open_source_notice": "Mastodon é software de código aberto (open source). Podes contribuir ou reportar problemas no GitHub do projecto: {github}.",
   "getting_started.security": "Segurança",
   "getting_started.terms": "Termos de serviço",
   "hashtag.column_header.tag_mode.all": "e {additional}",
@@ -159,28 +181,28 @@
   "hashtag.column_settings.tag_mode.none": "Nenhum destes",
   "hashtag.column_settings.tag_toggle": "Incluir etiquetas adicionais para esta coluna",
   "home.column_settings.basic": "Básico",
-  "home.column_settings.show_reblogs": "Mostrar as partilhas",
-  "home.column_settings.show_replies": "Mostrar as respostas",
-  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
-  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
-  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "home.column_settings.show_reblogs": "Mostrar boosts",
+  "home.column_settings.show_replies": "Mostrar respostas",
+  "intervals.full.days": "{number, plural, one {# dia} other {# dias}}",
+  "intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
+  "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}",
   "introduction.federation.action": "Seguinte",
-  "introduction.federation.federated.headline": "Federated",
+  "introduction.federation.federated.headline": "Federada",
   "introduction.federation.federated.text": "Publicações públicas de outros servidores do fediverse aparecerão na cronologia federativa.",
-  "introduction.federation.home.headline": "Home",
+  "introduction.federation.home.headline": "Início",
   "introduction.federation.home.text": "As publicações das pessoas que tu segues aparecerão na tua coluna inicial. Tu podes seguir qualquer pessoa em qualquer servidor!",
   "introduction.federation.local.headline": "Local",
   "introduction.federation.local.text": "Publicações públicas de pessoas que tu segues no teu servidor aparecerão na coluna local.",
   "introduction.interactions.action": "Terminar o tutorial!",
   "introduction.interactions.favourite.headline": "Favorito",
-  "introduction.interactions.favourite.text": "Tu podes guardar um toot para depois e deixar o autor saber que gostaste dele, favoritando-o.",
-  "introduction.interactions.reblog.headline": "Partilhar",
+  "introduction.interactions.favourite.text": "Podes guardar um toot para depois e deixar o autor saber que gostaste dele, marcando-o como favorito.",
+  "introduction.interactions.reblog.headline": "Boost",
   "introduction.interactions.reblog.text": "Podes partilhar os toots de outras pessoas com os teus seguidores partilhando-os.",
   "introduction.interactions.reply.headline": "Responder",
   "introduction.interactions.reply.text": "Tu podes responder a toots de outras pessoas e aos teus, o que os irá juntar numa conversa.",
   "introduction.welcome.action": "Vamos!",
   "introduction.welcome.headline": "Primeiros passos",
-  "introduction.welcome.text": "Bem-vindo ao fediverse! Em pouco tempo poderás enviar mensagens e falar com os teus amigos numa grande variedade de servidores. Mas este servidor, {domain}, é especial—ele alberga o teu perfil. Por isso, lembra-te do seu nome.",
+  "introduction.welcome.text": "Bem-vindo ao fediverso! Em pouco tempo poderás enviar mensagens e falar com os teus amigos numa grande variedade de servidores. Mas este servidor, {domain}, é especial—ele alberga o teu perfil. Por isso, lembra-te do seu nome.",
   "keyboard_shortcuts.back": "para voltar",
   "keyboard_shortcuts.blocked": "para abrir a lista de utilizadores bloqueados",
   "keyboard_shortcuts.boost": "para partilhar",
@@ -189,10 +211,10 @@
   "keyboard_shortcuts.description": "Descrição",
   "keyboard_shortcuts.direct": "para abrir a coluna das mensagens directas",
   "keyboard_shortcuts.down": "para mover para baixo na lista",
-  "keyboard_shortcuts.enter": "para expandir uma publicação",
+  "keyboard_shortcuts.enter": "para expandir um estado",
   "keyboard_shortcuts.favourite": "para adicionar aos favoritos",
   "keyboard_shortcuts.favourites": "para abrir a lista dos favoritos",
-  "keyboard_shortcuts.federated": "para abrir a cronologia federativa",
+  "keyboard_shortcuts.federated": "para abrir a cronologia federada",
   "keyboard_shortcuts.heading": "Atalhos do teclado",
   "keyboard_shortcuts.home": "para abrir a cronologia inicial",
   "keyboard_shortcuts.hotkey": "Atalho",
@@ -209,31 +231,32 @@
   "keyboard_shortcuts.search": "para focar na pesquisa",
   "keyboard_shortcuts.start": "para abrir a coluna dos \"primeiros passos\"",
   "keyboard_shortcuts.toggle_hidden": "para mostrar/esconder texto atrás de CW",
-  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
-  "keyboard_shortcuts.toot": "para compor um novo post",
-  "keyboard_shortcuts.unfocus": "para remover o foco da área de publicação/pesquisa",
+  "keyboard_shortcuts.toggle_sensitivity": "mostrar/ocultar média",
+  "keyboard_shortcuts.toot": "para compor um novo toot",
+  "keyboard_shortcuts.unfocus": "para remover o foco da área de texto/pesquisa",
   "keyboard_shortcuts.up": "para mover para cima na lista",
   "lightbox.close": "Fechar",
   "lightbox.next": "Próximo",
   "lightbox.previous": "Anterior",
-  "lightbox.view_context": "View context",
+  "lightbox.view_context": "Ver contexto",
   "lists.account.add": "Adicionar à lista",
   "lists.account.remove": "Remover da lista",
-  "lists.delete": "Delete list",
+  "lists.delete": "Remover lista",
   "lists.edit": "Editar lista",
   "lists.edit.submit": "Mudar o título",
   "lists.new.create": "Adicionar lista",
-  "lists.new.title_placeholder": "Novo título da lista",
+  "lists.new.title_placeholder": "Título da nova lista",
   "lists.search": "Pesquisa entre as pessoas que segues",
   "lists.subheading": "As tuas listas",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "A carregar...",
-  "media_gallery.toggle_visible": "Esconder/Mostrar",
+  "media_gallery.toggle_visible": "Mostrar/ocultar",
   "missing_indicator.label": "Não encontrado",
   "missing_indicator.sublabel": "Este recurso não foi encontrado",
   "mute_modal.hide_notifications": "Esconder notificações deste utilizador?",
   "navigation_bar.apps": "Aplicações móveis",
   "navigation_bar.blocks": "Utilizadores bloqueados",
-  "navigation_bar.community_timeline": "Local",
+  "navigation_bar.community_timeline": "Cronologia local",
   "navigation_bar.compose": "Escrever novo toot",
   "navigation_bar.direct": "Mensagens directas",
   "navigation_bar.discover": "Descobrir",
@@ -242,23 +265,22 @@
   "navigation_bar.favourites": "Favoritos",
   "navigation_bar.filters": "Palavras silenciadas",
   "navigation_bar.follow_requests": "Seguidores pendentes",
-  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.follows_and_followers": "Seguindo e seguidores",
   "navigation_bar.info": "Sobre este servidor",
   "navigation_bar.keyboard_shortcuts": "Atalhos de teclado",
   "navigation_bar.lists": "Listas",
   "navigation_bar.logout": "Sair",
   "navigation_bar.mutes": "Utilizadores silenciados",
-  "navigation_bar.personal": "Personal",
-  "navigation_bar.pins": "Posts fixos",
+  "navigation_bar.personal": "Pessoal",
+  "navigation_bar.pins": "Toots afixados",
   "navigation_bar.preferences": "Preferências",
-  "navigation_bar.profile_directory": "Profile directory",
-  "navigation_bar.public_timeline": "Global",
+  "navigation_bar.public_timeline": "Cronologia federada",
   "navigation_bar.security": "Segurança",
-  "notification.favourite": "{name} adicionou o teu post aos favoritos",
-  "notification.follow": "{name} seguiu-te",
+  "notification.favourite": "{name} adicionou o teu estado aos favoritos",
+  "notification.follow": "{name} começou a seguir-te",
   "notification.mention": "{name} mencionou-te",
-  "notification.poll": "A poll you have voted in has ended",
-  "notification.reblog": "{name} partilhou o teu post",
+  "notification.poll": "Uma votação em participaste chegou ao fim",
+  "notification.reblog": "{name} fez boost ao teu o teu estado",
   "notifications.clear": "Limpar notificações",
   "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
   "notifications.column_settings.alert": "Notificações no computador",
@@ -268,24 +290,26 @@
   "notifications.column_settings.filter_bar.show": "Mostrar",
   "notifications.column_settings.follow": "Novos seguidores:",
   "notifications.column_settings.mention": "Menções:",
-  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.poll": "Resultados da votação:",
   "notifications.column_settings.push": "Notificações Push",
-  "notifications.column_settings.reblog": "Partilhas:",
-  "notifications.column_settings.show": "Mostrar nas colunas",
+  "notifications.column_settings.reblog": "Boosts:",
+  "notifications.column_settings.show": "Mostrar na coluna",
   "notifications.column_settings.sound": "Reproduzir som",
   "notifications.filter.all": "Todas",
-  "notifications.filter.boosts": "Partilhas",
-  "notifications.filter.favourites": "Favoritas",
+  "notifications.filter.boosts": "Boosts",
+  "notifications.filter.favourites": "Favoritos",
   "notifications.filter.follows": "Seguimento",
   "notifications.filter.mentions": "Referências",
-  "notifications.filter.polls": "Poll results",
+  "notifications.filter.polls": "Resultados da votação",
   "notifications.group": "{count} notificações",
   "poll.closed": "Fechado",
   "poll.refresh": "Recarregar",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{contar, plural, um {# vote} outro {# votes}}",
   "poll.vote": "Votar",
-  "poll_button.add_poll": "Add a poll",
-  "poll_button.remove_poll": "Remove poll",
+  "poll.voted": "You voted for this answer",
+  "poll_button.add_poll": "Adicionar votação",
+  "poll_button.remove_poll": "Remover votação",
   "privacy.change": "Ajustar a privacidade da mensagem",
   "privacy.direct.long": "Apenas para utilizadores mencionados",
   "privacy.direct.short": "Directo",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Público",
   "privacy.unlisted.long": "Não publicar nos feeds públicos",
   "privacy.unlisted.short": "Não listar",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "A carregar…",
   "regeneration_indicator.sublabel": "A tua home está a ser preparada!",
   "relative_time.days": "{number}d",
@@ -305,26 +330,27 @@
   "reply_indicator.cancel": "Cancelar",
   "report.forward": "Reenviar para {target}",
   "report.forward_hint": "A conta é de outro servidor. Enviar uma cópia anónima do relatório para lá também?",
-  "report.hint": "O relatório será enviado para os moderadores do teu servidor. Podes fornecer, em baixo, uma explicação do motivo pelo qual estás a relatar esta conta:",
+  "report.hint": "O relatório será enviado para os moderadores do teu servidor. Podes fornecer, em baixo, uma explicação do motivo pelo qual estás a denunciar esta conta:",
   "report.placeholder": "Comentários adicionais",
   "report.submit": "Enviar",
   "report.target": "Denunciar",
   "search.placeholder": "Pesquisar",
   "search_popout.search_format": "Formato avançado de pesquisa",
-  "search_popout.tips.full_text": "Texto simples devolve publicações que tu escreveste, favoritaste, partilhaste ou em que foste mencionado, tal como nomes de utilizador correspondentes, alcunhas e hashtags.",
+  "search_popout.tips.full_text": "Texto simples devolve publicações que tu escreveste, marcaste como favorita, partilhaste ou em que foste mencionado, tal como nomes de utilizador correspondentes, alcunhas e hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "estado",
   "search_popout.tips.text": "O texto simples retorna a correspondência de nomes, utilizadores e hashtags",
   "search_popout.tips.user": "utilizador",
   "search_results.accounts": "Pessoas",
   "search_results.hashtags": "Hashtags",
-  "search_results.statuses": "Publicações",
+  "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "status.admin_account": "Abrir a interface de moderação para @{name}",
   "status.admin_status": "Abrir esta publicação na interface de moderação",
-  "status.block": "Block @{name}",
-  "status.cancel_reblog_private": "Não partilhar",
-  "status.cannot_reblog": "Este post não pode ser partilhado",
+  "status.block": "Bloquear @{name}",
+  "status.cancel_reblog_private": "Remover boost",
+  "status.cannot_reblog": "Não é possível fazer boost a esta publicação",
   "status.copy": "Copiar o link para a publicação",
   "status.delete": "Eliminar",
   "status.detailed_status": "Vista de conversação detalhada",
@@ -334,7 +360,7 @@
   "status.filtered": "Filtrada",
   "status.load_more": "Carregar mais",
   "status.local_only": "Esse post só é visível para outros usuários da sua instância",
-  "status.media_hidden": "Media escondida",
+  "status.media_hidden": "Média escondida",
   "status.mention": "Mencionar @{name}",
   "status.more": "Mais",
   "status.mute": "Silenciar @{name}",
@@ -344,48 +370,57 @@
   "status.pinned": "Publicação fixa",
   "status.read_more": "Ler mais",
   "status.reblog": "Partilhar",
-  "status.reblog_private": "Partilhar com a audiência original",
-  "status.reblogged_by": "{name} partilhou",
-  "status.reblogs.empty": "Ainda ninguém partilhou esta publicação. Quando alguém o fizer, ela irá aparecer aqui.",
+  "status.reblog_private": "Fazer boost com a audiência original",
+  "status.reblogged_by": "{name} fez boost",
+  "status.reblogs.empty": "Ainda ninguém fez boost a este toot. Quando alguém o fizer, ele irá aparecer aqui.",
   "status.redraft": "Apagar & reescrever",
   "status.reply": "Responder",
   "status.replyAll": "Responder à conversa",
   "status.report": "Denunciar @{name}",
   "status.sensitive_warning": "Conteúdo sensível",
-  "status.share": "Compartilhar",
+  "status.share": "Partilhar",
   "status.show_less": "Mostrar menos",
   "status.show_less_all": "Mostrar menos para todas",
   "status.show_more": "Mostrar mais",
   "status.show_more_all": "Mostrar mais para todas",
   "status.show_thread": "Mostrar conversa",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Deixar de silenciar esta conversa",
   "status.unpin": "Não fixar no perfil",
   "suggestions.dismiss": "Dispensar a sugestão",
   "suggestions.header": "Tu podes estar interessado em…",
-  "tabs_bar.federated_timeline": "Global",
-  "tabs_bar.home": "Home",
+  "tabs_bar.federated_timeline": "Federada",
+  "tabs_bar.home": "Início",
   "tabs_bar.local_timeline": "Local",
   "tabs_bar.notifications": "Notificações",
   "tabs_bar.search": "Pesquisar",
   "time_remaining.days": "{número, plural, um {# day} outro {# days}} faltam",
   "time_remaining.hours": "{número, plural, um {# hour} outro {# hours}} faltam",
   "time_remaining.minutes": "{número, plural, um {# minute} outro {# minutes}} faltam",
-  "time_remaining.moments": "Momentos em falta",
+  "time_remaining.moments": "Momentos restantes",
   "time_remaining.seconds": "{número, plural, um {# second} outro {# seconds}} faltam",
   "trends.count_by_accounts": "{count} {rawCount, plural, uma {person} outra {people}} a falar",
-  "ui.beforeunload": "O teu rascunho vai ser perdido se abandonares o Mastodon.",
+  "trends.trending_now": "Trending now",
+  "ui.beforeunload": "O teu rascunho será perdido se abandonares o Mastodon.",
   "upload_area.title": "Arraste e solte para enviar",
   "upload_button.label": "Adicionar media",
   "upload_error.limit": "Limite máximo do ficheiro a carregar excedido.",
-  "upload_error.poll": "File upload not allowed with polls.",
+  "upload_error.poll": "Carregamento de ficheiros não é permitido em votações.",
   "upload_form.description": "Descrição da imagem para pessoas com dificuldades visuais",
-  "upload_form.focus": "Alterar previsualização",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Apagar",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "A enviar...",
   "video.close": "Fechar vídeo",
   "video.exit_fullscreen": "Sair de full screen",
   "video.expand": "Expandir vídeo",
-  "video.fullscreen": "Full screen",
+  "video.fullscreen": "Ecrã completo",
   "video.hide": "Esconder vídeo",
   "video.mute": "Silenciar",
   "video.pause": "Pausar",
diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json
index 963ad4dec7b14d0e59794d4838ef933a00f72fcf..b52ec721b8d1743e0caa6b1c354f57e8541eeba1 100644
--- a/app/javascript/mastodon/locales/ro.json
+++ b/app/javascript/mastodon/locales/ro.json
@@ -4,6 +4,7 @@
   "account.block": "Blochează @{name}",
   "account.block_domain": "Ascunde tot de la {domain}",
   "account.blocked": "Blocat",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Mesaj direct @{name}",
   "account.domain_blocked": "Domeniu ascuns",
   "account.edit_profile": "Editează profilul",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Acest utilizator nu urmărește pe nimeni incă.",
   "account.follows_you": "Te urmărește",
   "account.hide_reblogs": "Ascunde redistribuirile de la @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Deținerea acestui link a fost verificată la {date}",
   "account.locked_info": "Acest profil este privat. Această persoană gestioneaz manual cine o urmărește.",
   "account.media": "Media",
@@ -23,6 +25,7 @@
   "account.mute": "Oprește @{name}",
   "account.mute_notifications": "Oprește notificările de la @{name}",
   "account.muted": "Oprit",
+  "account.never_active": "Never",
   "account.posts": "Postări",
   "account.posts_with_replies": "Postări și replici",
   "account.report": "Raportează @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Nu mai urmări",
   "account.unmute": "Activează notificările de la @{name}",
   "account.unmute_notifications": "Activează notificările de la @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "A apărut o eroare neașteptată.",
   "alert.unexpected.title": "Hopa!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Poți apăsa {combo} pentru a omite asta data viitoare",
   "bundle_column_error.body": "Ceva nu a funcționat la încărcarea acestui component.",
   "bundle_column_error.retry": "Încearcă din nou",
@@ -47,6 +53,7 @@
   "column.blocks": "Utilizatori blocați",
   "column.community": "Fluxul Local",
   "column.direct": "Mesaje directe",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Domenii ascunse",
   "column.favourites": "Favorite",
   "column.follow_requests": "Cereri de urmărire",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Ești sigur că vrei să ștergi permanent această listă?",
   "confirmations.domain_block.confirm": "Ascunde tot domeniul",
   "confirmations.domain_block.message": "Ești absolut sigur că vrei să blochezi complet {domain}? În cele mai multe cazuri raportarea sau oprirea anumitor lucruri este suficientă și de preferat. Nu vei mai vedea nici un conținut de la acest domeniu in nici un flux public sau în notificările tale. Urmăritorii tăi de la acele domenii vor fi eliminați.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Oprește",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Ești sigur că vrei să oprești {name}?",
   "confirmations.redraft.confirm": "Șterge și salvează ca ciornă",
   "confirmations.redraft.message": "Ești sigur că vrei să faci asta? Tot ce ține de această postare, inclusiv răspunsurile vor fi deconectate.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Răspunzând la asta acum, mesajul pe care îl compui în prezent se va șterge. Ești sigur că vrei să continui?",
   "confirmations.unfollow.confirm": "Nu mai urmări",
   "confirmations.unfollow.message": "Ești sigur că nu mai vrei să îl urmărești pe {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Inserează această postare pe site-ul tău adăugând codul de mai jos.",
   "embed.preview": "Cam așa va arăta:",
   "emoji_button.activity": "Activitate",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Nu ai oprit nici un utilizator incă.",
   "empty_column.notifications": "Nu ai nici o notificare încă. Interacționează cu alții pentru a începe o conversație.",
   "empty_column.public": "Nu este nimci aici încă! Scrie ceva public, sau urmărește alți utilizatori din alte instanțe pentru a porni fluxul",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Titlu pentru noua listă",
   "lists.search": "Caută printre persoanale pe care le urmărești",
   "lists.subheading": "Listele tale",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Încărcare...",
   "media_gallery.toggle_visible": "Comutați vizibilitatea",
   "missing_indicator.label": "Nu a fost găsit",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Postări fixate",
   "navigation_bar.preferences": "Preferințe",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Flux global",
   "navigation_bar.security": "Securitate",
   "notification.favourite": "{name} a adăugat statusul tău la favorite",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notificări",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Cine vede asta",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Nu afisa in fluxul public",
   "privacy.unlisted.short": "Nelistat",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Încărcare…",
   "regeneration_indicator.sublabel": "Fluxul tău este în preparare!",
   "relative_time.days": "{number}z",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Oameni",
   "search_results.hashtags": "Hashtaguri",
   "search_results.statuses": "Postări",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Arată mai mult",
   "status.show_more_all": "Arată mai mult pentru toți",
   "status.show_thread": "Arată topicul",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Repornește conversația",
   "status.unpin": "Eliberează din profil",
   "suggestions.dismiss": "Omite sugestia",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} vorbesc",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Postarea se va pierde dacă părăsești pagina.",
   "upload_area.title": "Trage și eliberează pentru a încărca",
   "upload_button.label": "Adaugă media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Adaugă o descriere pentru persoanele cu deficiențe de vedere",
-  "upload_form.focus": "Schimbă previzualizarea",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Șterge",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Se Încarcă...",
   "video.close": "ÃŽnchide video",
   "video.exit_fullscreen": "ÃŽnchide",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 609c0ab9ce38686ba53df9804745d8249675e97d..9a36c3b575ccbd7ff569196f9e5839715ce59d1f 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -4,6 +4,7 @@
   "account.block": "Блокировать",
   "account.block_domain": "Блокировать все с {domain}",
   "account.blocked": "Заблокирован(а)",
+  "account.cancel_follow_request": "Отменить запрос",
   "account.direct": "Написать @{name}",
   "account.domain_blocked": "Домен скрыт",
   "account.edit_profile": "Изменить профиль",
@@ -13,20 +14,22 @@
   "account.followers.empty": "Никто не подписан на этого пользователя.",
   "account.follows": "Подписки",
   "account.follows.empty": "Этот пользователь ни на кого не подписан.",
-  "account.follows_you": "Подписан(а) на Вас",
+  "account.follows_you": "Подписан(а) на вас",
   "account.hide_reblogs": "Скрыть реблоги от @{name}",
+  "account.last_status": "Последняя активность",
   "account.link_verified_on": "Владение этой ссылкой было проверено {date}",
   "account.locked_info": "Это закрытый аккаунт. Его владелец вручную одобряет подписчиков.",
   "account.media": "Медиа",
   "account.mention": "Упомянуть",
   "account.moved_to": "Ищите {name} здесь:",
-  "account.mute": "Заглушить",
+  "account.mute": "Скрыть @{name}",
   "account.mute_notifications": "Скрыть уведомления от @{name}",
-  "account.muted": "Приглушён",
+  "account.muted": "Скрыт",
+  "account.never_active": "Никогда",
   "account.posts": "Посты",
-  "account.posts_with_replies": "Посты и ответы",
+  "account.posts_with_replies": "Посты с ответами",
   "account.report": "Пожаловаться",
-  "account.requested": "Ожидает подтверждения",
+  "account.requested": "Ожидает подтверждения. Нажмите для отмены",
   "account.share": "Поделиться профилем @{name}",
   "account.show_reblogs": "Показывать продвижения от @{name}",
   "account.unblock": "Разблокировать",
@@ -35,8 +38,11 @@
   "account.unfollow": "Отписаться",
   "account.unmute": "Снять глушение",
   "account.unmute_notifications": "Показывать уведомления от @{name}",
+  "alert.rate_limited.message": "Пожалуйста, повторите через {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Скорость ограничена",
   "alert.unexpected.message": "Что-то пошло не так.",
   "alert.unexpected.title": "Ой!",
+  "autosuggest_hashtag.per_week": "{count} / неделю",
   "boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз",
   "bundle_column_error.body": "Что-то пошло не так при загрузке этого компонента.",
   "bundle_column_error.retry": "Попробовать снова",
@@ -47,12 +53,13 @@
   "column.blocks": "Список блокировки",
   "column.community": "Локальная лента",
   "column.direct": "Личные сообщения",
+  "column.directory": "Просмотр профилей",
   "column.domain_blocks": "Скрытые домены",
   "column.favourites": "Понравившееся",
   "column.follow_requests": "Запросы на подписку",
   "column.home": "Главная",
   "column.lists": "Списки",
-  "column.mutes": "Список глушения",
+  "column.mutes": "Список скрытых пользователей",
   "column.notifications": "Уведомления",
   "column.pins": "Закреплённый пост",
   "column.public": "Глобальная лента",
@@ -70,12 +77,12 @@
   "compose_form.hashtag_warning": "Этот пост не будет показывается в поиске по хэштегу, т.к. он непубличный. Только публичные посты можно найти в поиске по хэштегу.",
   "compose_form.lock_disclaimer": "Ваш аккаунт не {locked}. Любой человек может подписаться на Вас и просматривать посты для подписчиков.",
   "compose_form.lock_disclaimer.lock": "закрыт",
-  "compose_form.placeholder": "О чем Вы думаете?",
+  "compose_form.placeholder": "О чем вы думаете?",
   "compose_form.poll.add_option": "Добавить",
   "compose_form.poll.duration": "Длительность опроса",
   "compose_form.poll.option_placeholder": "Вариант {number}",
   "compose_form.poll.remove_option": "Удалить этот вариант",
-  "compose_form.publish": "Трубить",
+  "compose_form.publish": "Запостить",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Пометить медиафайл как чувствительный",
   "compose_form.sensitive.marked": "Медиафайлы не отмечены как чувствительные",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Вы действительно хотите навсегда удалить этот список?",
   "confirmations.domain_block.confirm": "Блокировать весь домен",
   "confirmations.domain_block.message": "Вы на самом деле уверены, что хотите блокировать весь {domain}? В большинстве случаев нескольких отдельных блокировок или глушений достаточно.",
+  "confirmations.logout.confirm": "Выйти",
+  "confirmations.logout.message": "Вы уверены, что хотите выйти?",
   "confirmations.mute.confirm": "Заглушить",
+  "confirmations.mute.explanation": "Будут скрыты их посты и те, где они упоминаются, но они при этом смогут видеть ваши посты и подписываться на вас.",
   "confirmations.mute.message": "Вы уверены, что хотите заглушить {name}?",
   "confirmations.redraft.confirm": "Удалить и исправить",
   "confirmations.redraft.message": "Вы уверены, что хотите удалить этот статус и превратить в черновик? Вы потеряете все ответы, продвижения и отметки 'нравится' к нему.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "При ответе текст набираемого сообщения будет перезаписан. Продолжить?",
   "confirmations.unfollow.confirm": "Отписаться",
   "confirmations.unfollow.message": "Вы уверены, что хотите отписаться от {name}?",
+  "conversation.delete": "Удалить беседу",
+  "conversation.mark_as_read": "Пометить прочитанным",
+  "conversation.open": "Просмотр беседы",
+  "conversation.with": "С {names}",
+  "directory.federated": "Со всей федерации",
+  "directory.local": "Только из {domain}",
+  "directory.new_arrivals": "Новички",
+  "directory.recently_active": "Недавно активные",
   "embed.instructions": "Встройте этот статус на Вашем сайте, скопировав код внизу.",
   "embed.preview": "Так это будет выглядеть:",
   "emoji_button.activity": "Занятия",
@@ -117,23 +135,27 @@
   "emoji_button.search_results": "Результаты поиска",
   "emoji_button.symbols": "Символы",
   "emoji_button.travel": "Путешествия",
-  "empty_column.account_timeline": "Статусов нет!",
+  "empty_column.account_timeline": "Здесь нет постов!",
   "empty_column.account_unavailable": "Профиль недоступен",
   "empty_column.blocks": "Вы ещё никого не заблокировали.",
   "empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
-  "empty_column.direct": "У Вас пока нет личных сообщений. Когда Вы начнёте их отправлять или получать, они появятся здесь.",
+  "empty_column.direct": "У вас пока нет личных сообщений. Как только вы отправите или получите одно, оно появится здесь.",
   "empty_column.domain_blocks": "Скрытых доменов пока нет.",
-  "empty_column.favourited_statuses": "Вы не добавили ни одного статуса в 'Избранное'. Как только Вы это сделаете, они появятся здесь.",
-  "empty_column.favourites": "Никто ещё не добавил этот статус в 'Избранное'. Как только кто-то это сделает, они появятся здесь.",
+  "empty_column.favourited_statuses": "Вы не добавили ни один пост в «Избранное». Как только вы это сделаете, он появится здесь.",
+  "empty_column.favourites": "Никто ещё не добавил этот пост в «Избранное». Как только кто-то это сделает, это отобразится здесь.",
   "empty_column.follow_requests": "Вам ещё не приходили запросы на подписку. Все новые запросы будут показаны здесь.",
   "empty_column.hashtag": "Статусов с таким хэштегом еще не существует.",
-  "empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.",
+  "empty_column.home": "Пока вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.",
   "empty_column.home.public_timeline": "публичные ленты",
   "empty_column.list": "В этом списке пока ничего нет.",
-  "empty_column.lists": "У Вас ещё нет списков. Все созданные Вами списки будут показаны здесь.",
-  "empty_column.mutes": "Вы ещё никого не заглушили.",
-  "empty_column.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.",
+  "empty_column.lists": "У вас ещё нет списков. Созданные вами списки будут показаны здесь.",
+  "empty_column.mutes": "Вы ещё никого не скрывали.",
+  "empty_column.notifications": "У вас пока нет уведомлений. Взаимодействуйте с другими, чтобы завести разговор.",
   "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.",
+  "error.unexpected_crash.explanation": "Из-за несовместимого браузера или ошибки в нашем коде, эта страница не может быть корректно отображена.",
+  "error.unexpected_crash.next_steps": "Попробуйте обновить страницу. Если проблема не исчезает, используйте Mastodon из-под другого браузера или приложения.",
+  "errors.unexpected_crash.copy_stacktrace": "Копировать стектрейс в буфер обмена",
+  "errors.unexpected_crash.report_issue": "Сообщить о проблеме",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -141,12 +163,12 @@
   "federation.local_only.short": "Local-only",
   "follow_request.authorize": "Авторизовать",
   "follow_request.reject": "Отказать",
-  "getting_started.developers": "Для разработчиков",
+  "getting_started.developers": "Разработчикам",
   "getting_started.directory": "Каталог профилей",
   "getting_started.documentation": "Документация",
   "getting_started.heading": "Добро пожаловать",
   "getting_started.invite": "Пригласить людей",
-  "getting_started.open_source_notice": "Mastodon - сервис с открытым исходным кодом. Вы можете помочь проекту или сообщить о проблемах на GitHub по адресу {github}.",
+  "getting_started.open_source_notice": "Mastodon — сервис с открытым исходным кодом. Вы можете внести вклад или сообщить о проблемах на GitHub: {github}.",
   "getting_started.security": "Безопасность",
   "getting_started.terms": "Условия использования",
   "hashtag.column_header.tag_mode.all": "и {additional}",
@@ -161,9 +183,9 @@
   "home.column_settings.basic": "Основные",
   "home.column_settings.show_reblogs": "Показывать продвижения",
   "home.column_settings.show_replies": "Показывать ответы",
-  "intervals.full.days": "{number, plural, one {# день} few {# дня} many {# дней} other {# дней}}",
-  "intervals.full.hours": "{number, plural, one {# час} few {# часа} many {# часов} other {# часов}}",
-  "intervals.full.minutes": "{number, plural, one {# минута} few {# минуты} many {# минут} other {# минут}}",
+  "intervals.full.days": "{number, plural, one {# день} few {# дня} other {# дней}}",
+  "intervals.full.hours": "{number, plural, one {# час} few {# часа} other {# часов}}",
+  "intervals.full.minutes": "{number, plural, one {# минута} few {# минуты} other {# минут}}",
   "introduction.federation.action": "Далее",
   "introduction.federation.federated.headline": "Глобальная лента",
   "introduction.federation.federated.text": "Публичные статусы с других серверов федеративной сети расположатся в глобальной ленте.",
@@ -172,7 +194,7 @@
   "introduction.federation.local.headline": "Локальная лента",
   "introduction.federation.local.text": "Публичные статусы от людей с того же сервера, что и вы, будут отображены в локальной ленте.",
   "introduction.interactions.action": "Завершить обучение",
-  "introduction.interactions.favourite.headline": "Отметки \"нравится\"",
+  "introduction.interactions.favourite.headline": "Отметки «нравится»",
   "introduction.interactions.favourite.text": "Вы можете отметить статус, чтобы вернуться к нему позже и дать знать автору, что запись вам понравилась, поставив отметку \"нравится\".",
   "introduction.interactions.reblog.headline": "Продвижения",
   "introduction.interactions.reblog.text": "Вы можете делиться статусами других людей, продвигая их в своём аккаунте.",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Заголовок списка",
   "lists.search": "Искать из ваших подписок",
   "lists.subheading": "Ваши списки",
+  "load_pending": "{count, plural, one {# новый элемент} few {# новых элемента} other {# новых элементов}}",
   "loading_indicator.label": "Загрузка...",
   "media_gallery.toggle_visible": "Показать/скрыть",
   "missing_indicator.label": "Не найдено",
@@ -247,16 +270,15 @@
   "navigation_bar.keyboard_shortcuts": "Сочетания клавиш",
   "navigation_bar.lists": "Списки",
   "navigation_bar.logout": "Выйти",
-  "navigation_bar.mutes": "Список глушения",
+  "navigation_bar.mutes": "Список скрытых пользователей",
   "navigation_bar.personal": "Личное",
   "navigation_bar.pins": "Закреплённые посты",
-  "navigation_bar.preferences": "Опции",
-  "navigation_bar.profile_directory": "Каталог профилей",
+  "navigation_bar.preferences": "Настройки",
   "navigation_bar.public_timeline": "Глобальная лента",
   "navigation_bar.security": "Безопасность",
   "notification.favourite": "{name} понравился Ваш статус",
-  "notification.follow": "{name} подписался(-лась) на Вас",
-  "notification.mention": "{name} упомянул(а) Вас",
+  "notification.follow": "{name} подписался (-лась) на вас",
+  "notification.mention": "{name} упомянул(а) вас",
   "notification.poll": "Опрос, в котором вы приняли участие, завершился",
   "notification.reblog": "{name} продвинул(а) Ваш статус",
   "notifications.clear": "Очистить уведомления",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} уведомл.",
   "poll.closed": "Завершён",
   "poll.refresh": "Обновить",
+  "poll.total_people": "{count, plural, one {# человек} few {# человека} many {# человек} other {# человек}}",
   "poll.total_votes": "{count, plural, one {# голос} few {# голоса} many {# голосов} other {# голосов}}",
   "poll.vote": "Голосовать",
+  "poll.voted": "Вы проголосовали за этот вариант",
   "poll_button.add_poll": "Добавить опрос",
   "poll_button.remove_poll": "Удалить опрос",
   "privacy.change": "Изменить видимость статуса",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Публичный",
   "privacy.unlisted.long": "Не показывать в лентах",
   "privacy.unlisted.short": "Скрытый",
+  "refresh": "Обновить",
   "regeneration_indicator.label": "Загрузка…",
   "regeneration_indicator.sublabel": "Ваша домашняя лента готовится!",
   "relative_time.days": "{number}д",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Люди",
   "search_results.hashtags": "Хэштеги",
   "search_results.statuses": "Посты",
+  "search_results.statuses_fts_disabled": "Поиск постов по их контенту не поддерживается на этом сервере Mastodon.",
   "search_results.total": "{count, number} {count, plural, one {результат} few {результата} many {результатов} other {результатов}}",
   "status.admin_account": "Открыть интерфейс модератора для @{name}",
   "status.admin_status": "Открыть этот статус в интерфейсе модератора",
@@ -358,6 +384,7 @@
   "status.show_more": "Развернуть",
   "status.show_more_all": "Развернуть для всех",
   "status.show_thread": "Показать обсуждение",
+  "status.uncached_media_warning": "Недоступно",
   "status.unmute_conversation": "Снять глушение с обсуждения",
   "status.unpin": "Открепить от профиля",
   "suggestions.dismiss": "Удалить предложение",
@@ -372,15 +399,23 @@
   "time_remaining.minutes": "{number, plural, one {осталась # минута} few {осталось # минуты} many {осталось # минут} other {осталось # минут}}",
   "time_remaining.moments": "остались считанные мгновения",
   "time_remaining.seconds": "{number, plural, one {осталась # секунду} few {осталось # секунды} many {осталось # секунд} other {осталось # секунд}}",
-  "trends.count_by_accounts": "Популярно у {count} {rawCount, plural, one {человека} few {человек} many {человек} other {человек}}",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {человек говорит} few {человека говорят} other {человек говорят}} про это",
+  "trends.trending_now": "Самое актуальное",
   "ui.beforeunload": "Ваш черновик будет утерян, если вы покинете Mastodon.",
   "upload_area.title": "Перетащите сюда, чтобы загрузить",
   "upload_button.label": "Добавить медиаконтент",
   "upload_error.limit": "Достигнут лимит загруженных файлов.",
   "upload_error.poll": "К опросам нельзя прикреплять файлы.",
-  "upload_form.description": "Описать для людей с нарушениями зрения",
-  "upload_form.focus": "Обрезать",
+  "upload_form.description": "Добавьте описание для людей с нарушениями зрения:",
+  "upload_form.edit": "Изменить",
   "upload_form.undo": "Отменить",
+  "upload_modal.analyzing_picture": "Обработка изображения…",
+  "upload_modal.apply": "Применить",
+  "upload_modal.description_placeholder": "На дворе трава, на траве дрова",
+  "upload_modal.detect_text": "Найти текст на картинке",
+  "upload_modal.edit_media": "Изменение медиа",
+  "upload_modal.hint": "Нажмите и перетащите круг в предпросмотре в точку фокуса, которая всегда будет видна на эскизах.",
+  "upload_modal.preview_label": "Предпросмотр ({ratio})",
   "upload_progress.label": "Загрузка...",
   "video.close": "Закрыть видео",
   "video.exit_fullscreen": "Покинуть полноэкранный режим",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 7bda37548489c087741fda13b7a34dc3b0107097..01fd3d42c979dfd89e483932411724886bb187bd 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -4,6 +4,7 @@
   "account.block": "Blokuj @{name}",
   "account.block_domain": "Ukry všetko z {domain}",
   "account.blocked": "Blokovaný/á",
+  "account.cancel_follow_request": "Zruš požiadanie o sledovanie",
   "account.direct": "Súkromná správa pre @{name}",
   "account.domain_blocked": "Doména ukrytá",
   "account.edit_profile": "Uprav profil",
@@ -15,15 +16,17 @@
   "account.follows.empty": "Tento užívateľ ešte nikoho nenásleduje.",
   "account.follows_you": "Následuje ťa",
   "account.hide_reblogs": "Skry vyzdvihnutia od @{name}",
+  "account.last_status": "Naposledy aktívny",
   "account.link_verified_on": "Vlastníctvo tohto odkazu bolo skontrolované {date}",
   "account.locked_info": "Stav súkromia pre tento účet je nastavený na zamknutý. Jeho vlastník sám prehodnocuje, kto ho môže sledovať.",
   "account.media": "Médiá",
   "account.mention": "Spomeň @{name}",
   "account.moved_to": "{name} sa presunul/a na:",
-  "account.mute": "Ignorovať @{name}",
+  "account.mute": "Nevšímaj si @{name}",
   "account.mute_notifications": "Stĺm oboznámenia od @{name}",
   "account.muted": "Utíšený/á",
-  "account.posts": "Príspevky",
+  "account.never_active": "Nikdy",
+  "account.posts": "Príspevkov",
   "account.posts_with_replies": "Príspevky aj s odpoveďami",
   "account.report": "Nahlás @{name}",
   "account.requested": "Čaká na schválenie. Klikni pre zrušenie žiadosti",
@@ -35,8 +38,11 @@
   "account.unfollow": "Prestaň následovať",
   "account.unmute": "Prestaň ignorovať @{name}",
   "account.unmute_notifications": "Zruš stĺmenie oboznámení od @{name}",
+  "alert.rate_limited.message": "Prosím, skús to znova za {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Tempo obmedzené",
   "alert.unexpected.message": "Vyskytla sa nečakaná chyba.",
   "alert.unexpected.title": "Ups!",
+  "autosuggest_hashtag.per_week": "{count} týždenne",
   "boost_modal.combo": "Nabudúce môžeš kliknúť {combo} pre preskočenie",
   "bundle_column_error.body": "Pri načítaní tohto prvku nastala nejaká chyba.",
   "bundle_column_error.retry": "Skús to znova",
@@ -47,6 +53,7 @@
   "column.blocks": "Blokovaní užívatelia",
   "column.community": "Miestna časová os",
   "column.direct": "Súkromné správy",
+  "column.directory": "Prehľadávaj profily",
   "column.domain_blocks": "Skryté domény",
   "column.favourites": "Obľúbené",
   "column.follow_requests": "Žiadosti o sledovanie",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Si si istý/á, že chceš natrvalo vymazať tento zoznam?",
   "confirmations.domain_block.confirm": "Skry celú doménu",
   "confirmations.domain_block.message": "Si si naozaj istý/á, že chceš blokovať celú doménu {domain}? Vo väčšine prípadov stačí blokovať alebo ignorovať pár konkrétnych užívateľov, čo sa doporučuje. Neuvidíš obsah z tejto domény v žiadnej verejnej časovej osi, ani v oznámeniach. Tvoji následovníci pochádzajúci z tejto domény budú odstránení.",
+  "confirmations.logout.confirm": "Odhlás sa",
+  "confirmations.logout.message": "Si si istý/á, že sa chceš odhlásiť?",
   "confirmations.mute.confirm": "Ignoruj",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Naozaj chceš ignorovať {name}?",
   "confirmations.redraft.confirm": "Vyčisti a prepíš",
   "confirmations.redraft.message": "Si si istý/á, že chceš premazať a prepísať tento príspevok? Jeho nadobudnuté vyzdvihnutia a obľúbenia, ale i odpovede na pôvodný príspevok budú odlúčené.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Odpovedaním akurát teraz prepíšeš správu, ktorú máš práve rozpísanú. Si si istý/á, že chceš pokračovať?",
   "confirmations.unfollow.confirm": "Nesleduj",
   "confirmations.unfollow.message": "Naozaj chceš prestať sledovať {name}?",
+  "conversation.delete": "Vymaž konverzáciu",
+  "conversation.mark_as_read": "Označ za prečítané",
+  "conversation.open": "Ukáž konverzáciu",
+  "conversation.with": "S {names}",
+  "directory.federated": "Zo známého fedivesmíru",
+  "directory.local": "Iba z {domain}",
+  "directory.new_arrivals": "Nové príchody",
+  "directory.recently_active": "Nedávno aktívne",
   "embed.instructions": "Umiestni kód uvedený nižšie pre pridanie tohto statusu na tvoju web stránku.",
   "embed.preview": "Tu je ako to bude vyzerať:",
   "emoji_button.activity": "Aktivita",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Ešte si nestĺmil žiadných užívateľov.",
   "empty_column.notifications": "Ešte nemáš žiadne oznámenia. Začni komunikovať s ostatnými, aby diskusia mohla začať.",
   "empty_column.public": "Ešte tu nič nie je. Napíš niečo verejne, alebo začni sledovať užívateľov z iných serverov, aby tu niečo pribudlo",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Skopíruj stacktrace do schránky",
+  "errors.unexpected_crash.report_issue": "Nahlás problém",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -142,10 +164,10 @@
   "follow_request.authorize": "Povoľ prístup",
   "follow_request.reject": "Odmietni",
   "getting_started.developers": "Vývojári",
-  "getting_started.directory": "Databáza profilov",
+  "getting_started.directory": "Zoznam profilov",
   "getting_started.documentation": "Dokumentácia",
   "getting_started.heading": "Začni tu",
-  "getting_started.invite": "Pozvať ľudí",
+  "getting_started.invite": "Pozvi ľudí",
   "getting_started.open_source_notice": "Mastodon je softvér s otvoreným kódom. Nahlásiť chyby, alebo prispievať môžeš na GitHube v {github}.",
   "getting_started.security": "Zabezpečenie",
   "getting_started.terms": "Podmienky prevozu",
@@ -161,9 +183,9 @@
   "home.column_settings.basic": "Základné",
   "home.column_settings.show_reblogs": "Zobraziť povýšené",
   "home.column_settings.show_replies": "Ukázať odpovede",
-  "intervals.full.days": "{number, plural, one {# deň} few {# dní} many {# dní} other {# dni}}",
-  "intervals.full.hours": "{number, plural, one {# hodina} few {# hodín} many {# hodín} other {# hodiny}}",
-  "intervals.full.minutes": "{number, plural, one {# minúta} few {# minút} many {# minút} other {# minúty}}",
+  "intervals.full.days": "{number, plural, one {# deň} few {# dní} many {# dní} other {# dní}}",
+  "intervals.full.hours": "{number, plural, one {# hodina} few {# hodín} many {# hodín} other {# hodín}}",
+  "intervals.full.minutes": "{number, plural, one {# minúta} few {# minút} many {# minút} other {# minút}}",
   "introduction.federation.action": "ÄŽalej",
   "introduction.federation.federated.headline": "Federovaná",
   "introduction.federation.federated.text": "Verejné príspevky z ostatných serverov vo fediverse budú zobrazené vo federovanej časovej osi.",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Názov nového zoznamu",
   "lists.search": "Vyhľadávaj medzi užívateľmi, ktorých sleduješ",
   "lists.subheading": "Tvoje zoznamy",
+  "load_pending": "{count, plural, one {# nová položka} other {# nových položiek}}",
   "loading_indicator.label": "Načítam...",
   "media_gallery.toggle_visible": "Zapni/Vypni viditeľnosť",
   "missing_indicator.label": "Nenájdené",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Osobné",
   "navigation_bar.pins": "Pripnuté príspevky",
   "navigation_bar.preferences": "Voľby",
-  "navigation_bar.profile_directory": "Katalóg profilov",
   "navigation_bar.public_timeline": "Federovaná časová os",
   "navigation_bar.security": "Zabezbečenie",
   "notification.favourite": "{name} si obľúbil/a tvoj príspevok",
@@ -259,7 +281,7 @@
   "notification.mention": "{name} ťa spomenul/a",
   "notification.poll": "Anketa v ktorej si hlasoval/a sa skončila",
   "notification.reblog": "{name} zdieľal/a tvoj príspevok",
-  "notifications.clear": "Vyčistiť zoznam oboznámení",
+  "notifications.clear": "Vyčisti oboznámenia",
   "notifications.clear_confirmation": "Naozaj chceš nenávratne prečistiť všetky tvoje oboznámenia?",
   "notifications.column_settings.alert": "Oboznámenia na ploche",
   "notifications.column_settings.favourite": "Obľúbené:",
@@ -281,9 +303,11 @@
   "notifications.filter.polls": "Výsledky ankiet",
   "notifications.group": "{count} oboznámení",
   "poll.closed": "Uzatvorená",
-  "poll.refresh": "Aktualizuj",
-  "poll.total_votes": "{count, plural, one {# hlas} few {# hlasov} many {# hlasov} other {# hlasy}}",
+  "poll.refresh": "Občerstvi",
+  "poll.total_people": "{count, plural, one {# človek} few {# ľudia} other {# ľudí}}",
+  "poll.total_votes": "{count, plural, one {# hlas} few {# hlasov} many {# hlasov} other {# hlasov}}",
   "poll.vote": "Hlasuj",
+  "poll.voted": "Hlasoval/a si za túto voľbu",
   "poll_button.add_poll": "Pridaj anketu",
   "poll_button.remove_poll": "Odstráň anketu",
   "privacy.change": "Uprav súkromie príspevku",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Verejné",
   "privacy.unlisted.long": "Neposielaj do verejných časových osí",
   "privacy.unlisted.short": "Verejne, ale nezobraziť v osi",
+  "refresh": "Občerstvi",
   "regeneration_indicator.label": "Načítava sa…",
   "regeneration_indicator.sublabel": "Vaša domovská nástenka sa pripravuje!",
   "relative_time.days": "{number}dní",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Ľudia",
   "search_results.hashtags": "Haštagy",
   "search_results.statuses": "Príspevky",
+  "search_results.statuses_fts_disabled": "Vyhľadávanie v obsahu príspevkov nieje na tomto Mastodon serveri povolené.",
   "search_results.total": "{count, number} {count, plural, one {výsledok} many {výsledkov} other {výsledky}}",
   "status.admin_account": "Otvor moderovacie rozhranie užívateľa @{name}",
   "status.admin_status": "Otvor tento príspevok v moderovacom rozhraní",
@@ -358,6 +384,7 @@
   "status.show_more": "Ukáž viac",
   "status.show_more_all": "Všetkým ukáž viac",
   "status.show_thread": "Ukáž diskusné vlákno",
+  "status.uncached_media_warning": "Nedostupný/é",
   "status.unmute_conversation": "Prestaň ignorovať konverzáciu",
   "status.unpin": "Odopni z profilu",
   "suggestions.dismiss": "Zavrhni návrh",
@@ -367,20 +394,28 @@
   "tabs_bar.local_timeline": "Miestna",
   "tabs_bar.notifications": "Oboznámenia",
   "tabs_bar.search": "Hľadaj",
-  "time_remaining.days": "Ostáva {number, plural, one {# deň} few {# dní} many {# dní} other {# dni}}",
+  "time_remaining.days": "Ostáva {number, plural, one {# deň} few {# dní} many {# dní} other {# dní}}",
   "time_remaining.hours": "Ostáva {number, plural, one {# hodina} few {# hodín} many {# hodín} other {# hodiny}}",
   "time_remaining.minutes": "Ostáva {number, plural, one {# minúta} few {# minút} many {# minút} other {# minúty}}",
   "time_remaining.moments": "Ostáva už iba chviľka",
-  "time_remaining.seconds": "Ostáva {number, plural, one {# sekunda} few {# sekúnd} many {# sekúnd} other {# sekundy}}",
+  "time_remaining.seconds": "Ostáva {number, plural, one {# sekunda} few {# sekúnd} many {# sekúnd} other {# sekúnd}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {človek vraví} other {ľudia vravia}}",
+  "trends.trending_now": "Teraz populárne",
   "ui.beforeunload": "Čo máš rozpísané sa stratí, ak opustíš Mastodon.",
   "upload_area.title": "Pretiahni a pusť pre nahratie",
   "upload_button.label": "Pridaj médiálny súbor (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Limit pre nahrávanie súborov bol prekročený.",
   "upload_error.poll": "Nahrávanie súborov pri anketách nieje možné.",
   "upload_form.description": "Opis pre slabo vidiacich",
-  "upload_form.focus": "Pozmeň náhľad",
+  "upload_form.edit": "Uprav",
   "upload_form.undo": "Vymaž",
+  "upload_modal.analyzing_picture": "Analyzujem obrázok…",
+  "upload_modal.apply": "Použi",
+  "upload_modal.description_placeholder": "Rýchla hnedá líška skáče ponad lenivého psa",
+  "upload_modal.detect_text": "Rozpoznaj text z obrázka",
+  "upload_modal.edit_media": "Uprav médiá",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Náhľad ({ratio})",
   "upload_progress.label": "Nahráva sa...",
   "video.close": "Zavri video",
   "video.exit_fullscreen": "Vypni zobrazenie na celú obrazovku",
diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json
index a0e150ec45382ba248b31687968fd855f9e7c507..29d7bf473ec6007201326431d1edfd6658ec045f 100644
--- a/app/javascript/mastodon/locales/sl.json
+++ b/app/javascript/mastodon/locales/sl.json
@@ -4,6 +4,7 @@
   "account.block": "Blokiraj @{name}",
   "account.block_domain": "Skrij vse iz {domain}",
   "account.blocked": "Blokirano",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Neposredno sporočilo @{name}",
   "account.domain_blocked": "Skrita domena",
   "account.edit_profile": "Uredi profil",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Ta uporabnik še ne sledi nikomur.",
   "account.follows_you": "Sledi tebi",
   "account.hide_reblogs": "Skrij spodbude od @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Lastništvo te povezave je bilo preverjeno {date}",
   "account.locked_info": "Stanje zasebnosti računa je nastavljeno na zaklenjeno. Lastnik ročno pregleda, kdo ga lahko spremlja.",
   "account.media": "Mediji",
@@ -23,6 +25,7 @@
   "account.mute": "Utišaj @{name}",
   "account.mute_notifications": "Utišaj obvestila od @{name}",
   "account.muted": "Utišan",
+  "account.never_active": "Never",
   "account.posts": "Tuti",
   "account.posts_with_replies": "Tuti in odgovori",
   "account.report": "Prijavi @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Prenehaj slediti",
   "account.unmute": "Odtišaj @{name}",
   "account.unmute_notifications": "Vklopi obvestila od @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Zgodila se je nepričakovana napaka.",
   "alert.unexpected.title": "Uups!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Če želite preskočiti to, lahko pritisnete {combo}",
   "bundle_column_error.body": "Med nalaganjem te komponente je prišlo do napake.",
   "bundle_column_error.retry": "Poskusi ponovno",
@@ -47,6 +53,7 @@
   "column.blocks": "Blokirani uporabniki",
   "column.community": "Lokalna časovnica",
   "column.direct": "Neposredna sporočila",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Skrite domene",
   "column.favourites": "Priljubljene",
   "column.follow_requests": "Sledi prošnjam",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Ali ste prepričani, da želite trajno izbrisati ta seznam?",
   "confirmations.domain_block.confirm": "Skrij celotno domeno",
   "confirmations.domain_block.message": "Ali ste res, res prepričani, da želite blokirati celotno {domain}? V večini primerov je nekaj ciljnih blokiranj ali utišanj dovolj in boljše. Vsebino iz te domene ne boste videli v javnih časovnicah ali obvestilih. Vaši sledilci iz te domene bodo odstranjeni.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Utišanje",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Ali ste prepričani, da želite utišati {name}?",
   "confirmations.redraft.confirm": "Izbriši in preoblikuj",
   "confirmations.redraft.message": "Ali ste prepričani, da želite izbrisati ta status in ga preoblikovati? Vzljubi in spodbude bodo izgubljeni, odgovori na izvirno objavo pa bodo osiroteli.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Odgovarjanje bo prepisalo sporočilo, ki ga trenutno sestavljate. Ali ste prepričani, da želite nadaljevati?",
   "confirmations.unfollow.confirm": "Prenehaj slediti",
   "confirmations.unfollow.message": "Ali ste prepričani, da ne želite več slediti {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Vstavi ta status na svojo spletno stran tako, da kopirate spodnjo kodo.",
   "embed.preview": "Tako bo izgledalo:",
   "emoji_button.activity": "Dejavnost",
@@ -132,8 +150,12 @@
   "empty_column.list": "Na tem seznamu ni ničesar. Ko bodo člani tega seznama objavili nove statuse, se bodo pojavili tukaj.",
   "empty_column.lists": "Nimate seznamov. Ko ga boste ustvarili, se bo prikazal tukaj.",
   "empty_column.mutes": "Niste utišali še nobenega uporabnika.",
-  "empty_column.notifications": "Nimate še nobenih obvestil. Poveži se z drugimi, da začnete pogovor.",
+  "empty_column.notifications": "Nimate še nobenih obvestil. Povežite se z drugimi, da začnete pogovor.",
   "empty_column.public": "Tukaj ni ničesar! Da ga napolnite, napišite nekaj javnega ali pa ročno sledite uporabnikom iz drugih strežnikov",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -172,7 +194,7 @@
   "introduction.federation.local.headline": "Lokalno",
   "introduction.federation.local.text": "Javne objave ljudi na istem strežniku, se bodo prikazale na lokalni časovnici.",
   "introduction.interactions.action": "Zaključi vadnico!",
-  "introduction.interactions.favourite.headline": "Priljubljeni",
+  "introduction.interactions.favourite.headline": "Vzljubi",
   "introduction.interactions.favourite.text": "Tut lahko shranite za pozneje in ga vzljubite ter s tem pokažete avtorju, da vam je ta tut priljubljen.",
   "introduction.interactions.reblog.headline": "Spodbudi",
   "introduction.interactions.reblog.text": "Tute drugih ljudi lahko delite z vašimi sledilci, tako da spodbudite tute.",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Nov naslov seznama",
   "lists.search": "Išči med ljudmi, katerim sledite",
   "lists.subheading": "Vaši seznami",
+  "load_pending": "{count, plural, one {# nov element} other {# novih elementov}}",
   "loading_indicator.label": "Nalaganje...",
   "media_gallery.toggle_visible": "Preklopi vidljivost",
   "missing_indicator.label": "Ni najdeno",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Osebno",
   "navigation_bar.pins": "Pripeti tuti",
   "navigation_bar.preferences": "Nastavitve",
-  "navigation_bar.profile_directory": "Imenik profilov",
   "navigation_bar.public_timeline": "Združena časovnica",
   "navigation_bar.security": "Varnost",
   "notification.favourite": "{name} je vzljubil/a vaš status",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} obvestil",
   "poll.closed": "Zaprto",
   "poll.refresh": "Osveži",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural,one {# glas} other {# glasov}}",
   "poll.vote": "Glasuj",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Dodaj anketo",
   "poll_button.remove_poll": "Odstrani anketo",
   "privacy.change": "Prilagodi zasebnost statusa",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Javno",
   "privacy.unlisted.long": "Ne objavi na javne časovnice",
   "privacy.unlisted.short": "Ni prikazano",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Nalaganje…",
   "regeneration_indicator.sublabel": "Vaš domači vir se pripravlja!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Ljudje",
   "search_results.hashtags": "Ključniki",
   "search_results.statuses": "Tuti",
+  "search_results.statuses_fts_disabled": "Iskanje tutov po njihovi vsebini ni omogočeno na tem strežniku Mastodon.",
   "search_results.total": "{count, number} {count, plural, one {rezultat} other {rezultatov}}",
   "status.admin_account": "Odpri vmesnik za moderiranje za @{name}",
   "status.admin_status": "Odpri status v vmesniku za moderiranje",
@@ -357,30 +383,39 @@
   "status.show_less_all": "Prikaži manj za vse",
   "status.show_more": "Prikaži več",
   "status.show_more_all": "Prikaži več za vse",
-  "status.show_thread": "Show thread",
+  "status.show_thread": "Prikaži objavo",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Odtišaj pogovor",
   "status.unpin": "Odpni iz profila",
-  "suggestions.dismiss": "Dismiss suggestion",
-  "suggestions.header": "You might be interested in…",
+  "suggestions.dismiss": "Zavrni predlog",
+  "suggestions.header": "Morda bi vas zanimalo…",
   "tabs_bar.federated_timeline": "Združeno",
   "tabs_bar.home": "Domov",
   "tabs_bar.local_timeline": "Lokalno",
   "tabs_bar.notifications": "Obvestila",
-  "tabs_bar.search": "Poišči",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "tabs_bar.search": "Iskanje",
+  "time_remaining.days": "{number, plural, one {# dan} other {# dni}} je ostalo",
+  "time_remaining.hours": "{number, plural, one {# ura} other {# ur}} je ostalo",
+  "time_remaining.minutes": "{number, plural, one {# minuta} other {# minut}} je ostalo",
+  "time_remaining.moments": "Preostali trenutki",
+  "time_remaining.seconds": "{number, plural, one {# sekunda} other {# sekund}} je ostalo",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {oseba} other {ljudi}} govori",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Vaš osnutek bo izgubljen, če zapustite Mastodona.",
-  "upload_area.title": "Povlecite in spustite za pošiljanje",
-  "upload_button.label": "Dodaj medij",
-  "upload_error.limit": "File upload limit exceeded.",
-  "upload_error.poll": "File upload not allowed with polls.",
+  "upload_area.title": "Za pošiljanje povlecite in spustite",
+  "upload_button.label": "Dodaj medije ({formats})",
+  "upload_error.limit": "Omejitev prenosa datoteke je presežena.",
+  "upload_error.poll": "Prenos datoteke z anketami ni dovoljen.",
   "upload_form.description": "Opišite za slabovidne",
-  "upload_form.focus": "Obreži",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Izbriši",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Pošiljanje...",
   "video.close": "Zapri video",
   "video.exit_fullscreen": "Izhod iz celozaslonskega načina",
diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json
index 3962e35f0c55e8383e3f38b3354007ef0e85e28b..1f6de613d636ed674a3b018ef3544e4f88e291f2 100644
--- a/app/javascript/mastodon/locales/sq.json
+++ b/app/javascript/mastodon/locales/sq.json
@@ -4,6 +4,7 @@
   "account.block": "Blloko @{name}",
   "account.block_domain": "Fshih gjithçka prej {domain}",
   "account.blocked": "E bllokuar",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Mesazh i drejtpërdrejt për @{name}",
   "account.domain_blocked": "Përkatësi e fshehur",
   "account.edit_profile": "Përpunoni profilin",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Ky përdorues ende s’ndjek njeri.",
   "account.follows_you": "Ju ndjek",
   "account.hide_reblogs": "Fshih përforcime nga @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Pronësia e kësaj lidhjeje qe kontrolluar më {date}",
   "account.locked_info": "Gjendja e privatësisë së kësaj llogarie është caktuar si e kyçur. I zoti merr dorazi në shqyrtim cilët mund ta ndjekin.",
   "account.media": "Media",
@@ -23,6 +25,7 @@
   "account.mute": "Heshtoni @{name}",
   "account.mute_notifications": "Heshtoji njoftimet prej @{name}",
   "account.muted": "Heshtuar",
+  "account.never_active": "Never",
   "account.posts": "Mesazhe",
   "account.posts_with_replies": "Mesazhe dhe përgjigje",
   "account.report": "Raportojeni @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Resht së ndjekuri",
   "account.unmute": "Ktheji zërin @{name}",
   "account.unmute_notifications": "Hiqua ndalimin e shfaqjes njoftimeve nga @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Ndodhi një gabim të papritur.",
   "alert.unexpected.title": "Hëm!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Mund të shtypni {combo}, që të anashkalohet kjo herës tjetër",
   "bundle_column_error.body": "Diç shkoi ters teksa ngarkohej ky përbërës.",
   "bundle_column_error.retry": "Riprovoni",
@@ -47,6 +53,7 @@
   "column.blocks": "Përdorues të bllokuar",
   "column.community": "Rrjedhë kohore vendore",
   "column.direct": "Mesazhe të drejtpërdrejta",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Përkatësi të fshehura",
   "column.favourites": "Të parapëlqyer",
   "column.follow_requests": "Kërkesa për ndjekje",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Jeni i sigurt që doni të fshihet përgjithmonë kjo listë?",
   "confirmations.domain_block.confirm": "Fshih krejt përkatësinë",
   "confirmations.domain_block.message": "Jeni i sigurt, shumë i sigurt se doni të bllokohet krejt {domain}? Në shumicën e rasteve, ndoca bllokime ose heshtime me synim të caktuar janë të mjaftueshme dhe të parapëlqyera. S’keni për të parë lëndë nga kjo përkatësi në ndonjë rrjedhë kohore publike, apo te njoftimet tuaja. Ndjekësit tuaj prej asaj përkatësie do të hiqen.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Heshtoje",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Jeni i sigurt se doni të heshtohet {name}?",
   "confirmations.redraft.confirm": "Fshijeni & rihartojeni",
   "confirmations.redraft.message": "Jeni i sigurt se doni të fshihet kjo gjendje dhe të rihartohet? Parapëlqimet dhe boosts do të humbin, ndërsa përgjigjet te postimi origjinal do të bëhen jetime.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Përgjigja tani do të shkaktojë mbishkrimin e mesazhit që po hartoni. Jeni i sigurt se doni të vazhdohet më tej?",
   "confirmations.unfollow.confirm": "Resht së ndjekuri",
   "confirmations.unfollow.message": "Jeni i sigurt se doni të mos ndiqet më {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Trupëzojeni këtë gjendje në sajtin tuaj duke kopjuar kodin më poshtë.",
   "embed.preview": "Ja si do të duket:",
   "emoji_button.activity": "Veprimtari",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "S’keni heshtuar ende ndonjë përdorues.",
   "empty_column.notifications": "Ende s’keni ndonjë njoftim. Ndërveproni me të tjerët që të nisë biseda.",
   "empty_column.public": "S’ka gjë këtu! Shkruani diçka publikisht, ose ndiqni dorazi përdorues prej instancash të tjera, që ta mbushni këtë zonë",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Titull liste të re",
   "lists.search": "Kërkoni mes personash që ndiqni",
   "lists.subheading": "Listat tuaja",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Po ngarkohet…",
   "media_gallery.toggle_visible": "Ndërroni dukshmërinë",
   "missing_indicator.label": "S’u gjet",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personale",
   "navigation_bar.pins": "Mesazhe të fiksuar",
   "navigation_bar.preferences": "Parapëlqime",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Rrjedhë kohore të federuarish",
   "navigation_bar.security": "Siguri",
   "notification.favourite": "{name} parapëlqeu gjendjen tuaj",
@@ -282,8 +304,10 @@
   "notifications.group": "{count}s njoftime",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Rregulloni privatësi gjendje",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Publike",
   "privacy.unlisted.long": "Mos e postoni në rrjedha publike kohore",
   "privacy.unlisted.short": "Jo në lista",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Po ngarkohet…",
   "regeneration_indicator.sublabel": "Prurja juaj vetjake po përgatiteet!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Persona",
   "search_results.hashtags": "Hashtagë",
   "search_results.statuses": "Mesazhe",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, një {result} {results} të tjera}",
   "status.admin_account": "Hap ndërfaqe moderimi për @{name}",
   "status.admin_status": "Hape këtë gjendje te ndërfaqja e moderimit",
@@ -358,6 +384,7 @@
   "status.show_more": "Shfaq më tepër",
   "status.show_more_all": "Shfaq më tepër për të tërë",
   "status.show_thread": "Shfaq rrjedhën",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Ktheji zërin bisedës",
   "status.unpin": "Shfiksoje nga profili",
   "suggestions.dismiss": "Mos e merr parasysh sugjerimin",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, një {person} {people} të tjerë} po flasin",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Skica juaj do të humbë nëse dilni nga Mastodon-i.",
   "upload_area.title": "Merreni & vëreni që të ngarkohet",
   "upload_button.label": "Shtoni media (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "U tejkalua kufi ngarkimi kartelash.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Përshkruajeni për persona me probleme shikimi",
-  "upload_form.focus": "Ndryshoni parapamjen",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Fshije",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Po ngarkohet…",
   "video.close": "Mbylle videon",
   "video.exit_fullscreen": "Dil nga mënyra Sa Krejt Ekrani",
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index b432dc70847f53d7ac94bb430bc174eb466f832c..e396087dff74b93a47bf97e6fb7b8fe8618d19ab 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -4,6 +4,7 @@
   "account.block": "Blokiraj korisnika @{name}",
   "account.block_domain": "Sakrij sve sa domena {domain}",
   "account.blocked": "Blocked",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Direct Message @{name}",
   "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Izmeni profil",
@@ -15,6 +16,7 @@
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Prati Vas",
   "account.hide_reblogs": "Sakrij podrške koje daje korisnika @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Mediji",
@@ -23,6 +25,7 @@
   "account.mute": "Ućutkaj korisnika @{name}",
   "account.mute_notifications": "Isključi obaveštenja od korisnika @{name}",
   "account.muted": "Muted",
+  "account.never_active": "Never",
   "account.posts": "Statusa",
   "account.posts_with_replies": "Toots with replies",
   "account.report": "Prijavi @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Otprati",
   "account.unmute": "Ukloni ućutkavanje korisniku @{name}",
   "account.unmute_notifications": "Uključi nazad obaveštenja od korisnika @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Možete pritisnuti {combo} da preskočite ovo sledeći put",
   "bundle_column_error.body": "Nešto je pošlo po zlu prilikom učitavanja ove komponente.",
   "bundle_column_error.retry": "Pokušajte ponovo",
@@ -47,6 +53,7 @@
   "column.blocks": "Blokirani korisnici",
   "column.community": "Lokalna lajna",
   "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Omiljeni",
   "column.follow_requests": "Zahtevi za praćenje",
@@ -64,7 +71,7 @@
   "column_header.show_settings": "Prikaži postavke",
   "column_header.unpin": "Otkači",
   "column_subheading.settings": "Postavke",
-  "community.column_settings.media_only": "Media Only",
+  "community.column_settings.media_only": "Media only",
   "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.",
   "compose_form.direct_message_warning_learn_more": "Learn more",
   "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Da li ste sigurni da želite da bespovratno obrišete ovu listu?",
   "confirmations.domain_block.confirm": "Sakrij ceo domen",
   "confirmations.domain_block.message": "Da li ste stvarno, stvarno sigurno da želite da blokirate ceo domen {domain}? U većini slučajeva, par dobrih blokiranja ili ućutkavanja su dovoljna i preporučljiva.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Ućutkaj",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Da li stvarno želite da ućutkate korisnika {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Otprati",
   "confirmations.unfollow.message": "Da li ste sigurni da želite da otpratite korisnika {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Ugradi ovaj status na Vaš veb sajt kopiranjem koda ispod.",
   "embed.preview": "Ovako će da izgleda:",
   "emoji_button.activity": "Aktivnost",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "Trenutno nemate obaveštenja. Družite se malo da započnete razgovore.",
   "empty_column.public": "Ovde nema ničega! Napišite nešto javno, ili nađite korisnike sa drugih instanci koje ćete zapratiti da popunite ovu prazninu",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Naslov nove liste",
   "lists.search": "Pretraži među ljudima koje pratite",
   "lists.subheading": "Vaše liste",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Učitavam...",
   "media_gallery.toggle_visible": "Uključi/isključi vidljivost",
   "missing_indicator.label": "Nije pronađeno",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Prikačeni tutovi",
   "navigation_bar.preferences": "Podešavanja",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Federisana lajna",
   "navigation_bar.security": "Security",
   "notification.favourite": "{name} je stavio Vaš status kao omiljeni",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Podesi status privatnosti",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Javno",
   "privacy.unlisted.long": "Ne objavljuj na javnim lajnama",
   "privacy.unlisted.short": "Neizlistano",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Loading…",
   "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "People",
   "search_results.hashtags": "Hashtags",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {rezultat} few {rezultata} other {rezultata}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Prikaži više",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Uključi prepisku",
   "status.unpin": "Otkači sa profila",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Ako napustite Mastodont, izgubićete napisani nacrt.",
   "upload_area.title": "Prevucite ovde da otpremite",
   "upload_button.label": "Dodaj multimediju",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Opiši za slabovide osobe",
-  "upload_form.focus": "Crop",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Opozovi",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Otpremam...",
   "video.close": "Zatvori video",
   "video.exit_fullscreen": "Napusti ceo ekran",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index 22c468076a391549d91bf4cb5cadab71bd178d52..40d14599a41b15a2f0abec6c01626e480d08c851 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -4,6 +4,7 @@
   "account.block": "Блокирај @{name}",
   "account.block_domain": "Сакриј све са домена {domain}",
   "account.blocked": "Блокиран",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "Директна порука @{name}",
   "account.domain_blocked": "Домен сакривен",
   "account.edit_profile": "Измени профил",
@@ -15,6 +16,7 @@
   "account.follows.empty": "Корисник тренутно не прати никога.",
   "account.follows_you": "Прати Вас",
   "account.hide_reblogs": "Сакриј подршке које даје корисника @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Медији",
@@ -23,6 +25,7 @@
   "account.mute": "Ућуткај корисника @{name}",
   "account.mute_notifications": "Искључи обавештења од корисника @{name}",
   "account.muted": "Ућуткан",
+  "account.never_active": "Never",
   "account.posts": "Трубе",
   "account.posts_with_replies": "Трубе и одговори",
   "account.report": "Пријави @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "Отпрати",
   "account.unmute": "Уклони ућуткавање кориснику @{name}",
   "account.unmute_notifications": "Укључи назад обавештења од корисника @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Појавила се неочекивана грешка.",
   "alert.unexpected.title": "Упс!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "Можете притиснути {combo} да прескочите ово следећи пут",
   "bundle_column_error.body": "Нешто је пошло по злу приликом учитавања ове компоненте.",
   "bundle_column_error.retry": "Покушајте поново",
@@ -47,6 +53,7 @@
   "column.blocks": "Блокирани корисници",
   "column.community": "Локална временска линија",
   "column.direct": "Директне поруке",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Скривени домени",
   "column.favourites": "Омиљене",
   "column.follow_requests": "Захтеви за праћење",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Да ли сте сигурни да желите да бесповратно обришете ову листу?",
   "confirmations.domain_block.confirm": "Сакриј цео домен",
   "confirmations.domain_block.message": "Да ли сте заиста сигурни да желите да блокирате цео домен {domain}? У већини случајева, неколико добро промишљених блокирања или ућуткавања су довољна и препоручљива.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Ућуткај",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Да ли стварно желите да ућуткате корисника {name}?",
   "confirmations.redraft.confirm": "Избриши и преправи",
   "confirmations.redraft.message": "Да ли сте сигурни да желите да избришете овај статус и да га преправите? Сва стављања у омиљене трубе, као и подршке ће бити изгубљене, а одговори на оригинални пост ће бити поништени.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Отпрати",
   "confirmations.unfollow.message": "Да ли сте сигурни да желите да отпратите корисника {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Угради овај статус на Ваш веб сајт копирањем кода испод.",
   "embed.preview": "Овако ће да изгледа:",
   "emoji_button.activity": "Активност",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Још увек немате ућутканих корисника.",
   "empty_column.notifications": "Тренутно немате обавештења. Дружите се мало да започнете разговор.",
   "empty_column.public": "Овде нема ничега! Напишите нешто јавно, или нађите кориснике са других инстанци које ћете запратити да попуните ову празнину",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Наслов нове листе",
   "lists.search": "Претражи међу људима које пратите",
   "lists.subheading": "Ваше листе",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "Учитавам...",
   "media_gallery.toggle_visible": "Укључи/искључи видљивост",
   "missing_indicator.label": "Није пронађено",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Прикачене трубе",
   "navigation_bar.preferences": "Подешавања",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Здружена временска линија",
   "navigation_bar.security": "Безбедност",
   "notification.favourite": "{name} је ставио/ла Ваш статус као омиљени",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} обавештења",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Подеси статус приватности",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Јавно",
   "privacy.unlisted.long": "Не објављуј на јавним временским линијама",
   "privacy.unlisted.short": "Неизлистано",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "Учитавање…",
   "regeneration_indicator.sublabel": "Ваша почетна страница се припрема!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "Људи",
   "search_results.hashtags": "Тарабе",
   "search_results.statuses": "Трубе",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {резултат} few {резултата} other {резултата}}",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "Прикажи више",
   "status.show_more_all": "Прикажи више за све",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Укључи преписку",
   "status.unpin": "Откачи са профила",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {човек} other {људи}} прича",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Ако напустите Мастодонт, изгубићете написани нацрт.",
   "upload_area.title": "Превуците овде да отпремите",
   "upload_button.label": "Додај мултимедију (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Опишите за особе са оштећеним видом",
-  "upload_form.focus": "Подесите",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Обриши",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Отпремам...",
   "video.close": "Затвори видео",
   "video.exit_fullscreen": "Напусти цео екран",
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index 56e2c1a5d58bab615299eefa2bc4e6bc80529e30..2bbb534c889df84c730304f1768a08d066b78caf 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -4,52 +4,59 @@
   "account.block": "Blockera @{name}",
   "account.block_domain": "Dölj allt från {domain}",
   "account.blocked": "Blockerad",
-  "account.direct": "Direktmeddelande @{name}",
+  "account.cancel_follow_request": "Avbryt förfrågan att följa",
+  "account.direct": "Skicka ett direktmeddelande till @{name}",
   "account.domain_blocked": "Domän dold",
   "account.edit_profile": "Redigera profil",
-  "account.endorse": "Feature on profile",
+  "account.endorse": "Visa upp på profil",
   "account.follow": "Följ",
   "account.followers": "Följare",
   "account.followers.empty": "Ingen följer denna användaren än.",
   "account.follows": "Följer",
-  "account.follows.empty": "This user doesn't follow anyone yet.",
+  "account.follows.empty": "Den här användaren följer inte någon ännu.",
   "account.follows_you": "Följer dig",
   "account.hide_reblogs": "Dölj knuffar från @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
-  "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
+  "account.last_status": "Senast aktiv",
+  "account.link_verified_on": "Ägarskapet för det här kontot kontrollerades den {date}",
+  "account.locked_info": "Detta konto har en låst integritetsstatus. Ägaren väljer manuellt vem som kan följa.",
   "account.media": "Media",
   "account.mention": "Nämna @{name}",
   "account.moved_to": "{name} har flyttat till:",
   "account.mute": "Tysta @{name}",
   "account.mute_notifications": "Stäng av notifieringar från @{name}",
-  "account.muted": "Nertystad",
+  "account.muted": "Tystad",
+  "account.never_active": "Aldrig",
   "account.posts": "Inlägg",
   "account.posts_with_replies": "Toots och svar",
   "account.report": "Rapportera @{name}",
   "account.requested": "Inväntar godkännande. Klicka för att avbryta följförfrågan",
-  "account.share": "Dela @{name}'s profil",
+  "account.share": "Dela @{name}s profil",
   "account.show_reblogs": "Visa knuffar från @{name}",
   "account.unblock": "Avblockera @{name}",
-  "account.unblock_domain": "Ta fram {domain}",
-  "account.unendorse": "Don't feature on profile",
+  "account.unblock_domain": "Sluta dölja {domain}",
+  "account.unendorse": "Visa inte upp på profil",
   "account.unfollow": "Sluta följa",
-  "account.unmute": "Ta bort tystad @{name}",
+  "account.unmute": "Sluta tysta @{name}",
   "account.unmute_notifications": "Återaktivera notifikationer från @{name}",
+  "alert.rate_limited.message": "Vänligen försök igen efter {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Begränsad mängd",
   "alert.unexpected.message": "Ett oväntat fel uppstod.",
-  "alert.unexpected.title": "Whups!",
+  "alert.unexpected.title": "Hoppsan!",
+  "autosuggest_hashtag.per_week": "{count} per vecka",
   "boost_modal.combo": "Du kan trycka {combo} för att slippa denna nästa gång",
   "bundle_column_error.body": "Något gick fel när du laddade denna komponent.",
   "bundle_column_error.retry": "Försök igen",
   "bundle_column_error.title": "Nätverksfel",
   "bundle_modal_error.close": "Stäng",
-  "bundle_modal_error.message": "Något gick fel när du laddade denna komponent.",
+  "bundle_modal_error.message": "Något gick fel när denna komponent laddades.",
   "bundle_modal_error.retry": "Försök igen",
   "column.blocks": "Blockerade användare",
   "column.community": "Lokal tidslinje",
-  "column.direct": "Direktmeddelande",
+  "column.direct": "Direktmeddelanden",
+  "column.directory": "Bläddra bland profiler",
   "column.domain_blocks": "Dolda domäner",
   "column.favourites": "Favoriter",
-  "column.follow_requests": "Följ förfrågningar",
+  "column.follow_requests": "Följförfrågningar",
   "column.home": "Hem",
   "column.lists": "Listor",
   "column.mutes": "Tystade användare",
@@ -65,75 +72,90 @@
   "column_header.unpin": "Ångra fäst",
   "column_subheading.settings": "Inställningar",
   "community.column_settings.media_only": "Enbart media",
-  "compose_form.direct_message_warning": "Denna toot kommer endast att skickas nämnda nämnda användare.",
+  "compose_form.direct_message_warning": "Denna toot kommer endast att skickas till nämnda användare.",
   "compose_form.direct_message_warning_learn_more": "Visa mer",
   "compose_form.hashtag_warning": "Denna toot kommer inte att listas under någon hashtag eftersom den är onoterad. Endast offentliga toots kan sökas med hashtag.",
-  "compose_form.lock_disclaimer": "Ditt konto är inte {locked}. Vemsomhelst kan följa dig och även se dina inlägg skrivna för endast dina följare.",
+  "compose_form.lock_disclaimer": "Ditt konto är inte {locked}. Vem som helst kan följa dig och även se dina inlägg som bara är för följare.",
   "compose_form.lock_disclaimer.lock": "låst",
   "compose_form.placeholder": "Vad funderar du på?",
-  "compose_form.poll.add_option": "Add a choice",
-  "compose_form.poll.duration": "Poll duration",
-  "compose_form.poll.option_placeholder": "Choice {number}",
-  "compose_form.poll.remove_option": "Remove this choice",
-  "compose_form.publish": "Toot",
+  "compose_form.poll.add_option": "Nytt alternativ",
+  "compose_form.poll.duration": "Varaktighet för omröstning",
+  "compose_form.poll.option_placeholder": "Alternativ {number}",
+  "compose_form.poll.remove_option": "Ta bort alternativ",
+  "compose_form.publish": "Tut",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.hide": "Mark media as sensitive",
+  "compose_form.sensitive.hide": "Markera media som känsligt",
   "compose_form.sensitive.marked": "Media har markerats som känsligt",
   "compose_form.sensitive.unmarked": "Media har inte markerats som känsligt",
   "compose_form.spoiler.marked": "Texten har dolts bakom en varning",
   "compose_form.spoiler.unmarked": "Texten är inte dold",
   "compose_form.spoiler_placeholder": "Skriv din varning här",
   "confirmation_modal.cancel": "Ã…ngra",
-  "confirmations.block.block_and_report": "Block & Report",
+  "confirmations.block.block_and_report": "Blockera & rapportera",
   "confirmations.block.confirm": "Blockera",
   "confirmations.block.message": "Är du säker att du vill blockera {name}?",
   "confirmations.delete.confirm": "Ta bort",
   "confirmations.delete.message": "Är du säker att du vill ta bort denna status?",
-  "confirmations.delete_list.confirm": "Delete",
+  "confirmations.delete_list.confirm": "Ta bort",
   "confirmations.delete_list.message": "Är du säker på att du vill radera denna lista permanent?",
-  "confirmations.domain_block.confirm": "Blockera hela domänen",
+  "confirmations.domain_block.confirm": "Dölj hela domänen",
   "confirmations.domain_block.message": "Är du verkligen säker på att du vill blockera hela {domain}? I de flesta fall är några riktade blockeringar eller nedtystade konton tillräckligt och att föredra. Du kommer sluta se innehåll från {domain}-domänen i den allmänna tidslinjen och i dina egna notifieringar. Du kommer även sluta följa alla eventuella följare du har från {domain}.",
+  "confirmations.logout.confirm": "Logga ut",
+  "confirmations.logout.message": "Är du säker på att du vill logga ut?",
   "confirmations.mute.confirm": "Tysta",
+  "confirmations.mute.explanation": "Detta kommer dölja postningar från dem och postningar som nämner dem, men fortfarande tillåta dem att se dina postningar och följa dig.",
   "confirmations.mute.message": "Är du säker du vill tysta ner {name}?",
   "confirmations.redraft.confirm": "Radera och gör om",
   "confirmations.redraft.message": "Är du säker på att du vill radera meddelandet och göra om det? Du kommer förlora alla svar, knuffar och favoriter som hänvisar till meddelandet.",
-  "confirmations.reply.confirm": "Reply",
-  "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+  "confirmations.reply.confirm": "Svara",
+  "confirmations.reply.message": "Om du svarar nu kommer det att ersätta meddelandet du håller på att skriva. Är du säker på att du vill fortsätta?",
   "confirmations.unfollow.confirm": "Sluta följa",
   "confirmations.unfollow.message": "Är du säker på att du vill sluta följa {name}?",
+  "conversation.delete": "Radera konversation",
+  "conversation.mark_as_read": "Markera som läst",
+  "conversation.open": "Se konversation",
+  "conversation.with": "Med {names}",
+  "directory.federated": "Från känt fediverse",
+  "directory.local": "Endast från {domain}",
+  "directory.new_arrivals": "Nyanlända",
+  "directory.recently_active": "Nyligen aktiva",
   "embed.instructions": "Bädda in den här statusen på din webbplats genom att kopiera koden nedan.",
-  "embed.preview": "Här ser du hur det kommer att se ut:",
+  "embed.preview": "Så här kommer det att se ut:",
   "emoji_button.activity": "Aktivitet",
-  "emoji_button.custom": "Specialgjord",
+  "emoji_button.custom": "Anpassad",
   "emoji_button.flags": "Flaggor",
-  "emoji_button.food": "Mat & Dryck",
+  "emoji_button.food": "Mat & dryck",
   "emoji_button.label": "Lägg till emoji",
   "emoji_button.nature": "Natur",
   "emoji_button.not_found": "Inga emojos!! (╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "Objekt",
-  "emoji_button.people": "Människor",
+  "emoji_button.people": "Personer",
   "emoji_button.recent": "Ofta använda",
   "emoji_button.search": "Sök...",
   "emoji_button.search_results": "Sökresultat",
   "emoji_button.symbols": "Symboler",
-  "emoji_button.travel": "Resor & Platser",
-  "empty_column.account_timeline": "No toots here!",
-  "empty_column.account_unavailable": "Profile unavailable",
-  "empty_column.blocks": "You haven't blocked any users yet.",
-  "empty_column.community": "Den lokala tidslinjen är tom. Skriv något offentligt för att få bollen att rulla!",
-  "empty_column.direct": "Du har inga direktmeddelanden än. När du skickar eller tar emot kommer den att dyka upp här.",
-  "empty_column.domain_blocks": "There are no hidden domains yet.",
-  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
-  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
-  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
+  "emoji_button.travel": "Resor & platser",
+  "empty_column.account_timeline": "Inga inlägg här!",
+  "empty_column.account_unavailable": "Profilen är inte tillgänglig",
+  "empty_column.blocks": "Du har ännu inte blockerat några användare.",
+  "empty_column.community": "Den lokala tidslinjen är tom. Skriv något offentligt för att sätta bollen i rullning!",
+  "empty_column.direct": "Du har inga direktmeddelanden än. När du skickar eller tar emot ett kommer det att dyka upp här.",
+  "empty_column.domain_blocks": "Det finns ännu inga dolda domäner.",
+  "empty_column.favourited_statuses": "Du har inga favoritmarkerade toots än. När du favoritmarkerar en kommer den synas här.",
+  "empty_column.favourites": "Ingen har favoritmarkerat den här tooten än. När någon gör det kommer de synas här.",
+  "empty_column.follow_requests": "Du har inga följförfrågningar än. När du får en kommer den synas här.",
   "empty_column.hashtag": "Det finns inget i denna hashtag ännu.",
   "empty_column.home": "Din hemma-tidslinje är tom! Besök {public} eller använd sökning för att komma igång och träffa andra användare.",
   "empty_column.home.public_timeline": "den publika tidslinjen",
   "empty_column.list": "Det finns inget i denna lista än. När medlemmar i denna lista lägger till nya statusar kommer de att visas här.",
-  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
-  "empty_column.mutes": "You haven't muted any users yet.",
+  "empty_column.lists": "Du har inga listor än. När skapar en kommer den dyka upp här.",
+  "empty_column.mutes": "Du har ännu inte tystat några användare.",
   "empty_column.notifications": "Du har inga meddelanden än. Interagera med andra för att starta konversationen.",
   "empty_column.public": "Det finns inget här! Skriv något offentligt, eller följ manuellt användarna från andra instanser för att fylla på det",
+  "error.unexpected_crash.explanation": "På grund av en bugg i vår kod eller kompatiblitetsproblem i webbläsaren kan den här sidan inte visas korrekt.",
+  "error.unexpected_crash.next_steps": "Prova att ladda om sidan. Om det inte hjälper kan du försöka använda Mastodon med en annan webbläsare eller app.",
+  "errors.unexpected_crash.copy_stacktrace": "Kopiera stacktrace till urklipp",
+  "errors.unexpected_crash.report_issue": "Rapportera problem",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -142,81 +164,81 @@
   "follow_request.authorize": "Godkänn",
   "follow_request.reject": "Avvisa",
   "getting_started.developers": "Utvecklare",
-  "getting_started.directory": "Profile directory",
-  "getting_started.documentation": "Documentation",
+  "getting_started.directory": "Profilkatalog",
+  "getting_started.documentation": "Dokumentation",
   "getting_started.heading": "Kom igång",
   "getting_started.invite": "Skicka inbjudningar",
   "getting_started.open_source_notice": "Mastodon är programvara med öppen källkod. Du kan bidra eller rapportera problem via GitHub på {github}.",
   "getting_started.security": "Säkerhet",
   "getting_started.terms": "Användarvillkor",
-  "hashtag.column_header.tag_mode.all": "and {additional}",
-  "hashtag.column_header.tag_mode.any": "or {additional}",
-  "hashtag.column_header.tag_mode.none": "without {additional}",
-  "hashtag.column_settings.select.no_options_message": "No suggestions found",
-  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
-  "hashtag.column_settings.tag_mode.all": "All of these",
-  "hashtag.column_settings.tag_mode.any": "Any of these",
+  "hashtag.column_header.tag_mode.all": "och {additional}",
+  "hashtag.column_header.tag_mode.any": "eller {additional}",
+  "hashtag.column_header.tag_mode.none": "utan {additional}",
+  "hashtag.column_settings.select.no_options_message": "Inga förslag hittades",
+  "hashtag.column_settings.select.placeholder": "Ange hashtags …",
+  "hashtag.column_settings.tag_mode.all": "Alla dessa",
+  "hashtag.column_settings.tag_mode.any": "NÃ¥gon av dessa",
   "hashtag.column_settings.tag_mode.none": "Ingen av dessa",
   "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
   "home.column_settings.basic": "Grundläggande",
   "home.column_settings.show_reblogs": "Visa knuffar",
   "home.column_settings.show_replies": "Visa svar",
-  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
-  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
-  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "intervals.full.days": "{number, plural, one {# dag} other {# dagar}}",
+  "intervals.full.hours": "{hours, plural, one {# timme} other {# timmar}}",
+  "intervals.full.minutes": "{minutes, plural, one {1 minut} other {# minuter}}",
   "introduction.federation.action": "Nästa",
-  "introduction.federation.federated.headline": "Federated",
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
-  "introduction.federation.home.headline": "Home",
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
-  "introduction.federation.local.headline": "Local",
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
-  "introduction.interactions.action": "Finish toot-orial!",
-  "introduction.interactions.favourite.headline": "Favourite",
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
-  "introduction.interactions.reblog.headline": "Boost",
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
-  "introduction.interactions.reply.headline": "Reply",
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
-  "introduction.welcome.action": "Let's go!",
-  "introduction.welcome.headline": "First steps",
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
-  "keyboard_shortcuts.back": "att navigera tillbaka",
-  "keyboard_shortcuts.blocked": "to open blocked users list",
-  "keyboard_shortcuts.boost": "att knuffa",
-  "keyboard_shortcuts.column": "att fokusera en status i en av kolumnerna",
-  "keyboard_shortcuts.compose": "att fokusera komponera text fältet",
-  "keyboard_shortcuts.description": "Description",
-  "keyboard_shortcuts.direct": "to open direct messages column",
-  "keyboard_shortcuts.down": "att flytta ner i listan",
-  "keyboard_shortcuts.enter": "to open status",
-  "keyboard_shortcuts.favourite": "att favorisera",
-  "keyboard_shortcuts.favourites": "to open favourites list",
-  "keyboard_shortcuts.federated": "to open federated timeline",
-  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
-  "keyboard_shortcuts.home": "to open home timeline",
-  "keyboard_shortcuts.hotkey": "Snabbvalstangent",
-  "keyboard_shortcuts.legend": "att visa denna översikt",
-  "keyboard_shortcuts.local": "to open local timeline",
-  "keyboard_shortcuts.mention": "att nämna författaren",
-  "keyboard_shortcuts.muted": "to open muted users list",
-  "keyboard_shortcuts.my_profile": "to open your profile",
-  "keyboard_shortcuts.notifications": "to open notifications column",
-  "keyboard_shortcuts.pinned": "to open pinned toots list",
-  "keyboard_shortcuts.profile": "to open author's profile",
-  "keyboard_shortcuts.reply": "att svara",
-  "keyboard_shortcuts.requests": "to open follow requests list",
-  "keyboard_shortcuts.search": "att fokusera sökfältet",
-  "keyboard_shortcuts.start": "to open \"get started\" column",
-  "keyboard_shortcuts.toggle_hidden": "att visa/gömma text bakom CW",
-  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
-  "keyboard_shortcuts.toot": "att börja en helt ny toot",
-  "keyboard_shortcuts.unfocus": "att avfokusera komponera text fält / sökfält",
-  "keyboard_shortcuts.up": "att flytta upp i listan",
+  "introduction.federation.federated.headline": "Federerad",
+  "introduction.federation.federated.text": "Publika inlägg från andra servrar i servernätverket visas i den förenade tidslinjen.",
+  "introduction.federation.home.headline": "Hem",
+  "introduction.federation.home.text": "Inlägg från personer du följer kommer att visas i din hemtidlinje. Du kan följa vem du vill på vald server!",
+  "introduction.federation.local.headline": "Lokal",
+  "introduction.federation.local.text": "Publika inlägg från personer på samma server som du kommer att visas på den lokala tidslinjen.",
+  "introduction.interactions.action": "Slutför introduktionsguide!",
+  "introduction.interactions.favourite.headline": "Favorit",
+  "introduction.interactions.favourite.text": "Du kan spara en tut till senare, och visa författaren att du gillade den genom att göra den till favorit.",
+  "introduction.interactions.reblog.headline": "Knuffa",
+  "introduction.interactions.reblog.text": "Du kan dela andra personers tutar med dina följare genom att knuffa dem.",
+  "introduction.interactions.reply.headline": "Svara",
+  "introduction.interactions.reply.text": "Du kan besvara andra personers och dina egna tutar, vilket kommer att koppla ihop dem i en konversation.",
+  "introduction.welcome.action": "Sätt igång!",
+  "introduction.welcome.headline": "Första stegen",
+  "introduction.welcome.text": "Välkommen till fediverse! Om några ögonblick kommer du kunna sända ut meddelanden och prata med dina vänner över en mängd servrar. Men den här servern, {domain}, är speciell — den är hem åt din profil, så kom ihåg vad den heter.",
+  "keyboard_shortcuts.back": "för att gå bakåt",
+  "keyboard_shortcuts.blocked": "för att öppna listan över blockerade användare",
+  "keyboard_shortcuts.boost": "för att knuffa",
+  "keyboard_shortcuts.column": "för att fokusera en status i en av kolumnerna",
+  "keyboard_shortcuts.compose": "för att fokusera skrivfältet",
+  "keyboard_shortcuts.description": "Beskrivning",
+  "keyboard_shortcuts.direct": "för att öppna Direktmeddelanden",
+  "keyboard_shortcuts.down": "för att flytta nedåt i listan",
+  "keyboard_shortcuts.enter": "för att öppna en status",
+  "keyboard_shortcuts.favourite": "för att sätta som favorit",
+  "keyboard_shortcuts.favourites": "för att öppna Favoriter",
+  "keyboard_shortcuts.federated": "för att öppna Förenad tidslinje",
+  "keyboard_shortcuts.heading": "Tangentbordsgenvägar",
+  "keyboard_shortcuts.home": "för att öppna Hem-tidslinjen",
+  "keyboard_shortcuts.hotkey": "Kommando",
+  "keyboard_shortcuts.legend": "för att visa denna översikt",
+  "keyboard_shortcuts.local": "för att öppna Lokal tidslinje",
+  "keyboard_shortcuts.mention": "för att nämna skaparen",
+  "keyboard_shortcuts.muted": "för att öppna listan över tystade användare",
+  "keyboard_shortcuts.my_profile": "för att öppna din profil",
+  "keyboard_shortcuts.notifications": "för att öppna Meddelanden",
+  "keyboard_shortcuts.pinned": "för att öppna Nålade toots",
+  "keyboard_shortcuts.profile": "för att öppna skaparens profil",
+  "keyboard_shortcuts.reply": "för att svara",
+  "keyboard_shortcuts.requests": "för att öppna Följförfrågningar",
+  "keyboard_shortcuts.search": "för att fokusera sökfältet",
+  "keyboard_shortcuts.start": "för att öppna \"Kom igång\"-kolumnen",
+  "keyboard_shortcuts.toggle_hidden": "för att visa/gömma text bakom CW",
+  "keyboard_shortcuts.toggle_sensitivity": "för att visa/gömma media",
+  "keyboard_shortcuts.toot": "för att påbörja en helt ny toot",
+  "keyboard_shortcuts.unfocus": "för att avfokusera skrivfält/sökfält",
+  "keyboard_shortcuts.up": "för att flytta uppåt i listan",
   "lightbox.close": "Stäng",
   "lightbox.next": "Nästa",
   "lightbox.previous": "Tidigare",
-  "lightbox.view_context": "View context",
+  "lightbox.view_context": "Visa kontext",
   "lists.account.add": "Lägg till i lista",
   "lists.account.remove": "Ta bort från lista",
   "lists.delete": "Radera lista",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Ny listrubrik",
   "lists.search": "Sök bland personer du följer",
   "lists.subheading": "Dina listor",
+  "load_pending": "{count, plural, other {# objekt}}",
   "loading_indicator.label": "Laddar...",
   "media_gallery.toggle_visible": "Växla synlighet",
   "missing_indicator.label": "Hittades inte",
@@ -234,58 +257,59 @@
   "navigation_bar.apps": "Mobilappar",
   "navigation_bar.blocks": "Blockerade användare",
   "navigation_bar.community_timeline": "Lokal tidslinje",
-  "navigation_bar.compose": "Compose new toot",
+  "navigation_bar.compose": "Författa ny toot",
   "navigation_bar.direct": "Direktmeddelanden",
   "navigation_bar.discover": "Upptäck",
   "navigation_bar.domain_blocks": "Dolda domäner",
   "navigation_bar.edit_profile": "Redigera profil",
   "navigation_bar.favourites": "Favoriter",
-  "navigation_bar.filters": "Muted words",
+  "navigation_bar.filters": "Tystade ord",
   "navigation_bar.follow_requests": "Följförfrågningar",
-  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.follows_and_followers": "Följer och följare",
   "navigation_bar.info": "Om denna instans",
-  "navigation_bar.keyboard_shortcuts": "Tangentbordsgenvägar",
+  "navigation_bar.keyboard_shortcuts": "Kortkommandon",
   "navigation_bar.lists": "Listor",
   "navigation_bar.logout": "Logga ut",
   "navigation_bar.mutes": "Tystade användare",
-  "navigation_bar.personal": "Personal",
+  "navigation_bar.personal": "Personligt",
   "navigation_bar.pins": "Nålade inlägg (toots)",
   "navigation_bar.preferences": "Inställningar",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Förenad tidslinje",
   "navigation_bar.security": "Säkerhet",
   "notification.favourite": "{name} favoriserade din status",
   "notification.follow": "{name} följer dig",
   "notification.mention": "{name} nämnde dig",
-  "notification.poll": "A poll you have voted in has ended",
+  "notification.poll": "En omröstning du röstat i har avslutats",
   "notification.reblog": "{name} knuffade din status",
   "notifications.clear": "Rensa meddelanden",
   "notifications.clear_confirmation": "Är du säker på att du vill radera alla dina meddelanden permanent?",
   "notifications.column_settings.alert": "Skrivbordsmeddelanden",
   "notifications.column_settings.favourite": "Favoriter:",
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
-  "notifications.column_settings.filter_bar.show": "Show",
+  "notifications.column_settings.filter_bar.advanced": "Visa alla kategorier",
+  "notifications.column_settings.filter_bar.category": "Snabbfilter",
+  "notifications.column_settings.filter_bar.show": "Visa",
   "notifications.column_settings.follow": "Nya följare:",
   "notifications.column_settings.mention": "Omnämningar:",
-  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.poll": "Omröstningsresultat:",
   "notifications.column_settings.push": "Push meddelanden",
   "notifications.column_settings.reblog": "Knuffar:",
   "notifications.column_settings.show": "Visa i kolumnen",
   "notifications.column_settings.sound": "Spela upp ljud",
-  "notifications.filter.all": "All",
-  "notifications.filter.boosts": "Boosts",
+  "notifications.filter.all": "Alla",
+  "notifications.filter.boosts": "Knuffar",
   "notifications.filter.favourites": "Favoriter",
-  "notifications.filter.follows": "Follows",
-  "notifications.filter.mentions": "Mentions",
-  "notifications.filter.polls": "Poll results",
+  "notifications.filter.follows": "Följer",
+  "notifications.filter.mentions": "Omnämningar",
+  "notifications.filter.polls": "Omröstningsresultat",
   "notifications.group": "{count} aviseringar",
-  "poll.closed": "Closed",
+  "poll.closed": "Stängd",
   "poll.refresh": "Ladda om",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
+  "poll.total_people": "{persons, plural, one {# person} other {# personer}}",
+  "poll.total_votes": "{count, plural, one {1 röst} other {# röster}}",
   "poll.vote": "Rösta",
-  "poll_button.add_poll": "Add a poll",
-  "poll_button.remove_poll": "Remove poll",
+  "poll.voted": "Du röstade för detta svar",
+  "poll_button.add_poll": "Lägg till en omröstning",
+  "poll_button.remove_poll": "Ta bort omröstning",
   "privacy.change": "Justera sekretess",
   "privacy.direct.long": "Skicka endast till nämnda användare",
   "privacy.direct.short": "Direkt",
@@ -295,13 +319,14 @@
   "privacy.public.short": "Publik",
   "privacy.unlisted.long": "Skicka inte till publik tidslinje",
   "privacy.unlisted.short": "Olistad",
+  "refresh": "Läs om",
   "regeneration_indicator.label": "Laddar…",
   "regeneration_indicator.sublabel": "Ditt hemmaflöde förbereds!",
   "relative_time.days": "{number}d",
-  "relative_time.hours": "{number}h",
+  "relative_time.hours": "{number}tim",
   "relative_time.just_now": "nu",
-  "relative_time.minutes": "{number}m",
-  "relative_time.seconds": "{number}s",
+  "relative_time.minutes": "{number}min",
+  "relative_time.seconds": "{number}sek",
   "reply_indicator.cancel": "Ã…ngra",
   "report.forward": "Vidarebefordra till {target}",
   "report.forward_hint": "Kontot är från en annan server. Skicka även en anonymiserad kopia av anmälan dit?",
@@ -317,21 +342,22 @@
   "search_popout.tips.text": "Enkel text returnerar matchande visningsnamn, användarnamn och hashtags",
   "search_popout.tips.user": "användare",
   "search_results.accounts": "Människor",
-  "search_results.hashtags": "Hashtags",
+  "search_results.hashtags": "Hashtaggar",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Att söka toots med deras innehåll är inte möjligt på denna Mastodon-server.",
   "search_results.total": "{count, number} {count, plural, ett {result} andra {results}}",
-  "status.admin_account": "Open moderation interface for @{name}",
-  "status.admin_status": "Open this status in the moderation interface",
-  "status.block": "Block @{name}",
+  "status.admin_account": "Öppet modereringsgränssnitt för @{name}",
+  "status.admin_status": "Öppna denna status i modereringsgränssnittet",
+  "status.block": "Blockera @{name}",
   "status.cancel_reblog_private": "Ta bort knuff",
   "status.cannot_reblog": "Detta inlägg kan inte knuffas",
-  "status.copy": "Copy link to status",
+  "status.copy": "Kopiera länk till status",
   "status.delete": "Ta bort",
-  "status.detailed_status": "Detailed conversation view",
+  "status.detailed_status": "Detaljerad samtalsvy",
   "status.direct": "Direktmeddela @{name}",
   "status.embed": "Bädda in",
   "status.favourite": "Favorit",
-  "status.filtered": "Filtered",
+  "status.filtered": "Filtrerat",
   "status.load_more": "Ladda fler",
   "status.local_only": "This post is only visible by other users of your instance",
   "status.media_hidden": "Media dold",
@@ -343,7 +369,7 @@
   "status.pin": "Fäst i profil",
   "status.pinned": "Fäst toot",
   "status.read_more": "Läs mer",
-  "status.reblog": "Knuff",
+  "status.reblog": "Knuffa",
   "status.reblog_private": "Knuffa till de ursprungliga åhörarna",
   "status.reblogged_by": "{name} knuffade",
   "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
@@ -358,6 +384,7 @@
   "status.show_more": "Visa mer",
   "status.show_more_all": "Visa mer för alla",
   "status.show_thread": "Visa tråd",
+  "status.uncached_media_warning": "Ej tillgängligt",
   "status.unmute_conversation": "Öppna konversation",
   "status.unpin": "Ångra fäst i profil",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -367,20 +394,28 @@
   "tabs_bar.local_timeline": "Lokal",
   "tabs_bar.notifications": "Meddelanden",
   "tabs_bar.search": "Sök",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
+  "time_remaining.days": "{number, plural, one {# dag} other {# dagar}} kvar",
+  "time_remaining.hours": "{hours, plural, one {# timme} other {# timmar}} kvar",
+  "time_remaining.minutes": "{minutes, plural, one {1 minut} other {# minuter}} kvar",
   "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "time_remaining.seconds": "{hours, plural, one {# sekund} other {# sekunder}} kvar",
   "trends.count_by_accounts": "{count} {rawCount, plural, en {person} andra {people}} pratar",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "Ditt utkast kommer att förloras om du lämnar Mastodon.",
   "upload_area.title": "Dra & släpp för att ladda upp",
   "upload_button.label": "Lägg till media",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "Beskriv för synskadade",
-  "upload_form.focus": "Beskär",
+  "upload_form.edit": "Redigera",
   "upload_form.undo": "Ta bort",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Verkställ",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "Laddar upp...",
   "video.close": "Stäng video",
   "video.exit_fullscreen": "Stäng helskärm",
diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json
index f0e6ff2b9574ef951ed65901ebbb4771ec56978a..957cf024c53fe3aa588ac186e88df6a5f01b9913 100644
--- a/app/javascript/mastodon/locales/ta.json
+++ b/app/javascript/mastodon/locales/ta.json
@@ -4,6 +4,7 @@
   "account.block": "Block @{name}",
   "account.block_domain": "எல்லாவற்றையும் மறைக்க {domain}",
   "account.blocked": "தடைமுட்டுகள்",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "நேரடி செய்தி @{name}",
   "account.domain_blocked": "டொமைன் மறைக்கப்பட்டது",
   "account.edit_profile": "சுயவிவரத்தைத் திருத்தவும்",
@@ -15,6 +16,7 @@
   "account.follows.empty": "இந்த பயனர் இதுவரை யாரையும் பின்தொடரவில்லை.",
   "account.follows_you": "நீ பின் தொடர்கிறாய்",
   "account.hide_reblogs": "இருந்து ஊக்கியாக மறை @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "இந்த இணைப்பை உரிமையாளர் சரிபார்க்கப்பட்டது {date}",
   "account.locked_info": "இந்தக் கணக்கு தனியுரிமை நிலை பூட்டப்பட்டுள்ளது. அவர்களைப் பின்தொடர்பவர் யார் என்பதை உரிமையாளர் கைமுறையாக மதிப்பாய்வு செய்கிறார்.",
   "account.media": "Media",
@@ -23,6 +25,7 @@
   "account.mute": "ஊமையான @{name}",
   "account.mute_notifications": "அறிவிப்புகளை முடக்கு @{name}",
   "account.muted": "முடக்கியது",
+  "account.never_active": "Never",
   "account.posts": "Toots",
   "account.posts_with_replies": "Toots மற்றும் பதில்கள்",
   "account.report": "Report @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "பின்தொடராட்",
   "account.unmute": "தடுப்புநீக்கு @{name}",
   "account.unmute_notifications": "அறிவிப்புகளை அகற்றவும் @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "எதிர் பாராத பிழை ஏற்பட்டு விட்டது.",
   "alert.unexpected.title": "அச்சச்சோ!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "நீங்கள் அழுத்தவும் {combo} அடுத்த முறை தவிர்க்கவும்",
   "bundle_column_error.body": "இந்த கூறுகளை ஏற்றும்போது ஏதோ தவறு ஏற்பட்டது.",
   "bundle_column_error.retry": "மீண்டும் முயற்சி செய்",
@@ -47,6 +53,7 @@
   "column.blocks": "தடுக்கப்பட்ட பயனர்கள்",
   "column.community": "உள்ளூர் காலக்கெடு",
   "column.direct": "நேரடி செய்திகள்",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "மறைந்த களங்கள்",
   "column.favourites": "விருப்பத்துக்குகந்த",
   "column.follow_requests": "கோரிக்கைகளை பின்பற்றவும்",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "இந்த பட்டியலில் நிரந்தரமாக நீக்க விரும்புகிறீர்களா?",
   "confirmations.domain_block.confirm": "முழு டொமைனை மறை",
   "confirmations.domain_block.message": "நீங்கள் உண்மையில், நிச்சயமாக நீங்கள் முழு தடுக்க வேண்டும் நிச்சயமாக {domain}? பெரும்பாலான சந்தர்ப்பங்களில் ஒரு சில இலக்குகள் அல்லது மியூட்கள் போதுமானவை மற்றும் சிறந்தவை. எந்த பொது நேரத்திலும் அல்லது உங்கள் அறிவிப்புகளிலும் அந்தக் களத்திலிருந்து உள்ளடக்கத்தை நீங்கள் பார்க்க மாட்டீர்கள். அந்த களத்தில் இருந்து உங்கள் ஆதரவாளர்கள் அகற்றப்படுவார்கள்.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "ஊமையான",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "நிச்சயமாக நீங்கள் முடக்க விரும்புகிறீர்களா {name}?",
   "confirmations.redraft.confirm": "நீக்கு & redraft",
   "confirmations.redraft.message": "நிச்சயமாக இந்த நிலையை நீக்கி, அதை மறுபடியும் உருவாக்க வேண்டுமா? பிடித்தவை மற்றும் ஊக்கங்கள் இழக்கப்படும், மற்றும் அசல் இடுகையில் பதில்கள் அனாதையான இருக்கும்.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "இப்போது பதில், தற்போது நீங்கள் உருவாக்கும் செய்தி மேலெழுதப்படும். நீங்கள் தொடர விரும்புகிறீர்களா?",
   "confirmations.unfollow.confirm": "பின்தொடராட்",
   "confirmations.unfollow.message": "நிச்சயமாக நீங்கள் பின்தொடர விரும்புகிறீர்களா {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "கீழே உள்ள குறியீட்டை நகலெடுப்பதன் மூலம் உங்கள் இணையதளத்தில் இந்த நிலையை உட்பொதிக்கவும்.",
   "embed.preview": "இது போன்ற தோற்றத்தை இங்கு காணலாம்:",
   "emoji_button.activity": "நடவடிக்கை",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "நீங்கள் இதுவரை எந்த பயனர்களையும் முடக்கியிருக்கவில்லை.",
   "empty_column.notifications": "உங்களிடம் எந்த அறிவிப்புகளும் இல்லை. உரையாடலைத் தொடங்க பிறருடன் தொடர்புகொள்ளவும்.",
   "empty_column.public": "இங்கே எதுவும் இல்லை! பகிரங்கமாக ஒன்றை எழுதவும் அல்லது மற்ற நிகழ்வுகளிலிருந்து பயனர்களை அதை நிரப்புவதற்கு கைமுறையாக பின்பற்றவும்",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "புதிய பட்டியல் தலைப்பு",
   "lists.search": "நீங்கள் பின்தொடரும் நபர்கள் மத்தியில் தேடுதல்",
   "lists.subheading": "உங்கள் பட்டியல்கள்",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "ஏற்றுதல்...",
   "media_gallery.toggle_visible": "நிலைமாற்று தெரியும்",
   "missing_indicator.label": "கிடைக்கவில்லை",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "பொருத்தப்பட்டன toots",
   "navigation_bar.preferences": "விருப்பங்கள்",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "கூட்டாட்சி காலக்கெடு",
   "navigation_bar.security": "பத்திரம்",
   "notification.favourite": "{name} ஆர்வம் கொண்டவர், உங்கள் நிலை",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} notifications",
   "poll.closed": "மூடிய",
   "poll.refresh": "பத்துயிர்ப்ப?ட்டு",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} மற்ற {# votes}}",
   "poll.vote": "வாக்களி",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "வாக்கெடுப்பைச் சேர்க்கவும்",
   "poll_button.remove_poll": "வாக்கெடுப்பை அகற்று",
   "privacy.change": "நிலை தனியுரிமை",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Do not show in public timelines",
   "privacy.unlisted.short": "பட்டியலிடப்படாத",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "சுமையேற்றம்…",
   "regeneration_indicator.sublabel": "உங்கள் வீட்டு ஊட்டம் தயார் செய்யப்படுகிறது!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "People",
   "search_results.hashtags": "ஹாஷ்டேக்குகளைச்",
   "search_results.statuses": "Toots",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {result} மற்ற {results}}",
   "status.admin_account": "மிதமான இடைமுகத்தை திறக்க @{name}",
   "status.admin_status": "மிதமான இடைமுகத்தில் இந்த நிலையை திறக்கவும்",
@@ -358,6 +384,7 @@
   "status.show_more": "மேலும் காட்ட",
   "status.show_more_all": "அனைவருக்கும் மேலும் காட்டு",
   "status.show_thread": "நூல் காட்டு",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "ஊமையாக உரையாடல் இல்லை",
   "status.unpin": "சுயவிவரத்திலிருந்து நீக்கவும்",
   "suggestions.dismiss": "பரிந்துரை விலக்க",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "தருணங்கள் மீதமுள்ளன",
   "time_remaining.seconds": "{number, plural, one {# second} மற்ற {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} மற்ற {people}} உரையாடு",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "நீங்கள் வெளியே சென்றால் உங்கள் வரைவு இழக்கப்படும் மஸ்தோடோன்.",
   "upload_area.title": "பதிவேற்ற & இழுக்கவும்",
   "upload_button.label": "மீடியாவைச் சேர்க்கவும் (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "கோப்பு பதிவேற்ற வரம்பு மீறப்பட்டது.",
   "upload_error.poll": "கோப்பு பதிவேற்றம் அனுமதிக்கப்படவில்லை.",
   "upload_form.description": "பார்வையற்ற விவரிக்கவும்",
-  "upload_form.focus": "மாற்றம் முன்னோட்டம்",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "Delete",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "ஏற்றுகிறது ...",
   "video.close": "வீடியோவை மூடு",
   "video.exit_fullscreen": "முழு திரையில் இருந்து வெளியேறவும்",
diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json
index 9fb454fbae58862fd7f6eb81405be0afd3d0ae3c..6e2a3acfd49d9b6aa15f339e1ce8bee1bb94f1dc 100644
--- a/app/javascript/mastodon/locales/te.json
+++ b/app/javascript/mastodon/locales/te.json
@@ -4,6 +4,7 @@
   "account.block": "@{name} ను బ్లాక్ చేయి",
   "account.block_domain": "{domain} నుంచి అన్నీ దాచిపెట్టు",
   "account.blocked": "బ్లాక్ అయినవి",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "@{name}కు నేరుగా సందేశం పంపు",
   "account.domain_blocked": "డొమైన్ దాచిపెట్టబడినది",
   "account.edit_profile": "ప్రొఫైల్ని సవరించండి",
@@ -15,6 +16,7 @@
   "account.follows.empty": "ఈ వినియోగదారి ఇంకా ఎవరినీ అనుసరించడంలేదు.",
   "account.follows_you": "మిమ్మల్ని అనుసరిస్తున్నారు",
   "account.hide_reblogs": "@{name} నుంచి బూస్ట్ లను దాచిపెట్టు",
+  "account.last_status": "Last active",
   "account.link_verified_on": "ఈ లంకె యొక్క యాజమాన్యం {date}న పరీక్షించబడింది",
   "account.locked_info": "ఈ ఖాతా యొక్క గోప్యత స్థితి లాక్ చేయబడి వుంది. ఈ ఖాతాను ఎవరు అనుసరించవచ్చో యజమానే నిర్ణయం తీసుకుంటారు.",
   "account.media": "మీడియా",
@@ -23,6 +25,7 @@
   "account.mute": "@{name}ను మ్యూట్ చెయ్యి",
   "account.mute_notifications": "@{name}నుంచి ప్రకటనలను మ్యూట్ చెయ్యి",
   "account.muted": "మ్యూట్ అయినవి",
+  "account.never_active": "Never",
   "account.posts": "టూట్లు",
   "account.posts_with_replies": "టూట్లు మరియు ప్రత్యుత్తరములు",
   "account.report": "@{name}పై ఫిర్యాదుచేయు",
@@ -35,8 +38,11 @@
   "account.unfollow": "అనుసరించవద్దు",
   "account.unmute": "@{name}పై మ్యూట్ ని తొలగించు",
   "account.unmute_notifications": "@{name} నుంచి ప్రకటనలపై మ్యూట్ ని తొలగించు",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "అనుకోని తప్పు జరిగినది.",
   "alert.unexpected.title": "అయ్యో!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "మీరు తదుపరిసారి దీనిని దాటవేయడానికి {combo} నొక్కవచ్చు",
   "bundle_column_error.body": "ఈ భాగం లోడ్ అవుతున్నప్పుడు ఏదో తప్పు జరిగింది.",
   "bundle_column_error.retry": "మళ్ళీ ప్రయత్నించండి",
@@ -47,6 +53,7 @@
   "column.blocks": "బ్లాక్ చేయబడిన వినియోగదారులు",
   "column.community": "స్థానిక కాలక్రమం",
   "column.direct": "ప్రత్యక్ష సందేశాలు",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "దాచిన డొమైన్లు",
   "column.favourites": "ఇష్టపడినవి",
   "column.follow_requests": "అనుసరించడానికి అభ్యర్ధనలు",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "మీరు ఖచ్చితంగా ఈ జాబితాను శాశ్వతంగా తొలగించాలనుకుంటున్నారా?",
   "confirmations.domain_block.confirm": "మొత్తం డొమైన్ను దాచు",
   "confirmations.domain_block.message": "మీరు నిజంగా నిజంగా మొత్తం {domain} ని బ్లాక్ చేయాలనుకుంటున్నారా? చాలా సందర్భాలలో కొన్ని లక్ష్యంగా ఉన్న బ్లాక్స్ లేదా మ్యూట్స్ సరిపోతాయి మరియు ఉత్తమమైనవి. మీరు ఆ డొమైన్ నుండి కంటెంట్ను ఏ ప్రజా కాలక్రమాలలో లేదా మీ నోటిఫికేషన్లలో చూడలేరు. ఆ డొమైన్ నుండి మీ అనుచరులు తీసివేయబడతారు.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "మ్యూట్ చేయి",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "{name}ను మీరు ఖచ్చితంగా మ్యూట్ చేయాలనుకుంటున్నారా?",
   "confirmations.redraft.confirm": "తొలగించు & తిరగరాయు",
   "confirmations.redraft.message": "మీరు ఖచ్చితంగా ఈ స్టేటస్ ని తొలగించి తిరగరాయాలనుకుంటున్నారా? ఈ స్టేటస్ యొక్క బూస్ట్ లు మరియు ఇష్టాలు పోతాయి,మరియు ప్రత్యుత్తరాలు అనాధలు అయిపోతాయి.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "ఇప్పుడే ప్రత్యుత్తరం ఇస్తే మీరు ప్రస్తుతం వ్రాస్తున్న సందేశం తిరగరాయబడుతుంది. మీరు ఖచ్చితంగా కొనసాగించాలనుకుంటున్నారా?",
   "confirmations.unfollow.confirm": "అనుసరించవద్దు",
   "confirmations.unfollow.message": "{name}ను మీరు ఖచ్చితంగా అనుసరించవద్దనుకుంటున్నారా?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "దిగువ కోడ్ను కాపీ చేయడం ద్వారా మీ వెబ్సైట్లో ఈ స్టేటస్ ని పొందుపరచండి.",
   "embed.preview": "అది ఈ క్రింది విధంగా కనిపిస్తుంది:",
   "emoji_button.activity": "కార్యకలాపాలు",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "మీరు ఇంకా ఏ వినియోగదారులనూ మ్యూట్ చేయలేదు.",
   "empty_column.notifications": "మీకు ఇంకా ఏ నోటిఫికేషన్లు లేవు. సంభాషణను ప్రారంభించడానికి ఇతరులతో ప్రతిస్పందించండి.",
   "empty_column.public": "ఇక్కడ ఏమీ లేదు! దీన్ని నింపడానికి బహిరంగంగా ఏదైనా వ్రాయండి, లేదా ఇతర సేవికల నుండి వినియోగదారులను అనుసరించండి",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "కొత్త జాబితా శీర్షిక",
   "lists.search": "మీరు అనుసరించే వ్యక్తులలో శోధించండి",
   "lists.subheading": "మీ జాబితాలు",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "లోడ్ అవుతోంది...",
   "media_gallery.toggle_visible": "దృశ్యమానతను టోగుల్ చేయండి",
   "missing_indicator.label": "దొరకలేదు",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "వ్యక్తిగతం",
   "navigation_bar.pins": "అతికించిన టూట్లు",
   "navigation_bar.preferences": "ప్రాధాన్యతలు",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "సమాఖ్య కాలక్రమం",
   "navigation_bar.security": "భద్రత",
   "notification.favourite": "{name} మీ స్టేటస్ ను ఇష్టపడ్డారు",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} ప్రకటనలు",
   "poll.closed": "మూసివేయబడినవి",
   "poll.refresh": "నవీకరించు",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "ఎన్నుకోండి",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "ఒక ఎన్నికను చేర్చు",
   "poll_button.remove_poll": "ఎన్నికను తొలగించు",
   "privacy.change": "స్టేటస్ గోప్యతను సర్దుబాటు చేయండి",
@@ -295,6 +319,7 @@
   "privacy.public.short": "ప్రజా",
   "privacy.unlisted.long": "ప్రజా కాలక్రమాలలో చూపించవద్దు",
   "privacy.unlisted.short": "జాబితా చేయబడనిది",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "లోడ్ అవుతోంది…",
   "regeneration_indicator.sublabel": "మీ హోమ్ ఫీడ్ సిద్ధమవుతోంది!",
   "relative_time.days": "{number}d",
@@ -319,6 +344,7 @@
   "search_results.accounts": "వ్యక్తులు",
   "search_results.hashtags": "హాష్ ట్యాగ్లు",
   "search_results.statuses": "టూట్లు",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "status.admin_account": "@{name} కొరకు సమన్వయ వినిమయసీమను తెరువు",
   "status.admin_status": "సమన్వయ వినిమయసీమలో ఈ స్టేటస్ ను తెరవండి",
@@ -358,6 +384,7 @@
   "status.show_more": "ఇంకా చూపించు",
   "status.show_more_all": "అన్నిటికీ ఇంకా చూపించు",
   "status.show_thread": "గొలుసును చూపించు",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "సంభాషణను అన్మ్యూట్ చేయి",
   "status.unpin": "ప్రొఫైల్ నుండి పీకివేయు",
   "suggestions.dismiss": "సూచనను రద్దు చేయి",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "కొన్ని క్షణాలు మాత్రమే మిగిలి ఉన్నాయి",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} మాట్లాడుతున్నారు",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "మీరు మాస్టొడొన్ను వదిలివేస్తే మీ డ్రాఫ్ట్లు పోతాయి.",
   "upload_area.title": "అప్లోడ్ చేయడానికి డ్రాగ్ & డ్రాప్ చేయండి",
   "upload_button.label": "మీడియాను జోడించండి (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "దృష్టి లోపమున్న వారి కోసం వివరించండి",
-  "upload_form.focus": "ప్రివ్యూను మార్చు",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "తొలగించు",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "అప్లోడ్ అవుతోంది...",
   "video.close": "వీడియోని మూసివేయి",
   "video.exit_fullscreen": "పూర్తి స్క్రీన్ నుండి నిష్క్రమించు",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index a854eecab1e2b43989f2cdb57bb4aff1c53df14d..352e92092290a44100291ad59d6252cdb0ca088d 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -4,6 +4,7 @@
   "account.block": "ปิดกั้น @{name}",
   "account.block_domain": "ซ่อนทุกอย่างจาก {domain}",
   "account.blocked": "ปิดกั้นอยู่",
+  "account.cancel_follow_request": "ยกเลิกคำขอติดตาม",
   "account.direct": "ส่งข้อความโดยตรงถึง @{name}",
   "account.domain_blocked": "ซ่อนโดเมนอยู่",
   "account.edit_profile": "แก้ไขโปรไฟล์",
@@ -15,6 +16,7 @@
   "account.follows.empty": "ผู้ใช้นี้ยังไม่ได้ติดตามใคร",
   "account.follows_you": "ติดตามคุณ",
   "account.hide_reblogs": "ซ่อนการดันจาก @{name}",
+  "account.last_status": "ใช้งานล่าสุด",
   "account.link_verified_on": "ตรวจสอบความเป็นเจ้าของของลิงก์นี้เมื่อ {date}",
   "account.locked_info": "มีการตั้งสถานะความเป็นส่วนตัวของบัญชีนี้เป็นล็อคอยู่ เจ้าของตรวจทานผู้ที่สามารถติดตามเขาด้วยตนเอง",
   "account.media": "สื่อ",
@@ -23,6 +25,7 @@
   "account.mute": "ปิดเสียง @{name}",
   "account.mute_notifications": "ปิดเสียงการแจ้งเตือนจาก @{name}",
   "account.muted": "ปิดเสียงอยู่",
+  "account.never_active": "ไม่เลย",
   "account.posts": "โพสต์",
   "account.posts_with_replies": "โพสต์และการตอบกลับ",
   "account.report": "รายงาน @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "เลิกติดตาม",
   "account.unmute": "เลิกปิดเสียง @{name}",
   "account.unmute_notifications": "เลิกปิดเสียงการแจ้งเตือนจาก @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "เกิดข้อผิดพลาดที่ไม่คาดคิด",
   "alert.unexpected.title": "อุปส์!",
+  "autosuggest_hashtag.per_week": "{count} ต่อสัปดาห์",
   "boost_modal.combo": "คุณสามารถกด {combo} เพื่อข้ามสิ่งนี้ในครั้งถัดไป",
   "bundle_column_error.body": "มีบางอย่างผิดพลาดขณะโหลดส่วนประกอบนี้",
   "bundle_column_error.retry": "ลองอีกครั้ง",
@@ -47,6 +53,7 @@
   "column.blocks": "ผู้ใช้ที่ปิดกั้นอยู่",
   "column.community": "เส้นเวลาในเว็บ",
   "column.direct": "ข้อความโดยตรง",
+  "column.directory": "เรียกดูโปรไฟล์",
   "column.domain_blocks": "โดเมนที่ซ่อนอยู่",
   "column.favourites": "รายการโปรด",
   "column.follow_requests": "คำขอติดตาม",
@@ -72,7 +79,7 @@
   "compose_form.lock_disclaimer.lock": "ล็อคอยู่",
   "compose_form.placeholder": "คุณกำลังคิดอะไรอยู่?",
   "compose_form.poll.add_option": "เพิ่มทางเลือก",
-  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.duration": "ระยะเวลาโพล",
   "compose_form.poll.option_placeholder": "ทางเลือก {number}",
   "compose_form.poll.remove_option": "เอาทางเลือกนี้ออก",
   "compose_form.publish": "โพสต์",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "คุณแน่ใจหรือไม่ว่าต้องการลบรายการนี้อย่างถาวร?",
   "confirmations.domain_block.confirm": "ซ่อนทั้งโดเมน",
   "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
+  "confirmations.logout.confirm": "ออกจากระบบ",
+  "confirmations.logout.message": "คุณแน่ใจหรือไม่ว่าต้องการออกจากระบบ?",
   "confirmations.mute.confirm": "ปิดเสียง",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "คุณแน่ใจหรือไม่ว่าต้องการปิดเสียง {name}?",
   "confirmations.redraft.confirm": "ลบแล้วร่างใหม่",
   "confirmations.redraft.message": "คุณแน่ใจหรือไม่ว่าต้องการลบสถานะนี้แล้วร่างใหม่? รายการโปรดและการดันจะหายไป และการตอบกลับโพสต์ดั้งเดิมจะไม่มีความเกี่ยวพัน",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "การตอบกลับตอนนี้จะเขียนทับข้อความที่คุณกำลังเขียน คุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?",
   "confirmations.unfollow.confirm": "เลิกติดตาม",
   "confirmations.unfollow.message": "คุณแน่ใจหรือไม่ว่าต้องการเลิกติดตาม {name}?",
+  "conversation.delete": "ลบการสนทนา",
+  "conversation.mark_as_read": "ทำเครื่องหมายว่าอ่านแล้ว",
+  "conversation.open": "ดูการสนทนา",
+  "conversation.with": "กับ {names}",
+  "directory.federated": "จากเฟดิเวิร์สที่รู้จัก",
+  "directory.local": "จาก {domain} เท่านั้น",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "ฝังสถานะนี้ในเว็บไซต์ของคุณโดยคัดลอกโค้ดด้านล่าง",
   "embed.preview": "นี่คือลักษณะที่จะปรากฏ:",
   "emoji_button.activity": "กิจกรรม",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "คุณยังไม่ได้ปิดเสียงผู้ใช้ใด ๆ",
   "empty_column.notifications": "คุณยังไม่มีการแจ้งเตือนใด ๆ โต้ตอบกับผู้อื่นเพื่อเริ่มการสนทนา",
   "empty_column.public": "ไม่มีสิ่งใดที่นี่! เขียนบางอย่างเป็นสาธารณะ หรือติดตามผู้ใช้จากเซิร์ฟเวอร์อื่น ๆ ด้วยตนเองเพื่อเติมให้เต็ม",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "รายงานปัญหา",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -146,7 +168,7 @@
   "getting_started.documentation": "เอกสารประกอบ",
   "getting_started.heading": "เริ่มต้นใช้งาน",
   "getting_started.invite": "เชิญผู้คน",
-  "getting_started.open_source_notice": "Mastodon เป็นซอฟต์แวร์เปิดต้นฉบับ คุณสามารถมีส่วนร่วมหรือรายงานปัญหาใน GitHub ที่ {github}",
+  "getting_started.open_source_notice": "Mastodon เป็นซอฟต์แวร์เปิดต้นฉบับ คุณสามารถมีส่วนร่วมหรือรายงานปัญหาที่ GitHub ที่ {github}",
   "getting_started.security": "ความปลอดภัย",
   "getting_started.terms": "เงื่อนไขการให้บริการ",
   "hashtag.column_header.tag_mode.all": "และ {additional}",
@@ -161,12 +183,12 @@
   "home.column_settings.basic": "พื้นฐาน",
   "home.column_settings.show_reblogs": "แสดงการดัน",
   "home.column_settings.show_replies": "แสดงการตอบกลับ",
-  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
-  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
-  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "intervals.full.days": "{number, plural, other {# วัน}}",
+  "intervals.full.hours": "{number, plural, other {# ชั่วโมง}}",
+  "intervals.full.minutes": "{number, plural, other {# นาที}}",
   "introduction.federation.action": "ถัดไป",
   "introduction.federation.federated.headline": "ที่ติดต่อกับภายนอก",
-  "introduction.federation.federated.text": "โพสต์สาธารณะจากเซิร์ฟเวอร์อื่น ๆ ของ Fediverse จะปรากฏในเส้นเวลาที่ติดต่อกับภายนอก",
+  "introduction.federation.federated.text": "โพสต์สาธารณะจากเซิร์ฟเวอร์อื่น ๆ ของเฟดิเวิร์สจะปรากฏในเส้นเวลาที่ติดต่อกับภายนอก",
   "introduction.federation.home.headline": "หน้าแรก",
   "introduction.federation.home.text": "โพสต์จากผู้คนที่คุณติดตามจะปรากฏในฟีดหน้าแรกของคุณ คุณสามารถติดตามใครก็ตามในเซิร์ฟเวอร์ใดก็ตาม!",
   "introduction.federation.local.headline": "ในเว็บ",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "ชื่อเรื่องรายการใหม่",
   "lists.search": "ค้นหาในหมู่ผู้คนที่คุณติดตาม",
   "lists.subheading": "รายการของคุณ",
+  "load_pending": "{count, plural, other {# รายการใหม่}}",
   "loading_indicator.label": "กำลังโหลด...",
   "media_gallery.toggle_visible": "เปิด/ปิดการมองเห็น",
   "missing_indicator.label": "ไม่พบ",
@@ -251,13 +274,12 @@
   "navigation_bar.personal": "ส่วนบุคคล",
   "navigation_bar.pins": "โพสต์ที่ปักหมุด",
   "navigation_bar.preferences": "การกำหนดลักษณะ",
-  "navigation_bar.profile_directory": "ไดเรกทอรีโปรไฟล์",
   "navigation_bar.public_timeline": "เส้นเวลาที่ติดต่อกับภายนอก",
   "navigation_bar.security": "ความปลอดภัย",
   "notification.favourite": "{name} ได้ชื่นชอบสถานะของคุณ",
   "notification.follow": "{name} ได้ติดตามคุณ",
   "notification.mention": "{name} ได้กล่าวถึงคุณ",
-  "notification.poll": "A poll you have voted in has ended",
+  "notification.poll": "โพลที่คุณได้ลงคะแนนได้สิ้นสุดแล้ว",
   "notification.reblog": "{name} ได้ดันสถานะของคุณ",
   "notifications.clear": "ล้างการแจ้งเตือน",
   "notifications.clear_confirmation": "คุณแน่ใจหรือไม่ว่าต้องการล้างการแจ้งเตือนทั้งหมดของคุณอย่างถาวร?",
@@ -268,7 +290,7 @@
   "notifications.column_settings.filter_bar.show": "แสดง",
   "notifications.column_settings.follow": "ผู้ติดตามใหม่:",
   "notifications.column_settings.mention": "การกล่าวถึง:",
-  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.poll": "ผลลัพธ์โพล:",
   "notifications.column_settings.push": "การแจ้งเตือนแบบผลัก",
   "notifications.column_settings.reblog": "การดัน:",
   "notifications.column_settings.show": "แสดงในคอลัมน์",
@@ -278,14 +300,16 @@
   "notifications.filter.favourites": "รายการโปรด",
   "notifications.filter.follows": "การติดตาม",
   "notifications.filter.mentions": "การกล่าวถึง",
-  "notifications.filter.polls": "Poll results",
+  "notifications.filter.polls": "ผลลัพธ์โพล",
   "notifications.group": "{count} การแจ้งเตือน",
   "poll.closed": "ปิดแล้ว",
   "poll.refresh": "รีเฟรช",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
-  "poll_button.add_poll": "Add a poll",
-  "poll_button.remove_poll": "Remove poll",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
+  "poll.total_votes": "{count, plural, other {# การลงคะแนน}}",
+  "poll.vote": "ลงคะแนน",
+  "poll.voted": "You voted for this answer",
+  "poll_button.add_poll": "เพิ่มโพล",
+  "poll_button.remove_poll": "เอาโพลออก",
   "privacy.change": "ปรับเปลี่ยนความเป็นส่วนตัวของสถานะ",
   "privacy.direct.long": "โพสต์ไปยังผู้ใช้ที่กล่าวถึงเท่านั้น",
   "privacy.direct.short": "โดยตรง",
@@ -295,6 +319,7 @@
   "privacy.public.short": "สาธารณะ",
   "privacy.unlisted.long": "ไม่โพสต์ไปยังเส้นเวลาสาธารณะ",
   "privacy.unlisted.short": "ไม่อยู่ในรายการ",
+  "refresh": "รีเฟรช",
   "regeneration_indicator.label": "กำลังโหลด…",
   "regeneration_indicator.sublabel": "กำลังเตรียมฟีดหน้าแรกของคุณ!",
   "relative_time.days": "{number} วัน",
@@ -319,7 +344,8 @@
   "search_results.accounts": "ผู้คน",
   "search_results.hashtags": "แฮชแท็ก",
   "search_results.statuses": "โพสต์",
-  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
+  "search_results.total": "{count, number} {count, plural, other {ผลลัพธ์}}",
   "status.admin_account": "เปิดส่วนติดต่อการควบคุมสำหรับ @{name}",
   "status.admin_status": "เปิดสถานะนี้ในส่วนติดต่อการควบคุม",
   "status.block": "ปิดกั้น @{name}",
@@ -358,6 +384,7 @@
   "status.show_more": "แสดงเพิ่มเติม",
   "status.show_more_all": "แสดงเพิ่มเติมทั้งหมด",
   "status.show_thread": "แสดงกระทู้",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "เลิกปิดเสียงการสนทนา",
   "status.unpin": "ถอนหมุดจากโปรไฟล์",
   "suggestions.dismiss": "ยกเลิกข้อเสนอแนะ",
@@ -367,20 +394,28 @@
   "tabs_bar.local_timeline": "ในเว็บ",
   "tabs_bar.notifications": "การแจ้งเตือน",
   "tabs_bar.search": "ค้นหา",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
+  "time_remaining.days": "เหลืออีก {number, plural, other {# วัน}}",
+  "time_remaining.hours": "เหลืออีก {number, plural, other {# ชั่วโมง}}",
+  "time_remaining.minutes": "เหลืออีก {number, plural, other {# นาที}}",
   "time_remaining.moments": "ช่วงเวลาที่เหลือ",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "time_remaining.seconds": "เหลืออีก {number, plural, other {# วินาที}}",
+  "trends.count_by_accounts": "{count} {rawCount, plural, other {คน}}กำลังคุย",
+  "trends.trending_now": "กำลังนิยม",
   "ui.beforeunload": "แบบร่างของคุณจะหายไปหากคุณออกจาก Mastodon",
   "upload_area.title": "ลากแล้วปล่อยเพื่ออัปโหลด",
   "upload_button.label": "เพิ่มสื่อ (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "เกินขีดจำกัดการอัปโหลดไฟล์",
   "upload_error.poll": "ไม่อนุญาตให้อัปโหลดไฟล์กับการลงคะแนน",
   "upload_form.description": "อธิบายสำหรับผู้บกพร่องทางการมองเห็น",
-  "upload_form.focus": "ตัวอย่างการเปลี่ยนแปลง",
+  "upload_form.edit": "แก้ไข",
   "upload_form.undo": "ลบ",
+  "upload_modal.analyzing_picture": "กำลังวิเคราะห์รูปภาพ…",
+  "upload_modal.apply": "นำไปใช้",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "แก้ไขสื่อ",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "ตัวอย่าง ({ratio})",
   "upload_progress.label": "กำลังอัปโหลด...",
   "video.close": "ปิดวิดีโอ",
   "video.exit_fullscreen": "ออกจากเต็มหน้าจอ",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 471ce5d2d5af680c6dbf63a45defdd3080703fb7..4be3eaa15e36c983462ad27d6876b33ada627646 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -1,43 +1,49 @@
 {
-  "account.add_or_remove_from_list": "Listelere ekle veya kaldır",
+  "account.add_or_remove_from_list": "Listeye ekle veya kaldır",
   "account.badges.bot": "Bot",
-  "account.block": "Engelle @{name}",
+  "account.block": "@{name} adlı kişiyi engelle",
   "account.block_domain": "{domain} alanından her şeyi gizle",
   "account.blocked": "EngellenmiÅŸ",
+  "account.cancel_follow_request": "Takip isteÄŸini iptal et",
   "account.direct": "Mesaj gönder : @{name}",
   "account.domain_blocked": "Alan adı gizlendi",
   "account.edit_profile": "Profili düzenle",
   "account.endorse": "Profildeki özellik",
   "account.follow": "Takip et",
-  "account.followers": "Takipçiler",
+  "account.followers": "Takipçi",
   "account.followers.empty": "Henüz kimse bu kullanıcıyı takip etmiyor.",
   "account.follows": "Takip ettikleri",
   "account.follows.empty": "Bu kullanıcı henüz kimseyi takip etmiyor.",
   "account.follows_you": "Seni takip ediyor",
-  "account.hide_reblogs": "@{name} kişisinden boost'ları gizle",
+  "account.hide_reblogs": "@{name} kiÅŸisinin yinelemelerini gizle",
+  "account.last_status": "Son aktivite",
   "account.link_verified_on": "Bu bağlantının mülkiyeti {date} tarihinde kontrol edildi",
-  "account.locked_info": "Bu hesabın gizlilik durumu kilitli olarak ayarlanmış. Sahibi, onu kimin takip edebileceğini elle inceler.",
+  "account.locked_info": "Bu hesabın gizlilik durumu kilitli olarak ayarlanmış. Sahibi, onu kimin takip edebileceğini elle inceliyor.",
   "account.media": "Medya",
   "account.mention": "@{name} kullanıcısından bahset",
   "account.moved_to": "{name} şuraya taşındı:",
-  "account.mute": "@{name} kullanıcısını sessize al",
-  "account.mute_notifications": "@{name} kullanıcısının bildirimlerini kapat",
-  "account.muted": "Sesi kısık",
-  "account.posts": "Gönderiler",
+  "account.mute": "@{name} adlı kişiyi sessize al",
+  "account.mute_notifications": "@{name} adlı kişinin bildirimlerini kapat",
+  "account.muted": "Susturuldu",
+  "account.never_active": "Asla",
+  "account.posts": "Gönderi",
   "account.posts_with_replies": "Gönderiler ve yanıtlar",
-  "account.report": "@{name} kullanıcısını bildir",
-  "account.requested": "Onay bekliyor. Takip isteğini iptal etmek için tıklayın",
+  "account.report": "@{name} adlı kişiyi bildir",
+  "account.requested": "Onay Bekleniyor. Takip isteğini iptal etmek için tıklayın",
   "account.share": "@{name} kullanıcısının profilini paylaş",
-  "account.show_reblogs": "@{name} kullanıcısından boostları göster",
-  "account.unblock": "Engeli kaldır @{name}",
+  "account.show_reblogs": "@{name} kullanıcısının yinelemelerini göster",
+  "account.unblock": "@{name} adlı kişinin engelini kaldır",
   "account.unblock_domain": "{domain} göster",
   "account.unendorse": "Profilde özellik yok",
-  "account.unfollow": "Takipten vazgeç",
-  "account.unmute": "Sesi aç : @{name}",
-  "account.unmute_notifications": "@{name} kullanıcısından bildirimleri aç",
+  "account.unfollow": "Takipi bırak",
+  "account.unmute": "@{name} adlı kişinin sesini aç",
+  "account.unmute_notifications": "@{name} adlı kişinin bildirimlerini aç",
+  "alert.rate_limited.message": "Lütfen sonra tekrar deneyin {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Oran sınırlıdır",
   "alert.unexpected.message": "Beklenmedik bir hata oluÅŸtu.",
   "alert.unexpected.title": "Hay aksi!",
-  "boost_modal.combo": "Bir dahaki sefere {combo} tuÅŸuna basabilirsiniz",
+  "autosuggest_hashtag.per_week": "Haftada {count}",
+  "boost_modal.combo": "Bir daha ki sefere {combo} tuÅŸuna basabilirsiniz",
   "bundle_column_error.body": "Bu bileşen yüklenirken bir şeyler ters gitti.",
   "bundle_column_error.retry": "Tekrar deneyin",
   "bundle_column_error.title": "Ağ hatası",
@@ -47,6 +53,7 @@
   "column.blocks": "Engellenen kullanıcılar",
   "column.community": "Yerel zaman tüneli",
   "column.direct": "DoÄŸrudan mesajlar",
+  "column.directory": "Profillere göz at",
   "column.domain_blocks": "Gizli alan adları",
   "column.favourites": "Favoriler",
   "column.follow_requests": "Takip istekleri",
@@ -77,7 +84,7 @@
   "compose_form.poll.remove_option": "Bu seçimi kaldır",
   "compose_form.publish": "Gönder",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.hide": "Mark media as sensitive",
+  "compose_form.sensitive.hide": "Medyayı hassas olarak işaretle",
   "compose_form.sensitive.marked": "Medya hassas olarak iÅŸaretlendi",
   "compose_form.sensitive.unmarked": "Medya hassas olarak iÅŸaretlenmemiÅŸ",
   "compose_form.spoiler.marked": "Metin uyarının arkasına gizlenir",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "Bu listeyi kalıcı olarak silmek istediğinize emin misiniz?",
   "confirmations.domain_block.confirm": "Alan adının tamamını gizle",
   "confirmations.domain_block.message": "tüm {domain} alan adını engellemek istediğinizden emin misiniz? Genellikle birkaç hedefli engel ve susturma işi görür ve tercih edilir.",
+  "confirmations.logout.confirm": "Çıkış Yap",
+  "confirmations.logout.message": "Çıkış yapmak istediğinize emin misiniz?",
   "confirmations.mute.confirm": "Sessize al",
+  "confirmations.mute.explanation": "Bu onlardan gelen ve onlardan bahseden gönderileri gizleyecek, fakat yine de onların gönderilerinizi görmelerine ve sizi takip etmelerine izin verecektir.",
   "confirmations.mute.message": "{name} kullanıcısını sessize almak istiyor musunuz?",
   "confirmations.redraft.confirm": "Sil ve yeniden tasarla",
   "confirmations.redraft.message": "Bu durumu silip tekrar taslaklaştırmak istediğinizden emin misiniz? Tüm cevapları, boostları ve favorileri kaybedeceksiniz.",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Şimdi yanıtlarken o an oluşturduğunuz mesajın üzerine yazılır. Devam etmek istediğinize emin misiniz?",
   "confirmations.unfollow.confirm": "Takibi kaldır",
   "confirmations.unfollow.message": "{name}'yi takipten çıkarmak istediğinizden emin misiniz?",
+  "conversation.delete": "Konuşmayı sil",
+  "conversation.mark_as_read": "OkunmuÅŸ olarak iÅŸaretle",
+  "conversation.open": "Konuşmayı görüntüle",
+  "conversation.with": "{names} ile",
+  "directory.federated": "Bilinen fediverse'lerden",
+  "directory.local": "Yalnızca {domain} adresinden",
+  "directory.new_arrivals": "Yeni gelenler",
+  "directory.recently_active": "Son zamanlarda aktif",
   "embed.instructions": "Aşağıdaki kodu kopyalayarak bu durumu sitenize gömün.",
   "embed.preview": "İşte nasıl görüneceği:",
   "emoji_button.activity": "Aktivite",
@@ -118,7 +136,7 @@
   "emoji_button.symbols": "Semboller",
   "emoji_button.travel": "Seyahat ve Yerler",
   "empty_column.account_timeline": "Burada hiç gönderi yok!",
-  "empty_column.account_unavailable": "Profile unavailable",
+  "empty_column.account_unavailable": "Profil kullanılamıyor",
   "empty_column.blocks": "Henüz bir kullanıcıyı engellemediniz.",
   "empty_column.community": "Yerel zaman çizelgesi boş. Daha fazla eğlence için herkese açık bir gönderi paylaşın!",
   "empty_column.direct": "Henüz doğrudan mesajınız yok. Bir tane gönderdiğinizde veya aldığınızda burada görünecektir.",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "Henüz hiçbir kullanıcıyı sessize almadınız.",
   "empty_column.notifications": "Henüz hiçbir bildiriminiz yok. Diğer insanlarla sobhet edebilmek için etkileşime geçebilirsiniz.",
   "empty_column.public": "Burada hiçbir şey yok! Herkese açık bir şeyler yazın veya burayı doldurmak için diğer sunuculardaki kullanıcıları takip edin",
+  "error.unexpected_crash.explanation": "Bizim kodumuzdaki bir hatadan ya da tarayıcı uyumluluk sorunundan dolayı, bu sayfa düzgün görüntülenemedi.",
+  "error.unexpected_crash.next_steps": "Sayfayı yenilemeyi deneyin. Eğer bu yardımcı olmazsa, Mastodon'u farklı bir tarayıcı ya da yerel uygulama üzerinden kullanabilirsiniz.",
+  "errors.unexpected_crash.copy_stacktrace": "Yığın izlemeyi (stacktrace) panoya kopyala",
+  "errors.unexpected_crash.report_issue": "Sorun bildir",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -161,20 +183,20 @@
   "home.column_settings.basic": "Temel",
   "home.column_settings.show_reblogs": "Boost edilenleri göster",
   "home.column_settings.show_replies": "Cevapları göster",
-  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
-  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
-  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "intervals.full.days": "{number, plural, one {# gün} other {# gün}}",
+  "intervals.full.hours": "{number, plural, one {# saat} other {# saat}}",
+  "intervals.full.minutes": "{number, plural, one {# dakika} other {# dakika}}",
   "introduction.federation.action": "İleri",
   "introduction.federation.federated.headline": "BirleÅŸik",
   "introduction.federation.federated.text": "Diğer dosya sunucularından gelen genel gönderiler, birleşik zaman çizelgesinde görünecektir.",
   "introduction.federation.home.headline": "Ana sayfa",
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
+  "introduction.federation.home.text": "Takip ettiğiniz kişilerin yayınları ana sayfada gösterilecek. Herhangi bir sunucudaki herkesi takip edebilirsiniz!",
   "introduction.federation.local.headline": "Yerel",
   "introduction.federation.local.text": "Aynı sunucudaki kişilerin gönderileri yerel zaman tünelinde gözükecektir.",
   "introduction.interactions.action": "Öğreticiyi bitirin!",
   "introduction.interactions.favourite.headline": "Favori",
   "introduction.interactions.favourite.text": "Bir gönderiyi favorilerinize alarak sonrası için saklayabilirsiniz ve yazara gönderiyi beğendiğinizi söyleyebilirsiniz.",
-  "introduction.interactions.reblog.headline": "Boost",
+  "introduction.interactions.reblog.headline": "Yinele",
   "introduction.interactions.reblog.text": "Başkalarının gönderilerini boostlayarak kendi takipçilerinizle paylaşabillirsiniz.",
   "introduction.interactions.reply.headline": "Yanıt",
   "introduction.interactions.reply.text": "Başkalarının gönderilerini ve kendi gönderilerinizi yanıtlayabilirsiniz. Bir konuşmada zincirli bir şekilde olacaklardır.",
@@ -209,14 +231,14 @@
   "keyboard_shortcuts.search": "aramaya odaklanmak için",
   "keyboard_shortcuts.start": "\"başlayın\" sütununu açmak için",
   "keyboard_shortcuts.toggle_hidden": "CW'den önceki yazıyı göstermek/gizlemek için",
-  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.toggle_sensitivity": "medyayı göstermek/gizlemek için",
   "keyboard_shortcuts.toot": "yeni bir gönderiye başlamak için",
   "keyboard_shortcuts.unfocus": "aramada bir gönderiye odaklanmamak için",
   "keyboard_shortcuts.up": "listede yukarıya çıkmak için",
   "lightbox.close": "Kapat",
   "lightbox.next": "Sonraki",
   "lightbox.previous": "Önceli",
-  "lightbox.view_context": "View context",
+  "lightbox.view_context": "İçeriği göster",
   "lists.account.add": "Listeye ekle",
   "lists.account.remove": "Listeden kaldır",
   "lists.delete": "Listeyi sil",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "Yeni liste başlığı",
   "lists.search": "Takip ettiğiniz kişiler arasından arayın",
   "lists.subheading": "Listeleriniz",
+  "load_pending": "{count, plural, one {# yeni öğe} other {# yeni öğe}}",
   "loading_indicator.label": "Yükleniyor...",
   "media_gallery.toggle_visible": "Görünürlüğü değiştir",
   "missing_indicator.label": "Bulunamadı",
@@ -242,7 +265,7 @@
   "navigation_bar.favourites": "Favoriler",
   "navigation_bar.filters": "SusturulmuÅŸ kelimeler",
   "navigation_bar.follow_requests": "Takip istekleri",
-  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.follows_and_followers": "Takip edilenler ve takipçiler",
   "navigation_bar.info": "GeniÅŸletilmiÅŸ bilgi",
   "navigation_bar.keyboard_shortcuts": "Klavye kısayolları",
   "navigation_bar.lists": "Listeler",
@@ -251,12 +274,11 @@
   "navigation_bar.personal": "KiÅŸisel",
   "navigation_bar.pins": "Sabitlenmiş gönderiler",
   "navigation_bar.preferences": "Tercihler",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Federe zaman tüneli",
   "navigation_bar.security": "Güvenlik",
   "notification.favourite": "{name} senin durumunu favorilere ekledi",
   "notification.follow": "{name} seni takip ediyor",
-  "notification.mention": "{name} mentioned you",
+  "notification.mention": "{name} senden bahsetti",
   "notification.poll": "Oy verdiÄŸiniz bir anket bitti",
   "notification.reblog": "{name} senin durumunu boost etti",
   "notifications.clear": "Bildirimleri temizle",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} bildirim",
   "poll.closed": "Kapandı",
   "poll.refresh": "Yenile",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
+  "poll.total_people": "{count, plural, one {# kiÅŸi} other {# kiÅŸi}}",
+  "poll.total_votes": "{count, plural, one {# oy} other {# oy}}",
   "poll.vote": "Oy ver",
+  "poll.voted": "Bu cevap için oy kullandınız",
   "poll_button.add_poll": "Bir anket ekleyin",
   "poll_button.remove_poll": "Anket kaldır",
   "privacy.change": "Gönderi gizliliğini ayarla",
@@ -295,6 +319,7 @@
   "privacy.public.short": "Herkese açık",
   "privacy.unlisted.long": "Herkese açık zaman tüneline gönderme",
   "privacy.unlisted.short": "ListelenmemiÅŸ",
+  "refresh": "Yenile",
   "regeneration_indicator.label": "Yükleniyor…",
   "regeneration_indicator.sublabel": "Ev akışınız hazırlanıyor!",
   "relative_time.days": "{number}g",
@@ -311,14 +336,15 @@
   "report.target": "Raporlama",
   "search.placeholder": "Ara",
   "search_popout.search_format": "Gelişmiş arama formatı",
-  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
-  "search_popout.tips.hashtag": "hashtag",
+  "search_popout.tips.full_text": "Basit metin yazdığınız, tercih ettiğiniz, yinelediğiniz veya bunlardan bahsettiğiniz durumların yanı sıra kullanıcı adlarını, görünen adları ve hashtag'leri eşleştiren durumları döndürür.",
+  "search_popout.tips.hashtag": "etiketler",
   "search_popout.tips.status": "durum",
-  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
+  "search_popout.tips.text": "Basit metin, eşleşen görünen adları, kullanıcı adlarını ve hashtag'leri döndürür",
   "search_popout.tips.user": "kullanıcı",
   "search_results.accounts": "İnsanlar",
   "search_results.hashtags": "Hashtagler",
   "search_results.statuses": "Gönderiler",
+  "search_results.statuses_fts_disabled": "Bu Mastodon sunucusunda gönderi içeriğine göre arama etkin değil.",
   "search_results.total": "{count, number} {count, plural, one {sonuç} other {sonuçlar}}",
   "status.admin_account": "@{name} için denetim arayüzünü açın",
   "status.admin_status": "Denetim arayüzünde bu durumu açın",
@@ -344,7 +370,7 @@
   "status.pinned": "Sabitlenmiş gönderi",
   "status.read_more": "Daha dazla oku",
   "status.reblog": "Boostla",
-  "status.reblog_private": "Boost to original audience",
+  "status.reblog_private": "Orjinal kitleye yinele",
   "status.reblogged_by": "{name} boost etti",
   "status.reblogs.empty": "Kimse bu gönderiyi boostlamadı. Biri yaptığında burada gözükecek.",
   "status.redraft": "Sil & tekrar taslakla",
@@ -358,7 +384,8 @@
   "status.show_more": "Daha fazla göster",
   "status.show_more_all": "Hepsi için daha fazla göster",
   "status.show_thread": "Başlığı göster",
-  "status.unmute_conversation": "Unmute conversation",
+  "status.uncached_media_warning": "Mevcut deÄŸil",
+  "status.unmute_conversation": "Sohbeti aç",
   "status.unpin": "Profilden sabitlemeyi kaldır",
   "suggestions.dismiss": "Öneriyi görmezden gel",
   "suggestions.header": "Şuna ilgi duyuyor olabilirsiniz…",
@@ -367,20 +394,28 @@
   "tabs_bar.local_timeline": "Yerel",
   "tabs_bar.notifications": "Bildirimler",
   "tabs_bar.search": "Ara",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
-  "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "time_remaining.days": "{number, plural, one {# gün} other {# gün}} kaldı",
+  "time_remaining.hours": "{number, plural, one {# saat} other {# saat}} kaldı",
+  "time_remaining.minutes": "{number, plural, one {# dakika} other {# dakika}} kaldı",
+  "time_remaining.moments": "Sadece birkaç dakika kaldı",
+  "time_remaining.seconds": "{number, plural, one {# saniye} other {# saniye}} kaldı",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {kiÅŸi} other {kiÅŸi}} konuÅŸuyor",
+  "trends.trending_now": "Şu an popüler",
   "ui.beforeunload": "Mastodon'dan ayrılırsanız taslağınız kaybolacak.",
   "upload_area.title": "Karşıya yükleme için sürükle bırak yapınız",
   "upload_button.label": "Görsel ekle",
   "upload_error.limit": "Dosya yükleme sınırı aşıldı.",
   "upload_error.poll": "Anketlerde dosya yüklemesine izin verilmez.",
-  "upload_form.description": "Describe for the visually impaired",
-  "upload_form.focus": "Kırp",
+  "upload_form.description": "Görme engelliler için açıklama",
+  "upload_form.edit": "Düzenle",
   "upload_form.undo": "Geri al",
+  "upload_modal.analyzing_picture": "Resmi analiz ediyor…",
+  "upload_modal.apply": "Uygula",
+  "upload_modal.description_placeholder": "Pijamalı hasta yağız şoföre çabucak güvendi",
+  "upload_modal.detect_text": "Resimdeki metni algıla",
+  "upload_modal.edit_media": "Medyayı düzenle",
+  "upload_modal.hint": "Her zaman tüm küçük resimlerde görüntülenecek odak noktasını seçmek için ön izlemedeki daireyi tıklayın veya sürükleyin.",
+  "upload_modal.preview_label": "Ön izleme ({ratio})",
   "upload_progress.label": "Yükleniyor...",
   "video.close": "Videoyu kapat",
   "video.exit_fullscreen": "Tam ekrandan çık",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 63c73563e2ce10033f443e05e15325d1d4fd214e..b8be74b008b2ba4b7d2d69f5186723300a5ae77e 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -1,52 +1,59 @@
 {
-  "account.add_or_remove_from_list": "Add or Remove from lists",
+  "account.add_or_remove_from_list": "Додати або видалити зі списків",
   "account.badges.bot": "Бот",
   "account.block": "Заблокувати @{name}",
   "account.block_domain": "Заглушити {domain}",
   "account.blocked": "Заблоковані",
+  "account.cancel_follow_request": "Скасувати запит на підписку",
   "account.direct": "Пряме повідомлення @{name}",
   "account.domain_blocked": "Домен приховано",
   "account.edit_profile": "Редагувати профіль",
   "account.endorse": "Feature on profile",
   "account.follow": "Підписатися",
   "account.followers": "Підписники",
-  "account.followers.empty": "No one follows this user yet.",
+  "account.followers.empty": "Ніхто ще не підписався на цього користувача.",
   "account.follows": "Підписки",
-  "account.follows.empty": "This user doesn't follow anyone yet.",
+  "account.follows.empty": "Цей користувач ще ні на кого не підписався.",
   "account.follows_you": "Підписаний(-а) на Вас",
   "account.hide_reblogs": "Сховати передмухи від @{name}",
-  "account.link_verified_on": "Ownership of this link was checked on {date}",
-  "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
+  "account.last_status": "Крайня активність",
+  "account.link_verified_on": "Права власності на це посилання були перевірені {date}",
+  "account.locked_info": "Статус конфіденційності цього облікового запису встановлено у заблокований. Власник вручну переглядає, хто може за ним стежити.",
   "account.media": "Медіа",
   "account.mention": "Згадати @{name}",
   "account.moved_to": "{name} переїхав на:",
   "account.mute": "Заглушити @{name}",
   "account.mute_notifications": "Не показувати сповіщення від @{name}",
   "account.muted": "Заглушений",
+  "account.never_active": "Ніколи",
   "account.posts": "Дмухи",
   "account.posts_with_replies": "Дмухи й відповіді",
   "account.report": "Поскаржитися на @{name}",
   "account.requested": "Очікує підтвердження. Натисніть щоб відмінити запит",
   "account.share": "Поширити профіль @{name}",
   "account.show_reblogs": "Показати передмухи від @{name}",
-  "account.unblock": "Розблокувати",
+  "account.unblock": "Розблокувати @{name}",
   "account.unblock_domain": "Розблокувати {domain}",
   "account.unendorse": "Don't feature on profile",
   "account.unfollow": "Відписатися",
-  "account.unmute": "Зняти глушення @{name}",
+  "account.unmute": "Зняти глушення з @{name}",
   "account.unmute_notifications": "Показувати сповіщення від @{name}",
+  "alert.rate_limited.message": "Спробуйте ще раз через {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Швидкість обмежена",
   "alert.unexpected.message": "Трапилась неочікувана помилка.",
   "alert.unexpected.title": "Ой!",
+  "autosuggest_hashtag.per_week": "{count} в тиждень",
   "boost_modal.combo": "Ви можете натиснути {combo}, щоб пропустити це наступного разу",
-  "bundle_column_error.body": "Щось пішло не так при завантаженні компоненту.",
-  "bundle_column_error.retry": "Спробуйте ще",
+  "bundle_column_error.body": "Щось пішло не так під час завантаження компоненту.",
+  "bundle_column_error.retry": "Спробуйте ще раз",
   "bundle_column_error.title": "Помилка мережі",
   "bundle_modal_error.close": "Закрити",
-  "bundle_modal_error.message": "Щось пішло не так при завантаженні компоненту.",
-  "bundle_modal_error.retry": "Спробувати ще",
+  "bundle_modal_error.message": "Щось пішло не так під час завантаження компоненту.",
+  "bundle_modal_error.retry": "Спробувати ще раз",
   "column.blocks": "Заблоковані користувачі",
   "column.community": "Локальна стрічка",
   "column.direct": "Прямі повідомлення",
+  "column.directory": "Переглянути профілі",
   "column.domain_blocks": "Приховані домени",
   "column.favourites": "Вподобане",
   "column.follow_requests": "Запити на підписку",
@@ -58,7 +65,7 @@
   "column.public": "Глобальна стрічка",
   "column_back_button.label": "Назад",
   "column_header.hide_settings": "Приховати налаштування",
-  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveLeft_settings": "Змістити колонку вліво",
   "column_header.moveRight_settings": "Змістити колонку вправо",
   "column_header.pin": "Закріпити",
   "column_header.show_settings": "Показати налаштування",
@@ -66,25 +73,25 @@
   "column_subheading.settings": "Налаштування",
   "community.column_settings.media_only": "Тільки медіа",
   "compose_form.direct_message_warning": "Цей дмух буде видимий тільки згаданим користувачам.",
-  "compose_form.direct_message_warning_learn_more": "Дізнатись більше",
-  "compose_form.hashtag_warning": "Цей дмух не буде відображений у жодній стрічці хештеґу, так як він прихований. Тільки публічні дмухи можуть бути знайдені за хештеґом.",
+  "compose_form.direct_message_warning_learn_more": "Дізнатися більше",
+  "compose_form.hashtag_warning": "Цей дмух не буде відображений у жодній стрічці хештеґу, оскільки він прихований. Тільки публічні дмухи можуть бути знайдені за хештеґом.",
   "compose_form.lock_disclaimer": "Ваш акаунт не {locked}. Кожен може підписатися на Вас та бачити Ваші приватні пости.",
   "compose_form.lock_disclaimer.lock": "приватний",
   "compose_form.placeholder": "Що у Вас на думці?",
-  "compose_form.poll.add_option": "Add a choice",
-  "compose_form.poll.duration": "Poll duration",
-  "compose_form.poll.option_placeholder": "Choice {number}",
-  "compose_form.poll.remove_option": "Remove this choice",
+  "compose_form.poll.add_option": "Додати варіант",
+  "compose_form.poll.duration": "Тривалість опитування",
+  "compose_form.poll.option_placeholder": "Варіант {number}",
+  "compose_form.poll.remove_option": "Видалити цей варіант",
   "compose_form.publish": "Дмухнути",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.hide": "Mark media as sensitive",
-  "compose_form.sensitive.marked": "Медіа відмічене <b>несприйнятливим</b>",
-  "compose_form.sensitive.unmarked": "Медіа відмічене сприйнятливим",
-  "compose_form.spoiler.marked": "Текст приховано за попередженням",
+  "compose_form.sensitive.hide": "Позначити медіа як дражливе",
+  "compose_form.sensitive.marked": "Медіа відмічене як дражливе",
+  "compose_form.sensitive.unmarked": "Медіа не відмічене як дражливе",
+  "compose_form.spoiler.marked": "Текст приховано під попередженням",
   "compose_form.spoiler.unmarked": "Текст видимий",
-  "compose_form.spoiler_placeholder": "Попередження щодо прихованого тексту",
+  "compose_form.spoiler_placeholder": "Напишіть своє попередження тут",
   "confirmation_modal.cancel": "Відмінити",
-  "confirmations.block.block_and_report": "Block & Report",
+  "confirmations.block.block_and_report": "Заблокувати та поскаржитися",
   "confirmations.block.confirm": "Заблокувати",
   "confirmations.block.message": "Ви впевнені, що хочете заблокувати {name}?",
   "confirmations.delete.confirm": "Видалити",
@@ -93,15 +100,26 @@
   "confirmations.delete_list.message": "Ви впевнені, що хочете видалити цей список назавжди?",
   "confirmations.domain_block.confirm": "Сховати весь домен",
   "confirmations.domain_block.message": "Ви точно, точно впевнені, що хочете заблокувати весь домен {domain}? У більшості випадків для нормальної роботи краще заблокувати/заглушити лише деяких користувачів. Ви не зможете бачити контент з цього домену у будь-яких стрічках або ваших сповіщеннях. Ваші підписники з цього домену будуть відписані від вас.",
+  "confirmations.logout.confirm": "Вийти",
+  "confirmations.logout.message": "Ви впевнені, що хочете вийти?",
   "confirmations.mute.confirm": "Заглушити",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "Ви впевнені, що хочете заглушити {name}?",
-  "confirmations.redraft.confirm": "Видалити і перестворити",
+  "confirmations.redraft.confirm": "Видалити та перестворити",
   "confirmations.redraft.message": "Ви впевнені, що хочете видалити допис і перестворити його? Ви втратите всі відповіді, передмухи та вподобайки допису.",
-  "confirmations.reply.confirm": "Reply",
-  "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+  "confirmations.reply.confirm": "Відповісти",
+  "confirmations.reply.message": "Поточна відповідь перезапише повідомлення, яке ви зараз пишете. Ви впевнені, що хочете продовжити?",
   "confirmations.unfollow.confirm": "Відписатися",
   "confirmations.unfollow.message": "Ви впевнені, що хочете відписатися від {name}?",
-  "embed.instructions": "Інтегруйте цей статус на вашому вебсайті, скопіювавши код нижче.",
+  "conversation.delete": "Видалити цю бесіду",
+  "conversation.mark_as_read": "Позначити як прочитане",
+  "conversation.open": "Переглянути бесіду",
+  "conversation.with": "With {names}",
+  "directory.federated": "З відомого федесвіту",
+  "directory.local": "Тільки з домену {domain}",
+  "directory.new_arrivals": "Нові надходження",
+  "directory.recently_active": "Активні нещодавно",
+  "embed.instructions": "Вбудуйте цей статус до вашого вебсайту, скопіювавши код нижче.",
   "embed.preview": "Ось як він виглядатиме:",
   "emoji_button.activity": "Заняття",
   "emoji_button.custom": "Особливі",
@@ -113,27 +131,31 @@
   "emoji_button.objects": "Предмети",
   "emoji_button.people": "Люди",
   "emoji_button.recent": "Часто використовувані",
-  "emoji_button.search": "Знайти...",
+  "emoji_button.search": "Шукати...",
   "emoji_button.search_results": "Результати пошуку",
   "emoji_button.symbols": "Символи",
   "emoji_button.travel": "Подорожі",
-  "empty_column.account_timeline": "No toots here!",
-  "empty_column.account_unavailable": "Profile unavailable",
-  "empty_column.blocks": "You haven't blocked any users yet.",
+  "empty_column.account_timeline": "Тут дмухалок немає!",
+  "empty_column.account_unavailable": "Профіль недоступний",
+  "empty_column.blocks": "Ви ще не заблокували жодного користувача.",
   "empty_column.community": "Локальна стрічка пуста. Напишіть щось, щоб розігріти народ!",
   "empty_column.direct": "У вас ще немає прямих повідомлень. Коли ви відправите чи отримаєте якесь, воно з'явиться тут.",
-  "empty_column.domain_blocks": "There are no hidden domains yet.",
-  "empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
-  "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
-  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
+  "empty_column.domain_blocks": "Тут поки немає прихованих доменів.",
+  "empty_column.favourited_statuses": "У вас ще немає вподобаних дмухів. Коли ви щось вподобаєте, воно з'явиться тут.",
+  "empty_column.favourites": "Ніхто ще не вподобав цього дмуху. Коли хтось це зробить, вони з'являться тут.",
+  "empty_column.follow_requests": "У вас ще немає запитів на підписку. Коли ви їх отримаєте, вони з'являться тут.",
   "empty_column.hashtag": "Дописів з цим хештегом поки не існує.",
   "empty_column.home": "Ви поки ні на кого не підписані. Погортайте {public}, або скористуйтесь пошуком, щоб освоїтися та познайомитися з іншими користувачами.",
   "empty_column.home.public_timeline": "публічні стрічки",
   "empty_column.list": "Немає нічого в цьому списку. Коли його учасники дмухнуть нові статуси, вони з'являться тут.",
-  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
-  "empty_column.mutes": "You haven't muted any users yet.",
+  "empty_column.lists": "У вас ще немає списків. Коли ви їх створите, вони з'являться тут.",
+  "empty_column.mutes": "Ви ще не заглушили жодного користувача.",
   "empty_column.notifications": "У вас ще немає сповіщень. Переписуйтесь з іншими користувачами, щоб почати розмову.",
   "empty_column.public": "Тут поки нічого немає! Опублікуйте щось, або вручну підпишіться на користувачів інших інстанцій, щоб заповнити стрічку",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -142,99 +164,100 @@
   "follow_request.authorize": "Авторизувати",
   "follow_request.reject": "Відмовити",
   "getting_started.developers": "Розробникам",
-  "getting_started.directory": "Profile directory",
+  "getting_started.directory": "Каталог профілів",
   "getting_started.documentation": "Документація",
   "getting_started.heading": "Ласкаво просимо",
   "getting_started.invite": "Запросіть людей",
-  "getting_started.open_source_notice": "Mastodon - програма з відкритим вихідним кодом. Ви можете допомогти проекту, або повідомити про проблеми на GitHub за адресою {github}.",
+  "getting_started.open_source_notice": "Mastodon — програма з відкритим сирцевим кодом. Ви можете допомогти проекту, або повідомити про проблеми на GitHub за адресою {github}.",
   "getting_started.security": "Безпека",
   "getting_started.terms": "Умови використання",
-  "hashtag.column_header.tag_mode.all": "and {additional}",
-  "hashtag.column_header.tag_mode.any": "or {additional}",
-  "hashtag.column_header.tag_mode.none": "without {additional}",
-  "hashtag.column_settings.select.no_options_message": "No suggestions found",
-  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
-  "hashtag.column_settings.tag_mode.all": "All of these",
-  "hashtag.column_settings.tag_mode.any": "Any of these",
-  "hashtag.column_settings.tag_mode.none": "None of these",
-  "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
+  "hashtag.column_header.tag_mode.all": "та {additional}",
+  "hashtag.column_header.tag_mode.any": "або {additional}",
+  "hashtag.column_header.tag_mode.none": "без {additional}",
+  "hashtag.column_settings.select.no_options_message": "Не знайдено пропозицій",
+  "hashtag.column_settings.select.placeholder": "Введіть хештеґи…",
+  "hashtag.column_settings.tag_mode.all": "Усі ці",
+  "hashtag.column_settings.tag_mode.any": "Який-небудь з цих",
+  "hashtag.column_settings.tag_mode.none": "Жоден з цих",
+  "hashtag.column_settings.tag_toggle": "Додайте додаткові теґи до цього стовпчика",
   "home.column_settings.basic": "Основні",
   "home.column_settings.show_reblogs": "Показувати передмухи",
   "home.column_settings.show_replies": "Показувати відповіді",
-  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
-  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
-  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
-  "introduction.federation.action": "Next",
-  "introduction.federation.federated.headline": "Federated",
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
-  "introduction.federation.home.headline": "Home",
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
-  "introduction.federation.local.headline": "Local",
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
-  "introduction.interactions.action": "Finish toot-orial!",
-  "introduction.interactions.favourite.headline": "Favourite",
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
-  "introduction.interactions.reblog.headline": "Boost",
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
-  "introduction.interactions.reply.headline": "Reply",
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
-  "introduction.welcome.action": "Let's go!",
-  "introduction.welcome.headline": "First steps",
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
+  "intervals.full.days": "{number, plural, one {# день} few {# дні} other {# днів}}",
+  "intervals.full.hours": "{number, plural, one {# година} few {# години} other {# годин}}",
+  "intervals.full.minutes": "{number, plural, one {# хвилина} few {# хвилини} other {# хвилин}}",
+  "introduction.federation.action": "Далі",
+  "introduction.federation.federated.headline": "Глобальна",
+  "introduction.federation.federated.text": "Публічні пости з інших серверів федіверсу будуть з'являтися у глобальній стрічці.",
+  "introduction.federation.home.headline": "Головна",
+  "introduction.federation.home.text": "Пости від людей, за якими ви слідкуєте, з'являться у Вашій домашній стрічці. Ви можете слідкувати за кожним на будь-якому сервері!",
+  "introduction.federation.local.headline": "Локальна",
+  "introduction.federation.local.text": "Публічні пости від людей на сервері, на якому Ви знаходитесь, будуть з'являтися у локальній стрічці.",
+  "introduction.interactions.action": "Завершити вступ!",
+  "introduction.interactions.favourite.headline": "Улюблене",
+  "introduction.interactions.favourite.text": "Ви можете зберегти дмух на потім і повідомити автора, що він вам сподобався, додавши його в улюблене.",
+  "introduction.interactions.reblog.headline": "Передмухнути",
+  "introduction.interactions.reblog.text": "Ви можете ділитися дмухами інших людей зі своїми підписниками, передмухуючи їх.",
+  "introduction.interactions.reply.headline": "Відповісти",
+  "introduction.interactions.reply.text": "Ви можете відповідати на дмухи інших людей та власні, створюючи ланцюжки розмов.",
+  "introduction.welcome.action": "Поїхали!",
+  "introduction.welcome.headline": "Перші кроки",
+  "introduction.welcome.text": "Вітаємо у федіверсі! Невдовзі ви зможете поширювати повідомлення та спілкуватися зі своїми друзями на розмаїтті серверів. Але цей сервер, {domain}, є особливим — на ньому розміщений ваш профіль, тож запам'ятайте його назву.",
   "keyboard_shortcuts.back": "переходити назад",
-  "keyboard_shortcuts.blocked": "to open blocked users list",
+  "keyboard_shortcuts.blocked": "відкрити список заблокованих користувачів",
   "keyboard_shortcuts.boost": "передмухувати",
   "keyboard_shortcuts.column": "фокусуватися на одній з колонок",
   "keyboard_shortcuts.compose": "фокусуватися на полі введення",
   "keyboard_shortcuts.description": "Опис",
-  "keyboard_shortcuts.direct": "to open direct messages column",
+  "keyboard_shortcuts.direct": "відкрити колонку прямих повідомлень",
   "keyboard_shortcuts.down": "рухатися вниз стрічкою",
   "keyboard_shortcuts.enter": "відкрити статус",
   "keyboard_shortcuts.favourite": "вподобати",
-  "keyboard_shortcuts.favourites": "to open favourites list",
-  "keyboard_shortcuts.federated": "to open federated timeline",
+  "keyboard_shortcuts.favourites": "відкрити список улюбленого",
+  "keyboard_shortcuts.federated": "відкрити глобальну стрічку",
   "keyboard_shortcuts.heading": "Гарячі клавіші",
-  "keyboard_shortcuts.home": "to open home timeline",
+  "keyboard_shortcuts.home": "відкрити домашню стрічку",
   "keyboard_shortcuts.hotkey": "Гаряча клавіша",
   "keyboard_shortcuts.legend": "показати підказку",
-  "keyboard_shortcuts.local": "to open local timeline",
+  "keyboard_shortcuts.local": "відкрити локальну стрічку",
   "keyboard_shortcuts.mention": "згадати автора",
-  "keyboard_shortcuts.muted": "to open muted users list",
-  "keyboard_shortcuts.my_profile": "to open your profile",
-  "keyboard_shortcuts.notifications": "to open notifications column",
-  "keyboard_shortcuts.pinned": "to open pinned toots list",
+  "keyboard_shortcuts.muted": "відкрити список заглушених користувачів",
+  "keyboard_shortcuts.my_profile": "відкрити ваш профіль",
+  "keyboard_shortcuts.notifications": "відкрити колонку сповіщень",
+  "keyboard_shortcuts.pinned": "відкрити список закріплених дмухів",
   "keyboard_shortcuts.profile": "відкрити профіль автора",
   "keyboard_shortcuts.reply": "відповісти",
-  "keyboard_shortcuts.requests": "to open follow requests list",
+  "keyboard_shortcuts.requests": "відкрити список бажаючих підписатися",
   "keyboard_shortcuts.search": "сфокусуватися на пошуку",
-  "keyboard_shortcuts.start": "to open \"get started\" column",
-  "keyboard_shortcuts.toggle_hidden": "показати/приховати прихований текст",
-  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.start": "відкрити колонку \"Початок\"",
+  "keyboard_shortcuts.toggle_hidden": "показати/приховати текст під попередженням",
+  "keyboard_shortcuts.toggle_sensitivity": "показати/приховати медіа",
   "keyboard_shortcuts.toot": "почати писати новий дмух",
   "keyboard_shortcuts.unfocus": "розфокусуватися з нового допису чи пошуку",
   "keyboard_shortcuts.up": "рухатися вверх списком",
   "lightbox.close": "Закрити",
   "lightbox.next": "Далі",
   "lightbox.previous": "Назад",
-  "lightbox.view_context": "View context",
+  "lightbox.view_context": "Переглянути контекст",
   "lists.account.add": "Додати до списку",
   "lists.account.remove": "Видалити зі списку",
   "lists.delete": "Видалити список",
   "lists.edit": "Редагувати список",
-  "lists.edit.submit": "Change title",
+  "lists.edit.submit": "Змінити назву",
   "lists.new.create": "Додати список",
   "lists.new.title_placeholder": "Нова назва списку",
   "lists.search": "Шукати серед людей, на яких ви підписані",
   "lists.subheading": "Ваші списки",
+  "load_pending": "{count, plural, one {# новий елемент} other {# нових елементів}}",
   "loading_indicator.label": "Завантаження...",
   "media_gallery.toggle_visible": "Показати/приховати",
   "missing_indicator.label": "Не знайдено",
   "missing_indicator.sublabel": "Ресурс не знайдений",
   "mute_modal.hide_notifications": "Приховати сповіщення від користувача?",
-  "navigation_bar.apps": "Mobile apps",
+  "navigation_bar.apps": "Мобільні додатки",
   "navigation_bar.blocks": "Заблоковані користувачі",
   "navigation_bar.community_timeline": "Локальна стрічка",
-  "navigation_bar.compose": "Compose new toot",
+  "navigation_bar.compose": "Написати новий дмух",
   "navigation_bar.direct": "Прямі повідомлення",
   "navigation_bar.discover": "Знайти",
   "navigation_bar.domain_blocks": "Приховані домени",
@@ -242,61 +265,63 @@
   "navigation_bar.favourites": "Вподобане",
   "navigation_bar.filters": "Приховані слова",
   "navigation_bar.follow_requests": "Запити на підписку",
-  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.follows_and_followers": "Підписки і підписники",
   "navigation_bar.info": "Про сайт",
-  "navigation_bar.keyboard_shortcuts": "Гарячі клавіши",
+  "navigation_bar.keyboard_shortcuts": "Гарячі клавіші",
   "navigation_bar.lists": "Списки",
   "navigation_bar.logout": "Вийти",
   "navigation_bar.mutes": "Заглушені користувачі",
   "navigation_bar.personal": "Особисте",
   "navigation_bar.pins": "Закріплені дмухи",
   "navigation_bar.preferences": "Налаштування",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Глобальна стрічка",
   "navigation_bar.security": "Безпека",
   "notification.favourite": "{name} вподобав(-ла) ваш допис",
   "notification.follow": "{name} підписався(-лась) на Вас",
   "notification.mention": "{name} згадав(-ла) Вас",
-  "notification.poll": "A poll you have voted in has ended",
+  "notification.poll": "Опитування, у якому ви голосували, закінчилося",
   "notification.reblog": "{name} передмухнув(-ла) Ваш допис",
   "notifications.clear": "Очистити сповіщення",
   "notifications.clear_confirmation": "Ви впевнені, що хочете назавжди видалити всі сповіщеня?",
   "notifications.column_settings.alert": "Сповіщення на комп'ютері",
   "notifications.column_settings.favourite": "Вподобане:",
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
-  "notifications.column_settings.filter_bar.show": "Show",
+  "notifications.column_settings.filter_bar.advanced": "Показати всі категорії",
+  "notifications.column_settings.filter_bar.category": "Панель швидкого фільтру",
+  "notifications.column_settings.filter_bar.show": "Показати",
   "notifications.column_settings.follow": "Нові підписники:",
   "notifications.column_settings.mention": "Згадки:",
-  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.poll": "Результати опитування:",
   "notifications.column_settings.push": "Push-сповіщення",
   "notifications.column_settings.reblog": "Передмухи:",
   "notifications.column_settings.show": "Показати в колонці",
   "notifications.column_settings.sound": "Відтворювати звуки",
-  "notifications.filter.all": "All",
-  "notifications.filter.boosts": "Boosts",
-  "notifications.filter.favourites": "Favourites",
-  "notifications.filter.follows": "Follows",
-  "notifications.filter.mentions": "Mentions",
-  "notifications.filter.polls": "Poll results",
+  "notifications.filter.all": "Усі",
+  "notifications.filter.boosts": "Передмухи",
+  "notifications.filter.favourites": "Улюблені",
+  "notifications.filter.follows": "Підписки",
+  "notifications.filter.mentions": "Згадки",
+  "notifications.filter.polls": "Результати опитування",
   "notifications.group": "{count} сповіщень",
-  "poll.closed": "Closed",
-  "poll.refresh": "Refresh",
-  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
-  "poll.vote": "Vote",
-  "poll_button.add_poll": "Add a poll",
-  "poll_button.remove_poll": "Remove poll",
+  "poll.closed": "Закрито",
+  "poll.refresh": "Оновити",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
+  "poll.total_votes": "{count, plural, one {# голос} few {# голоси} many {# голосів} other {# голосів}}",
+  "poll.vote": "Проголосувати",
+  "poll.voted": "You voted for this answer",
+  "poll_button.add_poll": "Додати опитування",
+  "poll_button.remove_poll": "Видалити опитування",
   "privacy.change": "Змінити видимість допису",
   "privacy.direct.long": "Показати тільки згаданим користувачам",
-  "privacy.direct.short": "Направлений",
+  "privacy.direct.short": "Особисто",
   "privacy.private.long": "Показати тільки підписникам",
   "privacy.private.short": "Тільки для підписників",
   "privacy.public.long": "Показувати у публічних стрічках",
-  "privacy.public.short": "Публічний",
+  "privacy.public.short": "Публічно",
   "privacy.unlisted.long": "Не показувати у публічних стрічках",
   "privacy.unlisted.short": "Прихований",
+  "refresh": "Оновити",
   "regeneration_indicator.label": "Завантаження…",
-  "regeneration_indicator.sublabel": "Ваша домашня стрічка готова!",
+  "regeneration_indicator.sublabel": "Ваша домашня стрічка готується!",
   "relative_time.days": "{number}д",
   "relative_time.hours": "{number}г",
   "relative_time.just_now": "щойно",
@@ -308,82 +333,92 @@
   "report.hint": "Скаргу буде відправлено модераторам Вашого сайту. Ви можете надати їм пояснення, чому ви скаржитесь на аккаунт нижче:",
   "report.placeholder": "Додаткові коментарі",
   "report.submit": "Відправити",
-  "report.target": "Скаржимося на",
+  "report.target": "Скаржимося на {target}",
   "search.placeholder": "Пошук",
-  "search_popout.search_format": "Advanced search format",
-  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
-  "search_popout.tips.hashtag": "hashtag",
-  "search_popout.tips.status": "status",
-  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
-  "search_popout.tips.user": "user",
-  "search_results.accounts": "People",
-  "search_results.hashtags": "Hashtags",
-  "search_results.statuses": "Toots",
+  "search_popout.search_format": "Розширений формат пошуку",
+  "search_popout.tips.full_text": "Пошук за текстом знаходить статуси, які ви написали, вподобали, передмухнули, або в яких вас згадували. Також він знаходить імена користувачів, реальні імена та хештеґи.",
+  "search_popout.tips.hashtag": "хештеґ",
+  "search_popout.tips.status": "статус",
+  "search_popout.tips.text": "Пошук за текстом знаходить імена користувачів, реальні імена та хештеґи",
+  "search_popout.tips.user": "користувач",
+  "search_results.accounts": "Люди",
+  "search_results.hashtags": "Хештеґи",
+  "search_results.statuses": "Дмухів",
+  "search_results.statuses_fts_disabled": "Пошук дмухів за вмістом недоступний на цьому сервері Mastodon.",
   "search_results.total": "{count, number} {count, plural, one {результат} few {результати} many {результатів} other {результатів}}",
-  "status.admin_account": "Open moderation interface for @{name}",
-  "status.admin_status": "Open this status in the moderation interface",
-  "status.block": "Block @{name}",
-  "status.cancel_reblog_private": "Unboost",
+  "status.admin_account": "Відкрити інтерфейс модерації для @{name}",
+  "status.admin_status": "Відкрити цей статус в інтерфейсі модерації",
+  "status.block": "Заблокувати @{name}",
+  "status.cancel_reblog_private": "Відмінити передмухання",
   "status.cannot_reblog": "Цей допис не може бути передмухнутий",
-  "status.copy": "Copy link to status",
+  "status.copy": "Копіювати посилання до статусу",
   "status.delete": "Видалити",
-  "status.detailed_status": "Detailed conversation view",
-  "status.direct": "Direct message @{name}",
-  "status.embed": "Embed",
+  "status.detailed_status": "Детальний вигляд бесіди",
+  "status.direct": "Пряме повідомлення до @{name}",
+  "status.embed": "Вбудувати",
   "status.favourite": "Подобається",
-  "status.filtered": "Filtered",
+  "status.filtered": "Відфільтровано",
   "status.load_more": "Завантажити більше",
   "status.local_only": "This post is only visible by other users of your instance",
   "status.media_hidden": "Медіаконтент приховано",
-  "status.mention": "Згадати",
-  "status.more": "More",
-  "status.mute": "Mute @{name}",
+  "status.mention": "Згадати @{name}",
+  "status.more": "Більше",
+  "status.mute": "Заглушити @{name}",
   "status.mute_conversation": "Заглушити діалог",
   "status.open": "Розгорнути допис",
-  "status.pin": "Pin on profile",
-  "status.pinned": "Pinned toot",
-  "status.read_more": "Read more",
+  "status.pin": "Закріпити у профілі",
+  "status.pinned": "Закріплений дмух",
+  "status.read_more": "Дізнатися більше",
   "status.reblog": "Передмухнути",
-  "status.reblog_private": "Boost to original audience",
+  "status.reblog_private": "Передмухнути для початкової аудиторії",
   "status.reblogged_by": "{name} передмухнув(-ла)",
-  "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
-  "status.redraft": "Delete & re-draft",
+  "status.reblogs.empty": "Ніхто ще не передмухнув цього дмуху. Коли якісь користувачі це зроблять, вони будуть відображені тут.",
+  "status.redraft": "Видалити та перестворити",
   "status.reply": "Відповісти",
-  "status.replyAll": "Відповісти на тред",
-  "status.report": "Поскаржитися",
-  "status.sensitive_warning": "Непристойний зміст",
-  "status.share": "Share",
+  "status.replyAll": "Відповісти на ланцюжок",
+  "status.report": "Поскаржитися на @{name}",
+  "status.sensitive_warning": "Дражливий зміст",
+  "status.share": "Поділитися",
   "status.show_less": "Згорнути",
   "status.show_less_all": "Show less for all",
   "status.show_more": "Розгорнути",
   "status.show_more_all": "Show more for all",
-  "status.show_thread": "Show thread",
+  "status.show_thread": "Показати ланцюжок",
+  "status.uncached_media_warning": "Недоступно",
   "status.unmute_conversation": "Зняти глушення з діалогу",
-  "status.unpin": "Unpin from profile",
-  "suggestions.dismiss": "Dismiss suggestion",
-  "suggestions.header": "You might be interested in…",
+  "status.unpin": "Відкріпити від профілю",
+  "suggestions.dismiss": "Відхилити пропозицію",
+  "suggestions.header": "Вас може зацікавити…",
   "tabs_bar.federated_timeline": "Глобальна",
   "tabs_bar.home": "Головна",
   "tabs_bar.local_timeline": "Локальна",
   "tabs_bar.notifications": "Сповіщення",
   "tabs_bar.search": "Пошук",
-  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
-  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
-  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
+  "time_remaining.days": "{number, plural, one {# день} few {# дні} other {# днів}}",
+  "time_remaining.hours": "{number, plural, one {# година} few {# години} other {# годин}}",
+  "time_remaining.minutes": "{number, plural, one {# хвилина} few {# хвилини} other {# хвилин}}",
   "time_remaining.moments": "Moments remaining",
-  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "time_remaining.seconds": "{number, plural, one {# секунда} few {# секунди} other {# секунд}}",
+  "trends.count_by_accounts": "{count} {rawCount, plural, one {людина} few {людини} many {людей} other {людей}} обговорюють це",
+  "trends.trending_now": "Актуальні",
   "ui.beforeunload": "Вашу чернетку буде втрачено, якщо ви покинете Mastodon.",
   "upload_area.title": "Перетягніть сюди, щоб завантажити",
-  "upload_button.label": "Додати медіаконтент",
-  "upload_error.limit": "File upload limit exceeded.",
-  "upload_error.poll": "File upload not allowed with polls.",
+  "upload_button.label": "Додати медіаконтент ({formats})",
+  "upload_error.limit": "Ліміт завантаження файлів перевищено.",
+  "upload_error.poll": "Не можна завантажувати файли до опитувань.",
   "upload_form.description": "Опишіть для людей з вадами зору",
-  "upload_form.focus": "Обрізати",
+  "upload_form.edit": "Змінити",
   "upload_form.undo": "Видалити",
+  "upload_modal.analyzing_picture": "Аналізуємо малюнок…",
+  "upload_modal.apply": "Застосувати",
+  "upload_modal.description_placeholder": "Щурячий бугай із їжаком-харцизом в'ючись підписали ґешефт у єнах",
+  "upload_modal.detect_text": "Виявити текст на малюнку",
+  "upload_modal.edit_media": "Редагувати медіа",
+  "upload_modal.hint": "Клацніть або перетягніть коло на превью, щоб обрати точку, яку буде завжди видно на мініатюрах.",
+  "upload_modal.preview_label": "Переглянути ({ratio})",
   "upload_progress.label": "Завантаження...",
   "video.close": "Закрити відео",
-  "video.exit_fullscreen": "Вийти з повного екрану",
+  "video.exit_fullscreen": "Вийти з повноекранного режиму",
   "video.expand": "Розширити відео",
   "video.fullscreen": "На весь екран",
   "video.hide": "Приховати відео",
diff --git a/app/javascript/mastodon/locales/whitelist_pt.json b/app/javascript/mastodon/locales/whitelist_br.json
similarity index 100%
rename from app/javascript/mastodon/locales/whitelist_pt.json
rename to app/javascript/mastodon/locales/whitelist_br.json
diff --git a/app/javascript/mastodon/locales/whitelist_es-AR.json b/app/javascript/mastodon/locales/whitelist_es-AR.json
new file mode 100644
index 0000000000000000000000000000000000000000..0d4f101c7a37a4c875e6999bee1a287fdb733380
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_es-AR.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/app/javascript/mastodon/locales/whitelist_et.json b/app/javascript/mastodon/locales/whitelist_et.json
new file mode 100644
index 0000000000000000000000000000000000000000..0d4f101c7a37a4c875e6999bee1a287fdb733380
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_et.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/app/javascript/mastodon/locales/whitelist_ga.json b/app/javascript/mastodon/locales/whitelist_ga.json
new file mode 100644
index 0000000000000000000000000000000000000000..0d4f101c7a37a4c875e6999bee1a287fdb733380
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_ga.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/app/javascript/mastodon/locales/whitelist_mk.json b/app/javascript/mastodon/locales/whitelist_mk.json
new file mode 100644
index 0000000000000000000000000000000000000000..0d4f101c7a37a4c875e6999bee1a287fdb733380
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_mk.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/app/javascript/mastodon/locales/whitelist_nn.json b/app/javascript/mastodon/locales/whitelist_nn.json
new file mode 100644
index 0000000000000000000000000000000000000000..0d4f101c7a37a4c875e6999bee1a287fdb733380
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_nn.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/app/javascript/mastodon/locales/whitelist_pt-PT.json b/app/javascript/mastodon/locales/whitelist_pt-PT.json
new file mode 100644
index 0000000000000000000000000000000000000000..0d4f101c7a37a4c875e6999bee1a287fdb733380
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_pt-PT.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index 6b9f9a0f8091948846a7827a2c853d681d413634..0e53f8ffad9930c2521bf6db635498d941973ebd 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -4,6 +4,7 @@
   "account.block": "屏蔽 @{name}",
   "account.block_domain": "隐藏来自 {domain} 的内容",
   "account.blocked": "已屏蔽",
+  "account.cancel_follow_request": "取消关注请求",
   "account.direct": "发送私信给 @{name}",
   "account.domain_blocked": "网站已屏蔽",
   "account.edit_profile": "修改个人资料",
@@ -12,9 +13,10 @@
   "account.followers": "关注者",
   "account.followers.empty": "目前无人关注此用户。",
   "account.follows": "正在关注",
-  "account.follows.empty": "此用户目前没有关注任何人。",
+  "account.follows.empty": "此用户目前尚未关注任何人。",
   "account.follows_you": "关注了你",
   "account.hide_reblogs": "隐藏来自 @{name} 的转嘟",
+  "account.last_status": "Last active",
   "account.link_verified_on": "此链接的所有权已在 {date} 检查",
   "account.locked_info": "此账户已锁嘟。账户的主人会手动审核关注者。",
   "account.media": "媒体",
@@ -23,6 +25,7 @@
   "account.mute": "隐藏 @{name}",
   "account.mute_notifications": "隐藏来自 @{name} 的通知",
   "account.muted": "已隐藏",
+  "account.never_active": "Never",
   "account.posts": "嘟文",
   "account.posts_with_replies": "嘟文和回复",
   "account.report": "举报 @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "取消关注",
   "account.unmute": "不再隐藏 @{name}",
   "account.unmute_notifications": "不再隐藏来自 @{name} 的通知",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "发生了意外错误。",
   "alert.unexpected.title": "哎呀!",
+  "autosuggest_hashtag.per_week": "每星期 {count} 条",
   "boost_modal.combo": "下次按住 {combo} 即可跳过此提示",
   "bundle_column_error.body": "载入这个组件时发生了错误。",
   "bundle_column_error.retry": "重试",
@@ -47,8 +53,9 @@
   "column.blocks": "已屏蔽的用户",
   "column.community": "本站时间轴",
   "column.direct": "私信",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "已屏蔽的网站",
-  "column.favourites": "收藏过的嘟文",
+  "column.favourites": "收藏",
   "column.follow_requests": "关注请求",
   "column.home": "主页",
   "column.lists": "列表",
@@ -92,15 +99,26 @@
   "confirmations.delete_list.confirm": "删除",
   "confirmations.delete_list.message": "你确定要永久删除这个列表吗?",
   "confirmations.domain_block.confirm": "隐藏整个网站的内容",
-  "confirmations.domain_block.message": "你真的确定要隐藏所有来自 {domain} 的内容吗?多数情况下,屏蔽或隐藏几个特定的用户应该就能满足你的需要了。来自该网站的内容将不再出现在你的公共时间轴或通知列表里。来自该网站的关注者将会被移除。",
+  "confirmations.domain_block.message": "你真的确定要隐藏所有来自 {domain} 的内容吗?多数情况下,屏蔽或隐藏几个特定的用户就已经足够了。来自该网站的内容将不再出现在你的任何公共时间轴或通知列表里。来自该网站的关注者将会被移除。",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "隐藏",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "你确定要隐藏 {name} 吗?",
   "confirmations.redraft.confirm": "删除并重新编辑",
-  "confirmations.redraft.message": "你确定要删除这条嘟文并重新编辑它吗?所有相关的转嘟和收藏都会被清除,回复将会被孤立。",
+  "confirmations.redraft.message": "你确定要删除这条嘟文并重新编辑它吗?所有相关的转嘟和收藏都会被清除,回复将会失去关联。",
   "confirmations.reply.confirm": "回复",
   "confirmations.reply.message": "回复此消息将会覆盖当前正在编辑的信息。确定继续吗?",
   "confirmations.unfollow.confirm": "取消关注",
   "confirmations.unfollow.message": "你确定要取消关注 {name} 吗?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "要在你的网站上嵌入这条嘟文,请复制以下代码。",
   "embed.preview": "它会像这样显示出来:",
   "emoji_button.activity": "活动",
@@ -120,20 +138,24 @@
   "empty_column.account_timeline": "这里没有嘟文!",
   "empty_column.account_unavailable": "个人资料不可用",
   "empty_column.blocks": "你目前没有屏蔽任何用户。",
-  "empty_column.community": "本站时间轴暂时没有内容,快嘟几个来抢头香啊!",
+  "empty_column.community": "本站时间轴暂时没有内容,快写点什么让它动起来吧!",
   "empty_column.direct": "你还没有使用过私信。当你发出或者收到私信时,它会在这里显示。",
   "empty_column.domain_blocks": "目前没有被隐藏的站点。",
   "empty_column.favourited_statuses": "你还没有收藏过任何嘟文。收藏过的嘟文会显示在这里。",
-  "empty_column.favourites": "没人收藏过这条嘟文。假如有人收藏了,就会显示在这里。",
+  "empty_column.favourites": "没有人收藏过这条嘟文。如果有人收藏了,就会显示在这里。",
   "empty_column.follow_requests": "你没有收到新的关注请求。收到了之后就会显示在这里。",
   "empty_column.hashtag": "这个话题标签下暂时没有内容。",
-  "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
+  "empty_column.home": "你还没有关注任何用户。快看看{public},向其他人问个好吧。",
   "empty_column.home.public_timeline": "公共时间轴",
   "empty_column.list": "这个列表中暂时没有内容。列表中用户所发送的的新嘟文将会在这里显示。",
-  "empty_column.lists": "你没有创建过列表。你创建的列表会在这里显示。",
+  "empty_column.lists": "你还没有创建过列表。你创建的列表会在这里显示。",
   "empty_column.mutes": "你没有隐藏任何用户。",
-  "empty_column.notifications": "你还没有收到过任何通知,快向其他用户搭讪吧。",
+  "empty_column.notifications": "你还没有收到过任何通知,快和其他用户互动吧。",
   "empty_column.public": "这里什么都没有!写一些公开的嘟文,或者关注其他服务器的用户后,这里就会有嘟文出现了",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -142,11 +164,11 @@
   "follow_request.authorize": "同意",
   "follow_request.reject": "拒绝",
   "getting_started.developers": "开发",
-  "getting_started.directory": "用户资料目录",
+  "getting_started.directory": "用户目录",
   "getting_started.documentation": "文档",
   "getting_started.heading": "开始使用",
   "getting_started.invite": "邀请用户",
-  "getting_started.open_source_notice": "Mastodon 是一个开源软件。欢迎前往 GitHub({github})贡献代码或反馈问题。",
+  "getting_started.open_source_notice": "Mastodon 是开源软件。欢迎前往 GitHub({github})贡献代码或反馈问题。",
   "getting_started.security": "帐户安全",
   "getting_started.terms": "使用条款",
   "hashtag.column_header.tag_mode.all": "以及 {additional}",
@@ -166,9 +188,9 @@
   "intervals.full.minutes": "{number} 分钟",
   "introduction.federation.action": "下一步",
   "introduction.federation.federated.headline": "跨站",
-  "introduction.federation.federated.text": "其他跨站服务器的公共动态会显示在跨站时间线中。",
+  "introduction.federation.federated.text": "联邦宇宙中其他服务器的公开嘟文会显示在跨站时间轴中。",
   "introduction.federation.home.headline": "主页",
-  "introduction.federation.home.text": "你所关注的用户的动态会显示在主页里。你可以关注任何服务器上的任何人!",
+  "introduction.federation.home.text": "你所关注的用户的动态会显示在主页里。你可以关注任何服务器上的任何人!",
   "introduction.federation.local.headline": "本站",
   "introduction.federation.local.text": "你所关注的用户的动态会显示在主页里,你可以关注任何服务器上的任何人。",
   "introduction.interactions.action": "教程结束!",
@@ -177,10 +199,10 @@
   "introduction.interactions.reblog.headline": "转嘟",
   "introduction.interactions.reblog.text": "通过转嘟,你可以向你的关注者分享其他人的嘟文。",
   "introduction.interactions.reply.headline": "回复",
-  "introduction.interactions.reply.text": "你可以向其他人回复,这些回复会像对话一样串在一起。",
+  "introduction.interactions.reply.text": "你可以回复其他嘟文,这些回复会像对话一样关联在一起。",
   "introduction.welcome.action": "让我们开始吧!",
   "introduction.welcome.headline": "首先",
-  "introduction.welcome.text": "欢迎来到联邦!稍后,您将可以广播消息并和您的朋友交流,这些消息将穿越于联邦中的各式服务器。但是这台服务器,{domain},是特殊的——它保存了你的个人资料,所以请记住它的名字。",
+  "introduction.welcome.text": "欢迎来到联邦宇宙!很快,您就可以发布信息并和您的朋友交流,这些消息将发送到联邦中的各个服务器中。但是这台服务器,{domain},是特殊的——它保存了你的个人资料,所以请记住它的名字。",
   "keyboard_shortcuts.back": "返回上一页",
   "keyboard_shortcuts.blocked": "打开被屏蔽用户列表",
   "keyboard_shortcuts.boost": "转嘟",
@@ -199,9 +221,9 @@
   "keyboard_shortcuts.legend": "显示此列表",
   "keyboard_shortcuts.local": "打开本站时间轴",
   "keyboard_shortcuts.mention": "提及嘟文作者",
-  "keyboard_shortcuts.muted": "打开屏蔽用户列表",
+  "keyboard_shortcuts.muted": "打开隐藏用户列表",
   "keyboard_shortcuts.my_profile": "打开你的个人资料",
-  "keyboard_shortcuts.notifications": "打卡通知栏",
+  "keyboard_shortcuts.notifications": "打开通知栏",
   "keyboard_shortcuts.pinned": "打开置顶嘟文列表",
   "keyboard_shortcuts.profile": "打开作者的个人资料",
   "keyboard_shortcuts.reply": "回复嘟文",
@@ -218,7 +240,7 @@
   "lightbox.previous": "上一个",
   "lightbox.view_context": "查看上下文",
   "lists.account.add": "添加到列表",
-  "lists.account.remove": "从列表中删除",
+  "lists.account.remove": "从列表中移除",
   "lists.delete": "删除列表",
   "lists.edit": "编辑列表",
   "lists.edit.submit": "更改标题",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "新列表的标题",
   "lists.search": "搜索你关注的人",
   "lists.subheading": "你的列表",
+  "load_pending": "{count} 项",
   "loading_indicator.label": "加载中……",
   "media_gallery.toggle_visible": "切换显示/隐藏",
   "missing_indicator.label": "找不到内容",
@@ -240,29 +263,28 @@
   "navigation_bar.domain_blocks": "已屏蔽的网站",
   "navigation_bar.edit_profile": "修改个人资料",
   "navigation_bar.favourites": "收藏的内容",
-  "navigation_bar.filters": "被隐藏的词",
+  "navigation_bar.filters": "屏蔽关键词",
   "navigation_bar.follow_requests": "关注请求",
-  "navigation_bar.follows_and_followers": "正在关注以及关注者",
+  "navigation_bar.follows_and_followers": "关注管理",
   "navigation_bar.info": "关于本站",
   "navigation_bar.keyboard_shortcuts": "快捷键列表",
   "navigation_bar.lists": "列表",
-  "navigation_bar.logout": "注销",
+  "navigation_bar.logout": "登出",
   "navigation_bar.mutes": "已隐藏的用户",
   "navigation_bar.personal": "个人",
   "navigation_bar.pins": "置顶嘟文",
   "navigation_bar.preferences": "首选项",
-  "navigation_bar.profile_directory": "用户资料目录",
   "navigation_bar.public_timeline": "跨站公共时间轴",
   "navigation_bar.security": "安全",
   "notification.favourite": "{name} 收藏了你的嘟文",
   "notification.follow": "{name} 开始关注你",
-  "notification.mention": "{name} 提及你",
+  "notification.mention": "{name} 提及了你",
   "notification.poll": "你参与的一个投票已经结束",
-  "notification.reblog": "{name} 转了你的嘟文",
+  "notification.reblog": "{name} 转嘟了你的嘟文",
   "notifications.clear": "清空通知列表",
   "notifications.clear_confirmation": "你确定要永久清空通知列表吗?",
   "notifications.column_settings.alert": "桌面通知",
-  "notifications.column_settings.favourite": "你的嘟文被收藏时:",
+  "notifications.column_settings.favourite": "当你的嘟文被收藏时:",
   "notifications.column_settings.filter_bar.advanced": "显示所有类别",
   "notifications.column_settings.filter_bar.category": "快速过滤栏",
   "notifications.column_settings.filter_bar.show": "显示",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} 条通知",
   "poll.closed": "已关闭",
   "poll.refresh": "刷新",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count} 票",
   "poll.vote": "投票",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "发起投票",
   "poll_button.remove_poll": "移除投票",
   "privacy.change": "设置嘟文可见范围",
@@ -295,6 +319,7 @@
   "privacy.public.short": "公开",
   "privacy.unlisted.long": "所有人可见,但不会出现在公共时间轴上",
   "privacy.unlisted.short": "不公开",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "加载中……",
   "regeneration_indicator.sublabel": "你的主页时间轴正在准备中!",
   "relative_time.days": "{number}天",
@@ -306,25 +331,26 @@
   "report.forward": "发送举报至 {target}",
   "report.forward_hint": "这名用户来自另一个服务器。是否要向那个服务器发送一条匿名的举报?",
   "report.hint": "举报将会发送给你所在服务器的监察员。你可以在下面填写举报该用户的理由:",
-  "report.placeholder": "附言",
+  "report.placeholder": "备注",
   "report.submit": "提交",
   "report.target": "举报 {target}",
   "search.placeholder": "搜索",
   "search_popout.search_format": "高级搜索格式",
-  "search_popout.tips.full_text": "输入其他内容将会返回所有你撰写、收藏、转嘟过或提及到你的嘟文,同时也会在用户名、昵称和话题标签中进行搜索。",
+  "search_popout.tips.full_text": "输入关键词检索所有你发送、收藏、转嘟过或提及到你的嘟文,以及其他用户公开的用户名、昵称和话题标签。",
   "search_popout.tips.hashtag": "话题标签",
   "search_popout.tips.status": "嘟文",
-  "search_popout.tips.text": "输入其他内容将会返回昵称、用户名和话题标签",
+  "search_popout.tips.text": "输入关键词检索昵称、用户名和话题标签",
   "search_popout.tips.user": "用户",
   "search_results.accounts": "用户",
   "search_results.hashtags": "话题标签",
   "search_results.statuses": "嘟文",
+  "search_results.statuses_fts_disabled": "此Mastodon服务器未启用嘟文内容搜索。",
   "search_results.total": "共 {count, number} 个结果",
   "status.admin_account": "打开 @{name} 的管理界面",
   "status.admin_status": "打开这条嘟文的管理界面",
   "status.block": "屏蔽 @{name}",
   "status.cancel_reblog_private": "取消转嘟",
-  "status.cannot_reblog": "无法转嘟这条嘟文",
+  "status.cannot_reblog": "这条嘟文不允许被转嘟",
   "status.copy": "复制嘟文链接",
   "status.delete": "删除",
   "status.detailed_status": "对话详情",
@@ -344,9 +370,9 @@
   "status.pinned": "置顶嘟文",
   "status.read_more": "阅读全文",
   "status.reblog": "转嘟",
-  "status.reblog_private": "转嘟给原有关注者",
+  "status.reblog_private": "转嘟(可见者不变)",
   "status.reblogged_by": "{name} 转嘟了",
-  "status.reblogs.empty": "无人转嘟此条。如果有人转嘟了,就会显示在这里。",
+  "status.reblogs.empty": "没有人转嘟过此条嘟文。如果有人转嘟了,就会显示在这里。",
   "status.redraft": "删除并重新编辑",
   "status.reply": "回复",
   "status.replyAll": "回复所有人",
@@ -358,6 +384,7 @@
   "status.show_more": "显示内容",
   "status.show_more_all": "显示所有内容",
   "status.show_thread": "显示全部对话",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "不再隐藏此对话",
   "status.unpin": "在个人资料页面取消置顶",
   "suggestions.dismiss": "关闭建议",
@@ -373,15 +400,23 @@
   "time_remaining.moments": "即将结束",
   "time_remaining.seconds": "剩余 {number, plural, one {# 秒} other {# 秒}}",
   "trends.count_by_accounts": "{count} 人正在讨论",
-  "ui.beforeunload": "如果你现在离开 Mastodon,你的草稿内容将会被丢弃。",
+  "trends.trending_now": "现在流行",
+  "ui.beforeunload": "如果你现在离开 Mastodon,你的草稿内容将会丢失。",
   "upload_area.title": "将文件拖放到此处开始上传",
   "upload_button.label": "上传媒体文件 (JPEG, PNG, GIF, WebM, MP4, MOV)",
-  "upload_error.limit": "超过文件上传限制。",
+  "upload_error.limit": "文件大小超过限制。",
   "upload_error.poll": "投票中不允许上传文件。",
   "upload_form.description": "为视觉障碍人士添加文字说明",
-  "upload_form.focus": "剪裁",
+  "upload_form.edit": "编辑",
   "upload_form.undo": "删除",
-  "upload_progress.label": "上传中…",
+  "upload_modal.analyzing_picture": "分析图片…",
+  "upload_modal.apply": "应用",
+  "upload_modal.description_placeholder": "天地玄黄 宇宙洪荒 日月盈仄 辰宿列张",
+  "upload_modal.detect_text": "从图片中检测文本",
+  "upload_modal.edit_media": "编辑媒体",
+  "upload_modal.hint": "在预览图上点击或拖动圆圈,以选择缩略图的焦点。",
+  "upload_modal.preview_label": "预览 ({ratio})",
+  "upload_progress.label": "上传中……",
   "video.close": "关闭视频",
   "video.exit_fullscreen": "退出全屏",
   "video.expand": "展开视频",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index cd1753ac8bf83e5c7ee7f06be7159840cb358129..49993cb52053fa5b68a9c53172a0c06bb78cddac 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -4,6 +4,7 @@
   "account.block": "封鎖 @{name}",
   "account.block_domain": "隱藏來自 {domain} 的一切文章",
   "account.blocked": "封鎖",
+  "account.cancel_follow_request": "Cancel follow request",
   "account.direct": "私訊 @{name}",
   "account.domain_blocked": "服務站被隱藏",
   "account.edit_profile": "修改個人資料",
@@ -15,6 +16,7 @@
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "關注你",
   "account.hide_reblogs": "隱藏 @{name} 的轉推",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "媒體",
@@ -23,6 +25,7 @@
   "account.mute": "將 @{name} 靜音",
   "account.mute_notifications": "將來自 @{name} 的通知靜音",
   "account.muted": "靜音",
+  "account.never_active": "Never",
   "account.posts": "文章",
   "account.posts_with_replies": "包含回覆的文章",
   "account.report": "舉報 @{name}",
@@ -35,8 +38,11 @@
   "account.unfollow": "取消關注",
   "account.unmute": "取消 @{name} 的靜音",
   "account.unmute_notifications": "取消來自 @{name} 通知的靜音",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "發生不可預期的錯誤。",
   "alert.unexpected.title": "噢!",
+  "autosuggest_hashtag.per_week": "{count} per week",
   "boost_modal.combo": "如你想在下次路過這顯示,請按{combo},",
   "bundle_column_error.body": "加載本組件出錯。",
   "bundle_column_error.retry": "重試",
@@ -47,6 +53,7 @@
   "column.blocks": "封鎖用戶",
   "column.community": "本站時間軸",
   "column.direct": "個人訊息",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "隱藏的服務站",
   "column.favourites": "最愛的文章",
   "column.follow_requests": "關注請求",
@@ -93,7 +100,10 @@
   "confirmations.delete_list.message": "你確定要永久刪除這列表嗎?",
   "confirmations.domain_block.confirm": "隱藏整個網站",
   "confirmations.domain_block.message": "你真的真的確定要隱藏整個 {domain} ?多數情況下,比較推薦封鎖或靜音幾個特定目標就好。你從此將不會再看到該站的內容和通知。來自該站的關注者亦會被移除。",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "靜音",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "你確定要將{name}靜音嗎?",
   "confirmations.redraft.confirm": "刪除並編輯",
   "confirmations.redraft.message": "你確定要刪除並重新編輯嗎?所有相關的回覆、轉推與最愛都會被刪除。",
@@ -101,6 +111,14 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "取消關注",
   "confirmations.unfollow.message": "真的不要繼續關注 {name} 了嗎?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "要內嵌此文章,請將以下代碼貼進你的網站。",
   "embed.preview": "看上去會是這樣:",
   "emoji_button.activity": "活動",
@@ -134,6 +152,10 @@
   "empty_column.mutes": "You haven't muted any users yet.",
   "empty_column.notifications": "你沒有任何通知紀錄,快向其他用戶搭訕吧。",
   "empty_column.public": "跨站時間軸暫時沒有內容!快寫一些公共的文章,或者關注另一些服務站的用戶吧!你和本站、友站的交流,將決定這裏出現的內容。",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -226,6 +248,7 @@
   "lists.new.title_placeholder": "新列表標題",
   "lists.search": "從你關注的用戶中搜索",
   "lists.subheading": "列表",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
   "loading_indicator.label": "載入中...",
   "media_gallery.toggle_visible": "打開或關上",
   "missing_indicator.label": "找不到內容",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "置頂文章",
   "navigation_bar.preferences": "偏好設定",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "跨站時間軸",
   "navigation_bar.security": "安全",
   "notification.favourite": "{name} 收藏了你的文章",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} 條通知",
   "poll.closed": "Closed",
   "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
   "poll_button.add_poll": "Add a poll",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "調整私隱設定",
@@ -295,6 +319,7 @@
   "privacy.public.short": "公共",
   "privacy.unlisted.long": "公開,但不在公共時間軸顯示",
   "privacy.unlisted.short": "公開",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "載入中……",
   "regeneration_indicator.sublabel": "你的主頁時間軸正在準備中!",
   "relative_time.days": "{number}æ—¥",
@@ -319,6 +344,7 @@
   "search_results.accounts": "使用者",
   "search_results.hashtags": "標籤",
   "search_results.statuses": "文章",
+  "search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
   "search_results.total": "{count, number} 項結果",
   "status.admin_account": "Open moderation interface for @{name}",
   "status.admin_status": "Open this status in the moderation interface",
@@ -358,6 +384,7 @@
   "status.show_more": "顯示更多",
   "status.show_more_all": "顯示更多這類文章",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "解禁對話",
   "status.unpin": "解除置頂",
   "suggestions.dismiss": "Dismiss suggestion",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "Moments remaining",
   "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
   "trends.count_by_accounts": "{count} 位用戶在討論",
+  "trends.trending_now": "Trending now",
   "ui.beforeunload": "如果你現在離開 Mastodon,你的草稿內容將會被丟棄。",
   "upload_area.title": "將檔案拖放至此上載",
   "upload_button.label": "上載媒體檔案",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.description": "為視覺障礙人士添加文字說明",
-  "upload_form.focus": "裁切",
+  "upload_form.edit": "Edit",
   "upload_form.undo": "刪除",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "Preview ({ratio})",
   "upload_progress.label": "上載中……",
   "video.close": "關閉影片",
   "video.exit_fullscreen": "退出全熒幕",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index 05a8c4dac72650c5e217b6c41b3abf4def9241cf..e6080011ee8ef9dc758ade4c3a3a329ec57ac506 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -2,27 +2,30 @@
   "account.add_or_remove_from_list": "從名單中新增或移除",
   "account.badges.bot": "機器人",
   "account.block": "封鎖 @{name}",
-  "account.block_domain": "隱藏來自 {domain} 的所有嘟文",
+  "account.block_domain": "隱藏來自 {domain} 的所有內容",
   "account.blocked": "已封鎖",
+  "account.cancel_follow_request": "取消關注請求",
   "account.direct": "傳私訊給 @{name}",
   "account.domain_blocked": "已隱藏網域",
   "account.edit_profile": "編輯個人資料",
   "account.endorse": "在個人資料推薦對方",
   "account.follow": "關注",
   "account.followers": "關注者",
-  "account.followers.empty": "還沒有人關注這位使用者。",
+  "account.followers.empty": "尚沒有人關注這位使用者。",
   "account.follows": "正在關注",
-  "account.follows.empty": "這個使用者尚未關注任何使用者。",
+  "account.follows.empty": "這位使用者尚未關注任何使用者。",
   "account.follows_you": "關注了你",
   "account.hide_reblogs": "隱藏來自 @{name} 的轉推",
-  "account.link_verified_on": "此連結的所有權已在 {date} 檢查",
-  "account.locked_info": "此帳號的隱私狀態被設為鎖定,擁有者將手動審核可關注此帳號的人。",
+  "account.last_status": "上次活躍",
+  "account.link_verified_on": "已在 {date} 檢查此連結的擁有者權限",
+  "account.locked_info": "這隻帳戶的隱私狀態被設成鎖定。該擁有者會手動審核能關注這隻帳號的人。",
   "account.media": "媒體",
   "account.mention": "提及 @{name}",
   "account.moved_to": "{name} 已遷移至:",
   "account.mute": "靜音 @{name}",
   "account.mute_notifications": "靜音來自 @{name} 的通知",
   "account.muted": "已靜音",
+  "account.never_active": "永不",
   "account.posts": "嘟文",
   "account.posts_with_replies": "嘟文與回覆",
   "account.report": "檢舉 @{name}",
@@ -33,22 +36,26 @@
   "account.unblock_domain": "取消隱藏 {domain}",
   "account.unendorse": "不再於個人資料頁面推薦對方",
   "account.unfollow": "取消關注",
-  "account.unmute": "不再靜音 @{name}",
-  "account.unmute_notifications": "不再靜音來自 @{name} 的通知",
+  "account.unmute": "取消靜音 @{name}",
+  "account.unmute_notifications": "重新接收來自 @{name} 的通知",
+  "alert.rate_limited.message": "請在 {retry_time, time, medium} 過後重試",
+  "alert.rate_limited.title": "已限速",
   "alert.unexpected.message": "發生了非預期的錯誤。",
   "alert.unexpected.title": "哎呀!",
+  "autosuggest_hashtag.per_week": "{count} / 週",
   "boost_modal.combo": "下次您可以按 {combo} 跳過",
-  "bundle_column_error.body": "載入此組件時發生錯誤。",
+  "bundle_column_error.body": "載入此元件時發生錯誤。",
   "bundle_column_error.retry": "重試",
   "bundle_column_error.title": "網路錯誤",
   "bundle_modal_error.close": "關閉",
-  "bundle_modal_error.message": "載入此組件時發生錯誤。",
+  "bundle_modal_error.message": "載入此元件時發生錯誤。",
   "bundle_modal_error.retry": "重試",
   "column.blocks": "封鎖的使用者",
-  "column.community": "本地時間軸",
+  "column.community": "本機時間軸",
   "column.direct": "私訊",
+  "column.directory": "瀏覽個人資料",
   "column.domain_blocks": "隱藏的網域",
-  "column.favourites": "最愛",
+  "column.favourites": "收藏",
   "column.follow_requests": "關注請求",
   "column.home": "主頁",
   "column.lists": "名單",
@@ -64,44 +71,55 @@
   "column_header.show_settings": "顯示設定",
   "column_header.unpin": "取消釘選",
   "column_subheading.settings": "設定",
-  "community.column_settings.media_only": "僅媒體",
-  "compose_form.direct_message_warning": "這條嘟文只有被提及的使用者才能看到。",
+  "community.column_settings.media_only": "只有媒體",
+  "compose_form.direct_message_warning": "這條嘟文只有被提及的使用者才看得到。",
   "compose_form.direct_message_warning_learn_more": "了解更多",
-  "compose_form.hashtag_warning": "因這則嘟文設成「不公開」,因此它不會列在任何「#」標籤下。只有公開嘟文才能用「#」標籤找到。",
-  "compose_form.lock_disclaimer": "您的帳戶尚未{locked}。任何人都能關注您並看到您設定成僅關注者能看的嘟文。",
+  "compose_form.hashtag_warning": "由於這則嘟文被設定成「不公開」,所以它將不會被列在任何主題標籤下。只有公開的嘟文才能藉主題標籤找到。",
+  "compose_form.lock_disclaimer": "您的帳戶尚未{locked}。任何人都能關注您並看到您設定成只有關注者能看的嘟文。",
   "compose_form.lock_disclaimer.lock": "上鎖",
   "compose_form.placeholder": "您正在想些什麼?",
   "compose_form.poll.add_option": "新增選擇",
   "compose_form.poll.duration": "投票期限",
   "compose_form.poll.option_placeholder": "第 {number} 個選擇",
   "compose_form.poll.remove_option": "移除此選擇",
-  "compose_form.publish": "嘟掉",
+  "compose_form.publish": "嘟出去",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.hide": "Mark media as sensitive",
+  "compose_form.sensitive.hide": "標記媒體為敏感內容",
   "compose_form.sensitive.marked": "此媒體被標記為敏感內容",
-  "compose_form.sensitive.unmarked": "此媒體未被標記為敏感內容",
-  "compose_form.spoiler.marked": "正文已隱藏在警告之後",
+  "compose_form.sensitive.unmarked": "此媒體未標記為敏感內容",
+  "compose_form.spoiler.marked": "正文已隱藏到警告之後",
   "compose_form.spoiler.unmarked": "正文未被隱藏",
   "compose_form.spoiler_placeholder": "請在此處寫入警告訊息",
   "confirmation_modal.cancel": "取消",
-  "confirmations.block.block_and_report": "Block & Report",
+  "confirmations.block.block_and_report": "封鎖並檢舉",
   "confirmations.block.confirm": "封鎖",
-  "confirmations.block.message": "你確定要封鎖 {name} ?",
+  "confirmations.block.message": "確定封鎖 {name} ?",
   "confirmations.delete.confirm": "刪除",
   "confirmations.delete.message": "你確定要刪除這條嘟文?",
   "confirmations.delete_list.confirm": "刪除",
-  "confirmations.delete_list.message": "確定要永久刪除此名單?",
+  "confirmations.delete_list.message": "確定永久刪除此名單?",
   "confirmations.domain_block.confirm": "隱藏整個網域",
-  "confirmations.domain_block.message": "確定封鎖整個 {domain} 嗎?多數情況下,封鎖或靜音幾個特定使用者應該就能滿足你的需求了。您將不能在任何公開時間軸或通知中看到來自該網域的內容。來自該網域的關注者將被移除。",
+  "confirmations.domain_block.message": "真的非常確定封鎖整個 {domain} 嗎?大部分情況下,你只需要封鎖或靜音少數特定的人就能滿足需求了。你將不能在任何公開的時間軸及通知中看到那個網域的內容。你來自該網域的關注者也會被移除。",
+  "confirmations.logout.confirm": "登出",
+  "confirmations.logout.message": "確定要登出嗎?",
   "confirmations.mute.confirm": "靜音",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
   "confirmations.mute.message": "確定靜音 {name} ?",
   "confirmations.redraft.confirm": "刪除並重新編輯",
-  "confirmations.redraft.message": "你確定要刪除這條嘟文並重新編輯它嗎?這麼做將失去轉嘟和最愛,而對原始嘟文的回覆將被孤立。",
+  "confirmations.redraft.message": "確定刪掉這則嘟文並重新編輯嗎?將會失去這則嘟文的轉嘟及收藏,且回覆這則的嘟文將會變成獨立的嘟文。",
   "confirmations.reply.confirm": "回覆",
   "confirmations.reply.message": "現在回覆將蓋掉您目前正在撰寫的訊息。是否仍要回覆?",
   "confirmations.unfollow.confirm": "取消關注",
   "confirmations.unfollow.message": "真的要取消關注 {name} 嗎?",
-  "embed.instructions": "要嵌入此嘟文,請將以下代碼貼進你的網站。",
+  "conversation.delete": "刪除對話",
+  "conversation.mark_as_read": "標為已讀",
+  "conversation.open": "檢視對話",
+  "conversation.with": "與 {names}",
+  "directory.federated": "來自已知聯邦宇宙",
+  "directory.local": "僅來自 {domain}",
+  "directory.new_arrivals": "新貨",
+  "directory.recently_active": "最近活躍",
+  "embed.instructions": "要嵌入此嘟文,請將以下程式碼貼進你的網站。",
   "embed.preview": "他會顯示成這樣:",
   "emoji_button.activity": "活動",
   "emoji_button.custom": "自訂",
@@ -109,7 +127,7 @@
   "emoji_button.food": "飲食",
   "emoji_button.label": "插入表情符號",
   "emoji_button.nature": "大自然",
-  "emoji_button.not_found": "就沒這表情符號吼!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.not_found": "啊就沒這表情符號吼!! (╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "物件",
   "emoji_button.people": "使用者",
   "emoji_button.recent": "最常使用",
@@ -118,22 +136,26 @@
   "emoji_button.symbols": "符號",
   "emoji_button.travel": "旅遊與地點",
   "empty_column.account_timeline": "這裡還沒有嘟文!",
-  "empty_column.account_unavailable": "Profile unavailable",
+  "empty_column.account_unavailable": "無法取得個人資料",
   "empty_column.blocks": "你還沒有封鎖任何使用者。",
   "empty_column.community": "本地時間軸是空的。快公開嘟些文搶頭香啊!",
   "empty_column.direct": "您還沒有任何私訊。當您私訊別人或收到私訊時,它將於此顯示。",
   "empty_column.domain_blocks": "尚未隱藏任何網域。",
-  "empty_column.favourited_statuses": "你還沒有將任何嘟文標為最愛。最愛的嘟文將顯示於此。",
-  "empty_column.favourites": "還沒有人將此嘟文標為最愛。如果有人標成最愛,則會顯示在這裡。",
-  "empty_column.follow_requests": "您尚未收到任何關注請求。收到時會顯示於此。",
-  "empty_column.hashtag": "這個「#」標籤下什麼都沒有。",
+  "empty_column.favourited_statuses": "你還沒收藏任何嘟文。這裡將會顯示你收藏的嘟文。",
+  "empty_column.favourites": "還沒有人收藏這則嘟文。這裡將會顯示被收藏的嘟文。",
+  "empty_column.follow_requests": "您尚未收到任何關注請求。這裡將會顯示收到的關注請求。",
+  "empty_column.hashtag": "這個主題標籤下什麼也沒有。",
   "empty_column.home": "您的首頁時間軸是空的!前往 {public} 或使用搜尋功能來認識其他人。",
   "empty_column.home.public_timeline": "公開時間軸",
-  "empty_column.list": "此份名單還沒有東西。當此名單的成員嘟出了新的嘟文時,它們就會出現在這裡。",
-  "empty_column.lists": "你還沒有建立任何名單。你建立的名單將會顯示在這裡。",
-  "empty_column.mutes": "你還沒有靜音任何使用者。",
+  "empty_column.list": "這份名單還沒有東西。當此名單的成員嘟出了新的嘟文時,它們就會顯示於此。",
+  "empty_column.lists": "你還沒有建立任何名單。這裡將會顯示你所建立的名單。",
+  "empty_column.mutes": "你尚未靜音任何使用者。",
   "empty_column.notifications": "您尚未收到任何通知,和別人互動開啟對話吧。",
   "empty_column.public": "這裡什麼都沒有!嘗試寫些公開的嘟文,或著自己關注其他伺服器的使用者後就會有嘟文出現了",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
   "federation.change": "Adjust status federation",
   "federation.federated.long": "Allow toot to reach other instances",
   "federation.federated.short": "Federated",
@@ -151,72 +173,72 @@
   "getting_started.terms": "服務條款",
   "hashtag.column_header.tag_mode.all": "以及{additional}",
   "hashtag.column_header.tag_mode.any": "或是{additional}",
-  "hashtag.column_header.tag_mode.none": "而不用{additional}",
+  "hashtag.column_header.tag_mode.none": "而無需{additional}",
   "hashtag.column_settings.select.no_options_message": "找不到建議",
-  "hashtag.column_settings.select.placeholder": "輸入「#」標籤…",
+  "hashtag.column_settings.select.placeholder": "輸入主題標籤…",
   "hashtag.column_settings.tag_mode.all": "全部",
   "hashtag.column_settings.tag_mode.any": "任一",
-  "hashtag.column_settings.tag_mode.none": "全都不要",
-  "hashtag.column_settings.tag_toggle": "對此欄位加入額外標籤",
+  "hashtag.column_settings.tag_mode.none": "全不",
+  "hashtag.column_settings.tag_toggle": "將額外標籤加入到這個欄位",
   "home.column_settings.basic": "基本",
-  "home.column_settings.show_reblogs": "顯示轉推",
+  "home.column_settings.show_reblogs": "顯示轉嘟",
   "home.column_settings.show_replies": "顯示回覆",
   "intervals.full.days": "{number, plural, one {# 天} other {# 天}}",
   "intervals.full.hours": "{number, plural, one {# 小時} other {# 小時}}",
   "intervals.full.minutes": "{number, plural, one {# 分鐘} other {# 分鐘}}",
   "introduction.federation.action": "下一步",
-  "introduction.federation.federated.headline": "聯邦",
-  "introduction.federation.federated.text": "來自聯邦網路中其他伺服器的公開嘟文將會在聯邦網路時間軸中顯示。",
+  "introduction.federation.federated.headline": "站台聯盟",
+  "introduction.federation.federated.text": "來自聯盟宇宙中其他站台的公開嘟文將會在站點聯盟時間軸中顯示。",
   "introduction.federation.home.headline": "首頁",
-  "introduction.federation.home.text": "您所關注使用者所發的嘟文將顯示在首頁的訊息來源。您能關注任何伺服器上的任何人!",
-  "introduction.federation.local.headline": "本地",
-  "introduction.federation.local.text": "跟您同伺服器之使用者所發的公開嘟文將會顯示在本地時間軸中。",
+  "introduction.federation.home.text": "你關注使用者的嘟文將會在首頁動態中顯示。你可以關注任何伺服器上的任何人!",
+  "introduction.federation.local.headline": "本機",
+  "introduction.federation.local.text": "跟您同伺服器之使用者所發的公開嘟文將會顯示在本機時間軸中。",
   "introduction.interactions.action": "完成教學!",
-  "introduction.interactions.favourite.headline": "最愛",
-  "introduction.interactions.favourite.text": "您能稍候儲存嘟文,或者將嘟文加到最愛,讓作者知道您喜歡這嘟文。",
+  "introduction.interactions.favourite.headline": "關注",
+  "introduction.interactions.favourite.text": "您能儲存嘟文供稍候觀看,或者收藏嘟文,讓作者知道您喜歡這則嘟文。",
   "introduction.interactions.reblog.headline": "轉嘟",
-  "introduction.interactions.reblog.text": "您能透過轉嘟他人嘟文來分享給您的關注者。",
+  "introduction.interactions.reblog.text": "您能藉由轉嘟他人嘟文來分享給您的關注者。",
   "introduction.interactions.reply.headline": "回覆",
-  "introduction.interactions.reply.text": "您能回覆其他人或自己的嘟文。將會把這些回覆串成一串對話。",
-  "introduction.welcome.action": "開始!",
+  "introduction.interactions.reply.text": "您能回覆其他人或自己的嘟文,這麼做會把這些回覆串成一串對話。",
+  "introduction.welcome.action": "開始旅程吧!",
   "introduction.welcome.headline": "第一步",
-  "introduction.welcome.text": "歡迎來到聯邦!稍候您將可以廣播訊息並跨各種各式各樣的伺服器與朋友聊天。但這台伺服器,{domain},十分特殊 -- 它寄管了您的個人資料,所以請記住這台伺服器的名稱。",
+  "introduction.welcome.text": "歡迎來到聯盟宇宙!等等你就可以廣播訊息及跨越各種各式各樣的伺服器與朋友聊天。但這台伺服器,{domain},非常特別 - 它寄管了你的個人資料,所以請記住它的名字。",
   "keyboard_shortcuts.back": "返回上一頁",
-  "keyboard_shortcuts.blocked": "開啟「封鎖的使用者」名單",
+  "keyboard_shortcuts.blocked": "開啟「封鎖使用者」名單",
   "keyboard_shortcuts.boost": "轉嘟",
   "keyboard_shortcuts.column": "將焦點放在其中一欄的嘟文",
   "keyboard_shortcuts.compose": "將焦點移至撰寫文字區塊",
   "keyboard_shortcuts.description": "描述",
   "keyboard_shortcuts.direct": "開啟私訊欄",
-  "keyboard_shortcuts.down": "在名單中往下移動",
+  "keyboard_shortcuts.down": "往下移動名單項目",
   "keyboard_shortcuts.enter": "檢視嘟文",
-  "keyboard_shortcuts.favourite": "加入最愛",
-  "keyboard_shortcuts.favourites": "開啟最愛名單",
-  "keyboard_shortcuts.federated": "開啟聯邦時間軸",
+  "keyboard_shortcuts.favourite": "收藏",
+  "keyboard_shortcuts.favourites": "開啟收藏名單",
+  "keyboard_shortcuts.federated": "開啟站點聯盟時間軸",
   "keyboard_shortcuts.heading": "鍵盤快速鍵",
   "keyboard_shortcuts.home": "開啟首頁時間軸",
   "keyboard_shortcuts.hotkey": "快速鍵",
-  "keyboard_shortcuts.legend": "顯示此說明",
-  "keyboard_shortcuts.local": "開啟本地時間軸",
+  "keyboard_shortcuts.legend": "顯示此列表",
+  "keyboard_shortcuts.local": "開啟本機時間軸",
   "keyboard_shortcuts.mention": "提及作者",
   "keyboard_shortcuts.muted": "開啟靜音使用者名單",
   "keyboard_shortcuts.my_profile": "開啟個人資料頁面",
   "keyboard_shortcuts.notifications": "開啟通知欄",
   "keyboard_shortcuts.pinned": "開啟釘選的嘟文名單",
-  "keyboard_shortcuts.profile": "開啟作者的個人資料頁",
-  "keyboard_shortcuts.reply": "回應嘟文",
+  "keyboard_shortcuts.profile": "開啟作者的個人資料頁面",
+  "keyboard_shortcuts.reply": "回覆",
   "keyboard_shortcuts.requests": "開啟關注請求名單",
   "keyboard_shortcuts.search": "將焦點移至搜尋框",
   "keyboard_shortcuts.start": "開啟「開始使用」欄位",
   "keyboard_shortcuts.toggle_hidden": "顯示/隱藏在內容警告之後的正文",
-  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.toggle_sensitivity": "顯示 / 隱藏媒體",
   "keyboard_shortcuts.toot": "開始發出新嘟文",
   "keyboard_shortcuts.unfocus": "取消輸入文字區塊 / 搜尋的焦點",
-  "keyboard_shortcuts.up": "在名單中往上移動",
+  "keyboard_shortcuts.up": "往上移動名單項目",
   "lightbox.close": "關閉",
   "lightbox.next": "下一步",
   "lightbox.previous": "上一步",
-  "lightbox.view_context": "View context",
+  "lightbox.view_context": "檢視內文",
   "lists.account.add": "新增至名單",
   "lists.account.remove": "從名單中移除",
   "lists.delete": "刪除名單",
@@ -226,23 +248,24 @@
   "lists.new.title_placeholder": "新名單標題",
   "lists.search": "搜尋您關注的使用者",
   "lists.subheading": "您的名單",
+  "load_pending": "{count, plural, other {# 個新項目}}",
   "loading_indicator.label": "讀取中...",
   "media_gallery.toggle_visible": "切換可見性",
   "missing_indicator.label": "找不到",
   "missing_indicator.sublabel": "找不到此資源",
-  "mute_modal.hide_notifications": "隱藏來自這個使用者的通知?",
-  "navigation_bar.apps": "行動應用程式",
-  "navigation_bar.blocks": "封鎖的使用者",
-  "navigation_bar.community_timeline": "本地時間軸",
+  "mute_modal.hide_notifications": "隱藏來自這位使用者的通知?",
+  "navigation_bar.apps": "封鎖的使用者",
+  "navigation_bar.blocks": "封鎖使用者",
+  "navigation_bar.community_timeline": "本機時間軸",
   "navigation_bar.compose": "撰寫新嘟文",
   "navigation_bar.direct": "私訊",
   "navigation_bar.discover": "探索",
   "navigation_bar.domain_blocks": "隱藏的網域",
   "navigation_bar.edit_profile": "編輯個人資料",
-  "navigation_bar.favourites": "最愛內容",
+  "navigation_bar.favourites": "收藏",
   "navigation_bar.filters": "靜音詞彙",
   "navigation_bar.follow_requests": "關注請求",
-  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.follows_and_followers": "關注及關注者",
   "navigation_bar.info": "關於此伺服器",
   "navigation_bar.keyboard_shortcuts": "快速鍵",
   "navigation_bar.lists": "名單",
@@ -251,7 +274,6 @@
   "navigation_bar.personal": "個人",
   "navigation_bar.pins": "釘選的嘟文",
   "navigation_bar.preferences": "偏好設定",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "聯邦時間軸",
   "navigation_bar.security": "安全性",
   "notification.favourite": "{name} 把你的嘟文加入了最愛",
@@ -282,8 +304,10 @@
   "notifications.group": "{count} 條通知",
   "poll.closed": "已關閉",
   "poll.refresh": "重新整理",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
   "poll.total_votes": "{count, plural, one {# 個投票} other {# 個投票}}",
   "poll.vote": "投票",
+  "poll.voted": "你已對此問題投票",
   "poll_button.add_poll": "建立投票",
   "poll_button.remove_poll": "移除投票",
   "privacy.change": "調整隱私狀態",
@@ -295,6 +319,7 @@
   "privacy.public.short": "公開",
   "privacy.unlisted.long": "公開,但不會顯示在公開時間軸",
   "privacy.unlisted.short": "不公開",
+  "refresh": "Refresh",
   "regeneration_indicator.label": "載入中…",
   "regeneration_indicator.sublabel": "你的主頁時間軸正在準備中!",
   "relative_time.days": "{number} 天",
@@ -319,6 +344,7 @@
   "search_results.accounts": "使用者",
   "search_results.hashtags": "主題標籤",
   "search_results.statuses": "嘟文",
+  "search_results.statuses_fts_disabled": "「依內容搜尋嘟文」未在此 Mastodon 伺服器啟用。",
   "search_results.total": "{count, number} 項結果",
   "status.admin_account": "開啟 @{name} 的管理介面",
   "status.admin_status": "在管理介面開啟此嘟文",
@@ -358,6 +384,7 @@
   "status.show_more": "顯示更多",
   "status.show_more_all": "顯示更多這類嘟文",
   "status.show_thread": "顯示討論串",
+  "status.uncached_media_warning": "無法使用",
   "status.unmute_conversation": "解除此對話的靜音",
   "status.unpin": "解除置頂",
   "suggestions.dismiss": "關閉建議",
@@ -373,14 +400,22 @@
   "time_remaining.moments": "剩餘時間",
   "time_remaining.seconds": "剩餘 {number, plural, one {# 秒} other {# 秒}}",
   "trends.count_by_accounts": "{count} 位使用者在討論",
+  "trends.trending_now": "目前趨勢",
   "ui.beforeunload": "如果離開 Mastodon,你的草稿將會不見。",
   "upload_area.title": "拖放來上傳",
   "upload_button.label": "上傳媒體檔案 (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "已達到檔案上傳限制。",
   "upload_error.poll": "不允許在投票上傳檔案。",
   "upload_form.description": "為視障人士增加文字說明",
-  "upload_form.focus": "變更預覽",
+  "upload_form.edit": "編輯",
   "upload_form.undo": "刪除",
+  "upload_modal.analyzing_picture": "正在分析圖片…",
+  "upload_modal.apply": "套用",
+  "upload_modal.description_placeholder": "A quick brown fox 跳過那隻懶狗",
+  "upload_modal.detect_text": "從圖片偵測文字",
+  "upload_modal.edit_media": "編輯媒體",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preview_label": "預覽 ({ratio})",
   "upload_progress.label": "上傳中...",
   "video.close": "關閉影片",
   "video.exit_fullscreen": "退出全螢幕",
diff --git a/app/javascript/mastodon/reducers/alerts.js b/app/javascript/mastodon/reducers/alerts.js
index 089d920c3e4eeaf786195df9da8bbcfa0e69afe5..c62ab0dfdb9c801fbf38efcea560d2a97c4f2001 100644
--- a/app/javascript/mastodon/reducers/alerts.js
+++ b/app/javascript/mastodon/reducers/alerts.js
@@ -14,6 +14,7 @@ export default function alerts(state = initialState, action) {
       key: state.size > 0 ? state.last().get('key') + 1 : 0,
       title: action.title,
       message: action.message,
+      message_values: action.message_values,
     }));
   case ALERT_DISMISS:
     return state.filterNot(item => item.get('key') === action.alert.key);
diff --git a/app/javascript/mastodon/reducers/blocks.js b/app/javascript/mastodon/reducers/blocks.js
new file mode 100644
index 0000000000000000000000000000000000000000..1b65071634fb16f24b897680ebe6106dba242282
--- /dev/null
+++ b/app/javascript/mastodon/reducers/blocks.js
@@ -0,0 +1,22 @@
+import Immutable from 'immutable';
+
+import {
+  BLOCKS_INIT_MODAL,
+} from '../actions/blocks';
+
+const initialState = Immutable.Map({
+  new: Immutable.Map({
+    account_id: null,
+  }),
+});
+
+export default function mutes(state = initialState, action) {
+  switch (action.type) {
+  case BLOCKS_INIT_MODAL:
+    return state.withMutations((state) => {
+      state.setIn(['new', 'account_id'], action.account.get('id'));
+    });
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index 77353528281f8e01af9dc8e2c43a8abf177137ac..8692cb25467a2e45c3039d12ac7e73ab90381847 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -107,10 +107,13 @@ function clearAll(state) {
   });
 };
 
-function appendMedia(state, media) {
+function appendMedia(state, media, file) {
   const prevSize = state.get('media_attachments').size;
 
   return state.withMutations(map => {
+    if (media.get('type') === 'image') {
+      media = media.set('file', file);
+    }
     map.update('media_attachments', list => list.push(media));
     map.set('is_uploading', false);
     map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
@@ -148,15 +151,20 @@ const insertSuggestion = (state, position, token, completion, path) => {
   });
 };
 
-const updateSuggestionTags = (state, token) => {
-  const prefix = token.slice(1);
+const sortHashtagsByUse = (state, tags) => {
+  const personalHistory = state.get('tagHistory');
 
-  return state.merge({
-    suggestions: state.get('tagHistory')
-      .filter(tag => tag.toLowerCase().startsWith(prefix.toLowerCase()))
-      .slice(0, 4)
-      .map(tag => '#' + tag),
-    suggestion_token: token,
+  return tags.sort((a, b) => {
+    const usedA = personalHistory.includes(a.name);
+    const usedB = personalHistory.includes(b.name);
+
+    if (usedA === usedB) {
+      return 0;
+    } else if (usedA && !usedB) {
+      return -1;
+    } else {
+      return 1;
+    }
   });
 };
 
@@ -205,6 +213,36 @@ const expiresInFromExpiresAt = expires_at => {
   return [300, 1800, 3600, 21600, 86400, 259200, 604800].find(expires_in => expires_in >= delta) || 24 * 3600;
 };
 
+const mergeLocalHashtagResults = (suggestions, prefix, tagHistory) => {
+  prefix = prefix.toLowerCase();
+  if (suggestions.length < 4) {
+    const localTags = tagHistory.filter(tag => tag.toLowerCase().startsWith(prefix) && !suggestions.some(suggestion => suggestion.type === 'hashtag' && suggestion.name.toLowerCase() === tag.toLowerCase()));
+    return suggestions.concat(localTags.slice(0, 4 - suggestions.length).toJS().map(tag => ({ type: 'hashtag', name: tag })));
+  } else {
+    return suggestions;
+  }
+};
+
+const normalizeSuggestions = (state, { accounts, emojis, tags, token }) => {
+  if (accounts) {
+    return accounts.map(item => ({ id: item.id, type: 'account' }));
+  } else if (emojis) {
+    return emojis.map(item => ({ ...item, type: 'emoji' }));
+  } else {
+    return mergeLocalHashtagResults(sortHashtagsByUse(state, tags.map(item => ({ ...item, type: 'hashtag' }))), token.slice(1), state.get('tagHistory'));
+  }
+};
+
+const updateSuggestionTags = (state, token) => {
+  const prefix = token.slice(1);
+
+  const suggestions = state.get('suggestions').toJS();
+  return state.merge({
+    suggestions: ImmutableList(mergeLocalHashtagResults(suggestions, prefix, state.get('tagHistory'))),
+    suggestion_token: token,
+  });
+};
+
 export default function compose(state = initialState, action) {
   switch(action.type) {
   case STORE_HYDRATE:
@@ -296,7 +334,7 @@ export default function compose(state = initialState, action) {
   case COMPOSE_UPLOAD_REQUEST:
     return state.set('is_uploading', true);
   case COMPOSE_UPLOAD_SUCCESS:
-    return appendMedia(state, fromJS(action.media));
+    return appendMedia(state, fromJS(action.media), action.file);
   case COMPOSE_UPLOAD_FAIL:
     return state.set('is_uploading', false);
   case COMPOSE_UPLOAD_UNDO:
@@ -321,7 +359,7 @@ export default function compose(state = initialState, action) {
   case COMPOSE_SUGGESTIONS_CLEAR:
     return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null);
   case COMPOSE_SUGGESTIONS_READY:
-    return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).set('suggestion_token', action.token);
+    return state.set('suggestions', ImmutableList(normalizeSuggestions(state, action))).set('suggestion_token', action.token);
   case COMPOSE_SUGGESTION_SELECT:
     return insertSuggestion(state, action.position, action.token, action.completion, action.path);
   case COMPOSE_SUGGESTION_TAGS_UPDATE:
diff --git a/app/javascript/mastodon/reducers/conversations.js b/app/javascript/mastodon/reducers/conversations.js
index 39065823928ae2d8e1f0fe0984ce8cc73cc04d7f..975418eda2b026f29f51dc41264fb64894a93a8c 100644
--- a/app/javascript/mastodon/reducers/conversations.js
+++ b/app/javascript/mastodon/reducers/conversations.js
@@ -7,6 +7,7 @@ import {
   CONVERSATIONS_FETCH_FAIL,
   CONVERSATIONS_UPDATE,
   CONVERSATIONS_READ,
+  CONVERSATIONS_DELETE_SUCCESS,
 } from '../actions/conversations';
 import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS } from 'mastodon/actions/accounts';
 import { DOMAIN_BLOCK_SUCCESS } from 'mastodon/actions/domain_blocks';
@@ -107,6 +108,8 @@ export default function conversations(state = initialState, action) {
     return filterConversations(state, [action.relationship.id]);
   case DOMAIN_BLOCK_SUCCESS:
     return filterConversations(state, action.accounts);
+  case CONVERSATIONS_DELETE_SUCCESS:
+    return state.update('items', list => list.filterNot(item => item.get('id') === action.id));
   default:
     return state;
   }
diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js
index 981ad8e64cfd682bfe94eba5c5504464a34fb639..b8d60888815471295d95a174bca4212bb2c5e73c 100644
--- a/app/javascript/mastodon/reducers/index.js
+++ b/app/javascript/mastodon/reducers/index.js
@@ -15,6 +15,7 @@ import settings from './settings';
 import push_notifications from './push_notifications';
 import status_lists from './status_lists';
 import mutes from './mutes';
+import blocks from './blocks';
 import reports from './reports';
 import contexts from './contexts';
 import compose from './compose';
@@ -31,6 +32,8 @@ import conversations from './conversations';
 import suggestions from './suggestions';
 import polls from './polls';
 import identity_proofs from './identity_proofs';
+import trends from './trends';
+import missed_updates from './missed_updates';
 
 const reducers = {
   dropdown_menu,
@@ -49,6 +52,7 @@ const reducers = {
   settings,
   push_notifications,
   mutes,
+  blocks,
   reports,
   contexts,
   compose,
@@ -65,6 +69,8 @@ const reducers = {
   conversations,
   suggestions,
   polls,
+  trends,
+  missed_updates,
 };
 
 export default combineReducers(reducers);
diff --git a/app/javascript/mastodon/reducers/missed_updates.js b/app/javascript/mastodon/reducers/missed_updates.js
new file mode 100644
index 0000000000000000000000000000000000000000..b71d62d8287a897f8768ab523a3b18ec92f9ce17
--- /dev/null
+++ b/app/javascript/mastodon/reducers/missed_updates.js
@@ -0,0 +1,21 @@
+import { Map as ImmutableMap } from 'immutable';
+import { NOTIFICATIONS_UPDATE } from 'mastodon/actions/notifications';
+import { APP_FOCUS, APP_UNFOCUS } from 'mastodon/actions/app';
+
+const initialState = ImmutableMap({
+  focused: true,
+  unread: 0,
+});
+
+export default function missed_updates(state = initialState, action) {
+  switch(action.type) {
+  case APP_FOCUS:
+    return state.set('focused', true).set('unread', 0);
+  case APP_UNFOCUS:
+    return state.set('focused', false);
+  case NOTIFICATIONS_UPDATE:
+    return state.get('focused') ? state : state.update('unread', x => x + 1);
+  default:
+    return state;
+  }
+};
diff --git a/app/javascript/mastodon/reducers/mutes.js b/app/javascript/mastodon/reducers/mutes.js
index a96232dbd2340f06df37bc4417beced856778015..4672e50974eaa2a1394b5df9a6c51bc15548e0fa 100644
--- a/app/javascript/mastodon/reducers/mutes.js
+++ b/app/javascript/mastodon/reducers/mutes.js
@@ -7,7 +7,6 @@ import {
 
 const initialState = Immutable.Map({
   new: Immutable.Map({
-    isSubmitting: false,
     account: null,
     notifications: true,
   }),
@@ -17,7 +16,6 @@ export default function mutes(state = initialState, action) {
   switch (action.type) {
   case MUTES_INIT_MODAL:
     return state.withMutations((state) => {
-      state.setIn(['new', 'isSubmitting'], false);
       state.setIn(['new', 'account'], action.account);
       state.setIn(['new', 'notifications'], true);
     });
diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js
index 33fe86e002e2e92a51ce148df9e4336dc577a3bd..6ba80bd6a5741118a2528cfbe1c07ffd60c2750d 100644
--- a/app/javascript/mastodon/reducers/notifications.js
+++ b/app/javascript/mastodon/reducers/notifications.js
@@ -6,6 +6,9 @@ import {
   NOTIFICATIONS_FILTER_SET,
   NOTIFICATIONS_CLEAR,
   NOTIFICATIONS_SCROLL_TOP,
+  NOTIFICATIONS_LOAD_PENDING,
+  NOTIFICATIONS_MOUNT,
+  NOTIFICATIONS_UNMOUNT,
 } from '../actions/notifications';
 import {
   ACCOUNT_BLOCK_SUCCESS,
@@ -17,9 +20,11 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 import compareId from '../compare_id';
 
 const initialState = ImmutableMap({
+  pendingItems: ImmutableList(),
   items: ImmutableList(),
   hasMore: true,
   top: false,
+  mounted: false,
   unread: 0,
   isLoading: false,
 });
@@ -32,9 +37,13 @@ const notificationToMap = notification => ImmutableMap({
   status: notification.status ? notification.status.id : null,
 });
 
-const normalizeNotification = (state, notification) => {
+const normalizeNotification = (state, notification, usePendingItems) => {
   const top = state.get('top');
 
+  if (usePendingItems || !state.get('pendingItems').isEmpty()) {
+    return state.update('pendingItems', list => list.unshift(notificationToMap(notification))).update('unread', unread => unread + 1);
+  }
+
   if (!top) {
     state = state.update('unread', unread => unread + 1);
   }
@@ -48,7 +57,7 @@ const normalizeNotification = (state, notification) => {
   });
 };
 
-const expandNormalizedNotifications = (state, notifications, next) => {
+const expandNormalizedNotifications = (state, notifications, next, isLoadingRecent, usePendingItems) => {
   let items = ImmutableList();
 
   notifications.forEach((n, i) => {
@@ -57,7 +66,9 @@ const expandNormalizedNotifications = (state, notifications, next) => {
 
   return state.withMutations(mutable => {
     if (!items.isEmpty()) {
-      mutable.update('items', list => {
+      usePendingItems = isLoadingRecent && (usePendingItems || !mutable.get('pendingItems').isEmpty());
+
+      mutable.update(usePendingItems ? 'pendingItems' : 'items', list => {
         const lastIndex = 1 + list.findLastIndex(
           item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id'))
         );
@@ -79,35 +90,39 @@ const expandNormalizedNotifications = (state, notifications, next) => {
 };
 
 const filterNotifications = (state, accountIds) => {
-  return state.update('items', list => list.filterNot(item => item !== null && accountIds.includes(item.get('account'))));
+  const helper = list => list.filterNot(item => item !== null && accountIds.includes(item.get('account')));
+  return state.update('items', helper).update('pendingItems', helper);
 };
 
 const updateTop = (state, top) => {
   if (top) {
-    state = state.set('unread', 0);
+    state = state.set('unread', state.get('pendingItems').size);
   }
 
   return state.set('top', top);
 };
 
 const deleteByStatus = (state, statusId) => {
-  return state.update('items', list => list.filterNot(item => item !== null && item.get('status') === statusId));
+  const helper = list => list.filterNot(item => item !== null && item.get('status') === statusId);
+  return state.update('items', helper).update('pendingItems', helper);
 };
 
 export default function notifications(state = initialState, action) {
   switch(action.type) {
+  case NOTIFICATIONS_LOAD_PENDING:
+    return state.update('items', list => state.get('pendingItems').concat(list.take(40))).set('pendingItems', ImmutableList()).set('unread', 0);
   case NOTIFICATIONS_EXPAND_REQUEST:
     return state.set('isLoading', true);
   case NOTIFICATIONS_EXPAND_FAIL:
     return state.set('isLoading', false);
   case NOTIFICATIONS_FILTER_SET:
-    return state.set('items', ImmutableList()).set('hasMore', true);
+    return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', true);
   case NOTIFICATIONS_SCROLL_TOP:
     return updateTop(state, action.top);
   case NOTIFICATIONS_UPDATE:
-    return normalizeNotification(state, action.notification);
+    return normalizeNotification(state, action.notification, action.usePendingItems);
   case NOTIFICATIONS_EXPAND_SUCCESS:
-    return expandNormalizedNotifications(state, action.notifications, action.next);
+    return expandNormalizedNotifications(state, action.notifications, action.next, action.isLoadingRecent, action.usePendingItems);
   case ACCOUNT_BLOCK_SUCCESS:
     return filterNotifications(state, [action.relationship.id]);
   case ACCOUNT_MUTE_SUCCESS:
@@ -115,13 +130,17 @@ export default function notifications(state = initialState, action) {
   case DOMAIN_BLOCK_SUCCESS:
     return filterNotifications(state, action.accounts);
   case NOTIFICATIONS_CLEAR:
-    return state.set('items', ImmutableList()).set('hasMore', false);
+    return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false);
   case TIMELINE_DELETE:
     return deleteByStatus(state, action.id);
   case TIMELINE_DISCONNECT:
     return action.timeline === 'home' ?
-      state.update('items', items => items.first() ? items.unshift(null) : items) :
+      state.update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) :
       state;
+  case NOTIFICATIONS_MOUNT:
+    return state.set('mounted', true);
+  case NOTIFICATIONS_UNMOUNT:
+    return state.set('mounted', false);
   default:
     return state;
   }
diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js
index 77b7f588c5864099516ff41cd8c1c6402a692b25..875b2d92b39d4ba6cd6ad2d9ca091c55729ac411 100644
--- a/app/javascript/mastodon/reducers/search.js
+++ b/app/javascript/mastodon/reducers/search.js
@@ -3,6 +3,7 @@ import {
   SEARCH_CLEAR,
   SEARCH_FETCH_SUCCESS,
   SEARCH_SHOW,
+  SEARCH_EXPAND_SUCCESS,
 } from '../actions/search';
 import {
   COMPOSE_MENTION,
@@ -42,6 +43,9 @@ export default function search(state = initialState, action) {
       statuses: ImmutableList(action.results.statuses.map(item => item.id)),
       hashtags: fromJS(action.results.hashtags),
     })).set('submitted', true).set('searchTerm', action.searchTerm);
+  case SEARCH_EXPAND_SUCCESS:
+    const results = action.searchType === 'hashtags' ? fromJS(action.results.hashtags) : action.results[action.searchType].map(item => item.id);
+    return state.updateIn(['results', action.searchType], list => list.concat(results));
   default:
     return state;
   }
diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js
index a0eea137f11057afdbe0c1019d01a0bbb36cae04..793a99f8f56f6beb069e2c2aad0f4766fe994b9d 100644
--- a/app/javascript/mastodon/reducers/settings.js
+++ b/app/javascript/mastodon/reducers/settings.js
@@ -10,10 +10,12 @@ import uuid from '../uuid';
 const initialState = ImmutableMap({
   saved: true,
 
-  onboarded: false,
-
   skinTone: 1,
 
+  trends: ImmutableMap({
+    show: true,
+  }),
+
   home: ImmutableMap({
     shows: ImmutableMap({
       reblog: true,
@@ -74,10 +76,6 @@ const initialState = ImmutableMap({
       body: '',
     }),
   }),
-
-  trends: ImmutableMap({
-    show: true,
-  }),
 });
 
 const defaultColumns = fromJS([
diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js
index 309a95a19b837eeb7dcf85cf0eea406a25833855..0d7222e10a884267056367733ecf78c62babe48d 100644
--- a/app/javascript/mastodon/reducers/timelines.js
+++ b/app/javascript/mastodon/reducers/timelines.js
@@ -8,6 +8,7 @@ import {
   TIMELINE_SCROLL_TOP,
   TIMELINE_CONNECT,
   TIMELINE_DISCONNECT,
+  TIMELINE_LOAD_PENDING,
 } from '../actions/timelines';
 import {
   ACCOUNT_BLOCK_SUCCESS,
@@ -25,10 +26,11 @@ const initialTimeline = ImmutableMap({
   top: true,
   isLoading: false,
   hasMore: true,
+  pendingItems: ImmutableList(),
   items: ImmutableList(),
 });
 
-const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent) => {
+const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent, usePendingItems) => {
   return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
     mMap.set('isLoading', false);
     mMap.set('isPartial', isPartial);
@@ -38,7 +40,9 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is
     if (timeline.endsWith(':pinned')) {
       mMap.set('items', statuses.map(status => status.get('id')));
     } else if (!statuses.isEmpty()) {
-      mMap.update('items', ImmutableList(), oldIds => {
+      usePendingItems = isLoadingRecent && (usePendingItems || !mMap.get('pendingItems').isEmpty());
+
+      mMap.update(usePendingItems ? 'pendingItems' : 'items', ImmutableList(), oldIds => {
         const newIds = statuses.map(status => status.get('id'));
 
         const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1;
@@ -57,8 +61,17 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is
   }));
 };
 
-const updateTimeline = (state, timeline, status) => {
-  const top        = state.getIn([timeline, 'top']);
+const updateTimeline = (state, timeline, status, usePendingItems) => {
+  const top = state.getIn([timeline, 'top']);
+
+  if (usePendingItems || !state.getIn([timeline, 'pendingItems']).isEmpty()) {
+    if (state.getIn([timeline, 'pendingItems'], ImmutableList()).includes(status.get('id')) || state.getIn([timeline, 'items'], ImmutableList()).includes(status.get('id'))) {
+      return state;
+    }
+
+    return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(status.get('id'))).update('unread', unread => unread + 1));
+  }
+
   const ids        = state.getIn([timeline, 'items'], ImmutableList());
   const includesId = ids.includes(status.get('id'));
   const unread     = state.getIn([timeline, 'unread'], 0);
@@ -78,8 +91,10 @@ const updateTimeline = (state, timeline, status) => {
 
 const deleteStatus = (state, id, accountId, references, exclude_account = null) => {
   state.keySeq().forEach(timeline => {
-    if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`)))
-      state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id));
+    if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) {
+      const helper = list => list.filterNot(item => item === id);
+      state = state.updateIn([timeline, 'items'], helper).updateIn([timeline, 'pendingItems'], helper);
+    }
   });
 
   // Remove reblogs of deleted status
@@ -109,29 +124,31 @@ const filterTimelines = (state, relationship, statuses) => {
   return state;
 };
 
-const filterTimeline = (timeline, state, relationship, statuses) =>
-  state.updateIn([timeline, 'items'], ImmutableList(), list =>
-    list.filterNot(statusId =>
-      statuses.getIn([statusId, 'account']) === relationship.id
-    ));
+const filterTimeline = (timeline, state, relationship, statuses) => {
+  const helper = list => list.filterNot(statusId => statuses.getIn([statusId, 'account']) === relationship.id);
+  return state.updateIn([timeline, 'items'], ImmutableList(), helper).updateIn([timeline, 'pendingItems'], ImmutableList(), helper);
+};
 
 const updateTop = (state, timeline, top) => {
   return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
-    if (top) mMap.set('unread', 0);
+    if (top) mMap.set('unread', mMap.get('pendingItems').size);
     mMap.set('top', top);
   }));
 };
 
 export default function timelines(state = initialState, action) {
   switch(action.type) {
+  case TIMELINE_LOAD_PENDING:
+    return state.update(action.timeline, initialTimeline, map =>
+      map.update('items', list => map.get('pendingItems').concat(list.take(40))).set('pendingItems', ImmutableList()).set('unread', 0));
   case TIMELINE_EXPAND_REQUEST:
     return state.update(action.timeline, initialTimeline, map => map.set('isLoading', true));
   case TIMELINE_EXPAND_FAIL:
     return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false));
   case TIMELINE_EXPAND_SUCCESS:
-    return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent);
+    return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent, action.usePendingItems);
   case TIMELINE_UPDATE:
-    return updateTimeline(state, action.timeline, fromJS(action.status));
+    return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems);
   case TIMELINE_DELETE:
     return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
   case TIMELINE_CLEAR:
@@ -149,7 +166,7 @@ export default function timelines(state = initialState, action) {
     return state.update(
       action.timeline,
       initialTimeline,
-      map => map.set('online', false).update('items', items => items.first() ? items.unshift(null) : items)
+      map => map.set('online', false).update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items)
     );
   default:
     return state;
diff --git a/app/javascript/mastodon/reducers/trends.js b/app/javascript/mastodon/reducers/trends.js
new file mode 100644
index 0000000000000000000000000000000000000000..5cecc8fcab5f777423edcb9b3d0f9920ac792301
--- /dev/null
+++ b/app/javascript/mastodon/reducers/trends.js
@@ -0,0 +1,23 @@
+import { TRENDS_FETCH_REQUEST, TRENDS_FETCH_SUCCESS, TRENDS_FETCH_FAIL } from '../actions/trends';
+import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+
+const initialState = ImmutableMap({
+  items: ImmutableList(),
+  isLoading: false,
+});
+
+export default function trendsReducer(state = initialState, action) {
+  switch(action.type) {
+  case TRENDS_FETCH_REQUEST:
+    return state.set('isLoading', true);
+  case TRENDS_FETCH_SUCCESS:
+    return state.withMutations(map => {
+      map.set('items', fromJS(action.trends));
+      map.set('isLoading', false);
+    });
+  case TRENDS_FETCH_FAIL:
+    return state.set('isLoading', false);
+  default:
+    return state;
+  }
+};
diff --git a/app/javascript/mastodon/reducers/user_lists.js b/app/javascript/mastodon/reducers/user_lists.js
index 8db18c5dc66eac1b1ab73cfe88c7f7ef89cfff3a..08e94022f1ce63ffe7de9f750539d97de61da590 100644
--- a/app/javascript/mastodon/reducers/user_lists.js
+++ b/app/javascript/mastodon/reducers/user_lists.js
@@ -20,6 +20,14 @@ import {
   MUTES_FETCH_SUCCESS,
   MUTES_EXPAND_SUCCESS,
 } from '../actions/mutes';
+import {
+  DIRECTORY_FETCH_REQUEST,
+  DIRECTORY_FETCH_SUCCESS,
+  DIRECTORY_FETCH_FAIL,
+  DIRECTORY_EXPAND_REQUEST,
+  DIRECTORY_EXPAND_SUCCESS,
+  DIRECTORY_EXPAND_FAIL,
+} from 'mastodon/actions/directory';
 import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 
 const initialState = ImmutableMap({
@@ -74,6 +82,16 @@ export default function userLists(state = initialState, action) {
     return state.setIn(['mutes', 'items'], ImmutableList(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
   case MUTES_EXPAND_SUCCESS:
     return state.updateIn(['mutes', 'items'], list => list.concat(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
+  case DIRECTORY_FETCH_SUCCESS:
+    return state.setIn(['directory', 'items'], ImmutableList(action.accounts.map(item => item.id))).setIn(['directory', 'isLoading'], false);
+  case DIRECTORY_EXPAND_SUCCESS:
+    return state.updateIn(['directory', 'items'], list => list.concat(action.accounts.map(item => item.id))).setIn(['directory', 'isLoading'], false);
+  case DIRECTORY_FETCH_REQUEST:
+  case DIRECTORY_EXPAND_REQUEST:
+    return state.setIn(['directory', 'isLoading'], true);
+  case DIRECTORY_FETCH_FAIL:
+  case DIRECTORY_EXPAND_FAIL:
+    return state.setIn(['directory', 'isLoading'], false);
   default:
     return state;
   }
diff --git a/app/javascript/mastodon/rtl.js b/app/javascript/mastodon/rtl.js
index 00870a15d677b48498a70bd12f22fc6d745494b3..89bed6de88811a5be77b99fbee174c7276898f67 100644
--- a/app/javascript/mastodon/rtl.js
+++ b/app/javascript/mastodon/rtl.js
@@ -20,6 +20,7 @@ export function isRtl(text) {
   text = text.replace(/(?:^|[^\/\w])@([a-z0-9_]+(@[a-z0-9\.\-]+)?)/ig, '');
   text = text.replace(/(?:^|[^\/\w])#([\S]+)/ig, '');
   text = text.replace(/\s+/g, '');
+  text = text.replace(/(\w\S+\.\w{2,}\S*)/g, '');
 
   const matches = text.match(rtlChars);
 
diff --git a/app/javascript/mastodon/scroll.js b/app/javascript/mastodon/scroll.js
index 2af07e0fb1f04c727084e0b342d52a3d89b1a14a..84fe582699e9ddfd0547e4403dc30ad045a90262 100644
--- a/app/javascript/mastodon/scroll.js
+++ b/app/javascript/mastodon/scroll.js
@@ -26,5 +26,7 @@ const scroll = (node, key, target) => {
   };
 };
 
-export const scrollRight = (node, position) => scroll(node, 'scrollLeft', position);
-export const scrollTop = (node) => scroll(node, 'scrollTop', 0);
+const isScrollBehaviorSupported = 'scrollBehavior' in document.documentElement.style;
+
+export const scrollRight = (node, position) => isScrollBehaviorSupported ? node.scrollTo({ left: position, behavior: 'smooth' }) : scroll(node, 'scrollLeft', position);
+export const scrollTop = (node) => isScrollBehaviorSupported ? node.scrollTo({ top: 0, behavior: 'smooth' }) : scroll(node, 'scrollTop', 0);
diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js
index c87654547d47feac1cb3c17a4fbec89514cd0b67..6f1ce9602a4986beee09fa21bbc5c5ad60f8771f 100644
--- a/app/javascript/mastodon/selectors/index.js
+++ b/app/javascript/mastodon/selectors/index.js
@@ -128,6 +128,7 @@ export const getAlerts = createSelector([getAlertsBase], (base) => {
   base.forEach(item => {
     arr.push({
       message: item.get('message'),
+      message_values: item.get('message_values'),
       title: item.get('title'),
       key: item.get('key'),
       dismissAfter: 5000,
diff --git a/app/javascript/mastodon/utils/log_out.js b/app/javascript/mastodon/utils/log_out.js
new file mode 100644
index 0000000000000000000000000000000000000000..b43417f4b421263c27ca2cd25c9253702acc758a
--- /dev/null
+++ b/app/javascript/mastodon/utils/log_out.js
@@ -0,0 +1,33 @@
+import Rails from 'rails-ujs';
+
+export const logOut = () => {
+  const form = document.createElement('form');
+
+  const methodInput = document.createElement('input');
+  methodInput.setAttribute('name', '_method');
+  methodInput.setAttribute('value', 'delete');
+  methodInput.setAttribute('type', 'hidden');
+  form.appendChild(methodInput);
+
+  const csrfToken = Rails.csrfToken();
+  const csrfParam = Rails.csrfParam();
+
+  if (csrfParam && csrfToken) {
+    const csrfInput = document.createElement('input');
+    csrfInput.setAttribute('name', csrfParam);
+    csrfInput.setAttribute('value', csrfToken);
+    csrfInput.setAttribute('type', 'hidden');
+    form.appendChild(csrfInput);
+  }
+
+  const submitButton = document.createElement('input');
+  submitButton.setAttribute('type', 'submit');
+  form.appendChild(submitButton);
+
+  form.method = 'post';
+  form.action = '/auth/sign_out';
+  form.style.display = 'none';
+
+  document.body.appendChild(form);
+  submitButton.click();
+};
diff --git a/app/javascript/mastodon/utils/numbers.js b/app/javascript/mastodon/utils/numbers.js
index fdd8269ae27870509808d51ecd054a1196e5cfd0..f7e4ceb9354e50ca340f3a20ca7750de1acb76aa 100644
--- a/app/javascript/mastodon/utils/numbers.js
+++ b/app/javascript/mastodon/utils/numbers.js
@@ -4,7 +4,9 @@ import { FormattedNumber } from 'react-intl';
 export const shortNumberFormat = number => {
   if (number < 1000) {
     return <FormattedNumber value={number} />;
-  } else {
+  } else if (number < 1000000) {
     return <Fragment><FormattedNumber value={number / 1000} maximumFractionDigits={1} />K</Fragment>;
+  } else {
+    return <Fragment><FormattedNumber value={number / 1000000} maximumFractionDigits={1} />M</Fragment>;
   }
 };
diff --git a/app/javascript/mastodon/utils/resize_image.js b/app/javascript/mastodon/utils/resize_image.js
index a8ec5f3fa767286042c9da1084dca64e960584af..7196dc96b1e9b26ab3db09072188f294529e4249 100644
--- a/app/javascript/mastodon/utils/resize_image.js
+++ b/app/javascript/mastodon/utils/resize_image.js
@@ -31,7 +31,7 @@ const loadImage = inputFile => new Promise((resolve, reject) => {
 });
 
 const getOrientation = (img, type = 'image/png') => new Promise(resolve => {
-  if (!['image/jpeg', 'image/webp'].includes(type)) {
+  if (type !== 'image/jpeg') {
     resolve(1);
     return;
   }
@@ -71,7 +71,7 @@ const processImage = (img, { width, height, orientation, type = 'image/png' }) =
   // and return an all-white image instead. Assume reading failed if the resized
   // image is perfectly white.
   const imageData = context.getImageData(0, 0, width, height);
-  if (imageData.every(value => value === 255)) {
+  if (imageData.data.every(value => value === 255)) {
     throw 'Failed to read from canvas';
   }
 
diff --git a/app/javascript/mastodon/utils/scrollbar.js b/app/javascript/mastodon/utils/scrollbar.js
new file mode 100644
index 0000000000000000000000000000000000000000..6f0ee010b5d02f9b8657e0009dcc88e90e9d4c35
--- /dev/null
+++ b/app/javascript/mastodon/utils/scrollbar.js
@@ -0,0 +1,36 @@
+import { isMobile } from '../is_mobile';
+
+/** @type {number | null} */
+let cachedScrollbarWidth = null;
+
+/**
+ * @return {number}
+ */
+const getActualScrollbarWidth = () => {
+  const outer = document.createElement('div');
+  outer.style.visibility = 'hidden';
+  outer.style.overflow = 'scroll';
+  document.body.appendChild(outer);
+
+  const inner = document.createElement('div');
+  outer.appendChild(inner);
+
+  const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
+  outer.parentNode.removeChild(outer);
+
+  return scrollbarWidth;
+};
+
+/**
+ * @return {number}
+ */
+export const getScrollbarWidth = () => {
+  if (cachedScrollbarWidth !== null) {
+    return cachedScrollbarWidth;
+  }
+
+  const scrollbarWidth = isMobile(window.innerWidth) ? 0 : getActualScrollbarWidth();
+  cachedScrollbarWidth = scrollbarWidth;
+
+  return scrollbarWidth;
+};
diff --git a/app/javascript/packs/admin.js b/app/javascript/packs/admin.js
index f0c0ee0b74eb10ccf67a8ed65c703ba89ae3893b..42c747d2efc50e379edee125c2717fdf5dbfb5d8 100644
--- a/app/javascript/packs/admin.js
+++ b/app/javascript/packs/admin.js
@@ -1,4 +1,5 @@
 import { delegate } from 'rails-ujs';
+import ready from '../mastodon/ready';
 
 const batchCheckboxClassName = '.batch-checkbox input[type="checkbox"]';
 
@@ -29,7 +30,7 @@ delegate(document, '.media-spoiler-hide-button', 'click', () => {
   });
 });
 
-delegate(document, '#domain_block_severity', 'change', ({ target }) => {
+const onDomainBlockSeverityChange = (target) => {
   const rejectMediaDiv   = document.querySelector('.input.with_label.domain_block_reject_media');
   const rejectReportsDiv = document.querySelector('.input.with_label.domain_block_reject_reports');
 
@@ -40,4 +41,11 @@ delegate(document, '#domain_block_severity', 'change', ({ target }) => {
   if (rejectReportsDiv) {
     rejectReportsDiv.style.display = (target.value === 'suspend') ? 'none' : 'block';
   }
+};
+
+delegate(document, '#domain_block_severity', 'change', ({ target }) => onDomainBlockSeverityChange(target));
+
+ready(() => {
+  const input = document.getElementById('domain_block_severity');
+  if (input) onDomainBlockSeverityChange(input);
 });
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index 132a9766ec7870013c326cf668ff0191beb6aa7f..ed713f335ae7ac6d685ae9a66e5799aca4f0f223 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -31,10 +31,10 @@ function main() {
   const React = require('react');
   const ReactDOM = require('react-dom');
   const Rellax = require('rellax');
-  const createHistory = require('history').createBrowserHistory;
+  const { createBrowserHistory } = require('history');
 
   const scrollToDetailedStatus = () => {
-    const history = createHistory();
+    const history = createBrowserHistory();
     const detailedStatuses = document.querySelectorAll('.public-layout .detailed-status');
     const location = history.location;
 
@@ -115,14 +115,6 @@ function main() {
       new Rellax('.parallax', { speed: -1 });
     }
 
-    if (document.body.classList.contains('with-modals')) {
-      const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
-      const scrollbarWidthStyle = document.createElement('style');
-      scrollbarWidthStyle.id = 'scrollbar-width';
-      document.head.appendChild(scrollbarWidthStyle);
-      scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0);
-    }
-
     delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original'));
     delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
   });
@@ -149,6 +141,15 @@ function main() {
     return false;
   });
 
+  delegate(document, '.blocks-table button.icon-button', 'click', function(e) {
+    e.preventDefault();
+
+    const classList = this.firstElementChild.classList;
+    classList.toggle('fa-chevron-down');
+    classList.toggle('fa-chevron-up');
+    this.parentElement.parentElement.nextElementSibling.classList.toggle('hidden');
+  });
+
   delegate(document, '.modal-button', 'click', e => {
     e.preventDefault();
 
@@ -246,6 +247,16 @@ function main() {
 
     input.readonly = oldReadOnly;
   });
+
+  delegate(document, '.sidebar__toggle__icon', 'click', () => {
+    const target = document.querySelector('.sidebar ul');
+
+    if (target.style.display === 'block') {
+      target.style.display = 'none';
+    } else {
+      target.style.display = 'block';
+    }
+  });
 }
 
 loadPolyfills().then(main).catch(error => {
diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss
index 6db3bc3dc9b0968420045b16e6bed3586ec84dc3..8ebc45b62d1ae84b3ba34d9323e718160352f52b 100644
--- a/app/javascript/styles/application.scss
+++ b/app/javascript/styles/application.scss
@@ -13,7 +13,7 @@
 @import 'mastodon/widgets';
 @import 'mastodon/forms';
 @import 'mastodon/accounts';
-@import 'mastodon/stream_entries';
+@import 'mastodon/statuses';
 @import 'mastodon/boost';
 @import 'mastodon/components';
 @import 'mastodon/polls';
diff --git a/app/javascript/styles/mailer.scss b/app/javascript/styles/mailer.scss
index b4fb1d709c02dff57bc31f6dc4eca83f44409f1b..e25a80c043287ba3aaae16fdbbd134bdb0a3cdc8 100644
--- a/app/javascript/styles/mailer.scss
+++ b/app/javascript/styles/mailer.scss
@@ -457,6 +457,13 @@ h5 {
 .status {
   padding-bottom: 32px;
 
+  &--highlighted {
+    border: 1px solid lighten($ui-base-color, 8%);
+    border-radius: 4px;
+    padding-bottom: 16px;
+    margin-bottom: 16px;
+  }
+
   .status-header {
     td {
       font-size: 14px;
diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index ee8a7d265a10afe85de1568e708191e00161650c..05e52966b03faae93ca712e555e43a00854c3cff 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -14,15 +14,49 @@ html {
   }
 }
 
+.status-card__actions button,
+.status-card__actions a {
+  color: rgba($white, 0.8);
+
+  &:hover,
+  &:active,
+  &:focus {
+    color: $white;
+  }
+}
+
 // Change default background colors of columns
 .column > .scrollable,
 .getting-started,
-.column-inline-form {
+.column-inline-form,
+.error-column,
+.regeneration-indicator {
   background: $white;
   border: 1px solid lighten($ui-base-color, 8%);
   border-top: 0;
 }
 
+.directory__card__img {
+  background: lighten($ui-base-color, 12%);
+}
+
+.filter-form,
+.directory__card__bar {
+  background: $white;
+  border-bottom: 1px solid lighten($ui-base-color, 8%);
+}
+
+.scrollable .directory__list {
+  width: calc(100% + 2px);
+  margin-left: -1px;
+  margin-right: -1px;
+}
+
+.directory__card,
+.table-of-contents {
+  border: 1px solid lighten($ui-base-color, 8%);
+}
+
 .column-back-button,
 .column-header {
   background: $white;
@@ -33,16 +67,16 @@ html {
   }
 
   &--slim-button {
-    border: 0;
-    top: -49px;
-    right: 1px;
+    top: -50px;
+    right: 0;
   }
 }
 
 .column-header__back-button,
 .column-header__button,
 .column-header__button.active,
-.account__header__bar {
+.account__header__bar,
+.directory__card__extra {
   background: $white;
 }
 
@@ -66,6 +100,19 @@ html {
   text-decoration: underline;
 }
 
+.confirmation-modal__secondary-button,
+.confirmation-modal__cancel-button,
+.mute-modal__cancel-button,
+.block-modal__cancel-button {
+  color: lighten($ui-base-color, 26%);
+
+  &:hover,
+  &:focus,
+  &:active {
+    color: $primary-text-color;
+  }
+}
+
 .column-subheading {
   background: darken($ui-base-color, 4%);
   border-bottom: 1px solid lighten($ui-base-color, 8%);
@@ -104,7 +151,8 @@ html {
 .box-widget input[type="email"],
 .box-widget input[type="password"],
 .box-widget textarea,
-.statuses-grid .detailed-status {
+.statuses-grid .detailed-status,
+.audio-player {
   border: 1px solid lighten($ui-base-color, 8%);
 }
 
@@ -309,11 +357,23 @@ html {
 .boost-modal,
 .confirmation-modal,
 .mute-modal,
+.block-modal,
 .report-modal,
 .embed-modal,
 .error-modal,
-.onboarding-modal {
-  background: $ui-base-color;
+.onboarding-modal,
+.report-modal__comment .setting-text__wrapper,
+.report-modal__comment .setting-text {
+  background: $white;
+  border: 1px solid lighten($ui-base-color, 8%);
+}
+
+.report-modal__comment {
+  border-right-color: lighten($ui-base-color, 8%);
+}
+
+.report-modal__container {
+  border-top-color: lighten($ui-base-color, 8%);
 }
 
 .column-header__collapsible-inner {
@@ -322,9 +382,14 @@ html {
   border-top: 0;
 }
 
+.focal-point__preview strong {
+  color: $white;
+}
+
 .boost-modal__action-bar,
 .confirmation-modal__action-bar,
 .mute-modal__action-bar,
+.block-modal__action-bar,
 .onboarding-modal__paginator,
 .error-modal__footer {
   background: darken($ui-base-color, 6%);
@@ -345,9 +410,11 @@ html {
 
 .embed-modal .embed-modal__container .embed-modal__html {
   background: $white;
+  border: 1px solid lighten($ui-base-color, 8%);
 
   &:focus {
-    background: darken($ui-base-color, 6%);
+    border-color: lighten($ui-base-color, 12%);
+    background: $white;
   }
 }
 
@@ -700,3 +767,10 @@ html {
 .compose-form .compose-form__warning {
   box-shadow: none;
 }
+
+.audio-player .video-player__controls button,
+.audio-player .video-player__time-sep,
+.audio-player .video-player__time-current,
+.audio-player .video-player__time-total {
+  color: $primary-text-color;
+}
diff --git a/app/javascript/styles/mastodon-light/variables.scss b/app/javascript/styles/mastodon-light/variables.scss
index 01748148f91c54d637958b4df13b4a10f8d50612..c689445286eae9eff3d2c958b1c6ab644a0448b4 100644
--- a/app/javascript/styles/mastodon-light/variables.scss
+++ b/app/javascript/styles/mastodon-light/variables.scss
@@ -8,7 +8,7 @@ $classic-secondary-color: #d9e1e8;
 $classic-highlight-color: #2b90d9;
 
 // Differences
-$success-green: #3c754d;
+$success-green: lighten(#3c754d, 8%);
 
 $base-overlay-background: $white !default;
 $valid-value-color: $success-green !default;
diff --git a/app/javascript/styles/mastodon/_mixins.scss b/app/javascript/styles/mastodon/_mixins.scss
index faaffb30f47f37afc2cb108e8d730d18a7b26e31..68cad0fde31787b36485cb645bc1b0c29d87e547 100644
--- a/app/javascript/styles/mastodon/_mixins.scss
+++ b/app/javascript/styles/mastodon/_mixins.scss
@@ -22,24 +22,6 @@
   color: $darker-text-color;
   font-size: 14px;
   margin: 0;
-
-  &::-moz-focus-inner {
-    border: 0;
-  }
-
-  &::-moz-focus-inner,
-  &:focus,
-  &:active {
-    outline: 0 !important;
-  }
-
-  &:focus {
-    background: lighten($ui-base-color, 4%);
-  }
-
-  @media screen and (max-width: 600px) {
-    font-size: 16px;
-  }
 }
 
 @mixin search-popout {
diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index 61637ce96721214253df8bc0e2835fe8d81e8e1f..cf16b54ac179acac973d9d4e654f0f8f6476e03e 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -17,109 +17,102 @@ $small-breakpoint: 960px;
 
 .rich-formatting {
   font-family: $font-sans-serif, sans-serif;
-  font-size: 16px;
+  font-size: 14px;
   font-weight: 400;
-  font-size: 16px;
-  line-height: 30px;
+  line-height: 1.7;
+  word-wrap: break-word;
   color: $darker-text-color;
-  padding-right: 10px;
 
   a {
     color: $highlight-text-color;
     text-decoration: underline;
+
+    &:hover,
+    &:focus,
+    &:active {
+      text-decoration: none;
+    }
   }
 
   p,
   li {
-    font-family: $font-sans-serif, sans-serif;
-    font-size: 16px;
-    font-weight: 400;
-    font-size: 16px;
-    line-height: 30px;
-    margin-bottom: 12px;
     color: $darker-text-color;
+  }
 
-    a {
-      color: $highlight-text-color;
-      text-decoration: underline;
-    }
+  p {
+    margin-top: 0;
+    margin-bottom: .85em;
 
     &:last-child {
       margin-bottom: 0;
     }
   }
 
-  strong,
-  em {
+  strong {
     font-weight: 700;
-    color: lighten($darker-text-color, 10%);
+    color: $secondary-text-color;
   }
 
-  h1 {
-    font-family: $font-display, sans-serif;
-    font-size: 26px;
-    line-height: 30px;
-    font-weight: 500;
-    margin-bottom: 20px;
+  em {
+    font-style: italic;
     color: $secondary-text-color;
+  }
 
-    small {
-      font-family: $font-sans-serif, sans-serif;
-      display: block;
-      font-size: 18px;
-      font-weight: 400;
-      color: lighten($darker-text-color, 10%);
-    }
+  code {
+    font-size: 0.85em;
+    background: darken($ui-base-color, 8%);
+    border-radius: 4px;
+    padding: 0.2em 0.3em;
   }
 
-  h2 {
+  h1,
+  h2,
+  h3,
+  h4,
+  h5,
+  h6 {
     font-family: $font-display, sans-serif;
-    font-size: 22px;
-    line-height: 26px;
+    margin-top: 1.275em;
+    margin-bottom: .85em;
     font-weight: 500;
-    margin-bottom: 20px;
     color: $secondary-text-color;
   }
 
+  h1 {
+    font-size: 2em;
+  }
+
+  h2 {
+    font-size: 1.75em;
+  }
+
   h3 {
-    font-family: $font-display, sans-serif;
-    font-size: 18px;
-    line-height: 24px;
-    font-weight: 500;
-    margin-bottom: 20px;
-    color: $secondary-text-color;
+    font-size: 1.5em;
   }
 
   h4 {
-    font-family: $font-display, sans-serif;
-    font-size: 16px;
-    line-height: 24px;
-    font-weight: 500;
-    margin-bottom: 20px;
-    color: $secondary-text-color;
+    font-size: 1.25em;
   }
 
-  h5 {
-    font-family: $font-display, sans-serif;
-    font-size: 14px;
-    line-height: 24px;
-    font-weight: 500;
-    margin-bottom: 20px;
-    color: $secondary-text-color;
+  h5,
+  h6 {
+    font-size: 1em;
   }
 
-  h6 {
-    font-family: $font-display, sans-serif;
-    font-size: 12px;
-    line-height: 24px;
-    font-weight: 500;
-    margin-bottom: 20px;
-    color: $secondary-text-color;
+  ul {
+    list-style: disc;
+  }
+
+  ol {
+    list-style: decimal;
   }
 
   ul,
   ol {
-    margin-left: 20px;
+    margin: 0;
+    padding: 0;
+    padding-left: 2em;
+    margin-bottom: 0.85em;
 
     &[type='a'] {
       list-style-type: lower-alpha;
@@ -130,31 +123,74 @@ $small-breakpoint: 960px;
     }
   }
 
-  ul {
-    list-style: disc;
-  }
-
-  ol {
-    list-style: decimal;
-  }
-
-  li > ol,
-  li > ul {
-    margin-top: 6px;
-  }
-
   hr {
     width: 100%;
     height: 0;
     border: 0;
-    border-bottom: 1px solid rgba($ui-base-lighter-color, .6);
-    margin: 20px 0;
+    border-bottom: 1px solid lighten($ui-base-color, 4%);
+    margin: 1.7em 0;
 
     &.spacer {
       height: 1px;
       border: 0;
     }
   }
+
+  table {
+    width: 100%;
+    border-collapse: collapse;
+    break-inside: auto;
+    margin-top: 24px;
+    margin-bottom: 32px;
+
+    thead tr,
+    tbody tr {
+      border-bottom: 1px solid lighten($ui-base-color, 4%);
+      font-size: 1em;
+      line-height: 1.625;
+      font-weight: 400;
+      text-align: left;
+      color: $darker-text-color;
+    }
+
+    thead tr {
+      border-bottom-width: 2px;
+      line-height: 1.5;
+      font-weight: 500;
+      color: $dark-text-color;
+    }
+
+    th,
+    td {
+      padding: 8px;
+      align-self: start;
+      align-items: start;
+      word-break: break-all;
+
+      &.nowrap {
+        width: 25%;
+        position: relative;
+
+        &::before {
+          content: '&nbsp;';
+          visibility: hidden;
+        }
+
+        span {
+          position: absolute;
+          left: 8px;
+          right: 8px;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+      }
+    }
+  }
+
+  & > :first-child {
+    margin-top: 0;
+  }
 }
 
 .information-board {
@@ -416,7 +452,7 @@ $small-breakpoint: 960px;
   }
 
   &__call-to-action {
-    background: darken($ui-base-color, 4%);
+    background: $ui-base-color;
     border-radius: 4px;
     padding: 25px 40px;
     overflow: hidden;
diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss
index f95313a259911cbde597d3def8c893a5e5f49625..5dc067f0e8f6805d5f2c8a6eed3d45a6669c3b06 100644
--- a/app/javascript/styles/mastodon/accounts.scss
+++ b/app/javascript/styles/mastodon/accounts.scss
@@ -224,6 +224,7 @@
 }
 
 .account__header__fields {
+  max-width: 100vw;
   padding: 0;
   margin: 15px -15px -15px;
   border: 0 none;
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 373a1026035e2c0f7b8c780afa009a1ef646b142..dde1d69ba0a54cf44b8a6c84aa7feb72b55dc26f 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -5,21 +5,66 @@ $content-width: 840px;
 .admin-wrapper {
   display: flex;
   justify-content: center;
-  height: 100%;
+  width: 100%;
+  min-height: 100vh;
 
   .sidebar-wrapper {
-    flex: 1 1 $sidebar-width;
-    height: 100%;
-    background: $ui-base-color;
-    display: flex;
-    justify-content: flex-end;
+    min-height: 100vh;
+    overflow: hidden;
+    pointer-events: none;
+    flex: 1 1 auto;
+
+    &__inner {
+      display: flex;
+      justify-content: flex-end;
+      background: $ui-base-color;
+      height: 100%;
+    }
   }
 
   .sidebar {
     width: $sidebar-width;
-    height: 100%;
     padding: 0;
-    overflow-y: auto;
+    pointer-events: auto;
+
+    &__toggle {
+      display: none;
+      background: lighten($ui-base-color, 8%);
+      height: 48px;
+
+      &__logo {
+        flex: 1 1 auto;
+
+        a {
+          display: inline-block;
+          padding: 15px;
+        }
+
+        svg {
+          fill: $primary-text-color;
+          height: 20px;
+          position: relative;
+          bottom: -2px;
+        }
+      }
+
+      &__icon {
+        display: block;
+        color: $darker-text-color;
+        text-decoration: none;
+        flex: 0 0 auto;
+        font-size: 20px;
+        padding: 15px;
+      }
+
+      a {
+        &:hover,
+        &:focus,
+        &:active {
+          background: lighten($ui-base-color, 12%);
+        }
+      }
+    }
 
     .logo {
       display: block;
@@ -52,6 +97,9 @@ $content-width: 840px;
         transition: all 200ms linear;
         transition-property: color, background-color;
         border-radius: 4px 0 0 4px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
 
         i.fa {
           margin-right: 5px;
@@ -99,12 +147,30 @@ $content-width: 840px;
   }
 
   .content-wrapper {
-    flex: 2 1 $content-width;
-    overflow: auto;
+    box-sizing: border-box;
+    width: 100%;
+    max-width: $content-width;
+    flex: 1 1 auto;
+  }
+
+  @media screen and (max-width: $content-width + $sidebar-width) {
+    .sidebar-wrapper--empty {
+      display: none;
+    }
+
+    .sidebar-wrapper {
+      width: $sidebar-width;
+      flex: 0 0 auto;
+    }
+  }
+
+  @media screen and (max-width: $no-columns-breakpoint) {
+    .sidebar-wrapper {
+      width: 100%;
+    }
   }
 
   .content {
-    max-width: $content-width;
     padding: 20px 15px;
     padding-top: 60px;
     padding-left: 25px;
@@ -123,6 +189,12 @@ $content-width: 840px;
       padding-bottom: 40px;
       border-bottom: 1px solid lighten($ui-base-color, 8%);
       margin-bottom: 40px;
+
+      @media screen and (max-width: $no-columns-breakpoint) {
+        border-bottom: 0;
+        padding-bottom: 0;
+        font-weight: 700;
+      }
     }
 
     h3 {
@@ -147,7 +219,7 @@ $content-width: 840px;
       font-size: 16px;
       color: $secondary-text-color;
       line-height: 28px;
-      font-weight: 400;
+      font-weight: 500;
     }
 
     .fields-group h6 {
@@ -176,7 +248,7 @@ $content-width: 840px;
 
     & > p {
       font-size: 14px;
-      line-height: 18px;
+      line-height: 21px;
       color: $secondary-text-color;
       margin-bottom: 20px;
 
@@ -204,61 +276,98 @@ $content-width: 840px;
         border: 0;
       }
     }
-
-    .muted-hint {
-      color: $darker-text-color;
-
-      a {
-        color: $highlight-text-color;
-      }
-    }
-
-    .positive-hint {
-      color: $valid-value-color;
-      font-weight: 500;
-    }
-
-    .negative-hint {
-      color: $error-value-color;
-      font-weight: 500;
-    }
-
-    .neutral-hint {
-      color: $dark-text-color;
-      font-weight: 500;
-    }
   }
 
   @media screen and (max-width: $no-columns-breakpoint) {
     display: block;
-    overflow-y: auto;
-    -webkit-overflow-scrolling: touch;
 
-    .sidebar-wrapper,
-    .content-wrapper {
-      flex: 0 0 auto;
-      height: auto;
-      overflow: initial;
+    .sidebar-wrapper {
+      min-height: 0;
     }
 
     .sidebar {
       width: 100%;
       padding: 0;
       height: auto;
+
+      &__toggle {
+        display: flex;
+      }
+
+      & > ul {
+        display: none;
+      }
+
+      ul a,
+      ul ul a {
+        border-radius: 0;
+        border-bottom: 1px solid lighten($ui-base-color, 4%);
+        transition: none;
+
+        &:hover {
+          transition: none;
+        }
+      }
+
+      ul ul {
+        border-radius: 0;
+      }
+
+      ul .simple-navigation-active-leaf a {
+        border-bottom-color: $ui-highlight-color;
+      }
     }
   }
 }
 
+hr.spacer {
+  width: 100%;
+  border: 0;
+  margin: 20px 0;
+  height: 1px;
+}
+
+body,
+.admin-wrapper .content {
+  .muted-hint {
+    color: $darker-text-color;
+
+    a {
+      color: $highlight-text-color;
+    }
+  }
+
+  .positive-hint {
+    color: $valid-value-color;
+    font-weight: 500;
+  }
+
+  .negative-hint {
+    color: $error-value-color;
+    font-weight: 500;
+  }
+
+  .neutral-hint {
+    color: $dark-text-color;
+    font-weight: 500;
+  }
+
+  .warning-hint {
+    color: $gold-star;
+    font-weight: 500;
+  }
+}
+
 .filters {
   display: flex;
   flex-wrap: wrap;
 
   .filter-subset {
     flex: 0 0 auto;
-    margin: 0 40px 10px 0;
+    margin: 0 40px 20px 0;
 
     &:last-child {
-      margin-bottom: 20px;
+      margin-bottom: 30px;
     }
 
     ul {
@@ -704,3 +813,47 @@ a.name-tag,
   text-overflow: ellipsis;
   vertical-align: middle;
 }
+
+.admin-account-bio {
+  display: flex;
+  flex-wrap: wrap;
+  margin: 0 -5px;
+  margin-top: 20px;
+
+  > div {
+    box-sizing: border-box;
+    padding: 0 5px;
+    margin-bottom: 10px;
+    flex: 1 0 50%;
+  }
+
+  .account__header__fields,
+  .account__header__content {
+    background: lighten($ui-base-color, 8%);
+    border-radius: 4px;
+    height: 100%;
+  }
+
+  .account__header__fields {
+    margin: 0;
+    border: 0;
+
+    a {
+      color: lighten($ui-highlight-color, 8%);
+    }
+
+    dl:first-child .verified {
+      border-radius: 0 4px 0 0;
+    }
+
+    .verified a {
+      color: $valid-value-color;
+    }
+  }
+
+  .account__header__content {
+    box-sizing: border-box;
+    padding: 20px;
+    color: $primary-text-color;
+  }
+}
diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss
index b5a77ce94feb2e5e1cd47acc3eec47cb36a83900..2b10b5ad3c3ab9e526903875ef01e5b14b38863f 100644
--- a/app/javascript/styles/mastodon/basics.scss
+++ b/app/javascript/styles/mastodon/basics.scss
@@ -8,7 +8,7 @@
 
 body {
   font-family: $font-sans-serif, sans-serif;
-  background: darken($ui-base-color, 8%);
+  background: darken($ui-base-color, 7%);
   font-size: 13px;
   line-height: 18px;
   font-weight: 400;
@@ -35,11 +35,19 @@ body {
   }
 
   &.app-body {
-    position: absolute;
-    width: 100%;
-    height: 100%;
     padding: 0;
-    background: $ui-base-color;
+
+    &.layout-single-column {
+      height: auto;
+      min-height: 100vh;
+      overflow-y: scroll;
+    }
+
+    &.layout-multiple-columns {
+      position: absolute;
+      width: 100%;
+      height: 100%;
+    }
 
     &.with-modals--active {
       overflow-y: hidden;
@@ -56,7 +64,6 @@ body {
 
     &--active {
       overflow-y: hidden;
-      margin-right: 13px;
     }
   }
 
@@ -79,9 +86,6 @@ body {
 
   &.admin {
     background: darken($ui-base-color, 4%);
-    position: fixed;
-    width: 100%;
-    height: 100%;
     padding: 0;
   }
 
@@ -131,12 +135,97 @@ button {
 
 .app-holder {
   &,
-  & > div {
+  & > div,
+  & > noscript {
     display: flex;
     width: 100%;
-    height: 100%;
     align-items: center;
     justify-content: center;
     outline: 0 !important;
   }
+
+  & > noscript {
+    height: 100vh;
+  }
+}
+
+.layout-single-column .app-holder {
+  &,
+  & > div {
+    min-height: 100vh;
+  }
+}
+
+.layout-multiple-columns .app-holder {
+  &,
+  & > div {
+    height: 100%;
+  }
+}
+
+.error-boundary,
+.app-holder noscript {
+  flex-direction: column;
+  font-size: 16px;
+  font-weight: 400;
+  line-height: 1.7;
+  color: lighten($error-red, 4%);
+  text-align: center;
+
+  & > div {
+    max-width: 500px;
+  }
+
+  p {
+    margin-bottom: .85em;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  a {
+    color: $highlight-text-color;
+
+    &:hover,
+    &:focus,
+    &:active {
+      text-decoration: none;
+    }
+  }
+
+  &__footer {
+    color: $dark-text-color;
+    font-size: 13px;
+
+    a {
+      color: $dark-text-color;
+    }
+  }
+
+  button {
+    display: inline;
+    border: 0;
+    background: transparent;
+    color: $dark-text-color;
+    font: inherit;
+    padding: 0;
+    margin: 0;
+    line-height: inherit;
+    cursor: pointer;
+    outline: 0;
+    transition: color 300ms linear;
+    text-decoration: underline;
+
+    &:hover,
+    &:focus,
+    &:active {
+      text-decoration: none;
+    }
+
+    &.copied {
+      color: $valid-value-color;
+      transition: none;
+    }
+  }
 }
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index e413b00131b157767ea400be0d3430968541573f..86425c47c4ac42f841a77fb3da2fb3b741bd146f 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -3,6 +3,27 @@
   -ms-overflow-style: -ms-autohiding-scrollbar;
 }
 
+.link-button {
+  display: block;
+  font-size: 15px;
+  line-height: 20px;
+  color: $ui-highlight-color;
+  border: 0;
+  background: transparent;
+  padding: 0;
+  cursor: pointer;
+
+  &:hover,
+  &:active {
+    text-decoration: underline;
+  }
+
+  &:disabled {
+    color: $ui-primary-color;
+    cursor: default;
+  }
+}
+
 .button {
   background-color: $ui-highlight-color;
   border: 10px none;
@@ -129,19 +150,28 @@
   padding: 0;
   color: $action-button-color;
   border: 0;
+  border-radius: 4px;
   background: transparent;
   cursor: pointer;
-  transition: color 100ms ease-in;
+  transition: all 100ms ease-in;
+  transition-property: background-color, color;
 
   &:hover,
   &:active,
   &:focus {
     color: lighten($action-button-color, 7%);
-    transition: color 200ms ease-out;
+    background-color: rgba($action-button-color, 0.15);
+    transition: all 200ms ease-out;
+    transition-property: background-color, color;
+  }
+
+  &:focus {
+    background-color: rgba($action-button-color, 0.3);
   }
 
   &.disabled {
     color: darken($action-button-color, 13%);
+    background-color: transparent;
     cursor: default;
   }
 
@@ -166,10 +196,16 @@
     &:active,
     &:focus {
       color: darken($lighter-text-color, 7%);
+      background-color: rgba($lighter-text-color, 0.15);
+    }
+
+    &:focus {
+      background-color: rgba($lighter-text-color, 0.3);
     }
 
     &.disabled {
       color: lighten($lighter-text-color, 7%);
+      background-color: transparent;
     }
 
     &.active {
@@ -197,6 +233,7 @@
 .text-icon-button {
   color: $lighter-text-color;
   border: 0;
+  border-radius: 4px;
   background: transparent;
   cursor: pointer;
   font-weight: 600;
@@ -204,17 +241,25 @@
   padding: 0 3px;
   line-height: 27px;
   outline: 0;
-  transition: color 100ms ease-in;
+  transition: all 100ms ease-in;
+  transition-property: background-color, color;
 
   &:hover,
   &:active,
   &:focus {
     color: darken($lighter-text-color, 7%);
-    transition: color 200ms ease-out;
+    background-color: rgba($lighter-text-color, 0.15);
+    transition: all 200ms ease-out;
+    transition-property: background-color, color;
+  }
+
+  &:focus {
+    background-color: rgba($lighter-text-color, 0.3);
   }
 
   &.disabled {
     color: lighten($lighter-text-color, 20%);
+    background-color: transparent;
     cursor: default;
   }
 
@@ -376,6 +421,10 @@
     border: 0;
     outline: 0;
 
+    &::placeholder {
+      color: $dark-text-color;
+    }
+
     &:focus {
       outline: 0;
     }
@@ -445,7 +494,8 @@
   }
 
   .autosuggest-account,
-  .autosuggest-emoji {
+  .autosuggest-emoji,
+  .autosuggest-hashtag {
     display: flex;
     flex-direction: row;
     align-items: center;
@@ -454,6 +504,29 @@
     font-size: 14px;
   }
 
+  .autosuggest-hashtag {
+    justify-content: space-between;
+
+    &__name {
+      flex: 1 1 auto;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    strong {
+      font-weight: 500;
+    }
+
+    &__uses {
+      flex: 0 0 auto;
+      text-align: right;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+
   .autosuggest-account-icon,
   .autosuggest-emoji img {
     display: block;
@@ -595,7 +668,8 @@
       }
     }
 
-    .icon-button {
+    .icon-button,
+    .text-icon-button {
       box-sizing: content-box;
       padding: 0 3px;
     }
@@ -603,18 +677,6 @@
     .character-counter__wrapper {
       align-self: center;
       margin-right: 4px;
-
-      .character-counter {
-        cursor: default;
-        font-family: $font-sans-serif, sans-serif;
-        font-size: 14px;
-        font-weight: 600;
-        color: $lighter-text-color;
-
-        &.character-counter--over {
-          color: $warning-red;
-        }
-      }
     }
   }
 
@@ -631,6 +693,18 @@
   }
 }
 
+.character-counter {
+  cursor: default;
+  font-family: $font-sans-serif, sans-serif;
+  font-size: 14px;
+  font-weight: 600;
+  color: $lighter-text-color;
+
+  &.character-counter--over {
+    color: $warning-red;
+  }
+}
+
 .no-reduce-motion .spoiler-input {
   transition: height 0.4s ease, opacity 0.4s ease;
 }
@@ -722,7 +796,7 @@
     white-space: pre-wrap;
 
     &:last-child {
-      margin-bottom: 2px;
+      margin-bottom: 0;
     }
   }
 
@@ -753,6 +827,10 @@
     }
   }
 
+  a.unhandled-link {
+    color: lighten($ui-highlight-color, 8%);
+  }
+
   .status__content__spoiler-link {
     background: $action-button-color;
 
@@ -877,7 +955,8 @@
   opacity: 1;
   animation: fade 150ms linear;
 
-  .video-player {
+  .video-player,
+  .audio-player {
     margin-top: 8px;
   }
 
@@ -972,7 +1051,8 @@
       white-space: normal;
     }
 
-    .video-player {
+    .video-player,
+    .audio-player {
       margin-top: 8px;
       max-width: 250px;
     }
@@ -1083,7 +1163,8 @@
     }
   }
 
-  .video-player {
+  .video-player,
+  .audio-player {
     margin-top: 8px;
   }
 }
@@ -1195,14 +1276,28 @@
 
   &-composite {
     @include avatar-radius;
+    border-radius: 50%;
     overflow: hidden;
+    position: relative;
+    cursor: default;
 
     & > div {
-      @include avatar-radius;
       float: left;
       position: relative;
       box-sizing: border-box;
     }
+
+    &__label {
+      display: block;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      color: $primary-text-color;
+      text-shadow: 1px 1px 2px $base-shadow-color;
+      font-weight: 700;
+      font-size: 15px;
+    }
   }
 }
 
@@ -1391,6 +1486,10 @@ a.account__display-name {
   color: inherit;
 }
 
+.detailed-status .button.logo-button {
+  margin-bottom: 15px;
+}
+
 .detailed-status__display-name {
   color: $secondary-text-color;
   display: block;
@@ -1434,6 +1533,7 @@ a.account__display-name {
 }
 
 .muted {
+  .status__content,
   .status__content p,
   .status__content a {
     color: $dark-text-color;
@@ -1804,6 +1904,7 @@ a.account__display-name {
     justify-content: center;
     width: 100%;
     height: 100%;
+    min-height: 100vh;
 
     &__pane {
       height: 100%;
@@ -1811,12 +1912,14 @@ a.account__display-name {
       pointer-events: none;
       display: flex;
       justify-content: flex-end;
+      min-width: 285px;
 
       &--start {
         justify-content: flex-start;
       }
 
       &__inner {
+        position: fixed;
         width: 285px;
         pointer-events: auto;
         height: 100%;
@@ -1827,6 +1930,7 @@ a.account__display-name {
       box-sizing: border-box;
       width: 100%;
       max-width: 600px;
+      flex: 0 0 auto;
       display: flex;
       flex-direction: column;
 
@@ -1837,6 +1941,26 @@ a.account__display-name {
   }
 }
 
+.tabs-bar__wrapper {
+  background: darken($ui-base-color, 8%);
+  position: sticky;
+  top: 0;
+  z-index: 2;
+  padding-top: 0;
+
+  @media screen and (min-width: $no-gap-breakpoint) {
+    padding-top: 10px;
+  }
+
+  .tabs-bar {
+    margin-bottom: 0;
+
+    @media screen and (min-width: $no-gap-breakpoint) {
+      margin-bottom: 10px;
+    }
+  }
+}
+
 .react-swipeable-view-container {
   &,
   .columns-area,
@@ -1871,7 +1995,6 @@ a.account__display-name {
   flex-direction: column;
   width: 100%;
   height: 100%;
-  background: darken($ui-base-color, 7%);
 }
 
 .drawer {
@@ -1997,6 +2120,24 @@ a.account__display-name {
     padding: 0;
   }
 
+  .directory__list {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: minmax(0, 50%) minmax(0, 50%);
+
+    @media screen and (max-width: $no-gap-breakpoint) {
+      display: block;
+    }
+  }
+
+  .directory__card {
+    margin-bottom: 0;
+  }
+
+  .filter-form {
+    display: flex;
+  }
+
   .autosuggest-textarea__textarea {
     font-size: 16px;
   }
@@ -2012,8 +2153,17 @@ a.account__display-name {
     top: 15px;
   }
 
+  .scrollable {
+    overflow: visible;
+
+    @supports(display: grid) {
+      contain: content;
+    }
+  }
+
   @media screen and (min-width: $no-gap-breakpoint) {
     padding: 10px 0;
+    padding-top: 0;
   }
 
   @media screen and (min-width: 630px) {
@@ -2021,7 +2171,8 @@ a.account__display-name {
       padding: 15px;
 
       .media-gallery,
-      .video-player {
+      .video-player,
+      .audio-player {
         margin-top: 15px;
       }
     }
@@ -2063,7 +2214,8 @@ a.account__display-name {
 
       .media-gallery,
       &__action-bar,
-      .video-player {
+      .video-player,
+      .audio-player {
         margin-top: 10px;
       }
     }
@@ -2128,17 +2280,14 @@ a.account__display-name {
 
 @media screen and (min-width: $no-gap-breakpoint) {
   .tabs-bar {
-    margin: 10px auto;
-    margin-bottom: 0;
     width: 100%;
   }
 
   .react-swipeable-view-container .columns-area--mobile {
-    height: calc(100% - 20px) !important;
+    height: calc(100% - 10px) !important;
   }
 
   .getting-started__wrapper,
-  .getting-started__trends,
   .search {
     margin-bottom: 10px;
   }
@@ -2245,13 +2394,24 @@ a.account__display-name {
   margin-bottom: 10px;
   height: calc(100% - 20px);
   overflow-y: auto;
+  display: flex;
+  flex-direction: column;
+
+  & > a {
+    flex: 0 0 auto;
+  }
 
   hr {
+    flex: 0 0 auto;
     border: 0;
     background: transparent;
     border-top: 1px solid lighten($ui-base-color, 4%);
     margin: 10px 0;
   }
+
+  .flex-spacer {
+    background: transparent;
+  }
 }
 
 .drawer__pager {
@@ -2362,6 +2522,8 @@ a.account__display-name {
 }
 
 .column-back-button {
+  box-sizing: border-box;
+  width: 100%;
   background: lighten($ui-base-color, 4%);
   color: $highlight-text-color;
   cursor: pointer;
@@ -2641,8 +2803,19 @@ a.account__display-name {
   }
 
   &__trends {
-    background: $ui-base-color;
     flex: 0 1 auto;
+    opacity: 1;
+    animation: fade 150ms linear;
+    margin-top: 10px;
+
+    h4 {
+      font-size: 12px;
+      text-transform: uppercase;
+      color: $darker-text-color;
+      padding: 10px;
+      font-weight: 500;
+      border-bottom: 1px solid lighten($ui-base-color, 8%);
+    }
 
     @media screen and (max-height: 810px) {
       .trends__item:nth-child(3) {
@@ -2659,11 +2832,15 @@ a.account__display-name {
     @media screen and (max-height: 670px) {
       display: none;
     }
-  }
 
-  &__scrollable {
-    max-height: 100%;
-    overflow-y: auto;
+    .trends__item {
+      border-bottom: 0;
+      padding: 10px;
+
+      &__current {
+        color: $darker-text-color;
+      }
+    }
   }
 }
 
@@ -2950,37 +3127,27 @@ a.status-card.compact:hover {
   cursor: default;
   display: flex;
   flex: 1 1 auto;
+  flex-direction: column;
   align-items: center;
   justify-content: center;
   padding: 20px;
 
-  & > div {
-    width: 100%;
-    background: transparent;
-    padding-top: 0;
-  }
-
   &__figure {
-    background: url('../images/elephant_ui_working.svg') no-repeat center 0;
-    width: 100%;
-    height: 160px;
-    background-size: contain;
-    position: absolute;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
+    &,
+    img {
+      display: block;
+      width: auto;
+      height: 160px;
+      margin: 0;
+    }
   }
 
-  &.missing-indicator {
+  &--without-header {
     padding-top: 20px + 48px;
-
-    .regeneration-indicator__figure {
-      background-image: url('../images/elephant_ui_disappointed.svg');
-    }
   }
 
   &__label {
-    margin-top: 200px;
+    margin-top: 30px;
 
     strong {
       display: block;
@@ -3300,6 +3467,10 @@ a.status-card.compact:hover {
     height: auto;
   }
 
+  &--click-thru {
+    pointer-events: none;
+  }
+
   &--hidden {
     display: none;
   }
@@ -3328,6 +3499,12 @@ a.status-card.compact:hover {
         background: rgba($base-overlay-background, 0.8);
       }
     }
+
+    &:disabled {
+      .spoiler-button__overlay__label {
+        background: rgba($base-overlay-background, 0.5);
+      }
+    }
   }
 }
 
@@ -3371,6 +3548,28 @@ a.status-card.compact:hover {
   .column-select {
     &__control {
       @include search-input;
+
+      &::placeholder {
+        color: lighten($darker-text-color, 4%);
+      }
+
+      &::-moz-focus-inner {
+        border: 0;
+      }
+
+      &::-moz-focus-inner,
+      &:focus,
+      &:active {
+        outline: 0 !important;
+      }
+
+      &:focus {
+        background: lighten($ui-base-color, 4%);
+      }
+
+      @media screen and (max-width: 600px) {
+        font-size: 16px;
+      }
     }
 
     &__placeholder {
@@ -3884,6 +4083,28 @@ a.status-card.compact:hover {
   padding-right: 30px;
   line-height: 18px;
   font-size: 16px;
+
+  &::placeholder {
+    color: lighten($darker-text-color, 4%);
+  }
+
+  &::-moz-focus-inner {
+    border: 0;
+  }
+
+  &::-moz-focus-inner,
+  &:focus,
+  &:active {
+    outline: 0 !important;
+  }
+
+  &:focus {
+    background: lighten($ui-base-color, 4%);
+  }
+
+  @media screen and (max-width: 600px) {
+    font-size: 16px;
+  }
 }
 
 .search__icon {
@@ -3997,8 +4218,9 @@ a.status-card.compact:hover {
 }
 
 .search-results__info {
-  padding: 10px;
-  color: $secondary-text-color;
+  padding: 20px;
+  color: $darker-text-color;
+  text-align: center;
 }
 
 .modal-root {
@@ -4039,10 +4261,13 @@ a.status-card.compact:hover {
   z-index: 9999;
 }
 
-.video-modal {
+.video-modal__container {
   max-width: 100vw;
   max-height: 100vh;
-  position: relative;
+}
+
+.audio-modal__container {
+  width: 50vw;
 }
 
 .media-modal {
@@ -4142,6 +4367,7 @@ a.status-card.compact:hover {
   }
 
   a {
+    pointer-events: auto;
     text-decoration: none;
     font-weight: 500;
     color: $ui-secondary-color;
@@ -4305,7 +4531,8 @@ a.status-card.compact:hover {
 .confirmation-modal,
 .report-modal,
 .actions-modal,
-.mute-modal {
+.mute-modal,
+.block-modal {
   background: lighten($ui-secondary-color, 8%);
   color: $inverted-text-color;
   border-radius: 8px;
@@ -4359,7 +4586,8 @@ a.status-card.compact:hover {
 
 .boost-modal__action-bar,
 .confirmation-modal__action-bar,
-.mute-modal__action-bar {
+.mute-modal__action-bar,
+.block-modal__action-bar {
   display: flex;
   justify-content: space-between;
   background: $ui-secondary-color;
@@ -4387,11 +4615,13 @@ a.status-card.compact:hover {
   font-size: 14px;
 }
 
-.mute-modal {
+.mute-modal,
+.block-modal {
   line-height: 24px;
 }
 
-.mute-modal .react-toggle {
+.mute-modal .react-toggle,
+.block-modal .react-toggle {
   vertical-align: middle;
 }
 
@@ -4420,7 +4650,8 @@ a.status-card.compact:hover {
   }
 }
 
-.report-modal__statuses {
+.report-modal__statuses,
+.focal-point-modal__content {
   flex: 1 1 auto;
   min-height: 20vh;
   max-height: 80vh;
@@ -4441,6 +4672,12 @@ a.status-card.compact:hover {
   }
 }
 
+.focal-point-modal__content {
+  @media screen and (max-width: 480px) {
+    max-height: 40vh;
+  }
+}
+
 .report-modal__comment {
   padding: 20px;
   border-right: 1px solid $ui-secondary-color;
@@ -4462,32 +4699,72 @@ a.status-card.compact:hover {
     padding: 10px;
     font-family: inherit;
     font-size: 14px;
-    resize: vertical;
+    resize: none;
     border: 0;
     outline: 0;
     border-radius: 4px;
     border: 1px solid $ui-secondary-color;
-    margin-bottom: 20px;
+    min-height: 100px;
+    max-height: 50vh;
+    margin-bottom: 10px;
 
     &:focus {
       border: 1px solid darken($ui-secondary-color, 8%);
     }
-  }
 
-  .setting-toggle {
-    margin-top: 20px;
-    margin-bottom: 24px;
-
-    &__label {
-      color: $inverted-text-color;
-      font-size: 14px;
-    }
-  }
+    &__wrapper {
+      background: $white;
+      border: 1px solid $ui-secondary-color;
+      margin-bottom: 10px;
+      border-radius: 4px;
 
-  @media screen and (max-width: 480px) {
-    padding: 10px;
-    max-width: 100%;
-    order: 2;
+      .setting-text {
+        border: 0;
+        margin-bottom: 0;
+        border-radius: 0;
+
+        &:focus {
+          border: 0;
+        }
+      }
+
+      &__modifiers {
+        color: $inverted-text-color;
+        font-family: inherit;
+        font-size: 14px;
+        background: $white;
+      }
+    }
+
+    &__toolbar {
+      display: flex;
+      justify-content: space-between;
+      margin-bottom: 20px;
+    }
+  }
+
+  .setting-text-label {
+    display: block;
+    color: $inverted-text-color;
+    font-size: 14px;
+    font-weight: 500;
+    margin-bottom: 10px;
+  }
+
+  .setting-toggle {
+    margin-top: 20px;
+    margin-bottom: 24px;
+
+    &__label {
+      color: $inverted-text-color;
+      font-size: 14px;
+    }
+  }
+
+  @media screen and (max-width: 480px) {
+    padding: 10px;
+    max-width: 100%;
+    order: 2;
 
     .setting-toggle {
       margin-bottom: 4px;
@@ -4555,33 +4832,36 @@ a.status-card.compact:hover {
 }
 
 .confirmation-modal__action-bar,
-.mute-modal__action-bar {
-  .confirmation-modal__secondary-button,
-  .confirmation-modal__cancel-button,
-  .mute-modal__cancel-button {
-    background-color: transparent;
-    color: $lighter-text-color;
-    font-size: 14px;
-    font-weight: 500;
-
-    &:hover,
-    &:focus,
-    &:active {
-      color: darken($lighter-text-color, 4%);
-    }
-  }
-
+.mute-modal__action-bar,
+.block-modal__action-bar {
   .confirmation-modal__secondary-button {
     flex-shrink: 1;
   }
 }
 
+.confirmation-modal__secondary-button,
+.confirmation-modal__cancel-button,
+.mute-modal__cancel-button,
+.block-modal__cancel-button {
+  background-color: transparent;
+  color: $lighter-text-color;
+  font-size: 14px;
+  font-weight: 500;
+
+  &:hover,
+  &:focus,
+  &:active {
+    color: darken($lighter-text-color, 4%);
+    background-color: transparent;
+  }
+}
+
 .confirmation-modal__container,
 .mute-modal__container,
+.block-modal__container,
 .report-modal__target {
   padding: 30px;
   font-size: 16px;
-  text-align: center;
 
   strong {
     font-weight: 500;
@@ -4594,11 +4874,36 @@ a.status-card.compact:hover {
   }
 }
 
+.confirmation-modal__container,
 .report-modal__target {
-  padding: 20px;
+  text-align: center;
+}
+
+.block-modal,
+.mute-modal {
+  &__explanation {
+    margin-top: 20px;
+  }
+
+  .setting-toggle {
+    margin-top: 20px;
+    margin-bottom: 24px;
+    display: flex;
+    align-items: center;
+
+    &__label {
+      color: $inverted-text-color;
+      margin: 0;
+      margin-left: 8px;
+    }
+  }
+}
+
+.report-modal__target {
+  padding: 15px;
 
   .media-modal__close {
-    top: 19px;
+    top: 14px;
     right: 15px;
   }
 }
@@ -4609,6 +4914,7 @@ a.status-card.compact:hover {
   position: absolute;
   top: 0;
   left: 0;
+  z-index: 9999;
 }
 
 .media-gallery__gifv__label {
@@ -4794,70 +5100,64 @@ a.status-card.compact:hover {
 }
 /* End Media Gallery */
 
-/* Status Video Player */
-.status__video-player {
-  background: $base-overlay-background;
-  box-sizing: border-box;
-  cursor: default; /* May not be needed */
-  margin-top: 8px;
-  overflow: hidden;
-  position: relative;
-}
+.detailed,
+.fullscreen {
+  .video-player__volume__current,
+  .video-player__volume::before {
+    bottom: 27px;
+  }
 
-.status__video-player-video {
-  height: 100%;
-  object-fit: cover;
-  position: relative;
-  top: 50%;
-  transform: translateY(-50%);
-  width: 100%;
-  z-index: 1;
-}
+  .video-player__volume__handle {
+    bottom: 23px;
+  }
 
-.status__video-player-expand,
-.status__video-player-mute {
-  color: $primary-text-color;
-  opacity: 0.8;
-  position: absolute;
-  right: 4px;
-  text-shadow: 0 1px 1px $base-shadow-color, 1px 0 1px $base-shadow-color;
 }
 
-.status__video-player-spoiler {
-  display: none;
-  color: $primary-text-color;
-  left: 4px;
-  position: absolute;
-  text-shadow: 0 1px 1px $base-shadow-color, 1px 0 1px $base-shadow-color;
-  top: 4px;
-  z-index: 100;
+.audio-player {
+  box-sizing: border-box;
+  position: relative;
+  background: darken($ui-base-color, 8%);
+  border-radius: 4px;
+  padding-bottom: 44px;
+  direction: ltr;
 
-  &.status__video-player-spoiler--visible {
-    display: block;
+  &.editable {
+    border-radius: 0;
+    height: 100%;
   }
-}
 
-.status__video-player-expand {
-  bottom: 4px;
-  z-index: 100;
-}
+  &__waveform {
+    padding: 15px 0;
+    position: relative;
+    overflow: hidden;
 
-.status__video-player-mute {
-  top: 4px;
-  z-index: 5;
-}
+    &::before {
+      content: "";
+      display: block;
+      position: absolute;
+      border-top: 1px solid lighten($ui-base-color, 4%);
+      width: 100%;
+      height: 0;
+      left: 0;
+      top: calc(50% + 1px);
+    }
+  }
 
-.detailed,
-.fullscreen {
-  .video-player__volume__current,
-  .video-player__volume::before {
-    bottom: 27px;
+  &__progress-placeholder {
+    background-color: rgba(lighten($ui-highlight-color, 8%), 0.5);
   }
 
-  .video-player__volume__handle {
-    bottom: 23px;
+  &__wave-placeholder {
+    background-color: lighten($ui-base-color, 16%);
   }
 
+  .video-player__controls {
+    padding: 0 15px;
+    padding-top: 10px;
+    background: darken($ui-base-color, 8%);
+    border-top: 1px solid lighten($ui-base-color, 4%);
+    border-radius: 0 0 4px 4px;
+  }
 }
 
 .video-player {
@@ -4866,6 +5166,13 @@ a.status-card.compact:hover {
   background: $base-shadow-color;
   max-width: 100%;
   border-radius: 4px;
+  box-sizing: border-box;
+  direction: ltr;
+
+  &.editable {
+    border-radius: 0;
+    height: 100% !important;
+  }
 
   &:focus {
     outline: 0;
@@ -5147,28 +5454,137 @@ a.status-card.compact:hover {
   }
 }
 
-.media-spoiler-video {
-  background-size: cover;
-  background-repeat: no-repeat;
-  background-position: center;
-  cursor: pointer;
-  margin-top: 8px;
-  position: relative;
-  border: 0;
-  display: block;
-}
+.directory {
+  &__list {
+    width: 100%;
+    margin: 10px 0;
+    transition: opacity 100ms ease-in;
 
-.media-spoiler-video-play-icon {
-  border-radius: 100px;
-  color: rgba($primary-text-color, 0.8);
-  font-size: 36px;
-  left: 50%;
-  padding: 5px;
-  position: absolute;
-  top: 50%;
-  transform: translate(-50%, -50%);
+    &.loading {
+      opacity: 0.7;
+    }
+
+    @media screen and (max-width: $no-gap-breakpoint) {
+      margin: 0;
+    }
+  }
+
+  &__card {
+    box-sizing: border-box;
+    margin-bottom: 10px;
+
+    &__img {
+      height: 125px;
+      position: relative;
+      background: darken($ui-base-color, 12%);
+      overflow: hidden;
+
+      img {
+        display: block;
+        width: 100%;
+        height: 100%;
+        margin: 0;
+        object-fit: cover;
+      }
+    }
+
+    &__bar {
+      display: flex;
+      align-items: center;
+      background: lighten($ui-base-color, 4%);
+      padding: 10px;
+
+      &__name {
+        flex: 1 1 auto;
+        display: flex;
+        align-items: center;
+        text-decoration: none;
+        overflow: hidden;
+      }
+
+      &__relationship {
+        width: 23px;
+        min-height: 1px;
+        flex: 0 0 auto;
+      }
+
+      .avatar {
+        flex: 0 0 auto;
+        width: 48px;
+        height: 48px;
+        padding-top: 2px;
+
+        img {
+          width: 100%;
+          height: 100%;
+          display: block;
+          margin: 0;
+          border-radius: 4px;
+          background: darken($ui-base-color, 8%);
+          object-fit: cover;
+        }
+      }
+
+      .display-name {
+        margin-left: 15px;
+        text-align: left;
+
+        strong {
+          font-size: 15px;
+          color: $primary-text-color;
+          font-weight: 500;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+
+        span {
+          display: block;
+          font-size: 14px;
+          color: $darker-text-color;
+          font-weight: 400;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+      }
+    }
+
+    &__extra {
+      background: $ui-base-color;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      .accounts-table__count {
+        width: 33.33%;
+        flex: 0 0 auto;
+        padding: 15px 0;
+      }
+
+      .account__header__content {
+        box-sizing: border-box;
+        padding: 15px 10px;
+        border-bottom: 1px solid lighten($ui-base-color, 8%);
+        width: 100%;
+        min-height: 18px + 30px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+
+        p {
+          display: none;
+
+          &:first-child {
+            display: inline;
+          }
+        }
+
+        br {
+          display: none;
+        }
+      }
+    }
+  }
 }
-/* End Video Player */
 
 .account-gallery__container {
   display: flex;
@@ -5244,6 +5660,73 @@ a.status-card.compact:hover {
       }
     }
   }
+
+  &.directory__section-headline {
+    background: darken($ui-base-color, 2%);
+    border-bottom-color: transparent;
+
+    a,
+    button {
+      &.active {
+        &::before {
+          display: none;
+        }
+
+        &::after {
+          border-color: transparent transparent darken($ui-base-color, 7%);
+        }
+      }
+    }
+  }
+}
+
+.filter-form {
+  background: $ui-base-color;
+
+  &__column {
+    padding: 10px 15px;
+  }
+
+  .radio-button {
+    display: block;
+  }
+}
+
+.radio-button {
+  font-size: 14px;
+  position: relative;
+  display: inline-block;
+  padding: 6px 0;
+  line-height: 18px;
+  cursor: default;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  cursor: pointer;
+
+  input[type=radio],
+  input[type=checkbox] {
+    display: none;
+  }
+
+  &__input {
+    display: inline-block;
+    position: relative;
+    border: 1px solid $ui-primary-color;
+    box-sizing: border-box;
+    width: 18px;
+    height: 18px;
+    flex: 0 0 auto;
+    margin-right: 10px;
+    top: -1px;
+    border-radius: 50%;
+    vertical-align: middle;
+
+    &.checked {
+      border-color: lighten($ui-highlight-color, 8%);
+      background: lighten($ui-highlight-color, 8%);
+    }
+  }
 }
 
 ::-webkit-scrollbar-thumb {
@@ -5368,6 +5851,7 @@ noscript {
 }
 
 .embed-modal {
+  width: auto;
   max-width: 80vw;
   max-height: 80vh;
 
@@ -5398,6 +5882,7 @@ noscript {
       font-size: 14px;
       margin: 0;
       margin-bottom: 15px;
+      border-radius: 4px;
 
       &::-moz-focus-inner {
         border: 0;
@@ -5423,6 +5908,7 @@ noscript {
       max-width: 100%;
       overflow: hidden;
       border: 0;
+      border-radius: 4px;
     }
   }
 }
@@ -5595,27 +6081,26 @@ noscript {
   }
 }
 
-.focal-point-modal {
-  max-width: 80vw;
-  max-height: 80vh;
-  position: relative;
-}
-
 .focal-point {
   position: relative;
-  cursor: pointer;
+  cursor: move;
   overflow: hidden;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: $base-shadow-color;
 
-  &.dragging {
-    cursor: move;
-  }
-
-  img {
-    max-width: 80vw;
+  img,
+  video,
+  canvas {
+    display: block;
     max-height: 80vh;
-    width: auto;
+    width: 100%;
     height: auto;
-    margin: auto;
+    margin: 0;
+    object-fit: contain;
+    background: $base-shadow-color;
   }
 
   &__reticle {
@@ -5635,6 +6120,43 @@ noscript {
     top: 0;
     left: 0;
   }
+
+  &__preview {
+    position: absolute;
+    bottom: 10px;
+    right: 10px;
+    z-index: 2;
+    cursor: move;
+    transition: opacity 0.1s ease;
+
+    &:hover {
+      opacity: 0.5;
+    }
+
+    strong {
+      color: $primary-text-color;
+      font-size: 14px;
+      font-weight: 500;
+      display: block;
+      margin-bottom: 5px;
+    }
+
+    div {
+      border-radius: 4px;
+      box-shadow: 0 0 14px rgba($base-shadow-color, 0.2);
+    }
+  }
+
+  @media screen and (max-width: 480px) {
+    img,
+    video {
+      max-height: 100%;
+    }
+
+    &__preview {
+      display: none;
+    }
+  }
 }
 
 .account__header__content {
@@ -5887,11 +6409,12 @@ noscript {
 
     &__current {
       flex: 0 0 auto;
-      width: 100px;
       font-size: 24px;
       line-height: 36px;
       font-weight: 500;
-      text-align: center;
+      text-align: right;
+      padding-right: 15px;
+      margin-left: 5px;
       color: $secondary-text-color;
     }
 
@@ -5899,55 +6422,78 @@ noscript {
       flex: 0 0 auto;
       width: 50px;
 
-      path {
+      path:first-child {
+        fill: rgba($highlight-text-color, 0.25) !important;
+        fill-opacity: 1 !important;
+      }
+
+      path:last-child {
         stroke: lighten($highlight-text-color, 6%) !important;
       }
     }
   }
 }
 
-.layout-toggle {
+.conversation {
   display: flex;
+  border-bottom: 1px solid lighten($ui-base-color, 8%);
   padding: 5px;
+  padding-bottom: 0;
 
-  button {
-    box-sizing: border-box;
-    flex: 0 0 50%;
-    background: transparent;
-    padding: 5px;
-    border: 0;
-    position: relative;
+  &:focus {
+    background: lighten($ui-base-color, 2%);
+    outline: 0;
+  }
 
-    &:hover,
-    &:focus,
-    &:active {
-      svg path:first-child {
-        fill: lighten($ui-base-color, 16%);
-      }
-    }
+  &__avatar {
+    flex: 0 0 auto;
+    padding: 10px;
+    padding-top: 12px;
   }
 
-  svg {
-    width: 100%;
-    height: auto;
+  &__content {
+    flex: 1 1 auto;
+    padding: 10px 5px;
+    padding-right: 15px;
+    overflow: hidden;
 
-    path:first-child {
-      fill: lighten($ui-base-color, 12%);
+    &__info {
+      overflow: hidden;
+      display: flex;
+      flex-direction: row-reverse;
+      justify-content: space-between;
     }
 
-    path:last-child {
-      fill: darken($ui-base-color, 14%);
+    &__relative-time {
+      font-size: 15px;
+      color: $darker-text-color;
+      padding-left: 15px;
     }
-  }
 
-  &__active {
-    color: $ui-highlight-color;
-    position: absolute;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    background: lighten($ui-base-color, 12%);
-    border-radius: 50%;
-    padding: 0.35rem;
+    &__names {
+      color: $darker-text-color;
+      font-size: 15px;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      margin-bottom: 4px;
+      flex-basis: 90px;
+      flex-grow: 1;
+
+      a {
+        color: $primary-text-color;
+        text-decoration: none;
+
+        &:hover,
+        &:focus,
+        &:active {
+          text-decoration: underline;
+        }
+      }
+    }
+
+    a {
+      word-break: break-word;
+    }
   }
 }
diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss
index 3564bf07b4ddc96e1c6168b6e163cbf5736bd635..319f8c94dad549384f284442d8b3bf38975777fc 100644
--- a/app/javascript/styles/mastodon/containers.scss
+++ b/app/javascript/styles/mastodon/containers.scss
@@ -141,10 +141,71 @@
     grid-row: 3;
   }
 
+  @media screen and (max-width: $no-gap-breakpoint) {
+    grid-gap: 0;
+    grid-template-columns: minmax(0, 100%);
+
+    .column-0 {
+      grid-column: 1;
+    }
+
+    .column-1 {
+      grid-column: 1;
+      grid-row: 3;
+    }
+
+    .column-2 {
+      grid-column: 1;
+      grid-row: 2;
+    }
+
+    .column-3 {
+      grid-column: 1;
+      grid-row: 4;
+    }
+  }
+}
+
+.grid-4 {
+  display: grid;
+  grid-gap: 10px;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  grid-auto-columns: 25%;
+  grid-auto-rows: max-content;
+
+  .column-0 {
+    grid-column: 1 / 5;
+    grid-row: 1;
+  }
+
+  .column-1 {
+    grid-column: 1 / 4;
+    grid-row: 2;
+  }
+
+  .column-2 {
+    grid-column: 4;
+    grid-row: 2;
+  }
+
+  .column-3 {
+    grid-column: 2 / 5;
+    grid-row: 3;
+  }
+
+  .column-4 {
+    grid-column: 1;
+    grid-row: 3;
+  }
+
   .landing-page__call-to-action {
     min-height: 100%;
   }
 
+  .flash-message {
+    margin-bottom: 10px;
+  }
+
   @media screen and (max-width: 738px) {
     grid-template-columns: minmax(0, 50%) minmax(0, 50%);
 
@@ -185,6 +246,11 @@
     }
 
     .column-3 {
+      grid-column: 1;
+      grid-row: 5;
+    }
+
+    .column-4 {
       grid-column: 1;
       grid-row: 4;
     }
@@ -346,6 +412,20 @@
     }
   }
 
+  .directory__card {
+    border-radius: 4px;
+
+    @media screen and (max-width: $no-gap-breakpoint) {
+      border-radius: 0;
+    }
+  }
+
+  .page-header {
+    @media screen and (max-width: $no-gap-breakpoint) {
+      border-bottom: 0;
+    }
+  }
+
   .public-account-header {
     overflow: hidden;
     margin-bottom: 10px;
@@ -749,14 +829,22 @@
     }
   }
 
-  .static-icon-button {
-    color: $action-button-color;
-    font-size: 18px;
+  .directory__list {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: minmax(0, 50%) minmax(0, 50%);
 
-    & > span {
-      font-size: 14px;
-      font-weight: 500;
+    @media screen and (max-width: $no-gap-breakpoint) {
+      display: block;
     }
+
+    .icon-button {
+      font-size: 18px;
+    }
+  }
+
+  .directory__card {
+    margin-bottom: 0;
   }
 
   .card-grid {
diff --git a/app/javascript/styles/mastodon/dashboard.scss b/app/javascript/styles/mastodon/dashboard.scss
index e4564f062cd7bbb97028507eab9083c49c012cbc..c0944d417dd150c0c22e7f08a9c63196307d892f 100644
--- a/app/javascript/styles/mastodon/dashboard.scss
+++ b/app/javascript/styles/mastodon/dashboard.scss
@@ -15,6 +15,8 @@
       padding: 20px;
       background: lighten($ui-base-color, 4%);
       border-radius: 4px;
+      box-sizing: border-box;
+      height: 100%;
     }
 
     & > a {
diff --git a/app/javascript/styles/mastodon/footer.scss b/app/javascript/styles/mastodon/footer.scss
index f74c004e995994b30c89db4e2c1d2807eb1d04b7..00d2908832fbca0a8cfe18715c3ab4b6381c586c 100644
--- a/app/javascript/styles/mastodon/footer.scss
+++ b/app/javascript/styles/mastodon/footer.scss
@@ -128,7 +128,7 @@
       &:hover,
       &:focus,
       &:active {
-        svg path {
+        svg {
           fill: lighten($ui-base-color, 38%);
         }
       }
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 456ee4e0d3d024ea068a75b7266df042bbff64c7..b729d912e539394bc47452d1464515818d6b4092 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -112,6 +112,15 @@ code {
       padding: 0.2em 0.4em;
       background: darken($ui-base-color, 12%);
     }
+
+    li {
+      list-style: disc;
+      margin-left: 18px;
+    }
+  }
+
+  ul.hint {
+    margin-bottom: 15px;
   }
 
   span.hint {
@@ -245,6 +254,10 @@ code {
       &-6 {
         max-width: 50%;
       }
+
+      .actions {
+        margin-top: 27px;
+      }
     }
 
     .fields-group:last-child,
@@ -300,6 +313,13 @@ code {
     }
   }
 
+  .input.static .label_input__wrapper {
+    font-size: 16px;
+    padding: 10px;
+    border: 1px solid $dark-text-color;
+    border-radius: 4px;
+  }
+
   input[type=text],
   input[type=number],
   input[type=email],
@@ -318,6 +338,10 @@ code {
     border-radius: 4px;
     padding: 10px;
 
+    &::placeholder {
+      color: lighten($darker-text-color, 4%);
+    }
+
     &:invalid {
       box-shadow: none;
     }
diff --git a/app/javascript/styles/mastodon/introduction.scss b/app/javascript/styles/mastodon/introduction.scss
index 222d8f60e863b167b5b76326a317b1ad0792f5b4..b44ae730649a847f433b76e2fd4ec7179e416179 100644
--- a/app/javascript/styles/mastodon/introduction.scss
+++ b/app/javascript/styles/mastodon/introduction.scss
@@ -3,9 +3,10 @@
   flex-direction: column;
   justify-content: center;
   align-items: center;
+  height: 100vh;
+  background: $ui-base-color;
 
   @media screen and (max-width: 920px) {
-    background: darken($ui-base-color, 8%);
     display: block !important;
   }
 
diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss
index 12f57b7a9185c8a161b86dd3f04d4622e101be73..f59a9d693a07ab5bd35f455644c03f41b7b1dbb4 100644
--- a/app/javascript/styles/mastodon/polls.scss
+++ b/app/javascript/styles/mastodon/polls.scss
@@ -5,7 +5,6 @@
   li {
     margin-bottom: 10px;
     position: relative;
-    height: 18px + 12px;
   }
 
   &__chart {
@@ -24,13 +23,11 @@
 
   &__text {
     position: relative;
-    display: inline-block;
+    display: flex;
     padding: 6px 0;
     line-height: 18px;
     cursor: default;
-    white-space: nowrap;
     overflow: hidden;
-    text-overflow: ellipsis;
 
     input[type=radio],
     input[type=checkbox] {
@@ -82,6 +79,9 @@
     top: -1px;
     border-radius: 50%;
     vertical-align: middle;
+    margin-top: auto;
+    margin-bottom: auto;
+    flex: 0 0 18px;
 
     &.checkbox {
       border-radius: 4px;
@@ -95,10 +95,19 @@
 
   &__number {
     display: inline-block;
-    width: 36px;
+    width: 52px;
     font-weight: 700;
     padding: 0 10px;
+    padding-left: 8px;
     text-align: right;
+    margin-top: auto;
+    margin-bottom: auto;
+    flex: 0 0 52px;
+  }
+
+  &__vote__mark {
+    float: left;
+    line-height: 18px;
   }
 
   &__footer {
diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss
index 58bc53b141fe716e55195854544ef84b072dcaa8..ecd16625357df482c8ef13556ba9896ff9ae2d05 100644
--- a/app/javascript/styles/mastodon/rtl.scss
+++ b/app/javascript/styles/mastodon/rtl.scss
@@ -7,6 +7,34 @@ body.rtl {
     padding-right: 15px;
   }
 
+  .radio-button__input {
+    margin-right: 0;
+    margin-left: 10px;
+  }
+
+  .directory__card__bar .display-name {
+    margin-left: 0;
+    margin-right: 15px;
+  }
+
+  .display-name {
+    text-align: right;
+  }
+
+  .notification__message {
+    margin-left: 0;
+    margin-right: 68px;
+  }
+
+  .drawer__inner__mastodon > img {
+    transform: scaleX(-1);
+  }
+
+  .notification__favourite-icon-wrapper {
+    left: auto;
+    right: -26px;
+  }
+
   .landing-page__logo {
     margin-right: 0;
     margin-left: 20px;
@@ -135,7 +163,6 @@ body.rtl {
   }
 
   .status__action-bar {
-
     &__counter {
       margin-right: 0;
       margin-left: 11px;
@@ -328,6 +355,12 @@ body.rtl {
     }
   }
 
+  .columns-area--mobile .column,
+  .columns-area--mobile .drawer {
+    padding-left: 0;
+    padding-right: 0;
+  }
+
   .public-layout {
     .header {
       .nav-button {
diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/statuses.scss
similarity index 100%
rename from app/javascript/styles/mastodon/stream_entries.scss
rename to app/javascript/styles/mastodon/statuses.scss
diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss
index 11ac6dfeb1595ca2b325cfc0dd8a92224fc3c5d6..5a6e10aa4908cdb95b207cb230aa1ac762859543 100644
--- a/app/javascript/styles/mastodon/tables.scss
+++ b/app/javascript/styles/mastodon/tables.scss
@@ -180,6 +180,18 @@ a.table-action-link {
     }
   }
 
+  &__form {
+    padding: 16px;
+    border: 1px solid darken($ui-base-color, 8%);
+    border-top: 0;
+    background: $ui-base-color;
+
+    .fields-row {
+      padding-top: 0;
+      margin-bottom: 0;
+    }
+  }
+
   &__row {
     border: 1px solid darken($ui-base-color, 8%);
     border-top: 0;
@@ -210,6 +222,45 @@ a.table-action-link {
       &--unpadded {
         padding: 0;
       }
+
+      &--with-image {
+        display: flex;
+        align-items: center;
+      }
+
+      &__image {
+        flex: 0 0 auto;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        margin-right: 10px;
+
+        .emojione {
+          width: 32px;
+          height: 32px;
+        }
+      }
+
+      &__text {
+        flex: 1 1 auto;
+      }
+
+      &__extra {
+        flex: 0 0 auto;
+        text-align: right;
+        color: $darker-text-color;
+        font-weight: 500;
+      }
+    }
+
+    .directory__tag {
+      margin: 0;
+      width: 100%;
+
+      a {
+        background: transparent;
+        border-radius: 0;
+      }
     }
   }
 
diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss
index acaf5b02404a7bdd0086e6560a24c337e97bcaf2..ca050a8d9931d4ba2de070689a364e0810f5945d 100644
--- a/app/javascript/styles/mastodon/widgets.scss
+++ b/app/javascript/styles/mastodon/widgets.scss
@@ -100,6 +100,16 @@
       background-size: 44px 44px;
     }
   }
+
+  .trends__item {
+    padding: 10px;
+  }
+}
+
+.trends-widget {
+  h4 {
+    color: $darker-text-color;
+  }
 }
 
 .box-widget {
@@ -109,41 +119,52 @@
   box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
 }
 
-.contact-widget,
-.landing-page__information.contact-widget {
-  box-sizing: border-box;
-  padding: 20px;
-  min-height: 100%;
+.placeholder-widget {
+  padding: 16px;
   border-radius: 4px;
-  background: $ui-base-color;
-  box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
+  border: 2px dashed $dark-text-color;
+  text-align: center;
+  color: $darker-text-color;
+  margin-bottom: 10px;
 }
 
 .contact-widget {
+  min-height: 100%;
   font-size: 15px;
   color: $darker-text-color;
   line-height: 20px;
   word-wrap: break-word;
   font-weight: 400;
+  padding: 0;
 
-  strong {
-    font-weight: 500;
+  h4 {
+    padding: 10px;
+    text-transform: uppercase;
+    font-weight: 700;
+    font-size: 13px;
+    color: $darker-text-color;
   }
 
-  p {
-    margin-bottom: 10px;
-
-    &:last-child {
-      margin-bottom: 0;
-    }
+  .account {
+    border-bottom: 0;
+    padding: 10px 0;
+    padding-top: 5px;
   }
 
-  &__mail {
-    margin-top: 10px;
+  & > a {
+    display: inline-block;
+    padding: 10px;
+    padding-top: 0;
+    color: $darker-text-color;
+    text-decoration: none;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
 
-    a {
-      color: $primary-text-color;
-      text-decoration: none;
+    &:hover,
+    &:focus,
+    &:active {
+      text-decoration: underline;
     }
   }
 }
@@ -324,7 +345,8 @@
     &.active h4 {
       &,
       .fa,
-      small {
+      small,
+      .trends__item__current {
         color: $primary-text-color;
       }
     }
@@ -337,6 +359,10 @@
     &.active .avatar-stack .account__avatar {
       border-color: $ui-highlight-color;
     }
+
+    .trends__item__current {
+      padding-right: 0;
+    }
   }
 }
 
@@ -521,6 +547,12 @@ $fluid-breakpoint: $maximum-width + 20px;
   a {
     font-size: 14px;
     line-height: 20px;
+  }
+}
+
+.notice-widget,
+.placeholder-widget {
+  a {
     text-decoration: none;
     font-weight: 500;
     color: $ui-highlight-color;
@@ -532,3 +564,38 @@ $fluid-breakpoint: $maximum-width + 20px;
     }
   }
 }
+
+.table-of-contents {
+  background: darken($ui-base-color, 4%);
+  min-height: 100%;
+  font-size: 14px;
+  border-radius: 4px;
+
+  li a {
+    display: block;
+    font-weight: 500;
+    padding: 15px;
+    overflow: hidden;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    text-decoration: none;
+    color: $primary-text-color;
+    border-bottom: 1px solid lighten($ui-base-color, 4%);
+
+    &:hover,
+    &:focus,
+    &:active {
+      text-decoration: underline;
+    }
+  }
+
+  li:last-child a {
+    border-bottom: 0;
+  }
+
+  li ul {
+    padding-left: 20px;
+    border-bottom: 1px solid lighten($ui-base-color, 4%);
+  }
+}
diff --git a/app/lib/activity_tracker.rb b/app/lib/activity_tracker.rb
index ae3c11b6af5c99e6f852f2ac8d284218839184ed..81303b71584b3a5b43fcfb439435f6e22f47ca2d 100644
--- a/app/lib/activity_tracker.rb
+++ b/app/lib/activity_tracker.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class ActivityTracker
-  EXPIRE_AFTER = 90.days.seconds
+  EXPIRE_AFTER = 6.months.seconds
 
   class << self
     include Redisable
diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb
index 66b5763a9cf5df8af0bc908cc36d1e80291ee144..a4a9baaee40e568c239b752835b80b0f720cbcb1 100644
--- a/app/lib/activitypub/activity.rb
+++ b/app/lib/activitypub/activity.rb
@@ -5,7 +5,7 @@ class ActivityPub::Activity
   include Redisable
 
   SUPPORTED_TYPES = %w(Note Question).freeze
-  CONVERTED_TYPES = %w(Image Video Article Page).freeze
+  CONVERTED_TYPES = %w(Image Audio Video Article Page).freeze
 
   def initialize(json, account, **options)
     @json    = json
diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb
index 1aa6ee9ec2dd72679488bc92994cefc0e5ac8d9f..34c646668b15ade966f24dbf28f094758e97e6ab 100644
--- a/app/lib/activitypub/activity/announce.rb
+++ b/app/lib/activitypub/activity/announce.rb
@@ -40,7 +40,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
   end
 
   def announceable?(status)
-    status.account_id == @account.id || status.public_visibility? || status.unlisted_visibility?
+    status.account_id == @account.id || status.distributable?
   end
 
   def related_to_local_activity?
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 00f0dd42d7ba3817b788052b8820d1a98279561b..76bf9b2e55b244ffe687a017c23f3556d414e555 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -41,8 +41,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 
     resolve_thread(@status)
     fetch_replies(@status)
+    check_for_spam
     distribute(@status)
-    forward_for_reply if @status.public_visibility? || @status.unlisted_visibility?
+    forward_for_reply if @status.distributable?
   end
 
   def find_existing_status
@@ -147,12 +148,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   def process_hashtag(tag)
     return if tag['name'].blank?
 
-    hashtag = tag['name'].gsub(/\A#/, '').mb_chars.downcase
-    hashtag = Tag.where(name: hashtag).first_or_create!(name: hashtag)
-
-    return if @tags.include?(hashtag)
-
-    @tags << hashtag
+    Tag.find_or_create_by_names(tag['name']) do |hashtag|
+      @tags << hashtag unless @tags.include?(hashtag)
+    end
   rescue ActiveRecord::RecordInvalid
     nil
   end
@@ -191,22 +189,25 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     media_attachments = []
 
     as_array(@object['attachment']).each do |attachment|
-      next if attachment['url'].blank?
+      next if attachment['url'].blank? || media_attachments.size >= 4
 
-      href             = Addressable::URI.parse(attachment['url']).normalize.to_s
-      media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint'], blurhash: supported_blurhash?(attachment['blurhash']) ? attachment['blurhash'] : nil)
-      media_attachments << media_attachment
+      begin
+        href             = Addressable::URI.parse(attachment['url']).normalize.to_s
+        media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint'], blurhash: supported_blurhash?(attachment['blurhash']) ? attachment['blurhash'] : nil)
+        media_attachments << media_attachment
 
-      next if unsupported_media_type?(attachment['mediaType']) || skip_download?
+        next if unsupported_media_type?(attachment['mediaType']) || skip_download?
 
-      media_attachment.file_remote_url = href
-      media_attachment.save
+        media_attachment.file_remote_url = href
+        media_attachment.save
+      rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
+        RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id)
+      end
     end
 
     media_attachments
   rescue Addressable::URI::InvalidURIError => e
-    Rails.logger.debug e
-
+    Rails.logger.debug "Invalid URL in attachment: #{e}"
     media_attachments
   end
 
@@ -231,25 +232,40 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
       items    = @object['oneOf']
     end
 
+    voters_count = @object['votersCount']
+
     @account.polls.new(
       multiple: multiple,
       expires_at: expires_at,
       options: items.map { |item| item['name'].presence || item['content'] }.compact,
-      cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 }
+      cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 },
+      voters_count: voters_count
     )
   end
 
   def poll_vote?
     return false if replied_to_status.nil? || replied_to_status.preloadable_poll.nil? || !replied_to_status.local? || !replied_to_status.preloadable_poll.options.include?(@object['name'])
 
-    unless replied_to_status.preloadable_poll.expired?
-      replied_to_status.preloadable_poll.votes.create!(account: @account, choice: replied_to_status.preloadable_poll.options.index(@object['name']), uri: @object['id'])
-      ActivityPub::DistributePollUpdateWorker.perform_in(3.minutes, replied_to_status.id) unless replied_to_status.preloadable_poll.hide_totals?
-    end
+    poll_vote! unless replied_to_status.preloadable_poll.expired?
 
     true
   end
 
+  def poll_vote!
+    poll = replied_to_status.preloadable_poll
+    already_voted = true
+    RedisLock.acquire(poll_lock_options) do |lock|
+      if lock.acquired?
+        already_voted = poll.votes.where(account: @account).exists?
+        poll.votes.create!(account: @account, choice: poll.options.index(@object['name']), uri: @object['id'])
+      else
+        raise Mastodon::RaceConditionError
+      end
+    end
+    increment_voters_count! unless already_voted
+    ActivityPub::DistributePollUpdateWorker.perform_in(3.minutes, replied_to_status.id) unless replied_to_status.preloadable_poll.hide_totals?
+  end
+
   def resolve_thread(status)
     return unless status.reply? && status.thread.nil? && Request.valid_url?(in_reply_to_uri)
     ThreadResolveWorker.perform_async(status.id, in_reply_to_uri)
@@ -406,12 +422,31 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     Account.local.where(username: local_usernames).exists?
   end
 
+  def check_for_spam
+    SpamCheck.perform(@status)
+  end
+
   def forward_for_reply
     return unless @json['signature'].present? && reply_to_local?
     ActivityPub::RawDistributionWorker.perform_async(Oj.dump(@json), replied_to_status.account_id, [@account.preferred_inbox_url])
   end
 
+  def increment_voters_count!
+    poll = replied_to_status.preloadable_poll
+    unless poll.voters_count.nil?
+      poll.voters_count = poll.voters_count + 1
+      poll.save
+    end
+  rescue ActiveRecord::StaleObjectError
+    poll.reload
+    retry
+  end
+
   def lock_options
     { redis: Redis.current, key: "create:#{@object['id']}" }
   end
+
+  def poll_lock_options
+    { redis: Redis.current, key: "vote:#{replied_to_status.poll_id}:#{@account.id}" }
+  end
 end
diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb
index 0eb14b89cef4383dc59e9f0c8884388d39e1a84a..dc9ff580c1ae8b4343d41f38f74a040ff0b99475 100644
--- a/app/lib/activitypub/activity/delete.rb
+++ b/app/lib/activitypub/activity/delete.rb
@@ -13,8 +13,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
 
   def delete_person
     lock_or_return("delete_in_progress:#{@account.id}") do
-      SuspendAccountService.new.call(@account)
-      @account.destroy!
+      SuspendAccountService.new.call(@account, reserve_username: false)
     end
   end
 
@@ -31,7 +30,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
 
     return if @status.nil?
 
-    if @status.public_visibility? || @status.unlisted_visibility?
+    if @status.distributable?
       forward_for_reply
       forward_for_reblogs
     end
@@ -70,7 +69,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
   end
 
   def delete_now!
-    RemoveStatusService.new.call(@status)
+    RemoveStatusService.new.call(@status, redraft: false)
   end
 
   def payload
diff --git a/app/lib/activitypub/activity/follow.rb b/app/lib/activitypub/activity/follow.rb
index 3eb88339aebaa6cf4abbd0c4e857a00a06ebe52a..ec92f4255f6b0295acc9bb457f90fcc22ce23dd6 100644
--- a/app/lib/activitypub/activity/follow.rb
+++ b/app/lib/activitypub/activity/follow.rb
@@ -8,7 +8,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
 
     return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) || @account.requested?(target_account)
 
-    if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved?
+    if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved? || target_account.instance_actor?
       reject_follow_request!(target_account)
       return
     end
@@ -21,7 +21,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
 
     follow_request = FollowRequest.create!(account: @account, target_account: target_account, uri: @json['id'])
 
-    if target_account.locked?
+    if target_account.locked? || @account.silenced?
       NotifyService.new.call(target_account, follow_request)
     else
       AuthorizeFollowService.new.call(@account, target_account)
diff --git a/app/lib/activitypub/activity/move.rb b/app/lib/activitypub/activity/move.rb
index d7a5f595cc1c6707ce247fb6f5dafa1a924f6dd6..12bb82d259a9dac0bafa154330557984c3aebeab 100644
--- a/app/lib/activitypub/activity/move.rb
+++ b/app/lib/activitypub/activity/move.rb
@@ -10,17 +10,16 @@ class ActivityPub::Activity::Move < ActivityPub::Activity
 
     target_account = ActivityPub::FetchRemoteAccountService.new.call(target_uri)
 
-    return if target_account.nil? || !target_account.also_known_as.include?(origin_account.uri)
+    if target_account.nil? || target_account.suspended? || !target_account.also_known_as.include?(origin_account.uri)
+      unmark_as_processing!
+      return
+    end
 
     # In case for some reason we didn't have a redirect for the profile already, set it
-    origin_account.update(moved_to_account: target_account) if origin_account.moved_to_account_id.nil?
+    origin_account.update(moved_to_account: target_account)
 
     # Initiate a re-follow for each follower
-    origin_account.followers.local.select(:id).find_in_batches do |follower_accounts|
-      UnfollowFollowWorker.push_bulk(follower_accounts.map(&:id)) do |follower_account_id|
-        [follower_account_id, origin_account.id, target_account.id]
-      end
-    end
+    MoveWorker.perform_async(origin_account.id, target_account.id)
   end
 
   private
@@ -40,4 +39,8 @@ class ActivityPub::Activity::Move < ActivityPub::Activity
   def mark_as_processing!
     redis.setex("move_in_progress:#{@account.id}", PROCESSING_COOLDOWN, true)
   end
+
+  def unmark_as_processing!
+    redis.del("move_in_progress:#{@account.id}")
+  end
 end
diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb
index c259c96f410fb87c531656c3c6a586f4e985867d..2a8f723334652590055949839a6e17654a77b437 100644
--- a/app/lib/activitypub/adapter.rb
+++ b/app/lib/activitypub/adapter.rb
@@ -20,6 +20,8 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
     focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } },
     identity_proof: { 'toot' => 'http://joinmastodon.org/ns#', 'IdentityProof' => 'toot:IdentityProof' },
     blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' },
+    discoverable: { 'toot' => 'http://joinmastodon.org/ns#', 'discoverable' => 'toot:discoverable' },
+    voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' },
   }.freeze
 
   def self.default_key_transform
@@ -31,21 +33,23 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
   end
 
   def serializable_hash(options = nil)
+    named_contexts     = {}
+    context_extensions = {}
     options         = serialization_options(options)
-    serialized_hash = serializer.serializable_hash(options)
+    serialized_hash = serializer.serializable_hash(options.merge(named_contexts: named_contexts, context_extensions: context_extensions))
+    serialized_hash = serialized_hash.select { |k, _| options[:fields].include?(k) } if options[:fields]
     serialized_hash = self.class.transform_key_casing!(serialized_hash, instance_options)
 
-    { '@context' => serialized_context }.merge(serialized_hash)
+    { '@context' => serialized_context(named_contexts, context_extensions) }.merge(serialized_hash)
   end
 
   private
 
-  def serialized_context
+  def serialized_context(named_contexts_map, context_extensions_map)
     context_array = []
 
-    serializer_options = serializer.send(:instance_options) || {}
-    named_contexts     = [:activitystreams] + serializer._named_contexts.keys + serializer_options.fetch(:named_contexts, {}).keys
-    context_extensions = serializer._context_extensions.keys + serializer_options.fetch(:context_extensions, {}).keys
+    named_contexts     = [:activitystreams] + named_contexts_map.keys
+    context_extensions = context_extensions_map.keys
 
     named_contexts.each do |key|
       context_array << NAMED_CONTEXT_MAP[key]
diff --git a/app/lib/activitypub/serializer.rb b/app/lib/activitypub/serializer.rb
index 07bd8c494612e2117d3d1dd55fc34cd9953ced9b..1fdc7931041bcfe6ad51af744d606749be4da324 100644
--- a/app/lib/activitypub/serializer.rb
+++ b/app/lib/activitypub/serializer.rb
@@ -27,4 +27,12 @@ class ActivityPub::Serializer < ActiveModel::Serializer
       _context_extensions[extension_name] = true
     end
   end
+
+  def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance)
+    unless adapter_options&.fetch(:named_contexts, nil).nil?
+      adapter_options[:named_contexts].merge!(_named_contexts)
+      adapter_options[:context_extensions].merge!(_context_extensions)
+    end
+    super(adapter_options, options, adapter_instance)
+  end
 end
diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb
index 595291342a361ea583adc5cfc9e4b19450c42c42..512272dbebe3bcc7b4647c2396ac07b789494c11 100644
--- a/app/lib/activitypub/tag_manager.rb
+++ b/app/lib/activitypub/tag_manager.rb
@@ -17,7 +17,7 @@ class ActivityPub::TagManager
 
     case target.object_type
     when :person
-      short_account_url(target)
+      target.instance_actor? ? about_more_url(instance_actor: true) : short_account_url(target)
     when :note, :comment, :activity
       return activity_account_status_url(target.account, target) if target.reblog?
       short_account_status_url(target.account, target)
@@ -29,7 +29,7 @@ class ActivityPub::TagManager
 
     case target.object_type
     when :person
-      account_url(target)
+      target.instance_actor? ? instance_actor_url : account_url(target)
     when :note, :comment, :activity
       return activity_account_status_url(target.account, target) if target.reblog?
       account_status_url(target.account, target)
@@ -51,7 +51,7 @@ class ActivityPub::TagManager
   def replies_uri_for(target, page_params = nil)
     raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local?
 
-    replies_account_status_url(target.account, target, page_params)
+    account_status_replies_url(target.account, target, page_params)
   end
 
   # Primary audience of a status
@@ -119,6 +119,7 @@ class ActivityPub::TagManager
 
   def uri_to_local_id(uri, param = :id)
     path_params = Rails.application.routes.recognize_path(uri)
+    path_params[:username] = Rails.configuration.x.local_domain if path_params[:controller] == 'instance_actors'
     path_params[param]
   end
 
diff --git a/app/lib/connection_pool/shared_connection_pool.rb b/app/lib/connection_pool/shared_connection_pool.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2865a4108d4ff34f7f9fa3f8301a81ef67beb5d4
--- /dev/null
+++ b/app/lib/connection_pool/shared_connection_pool.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'connection_pool'
+require_relative './shared_timed_stack'
+
+class ConnectionPool::SharedConnectionPool < ConnectionPool
+  def initialize(options = {}, &block)
+    super(options, &block)
+
+    @available = ConnectionPool::SharedTimedStack.new(@size, &block)
+  end
+
+  delegate :size, :flush, to: :@available
+
+  def with(preferred_tag, options = {})
+    Thread.handle_interrupt(Exception => :never) do
+      conn = checkout(preferred_tag, options)
+
+      begin
+        Thread.handle_interrupt(Exception => :immediate) do
+          yield conn
+        end
+      ensure
+        checkin(preferred_tag)
+      end
+    end
+  end
+
+  def checkout(preferred_tag, options = {})
+    if ::Thread.current[key(preferred_tag)]
+      ::Thread.current[key_count(preferred_tag)] += 1
+      ::Thread.current[key(preferred_tag)]
+    else
+      ::Thread.current[key_count(preferred_tag)] = 1
+      ::Thread.current[key(preferred_tag)] = @available.pop(preferred_tag, options[:timeout] || @timeout)
+    end
+  end
+
+  def checkin(preferred_tag)
+    if ::Thread.current[key(preferred_tag)]
+      if ::Thread.current[key_count(preferred_tag)] == 1
+        @available.push(::Thread.current[key(preferred_tag)])
+        ::Thread.current[key(preferred_tag)] = nil
+      else
+        ::Thread.current[key_count(preferred_tag)] -= 1
+      end
+    else
+      raise ConnectionPool::Error, 'no connections are checked out'
+    end
+
+    nil
+  end
+
+  private
+
+  def key(tag)
+    :"#{@key}-#{tag}"
+  end
+
+  def key_count(tag)
+    :"#{@key_count}-#{tag}"
+  end
+end
diff --git a/app/lib/connection_pool/shared_timed_stack.rb b/app/lib/connection_pool/shared_timed_stack.rb
new file mode 100644
index 0000000000000000000000000000000000000000..14a5285c45753280f0380ad9cbc79efb303e48b9
--- /dev/null
+++ b/app/lib/connection_pool/shared_timed_stack.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+class ConnectionPool::SharedTimedStack
+  def initialize(max = 0, &block)
+    @create_block = block
+    @max          = max
+    @created      = 0
+    @queue        = []
+    @tagged_queue = Hash.new { |hash, key| hash[key] = [] }
+    @mutex        = Mutex.new
+    @resource     = ConditionVariable.new
+  end
+
+  def push(connection)
+    @mutex.synchronize do
+      store_connection(connection)
+      @resource.broadcast
+    end
+  end
+
+  alias << push
+
+  def pop(preferred_tag, timeout = 5.0)
+    deadline = current_time + timeout
+
+    @mutex.synchronize do
+      loop do
+        return fetch_preferred_connection(preferred_tag) unless @tagged_queue[preferred_tag].empty?
+
+        connection = try_create(preferred_tag)
+        return connection if connection
+
+        to_wait = deadline - current_time
+        raise Timeout::Error, "Waited #{timeout} sec" if to_wait <= 0
+
+        @resource.wait(@mutex, to_wait)
+      end
+    end
+  end
+
+  def empty?
+    size.zero?
+  end
+
+  def size
+    @mutex.synchronize do
+      @queue.size
+    end
+  end
+
+  def flush
+    @mutex.synchronize do
+      @queue.delete_if do |connection|
+        delete = !connection.in_use && (connection.dead || connection.seconds_idle >= RequestPool::MAX_IDLE_TIME)
+
+        if delete
+          @tagged_queue[connection.site].delete(connection)
+          connection.close
+          @created -= 1
+        end
+
+        delete
+      end
+    end
+  end
+
+  private
+
+  def try_create(preferred_tag)
+    if @created == @max && !@queue.empty?
+      throw_away_connection = @queue.pop
+      @tagged_queue[throw_away_connection.site].delete(throw_away_connection)
+      @create_block.call(preferred_tag)
+    elsif @created != @max
+      connection = @create_block.call(preferred_tag)
+      @created += 1
+      connection
+    end
+  end
+
+  def fetch_preferred_connection(preferred_tag)
+    connection = @tagged_queue[preferred_tag].pop
+    @queue.delete(connection)
+    connection
+  end
+
+  def current_time
+    Process.clock_gettime(Process::CLOCK_MONOTONIC)
+  end
+
+  def store_connection(connection)
+    @tagged_queue[connection.site].push(connection)
+    @queue.push(connection)
+  end
+end
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index ca3d890a813f7a503a38762ace96313f966131eb..d8b486b6094ef770ee458332625d5d3e809dad60 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -19,7 +19,7 @@ class FeedManager
 
   def filter?(timeline_type, status, receiver_id)
     if timeline_type == :home
-      filter_from_home?(status, receiver_id)
+      filter_from_home?(status, receiver_id, build_crutches(receiver_id, [status]))
     elsif timeline_type == :mentions
       filter_from_mentions?(status, receiver_id)
     else
@@ -29,6 +29,7 @@ class FeedManager
 
   def push_to_home(account, status)
     return false unless add_to_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
+
     trim(:home, account.id)
     PushUpdateWorker.perform_async(account.id, status.id, "timeline:#{account.id}") if push_update_required?("timeline:#{account.id}")
     true
@@ -36,6 +37,7 @@ class FeedManager
 
   def unpush_from_home(account, status)
     return false unless remove_from_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
+
     redis.publish("timeline:#{account.id}", Oj.dump(event: :delete, payload: status.id.to_s))
     true
   end
@@ -46,7 +48,9 @@ class FeedManager
       should_filter &&= !ListAccount.where(list_id: list.id, account_id: status.in_reply_to_account_id).exists?
       return false if should_filter
     end
+
     return false unless add_to_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?)
+
     trim(:list, list.id)
     PushUpdateWorker.perform_async(list.account_id, status.id, "timeline:list:#{list.id}") if push_update_required?("timeline:list:#{list.id}")
     true
@@ -54,6 +58,7 @@ class FeedManager
 
   def unpush_from_list(list, status)
     return false unless remove_from_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?)
+
     redis.publish("timeline:list:#{list.id}", Oj.dump(event: :delete, payload: status.id.to_s))
     true
   end
@@ -63,7 +68,7 @@ class FeedManager
     reblog_key   = key(type, account_id, 'reblogs')
 
     # Remove any items past the MAX_ITEMS'th entry in our feed
-    redis.zremrangebyrank(timeline_key, '0', (-(FeedManager::MAX_ITEMS + 1)).to_s)
+    redis.zremrangebyrank(timeline_key, 0, -(FeedManager::MAX_ITEMS + 1))
 
     # Get the score of the REBLOG_FALLOFF'th item in our feed, and stop
     # tracking anything after it for deduplication purposes.
@@ -85,16 +90,21 @@ class FeedManager
 
   def merge_into_timeline(from_account, into_account)
     timeline_key = key(:home, into_account.id)
-    query        = from_account.statuses.limit(FeedManager::MAX_ITEMS / 4)
+    aggregate    = into_account.user&.aggregates_reblogs?
+    query        = from_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
 
     if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
-      oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true)&.first&.last&.to_i || 0
+      oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
       query = query.where('id > ?', oldest_home_score)
     end
 
-    query.each do |status|
-      next if status.direct_visibility? || status.limited_visibility? || filter?(:home, status, into_account)
-      add_to_feed(:home, into_account.id, status, into_account.user&.aggregates_reblogs?)
+    statuses = query.to_a
+    crutches = build_crutches(into_account.id, statuses)
+
+    statuses.each do |status|
+      next if filter_from_home?(status, into_account, crutches)
+
+      add_to_feed(:home, into_account.id, status, aggregate)
     end
 
     trim(:home, into_account.id)
@@ -120,24 +130,35 @@ class FeedManager
   end
 
   def populate_feed(account)
-    added  = 0
-    limit  = FeedManager::MAX_ITEMS / 2
-    max_id = nil
+    limit        = FeedManager::MAX_ITEMS / 2
+    aggregate    = account.user&.aggregates_reblogs?
+    timeline_key = key(:home, account.id)
 
-    loop do
-      statuses = Status.as_home_timeline(account)
-                       .paginate_by_max_id(limit, max_id)
+    account.statuses.where.not(visibility: :direct).limit(limit).each do |status|
+      add_to_feed(:home, account.id, status, aggregate)
+    end
 
-      break if statuses.empty?
+    account.following.includes(:account_stat).find_each do |target_account|
+      if redis.zcard(timeline_key) >= limit
+        oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
+        last_status_score = Mastodon::Snowflake.id_at(account.last_status_at)
 
-      statuses.each do |status|
-        next if filter_from_home?(status, account)
-        added += 1 if add_to_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
+        # If the feed is full and this account has not posted more recently
+        # than the last item on the feed, then we can skip the whole account
+        # because none of its statuses would stay on the feed anyway
+        next if last_status_score < oldest_home_score
       end
 
-      break unless added.zero?
+      statuses = target_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, reblog: :account).limit(limit)
+      crutches = build_crutches(account.id, statuses)
+
+      statuses.each do |status|
+        next if filter_from_home?(status, account, crutches)
+
+        add_to_feed(:home, account.id, status, aggregate)
+      end
 
-      max_id = statuses.last.id
+      trim(:home, account.id)
     end
   end
 
@@ -152,31 +173,33 @@ class FeedManager
       (context == :home ? Mute.where(account_id: receiver_id, target_account_id: account_ids).any? : Mute.where(account_id: receiver_id, target_account_id: account_ids, hide_notifications: true).any?)
   end
 
-  def filter_from_home?(status, receiver_id)
+  def filter_from_home?(status, receiver_id, crutches)
     return false if receiver_id == status.account_id
     return true  if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
     return true  if phrase_filtered?(status, receiver_id, :home)
 
-    check_for_blocks = status.active_mentions.pluck(:account_id)
+    check_for_blocks = crutches[:active_mentions][status.id] || []
     check_for_blocks.concat([status.account_id])
 
     if status.reblog?
       check_for_blocks.concat([status.reblog.account_id])
-      check_for_blocks.concat(status.reblog.active_mentions.pluck(:account_id))
+      check_for_blocks.concat(crutches[:active_mentions][status.reblog_of_id] || [])
     end
 
-    return true if blocks_or_mutes?(receiver_id, check_for_blocks, :home)
+    return true if check_for_blocks.any? { |target_account_id| crutches[:blocking][target_account_id] || crutches[:muting][target_account_id] }
 
     if status.reply? && !status.in_reply_to_account_id.nil?                                                                      # Filter out if it's a reply
-      should_filter   = !Follow.where(account_id: receiver_id, target_account_id: status.in_reply_to_account_id).exists?         # and I'm not following the person it's a reply to
+      should_filter   = !crutches[:following][status.in_reply_to_account_id]                                                     # and I'm not following the person it's a reply to
       should_filter &&= receiver_id != status.in_reply_to_account_id                                                             # and it's not a reply to me
       should_filter &&= status.account_id != status.in_reply_to_account_id                                                       # and it's not a self-reply
-      return should_filter
+
+      return !!should_filter
     elsif status.reblog?                                                                                                         # Filter out a reblog
-      should_filter   = Follow.where(account_id: receiver_id, target_account_id: status.account_id, show_reblogs: false).exists? # if the reblogger's reblogs are suppressed
-      should_filter ||= Block.where(account_id: status.reblog.account_id, target_account_id: receiver_id).exists?                # or if the author of the reblogged status is blocking me
-      should_filter ||= AccountDomainBlock.where(account_id: receiver_id, domain: status.reblog.account.domain).exists?          # or the author's domain is blocked
-      return should_filter
+      should_filter   = crutches[:hiding_reblogs][status.account_id]                                                             # if the reblogger's reblogs are suppressed
+      should_filter ||= crutches[:blocked_by][status.reblog.account_id]                                                          # or if the author of the reblogged status is blocking me
+      should_filter ||= crutches[:domain_blocking][status.reblog.account.domain]                                                 # or the author's domain is blocked
+
+      return !!should_filter
     end
 
     false
@@ -308,4 +331,31 @@ class FeedManager
 
     redis.zrem(timeline_key, status.id)
   end
+
+  def build_crutches(receiver_id, statuses)
+    crutches = {}
+
+    crutches[:active_mentions] = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact).pluck(:status_id, :account_id).each_with_object({}) { |(id, account_id), mapping| (mapping[id] ||= []).push(account_id) }
+
+    check_for_blocks = statuses.flat_map do |s|
+      arr = crutches[:active_mentions][s.id] || []
+      arr.concat([s.account_id])
+
+      if s.reblog?
+        arr.concat([s.reblog.account_id])
+        arr.concat(crutches[:active_mentions][s.reblog_of_id] || [])
+      end
+
+      arr
+    end
+
+    crutches[:following]       = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:in_reply_to_account_id).compact).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
+    crutches[:hiding_reblogs]  = Follow.where(account_id: receiver_id, target_account_id: statuses.map { |s| s.account_id if s.reblog? }.compact, show_reblogs: false).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
+    crutches[:blocking]        = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
+    crutches[:muting]          = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
+    crutches[:domain_blocking] = AccountDomainBlock.where(account_id: receiver_id, domain: statuses.map { |s| s.reblog&.account&.domain }.compact).pluck(:domain).each_with_object({}) { |domain, mapping| mapping[domain] = true }
+    crutches[:blocked_by]      = Block.where(target_account_id: receiver_id, account_id: statuses.map { |s| s.reblog&.account_id }.compact).pluck(:account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
+
+    crutches
+  end
 end
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
index 6b5a8563ad829dcd883b01454d90fdb467af3300..990b9f63ed809b31ee43090a6c238a662d1f9ca8 100644
--- a/app/lib/formatter.rb
+++ b/app/lib/formatter.rb
@@ -84,8 +84,7 @@ class Formatter
   end
 
   def format_field(account, str, **options)
-    return reformat(str).html_safe unless account.local? # rubocop:disable Rails/OutputSafety
-    html = encode_and_link_urls(str, me: true)
+    html = account.local? ? encode_and_link_urls(str, me: true) : reformat(str)
     html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
     html.html_safe # rubocop:disable Rails/OutputSafety
   end
@@ -294,10 +293,10 @@ class Formatter
   end
 
   def hashtag_html(tag)
-    "<a href=\"#{encode(tag_url(tag.downcase))}\" class=\"mention hashtag\" rel=\"tag\">#<span>#{encode(tag)}</span></a>"
+    "<a href=\"#{encode(tag_url(tag))}\" class=\"mention hashtag\" rel=\"tag\">#<span>#{encode(tag)}</span></a>"
   end
 
   def mention_html(account)
-    "<span class=\"h-card\"><a href=\"#{encode(TagManager.instance.url_for(account))}\" class=\"u-url mention\">@<span>#{encode(account.username)}</span></a></span>"
+    "<span class=\"h-card\"><a href=\"#{encode(ActivityPub::TagManager.instance.url_for(account))}\" class=\"u-url mention\">@<span>#{encode(account.username)}</span></a></span>"
   end
 end
diff --git a/app/lib/nodeinfo/adapter.rb b/app/lib/nodeinfo/adapter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1b48dcb98fadf2a0b96b8ac64e98e2adb1b901a4
--- /dev/null
+++ b/app/lib/nodeinfo/adapter.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class NodeInfo::Adapter < ActiveModelSerializers::Adapter::Attributes
+  def self.default_key_transform
+    :camel_lower
+  end
+end
diff --git a/app/lib/ostatus/activity/base.rb b/app/lib/ostatus/activity/base.rb
deleted file mode 100644
index db70f19980cd584afac66dda7d9d4707ebac4df1..0000000000000000000000000000000000000000
--- a/app/lib/ostatus/activity/base.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-class OStatus::Activity::Base
-  include Redisable
-
-  def initialize(xml, account = nil, **options)
-    @xml     = xml
-    @account = account
-    @options = options
-  end
-
-  def status?
-    [:activity, :note, :comment].include?(type)
-  end
-
-  def verb
-    raw = @xml.at_xpath('./activity:verb', activity: OStatus::TagManager::AS_XMLNS).content
-    OStatus::TagManager::VERBS.key(raw)
-  rescue
-    :post
-  end
-
-  def type
-    raw = @xml.at_xpath('./activity:object-type', activity: OStatus::TagManager::AS_XMLNS).content
-    OStatus::TagManager::TYPES.key(raw)
-  rescue
-    :activity
-  end
-
-  def id
-    @xml.at_xpath('./xmlns:id', xmlns: OStatus::TagManager::XMLNS).content
-  end
-
-  def url
-    link = @xml.xpath('./xmlns:link[@rel="alternate"]', xmlns: OStatus::TagManager::XMLNS).find { |link_candidate| link_candidate['type'] == 'text/html' }
-    link.nil? ? nil : link['href']
-  end
-
-  def activitypub_uri
-    link = @xml.xpath('./xmlns:link[@rel="alternate"]', xmlns: OStatus::TagManager::XMLNS).find { |link_candidate| ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(link_candidate['type']) }
-    link.nil? ? nil : link['href']
-  end
-
-  def activitypub_uri?
-    activitypub_uri.present?
-  end
-
-  private
-
-  def find_status(uri)
-    if OStatus::TagManager.instance.local_id?(uri)
-      local_id = OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Status')
-      return Status.find_by(id: local_id)
-    elsif ActivityPub::TagManager.instance.local_uri?(uri)
-      local_id = ActivityPub::TagManager.instance.uri_to_local_id(uri)
-      return Status.find_by(id: local_id)
-    end
-
-    Status.find_by(uri: uri)
-  end
-
-  def find_activitypub_status(uri, href)
-    tag_matches = /tag:([^,:]+)[^:]*:objectId=([\d]+)/.match(uri)
-    href_matches = %r{/users/([^/]+)}.match(href)
-
-    unless tag_matches.nil? || href_matches.nil?
-      uri = "https://#{tag_matches[1]}/users/#{href_matches[1]}/statuses/#{tag_matches[2]}"
-      Status.find_by(uri: uri)
-    end
-  end
-end
diff --git a/app/lib/ostatus/activity/creation.rb b/app/lib/ostatus/activity/creation.rb
deleted file mode 100644
index 60de712db8adec04c6f63be0a7ce11e243e5b3df..0000000000000000000000000000000000000000
--- a/app/lib/ostatus/activity/creation.rb
+++ /dev/null
@@ -1,219 +0,0 @@
-# frozen_string_literal: true
-
-class OStatus::Activity::Creation < OStatus::Activity::Base
-  def perform
-    if redis.exists("delete_upon_arrival:#{@account.id}:#{id}")
-      Rails.logger.debug "Delete for status #{id} was queued, ignoring"
-      return [nil, false]
-    end
-
-    return [nil, false] if @account.suspended? || invalid_origin?
-
-    RedisLock.acquire(lock_options) do |lock|
-      if lock.acquired?
-        # Return early if status already exists in db
-        @status = find_status(id)
-        return [@status, false] unless @status.nil?
-        @status = process_status
-      else
-        raise Mastodon::RaceConditionError
-      end
-    end
-
-    [@status, true]
-  end
-
-  def process_status
-    Rails.logger.debug "Creating remote status #{id}"
-    cached_reblog = reblog
-    status = nil
-
-    # Skip if the reblogged status is not public
-    return if cached_reblog && !(cached_reblog.public_visibility? || cached_reblog.unlisted_visibility?)
-
-    media_attachments = save_media.take(4)
-
-    ApplicationRecord.transaction do
-      status = Status.create!(
-        uri: id,
-        url: url,
-        account: @account,
-        reblog: cached_reblog,
-        text: content,
-        spoiler_text: content_warning,
-        created_at: published,
-        override_timestamps: @options[:override_timestamps],
-        reply: thread?,
-        language: content_language,
-        visibility: visibility_scope,
-        conversation: find_or_create_conversation,
-        thread: thread? ? find_status(thread.first) || find_activitypub_status(thread.first, thread.second) : nil,
-        media_attachment_ids: media_attachments.map(&:id),
-        sensitive: sensitive?
-      )
-
-      save_mentions(status)
-      save_hashtags(status)
-      save_emojis(status)
-    end
-
-    if thread? && status.thread.nil? && Request.valid_url?(thread.second)
-      Rails.logger.debug "Trying to attach #{status.id} (#{id}) to #{thread.first}"
-      ThreadResolveWorker.perform_async(status.id, thread.second)
-    end
-
-    Rails.logger.debug "Queuing remote status #{status.id} (#{id}) for distribution"
-
-    LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text?
-
-    # Only continue if the status is supposed to have arrived in real-time.
-    # Note that if @options[:override_timestamps] isn't set, the status
-    # may have a lower snowflake id than other existing statuses, potentially
-    # "hiding" it from paginated API calls
-    return status unless @options[:override_timestamps] || status.within_realtime_window?
-
-    DistributionWorker.perform_async(status.id)
-
-    status
-  end
-
-  def content
-    @xml.at_xpath('./xmlns:content', xmlns: OStatus::TagManager::XMLNS).content
-  end
-
-  def content_language
-    @xml.at_xpath('./xmlns:content', xmlns: OStatus::TagManager::XMLNS)['xml:lang']&.presence || 'en'
-  end
-
-  def content_warning
-    @xml.at_xpath('./xmlns:summary', xmlns: OStatus::TagManager::XMLNS)&.content || ''
-  end
-
-  def visibility_scope
-    @xml.at_xpath('./mastodon:scope', mastodon: OStatus::TagManager::MTDN_XMLNS)&.content&.to_sym || :public
-  end
-
-  def published
-    @xml.at_xpath('./xmlns:published', xmlns: OStatus::TagManager::XMLNS).content
-  end
-
-  def thread?
-    !@xml.at_xpath('./thr:in-reply-to', thr: OStatus::TagManager::THR_XMLNS).nil?
-  end
-
-  def thread
-    thr = @xml.at_xpath('./thr:in-reply-to', thr: OStatus::TagManager::THR_XMLNS)
-    [thr['ref'], thr['href']]
-  end
-
-  private
-
-  def sensitive?
-    # OStatus-specific convention (not standard)
-    @xml.xpath('./xmlns:category', xmlns: OStatus::TagManager::XMLNS).any? { |category| category['term'] == 'nsfw' }
-  end
-
-  def find_or_create_conversation
-    uri = @xml.at_xpath('./ostatus:conversation', ostatus: OStatus::TagManager::OS_XMLNS)&.attribute('ref')&.content
-    return if uri.nil?
-
-    if OStatus::TagManager.instance.local_id?(uri)
-      local_id = OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')
-      return Conversation.find_by(id: local_id)
-    end
-
-    Conversation.find_by(uri: uri) || Conversation.create!(uri: uri)
-  end
-
-  def save_mentions(parent)
-    processed_account_ids = []
-
-    @xml.xpath('./xmlns:link[@rel="mentioned"]', xmlns: OStatus::TagManager::XMLNS).each do |link|
-      next if [OStatus::TagManager::TYPES[:group], OStatus::TagManager::TYPES[:collection]].include? link['ostatus:object-type']
-
-      mentioned_account = account_from_href(link['href'])
-
-      next if mentioned_account.nil? || processed_account_ids.include?(mentioned_account.id)
-
-      mentioned_account.mentions.where(status: parent).first_or_create(status: parent)
-
-      # So we can skip duplicate mentions
-      processed_account_ids << mentioned_account.id
-    end
-  end
-
-  def save_hashtags(parent)
-    tags = @xml.xpath('./xmlns:category', xmlns: OStatus::TagManager::XMLNS).map { |category| category['term'] }.select(&:present?)
-    ProcessHashtagsService.new.call(parent, tags)
-  end
-
-  def save_media
-    do_not_download = DomainBlock.reject_media?(@account.domain)
-    media_attachments = []
-
-    @xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: OStatus::TagManager::XMLNS).each do |link|
-      next unless link['href']
-
-      media = MediaAttachment.where(status: nil, remote_url: link['href']).first_or_initialize(account: @account, status: nil, remote_url: link['href'])
-      parsed_url = Addressable::URI.parse(link['href']).normalize
-
-      next if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.empty?
-
-      media.save
-      media_attachments << media
-
-      next if do_not_download
-
-      begin
-        media.file_remote_url = link['href']
-        media.save!
-      rescue ActiveRecord::RecordInvalid
-        next
-      end
-    end
-
-    media_attachments
-  end
-
-  def save_emojis(parent)
-    do_not_download = DomainBlock.reject_media?(parent.account.domain)
-
-    return if do_not_download
-
-    @xml.xpath('./xmlns:link[@rel="emoji"]', xmlns: OStatus::TagManager::XMLNS).each do |link|
-      next unless link['href'] && link['name']
-
-      shortcode = link['name'].delete(':')
-      emoji     = CustomEmoji.find_by(shortcode: shortcode, domain: parent.account.domain)
-
-      next unless emoji.nil?
-
-      emoji = CustomEmoji.new(shortcode: shortcode, domain: parent.account.domain)
-      emoji.image_remote_url = link['href']
-      emoji.save
-    end
-  end
-
-  def account_from_href(href)
-    url = Addressable::URI.parse(href).normalize
-
-    if TagManager.instance.web_domain?(url.host)
-      Account.find_local(url.path.gsub('/users/', ''))
-    else
-      Account.where(uri: href).or(Account.where(url: href)).first || FetchRemoteAccountService.new.call(href)
-    end
-  end
-
-  def invalid_origin?
-    return false unless id.start_with?('http') # Legacy IDs cannot be checked
-
-    needle = Addressable::URI.parse(id).normalized_host
-
-    !(needle.casecmp(@account.domain).zero? ||
-      needle.casecmp(Addressable::URI.parse(@account.remote_url.presence || @account.uri).normalized_host).zero?)
-  end
-
-  def lock_options
-    { redis: Redis.current, key: "create:#{id}" }
-  end
-end
diff --git a/app/lib/ostatus/activity/deletion.rb b/app/lib/ostatus/activity/deletion.rb
deleted file mode 100644
index c98f5ee0ad9b063a55a5c8bda1a3b7eeb9198be2..0000000000000000000000000000000000000000
--- a/app/lib/ostatus/activity/deletion.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-class OStatus::Activity::Deletion < OStatus::Activity::Base
-  def perform
-    Rails.logger.debug "Deleting remote status #{id}"
-
-    status   = Status.find_by(uri: id, account: @account)
-    status ||= Status.find_by(uri: activitypub_uri, account: @account) if activitypub_uri?
-
-    if status.nil?
-      redis.setex("delete_upon_arrival:#{@account.id}:#{id}", 6 * 3_600, id)
-    else
-      RemoveStatusService.new.call(status)
-    end
-  end
-end
diff --git a/app/lib/ostatus/activity/general.rb b/app/lib/ostatus/activity/general.rb
deleted file mode 100644
index 8a6aabc3372edb36b0d68bf12f47cd0c2a53b7b9..0000000000000000000000000000000000000000
--- a/app/lib/ostatus/activity/general.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-class OStatus::Activity::General < OStatus::Activity::Base
-  def specialize
-    special_class&.new(@xml, @account, @options)
-  end
-
-  private
-
-  def special_class
-    case verb
-    when :post
-      OStatus::Activity::Post
-    when :share
-      OStatus::Activity::Share
-    when :delete
-      OStatus::Activity::Deletion
-    end
-  end
-end
diff --git a/app/lib/ostatus/activity/post.rb b/app/lib/ostatus/activity/post.rb
deleted file mode 100644
index 755ed865639a5663da4b5a5737d57ef44b02f75b..0000000000000000000000000000000000000000
--- a/app/lib/ostatus/activity/post.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-class OStatus::Activity::Post < OStatus::Activity::Creation
-  def perform
-    status, just_created = super
-
-    if just_created
-      status.mentions.includes(:account).each do |mention|
-        mentioned_account = mention.account
-        next unless mentioned_account.local?
-        NotifyService.new.call(mentioned_account, mention)
-      end
-    end
-
-    status
-  end
-
-  private
-
-  def reblog
-    nil
-  end
-end
diff --git a/app/lib/ostatus/activity/remote.rb b/app/lib/ostatus/activity/remote.rb
deleted file mode 100644
index 5b204b6d8f0f022278d4f18cea7a36b546be3d94..0000000000000000000000000000000000000000
--- a/app/lib/ostatus/activity/remote.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-class OStatus::Activity::Remote < OStatus::Activity::Base
-  def perform
-    if activitypub_uri?
-      find_status(activitypub_uri) || FetchRemoteStatusService.new.call(url)
-    else
-      find_status(id) || FetchRemoteStatusService.new.call(url)
-    end
-  end
-end
diff --git a/app/lib/ostatus/activity/share.rb b/app/lib/ostatus/activity/share.rb
deleted file mode 100644
index 5ca6014154ba7a564a8d53a196fa2b13c30dfa80..0000000000000000000000000000000000000000
--- a/app/lib/ostatus/activity/share.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-class OStatus::Activity::Share < OStatus::Activity::Creation
-  def perform
-    return if reblog.nil?
-
-    status, just_created = super
-    NotifyService.new.call(reblog.account, status) if reblog.account.local? && just_created
-    status
-  end
-
-  def object
-    @xml.at_xpath('.//activity:object', activity: OStatus::TagManager::AS_XMLNS)
-  end
-
-  private
-
-  def reblog
-    return @reblog if defined? @reblog
-
-    original_status = OStatus::Activity::Remote.new(object).perform
-    return if original_status.nil?
-
-    @reblog = original_status.reblog? ? original_status.reblog : original_status
-  end
-end
diff --git a/app/lib/ostatus/atom_serializer.rb b/app/lib/ostatus/atom_serializer.rb
deleted file mode 100644
index 9a05d96cf9cf721fc4402f227db4c7be050e66ed..0000000000000000000000000000000000000000
--- a/app/lib/ostatus/atom_serializer.rb
+++ /dev/null
@@ -1,378 +0,0 @@
-# frozen_string_literal: true
-
-class OStatus::AtomSerializer
-  include RoutingHelper
-  include ActionView::Helpers::SanitizeHelper
-
-  class << self
-    def render(element)
-      document = Ox::Document.new(version: '1.0')
-      document << element
-      ('<?xml version="1.0"?>' + Ox.dump(element, effort: :tolerant)).force_encoding('UTF-8')
-    end
-  end
-
-  def author(account)
-    author = Ox::Element.new('author')
-
-    uri = OStatus::TagManager.instance.uri_for(account)
-
-    append_element(author, 'id', uri)
-    append_element(author, 'activity:object-type', OStatus::TagManager::TYPES[:person])
-    append_element(author, 'uri', uri)
-    append_element(author, 'name', account.username)
-    append_element(author, 'email', account.local? ? account.local_username_and_domain : account.acct)
-    append_element(author, 'summary', Formatter.instance.simplified_format(account).to_str, type: :html) if account.note?
-    append_element(author, 'link', nil, rel: :alternate, type: 'text/html', href: ::TagManager.instance.url_for(account))
-    append_element(author, 'link', nil, rel: :avatar, type: account.avatar_content_type, 'media:width': 120, 'media:height': 120, href: full_asset_url(account.avatar.url(:original))) if account.avatar?
-    append_element(author, 'link', nil, rel: :header, type: account.header_content_type, 'media:width': 700, 'media:height': 335, href: full_asset_url(account.header.url(:original))) if account.header?
-    account.emojis.each do |emoji|
-      append_element(author, 'link', nil, rel: :emoji, href: full_asset_url(emoji.image.url), name: emoji.shortcode)
-    end
-    append_element(author, 'poco:preferredUsername', account.username)
-    append_element(author, 'poco:displayName', account.display_name) if account.display_name?
-    append_element(author, 'poco:note', account.local? ? account.note : strip_tags(account.note)) if account.note?
-    append_element(author, 'mastodon:scope', account.locked? ? :private : :public)
-
-    author
-  end
-
-  def feed(account, stream_entries)
-    feed = Ox::Element.new('feed')
-
-    add_namespaces(feed)
-
-    append_element(feed, 'id', account_url(account, format: 'atom'))
-    append_element(feed, 'title', account.display_name.presence || account.username)
-    append_element(feed, 'subtitle', account.note)
-    append_element(feed, 'updated', account.updated_at.iso8601)
-    append_element(feed, 'logo', full_asset_url(account.avatar.url(:original)))
-
-    feed << author(account)
-
-    append_element(feed, 'link', nil, rel: :alternate, type: 'text/html', href: ::TagManager.instance.url_for(account))
-    append_element(feed, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_url(account, format: 'atom'))
-    append_element(feed, 'link', nil, rel: :next, type: 'application/atom+xml', href: account_url(account, format: 'atom', max_id: stream_entries.last.id)) if stream_entries.size == 20
-    append_element(feed, 'link', nil, rel: :hub, href: api_push_url)
-    append_element(feed, 'link', nil, rel: :salmon, href: api_salmon_url(account.id))
-
-    stream_entries.each do |stream_entry|
-      feed << entry(stream_entry)
-    end
-
-    feed
-  end
-
-  def entry(stream_entry, root = false)
-    entry = Ox::Element.new('entry')
-
-    add_namespaces(entry) if root
-
-    append_element(entry, 'id', OStatus::TagManager.instance.uri_for(stream_entry.status))
-    append_element(entry, 'published', stream_entry.created_at.iso8601)
-    append_element(entry, 'updated', stream_entry.updated_at.iso8601)
-    append_element(entry, 'title', stream_entry&.status&.title || "#{stream_entry.account.acct} deleted status")
-
-    entry << author(stream_entry.account) if root
-
-    append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[stream_entry.object_type])
-    append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[stream_entry.verb])
-
-    entry << object(stream_entry.target) if stream_entry.targeted?
-
-    if stream_entry.status.nil?
-      append_element(entry, 'content', 'Deleted status')
-    elsif stream_entry.status.destroyed?
-      append_element(entry, 'content', 'Deleted status')
-      append_element(entry, 'link', nil, rel: :alternate, type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(stream_entry.status)) if stream_entry.account.local?
-    else
-      serialize_status_attributes(entry, stream_entry.status)
-    end
-
-    append_element(entry, 'link', nil, rel: :alternate, type: 'text/html', href: ::TagManager.instance.url_for(stream_entry.status))
-    append_element(entry, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_stream_entry_url(stream_entry.account, stream_entry, format: 'atom'))
-    append_element(entry, 'thr:in-reply-to', nil, ref: OStatus::TagManager.instance.uri_for(stream_entry.thread), href: ::TagManager.instance.url_for(stream_entry.thread)) if stream_entry.threaded?
-    append_element(entry, 'ostatus:conversation', nil, ref: conversation_uri(stream_entry.status.conversation)) unless stream_entry&.status&.conversation_id.nil?
-
-    entry
-  end
-
-  def object(status)
-    object = Ox::Element.new('activity:object')
-
-    append_element(object, 'id', OStatus::TagManager.instance.uri_for(status))
-    append_element(object, 'published', status.created_at.iso8601)
-    append_element(object, 'updated', status.updated_at.iso8601)
-    append_element(object, 'title', status.title)
-
-    object << author(status.account)
-
-    append_element(object, 'activity:object-type', OStatus::TagManager::TYPES[status.object_type])
-    append_element(object, 'activity:verb', OStatus::TagManager::VERBS[status.verb])
-
-    serialize_status_attributes(object, status)
-
-    append_element(object, 'link', nil, rel: :alternate, type: 'text/html', href: ::TagManager.instance.url_for(status))
-    append_element(object, 'thr:in-reply-to', nil, ref: OStatus::TagManager.instance.uri_for(status.thread), href: ::TagManager.instance.url_for(status.thread)) unless status.thread.nil?
-    append_element(object, 'ostatus:conversation', nil, ref: conversation_uri(status.conversation)) unless status.conversation_id.nil?
-
-    object
-  end
-
-  def follow_salmon(follow)
-    entry = Ox::Element.new('entry')
-    add_namespaces(entry)
-
-    description = "#{follow.account.acct} started following #{follow.target_account.acct}"
-
-    append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(follow.created_at, follow.id, 'Follow'))
-    append_element(entry, 'title', description)
-    append_element(entry, 'content', description, type: :html)
-
-    entry << author(follow.account)
-
-    append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
-    append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:follow])
-
-    object = author(follow.target_account)
-    object.value = 'activity:object'
-
-    entry << object
-    entry
-  end
-
-  def follow_request_salmon(follow_request)
-    entry = Ox::Element.new('entry')
-    add_namespaces(entry)
-
-    append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(follow_request.created_at, follow_request.id, 'FollowRequest'))
-    append_element(entry, 'title', "#{follow_request.account.acct} requested to follow #{follow_request.target_account.acct}")
-
-    entry << author(follow_request.account)
-
-    append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
-    append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:request_friend])
-
-    object = author(follow_request.target_account)
-    object.value = 'activity:object'
-
-    entry << object
-    entry
-  end
-
-  def authorize_follow_request_salmon(follow_request)
-    entry = Ox::Element.new('entry')
-    add_namespaces(entry)
-
-    append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest'))
-    append_element(entry, 'title', "#{follow_request.target_account.acct} authorizes follow request by #{follow_request.account.acct}")
-
-    entry << author(follow_request.target_account)
-
-    append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
-    append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:authorize])
-
-    object = Ox::Element.new('activity:object')
-    object << author(follow_request.account)
-
-    append_element(object, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
-    append_element(object, 'activity:verb', OStatus::TagManager::VERBS[:request_friend])
-
-    inner_object = author(follow_request.target_account)
-    inner_object.value = 'activity:object'
-
-    object << inner_object
-    entry  << object
-    entry
-  end
-
-  def reject_follow_request_salmon(follow_request)
-    entry = Ox::Element.new('entry')
-    add_namespaces(entry)
-
-    append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest'))
-    append_element(entry, 'title', "#{follow_request.target_account.acct} rejects follow request by #{follow_request.account.acct}")
-
-    entry << author(follow_request.target_account)
-
-    append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
-    append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:reject])
-
-    object = Ox::Element.new('activity:object')
-    object << author(follow_request.account)
-
-    append_element(object, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
-    append_element(object, 'activity:verb', OStatus::TagManager::VERBS[:request_friend])
-
-    inner_object = author(follow_request.target_account)
-    inner_object.value = 'activity:object'
-
-    object << inner_object
-    entry  << object
-    entry
-  end
-
-  def unfollow_salmon(follow)
-    entry = Ox::Element.new('entry')
-    add_namespaces(entry)
-
-    description = "#{follow.account.acct} is no longer following #{follow.target_account.acct}"
-
-    append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(Time.now.utc, follow.id, 'Follow'))
-    append_element(entry, 'title', description)
-    append_element(entry, 'content', description, type: :html)
-
-    entry << author(follow.account)
-
-    append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
-    append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:unfollow])
-
-    object = author(follow.target_account)
-    object.value = 'activity:object'
-
-    entry << object
-    entry
-  end
-
-  def block_salmon(block)
-    entry = Ox::Element.new('entry')
-    add_namespaces(entry)
-
-    description = "#{block.account.acct} no longer wishes to interact with #{block.target_account.acct}"
-
-    append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block'))
-    append_element(entry, 'title', description)
-
-    entry << author(block.account)
-
-    append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
-    append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:block])
-
-    object = author(block.target_account)
-    object.value = 'activity:object'
-
-    entry << object
-    entry
-  end
-
-  def unblock_salmon(block)
-    entry = Ox::Element.new('entry')
-    add_namespaces(entry)
-
-    description = "#{block.account.acct} no longer blocks #{block.target_account.acct}"
-
-    append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block'))
-    append_element(entry, 'title', description)
-
-    entry << author(block.account)
-
-    append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
-    append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:unblock])
-
-    object = author(block.target_account)
-    object.value = 'activity:object'
-
-    entry << object
-    entry
-  end
-
-  def favourite_salmon(favourite)
-    entry = Ox::Element.new('entry')
-    add_namespaces(entry)
-
-    description = "#{favourite.account.acct} favourited a status by #{favourite.status.account.acct}"
-
-    append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(favourite.created_at, favourite.id, 'Favourite'))
-    append_element(entry, 'title', description)
-    append_element(entry, 'content', description, type: :html)
-
-    entry << author(favourite.account)
-
-    append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
-    append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:favorite])
-
-    entry << object(favourite.status)
-
-    append_element(entry, 'thr:in-reply-to', nil, ref: OStatus::TagManager.instance.uri_for(favourite.status), href: ::TagManager.instance.url_for(favourite.status))
-
-    entry
-  end
-
-  def unfavourite_salmon(favourite)
-    entry = Ox::Element.new('entry')
-    add_namespaces(entry)
-
-    description = "#{favourite.account.acct} no longer favourites a status by #{favourite.status.account.acct}"
-
-    append_element(entry, 'id', OStatus::TagManager.instance.unique_tag(Time.now.utc, favourite.id, 'Favourite'))
-    append_element(entry, 'title', description)
-    append_element(entry, 'content', description, type: :html)
-
-    entry << author(favourite.account)
-
-    append_element(entry, 'activity:object-type', OStatus::TagManager::TYPES[:activity])
-    append_element(entry, 'activity:verb', OStatus::TagManager::VERBS[:unfavorite])
-
-    entry << object(favourite.status)
-
-    append_element(entry, 'thr:in-reply-to', nil, ref: OStatus::TagManager.instance.uri_for(favourite.status), href: ::TagManager.instance.url_for(favourite.status))
-
-    entry
-  end
-
-  private
-
-  def append_element(parent, name, content = nil, **attributes)
-    element = Ox::Element.new(name)
-    attributes.each { |k, v| element[k] = sanitize_str(v) }
-    element << sanitize_str(content) unless content.nil?
-    parent  << element
-  end
-
-  def sanitize_str(raw_str)
-    raw_str.to_s
-  end
-
-  def conversation_uri(conversation)
-    return conversation.uri if conversation.uri?
-    OStatus::TagManager.instance.unique_tag(conversation.created_at, conversation.id, 'Conversation')
-  end
-
-  def add_namespaces(parent)
-    parent['xmlns']          = OStatus::TagManager::XMLNS
-    parent['xmlns:thr']      = OStatus::TagManager::THR_XMLNS
-    parent['xmlns:activity'] = OStatus::TagManager::AS_XMLNS
-    parent['xmlns:poco']     = OStatus::TagManager::POCO_XMLNS
-    parent['xmlns:media']    = OStatus::TagManager::MEDIA_XMLNS
-    parent['xmlns:ostatus']  = OStatus::TagManager::OS_XMLNS
-    parent['xmlns:mastodon'] = OStatus::TagManager::MTDN_XMLNS
-  end
-
-  def serialize_status_attributes(entry, status)
-    append_element(entry, 'link', nil, rel: :alternate, type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(status)) if status.account.local?
-
-    append_element(entry, 'summary', status.spoiler_text, 'xml:lang': status.language) if status.spoiler_text?
-    append_element(entry, 'content', Formatter.instance.format(status, inline_poll_options: true).to_str || '.', type: 'html', 'xml:lang': status.language)
-
-    status.active_mentions.sort_by(&:id).each do |mentioned|
-      append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': OStatus::TagManager::TYPES[:person], href: OStatus::TagManager.instance.uri_for(mentioned.account))
-    end
-
-    append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': OStatus::TagManager::TYPES[:collection], href: OStatus::TagManager::COLLECTIONS[:public]) if status.public_visibility?
-
-    status.tags.each do |tag|
-      append_element(entry, 'category', nil, term: tag.name)
-    end
-
-    status.media_attachments.each do |media|
-      append_element(entry, 'link', nil, rel: :enclosure, type: media.file_content_type, length: media.file_file_size, href: full_asset_url(media.file.url(:original, false)))
-    end
-
-    append_element(entry, 'category', nil, term: 'nsfw') if status.sensitive? && status.media_attachments.any?
-    append_element(entry, 'mastodon:scope', status.visibility)
-
-    status.emojis.each do |emoji|
-      append_element(entry, 'link', nil, rel: :emoji, href: full_asset_url(emoji.image.url), name: emoji.shortcode)
-    end
-  end
-end
diff --git a/app/lib/request.rb b/app/lib/request.rb
index e555ae6a109b759255f5333c801345ed64413ccf..42ccc6513d175136fb2008585deecd0d2ce24c76 100644
--- a/app/lib/request.rb
+++ b/app/lib/request.rb
@@ -17,15 +17,22 @@ end
 class Request
   REQUEST_TARGET = '(request-target)'
 
+  # We enforce a 5s timeout on DNS resolving, 5s timeout on socket opening
+  # and 5s timeout on the TLS handshake, meaning the worst case should take
+  # about 15s in total
+  TIMEOUT = { connect: 5, read: 10, write: 10 }.freeze
+
   include RoutingHelper
 
   def initialize(verb, url, **options)
     raise ArgumentError if url.blank?
 
-    @verb    = verb
-    @url     = Addressable::URI.parse(url).normalize
-    @options = options.merge(use_proxy? ? Rails.configuration.x.http_client_proxy : { socket_class: Socket })
-    @headers = {}
+    @verb        = verb
+    @url         = Addressable::URI.parse(url).normalize
+    @http_client = options.delete(:http_client)
+    @options     = options.merge(socket_class: use_proxy? ? ProxySocket : Socket)
+    @options     = @options.merge(Rails.configuration.x.http_client_proxy) if use_proxy?
+    @headers     = {}
 
     raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if block_hidden_service?
 
@@ -33,8 +40,8 @@ class Request
     set_digest! if options.key?(:body)
   end
 
-  def on_behalf_of(account, key_id_format = :acct, sign_with: nil)
-    raise ArgumentError unless account.local?
+  def on_behalf_of(account, key_id_format = :uri, sign_with: nil)
+    raise ArgumentError, 'account must not be nil' if account.nil?
 
     @account       = account
     @keypair       = sign_with.present? ? OpenSSL::PKey::RSA.new(sign_with) : @account.keypair
@@ -50,15 +57,24 @@ class Request
 
   def perform
     begin
-      response = http_client.headers(headers).public_send(@verb, @url.to_s, @options)
+      response = http_client.public_send(@verb, @url.to_s, @options.merge(headers: headers))
     rescue => e
-      raise e.class, "#{e.message} on #{@url}", e.backtrace[0]
+      raise e.class, "#{e.message} on #{@url}", e.backtrace
     end
 
     begin
-      yield response.extend(ClientLimit) if block_given?
+      response = response.extend(ClientLimit)
+
+      # If we are using a persistent connection, we have to
+      # read every response to be able to move forward at all.
+      # However, simply calling #to_s or #flush may not be safe,
+      # as the response body, if malicious, could be too big
+      # for our memory. So we use the #body_with_limit method
+      response.body_with_limit if http_client.persistent?
+
+      yield response if block_given?
     ensure
-      http_client.close
+      http_client.close unless http_client.persistent?
     end
   end
 
@@ -76,6 +92,10 @@ class Request
 
       %w(http https).include?(parsed_url.scheme) && parsed_url.host.present?
     end
+
+    def http_client
+      HTTP.use(:auto_inflate).timeout(:per_operation, TIMEOUT.dup).follow(max_hops: 2)
+    end
   end
 
   private
@@ -116,16 +136,8 @@ class Request
     end
   end
 
-  def timeout
-    # We enforce a 1s timeout on DNS resolving, 10s timeout on socket opening
-    # and 5s timeout on the TLS handshake, meaning the worst case should take
-    # about 16s in total
-
-    { connect: 5, read: 10, write: 10 }
-  end
-
   def http_client
-    @http_client ||= HTTP.use(:auto_inflate).timeout(:per_operation, timeout).follow(max_hops: 2)
+    @http_client ||= Request.http_client
   end
 
   def use_proxy?
@@ -166,26 +178,67 @@ class Request
   class Socket < TCPSocket
     class << self
       def open(host, *args)
-        return super(host, *args) if thru_hidden_service?(host)
-
         outer_e = nil
+        port    = args.first
 
-        Resolv::DNS.open do |dns|
-          dns.timeouts = 5
+        addresses = []
+        begin
+          addresses = [IPAddr.new(host)]
+        rescue IPAddr::InvalidAddressError
+          Resolv::DNS.open do |dns|
+            dns.timeouts = 5
+            addresses = dns.getaddresses(host).take(2)
+          end
+        end
 
-          addresses = dns.getaddresses(host).take(2)
-          time_slot = 10.0 / addresses.size
+        socks = []
+        addr_by_socket = {}
 
-          addresses.each do |address|
-            begin
-              raise Mastodon::HostValidationError if PrivateAddressCheck.private_address?(IPAddr.new(address.to_s))
+        addresses.each do |address|
+          begin
+            check_private_address(address)
+
+            sock     = ::Socket.new(address.is_a?(Resolv::IPv6) ? ::Socket::AF_INET6 : ::Socket::AF_INET, ::Socket::SOCK_STREAM, 0)
+            sockaddr = ::Socket.pack_sockaddr_in(port, address.to_s)
+
+            sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
 
-              ::Timeout.timeout(time_slot, HTTP::TimeoutError) do
-                return super(address.to_s, *args)
-              end
+            sock.connect_nonblock(sockaddr)
+
+            # If that hasn't raised an exception, we somehow managed to connect
+            # immediately, close pending sockets and return immediately
+            socks.each(&:close)
+            return sock
+          rescue IO::WaitWritable
+            socks << sock
+            addr_by_socket[sock] = sockaddr
+          rescue => e
+            outer_e = e
+          end
+        end
+
+        until socks.empty?
+          _, available_socks, = IO.select(nil, socks, nil, Request::TIMEOUT[:connect])
+
+          if available_socks.nil?
+            socks.each(&:close)
+            raise HTTP::TimeoutError, "Connect timed out after #{Request::TIMEOUT[:connect]} seconds"
+          end
+
+          available_socks.each do |sock|
+            socks.delete(sock)
+
+            begin
+              sock.connect_nonblock(addr_by_socket[sock])
+            rescue Errno::EISCONN
             rescue => e
+              sock.close
               outer_e = e
+              next
             end
+
+            socks.each(&:close)
+            return sock
           end
         end
 
@@ -198,11 +251,21 @@ class Request
 
       alias new open
 
-      def thru_hidden_service?(host)
-        Rails.configuration.x.access_to_hidden_service && /\.(onion|i2p)$/.match(host)
+      def check_private_address(address)
+        raise Mastodon::HostValidationError if PrivateAddressCheck.private_address?(IPAddr.new(address.to_s))
+      end
+    end
+  end
+
+  class ProxySocket < Socket
+    class << self
+      def check_private_address(_address)
+        # Accept connections to private addresses as HTTP proxies will usually
+        # be on local addresses
+        nil
       end
     end
   end
 
-  private_constant :ClientLimit, :Socket
+  private_constant :ClientLimit, :Socket, :ProxySocket
 end
diff --git a/app/lib/request_pool.rb b/app/lib/request_pool.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e5899a79aab6cc50d8a4a2a2edef0a34f941a3cb
--- /dev/null
+++ b/app/lib/request_pool.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require_relative './connection_pool/shared_connection_pool'
+
+class RequestPool
+  def self.current
+    @current ||= RequestPool.new
+  end
+
+  class Reaper
+    attr_reader :pool, :frequency
+
+    def initialize(pool, frequency)
+      @pool      = pool
+      @frequency = frequency
+    end
+
+    def run
+      return unless frequency&.positive?
+
+      Thread.new(frequency, pool) do |t, p|
+        loop do
+          sleep t
+          p.flush
+        end
+      end
+    end
+  end
+
+  MAX_IDLE_TIME = 30
+  WAIT_TIMEOUT  = 5
+  MAX_POOL_SIZE = ENV.fetch('MAX_REQUEST_POOL_SIZE', 512).to_i
+
+  class Connection
+    attr_reader :site, :last_used_at, :created_at, :in_use, :dead, :fresh
+
+    def initialize(site)
+      @site         = site
+      @http_client  = http_client
+      @last_used_at = nil
+      @created_at   = current_time
+      @dead         = false
+      @fresh        = true
+    end
+
+    def use
+      @last_used_at = current_time
+      @in_use       = true
+
+      retries = 0
+
+      begin
+        yield @http_client
+      rescue HTTP::ConnectionError
+        # It's possible the connection was closed, so let's
+        # try re-opening it once
+
+        close
+
+        if @fresh || retries.positive?
+          raise
+        else
+          @http_client = http_client
+          retries     += 1
+          retry
+        end
+      rescue StandardError
+        # If this connection raises errors of any kind, it's
+        # better if it gets reaped as soon as possible
+
+        close
+        @dead = true
+        raise
+      end
+    ensure
+      @fresh  = false
+      @in_use = false
+    end
+
+    def seconds_idle
+      current_time - (@last_used_at || @created_at)
+    end
+
+    def close
+      @http_client.close
+    end
+
+    private
+
+    def http_client
+      Request.http_client.persistent(@site, timeout: MAX_IDLE_TIME)
+    end
+
+    def current_time
+      Process.clock_gettime(Process::CLOCK_MONOTONIC)
+    end
+  end
+
+  def initialize
+    @pool   = ConnectionPool::SharedConnectionPool.new(size: MAX_POOL_SIZE, timeout: WAIT_TIMEOUT) { |site| Connection.new(site) }
+    @reaper = Reaper.new(self, 30)
+    @reaper.run
+  end
+
+  def with(site, &block)
+    @pool.with(site) do |connection|
+      ActiveSupport::Notifications.instrument('with.request_pool', miss: connection.fresh, host: connection.site) do
+        connection.use(&block)
+      end
+    end
+  end
+
+  delegate :size, :flush, to: :@pool
+end
diff --git a/app/lib/search_query_parser.rb b/app/lib/search_query_parser.rb
new file mode 100644
index 0000000000000000000000000000000000000000..15956d4cfd207502db16b8468dc3e99ec49c7cbc
--- /dev/null
+++ b/app/lib/search_query_parser.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class SearchQueryParser < Parslet::Parser
+  rule(:term)      { match('[^\s":]').repeat(1).as(:term) }
+  rule(:quote)     { str('"') }
+  rule(:colon)     { str(':') }
+  rule(:space)     { match('\s').repeat(1) }
+  rule(:operator)  { (str('+') | str('-')).as(:operator) }
+  rule(:prefix)    { (term >> colon).as(:prefix) }
+  rule(:shortcode) { (colon >> term >> colon.maybe).as(:shortcode) }
+  rule(:phrase)    { (quote >> (term >> space.maybe).repeat >> quote).as(:phrase) }
+  rule(:clause)    { (prefix.maybe >> operator.maybe >> (phrase | term | shortcode)).as(:clause) }
+  rule(:query)     { (clause >> space.maybe).repeat.as(:query) }
+  root(:query)
+end
diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6a299f59d8a27d9565659b0627cc43f3bc74c919
--- /dev/null
+++ b/app/lib/search_query_transformer.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+class SearchQueryTransformer < Parslet::Transform
+  class Query
+    attr_reader :should_clauses, :must_not_clauses, :must_clauses
+
+    def initialize(clauses)
+      grouped = clauses.chunk(&:operator).to_h
+      @should_clauses = grouped.fetch(:should, [])
+      @must_not_clauses = grouped.fetch(:must_not, [])
+      @must_clauses = grouped.fetch(:must, [])
+    end
+
+    def apply(search)
+      should_clauses.each { |clause| search = search.query.should(clause_to_query(clause)) }
+      must_clauses.each { |clause| search = search.query.must(clause_to_query(clause)) }
+      must_not_clauses.each { |clause| search = search.query.must_not(clause_to_query(clause)) }
+      search.query.minimum_should_match(1)
+    end
+
+    private
+
+    def clause_to_query(clause)
+      case clause
+      when TermClause
+        { multi_match: { type: 'most_fields', query: clause.term, fields: ['text', 'text.stemmed'] } }
+      when PhraseClause
+        { match_phrase: { text: { query: clause.phrase } } }
+      else
+        raise "Unexpected clause type: #{clause}"
+      end
+    end
+  end
+
+  class Operator
+    class << self
+      def symbol(str)
+        case str
+        when '+'
+          :must
+        when '-'
+          :must_not
+        when nil
+          :should
+        else
+          raise "Unknown operator: #{str}"
+        end
+      end
+    end
+  end
+
+  class TermClause
+    attr_reader :prefix, :operator, :term
+
+    def initialize(prefix, operator, term)
+      @prefix = prefix
+      @operator = Operator.symbol(operator)
+      @term = term
+    end
+  end
+
+  class PhraseClause
+    attr_reader :prefix, :operator, :phrase
+
+    def initialize(prefix, operator, phrase)
+      @prefix = prefix
+      @operator = Operator.symbol(operator)
+      @phrase = phrase
+    end
+  end
+
+  rule(clause: subtree(:clause)) do
+    prefix   = clause[:prefix][:term].to_s if clause[:prefix]
+    operator = clause[:operator]&.to_s
+
+    if clause[:term]
+      TermClause.new(prefix, operator, clause[:term].to_s)
+    elsif clause[:shortcode]
+      TermClause.new(prefix, operator, ":#{clause[:term]}:")
+    elsif clause[:phrase]
+      PhraseClause.new(prefix, operator, clause[:phrase].map { |p| p[:term].to_s }.join(' '))
+    else
+      raise "Unexpected clause type: #{clause}"
+    end
+  end
+
+  rule(query: sequence(:clauses)) { Query.new(clauses) }
+end
diff --git a/app/lib/settings/scoped_settings.rb b/app/lib/settings/scoped_settings.rb
index 3653ab1149d5432c53cb709f0b6e3ba4e5e022c9..9ca39510acd3a3ac567de71cd6b60c021946055f 100644
--- a/app/lib/settings/scoped_settings.rb
+++ b/app/lib/settings/scoped_settings.rb
@@ -4,6 +4,7 @@ module Settings
   class ScopedSettings
     DEFAULTING_TO_UNSCOPED = %w(
       theme
+      noindex
     ).freeze
 
     def initialize(object)
diff --git a/app/lib/spam_check.rb b/app/lib/spam_check.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5b40514fd064017422d35b25d2291adfe162f227
--- /dev/null
+++ b/app/lib/spam_check.rb
@@ -0,0 +1,198 @@
+# frozen_string_literal: true
+
+class SpamCheck
+  include Redisable
+  include ActionView::Helpers::TextHelper
+
+  # Threshold over which two Nilsimsa values are considered
+  # to refer to the same text
+  NILSIMSA_COMPARE_THRESHOLD = 95
+
+  # Nilsimsa doesn't work well on small inputs, so below
+  # this size, we check only for exact matches with MD5
+  NILSIMSA_MIN_SIZE = 10
+
+  # How long to keep the trail of digests between updates,
+  # there is no reason to store it forever
+  EXPIRE_SET_AFTER = 1.week.seconds
+
+  # How many digests to keep in an account's trail. If it's
+  # too small, spam could rotate around different message templates
+  MAX_TRAIL_SIZE = 10
+
+  # How many detected duplicates to allow through before
+  # considering the message as spam
+  THRESHOLD = 5
+
+  def initialize(status)
+    @account = status.account
+    @status  = status
+  end
+
+  def skip?
+    disabled? || already_flagged? || trusted? || no_unsolicited_mentions? || solicited_reply?
+  end
+
+  def spam?
+    if insufficient_data?
+      false
+    elsif nilsimsa?
+      digests_over_threshold?('nilsimsa') { |_, other_digest| nilsimsa_compare_value(digest, other_digest) >= NILSIMSA_COMPARE_THRESHOLD }
+    else
+      digests_over_threshold?('md5') { |_, other_digest| other_digest == digest }
+    end
+  end
+
+  def flag!
+    auto_report_status!
+  end
+
+  def remember!
+    # The scores in sorted sets don't actually have enough bits to hold an exact
+    # value of our snowflake IDs, so we use it only for its ordering property. To
+    # get the correct status ID back, we have to save it in the string value
+
+    redis.zadd(redis_key, @status.id, digest_with_algorithm)
+    redis.zremrangebyrank(redis_key, 0, -(MAX_TRAIL_SIZE + 1))
+    redis.expire(redis_key, EXPIRE_SET_AFTER)
+  end
+
+  def reset!
+    redis.del(redis_key)
+  end
+
+  def hashable_text
+    return @hashable_text if defined?(@hashable_text)
+
+    @hashable_text = @status.text
+    @hashable_text = remove_mentions(@hashable_text)
+    @hashable_text = strip_tags(@hashable_text) unless @status.local?
+    @hashable_text = normalize_unicode(@status.spoiler_text + ' ' + @hashable_text)
+    @hashable_text = remove_whitespace(@hashable_text)
+  end
+
+  def insufficient_data?
+    hashable_text.blank?
+  end
+
+  def digest
+    @digest ||= begin
+      if nilsimsa?
+        Nilsimsa.new(hashable_text).hexdigest
+      else
+        Digest::MD5.hexdigest(hashable_text)
+      end
+    end
+  end
+
+  def digest_with_algorithm
+    if nilsimsa?
+      ['nilsimsa', digest, @status.id].join(':')
+    else
+      ['md5', digest, @status.id].join(':')
+    end
+  end
+
+  class << self
+    def perform(status)
+      spam_check = new(status)
+
+      return if spam_check.skip?
+
+      if spam_check.spam?
+        spam_check.flag!
+      else
+        spam_check.remember!
+      end
+    end
+  end
+
+  private
+
+  def disabled?
+    !Setting.spam_check_enabled
+  end
+
+  def remove_mentions(text)
+    return text.gsub(Account::MENTION_RE, '') if @status.local?
+
+    Nokogiri::HTML.fragment(text).tap do |html|
+      mentions = @status.mentions.map { |mention| ActivityPub::TagManager.instance.url_for(mention.account) }
+
+      html.traverse do |element|
+        element.unlink if element.name == 'a' && mentions.include?(element['href'])
+      end
+    end.to_s
+  end
+
+  def normalize_unicode(text)
+    text.unicode_normalize(:nfkc).downcase
+  end
+
+  def remove_whitespace(text)
+    text.gsub(/\s+/, ' ').strip
+  end
+
+  def auto_report_status!
+    status_ids = Status.where(visibility: %i(public unlisted)).where(id: matching_status_ids).pluck(:id) + [@status.id] if @status.distributable?
+    ReportService.new.call(Account.representative, @account, status_ids: status_ids, comment: I18n.t('spam_check.spam_detected'))
+  end
+
+  def already_flagged?
+    @account.silenced? || @account.targeted_reports.unresolved.where(account_id: -99).exists?
+  end
+
+  def trusted?
+    @account.trust_level > Account::TRUST_LEVELS[:untrusted]
+  end
+
+  def no_unsolicited_mentions?
+    @status.mentions.all? { |mention| mention.silent? || (!@account.local? && !mention.account.local?) || mention.account.following?(@account) }
+  end
+
+  def solicited_reply?
+    !@status.thread.nil? && @status.thread.mentions.where(account: @account).exists?
+  end
+
+  def nilsimsa_compare_value(first, second)
+    first  = [first].pack('H*')
+    second = [second].pack('H*')
+    bits   = 0
+
+    0.upto(31) do |i|
+      bits += Nilsimsa::POPC[255 & (first[i].ord ^ second[i].ord)].ord
+    end
+
+    128 - bits # -128 <= Nilsimsa Compare Value <= 128
+  end
+
+  def nilsimsa?
+    hashable_text.size > NILSIMSA_MIN_SIZE
+  end
+
+  def other_digests
+    redis.zrange(redis_key, 0, -1)
+  end
+
+  def digests_over_threshold?(filter_algorithm)
+    other_digests.select do |record|
+      algorithm, other_digest, status_id = record.split(':')
+
+      next unless algorithm == filter_algorithm
+
+      yield algorithm, other_digest, status_id
+    end.size >= THRESHOLD
+  end
+
+  def matching_status_ids
+    if nilsimsa?
+      other_digests.select { |record| record.start_with?('nilsimsa') && nilsimsa_compare_value(digest, record.split(':')[1]) >= NILSIMSA_COMPARE_THRESHOLD }.map { |record| record.split(':')[2] }.compact
+    else
+      other_digests.select { |record| record.start_with?('md5') && record.split(':')[1] == digest }.map { |record| record.split(':')[2] }.compact
+    end
+  end
+
+  def redis_key
+    @redis_key ||= "spam_check:#{@account.id}"
+  end
+end
diff --git a/app/lib/status_finder.rb b/app/lib/status_finder.rb
index 4d1aed297952c7a7f9fb756d78c6e42a988c1b42..22ced8bf82d8648de62f03c1d20e337f39e79969 100644
--- a/app/lib/status_finder.rb
+++ b/app/lib/status_finder.rb
@@ -13,8 +13,6 @@ class StatusFinder
     raise ActiveRecord::RecordNotFound unless TagManager.instance.local_url?(url)
 
     case recognized_params[:controller]
-    when 'stream_entries'
-      StreamEntry.find(recognized_params[:id]).status
     when 'statuses'
       Status.find(recognized_params[:id])
     else
diff --git a/app/lib/tag_manager.rb b/app/lib/tag_manager.rb
index fb364cb98aea39385b40b968af336f5a984cb25a..c88cf499474634d50695df18f6a4ed72ec2bedf3 100644
--- a/app/lib/tag_manager.rb
+++ b/app/lib/tag_manager.rb
@@ -24,24 +24,16 @@ class TagManager
 
   def same_acct?(canonical, needle)
     return true if canonical.casecmp(needle).zero?
+
     username, domain = needle.split('@')
+
     local_domain?(domain) && canonical.casecmp(username).zero?
   end
 
   def local_url?(url)
     uri    = Addressable::URI.parse(url).normalize
     domain = uri.host + (uri.port ? ":#{uri.port}" : '')
-    TagManager.instance.web_domain?(domain)
-  end
-
-  def url_for(target)
-    return target.url if target.respond_to?(:local?) && !target.local?
 
-    case target.object_type
-    when :person
-      short_account_url(target)
-    when :note, :comment, :activity
-      short_account_status_url(target.account, target)
-    end
+    TagManager.instance.web_domain?(domain)
   end
 end
diff --git a/app/lib/toc_generator.rb b/app/lib/toc_generator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0c8f766ca42fdde089afdac7795c3ab5cbf81963
--- /dev/null
+++ b/app/lib/toc_generator.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+class TOCGenerator
+  TARGET_ELEMENTS = %w(h1 h2 h3 h4 h5 h6).freeze
+  LISTED_ELEMENTS = %w(h2 h3).freeze
+
+  class Section
+    attr_accessor :depth, :title, :children, :anchor
+
+    def initialize(depth, title, anchor)
+      @depth    = depth
+      @title    = title
+      @children = []
+      @anchor   = anchor
+    end
+
+    delegate :<<, to: :children
+  end
+
+  def initialize(source_html)
+    @source_html = source_html
+    @processed   = false
+    @target_html = ''
+    @headers     = []
+    @slugs       = Hash.new { |h, k| h[k] = 0 }
+  end
+
+  def html
+    parse_and_transform unless @processed
+    @target_html
+  end
+
+  def toc
+    parse_and_transform unless @processed
+    @headers
+  end
+
+  private
+
+  def parse_and_transform
+    return if @source_html.blank?
+
+    parsed_html = Nokogiri::HTML.fragment(@source_html)
+
+    parsed_html.traverse do |node|
+      next unless TARGET_ELEMENTS.include?(node.name)
+
+      anchor = node['id'] || node.text.parameterize.presence || 'sec'
+      @slugs[anchor] += 1
+      anchor = "#{anchor}-#{@slugs[anchor]}" if @slugs[anchor] > 1
+
+      node['id'] = anchor
+
+      next unless LISTED_ELEMENTS.include?(node.name)
+
+      depth          = node.name[1..-1]
+      latest_section = @headers.last
+
+      if latest_section.nil? || latest_section.depth >= depth
+        @headers << Section.new(depth, node.text, anchor)
+      else
+        latest_section << Section.new(depth, node.text, anchor)
+      end
+    end
+
+    @target_html = parsed_html.to_s
+    @processed   = true
+  end
+end
diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb
index 891a87a45c8e833a079ac4b42a7971d4b50ec5e4..52249f940111fd9c652f1985380e82b18e824427 100644
--- a/app/lib/user_settings_decorator.rb
+++ b/app/lib/user_settings_decorator.rb
@@ -35,6 +35,9 @@ class UserSettingsDecorator
     user.settings['aggregate_reblogs']   = aggregate_reblogs_preference if change?('setting_aggregate_reblogs')
     user.settings['show_application']    = show_application_preference if change?('setting_show_application')
     user.settings['advanced_layout']     = advanced_layout_preference if change?('setting_advanced_layout')
+    user.settings['use_blurhash']        = use_blurhash_preference if change?('setting_use_blurhash')
+    user.settings['use_pending_items']   = use_pending_items_preference if change?('setting_use_pending_items')
+    user.settings['trends']              = trends_preference if change?('setting_trends')
   end
 
   def merged_notification_emails
@@ -117,6 +120,18 @@ class UserSettingsDecorator
     boolean_cast_setting 'setting_advanced_layout'
   end
 
+  def use_blurhash_preference
+    boolean_cast_setting 'setting_use_blurhash'
+  end
+
+  def use_pending_items_preference
+    boolean_cast_setting 'setting_use_pending_items'
+  end
+
+  def trends_preference
+    boolean_cast_setting 'setting_trends'
+  end
+
   def boolean_cast_setting(key)
     ActiveModel::Type::Boolean.new.cast(settings[key])
   end
diff --git a/app/lib/webfinger_resource.rb b/app/lib/webfinger_resource.rb
index a54a702a2e9c5a66b6515701039f77a4e7668d63..22d78874a4b263ef2c1a359c4bd7076936d340a1 100644
--- a/app/lib/webfinger_resource.rb
+++ b/app/lib/webfinger_resource.rb
@@ -23,11 +23,17 @@ class WebfingerResource
   def username_from_url
     if account_show_page?
       path_params[:username]
+    elsif instance_actor_page?
+      Rails.configuration.x.local_domain
     else
       raise ActiveRecord::RecordNotFound
     end
   end
 
+  def instance_actor_page?
+    path_params[:controller] == 'instance_actors'
+  end
+
   def account_show_page?
     path_params[:controller] == 'accounts' && path_params[:action] == 'show'
   end
diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb
index db154cad53afe9a5b5b25c92f7286676ecbcb4d6..8abce5f055d568648a9546f039329eb87438ef99 100644
--- a/app/mailers/admin_mailer.rb
+++ b/app/mailers/admin_mailer.rb
@@ -3,7 +3,7 @@
 class AdminMailer < ApplicationMailer
   layout 'plain_mailer'
 
-  helper :stream_entries
+  helper :statuses
 
   def new_report(recipient, report)
     @report   = report
@@ -24,4 +24,14 @@ class AdminMailer < ApplicationMailer
       mail to: @me.user_email, subject: I18n.t('admin_mailer.new_pending_account.subject', instance: @instance, username: @account.username)
     end
   end
+
+  def new_trending_tag(recipient, tag)
+    @tag      = tag
+    @me       = recipient
+    @instance = Rails.configuration.x.local_domain
+
+    locale_for_account(@me) do
+      mail to: @me.user_email, subject: I18n.t('admin_mailer.new_trending_tag.subject', instance: @instance, name: @tag.name)
+    end
+  end
 end
diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb
index 66fa337c1f277a6c276fbee0e92cfc44c1c16998..723d901fc6e4d2803aa45236a2766b1353415873 100644
--- a/app/mailers/notification_mailer.rb
+++ b/app/mailers/notification_mailer.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class NotificationMailer < ApplicationMailer
-  helper :stream_entries
+  helper :statuses
 
   add_template_helper RoutingHelper
 
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 8f3a4ab3aa269f32857fe58ffa8bfd881a2aa945..6b81f68739c8811e0a5dccc1a2b478e241f017d4 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -5,6 +5,7 @@ class UserMailer < Devise::Mailer
 
   helper :application
   helper :instance
+  helper :statuses
 
   add_template_helper RoutingHelper
 
@@ -56,6 +57,39 @@ class UserMailer < Devise::Mailer
     end
   end
 
+  def two_factor_enabled(user, **)
+    @resource = user
+    @instance = Rails.configuration.x.local_domain
+
+    return if @resource.disabled?
+
+    I18n.with_locale(@resource.locale || I18n.default_locale) do
+      mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_enabled.subject')
+    end
+  end
+
+  def two_factor_disabled(user, **)
+    @resource = user
+    @instance = Rails.configuration.x.local_domain
+
+    return if @resource.disabled?
+
+    I18n.with_locale(@resource.locale || I18n.default_locale) do
+      mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_disabled.subject')
+    end
+  end
+
+  def two_factor_recovery_codes_changed(user, **)
+    @resource = user
+    @instance = Rails.configuration.x.local_domain
+
+    return if @resource.disabled?
+
+    I18n.with_locale(@resource.locale || I18n.default_locale) do
+      mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject')
+    end
+  end
+
   def welcome(user)
     @resource = user
     @instance = Rails.configuration.x.local_domain
@@ -79,10 +113,11 @@ class UserMailer < Devise::Mailer
     end
   end
 
-  def warning(user, warning)
+  def warning(user, warning, status_ids = nil)
     @resource = user
     @warning  = warning
     @instance = Rails.configuration.x.local_domain
+    @statuses = Status.where(id: status_ids).includes(:account) if status_ids.is_a?(Array)
 
     I18n.with_locale(@resource.locale || I18n.default_locale) do
       mail to: @resource.email,
diff --git a/app/models/account.rb b/app/models/account.rb
index c588451fce0286dce224f78b07b3ef628017fdcb..05936def3204db7a7e6182cd57ac7afb60f21cd9 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -45,12 +45,12 @@
 #  also_known_as           :string           is an Array
 #  silenced_at             :datetime
 #  suspended_at            :datetime
+#  trust_level             :integer
 #
 
 class Account < ApplicationRecord
   USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i
   MENTION_RE  = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
-  MIN_FOLLOWERS_DISCOVERY = 10
 
   include AccountAssociations
   include AccountAvatar
@@ -62,6 +62,11 @@ class Account < ApplicationRecord
   include AccountCounters
   include DomainNormalizable
 
+  TRUST_LEVELS = {
+    untrusted: 0,
+    trusted: 1,
+  }.freeze
+
   enum protocol: [:ostatus, :activitypub]
 
   validates :username, presence: true
@@ -71,7 +76,7 @@ class Account < ApplicationRecord
   validates :username, format: { with: /\A#{USERNAME_RE}\z/i }, if: -> { !local? && will_save_change_to_username? }
 
   # Local user validations
-  validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? }
+  validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' }
   validates_with UniqueUsernameValidator, if: -> { local? && will_save_change_to_username? }
   validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? }
   validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
@@ -94,11 +99,13 @@ class Account < ApplicationRecord
   scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
   scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
   scope :searchable, -> { without_suspended.where(moved_to_account_id: nil) }
-  scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).joins(:account_stat).where(AccountStat.arel_table[:followers_count].gteq(MIN_FOLLOWERS_DISCOVERY)) }
+  scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).left_outer_joins(:account_stat) }
   scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) }
-  scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) }
+  scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc, accounts.id desc')) }
   scope :popular, -> { order('account_stats.followers_count desc') }
   scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches('%.' + domain))) }
+  scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) }
+  scope :not_domain_blocked_by_account, ->(account) { where(arel_table[:domain].eq(nil).or(arel_table[:domain].not_in(account.excluded_from_timeline_domains))) }
 
   delegate :email,
            :unconfirmed_email,
@@ -108,6 +115,7 @@ class Account < ApplicationRecord
            :approved?,
            :pending?,
            :disabled?,
+           :unconfirmed_or_pending?,
            :role,
            :admin?,
            :moderator?,
@@ -121,6 +129,8 @@ class Account < ApplicationRecord
 
   delegate :chosen_languages, to: :user, prefix: false, allow_nil: true
 
+  update_index('accounts#account', :self)
+
   def local?
     domain.nil?
   end
@@ -133,6 +143,10 @@ class Account < ApplicationRecord
     %w(Application Service).include? actor_type
   end
 
+  def instance_actor?
+    id == -99
+  end
+
   alias bot bot?
 
   def bot=(val)
@@ -159,21 +173,27 @@ class Account < ApplicationRecord
     subscription_expires_at.present?
   end
 
+  def searchable?
+    !(suspended? || moved?)
+  end
+
   def possibly_stale?
     last_webfingered_at.nil? || last_webfingered_at <= 1.day.ago
   end
 
+  def trust_level
+    self[:trust_level] || 0
+  end
+
   def refresh!
-    return if local?
-    ResolveAccountService.new.call(acct)
+    ResolveAccountService.new.call(acct) unless local?
   end
 
   def silenced?
     silenced_at.present?
   end
 
-  def silence!(date = nil)
-    date ||= Time.now.utc
+  def silence!(date = Time.now.utc)
     update!(silenced_at: date)
   end
 
@@ -185,8 +205,7 @@ class Account < ApplicationRecord
     suspended_at.present?
   end
 
-  def suspend!(date = nil)
-    date ||= Time.now.utc
+  def suspend!(date = Time.now.utc)
     transaction do
       user&.disable! if local?
       update!(suspended_at: date)
@@ -216,17 +235,7 @@ class Account < ApplicationRecord
   end
 
   def tags_as_strings=(tag_names)
-    tag_names.map! { |name| name.mb_chars.downcase.to_s }
-    tag_names.uniq!
-
-    # Existing hashtags
-    hashtags_map = Tag.where(name: tag_names).each_with_object({}) { |tag, h| h[tag.name] = tag }
-
-    # Initialize not yet existing hashtags
-    tag_names.each do |name|
-      next if hashtags_map.key?(name)
-      hashtags_map[name] = Tag.new(name: name)
-    end
+    hashtags_map = Tag.find_or_create_by_names(tag_names).each_with_object({}) { |tag, h| h[tag.name] = tag }
 
     # Remove hashtags that are to be deleted
     tags.each do |tag|
@@ -294,21 +303,6 @@ class Account < ApplicationRecord
     self.fields = tmp
   end
 
-  def magic_key
-    modulus, exponent = [keypair.public_key.n, keypair.public_key.e].map do |component|
-      result = []
-
-      until component.zero?
-        result << [component % 256].pack('C')
-        component >>= 8
-      end
-
-      result.reverse.join
-    end
-
-    (['RSA'] + [modulus, exponent].map { |n| Base64.urlsafe_encode64(n) }).join('.')
-  end
-
   def subscription(webhook_url)
     @subscription ||= OStatus2::Subscription.new(remote_url, secret: secret, webhook: webhook_url, hub: hub_url)
   end
@@ -316,10 +310,9 @@ class Account < ApplicationRecord
   def save_with_optional_media!
     save!
   rescue ActiveRecord::RecordInvalid
-    self.avatar              = nil
-    self.header              = nil
-    self[:avatar_remote_url] = ''
-    self[:header_remote_url] = ''
+    self.avatar = nil
+    self.header = nil
+
     save!
   end
 
@@ -506,7 +499,7 @@ class Account < ApplicationRecord
   end
 
   def generate_keys
-    return unless local? && !Rails.env.test?
+    return unless local? && private_key.blank? && public_key.blank?
 
     keypair = OpenSSL::PKey::RSA.new(2048)
     self.private_key = keypair.to_pem
@@ -520,7 +513,7 @@ class Account < ApplicationRecord
   end
 
   def emojifiable_text
-    [note, display_name, fields.map(&:value)].join(' ')
+    [note, display_name, fields.map(&:name), fields.map(&:value)].join(' ')
   end
 
   def clean_feed_manager
diff --git a/app/models/account_alias.rb b/app/models/account_alias.rb
new file mode 100644
index 0000000000000000000000000000000000000000..66f8ce40953c9a8e5b2b9198bbb899610c3cb6de
--- /dev/null
+++ b/app/models/account_alias.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: account_aliases
+#
+#  id         :bigint(8)        not null, primary key
+#  account_id :bigint(8)
+#  acct       :string           default(""), not null
+#  uri        :string           default(""), not null
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#
+
+class AccountAlias < ApplicationRecord
+  belongs_to :account
+
+  validates :acct, presence: true, domain: { acct: true }
+  validates :uri, presence: true
+  validates :uri, uniqueness: { scope: :account_id }
+
+  before_validation :set_uri
+  after_create :add_to_account
+  after_destroy :remove_from_account
+
+  def acct=(val)
+    val = val.to_s.strip
+    super(val.start_with?('@') ? val[1..-1] : val)
+  end
+
+  private
+
+  def set_uri
+    target_account = ResolveAccountService.new.call(acct)
+    self.uri       = ActivityPub::TagManager.instance.uri_for(target_account) unless target_account.nil?
+  rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
+    # Validation will take care of it
+  end
+
+  def add_to_account
+    account.update(also_known_as: account.also_known_as + [uri])
+  end
+
+  def remove_from_account
+    account.update(also_known_as: account.also_known_as.reject { |x| x == uri })
+  end
+end
diff --git a/app/models/account_migration.rb b/app/models/account_migration.rb
new file mode 100644
index 0000000000000000000000000000000000000000..681b5b2cd0bc9e76d1f08e2d1a8c72dd7a26d3f3
--- /dev/null
+++ b/app/models/account_migration.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: account_migrations
+#
+#  id                :bigint(8)        not null, primary key
+#  account_id        :bigint(8)
+#  acct              :string           default(""), not null
+#  followers_count   :bigint(8)        default(0), not null
+#  target_account_id :bigint(8)
+#  created_at        :datetime         not null
+#  updated_at        :datetime         not null
+#
+
+class AccountMigration < ApplicationRecord
+  COOLDOWN_PERIOD = 30.days.freeze
+
+  belongs_to :account
+  belongs_to :target_account, class_name: 'Account'
+
+  before_validation :set_target_account
+  before_validation :set_followers_count
+
+  validates :acct, presence: true, domain: { acct: true }
+  validate :validate_migration_cooldown
+  validate :validate_target_account
+
+  scope :within_cooldown, ->(now = Time.now.utc) { where(arel_table[:created_at].gteq(now - COOLDOWN_PERIOD)) }
+
+  attr_accessor :current_password, :current_username
+
+  def save_with_challenge(current_user)
+    if current_user.encrypted_password.present?
+      errors.add(:current_password, :invalid) unless current_user.valid_password?(current_password)
+    else
+      errors.add(:current_username, :invalid) unless account.username == current_username
+    end
+
+    return false unless errors.empty?
+
+    save
+  end
+
+  def cooldown_at
+    created_at + COOLDOWN_PERIOD
+  end
+
+  def acct=(val)
+    super(val.to_s.strip.gsub(/\A@/, ''))
+  end
+
+  private
+
+  def set_target_account
+    self.target_account = ResolveAccountService.new.call(acct)
+  rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
+    # Validation will take care of it
+  end
+
+  def set_followers_count
+    self.followers_count = account.followers_count
+  end
+
+  def validate_target_account
+    if target_account.nil?
+      errors.add(:acct, I18n.t('migrations.errors.not_found'))
+    else
+      errors.add(:acct, I18n.t('migrations.errors.missing_also_known_as')) unless target_account.also_known_as.include?(ActivityPub::TagManager.instance.uri_for(account))
+      errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved_to_account_id.present? && account.moved_to_account_id == target_account.id
+      errors.add(:acct, I18n.t('migrations.errors.move_to_self')) if account.id == target_account.id
+    end
+  end
+
+  def validate_migration_cooldown
+    errors.add(:base, I18n.t('migrations.errors.on_cooldown')) if account.migrations.within_cooldown.exists?
+  end
+end
diff --git a/app/models/account_stat.rb b/app/models/account_stat.rb
index 9813aa84ff105420b0629e5a9f501ba78296ff30..c84e4217c8c6b11d5a0770ff443a6de39db1afd6 100644
--- a/app/models/account_stat.rb
+++ b/app/models/account_stat.rb
@@ -11,17 +11,36 @@
 #  created_at      :datetime         not null
 #  updated_at      :datetime         not null
 #  last_status_at  :datetime
+#  lock_version    :integer          default(0), not null
 #
 
 class AccountStat < ApplicationRecord
   belongs_to :account, inverse_of: :account_stat
 
+  update_index('accounts#account', :account)
+
   def increment_count!(key)
     update(attributes_for_increment(key))
+  rescue ActiveRecord::StaleObjectError
+    begin
+      reload_with_id
+    rescue ActiveRecord::RecordNotFound
+      # Nothing to do
+    else
+      retry
+    end
   end
 
   def decrement_count!(key)
     update(key => [public_send(key) - 1, 0].max)
+  rescue ActiveRecord::StaleObjectError
+    begin
+      reload_with_id
+    rescue ActiveRecord::RecordNotFound
+      # Nothing to do
+    else
+      retry
+    end
   end
 
   private
@@ -31,4 +50,9 @@ class AccountStat < ApplicationRecord
     attrs[:last_status_at] = Time.now.utc if key == :statuses_count
     attrs
   end
+
+  def reload_with_id
+    self.id = find_by!(account: account).id if new_record?
+    reload
+  end
 end
diff --git a/app/models/admin/account_action.rb b/app/models/admin/account_action.rb
index bdbd342fb3ccf3fb0444a831125b72d90ef5f8e9..e9da003a30640e804cefa92e7d51912e77d8a462 100644
--- a/app/models/admin/account_action.rb
+++ b/app/models/admin/account_action.rb
@@ -19,20 +19,25 @@ class Admin::AccountAction
                 :report_id,
                 :warning_preset_id
 
-  attr_reader :warning, :send_email_notification
+  attr_reader :warning, :send_email_notification, :include_statuses
 
   def send_email_notification=(value)
     @send_email_notification = ActiveModel::Type::Boolean.new.cast(value)
   end
 
+  def include_statuses=(value)
+    @include_statuses = ActiveModel::Type::Boolean.new.cast(value)
+  end
+
   def save!
     ApplicationRecord.transaction do
       process_action!
       process_warning!
     end
 
-    queue_email!
+    process_email!
     process_reports!
+    process_queue!
   end
 
   def report
@@ -57,6 +62,8 @@ class Admin::AccountAction
 
   def process_action!
     case type
+    when 'none'
+      handle_resolve!
     when 'disable'
       handle_disable!
     when 'silence'
@@ -78,19 +85,33 @@ class Admin::AccountAction
 
     # A log entry is only interesting if the warning contains
     # custom text from someone. Otherwise it's just noise.
+
     log_action(:create, warning) if warning.text.present?
   end
 
   def process_reports!
-    return if report_id.blank?
+    # If we're doing "mark as resolved" on a single report,
+    # then we want to keep other reports open in case they
+    # contain new actionable information.
+    #
+    # Otherwise, we will mark all unresolved reports about
+    # the account as resolved.
 
-    authorize(report, :update?)
+    reports.each { |report| authorize(report, :update?) }
 
-    if type == 'none'
+    reports.each do |report|
       log_action(:resolve, report)
       report.resolve!(current_account)
-    else
-      Report.where(target_account: target_account).unresolved.update_all(action_taken: true, action_taken_by_account_id: current_account.id)
+    end
+  end
+
+  def handle_resolve!
+    if with_report? && report.account_id == -99 && target_account.trust_level == Account::TRUST_LEVELS[:untrusted]
+      # This is an automated report and it is being dismissed, so it's
+      # a false positive, in which case update the account's trust level
+      # to prevent further spam checks
+
+      target_account.update(trust_level: Account::TRUST_LEVELS[:trusted])
     end
   end
 
@@ -110,7 +131,6 @@ class Admin::AccountAction
     authorize(target_account, :suspend?)
     log_action(:suspend, target_account)
     target_account.suspend!
-    queue_suspension_worker!
   end
 
   def text_for_warning
@@ -121,16 +141,32 @@ class Admin::AccountAction
     Admin::SuspensionWorker.perform_async(target_account.id)
   end
 
-  def queue_email!
-    return unless warnable?
+  def process_queue!
+    queue_suspension_worker! if type == 'suspend'
+  end
 
-    UserMailer.warning(target_account.user, warning).deliver_later!
+  def process_email!
+    UserMailer.warning(target_account.user, warning, status_ids).deliver_now! if warnable?
   end
 
   def warnable?
     send_email_notification && target_account.local?
   end
 
+  def status_ids
+    @report.status_ids if @report && include_statuses
+  end
+
+  def reports
+    @reports ||= begin
+      if type == 'none' && with_report?
+        [report]
+      else
+        Report.where(target_account: target_account).unresolved
+      end
+    end
+  end
+
   def warning_preset
     @warning_preset ||= AccountWarningPreset.find(warning_preset_id) if warning_preset_id.present?
   end
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index 83134d41a4705fcc2d42ad24982bcebf56541cfe..5d7d3a09610a375633fde62188cfe514801b2f72 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -2,5 +2,22 @@
 
 class ApplicationRecord < ActiveRecord::Base
   self.abstract_class = true
+
   include Remotable
+
+  class << self
+    def update_index(_type_name, *_args, &_block)
+      super if Chewy.enabled?
+    end
+  end
+
+  def boolean_with_default(key, default_value)
+    value = attributes[key]
+
+    if value.nil?
+      default_value
+    else
+      value
+    end
+  end
 end
diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb
index 70855e0543ee83464b1d588ffaacc69554d019b8..c9cc5c610b8e522cdebfbd1022e784c11b3a1b9f 100644
--- a/app/models/concerns/account_associations.rb
+++ b/app/models/concerns/account_associations.rb
@@ -11,7 +11,6 @@ module AccountAssociations
     has_many :identity_proofs, class_name: 'AccountIdentityProof', dependent: :destroy, inverse_of: :account
 
     # Timelines
-    has_many :stream_entries, inverse_of: :account, dependent: :destroy
     has_many :statuses, inverse_of: :account, dependent: :destroy
     has_many :favourites, inverse_of: :account, dependent: :destroy
     has_many :mentions, inverse_of: :account, dependent: :destroy
@@ -31,9 +30,6 @@ module AccountAssociations
     has_many :media_attachments, dependent: :destroy
     has_many :polls, dependent: :destroy
 
-    # PuSH subscriptions
-    has_many :subscriptions, dependent: :destroy
-
     # Report relationships
     has_many :reports, dependent: :destroy, inverse_of: :account
     has_many :targeted_reports, class_name: 'Report', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
@@ -56,6 +52,8 @@ module AccountAssociations
 
     # Account migrations
     belongs_to :moved_to_account, class_name: 'Account', optional: true
+    has_many :migrations, class_name: 'AccountMigration', dependent: :destroy, inverse_of: :account
+    has_many :aliases, class_name: 'AccountAlias', dependent: :destroy, inverse_of: :account
 
     # Hashtags
     has_and_belongs_to_many :tags
diff --git a/app/models/concerns/account_avatar.rb b/app/models/concerns/account_avatar.rb
index 5fff3ef5d2128399463897dc837fd402045bd3c8..2d5ebfca35aea67c09a2d5bb1862e2ee5cb8a1d1 100644
--- a/app/models/concerns/account_avatar.rb
+++ b/app/models/concerns/account_avatar.rb
@@ -3,7 +3,7 @@
 module AccountAvatar
   extend ActiveSupport::Concern
 
-  IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze
+  IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
   LIMIT = 2.megabytes
 
   class_methods do
diff --git a/app/models/concerns/account_counters.rb b/app/models/concerns/account_counters.rb
index 3581df8dd8b2ac69b6806eb4b0bea7c3651eec06..6e25e1905e4d6919ed91c3fda3497ffb2933d04a 100644
--- a/app/models/concerns/account_counters.rb
+++ b/app/models/concerns/account_counters.rb
@@ -26,7 +26,8 @@ module AccountCounters
   private
 
   def save_account_stat
-    return unless account_stat&.changed?
+    return unless association(:account_stat).loaded? && account_stat&.changed?
+
     account_stat.save
   end
 end
diff --git a/app/models/concerns/account_finder_concern.rb b/app/models/concerns/account_finder_concern.rb
index ccd7bfa12311310d8ce3cbf66677e7fc86ae3780..a54c2174d490db945ae8c352cac4b8c3bb371214 100644
--- a/app/models/concerns/account_finder_concern.rb
+++ b/app/models/concerns/account_finder_concern.rb
@@ -13,7 +13,7 @@ module AccountFinderConcern
     end
 
     def representative
-      find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')) || Account.local.without_suspended.first
+      Account.find(-99)
     end
 
     def find_local(username)
diff --git a/app/models/concerns/account_header.rb b/app/models/concerns/account_header.rb
index a748fdff72df33e23613364f55ecb53950febf6e..067e166eb6ce5e340a86489c1c12d525c2c24dfd 100644
--- a/app/models/concerns/account_header.rb
+++ b/app/models/concerns/account_header.rb
@@ -3,7 +3,7 @@
 module AccountHeader
   extend ActiveSupport::Concern
 
-  IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze
+  IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
   LIMIT = 2.megabytes
   MAX_PIXELS = 750_000 # 1500x500px
 
diff --git a/app/models/concerns/attachmentable.rb b/app/models/concerns/attachmentable.rb
index 7c78bb45698dccf8e9036cc3e2eb53272ee2ba43..3bbc6453c7f5575937352fb4c5b0f224a6d3dfbf 100644
--- a/app/models/concerns/attachmentable.rb
+++ b/app/models/concerns/attachmentable.rb
@@ -6,6 +6,7 @@ module Attachmentable
   extend ActiveSupport::Concern
 
   MAX_MATRIX_LIMIT = 16_777_216 # 4096x4096px or approx. 16MB
+  GIF_MATRIX_LIMIT = 921_600    # 1280x720px
 
   included do
     before_post_process :set_file_extensions
@@ -42,8 +43,9 @@ module Attachmentable
       next if attachment.blank? || !/image.*/.match?(attachment.content_type) || attachment.queued_for_write[:original].blank?
 
       width, height = FastImage.size(attachment.queued_for_write[:original].path)
+      matrix_limit  = attachment.content_type == 'image/gif' ? GIF_MATRIX_LIMIT : MAX_MATRIX_LIMIT
 
-      raise Mastodon::DimensionsValidationError, "#{width}x#{height} images are not supported" if width.present? && height.present? && (width * height >= MAX_MATRIX_LIMIT)
+      raise Mastodon::DimensionsValidationError, "#{width}x#{height} images are not supported" if width.present? && height.present? && (width * height > matrix_limit)
     end
   end
 
diff --git a/app/models/concerns/ldap_authenticable.rb b/app/models/concerns/ldap_authenticable.rb
index 84ff84c4b05c856c30cf7190db6ed392df3dd063..1179939478454401bf220317a718c0db6b27efcb 100644
--- a/app/models/concerns/ldap_authenticable.rb
+++ b/app/models/concerns/ldap_authenticable.rb
@@ -3,24 +3,50 @@
 module LdapAuthenticable
   extend ActiveSupport::Concern
 
-  def ldap_setup(_attributes)
-    self.confirmed_at = Time.now.utc
-    self.admin        = false
-    self.external     = true
+  class_methods do
+    def authenticate_with_ldap(params = {})
+      ldap   = Net::LDAP.new(ldap_options)
+      filter = format(Devise.ldap_search_filter, uid: Devise.ldap_uid, email: params[:email])
 
-    save!
-  end
+      if (user_info = ldap.bind_as(base: Devise.ldap_base, filter: filter, password: params[:password]))
+        ldap_get_user(user_info.first)
+      end
+    end
 
-  class_methods do
     def ldap_get_user(attributes = {})
       resource = joins(:account).find_by(accounts: { username: attributes[Devise.ldap_uid.to_sym].first })
 
       if resource.blank?
-        resource = new(email: attributes[:mail].first, agreement: true, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first })
-        resource.ldap_setup(attributes)
+        resource = new(email: attributes[:mail].first, agreement: true, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first }, admin: false, external: true, confirmed_at: Time.now.utc)
+        resource.save!
       end
 
       resource
     end
+
+    def ldap_options
+      opts = {
+        host: Devise.ldap_host,
+        port: Devise.ldap_port,
+        base: Devise.ldap_base,
+
+        auth: {
+          method: :simple,
+          username: Devise.ldap_bind_dn,
+          password: Devise.ldap_password,
+        },
+
+        connect_timeout: 10,
+      }
+
+      if [:simple_tls, :start_tls].include?(Devise.ldap_method)
+        opts[:encryption] = {
+          method: Devise.ldap_method,
+          tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.tap { |options| options[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if Devise.ldap_tls_no_verify },
+        }
+      end
+
+      opts
+    end
   end
 end
diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb
index 283033083959caf767db8450ddde1c1f6f77d382..960784222702ce08deef9772048ab495b53d32f4 100644
--- a/app/models/concerns/omniauthable.rb
+++ b/app/models/concerns/omniauthable.rb
@@ -4,7 +4,7 @@ module Omniauthable
   extend ActiveSupport::Concern
 
   TEMP_EMAIL_PREFIX = 'change@me'
-  TEMP_EMAIL_REGEX = /\Achange@me/
+  TEMP_EMAIL_REGEX  = /\A#{TEMP_EMAIL_PREFIX}/.freeze
 
   included do
     devise :omniauthable
@@ -28,8 +28,8 @@ module Omniauthable
       # to prevent the identity being locked with accidentally created accounts.
       # Note that this may leave zombie accounts (with no associated identity) which
       # can be cleaned up at a later date.
-      user = signed_in_resource || identity.user
-      user = create_for_oauth(auth) if user.nil?
+      user   = signed_in_resource || identity.user
+      user ||= create_for_oauth(auth)
 
       if identity.user.nil?
         identity.user = user
@@ -43,9 +43,20 @@ module Omniauthable
       # Check if the user exists with provided email if the provider gives us a
       # verified email.  If no verified email was provided or the user already
       # exists, we assign a temporary email and ask the user to verify it on
-      # the next step via Auth::ConfirmationsController.finish_signup
+      # the next step via Auth::SetupController.show
+
+      strategy          = Devise.omniauth_configs[auth.provider.to_sym].strategy
+      assume_verified   = strategy&.security&.assume_email_is_verified
+      email_is_verified = auth.info.verified || auth.info.verified_email || assume_verified
+      email             = auth.info.verified_email || auth.info.email
+      email             = nil unless email_is_verified
+
+      user = User.find_by(email: email) if email_is_verified
+
+      return user unless user.nil?
+
+      user = User.new(user_params_from_auth(email, auth))
 
-      user = User.new(user_params_from_auth(auth))
       user.account.avatar_remote_url = auth.info.image if auth.info.image =~ /\A#{URI.regexp(%w(http https))}\z/
       user.skip_confirmation!
       user.save!
@@ -54,14 +65,7 @@ module Omniauthable
 
     private
 
-    def user_params_from_auth(auth)
-      strategy          = Devise.omniauth_configs[auth.provider.to_sym].strategy
-      assume_verified   = strategy.try(:security).try(:assume_email_is_verified)
-      email_is_verified = auth.info.verified || auth.info.verified_email || assume_verified
-      email             = auth.info.verified_email || auth.info.email
-      email             = email_is_verified && !User.exists?(email: auth.info.email) && email
-      display_name      = auth.info.full_name || [auth.info.first_name, auth.info.last_name].join(' ')
-
+    def user_params_from_auth(email, auth)
       {
         email: email || "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
         password: Devise.friendly_token[0, 20],
@@ -69,7 +73,7 @@ module Omniauthable
         external: true,
         account_attributes: {
           username: ensure_unique_username(auth.uid),
-          display_name: display_name,
+          display_name: auth.info.full_name || [auth.info.first_name, auth.info.last_name].join(' '),
         },
       }
     end
diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb
index 9372a963bf64eef747b062399d3e05639def5567..b7a476c87a51548bc8cc47434f50dadceb23c799 100644
--- a/app/models/concerns/remotable.rb
+++ b/app/models/concerns/remotable.rb
@@ -4,7 +4,7 @@ module Remotable
   extend ActiveSupport::Concern
 
   class_methods do
-    def remotable_attachment(attachment_name, limit)
+    def remotable_attachment(attachment_name, limit, suppress_errors: true)
       attribute_name  = "#{attachment_name}_remote_url".to_sym
       method_name     = "#{attribute_name}=".to_sym
       alt_method_name = "reset_#{attachment_name}!".to_sym
@@ -18,11 +18,11 @@ module Remotable
           return
         end
 
-        return if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.blank? || self[attribute_name] == url
+        return if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.blank? || (self[attribute_name] == url && send("#{attachment_name}_file_name").present?)
 
         begin
           Request.new(:get, url).perform do |response|
-            next if response.code != 200
+            raise Mastodon::UnexpectedResponseError, response unless (200...300).cover?(response.code)
 
             content_type = parse_content_type(response.headers.get('content-type').last)
             extname      = detect_extname_from_content_type(content_type)
@@ -41,11 +41,11 @@ module Remotable
 
             self[attribute_name] = url if has_attribute?(attribute_name)
           end
-        rescue HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
+        rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError => e
+          Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}"
+          raise e unless suppress_errors
+        rescue Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Paperclip::Error, Mastodon::DimensionsValidationError => e
           Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}"
-          nil
-        rescue Paperclip::Error, Mastodon::DimensionsValidationError => e
-          Rails.logger.debug "Error processing remote #{attachment_name}: #{e}"
           nil
         end
       end
diff --git a/app/models/concerns/streamable.rb b/app/models/concerns/streamable.rb
deleted file mode 100644
index 7c9edb8ef8f6f89cd5f40d842a2d89497d69b081..0000000000000000000000000000000000000000
--- a/app/models/concerns/streamable.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-module Streamable
-  extend ActiveSupport::Concern
-
-  included do
-    has_one :stream_entry, as: :activity
-
-    after_create do
-      account.stream_entries.create!(activity: self, hidden: hidden?) if needs_stream_entry?
-    end
-  end
-
-  def title
-    super
-  end
-
-  def content
-    title
-  end
-
-  def target
-    super
-  end
-
-  def object_type
-    :activity
-  end
-
-  def thread
-    super
-  end
-
-  def hidden?
-    false
-  end
-
-  private
-
-  def needs_stream_entry?
-    account.local?
-  end
-end
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
index def1b3faa162057c89a80601ac45df7f5a3ae9c1..0dacaf654bc7c4f78cf074a9b592fc271aba6d59 100644
--- a/app/models/custom_emoji.rb
+++ b/app/models/custom_emoji.rb
@@ -16,6 +16,7 @@
 #  uri                :string
 #  image_remote_url   :string
 #  visible_in_picker  :boolean          default(TRUE), not null
+#  category_id        :bigint(8)
 #
 
 class CustomEmoji < ApplicationRecord
@@ -27,8 +28,9 @@ class CustomEmoji < ApplicationRecord
     :(#{SHORTCODE_RE_FRAGMENT}):
     (?=[^[:alnum:]:]|$)/x
 
-  IMAGE_MIME_TYPES = %w(image/png image/gif image/webp).freeze
+  IMAGE_MIME_TYPES = %w(image/png image/gif).freeze
 
+  belongs_to :category, class_name: 'CustomEmojiCategory', optional: true
   has_one :local_counterpart, -> { where(domain: nil) }, class_name: 'CustomEmoji', primary_key: :shortcode, foreign_key: :shortcode
 
   has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce -strip' } }
@@ -38,10 +40,11 @@ class CustomEmoji < ApplicationRecord
   validates_attachment :image, content_type: { content_type: IMAGE_MIME_TYPES }, presence: true, size: { less_than: LIMIT }
   validates :shortcode, uniqueness: { scope: :domain }, format: { with: /\A#{SHORTCODE_RE_FRAGMENT}\z/ }, length: { minimum: 2 }
 
-  scope :local,      -> { where(domain: nil) }
-  scope :remote,     -> { where.not(domain: nil) }
+  scope :local, -> { where(domain: nil) }
+  scope :remote, -> { where.not(domain: nil) }
   scope :alphabetic, -> { order(domain: :asc, shortcode: :asc) }
   scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches('%.' + domain))) }
+  scope :listed, -> { local.where(disabled: false).where(visible_in_picker: true) }
 
   remotable_attachment :image, LIMIT
 
@@ -57,6 +60,12 @@ class CustomEmoji < ApplicationRecord
     :emoji
   end
 
+  def copy!
+    copy = self.class.find_or_initialize_by(domain: nil, shortcode: shortcode)
+    copy.image = image
+    copy.tap(&:save!)
+  end
+
   class << self
     def from_text(text, domain)
       return [] if text.blank?
diff --git a/app/models/custom_emoji_category.rb b/app/models/custom_emoji_category.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3c87f2b2e5644471f14a25ca79429846a0377bde
--- /dev/null
+++ b/app/models/custom_emoji_category.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: custom_emoji_categories
+#
+#  id         :bigint(8)        not null, primary key
+#  name       :string
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#
+
+class CustomEmojiCategory < ApplicationRecord
+  has_many :emojis, class_name: 'CustomEmoji', foreign_key: 'category_id', inverse_of: :category
+
+  validates :name, presence: true, uniqueness: true
+end
diff --git a/app/models/custom_emoji_filter.rb b/app/models/custom_emoji_filter.rb
index 7649055d2839037ded1cd8eef11f3244c1258a0a..15b8da1d12f54ff6d62548dc5d33d1abd3868cc7 100644
--- a/app/models/custom_emoji_filter.rb
+++ b/app/models/custom_emoji_filter.rb
@@ -11,6 +11,8 @@ class CustomEmojiFilter
     scope = CustomEmoji.alphabetic
 
     params.each do |key, value|
+      next if key.to_s == 'page'
+
       scope.merge!(scope_for(key, value)) if value.present?
     end
 
@@ -22,13 +24,13 @@ class CustomEmojiFilter
   def scope_for(key, value)
     case key.to_s
     when 'local'
-      CustomEmoji.local
+      CustomEmoji.local.left_joins(:category).reorder(Arel.sql('custom_emoji_categories.name ASC NULLS FIRST, custom_emojis.shortcode ASC'))
     when 'remote'
       CustomEmoji.remote
     when 'by_domain'
-      CustomEmoji.where(domain: value.downcase)
+      CustomEmoji.where(domain: value.strip.downcase)
     when 'shortcode'
-      CustomEmoji.search(value)
+      CustomEmoji.search(value.strip)
     else
       raise "Unknown filter: #{key}"
     end
diff --git a/app/models/domain_allow.rb b/app/models/domain_allow.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5fe0e3a29c340d8a2da1dfdbdea1e32577b0880f
--- /dev/null
+++ b/app/models/domain_allow.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: domain_allows
+#
+#  id         :bigint(8)        not null, primary key
+#  domain     :string           default(""), not null
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#
+
+class DomainAllow < ApplicationRecord
+  include DomainNormalizable
+
+  validates :domain, presence: true, uniqueness: true, domain: true
+
+  scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
+
+  class << self
+    def allowed?(domain)
+      !rule_for(domain).nil?
+    end
+
+    def rule_for(domain)
+      return if domain.blank?
+
+      uri = Addressable::URI.new.tap { |u| u.host = domain.gsub(/[\/]/, '') }
+
+      find_by(domain: uri.normalized_host)
+    end
+  end
+end
diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb
index c230fb14bda41a021f62e5eb4eb1ae4dd8fa2c09..4e865b850a970145d39ea27aaeb6033b6134d0da 100644
--- a/app/models/domain_block.rb
+++ b/app/models/domain_block.rb
@@ -3,13 +3,15 @@
 #
 # Table name: domain_blocks
 #
-#  id             :bigint(8)        not null, primary key
-#  domain         :string           default(""), not null
-#  created_at     :datetime         not null
-#  updated_at     :datetime         not null
-#  severity       :integer          default("silence")
-#  reject_media   :boolean          default(FALSE), not null
-#  reject_reports :boolean          default(FALSE), not null
+#  id              :bigint(8)        not null, primary key
+#  domain          :string           default(""), not null
+#  created_at      :datetime         not null
+#  updated_at      :datetime         not null
+#  severity        :integer          default("silence")
+#  reject_media    :boolean          default(FALSE), not null
+#  reject_reports  :boolean          default(FALSE), not null
+#  private_comment :text
+#  public_comment  :text
 #
 
 class DomainBlock < ApplicationRecord
@@ -23,6 +25,8 @@ class DomainBlock < ApplicationRecord
   delegate :count, to: :accounts, prefix: true
 
   scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
+  scope :with_user_facing_limitations, -> { where(severity: [:silence, :suspend]).or(where(reject_media: true)) }
+  scope :by_severity, -> { order(Arel.sql('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), reject_media, domain')) }
 
   class << self
     def suspend?(domain)
diff --git a/app/models/favourite.rb b/app/models/favourite.rb
index 17f8c9fa631d2a758512dff8cb458ad2e92e1505..bf0ec4449c4615c6aee68c89b5c890bd18831330 100644
--- a/app/models/favourite.rb
+++ b/app/models/favourite.rb
@@ -13,7 +13,7 @@
 class Favourite < ApplicationRecord
   include Paginable
 
-  update_index('statuses#status', :status) if Chewy.enabled?
+  update_index('statuses#status', :status)
 
   belongs_to :account, inverse_of: :favourites
   belongs_to :status,  inverse_of: :favourites
diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb
index d06ae26a8902995bbfaa870812380703ff6b6a44..e02ae0705a2fceb71de5f4501205311ce5c6ca43 100644
--- a/app/models/featured_tag.rb
+++ b/app/models/featured_tag.rb
@@ -23,7 +23,7 @@ class FeaturedTag < ApplicationRecord
   validate :validate_featured_tags_limit, on: :create
 
   def name=(str)
-    self.tag = Tag.find_or_initialize_by(name: str.strip.delete('#').mb_chars.downcase.to_s)
+    self.tag = Tag.find_or_create_by_names(str.strip)&.first
   end
 
   def increment(timestamp)
diff --git a/app/models/feed.rb b/app/models/feed.rb
index 0e8943ff863bde253b75912a6870345bf974f048..36e0c1e0a09b94375a4ba3d2178c5dd00b7df0e0 100644
--- a/app/models/feed.rb
+++ b/app/models/feed.rb
@@ -9,6 +9,11 @@ class Feed
   end
 
   def get(limit, max_id = nil, since_id = nil, min_id = nil)
+    limit    = limit.to_i
+    max_id   = max_id.to_i if max_id.present?
+    since_id = since_id.to_i if since_id.present?
+    min_id   = min_id.to_i if min_id.present?
+
     from_redis(limit, max_id, since_id, min_id)
   end
 
diff --git a/app/models/form/account_batch.rb b/app/models/form/account_batch.rb
index f1b7a45664f25173a1000a7fdabee37876c77e9f..0b285fde92caa40d895f558d6aa9a0ff9bd116b6 100644
--- a/app/models/form/account_batch.rb
+++ b/app/models/form/account_batch.rb
@@ -69,6 +69,6 @@ class Form::AccountBatch
     records = accounts.includes(:user)
 
     records.each { |account| authorize(account.user, :reject?) }
-           .each { |account| SuspendAccountService.new.call(account, including_user: true, destroy: true, skip_distribution: true) }
+           .each { |account| SuspendAccountService.new.call(account, reserve_email: false, reserve_username: false) }
   end
 end
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index 86a86ec6661275de64b9d270a358c66674b50718..70e9c21f166613626d4eb279971749c6d4876b18 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -28,6 +28,12 @@ class Form::AdminSettings
     thumbnail
     hero
     mascot
+    spam_check_enabled
+    trends
+    trendable_by_default
+    show_domain_blocks
+    show_domain_blocks_rationale
+    noindex
   ).freeze
 
   BOOLEAN_KEYS = %i(
@@ -39,6 +45,10 @@ class Form::AdminSettings
     show_known_fediverse_at_about_page
     preview_sensitive_media
     profile_directory
+    spam_check_enabled
+    trends
+    trendable_by_default
+    noindex
   ).freeze
 
   UPLOAD_KEYS = %i(
@@ -56,6 +66,8 @@ class Form::AdminSettings
   validates :site_contact_email, :site_contact_username, presence: true
   validates :site_contact_username, existing_username: true
   validates :bootstrap_timeline_accounts, existing_username: { multiple: true }
+  validates :show_domain_blocks, inclusion: { in: %w(disabled users all) }
+  validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }
 
   def initialize(_attributes = {})
     super
diff --git a/app/models/form/challenge.rb b/app/models/form/challenge.rb
new file mode 100644
index 0000000000000000000000000000000000000000..40c99649cce8b21e3184bd0b26527ffb6dcdfaab
--- /dev/null
+++ b/app/models/form/challenge.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class Form::Challenge
+  include ActiveModel::Model
+
+  attr_accessor :current_password, :current_username,
+                :return_to
+end
diff --git a/app/models/form/custom_emoji_batch.rb b/app/models/form/custom_emoji_batch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..076e8c9e39aebd7b620c473cc11d6d0af7b93e04
--- /dev/null
+++ b/app/models/form/custom_emoji_batch.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+class Form::CustomEmojiBatch
+  include ActiveModel::Model
+  include Authorization
+  include AccountableConcern
+
+  attr_accessor :custom_emoji_ids, :action, :current_account,
+                :category_id, :category_name, :visible_in_picker
+
+  def save
+    case action
+    when 'update'
+      update!
+    when 'list'
+      list!
+    when 'unlist'
+      unlist!
+    when 'enable'
+      enable!
+    when 'disable'
+      disable!
+    when 'copy'
+      copy!
+    when 'delete'
+      delete!
+    end
+  end
+
+  private
+
+  def custom_emojis
+    CustomEmoji.where(id: custom_emoji_ids)
+  end
+
+  def update!
+    custom_emojis.each { |custom_emoji| authorize(custom_emoji, :update?) }
+
+    category = begin
+      if category_id.present?
+        CustomEmojiCategory.find(category_id)
+      elsif category_name.present?
+        CustomEmojiCategory.create!(name: category_name)
+      end
+    end
+
+    custom_emojis.each do |custom_emoji|
+      custom_emoji.update(category_id: category&.id)
+      log_action :update, custom_emoji
+    end
+  end
+
+  def list!
+    custom_emojis.each { |custom_emoji| authorize(custom_emoji, :update?) }
+
+    custom_emojis.each do |custom_emoji|
+      custom_emoji.update(visible_in_picker: true)
+      log_action :update, custom_emoji
+    end
+  end
+
+  def unlist!
+    custom_emojis.each { |custom_emoji| authorize(custom_emoji, :update?) }
+
+    custom_emojis.each do |custom_emoji|
+      custom_emoji.update(visible_in_picker: false)
+      log_action :update, custom_emoji
+    end
+  end
+
+  def enable!
+    custom_emojis.each { |custom_emoji| authorize(custom_emoji, :enable?) }
+
+    custom_emojis.each do |custom_emoji|
+      custom_emoji.update(disabled: false)
+      log_action :enable, custom_emoji
+    end
+  end
+
+  def disable!
+    custom_emojis.each { |custom_emoji| authorize(custom_emoji, :disable?) }
+
+    custom_emojis.each do |custom_emoji|
+      custom_emoji.update(disabled: true)
+      log_action :disable, custom_emoji
+    end
+  end
+
+  def copy!
+    custom_emojis.each { |custom_emoji| authorize(custom_emoji, :copy?) }
+
+    custom_emojis.each do |custom_emoji|
+      copied_custom_emoji = custom_emoji.copy!
+      log_action :create, copied_custom_emoji
+    end
+  end
+
+  def delete!
+    custom_emojis.each { |custom_emoji| authorize(custom_emoji, :destroy?) }
+
+    custom_emojis.each do |custom_emoji|
+      custom_emoji.destroy
+      log_action :destroy, custom_emoji
+    end
+  end
+end
diff --git a/app/models/form/delete_confirmation.rb b/app/models/form/delete_confirmation.rb
index 0884a09b882c9b1630a7686fba3cbaa23ace7437..99d04b331a588427be841f07b4f4f833cb9c0ad8 100644
--- a/app/models/form/delete_confirmation.rb
+++ b/app/models/form/delete_confirmation.rb
@@ -3,5 +3,5 @@
 class Form::DeleteConfirmation
   include ActiveModel::Model
 
-  attr_accessor :password
+  attr_accessor :password, :username
 end
diff --git a/app/models/form/migration.rb b/app/models/form/migration.rb
deleted file mode 100644
index c2a8655e13211d8c41bd7d39fbb93e6cd4a34cfd..0000000000000000000000000000000000000000
--- a/app/models/form/migration.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-class Form::Migration
-  include ActiveModel::Validations
-
-  attr_accessor :acct, :account
-
-  def initialize(attrs = {})
-    @account = attrs[:account]
-    @acct    = attrs[:account].acct unless @account.nil?
-    @acct    = attrs[:acct].gsub(/\A@/, '').strip unless attrs[:acct].nil?
-  end
-
-  def valid?
-    return false unless super
-    set_account
-    errors.empty?
-  end
-
-  private
-
-  def set_account
-    self.account = (ResolveAccountService.new.call(acct) if account.nil? && acct.present?)
-  end
-end
diff --git a/app/models/form/redirect.rb b/app/models/form/redirect.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a7961f8e8aa00e7b311a3742644e3f79c89ebf61
--- /dev/null
+++ b/app/models/form/redirect.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+class Form::Redirect
+  include ActiveModel::Model
+
+  attr_accessor :account, :target_account, :current_password,
+                :current_username
+
+  attr_reader :acct
+
+  validates :acct, presence: true, domain: { acct: true }
+  validate :validate_target_account
+
+  def valid_with_challenge?(current_user)
+    if current_user.encrypted_password.present?
+      errors.add(:current_password, :invalid) unless current_user.valid_password?(current_password)
+    else
+      errors.add(:current_username, :invalid) unless account.username == current_username
+    end
+
+    return false unless errors.empty?
+
+    set_target_account
+    valid?
+  end
+
+  def acct=(val)
+    @acct = val.to_s.strip.gsub(/\A@/, '')
+  end
+
+  private
+
+  def set_target_account
+    @target_account = ResolveAccountService.new.call(acct)
+  rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
+    # Validation will take care of it
+  end
+
+  def validate_target_account
+    if target_account.nil?
+      errors.add(:acct, I18n.t('migrations.errors.not_found'))
+    else
+      errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved_to_account_id.present? && account.moved_to_account_id == target_account.id
+      errors.add(:acct, I18n.t('migrations.errors.move_to_self')) if account.id == target_account.id
+    end
+  end
+end
diff --git a/app/models/form/status_batch.rb b/app/models/form/status_batch.rb
index 933dfdaca1ba5a7465093b921833b05f6da960f5..c4943a7eabd73a73c21b6024102e71f2c8e21a1a 100644
--- a/app/models/form/status_batch.rb
+++ b/app/models/form/status_batch.rb
@@ -34,7 +34,8 @@ class Form::StatusBatch
 
   def delete_statuses
     Status.where(id: status_ids).reorder(nil).find_each do |status|
-      RemovalWorker.perform_async(status.id)
+      status.discard
+      RemovalWorker.perform_async(status.id, immediate: true)
       Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true)
       log_action :destroy, status
     end
diff --git a/app/models/form/tag_batch.rb b/app/models/form/tag_batch.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fd517a1a64107e81b6d4706693b86093c7a79f3c
--- /dev/null
+++ b/app/models/form/tag_batch.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class Form::TagBatch
+  include ActiveModel::Model
+  include Authorization
+
+  attr_accessor :tag_ids, :action, :current_account
+
+  def save
+    case action
+    when 'approve'
+      approve!
+    when 'reject'
+      reject!
+    end
+  end
+
+  private
+
+  def tags
+    Tag.where(id: tag_ids)
+  end
+
+  def approve!
+    tags.each { |tag| authorize(tag, :update?) }
+    tags.update_all(trendable: true, reviewed_at: Time.now.utc)
+  end
+
+  def reject!
+    tags.each { |tag| authorize(tag, :update?) }
+    tags.update_all(trendable: false, reviewed_at: Time.now.utc)
+  end
+end
diff --git a/app/models/form/two_factor_confirmation.rb b/app/models/form/two_factor_confirmation.rb
index b8cf76d05887c7a1e00bf8eb565eab3fd590b4ae..27ada65333f4390d66ca819d0eb22af0a72265a0 100644
--- a/app/models/form/two_factor_confirmation.rb
+++ b/app/models/form/two_factor_confirmation.rb
@@ -3,5 +3,5 @@
 class Form::TwoFactorConfirmation
   include ActiveModel::Model
 
-  attr_accessor :code
+  attr_accessor :otp_attempt
 end
diff --git a/app/models/home_feed.rb b/app/models/home_feed.rb
index ba7564983b0f8bc9820759773c7d53b0ec075578..1fd506138be7db87e0730427393f792abca52370 100644
--- a/app/models/home_feed.rb
+++ b/app/models/home_feed.rb
@@ -7,19 +7,7 @@ class HomeFeed < Feed
     @account = account
   end
 
-  def get(limit, max_id = nil, since_id = nil, min_id = nil)
-    if redis.exists("account:#{@account.id}:regeneration")
-      from_database(limit, max_id, since_id, min_id)
-    else
-      super
-    end
-  end
-
-  private
-
-  def from_database(limit, max_id, since_id, min_id)
-    Status.as_home_timeline(@account)
-          .paginate_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
-          .reject { |status| FeedManager.instance.filter?(:home, status, @account.id) }
+  def regenerating?
+    redis.exists("account:#{@id}:regeneration")
   end
 end
diff --git a/app/models/instance.rb b/app/models/instance.rb
index 797a191e07698294b7f82b91aa96209171e4213c..3c740f8a2bc7dc50554db94fe082a12439fcc2bf 100644
--- a/app/models/instance.rb
+++ b/app/models/instance.rb
@@ -7,8 +7,9 @@ class Instance
 
   def initialize(resource)
     @domain         = resource.domain
-    @accounts_count = resource.is_a?(DomainBlock) ? nil : resource.accounts_count
+    @accounts_count = resource.respond_to?(:accounts_count) ? resource.accounts_count : nil
     @domain_block   = resource.is_a?(DomainBlock) ? resource : DomainBlock.rule_for(domain)
+    @domain_allow   = resource.is_a?(DomainAllow) ? resource : DomainAllow.rule_for(domain)
   end
 
   def countable?
diff --git a/app/models/instance_filter.rb b/app/models/instance_filter.rb
index 848fff53e15c5ee416224e39624d64b96295bc8c..8bfab826d7393685c8d0e3a545698c37015ad68d 100644
--- a/app/models/instance_filter.rb
+++ b/app/models/instance_filter.rb
@@ -12,6 +12,10 @@ class InstanceFilter
       scope = DomainBlock
       scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present?
       scope.order(id: :desc)
+    elsif params[:allowed].present?
+      scope = DomainAllow
+      scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present?
+      scope.order(id: :desc)
     else
       scope = Account.remote
       scope = scope.matches_domain(params[:by_domain]) if params[:by_domain].present?
diff --git a/app/models/invite.rb b/app/models/invite.rb
index 02ab8e0b2170926fc0e1125163615643f8b5115f..29d25eae801c3ea576bf45cffbd05d92024424f5 100644
--- a/app/models/invite.rb
+++ b/app/models/invite.rb
@@ -12,6 +12,7 @@
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
 #  autofollow :boolean          default(FALSE), not null
+#  comment    :text
 #
 
 class Invite < ApplicationRecord
@@ -22,6 +23,8 @@ class Invite < ApplicationRecord
 
   scope :available, -> { where(expires_at: nil).or(where('expires_at >= ?', Time.now.utc)) }
 
+  validates :comment, length: { maximum: 420 }
+
   before_validation :set_code
 
   def valid_for_use?
diff --git a/app/models/marker.rb b/app/models/marker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a5bd2176a84e25d0b00b56ae8b573399cdde894e
--- /dev/null
+++ b/app/models/marker.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: markers
+#
+#  id           :bigint(8)        not null, primary key
+#  user_id      :bigint(8)
+#  timeline     :string           default(""), not null
+#  last_read_id :bigint(8)        default(0), not null
+#  lock_version :integer          default(0), not null
+#  created_at   :datetime         not null
+#  updated_at   :datetime         not null
+#
+
+class Marker < ApplicationRecord
+  TIMELINES = %w(home notifications).freeze
+
+  belongs_to :user
+
+  validates :timeline, :last_read_id, presence: true
+  validates :timeline, inclusion: { in: TIMELINES }
+end
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index b1c5589478fb194f88a2b11f1a930af328459465..9c6c04556e282e971b6dd87e3f4b520b5bc936b9 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -26,14 +26,14 @@ class MediaAttachment < ApplicationRecord
 
   enum type: [:image, :gifv, :video, :unknown, :audio]
 
-  IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'].freeze
-  VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v', '.mov'].freeze
-  AUDIO_FILE_EXTENSIONS = ['.ogg', '.oga', '.mp3', '.wav', '.flac', '.opus'].freeze
+  IMAGE_FILE_EXTENSIONS = %w(.jpg .jpeg .png .gif).freeze
+  VIDEO_FILE_EXTENSIONS = %w(.webm .mp4 .m4v .mov).freeze
+  AUDIO_FILE_EXTENSIONS = %w(.ogg .oga .mp3 .wav .flac .opus .aac .m4a .3gp .wma).freeze
 
-  IMAGE_MIME_TYPES             = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze
-  VIDEO_MIME_TYPES             = ['video/webm', 'video/mp4', 'video/quicktime', 'video/ogg'].freeze
-  VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm', 'video/quicktime'].freeze
-  AUDIO_MIME_TYPES             = ['audio/wave', 'audio/wav', 'audio/x-wav', 'audio/x-pn-wave', 'audio/ogg', 'audio/mpeg', 'audio/mp3', 'audio/webm', 'audio/flac'].freeze
+  IMAGE_MIME_TYPES             = %w(image/jpeg image/png image/gif).freeze
+  VIDEO_MIME_TYPES             = %w(video/webm video/mp4 video/quicktime video/ogg).freeze
+  VIDEO_CONVERTIBLE_MIME_TYPES = %w(video/webm video/quicktime).freeze
+  AUDIO_MIME_TYPES             = %w(audio/wave audio/wav audio/x-wav audio/x-pn-wave audio/ogg audio/mpeg audio/mp3 audio/webm audio/flac audio/aac audio/m4a audio/x-m4a audio/mp4 audio/3gpp video/x-ms-asf).freeze
 
   BLURHASH_OPTIONS = {
     x_comp: 4,
@@ -57,6 +57,7 @@ class MediaAttachment < ApplicationRecord
     small: {
       convert_options: {
         output: {
+          'loglevel' => 'fatal',
           vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
         },
       },
@@ -65,6 +66,18 @@ class MediaAttachment < ApplicationRecord
       file_geometry_parser: FastGeometryParser,
       blurhash: BLURHASH_OPTIONS,
     },
+
+    original: {
+      keep_same_format: true,
+      convert_options: {
+        output: {
+          'loglevel' => 'fatal',
+          'map_metadata' => '-1',
+          'c:v' => 'copy',
+          'c:a' => 'copy',
+        },
+      },
+    },
   }.freeze
 
   AUDIO_STYLES = {
@@ -73,6 +86,7 @@ class MediaAttachment < ApplicationRecord
       content_type: 'audio/mpeg',
       convert_options: {
         output: {
+          'loglevel' => 'fatal',
           'q:a' => 2,
         },
       },
@@ -86,14 +100,15 @@ class MediaAttachment < ApplicationRecord
       output: {
         'loglevel' => 'fatal',
         'movflags' => 'faststart',
-        'pix_fmt'  => 'yuv420p',
-        'vf'       => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'',
-        'vsync'    => 'cfr',
-        'c:v'      => 'h264',
-        'b:v'      => '500K',
-        'maxrate'  => '1300K',
-        'bufsize'  => '1300K',
-        'crf'      => 18,
+        'pix_fmt' => 'yuv420p',
+        'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'',
+        'vsync' => 'cfr',
+        'c:v' => 'h264',
+        'maxrate' => '1300K',
+        'bufsize' => '1300K',
+        'frames:v' => 60 * 60 * 3,
+        'crf' => 18,
+        'map_metadata' => '-1',
       },
     },
   }.freeze
@@ -103,7 +118,7 @@ class MediaAttachment < ApplicationRecord
     original: VIDEO_FORMAT,
   }.freeze
 
-  IMAGE_LIMIT = 8.megabytes
+  IMAGE_LIMIT = 10.megabytes
   VIDEO_LIMIT = 40.megabytes
 
   belongs_to :account,          inverse_of: :media_attachments, optional: true
@@ -118,17 +133,18 @@ class MediaAttachment < ApplicationRecord
   validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES
   validates_attachment_size :file, less_than: IMAGE_LIMIT, unless: :larger_media_format?
   validates_attachment_size :file, less_than: VIDEO_LIMIT, if: :larger_media_format?
-  remotable_attachment :file, VIDEO_LIMIT
+  remotable_attachment :file, VIDEO_LIMIT, suppress_errors: false
 
   include Attachmentable
 
   validates :account, presence: true
-  validates :description, length: { maximum: 420 }, if: :local?
+  validates :description, length: { maximum: 1_500 }, if: :local?
 
   scope :attached,   -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) }
   scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) }
   scope :local,      -> { where(remote_url: '') }
   scope :remote,     -> { where.not(remote_url: '') }
+  scope :cached,     -> { remote.where.not(file_file_name: nil) }
 
   default_scope { order(id: :asc) }
 
@@ -243,7 +259,9 @@ class MediaAttachment < ApplicationRecord
 
   def set_meta
     meta = populate_meta
+
     return if meta == {}
+
     file.instance_write :meta, meta
   end
 
@@ -286,6 +304,7 @@ class MediaAttachment < ApplicationRecord
 
   def reset_parent_cache
     return if status_id.nil?
+
     Rails.cache.delete("statuses/#{status_id}")
   end
 end
diff --git a/app/models/poll.rb b/app/models/poll.rb
index 8f72c7b1124312ae20fc53edf067a6f2f610f4e6..5427368fd04401cc963be90972ebe4dba62a5a36 100644
--- a/app/models/poll.rb
+++ b/app/models/poll.rb
@@ -16,6 +16,7 @@
 #  created_at      :datetime         not null
 #  updated_at      :datetime         not null
 #  lock_version    :integer          default(0), not null
+#  voters_count    :bigint(8)
 #
 
 class Poll < ApplicationRecord
@@ -54,6 +55,10 @@ class Poll < ApplicationRecord
     account.id == account_id || votes.where(account: account).exists?
   end
 
+  def own_votes(account)
+    votes.where(account: account).pluck(:choice)
+  end
+
   delegate :local?, to: :account
 
   def remote?
diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb
index f26ea0c748085001a28c459e28569d0daa48b282..4e89fbf8510f9a33783aca9aa695d4b1580eb0c3 100644
--- a/app/models/preview_card.rb
+++ b/app/models/preview_card.rb
@@ -25,7 +25,7 @@
 #
 
 class PreviewCard < ApplicationRecord
-  IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze
+  IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
   LIMIT = 1.megabytes
 
   self.inheritance_column = false
@@ -43,8 +43,14 @@ class PreviewCard < ApplicationRecord
   validates_attachment_size :image, less_than: LIMIT
   remotable_attachment :image, LIMIT
 
+  scope :cached, -> { where.not(image_file_name: [nil, '']) }
+
   before_save :extract_dimensions, if: :link?
 
+  def missing_image?
+    width.present? && height.present? && image_file_name.blank?
+  end
+
   def save_with_optional_image!
     save!
   rescue ActiveRecord::RecordInvalid
diff --git a/app/models/relay.rb b/app/models/relay.rb
index 6934a5c628760f689ceb60244f07ef3eef06ef9d..8c8a97db3290c704ceedb37795c384c1c8c145af 100644
--- a/app/models/relay.rb
+++ b/app/models/relay.rb
@@ -12,8 +12,6 @@
 #
 
 class Relay < ApplicationRecord
-  PRESET_RELAY = 'https://relay.joinmastodon.org/inbox'
-
   validates :inbox_url, presence: true, uniqueness: true, url: true, if: :will_save_change_to_inbox_url?
 
   enum state: [:idle, :pending, :accepted, :rejected]
@@ -74,7 +72,6 @@ class Relay < ApplicationRecord
   end
 
   def ensure_disabled
-    return unless enabled?
-    disable!
+    disable! if enabled?
   end
 end
diff --git a/app/models/remote_follow.rb b/app/models/remote_follow.rb
index 2537de36c7b3f3ef8b6526114ebefd8196b4d293..5ea53528727b0a4f66f4041d96149d6468ad6427 100644
--- a/app/models/remote_follow.rb
+++ b/app/models/remote_follow.rb
@@ -2,24 +2,26 @@
 
 class RemoteFollow
   include ActiveModel::Validations
+  include RoutingHelper
 
   attr_accessor :acct, :addressable_template
 
-  validates :acct, presence: true
+  validates :acct, presence: true, domain: { acct: true }
 
-  def initialize(attrs = nil)
-    @acct = attrs[:acct].gsub(/\A@/, '').strip if !attrs.nil? && !attrs[:acct].nil?
+  def initialize(attrs = {})
+    @acct = normalize_acct(attrs[:acct])
   end
 
   def valid?
     return false unless super
 
-    populate_template
+    fetch_template!
+
     errors.empty?
   end
 
   def subscribe_address_for(account)
-    addressable_template.expand(uri: account.local_username_and_domain).to_s
+    addressable_template.expand(uri: ActivityPub::TagManager.instance.uri_for(account)).to_s
   end
 
   def interact_address_for(status)
@@ -28,8 +30,32 @@ class RemoteFollow
 
   private
 
-  def populate_template
-    if acct.blank? || redirect_url_link.nil? || redirect_url_link.template.nil?
+  def normalize_acct(value)
+    return if value.blank?
+
+    username, domain = value.strip.gsub(/\A@/, '').split('@')
+
+    domain = begin
+      if TagManager.instance.local_domain?(domain)
+        nil
+      else
+        TagManager.instance.normalize_domain(domain)
+      end
+    end
+
+    [username, domain].compact.join('@')
+  rescue Addressable::URI::InvalidURIError
+    value
+  end
+
+  def fetch_template!
+    return missing_resource_error if acct.blank?
+
+    _, domain = acct.split('@')
+
+    if domain.nil?
+      @addressable_template = Addressable::Template.new("#{authorize_interaction_url}?uri={uri}")
+    elsif redirect_url_link.nil? || redirect_url_link.template.nil?
       missing_resource_error
     else
       @addressable_template = Addressable::Template.new(redirect_uri_template)
@@ -45,7 +71,7 @@ class RemoteFollow
   end
 
   def acct_resource
-    @_acct_resource ||= Goldfinger.finger("acct:#{acct}")
+    @acct_resource ||= Goldfinger.finger("acct:#{acct}")
   rescue Goldfinger::Error, HTTP::ConnectionError
     nil
   end
diff --git a/app/models/remote_profile.rb b/app/models/remote_profile.rb
deleted file mode 100644
index 742d2b56f4c61e6864d3e34e34d1b1ac3cb23575..0000000000000000000000000000000000000000
--- a/app/models/remote_profile.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# frozen_string_literal: true
-
-class RemoteProfile
-  include ActiveModel::Model
-
-  attr_reader :document
-
-  def initialize(body)
-    @document = Nokogiri::XML.parse(body, nil, 'utf-8')
-  end
-
-  def root
-    @root ||= document.at_xpath('/atom:feed|/atom:entry', atom: OStatus::TagManager::XMLNS)
-  end
-
-  def author
-    @author ||= root.at_xpath('./atom:author|./dfrn:owner', atom: OStatus::TagManager::XMLNS, dfrn: OStatus::TagManager::DFRN_XMLNS)
-  end
-
-  def hub_link
-    @hub_link ||= link_href_from_xml(root, 'hub')
-  end
-
-  def display_name
-    @display_name ||= author.at_xpath('./poco:displayName', poco: OStatus::TagManager::POCO_XMLNS)&.content
-  end
-
-  def note
-    @note ||= author.at_xpath('./atom:summary|./poco:note', atom: OStatus::TagManager::XMLNS, poco: OStatus::TagManager::POCO_XMLNS)&.content
-  end
-
-  def scope
-    @scope ||= author.at_xpath('./mastodon:scope', mastodon: OStatus::TagManager::MTDN_XMLNS)&.content
-  end
-
-  def avatar
-    @avatar ||= link_href_from_xml(author, 'avatar')
-  end
-
-  def header
-    @header ||= link_href_from_xml(author, 'header')
-  end
-
-  def emojis
-    @emojis ||= author.xpath('./xmlns:link[@rel="emoji"]', xmlns: OStatus::TagManager::XMLNS)
-  end
-
-  def locked?
-    scope == 'private'
-  end
-
-  private
-
-  def link_href_from_xml(xml, type)
-    xml.at_xpath(%(./atom:link[@rel="#{type}"]/@href), atom: OStatus::TagManager::XMLNS)&.content
-  end
-end
diff --git a/app/models/report.rb b/app/models/report.rb
index 5192ceef799cceee317fc8322eb23c81b2658596..fb2e040ee8bc8f871f82af0d1585f5d0222080ff 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -43,7 +43,7 @@ class Report < ApplicationRecord
   end
 
   def statuses
-    Status.where(id: status_ids).includes(:account, :media_attachments, :mentions)
+    Status.with_discarded.where(id: status_ids).includes(:account, :media_attachments, :mentions)
   end
 
   def media_attachments
@@ -59,6 +59,7 @@ class Report < ApplicationRecord
   end
 
   def resolve!(acting_account)
+    RemovalWorker.push_bulk(Status.with_discarded.discarded.where(id: status_ids).pluck(:id)) { |status_id| [status_id, { immediate: true }] }
     update!(action_taken: true, action_taken_by_account_id: acting_account.id)
   end
 
diff --git a/app/models/status.rb b/app/models/status.rb
index acbebf0cb4caa38e9cf8310d195ffbf54eb5a8db..8dd50e40c45756d1cf4b2a49be9aeb93285aeb0b 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -22,22 +22,25 @@
 #  application_id         :bigint(8)
 #  in_reply_to_account_id :bigint(8)
 #  poll_id                :bigint(8)
+#  deleted_at             :datetime
 #  local_only             :boolean
 #
 
 class Status < ApplicationRecord
   before_destroy :unlink_from_conversations
 
+  include Discard::Model
   include Paginable
-  include Streamable
   include Cacheable
   include StatusThreadingConcern
 
+  self.discard_column = :deleted_at
+
   # If `override_timestamps` is set at creation time, Snowflake ID creation
   # will be based on current time instead of `created_at`
   attr_accessor :override_timestamps
 
-  update_index('statuses#status', :proper) if Chewy.enabled?
+  update_index('statuses#status', :proper)
 
   enum visibility: [:public, :unlisted, :private, :direct, :limited], _suffix: :visibility
 
@@ -62,7 +65,6 @@ class Status < ApplicationRecord
   has_and_belongs_to_many :preview_cards
 
   has_one :notification, as: :activity, dependent: :destroy
-  has_one :stream_entry, as: :activity, inverse_of: :status
   has_one :status_stat, inverse_of: :status
   has_one :poll, inverse_of: :status, dependent: :destroy
 
@@ -75,7 +77,7 @@ class Status < ApplicationRecord
 
   accepts_nested_attributes_for :poll
 
-  default_scope { recent }
+  default_scope { recent.kept }
 
   scope :recent, -> { reorder(id: :desc) }
   scope :remote, -> { where(local: false).where.not(uri: nil) }
@@ -108,13 +110,11 @@ class Status < ApplicationRecord
                    :status_stat,
                    :tags,
                    :preview_cards,
-                   :stream_entry,
                    :preloadable_poll,
                    account: :account_stat,
                    active_mentions: { account: :account_stat },
                    reblog: [
                      :application,
-                     :stream_entry,
                      :tags,
                      :preview_cards,
                      :media_attachments,
@@ -131,12 +131,14 @@ class Status < ApplicationRecord
   REAL_TIME_WINDOW = 6.hours
 
   def searchable_by(preloaded = nil)
-    ids = [account_id]
+    ids = []
+
+    ids << account_id if local?
 
     if preloaded.nil?
-      ids += mentions.pluck(:account_id)
-      ids += favourites.pluck(:account_id)
-      ids += reblogs.pluck(:account_id)
+      ids += mentions.where(account: Account.local).pluck(:account_id)
+      ids += favourites.where(account: Account.local).pluck(:account_id)
+      ids += reblogs.where(account: Account.local).pluck(:account_id)
     else
       ids += preloaded.mentions[id] || []
       ids += preloaded.favourites[id] || []
@@ -203,7 +205,7 @@ class Status < ApplicationRecord
   end
 
   def hidden?
-    private_visibility? || direct_visibility? || limited_visibility?
+    !distributable?
   end
 
   def distributable?
@@ -220,6 +222,10 @@ class Status < ApplicationRecord
     !sensitive? && with_media?
   end
 
+  def reported?
+    @reported ||= Report.where(target_account: account).unresolved.where('? = ANY(status_ids)', id).exists?
+  end
+
   def emojis
     return @emojis if defined?(@emojis)
 
@@ -284,51 +290,6 @@ class Status < ApplicationRecord
       where(language: nil).or where(language: account.chosen_languages)
     end
 
-    def as_home_timeline(account)
-      where(account: [account] + account.following).where(visibility: [:public, :unlisted, :private])
-    end
-
-    def as_direct_timeline(account, limit = 20, max_id = nil, since_id = nil, cache_ids = false)
-      # direct timeline is mix of direct message from_me and to_me.
-      # 2 queries are executed with pagination.
-      # constant expression using arel_table is required for partial index
-
-      # _from_me part does not require any timeline filters
-      query_from_me = where(account_id: account.id)
-                      .where(Status.arel_table[:visibility].eq(3))
-                      .limit(limit)
-                      .order('statuses.id DESC')
-
-      # _to_me part requires mute and block filter.
-      # FIXME: may we check mutes.hide_notifications?
-      query_to_me = Status
-                    .joins(:mentions)
-                    .merge(Mention.where(account_id: account.id))
-                    .where(Status.arel_table[:visibility].eq(3))
-                    .limit(limit)
-                    .order('mentions.status_id DESC')
-                    .not_excluded_by_account(account)
-
-      if max_id.present?
-        query_from_me = query_from_me.where('statuses.id < ?', max_id)
-        query_to_me = query_to_me.where('mentions.status_id < ?', max_id)
-      end
-
-      if since_id.present?
-        query_from_me = query_from_me.where('statuses.id > ?', since_id)
-        query_to_me = query_to_me.where('mentions.status_id > ?', since_id)
-      end
-
-      if cache_ids
-        # returns array of cache_ids object that have id and updated_at
-        (query_from_me.cache_ids.to_a + query_to_me.cache_ids.to_a).uniq(&:id).sort_by(&:id).reverse.take(limit)
-      else
-        # returns ActiveRecord.Relation
-        items = (query_from_me.select(:id).to_a + query_to_me.select(:id).to_a).uniq(&:id).sort_by(&:id).reverse.take(limit)
-        Status.where(id: items.map(&:id))
-      end
-    end
-
     def as_public_timeline(account = nil, local_only = false)
       query = timeline_scope(local_only).without_replies
 
@@ -350,7 +311,7 @@ class Status < ApplicationRecord
     end
 
     def reblogs_map(status_ids, account_id)
-      select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).reorder(nil).each_with_object({}) { |s, h| h[s.reblog_of_id] = true }
+      unscoped.select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).each_with_object({}) { |s, h| h[s.reblog_of_id] = true }
     end
 
     def mutes_map(conversation_ids, account_id)
@@ -441,13 +402,16 @@ class Status < ApplicationRecord
     end
   end
 
+  def status_stat
+    super || build_status_stat
+  end
+
   private
 
   def update_status_stat!(attrs)
     return if marked_for_destruction? || destroyed?
 
-    record = status_stat || build_status_stat
-    record.update(attrs)
+    status_stat.update(attrs)
   end
 
   def store_uri
@@ -503,7 +467,8 @@ class Status < ApplicationRecord
   end
 
   def update_statistics
-    return unless public_visibility? || unlisted_visibility?
+    return unless distributable?
+
     ActivityTracker.increment('activity:statuses:local')
   end
 
@@ -512,7 +477,7 @@ class Status < ApplicationRecord
 
     account&.increment_count!(:statuses_count)
     reblog&.increment_count!(:reblogs_count) if reblog?
-    thread&.increment_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?)
+    thread&.increment_count!(:replies_count) if in_reply_to_id.present? && distributable?
   end
 
   def decrement_counter_caches
@@ -520,7 +485,7 @@ class Status < ApplicationRecord
 
     account&.decrement_count!(:statuses_count)
     reblog&.decrement_count!(:reblogs_count) if reblog?
-    thread&.decrement_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?)
+    thread&.decrement_count!(:replies_count) if in_reply_to_id.present? && distributable?
   end
 
   def unlink_from_conversations
diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb
deleted file mode 100644
index b0990df90ccb9b9b74accbfaf7cbb59e3493d1fc..0000000000000000000000000000000000000000
--- a/app/models/stream_entry.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# frozen_string_literal: true
-# == Schema Information
-#
-# Table name: stream_entries
-#
-#  id            :bigint(8)        not null, primary key
-#  activity_id   :bigint(8)
-#  activity_type :string
-#  created_at    :datetime         not null
-#  updated_at    :datetime         not null
-#  hidden        :boolean          default(FALSE), not null
-#  account_id    :bigint(8)
-#
-
-class StreamEntry < ApplicationRecord
-  include Paginable
-
-  belongs_to :account, inverse_of: :stream_entries
-  belongs_to :activity, polymorphic: true
-  belongs_to :status, foreign_type: 'Status', foreign_key: 'activity_id', inverse_of: :stream_entry
-
-  validates :account, :activity, presence: true
-
-  STATUS_INCLUDES = [:account, :stream_entry, :conversation, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, :conversation, :media_attachments, :tags, mentions: :account], thread: [:stream_entry, :account]].freeze
-
-  default_scope { where(activity_type: 'Status') }
-  scope :recent, -> { reorder(id: :desc) }
-  scope :with_includes, -> { includes(:account, status: STATUS_INCLUDES) }
-  scope :without_local_only, -> { where(statuses: { local_only: [false, nil] }) }
-
-  delegate :target, :title, :content, :thread, :local_only?,
-           to: :status,
-           allow_nil: true
-
-  def object_type
-    orphaned? || targeted? ? :activity : status.object_type
-  end
-
-  def verb
-    orphaned? ? :delete : status.verb
-  end
-
-  def targeted?
-    [:follow, :request_friend, :authorize, :reject, :unfollow, :block, :unblock, :share, :favorite].include? verb
-  end
-
-  def threaded?
-    (verb == :favorite || object_type == :comment) && !thread.nil?
-  end
-
-  def mentions
-    orphaned? ? [] : status.active_mentions.map(&:account)
-  end
-
-  private
-
-  def orphaned?
-    status.nil?
-  end
-end
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
deleted file mode 100644
index 79b81828da51eed043e0d5df28717ef566bbd266..0000000000000000000000000000000000000000
--- a/app/models/subscription.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-# frozen_string_literal: true
-# == Schema Information
-#
-# Table name: subscriptions
-#
-#  id                          :bigint(8)        not null, primary key
-#  callback_url                :string           default(""), not null
-#  secret                      :string
-#  expires_at                  :datetime
-#  confirmed                   :boolean          default(FALSE), not null
-#  created_at                  :datetime         not null
-#  updated_at                  :datetime         not null
-#  last_successful_delivery_at :datetime
-#  domain                      :string
-#  account_id                  :bigint(8)        not null
-#
-
-class Subscription < ApplicationRecord
-  MIN_EXPIRATION = 1.day.to_i
-  MAX_EXPIRATION = 30.days.to_i
-
-  belongs_to :account
-
-  validates :callback_url, presence: true
-  validates :callback_url, uniqueness: { scope: :account_id }
-
-  scope :confirmed, -> { where(confirmed: true) }
-  scope :future_expiration, -> { where(arel_table[:expires_at].gt(Time.now.utc)) }
-  scope :expired, -> { where(arel_table[:expires_at].lt(Time.now.utc)) }
-  scope :active, -> { confirmed.future_expiration }
-
-  def lease_seconds=(value)
-    self.expires_at = future_expiration(value)
-  end
-
-  def lease_seconds
-    (expires_at - Time.now.utc).to_i
-  end
-
-  def expired?
-    Time.now.utc > expires_at
-  end
-
-  before_validation :set_min_expiration
-
-  private
-
-  def future_expiration(value)
-    Time.now.utc + future_offset(value).seconds
-  end
-
-  def future_offset(seconds)
-    [
-      [MIN_EXPIRATION, seconds.to_i].max,
-      MAX_EXPIRATION,
-    ].min
-  end
-
-  def set_min_expiration
-    self.lease_seconds = 0 unless expires_at
-  end
-end
diff --git a/app/models/tag.rb b/app/models/tag.rb
index b371d59c1be396a5070e12d913aff4b6fe616e27..d3a7e1e6d4e2d8f1c6cf47a1609b03d47ac8d3bc 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -3,38 +3,55 @@
 #
 # Table name: tags
 #
-#  id         :bigint(8)        not null, primary key
-#  name       :string           default(""), not null
-#  created_at :datetime         not null
-#  updated_at :datetime         not null
+#  id                  :bigint(8)        not null, primary key
+#  name                :string           default(""), not null
+#  created_at          :datetime         not null
+#  updated_at          :datetime         not null
+#  usable              :boolean
+#  trendable           :boolean
+#  listable            :boolean
+#  reviewed_at         :datetime
+#  requested_review_at :datetime
+#  last_status_at      :datetime
+#  max_score           :float
+#  max_score_at        :datetime
 #
 
 class Tag < ApplicationRecord
   has_and_belongs_to_many :statuses
   has_and_belongs_to_many :accounts
-  has_and_belongs_to_many :sample_accounts, -> { searchable.discoverable.popular.limit(3) }, class_name: 'Account'
+  has_and_belongs_to_many :sample_accounts, -> { local.discoverable.popular.limit(3) }, class_name: 'Account'
 
   has_many :featured_tags, dependent: :destroy, inverse_of: :tag
   has_one :account_tag_stat, dependent: :destroy
 
-  HASHTAG_NAME_RE = '([[:word:]_][[:word:]_·]*[[:alpha:]_·][[:word:]_·]*[[:word:]_])|([[:word:]_]*[[:alpha:]][[:word:]_]*)'
-  HASHTAG_RE = /(?:^|[^\/\)\w])#(#{HASHTAG_NAME_RE})/i
+  HASHTAG_SEPARATORS = "_\u00B7\u200c"
+  HASHTAG_NAME_RE    = "([[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}][[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_])|([[:word:]_]*[[:alpha:]][[:word:]_]*)"
+  HASHTAG_RE         = /(?:^|[^\/\)\w])#(#{HASHTAG_NAME_RE})/i
 
-  validates :name, presence: true, uniqueness: true, format: { with: /\A(#{HASHTAG_NAME_RE})\z/i }
+  validates :name, presence: true, format: { with: /\A(#{HASHTAG_NAME_RE})\z/i }
+  validate :validate_name_change, if: -> { !new_record? && name_changed? }
 
-  scope :discoverable, -> { joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).where(account_tag_stats: { hidden: false }).order(Arel.sql('account_tag_stats.accounts_count desc')) }
-  scope :hidden, -> { where(account_tag_stats: { hidden: true }) }
+  scope :reviewed, -> { where.not(reviewed_at: nil) }
+  scope :unreviewed, -> { where(reviewed_at: nil) }
+  scope :pending_review, -> { unreviewed.where.not(requested_review_at: nil) }
+  scope :usable, -> { where(usable: [true, nil]) }
+  scope :listable, -> { where(listable: [true, nil]) }
+  scope :trendable, -> { Setting.trendable_by_default ? where(trendable: [true, nil]) : where(trendable: true) }
+  scope :discoverable, -> { listable.joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).order(Arel.sql('account_tag_stats.accounts_count desc')) }
   scope :most_used, ->(account) { joins(:statuses).where(statuses: { account: account }).group(:id).order(Arel.sql('count(*) desc')) }
+  scope :matches_name, ->(value) { where(arel_table[:name].matches("#{value}%")) }
 
   delegate :accounts_count,
            :accounts_count=,
            :increment_count!,
            :decrement_count!,
-           :hidden?,
            to: :account_tag_stat
 
   after_save :save_account_tag_stat
 
+  update_index('tags#tag', :self)
+
   def account_tag_stat
     super || build_account_tag_stat
   end
@@ -47,6 +64,40 @@ class Tag < ApplicationRecord
     name
   end
 
+  def usable
+    boolean_with_default('usable', true)
+  end
+
+  alias usable? usable
+
+  def listable
+    boolean_with_default('listable', true)
+  end
+
+  alias listable? listable
+
+  def trendable
+    boolean_with_default('trendable', Setting.trendable_by_default)
+  end
+
+  alias trendable? trendable
+
+  def requires_review?
+    reviewed_at.nil?
+  end
+
+  def reviewed?
+    reviewed_at.present?
+  end
+
+  def requested_review?
+    requested_review_at.present?
+  end
+
+  def trending?
+    TrendingTags.trending?(self)
+  end
+
   def history
     days = []
 
@@ -64,22 +115,50 @@ class Tag < ApplicationRecord
   end
 
   class << self
-    def search_for(term, limit = 5, offset = 0)
-      pattern = sanitize_sql_like(term.strip) + '%'
+    def find_or_create_by_names(name_or_names)
+      Array(name_or_names).map(&method(:normalize)).uniq { |str| str.mb_chars.downcase.to_s }.map do |normalized_name|
+        tag = matching_name(normalized_name).first || create!(name: normalized_name)
+
+        yield tag if block_given?
 
-      Tag.where('lower(name) like lower(?)', pattern)
-         .order(:name)
-         .limit(limit)
-         .offset(offset)
+        tag
+      end
+    end
+
+    def search_for(term, limit = 5, offset = 0, options = {})
+      normalized_term = normalize(term.strip).mb_chars.downcase.to_s
+      pattern         = sanitize_sql_like(normalized_term) + '%'
+      query           = Tag.listable.where(arel_table[:name].lower.matches(pattern))
+      query           = query.where(arel_table[:name].lower.eq(normalized_term).or(arel_table[:reviewed_at].not_eq(nil))) if options[:exclude_unreviewed]
+
+      query.order(Arel.sql('length(name) ASC, name ASC'))
+           .limit(limit)
+           .offset(offset)
     end
 
     def find_normalized(name)
-      find_by(name: name.mb_chars.downcase.to_s)
+      matching_name(name).first
     end
 
     def find_normalized!(name)
       find_normalized(name) || raise(ActiveRecord::RecordNotFound)
     end
+
+    def matching_name(name_or_names)
+      names = Array(name_or_names).map { |name| normalize(name).mb_chars.downcase.to_s }
+
+      if names.size == 1
+        where(arel_table[:name].lower.eq(names.first))
+      else
+        where(arel_table[:name].lower.in(names))
+      end
+    end
+
+    private
+
+    def normalize(str)
+      str.gsub(/\A#/, '')
+    end
   end
 
   private
@@ -88,4 +167,8 @@ class Tag < ApplicationRecord
     return unless account_tag_stat&.changed?
     account_tag_stat.save
   end
+
+  def validate_name_change
+    errors.add(:name, I18n.t('tags.does_not_match_previous_name')) unless name_was.mb_chars.casecmp(name.mb_chars).zero?
+  end
 end
diff --git a/app/models/tag_filter.rb b/app/models/tag_filter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8921e186b05b498184cdcb77e2a9ddc7a795f574
--- /dev/null
+++ b/app/models/tag_filter.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+class TagFilter
+  attr_reader :params
+
+  def initialize(params)
+    @params = params
+  end
+
+  def results
+    scope = Tag.unscoped
+
+    params.each do |key, value|
+      next if key.to_s == 'page'
+
+      scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
+    end
+
+    scope.order(id: :desc)
+  end
+
+  private
+
+  def scope_for(key, value)
+    case key.to_s
+    when 'directory'
+      Tag.discoverable
+    when 'reviewed'
+      Tag.reviewed.order(reviewed_at: :desc)
+    when 'unreviewed'
+      Tag.unreviewed
+    when 'pending_review'
+      Tag.pending_review.order(requested_review_at: :desc)
+    when 'popular'
+      Tag.order('max_score DESC NULLS LAST')
+    when 'active'
+      Tag.order('last_status_at DESC NULLS LAST')
+    when 'name'
+      Tag.matches_name(value)
+    else
+      raise "Unknown filter: #{key}"
+    end
+  end
+end
diff --git a/app/models/trending_tags.rb b/app/models/trending_tags.rb
index 148535c2121e0a2c9d783a8fb971226ac5d00e29..c69f6d3c331cc8ca1e0b692be02dc46bc2f5fe4c 100644
--- a/app/models/trending_tags.rb
+++ b/app/models/trending_tags.rb
@@ -5,23 +5,100 @@ class TrendingTags
   EXPIRE_HISTORY_AFTER = 7.days.seconds
   EXPIRE_TRENDS_AFTER  = 1.day.seconds
   THRESHOLD            = 5
+  LIMIT                = 10
+  REVIEW_THRESHOLD     = 3
+  MAX_SCORE_COOLDOWN   = 2.days.freeze
+  MAX_SCORE_HALFLIFE   = 2.hours.freeze
 
   class << self
     include Redisable
 
     def record_use!(tag, account, at_time = Time.now.utc)
-      return if disallowed_hashtags.include?(tag.name) || account.silenced? || account.bot?
+      return if account.silenced? || account.bot? || !tag.usable? || !(tag.trendable? || tag.requires_review?)
 
       increment_historical_use!(tag.id, at_time)
       increment_unique_use!(tag.id, account.id, at_time)
-      increment_vote!(tag.id, at_time)
+      increment_use!(tag.id, at_time)
+
+      tag.update(last_status_at: Time.now.utc) if tag.last_status_at.nil? || tag.last_status_at < 12.hours.ago
+    end
+
+    def update!(at_time = Time.now.utc)
+      tag_ids = redis.smembers("#{KEY}:used:#{at_time.beginning_of_day.to_i}") + redis.zrange(KEY, 0, -1)
+      tags    = Tag.where(id: tag_ids.uniq)
+
+      # First pass to calculate scores and update the set
+
+      tags.each do |tag|
+        expected  = redis.pfcount("activity:tags:#{tag.id}:#{(at_time - 1.day).beginning_of_day.to_i}:accounts").to_f
+        expected  = 1.0 if expected.zero?
+        observed  = redis.pfcount("activity:tags:#{tag.id}:#{at_time.beginning_of_day.to_i}:accounts").to_f
+        max_time  = tag.max_score_at
+        max_score = tag.max_score
+        max_score = 0 if max_time.nil? || max_time < (at_time - MAX_SCORE_COOLDOWN)
+
+        score = begin
+          if expected > observed || observed < THRESHOLD
+            0
+          else
+            ((observed - expected)**2) / expected
+          end
+        end
+
+        if score > max_score
+          max_score = score
+          max_time  = at_time
+
+          # Not interested in triggering any callbacks for this
+          tag.update_columns(max_score: max_score, max_score_at: max_time)
+        end
+
+        decaying_score = max_score * (0.5**((at_time.to_f - max_time.to_f) / MAX_SCORE_HALFLIFE.to_f))
+
+        if decaying_score.zero?
+          redis.zrem(KEY, tag.id)
+        else
+          redis.zadd(KEY, decaying_score, tag.id)
+        end
+      end
+
+      users_for_review = User.staff.includes(:account).to_a.select(&:allows_trending_tag_emails?)
+
+      # Second pass to notify about previously unreviewed trends
+
+      tags.each do |tag|
+        current_rank              = redis.zrevrank(KEY, tag.id)
+        needs_review_notification = tag.requires_review? && !tag.requested_review?
+        rank_passes_threshold     = current_rank.present? && current_rank <= REVIEW_THRESHOLD
+
+        next unless !tag.trendable? && rank_passes_threshold && needs_review_notification
+
+        tag.touch(:requested_review_at)
+
+        users_for_review.each do |user|
+          AdminMailer.new_trending_tag(user.account, tag).deliver_later!
+        end
+      end
+
+      # Trim older items
+
+      redis.zremrangebyrank(KEY, 0, -(LIMIT + 1))
+      redis.zremrangebyscore(KEY, '(0.3', '-inf')
     end
 
-    def get(limit)
-      key     = "#{KEY}:#{Time.now.utc.beginning_of_day.to_i}"
-      tag_ids = redis.zrevrange(key, 0, limit - 1).map(&:to_i)
-      tags    = Tag.where(id: tag_ids).to_a.each_with_object({}) { |tag, h| h[tag.id] = tag }
-      tag_ids.map { |tag_id| tags[tag_id] }.compact
+    def get(limit, filtered: true)
+      tag_ids = redis.zrevrange(KEY, 0, LIMIT - 1).map(&:to_i)
+
+      tags = Tag.where(id: tag_ids)
+      tags = tags.trendable if filtered
+      tags = tags.each_with_object({}) { |tag, h| h[tag.id] = tag }
+
+      tag_ids.map { |tag_id| tags[tag_id] }.compact.take(limit)
+    end
+
+    def trending?(tag)
+      rank = redis.zrevrank(KEY, tag.id)
+      rank.present? && rank < LIMIT
     end
 
     private
@@ -38,28 +115,10 @@ class TrendingTags
       redis.expire(key, EXPIRE_HISTORY_AFTER)
     end
 
-    def increment_vote!(tag_id, at_time)
-      key      = "#{KEY}:#{at_time.beginning_of_day.to_i}"
-      expected = redis.pfcount("activity:tags:#{tag_id}:#{(at_time - 1.day).beginning_of_day.to_i}:accounts").to_f
-      expected = 1.0 if expected.zero?
-      observed = redis.pfcount("activity:tags:#{tag_id}:#{at_time.beginning_of_day.to_i}:accounts").to_f
-
-      if expected > observed || observed < THRESHOLD
-        redis.zrem(key, tag_id.to_s)
-      else
-        score = ((observed - expected)**2) / expected
-        redis.zadd(key, score, tag_id.to_s)
-      end
-
-      redis.expire(key, EXPIRE_TRENDS_AFTER)
-    end
-
-    def disallowed_hashtags
-      return @disallowed_hashtags if defined?(@disallowed_hashtags)
-
-      @disallowed_hashtags = Setting.disallowed_hashtags.nil? ? [] : Setting.disallowed_hashtags
-      @disallowed_hashtags = @disallowed_hashtags.split(' ') if @disallowed_hashtags.is_a? String
-      @disallowed_hashtags = @disallowed_hashtags.map(&:downcase)
+    def increment_use!(tag_id, at_time)
+      key = "#{KEY}:used:#{at_time.beginning_of_day.to_i}"
+      redis.sadd(key, tag_id)
+      redis.expire(key, EXPIRE_HISTORY_AFTER)
     end
   end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 2ff886531a30d2632c1d8a6c5bfe9c92281d126c..b34546ab98447d67a13ce94f8057346a3ae923eb 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -74,6 +74,7 @@ class User < ApplicationRecord
   has_many :applications, class_name: 'Doorkeeper::Application', as: :owner
   has_many :backups, inverse_of: :user
   has_many :invites, inverse_of: :user
+  has_many :markers, inverse_of: :user, dependent: :destroy
 
   has_one :invite_request, class_name: 'UserInviteRequest', inverse_of: :user, dependent: :destroy
   accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? }
@@ -107,7 +108,8 @@ class User < ApplicationRecord
   delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal,
            :reduce_motion, :system_font_ui, :noindex, :theme, :display_media, :hide_network,
            :expand_spoilers, :default_language, :aggregate_reblogs, :show_application,
-           :advanced_layout, :default_federation, to: :settings, prefix: :setting, allow_nil: false
+           :advanced_layout, :use_blurhash, :use_pending_items, :trends, :default_federation,
+           to: :settings, prefix: :setting, allow_nil: false
 
   attr_reader :invite_code
   attr_writer :external
@@ -162,7 +164,15 @@ class User < ApplicationRecord
   end
 
   def active_for_authentication?
-    super && approved?
+    true
+  end
+
+  def functional?
+    confirmed? && approved? && !disabled? && !account.suspended? && account.moved_to_account_id.nil?
+  end
+
+  def unconfirmed_or_pending?
+    !(confirmed? && approved?)
   end
 
   def inactive_message
@@ -203,6 +213,10 @@ class User < ApplicationRecord
     settings.notification_emails['pending_account']
   end
 
+  def allows_trending_tag_emails?
+    settings.notification_emails['trending_tag']
+  end
+
   def hides_network?
     @hides_network ||= settings.hide_network
   end
@@ -250,17 +264,20 @@ class User < ApplicationRecord
   end
 
   def password_required?
-    return false if Devise.pam_authentication || Devise.ldap_authentication
+    return false if external?
+
     super
   end
 
   def send_reset_password_instructions
-    return false if encrypted_password.blank? && (Devise.pam_authentication || Devise.ldap_authentication)
+    return false if encrypted_password.blank?
+
     super
   end
 
   def reset_password!(new_password, new_password_confirmation)
-    return false if encrypted_password.blank? && (Devise.pam_authentication || Devise.ldap_authentication)
+    return false if encrypted_password.blank?
+
     super
   end
 
diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb
index b57807d1c2bd5a38144cc617d4911eaabeeb5e97..c5dbb58baad0e5d01340074118ef9577dec9006f 100644
--- a/app/models/web/push_subscription.rb
+++ b/app/models/web/push_subscription.rb
@@ -20,6 +20,10 @@ class Web::PushSubscription < ApplicationRecord
 
   has_one :session_activation, foreign_key: 'web_push_subscription_id', inverse_of: :web_push_subscription
 
+  validates :endpoint, presence: true
+  validates :key_p256dh, presence: true
+  validates :key_auth, presence: true
+
   def push(notification)
     I18n.with_locale(associated_user&.locale || I18n.default_locale) do
       push_payload(payload_for_notification(notification), 48.hours.seconds)
diff --git a/app/policies/domain_allow_policy.rb b/app/policies/domain_allow_policy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5030453bbcc2cb86ce07b079f9ec1a83353e9169
--- /dev/null
+++ b/app/policies/domain_allow_policy.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class DomainAllowPolicy < ApplicationPolicy
+  def create?
+    admin?
+  end
+
+  def destroy?
+    admin?
+  end
+end
diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb
index a5646d43a48c923ead46e329818edf861776fe3a..00e12bcbc9b18e308e0298dedbc5c7d0276aa6e8 100644
--- a/app/policies/status_policy.rb
+++ b/app/policies/status_policy.rb
@@ -19,7 +19,7 @@ class StatusPolicy < ApplicationPolicy
     elsif private?
       owned? || following_author? || mention_exists?
     else
-      current_account.nil? || !author_blocking?
+      current_account.nil? || (!author_blocking? && !author_blocking_domain?)
     end
   end
 
@@ -65,6 +65,12 @@ class StatusPolicy < ApplicationPolicy
     end
   end
 
+  def author_blocking_domain?
+    return false if current_account.nil? || current_account.domain.nil?
+
+    author.domain_blocking?(current_account.domain)
+  end
+
   def blocking_author?
     return false if current_account.nil?
 
diff --git a/app/policies/subscription_policy.rb b/app/policies/subscription_policy.rb
deleted file mode 100644
index ac9a8a6c44189dca9dc4d2674ba6931ddd5f5ce9..0000000000000000000000000000000000000000
--- a/app/policies/subscription_policy.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class SubscriptionPolicy < ApplicationPolicy
-  def index?
-    admin?
-  end
-end
diff --git a/app/policies/tag_policy.rb b/app/policies/tag_policy.rb
index c63de01dbe9ef4a34de76da9b90c77438928c8e4..aaf70fcabc4c53afd67a6ee5283e2d9ee1cda9a2 100644
--- a/app/policies/tag_policy.rb
+++ b/app/policies/tag_policy.rb
@@ -5,11 +5,11 @@ class TagPolicy < ApplicationPolicy
     staff?
   end
 
-  def hide?
+  def show?
     staff?
   end
 
-  def unhide?
+  def update?
     staff?
   end
 end
diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb
index f3a73209afe56658c78372d3f2b272e5fbcdd10b..c150bf742be5799816de757aa6d0795326dddaff 100644
--- a/app/presenters/instance_presenter.rb
+++ b/app/presenters/instance_presenter.rb
@@ -20,8 +20,8 @@ class InstancePresenter
     Rails.cache.fetch('user_count') { User.confirmed.joins(:account).merge(Account.without_suspended).count }
   end
 
-  def active_user_count
-    Rails.cache.fetch('active_user_count') { Redis.current.pfcount(*(0..3).map { |i| "activity:logins:#{i.weeks.ago.utc.to_date.cweek}" }) }
+  def active_user_count(weeks = 4)
+    Rails.cache.fetch("active_user_count/#{weeks}") { Redis.current.pfcount(*(0...weeks).map { |i| "activity:logins:#{i.weeks.ago.utc.to_date.cweek}" }) }
   end
 
   def status_count
@@ -33,7 +33,7 @@ class InstancePresenter
   end
 
   def sample_accounts
-    Rails.cache.fetch('sample_accounts', expires_in: 12.hours) { Account.discoverable.popular.limit(3) }
+    Rails.cache.fetch('sample_accounts', expires_in: 12.hours) { Account.local.discoverable.popular.limit(3) }
   end
 
   def version_number
diff --git a/app/serializers/activitypub/activity_serializer.rb b/app/serializers/activitypub/activity_serializer.rb
index c06d5c87ca3855f0a9c6d6dd20c10ec1eebec1df..d0edad78682ba3383cd2c252c6a5d4055e4f6e0b 100644
--- a/app/serializers/activitypub/activity_serializer.rb
+++ b/app/serializers/activitypub/activity_serializer.rb
@@ -4,6 +4,7 @@ class ActivityPub::ActivitySerializer < ActivityPub::Serializer
   attributes :id, :type, :actor, :published, :to, :cc
 
   has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, if: :serialize_object?
+
   attribute :proper_uri, key: :object, unless: :serialize_object?
   attribute :atom_uri, if: :announce?
 
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index 0644219fb69bf5babc78e725150f70cfa480dc51..17df85de31a6fbc60dee5324ab5570b7d6ac4522 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -6,12 +6,14 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
   context :security
 
   context_extensions :manually_approves_followers, :featured, :also_known_as,
-                     :moved_to, :property_value, :hashtag, :emoji, :identity_proof
+                     :moved_to, :property_value, :identity_proof,
+                     :discoverable
 
   attributes :id, :type, :following, :followers,
              :inbox, :outbox, :featured,
              :preferred_username, :name, :summary,
-             :url, :manually_approves_followers
+             :url, :manually_approves_followers,
+             :discoverable
 
   has_one :public_key, serializer: ActivityPub::PublicKeySerializer
 
@@ -39,11 +41,17 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
   delegate :moved?, to: :object
 
   def id
-    account_url(object)
+    object.instance_actor? ? instance_actor_url : account_url(object)
   end
 
   def type
-    object.bot? ? 'Service' : 'Person'
+    if object.instance_actor?
+      'Application'
+    elsif object.bot?
+      'Service'
+    else
+      'Person'
+    end
   end
 
   def following
@@ -55,7 +63,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
   end
 
   def inbox
-    account_inbox_url(object)
+    object.instance_actor? ? instance_actor_inbox_url : account_inbox_url(object)
   end
 
   def outbox
@@ -95,7 +103,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
   end
 
   def url
-    short_account_url(object)
+    object.instance_actor? ? about_more_url(instance_actor: true) : short_account_url(object)
   end
 
   def avatar_exists?
@@ -130,6 +138,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
   end
 
   class TagSerializer < ActivityPub::Serializer
+    context_extensions :hashtag
+
     include RoutingHelper
 
     attributes :type, :href, :name
diff --git a/app/serializers/activitypub/move_serializer.rb b/app/serializers/activitypub/move_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5675875fa52e588d7e990b72b7b19a46a90c2f80
--- /dev/null
+++ b/app/serializers/activitypub/move_serializer.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class ActivityPub::MoveSerializer < ActivityPub::Serializer
+  attributes :id, :type, :target, :actor
+  attribute :virtual_object, key: :object
+
+  def id
+    [ActivityPub::TagManager.instance.uri_for(object.account), '#moves/', object.id].join
+  end
+
+  def type
+    'Move'
+  end
+
+  def target
+    ActivityPub::TagManager.instance.uri_for(object.target_account)
+  end
+
+  def virtual_object
+    ActivityPub::TagManager.instance.uri_for(object.account)
+  end
+
+  def actor
+    ActivityPub::TagManager.instance.uri_for(object.account)
+  end
+end
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 67f596e78a65042a85821ae7693047dbfefc9e4e..110621a28bf88ec6498b776368bac696bfa0fdc5 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -1,8 +1,7 @@
 # frozen_string_literal: true
 
 class ActivityPub::NoteSerializer < ActivityPub::Serializer
-  context_extensions :atom_uri, :conversation, :sensitive,
-                     :hashtag, :emoji, :focal_point, :blurhash
+  context_extensions :atom_uri, :conversation, :sensitive, :voters_count
 
   attributes :id, :type, :summary,
              :in_reply_to, :published, :url,
@@ -24,6 +23,8 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
   attribute :end_time, if: :poll_and_expires?
   attribute :closed, if: :poll_and_expired?
 
+  attribute :voters_count, if: :poll_and_voters_count?
+
   def id
     ActivityPub::TagManager.instance.uri_for(object)
   end
@@ -55,7 +56,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
         type: :unordered,
         part_of: ActivityPub::TagManager.instance.replies_uri_for(object),
         items: replies.map(&:second),
-        next: last_id ? ActivityPub::TagManager.instance.replies_uri_for(object, page: true, min_id: last_id) : nil
+        next: last_id ? ActivityPub::TagManager.instance.replies_uri_for(object, page: true, min_id: last_id) : ActivityPub::TagManager.instance.replies_uri_for(object, page: true, only_other_accounts: true)
       )
     )
   end
@@ -142,6 +143,10 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
 
   alias end_time closed
 
+  def voters_count
+    object.preloadable_poll.voters_count
+  end
+
   def poll_and_expires?
     object.preloadable_poll&.expires_at&.present?
   end
@@ -150,7 +155,13 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
     object.preloadable_poll&.expired?
   end
 
+  def poll_and_voters_count?
+    object.preloadable_poll&.voters_count
+  end
+
   class MediaAttachmentSerializer < ActivityPub::Serializer
+    context_extensions :blurhash, :focal_point
+
     include RoutingHelper
 
     attributes :type, :media_type, :url, :name, :blurhash
@@ -198,6 +209,8 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
   end
 
   class TagSerializer < ActivityPub::Serializer
+    context_extensions :hashtag
+
     include RoutingHelper
 
     attributes :type, :href, :name
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index 883598eb0b8642bbcbdd58787ba4e95c74f8b8a0..e5c43bd7bfd8b6eb75f0c19296ab057c4167a93b 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -12,6 +12,7 @@ class InitialStateSerializer < ActiveModel::Serializer
       access_token: object.token,
       locale: I18n.locale,
       domain: Rails.configuration.x.local_domain,
+      title: instance_presenter.site_title,
       admin: object.admin&.id&.to_s,
       search_enabled: Chewy.enabled?,
       repository: Mastodon::Version.repository,
@@ -20,19 +21,28 @@ class InitialStateSerializer < ActiveModel::Serializer
       invites_enabled: Setting.min_invite_role == 'user',
       mascot: instance_presenter.mascot&.file&.url,
       profile_directory: Setting.profile_directory,
+      trends: Setting.trends,
     }
 
     if object.current_account
-      store[:me]              = object.current_account.id.to_s
-      store[:unfollow_modal]  = object.current_account.user.setting_unfollow_modal
-      store[:boost_modal]     = object.current_account.user.setting_boost_modal
-      store[:delete_modal]    = object.current_account.user.setting_delete_modal
-      store[:auto_play_gif]   = object.current_account.user.setting_auto_play_gif
-      store[:display_media]   = object.current_account.user.setting_display_media
-      store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers
-      store[:reduce_motion]   = object.current_account.user.setting_reduce_motion
-      store[:advanced_layout] = object.current_account.user.setting_advanced_layout
-      store[:is_staff]        = object.current_account.user.staff?
+      store[:me]                = object.current_account.id.to_s
+      store[:unfollow_modal]    = object.current_account.user.setting_unfollow_modal
+      store[:boost_modal]       = object.current_account.user.setting_boost_modal
+      store[:delete_modal]      = object.current_account.user.setting_delete_modal
+      store[:auto_play_gif]     = object.current_account.user.setting_auto_play_gif
+      store[:display_media]     = object.current_account.user.setting_display_media
+      store[:expand_spoilers]   = object.current_account.user.setting_expand_spoilers
+      store[:reduce_motion]     = object.current_account.user.setting_reduce_motion
+      store[:advanced_layout]   = object.current_account.user.setting_advanced_layout
+      store[:use_blurhash]      = object.current_account.user.setting_use_blurhash
+      store[:use_pending_items] = object.current_account.user.setting_use_pending_items
+      store[:is_staff]          = object.current_account.user.staff?
+      store[:trends]            = Setting.trends && object.current_account.user.setting_trends
+    else
+      store[:auto_play_gif] = Setting.auto_play_gif
+      store[:display_media] = Setting.display_media
+      store[:reduce_motion] = Setting.reduce_motion
+      store[:use_blurhash]  = Setting.use_blurhash
     end
 
     store
diff --git a/app/serializers/manifest_serializer.rb b/app/serializers/manifest_serializer.rb
index 28127437d568831e33836d2bef63ae2addcc6b73..21ec0d4bed2f96f38b9ec58f1f6dc4950a6413fc 100644
--- a/app/serializers/manifest_serializer.rb
+++ b/app/serializers/manifest_serializer.rb
@@ -55,6 +55,8 @@ class ManifestSerializer < ActiveModel::Serializer
     {
       url_template: 'share?title={title}&text={text}&url={url}',
       action: 'share',
+      method: 'GET',
+      enctype: 'application/x-www-form-urlencoded',
       params: {
         title: 'title',
         text: 'text',
diff --git a/app/serializers/nodeinfo/discovery_serializer.rb b/app/serializers/nodeinfo/discovery_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..07ab2a6eee304e3f64bf51ab626e3969ec93c89f
--- /dev/null
+++ b/app/serializers/nodeinfo/discovery_serializer.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class NodeInfo::DiscoverySerializer < ActiveModel::Serializer
+  include RoutingHelper
+
+  attribute :links
+
+  def links
+    [{ rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', href: nodeinfo_schema_url }]
+  end
+end
diff --git a/app/serializers/nodeinfo/serializer.rb b/app/serializers/nodeinfo/serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7ff8aabecc56c50eddde2294511bc26635a3d646
--- /dev/null
+++ b/app/serializers/nodeinfo/serializer.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+class NodeInfo::Serializer < ActiveModel::Serializer
+  include RoutingHelper
+
+  attributes :version, :software, :protocols, :usage, :open_registrations
+
+  def version
+    '2.0'
+  end
+
+  def software
+    { name: 'mastodon', version: Mastodon::Version.to_s }
+  end
+
+  def services
+    { outbound: [], inbound: [] }
+  end
+
+  def protocols
+    %w(activitypub)
+  end
+
+  def usage
+    {
+      users: {
+        total: instance_presenter.user_count,
+        active_month: instance_presenter.active_user_count(4),
+        active_halfyear: instance_presenter.active_user_count(24),
+      },
+
+      local_posts: instance_presenter.status_count,
+    }
+  end
+
+  def open_registrations
+    Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode
+  end
+
+  private
+
+  def instance_presenter
+    @instance_presenter ||= InstancePresenter.new
+  end
+end
diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb
index 12adc971cb76a6894338b2dcf342d34026b287a3..75b6cf13bb7b74e96229838fdcef4aefd39d0cff 100644
--- a/app/serializers/rest/account_serializer.rb
+++ b/app/serializers/rest/account_serializer.rb
@@ -5,7 +5,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
 
   attributes :id, :username, :acct, :display_name, :locked, :bot, :created_at,
              :note, :url, :avatar, :avatar_static, :header, :header_static,
-             :followers_count, :following_count, :statuses_count
+             :followers_count, :following_count, :statuses_count, :last_status_at
 
   has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested?
   has_many :emojis, serializer: REST::CustomEmojiSerializer
@@ -29,7 +29,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
   end
 
   def url
-    TagManager.instance.url_for(object)
+    ActivityPub::TagManager.instance.url_for(object)
   end
 
   def avatar
diff --git a/app/serializers/rest/credential_account_serializer.rb b/app/serializers/rest/credential_account_serializer.rb
index f07f9f05802386365fa3c0aad9d924afd84bbc85..fa8f900cc594170dc646a0fab0fee5643a6082b3 100644
--- a/app/serializers/rest/credential_account_serializer.rb
+++ b/app/serializers/rest/credential_account_serializer.rb
@@ -13,6 +13,7 @@ class REST::CredentialAccountSerializer < REST::AccountSerializer
       federation: user.setting_default_federation,
       note: object.note,
       fields: object.fields.map(&:to_h),
+      follow_requests_count: FollowRequest.where(target_account: object).limit(40).count,
     }
   end
 end
diff --git a/app/serializers/rest/custom_emoji_serializer.rb b/app/serializers/rest/custom_emoji_serializer.rb
index 65686a86668d2fc450a73bf4fe9bacdfd325dcc6..aff58e4d9466accb3b61142fa2b69b9d65d48fa6 100644
--- a/app/serializers/rest/custom_emoji_serializer.rb
+++ b/app/serializers/rest/custom_emoji_serializer.rb
@@ -5,6 +5,8 @@ class REST::CustomEmojiSerializer < ActiveModel::Serializer
 
   attributes :shortcode, :url, :static_url, :visible_in_picker
 
+  attribute :category, if: :category_loaded?
+
   def url
     full_asset_url(object.image.url)
   end
@@ -12,4 +14,12 @@ class REST::CustomEmojiSerializer < ActiveModel::Serializer
   def static_url
     full_asset_url(object.image.url(:static))
   end
+
+  def category
+    object.category.name
+  end
+
+  def category_loaded?
+    object.association(:category).loaded? && object.category.present?
+  end
 end
diff --git a/app/serializers/rest/featured_tag_serializer.rb b/app/serializers/rest/featured_tag_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..08121ff16db2be23bcb352b5a4f4cfc129eb74a0
--- /dev/null
+++ b/app/serializers/rest/featured_tag_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class REST::FeaturedTagSerializer < ActiveModel::Serializer
+  attributes :id, :name, :statuses_count, :last_status_at
+
+  def id
+    object.id.to_s
+  end
+end
diff --git a/app/serializers/rest/marker_serializer.rb b/app/serializers/rest/marker_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2eaf3d5076724ce8f0195655fa8c8628d24d92bd
--- /dev/null
+++ b/app/serializers/rest/marker_serializer.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class REST::MarkerSerializer < ActiveModel::Serializer
+  attributes :last_read_id, :version, :updated_at
+
+  def last_read_id
+    object.last_read_id.to_s
+  end
+
+  def version
+    object.lock_version
+  end
+end
diff --git a/app/serializers/rest/poll_serializer.rb b/app/serializers/rest/poll_serializer.rb
index 356c45b83868de1d96d257cc5838d0085df37e67..df6ebd0d44676c5f5a9d4014fccb031015ae1cd3 100644
--- a/app/serializers/rest/poll_serializer.rb
+++ b/app/serializers/rest/poll_serializer.rb
@@ -2,12 +2,13 @@
 
 class REST::PollSerializer < ActiveModel::Serializer
   attributes :id, :expires_at, :expired,
-             :multiple, :votes_count
+             :multiple, :votes_count, :voters_count
 
   has_many :loaded_options, key: :options
   has_many :emojis, serializer: REST::CustomEmojiSerializer
 
   attribute :voted, if: :current_user?
+  attribute :own_votes, if: :current_user?
 
   def id
     object.id.to_s
@@ -21,6 +22,10 @@ class REST::PollSerializer < ActiveModel::Serializer
     object.voted?(current_user.account)
   end
 
+  def own_votes
+    object.own_votes(current_user.account)
+  end
+
   def current_user?
     !current_user.nil?
   end
diff --git a/app/serializers/rest/search_serializer.rb b/app/serializers/rest/search_serializer.rb
index 157f543aef716a9d413e13ed2e3f4f18cdc4ce5a..ee9b421eb223daf94650dc58f8e07fbb1e7c925b 100644
--- a/app/serializers/rest/search_serializer.rb
+++ b/app/serializers/rest/search_serializer.rb
@@ -1,12 +1,7 @@
 # frozen_string_literal: true
 
 class REST::SearchSerializer < ActiveModel::Serializer
-  attributes :hashtags
-
   has_many :accounts, serializer: REST::AccountSerializer
   has_many :statuses, serializer: REST::StatusSerializer
-
-  def hashtags
-    object.hashtags.map(&:name)
-  end
+  has_many :hashtags, serializer: REST::TagSerializer
 end
diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb
index 7d2dc4e82d27ce17965a0352f99ae41156e30fcf..ad0287a91c9cdfc93dc7d1ed73282ce6e82f6c16 100644
--- a/app/serializers/rest/status_serializer.rb
+++ b/app/serializers/rest/status_serializer.rb
@@ -58,7 +58,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
   end
 
   def uri
-    OStatus::TagManager.instance.uri_for(object)
+    ActivityPub::TagManager.instance.uri_for(object)
   end
 
   def content
@@ -66,7 +66,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
   end
 
   def url
-    TagManager.instance.url_for(object)
+    ActivityPub::TagManager.instance.url_for(object)
   end
 
   def favourited
@@ -132,7 +132,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
     end
 
     def url
-      TagManager.instance.url_for(object.account)
+      ActivityPub::TagManager.instance.url_for(object.account)
     end
 
     def acct
diff --git a/app/serializers/rest/v2/search_serializer.rb b/app/serializers/rest/v2/search_serializer.rb
deleted file mode 100644
index cdb6b3a53012c974414fa90aa0928983e732aaa4..0000000000000000000000000000000000000000
--- a/app/serializers/rest/v2/search_serializer.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class REST::V2::SearchSerializer < ActiveModel::Serializer
-  has_many :accounts, serializer: REST::AccountSerializer
-  has_many :statuses, serializer: REST::StatusSerializer
-  has_many :hashtags, serializer: REST::TagSerializer
-end
diff --git a/app/serializers/rss/account_serializer.rb b/app/serializers/rss/account_serializer.rb
index 88eca79ed2311cacf7ceb4b58da32942c8586cce..e39b2b3720ebdeafa6ebb5187d55a9c59171ea9c 100644
--- a/app/serializers/rss/account_serializer.rb
+++ b/app/serializers/rss/account_serializer.rb
@@ -2,15 +2,15 @@
 
 class RSS::AccountSerializer
   include ActionView::Helpers::NumberHelper
-  include StreamEntriesHelper
+  include StatusesHelper
   include RoutingHelper
 
-  def render(account, statuses)
+  def render(account, statuses, tag)
     builder = RSSBuilder.new
 
     builder.title("#{display_name(account)} (@#{account.local_username_and_domain})")
            .description(account_description(account))
-           .link(TagManager.instance.url_for(account))
+           .link(tag.present? ? short_account_tag_url(account, tag) : short_account_url(account))
            .logo(full_pack_url('media/images/logo.svg'))
            .accent_color('2b90d9')
 
@@ -20,12 +20,12 @@ class RSS::AccountSerializer
     statuses.each do |status|
       builder.item do |item|
         item.title(status.title)
-            .link(TagManager.instance.url_for(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)
 
         status.media_attachments.each do |media|
-          item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, length: media.file.size)
+          item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
         end
       end
     end
@@ -33,7 +33,7 @@ class RSS::AccountSerializer
     builder.to_xml
   end
 
-  def self.render(account, statuses)
-    new.render(account, statuses)
+  def self.render(account, statuses, tag)
+    new.render(account, statuses, tag)
   end
 end
diff --git a/app/serializers/rss/tag_serializer.rb b/app/serializers/rss/tag_serializer.rb
index 644380149bab9ceae35120d0a077687a038635ef..6737fb2c90088eef2550c615988c4062ec2e47c3 100644
--- a/app/serializers/rss/tag_serializer.rb
+++ b/app/serializers/rss/tag_serializer.rb
@@ -3,7 +3,7 @@
 class RSS::TagSerializer
   include ActionView::Helpers::NumberHelper
   include ActionView::Helpers::SanitizeHelper
-  include StreamEntriesHelper
+  include StatusesHelper
   include RoutingHelper
 
   def render(tag, statuses)
@@ -18,12 +18,12 @@ class RSS::TagSerializer
     statuses.each do |status|
       builder.item do |item|
         item.title(status.title)
-            .link(TagManager.instance.url_for(status))
+            .link(ActivityPub::TagManager.instance.url_for(status))
             .pub_date(status.created_at)
             .description(status.spoiler_text.presence || Formatter.instance.format(status).to_str)
 
         status.media_attachments.each do |media|
-          item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, length: media.file.size)
+          item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
         end
       end
     end
diff --git a/app/serializers/webfinger_serializer.rb b/app/serializers/webfinger_serializer.rb
index 8c0b077020dcd2ce7833cf7063c8db3bf764bfb0..c67363b8f8be6880bca876f5aae2ff88d8a9b5dd 100644
--- a/app/serializers/webfinger_serializer.rb
+++ b/app/serializers/webfinger_serializer.rb
@@ -10,17 +10,25 @@ class WebfingerSerializer < ActiveModel::Serializer
   end
 
   def aliases
-    [short_account_url(object), account_url(object)]
+    if object.instance_actor?
+      [instance_actor_url]
+    else
+      [short_account_url(object), account_url(object)]
+    end
   end
 
   def links
-    [
-      { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: short_account_url(object) },
-      { rel: 'http://schemas.google.com/g/2010#updates-from', type: 'application/atom+xml', href: account_url(object, format: 'atom') },
-      { rel: 'self', type: 'application/activity+json', href: account_url(object) },
-      { rel: 'salmon', href: api_salmon_url(object.id) },
-      { rel: 'magic-public-key', href: "data:application/magic-public-key,#{object.magic_key}" },
-      { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" },
-    ]
+    if object.instance_actor?
+      [
+        { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: about_more_url(instance_actor: true) },
+        { rel: 'self', type: 'application/activity+json', href: instance_actor_url },
+      ]
+    else
+      [
+        { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: short_account_url(object) },
+        { rel: 'self', type: 'application/activity+json', href: account_url(object) },
+        { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" },
+      ]
+    end
   end
 end
diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb
index 7bdffbbd22c94f9bd3096c727d86819bfd0810d4..40c5f8590c9888a5278adf7ca1485b4c35a3ca56 100644
--- a/app/services/account_search_service.rb
+++ b/app/services/account_search_service.rb
@@ -4,98 +4,177 @@ class AccountSearchService < BaseService
   attr_reader :query, :limit, :offset, :options, :account
 
   def call(query, account = nil, options = {})
-    @query   = query.strip
-    @limit   = options[:limit].to_i
-    @offset  = options[:offset].to_i
-    @options = options
-    @account = account
+    @acct_hint = query.start_with?('@')
+    @query     = query.strip.gsub(/\A@/, '')
+    @limit     = options[:limit].to_i
+    @offset    = options[:offset].to_i
+    @options   = options
+    @account   = account
 
-    search_service_results
+    search_service_results.compact.uniq
   end
 
   private
 
   def search_service_results
-    return [] if query_blank_or_hashtag? || limit < 1
+    return [] if query.blank? || limit < 1
 
-    if resolving_non_matching_remote_account?
-      [ResolveAccountService.new.call("#{query_username}@#{query_domain}")].compact
-    else
-      search_results_and_exact_match.compact.uniq.slice(0, limit)
-    end
+    [exact_match] + search_results
   end
 
-  def resolving_non_matching_remote_account?
-    options[:resolve] && !exact_match && !domain_is_local?
-  end
+  def exact_match
+    return unless offset.zero? && username_complete?
 
-  def search_results_and_exact_match
-    exact = [exact_match]
-    return exact if !exact[0].nil? && limit == 1
-    exact + search_results.to_a
-  end
+    return @exact_match if defined?(@exact_match)
 
-  def query_blank_or_hashtag?
-    query.blank? || query.start_with?('#')
+    @exact_match = begin
+      if options[:resolve]
+        ResolveAccountService.new.call(query)
+      elsif domain_is_local?
+        Account.find_local(query_username)
+      else
+        Account.find_remote(query_username, query_domain)
+      end
+    end
   end
 
-  def split_query_string
-    @_split_query_string ||= query.gsub(/\A@/, '').split('@')
-  end
+  def search_results
+    return [] if limit_for_non_exact_results.zero?
 
-  def query_username
-    @_query_username ||= split_query_string.first || ''
+    @search_results ||= begin
+      results = from_elasticsearch if Chewy.enabled?
+      results ||= from_database
+      results
+    end
   end
 
-  def query_domain
-    @_query_domain ||= query_without_split? ? nil : split_query_string.last
+  def from_database
+    if account
+      advanced_search_results
+    else
+      simple_search_results
+    end
   end
 
-  def query_without_split?
-    split_query_string.size == 1
+  def advanced_search_results
+    Account.advanced_search_for(terms_for_query, account, limit_for_non_exact_results, options[:following], offset)
   end
 
-  def domain_is_local?
-    @_domain_is_local ||= TagManager.instance.local_domain?(query_domain)
+  def simple_search_results
+    Account.search_for(terms_for_query, limit_for_non_exact_results, offset)
   end
 
-  def search_from
-    options[:following] && account ? account.following : Account
-  end
+  def from_elasticsearch
+    must_clauses   = [{ multi_match: { query: terms_for_query, fields: likely_acct? ? %w(acct.edge_ngram acct) : %w(acct.edge_ngram acct display_name.edge_ngram display_name), type: 'most_fields', operator: 'and' } }]
+    should_clauses = []
 
-  def exact_match
-    @_exact_match ||= begin
-      if domain_is_local?
-        search_from.without_suspended.find_local(query_username)
-      else
-        search_from.without_suspended.find_remote(query_username, query_domain)
+    if account
+      return [] if options[:following] && following_ids.empty?
+
+      if options[:following]
+        must_clauses << { terms: { id: following_ids } }
+      elsif following_ids.any?
+        should_clauses << { terms: { id: following_ids, boost: 100 } }
       end
     end
+
+    query     = { bool: { must: must_clauses, should: should_clauses } }
+    functions = [reputation_score_function, followers_score_function, time_distance_function]
+
+    records = AccountsIndex.query(function_score: { query: query, functions: functions, boost_mode: 'multiply', score_mode: 'avg' })
+                           .limit(limit_for_non_exact_results)
+                           .offset(offset)
+                           .objects
+                           .compact
+
+    ActiveRecord::Associations::Preloader.new.preload(records, :account_stat)
+
+    records
+  rescue Faraday::ConnectionFailed, Parslet::ParseFailed
+    nil
   end
 
-  def search_results
-    @_search_results ||= begin
-      if account
-        advanced_search_results
-      else
-        simple_search_results
-      end
-    end
+  def reputation_score_function
+    {
+      script_score: {
+        script: {
+          source: "(doc['followers_count'].value + 0.0) / (doc['followers_count'].value + doc['following_count'].value + 1)",
+        },
+      },
+    }
   end
 
-  def advanced_search_results
-    Account.advanced_search_for(terms_for_query, account, limit, options[:following], offset)
+  def followers_score_function
+    {
+      field_value_factor: {
+        field: 'followers_count',
+        modifier: 'log2p',
+        missing: 0,
+      },
+    }
   end
 
-  def simple_search_results
-    Account.search_for(terms_for_query, limit, offset)
+  def time_distance_function
+    {
+      gauss: {
+        last_status_at: {
+          scale: '30d',
+          offset: '30d',
+          decay: 0.3,
+        },
+      },
+    }
+  end
+
+  def following_ids
+    @following_ids ||= account.active_relationships.pluck(:target_account_id)
+  end
+
+  def limit_for_non_exact_results
+    if exact_match?
+      limit - 1
+    else
+      limit
+    end
   end
 
   def terms_for_query
     if domain_is_local?
       query_username
     else
-      "#{query_username} #{query_domain}"
+      query
     end
   end
+
+  def split_query_string
+    @split_query_string ||= query.split('@')
+  end
+
+  def query_username
+    @query_username ||= split_query_string.first || ''
+  end
+
+  def query_domain
+    @query_domain ||= query_without_split? ? nil : split_query_string.last
+  end
+
+  def query_without_split?
+    split_query_string.size == 1
+  end
+
+  def domain_is_local?
+    @domain_is_local ||= TagManager.instance.local_domain?(query_domain)
+  end
+
+  def exact_match?
+    exact_match.present?
+  end
+
+  def username_complete?
+    query.include?('@') && "@#{query}" =~ Account::MENTION_RE
+  end
+
+  def likely_acct?
+    @acct_hint || username_complete?
+  end
 end
diff --git a/app/services/activitypub/fetch_featured_collection_service.rb b/app/services/activitypub/fetch_featured_collection_service.rb
index 6a137b520bdc4906105dea7099aadb6061fae4d3..2c27704661c9448d57b0163244b7fb91652b02f4 100644
--- a/app/services/activitypub/fetch_featured_collection_service.rb
+++ b/app/services/activitypub/fetch_featured_collection_service.rb
@@ -4,13 +4,12 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService
   include JsonLdHelper
 
   def call(account)
-    return if account.featured_collection_url.blank?
+    return if account.featured_collection_url.blank? || account.suspended? || account.local?
 
     @account = account
     @json    = fetch_resource(@account.featured_collection_url, true)
 
     return unless supported_context?
-    return if @account.suspended? || @account.local?
 
     case @json['type']
     when 'Collection', 'CollectionPage'
diff --git a/app/services/activitypub/fetch_remote_account_service.rb b/app/services/activitypub/fetch_remote_account_service.rb
index 3c204494128ec10afe6da731ac1c196a37b699f5..d65c8f95117c65949eb920e2e0ed6a5f5b17643c 100644
--- a/app/services/activitypub/fetch_remote_account_service.rb
+++ b/app/services/activitypub/fetch_remote_account_service.rb
@@ -2,18 +2,22 @@
 
 class ActivityPub::FetchRemoteAccountService < BaseService
   include JsonLdHelper
+  include DomainControlHelper
 
   SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
 
   # Does a WebFinger roundtrip on each call, unless `only_key` is true
   def call(uri, id: true, prefetched_body: nil, break_on_redirect: false, only_key: false)
+    return if domain_not_allowed?(uri)
     return ActivityPub::TagManager.instance.uri_to_resource(uri, Account) if ActivityPub::TagManager.instance.local_uri?(uri)
 
-    @json = if prefetched_body.nil?
-              fetch_resource(uri, id)
-            else
-              body_to_json(prefetched_body, compare_id: id ? uri : nil)
-            end
+    @json = begin
+      if prefetched_body.nil?
+        fetch_resource(uri, id)
+      else
+        body_to_json(prefetched_body, compare_id: id ? uri : nil)
+      end
+    end
 
     return if !supported_context? || !expected_type? || (break_on_redirect && @json['movedTo'].present?)
 
diff --git a/app/services/activitypub/fetch_remote_poll_service.rb b/app/services/activitypub/fetch_remote_poll_service.rb
index 854a32d050a75e0d0e9f58e804556de873ab5526..1c79ecf1162f6e2d7b4c97ab0bbad9ad71e208d8 100644
--- a/app/services/activitypub/fetch_remote_poll_service.rb
+++ b/app/services/activitypub/fetch_remote_poll_service.rb
@@ -5,7 +5,9 @@ class ActivityPub::FetchRemotePollService < BaseService
 
   def call(poll, on_behalf_of = nil)
     json = fetch_resource(poll.status.uri, true, on_behalf_of)
+
     return unless supported_context?(json)
+
     ActivityPub::ProcessPollService.new.call(poll, json)
   end
 end
diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb
index 469821032ab69746c12ea79b868f1e4b1626fa9c..cf4f62899fb85dcf871d5978f1ea097d55275b00 100644
--- a/app/services/activitypub/fetch_remote_status_service.rb
+++ b/app/services/activitypub/fetch_remote_status_service.rb
@@ -5,18 +5,18 @@ class ActivityPub::FetchRemoteStatusService < BaseService
 
   # Should be called when uri has already been checked for locality
   def call(uri, id: true, prefetched_body: nil, on_behalf_of: nil)
-    @json = if prefetched_body.nil?
-              fetch_resource(uri, id, on_behalf_of)
-            else
-              body_to_json(prefetched_body, compare_id: id ? uri : nil)
-            end
+    @json = begin
+      if prefetched_body.nil?
+        fetch_resource(uri, id, on_behalf_of)
+      else
+        body_to_json(prefetched_body, compare_id: id ? uri : nil)
+      end
+    end
 
-    return unless supported_context? && expected_type?
-
-    return if actor_id.nil? || !trustworthy_attribution?(@json['id'], actor_id)
+    return if !(supported_context? && expected_type?) || actor_id.nil? || !trustworthy_attribution?(@json['id'], actor_id)
 
     actor = ActivityPub::TagManager.instance.uri_to_resource(actor_id, Account)
-    actor = ActivityPub::FetchRemoteAccountService.new.call(actor_id, id: true) if actor.nil? || needs_update(actor)
+    actor = ActivityPub::FetchRemoteAccountService.new.call(actor_id, id: true) if actor.nil? || needs_update?(actor)
 
     return if actor.nil? || actor.suspended?
 
@@ -46,7 +46,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService
     equals_or_includes_any?(@json['type'], ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES)
   end
 
-  def needs_update(actor)
+  def needs_update?(actor)
     actor.possibly_stale?
   end
 end
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index 3857e7c16d58991b3f6fc2f55adbe187e45f6ad1..9c649d35cec1675a659c73da5c996544f9d079f0 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -2,11 +2,12 @@
 
 class ActivityPub::ProcessAccountService < BaseService
   include JsonLdHelper
+  include DomainControlHelper
 
   # Should be called with confirmed valid JSON
   # and WebFinger-resolved username and domain
   def call(username, domain, json, options = {})
-    return if json['inbox'].blank? || unsupported_uri_scheme?(json['id'])
+    return if json['inbox'].blank? || unsupported_uri_scheme?(json['id']) || domain_not_allowed?(domain)
 
     @options     = options
     @json        = json
@@ -84,6 +85,7 @@ class ActivityPub::ProcessAccountService < BaseService
     @account.fields                  = property_values || {}
     @account.also_known_as           = as_array(@json['alsoKnownAs'] || []).map { |item| value_or_id(item) }
     @account.actor_type              = actor_type
+    @account.discoverable            = @json['discoverable'] || false
   end
 
   def set_fetchable_attributes!
diff --git a/app/services/activitypub/process_collection_service.rb b/app/services/activitypub/process_collection_service.rb
index 881df478bf53c7b265df09c1cf01917c4a9ae382..a2a2e7071545aae129896eac2102a9317cb23aaa 100644
--- a/app/services/activitypub/process_collection_service.rb
+++ b/app/services/activitypub/process_collection_service.rb
@@ -8,9 +8,7 @@ class ActivityPub::ProcessCollectionService < BaseService
     @json    = Oj.load(body, mode: :strict)
     @options = options
 
-    return unless supported_context?
-    return if different_actor? && verify_account!.nil?
-    return if @account.suspended? || @account.local?
+    return if !supported_context? || (different_actor? && verify_account!.nil?) || @account.suspended? || @account.local?
 
     case @json['type']
     when 'Collection', 'CollectionPage'
diff --git a/app/services/activitypub/process_poll_service.rb b/app/services/activitypub/process_poll_service.rb
index 61357abd3ca6aa24812159a743cc61385a7171a8..cb4a0d46025a82153142860d7c3b87edeb3353a8 100644
--- a/app/services/activitypub/process_poll_service.rb
+++ b/app/services/activitypub/process_poll_service.rb
@@ -5,6 +5,7 @@ class ActivityPub::ProcessPollService < BaseService
 
   def call(poll, json)
     @json = json
+
     return unless expected_type?
 
     previous_expires_at = poll.expires_at
@@ -27,6 +28,8 @@ class ActivityPub::ProcessPollService < BaseService
       end
     end
 
+    voters_count = @json['votersCount']
+
     latest_options = items.map { |item| item['name'].presence || item['content'] }
 
     # If for some reasons the options were changed, it invalidates all previous
@@ -38,7 +41,8 @@ class ActivityPub::ProcessPollService < BaseService
         last_fetched_at: Time.now.utc,
         expires_at: expires_at,
         options: latest_options,
-        cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 }
+        cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 },
+        voters_count: voters_count
       )
     rescue ActiveRecord::StaleObjectError
       poll.reload
diff --git a/app/services/app_sign_up_service.rb b/app/services/app_sign_up_service.rb
index 6dee9cd81568eb1b208183e70a90df3a66701954..c9739c77d194796e738057d8856fc4421f2c01af 100644
--- a/app/services/app_sign_up_service.rb
+++ b/app/services/app_sign_up_service.rb
@@ -4,9 +4,10 @@ class AppSignUpService < BaseService
   def call(app, params)
     return unless allowed_registrations?
 
-    user_params    = params.slice(:email, :password, :agreement, :locale)
-    account_params = params.slice(:username)
-    user           = User.create!(user_params.merge(created_by_application: app, password_confirmation: user_params[:password], account_attributes: account_params))
+    user_params           = params.slice(:email, :password, :agreement, :locale)
+    account_params        = params.slice(:username)
+    invite_request_params = { text: params[:reason] }
+    user                  = User.create!(user_params.merge(created_by_application: app, password_confirmation: user_params[:password], account_attributes: account_params, invite_request_attributes: invite_request_params))
 
     Doorkeeper::AccessToken.create!(application: app,
                                     resource_owner_id: user.id,
diff --git a/app/services/authorize_follow_service.rb b/app/services/authorize_follow_service.rb
index 29b8700c7c7d06781d00d8d11b695f8da7b779b0..49bef727e6a8c9d90df07728a86d536ca75e506d 100644
--- a/app/services/authorize_follow_service.rb
+++ b/app/services/authorize_follow_service.rb
@@ -11,25 +11,17 @@ class AuthorizeFollowService < BaseService
       follow_request.authorize!
     end
 
-    create_notification(follow_request) unless source_account.local?
+    create_notification(follow_request) if !source_account.local? && source_account.activitypub?
     follow_request
   end
 
   private
 
   def create_notification(follow_request)
-    if follow_request.account.ostatus?
-      NotificationWorker.perform_async(build_xml(follow_request), follow_request.target_account_id, follow_request.account_id)
-    elsif follow_request.account.activitypub?
-      ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), follow_request.target_account_id, follow_request.account.inbox_url)
-    end
+    ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), follow_request.target_account_id, follow_request.account.inbox_url)
   end
 
   def build_json(follow_request)
     Oj.dump(serialize_payload(follow_request, ActivityPub::AcceptFollowSerializer))
   end
-
-  def build_xml(follow_request)
-    OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request))
-  end
 end
diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb
index e328b17391b94008a2027295cf5cc7692fe97d25..3638134be76447612ded2a6fcc9e6766aef8ee58 100644
--- a/app/services/batched_remove_status_service.rb
+++ b/app/services/batched_remove_status_service.rb
@@ -1,7 +1,6 @@
 # frozen_string_literal: true
 
 class BatchedRemoveStatusService < BaseService
-  include StreamEntryRenderer
   include Redisable
 
   # Delete given statuses and reblogs of them
@@ -9,19 +8,16 @@ class BatchedRemoveStatusService < BaseService
   # Dispatch Salmon deletes, unique per domain, of the deleted statuses, but only local ones
   # Remove statuses from home feeds
   # Push delete events to streaming API for home feeds and public feeds
-  # @param [Status] statuses A preferably batched array of statuses
+  # @param [Enumerable<Status>] statuses A preferably batched array of statuses
   # @param [Hash] options
   # @option [Boolean] :skip_side_effects
   def call(statuses, **options)
-    statuses = Status.where(id: statuses.map(&:id)).includes(:account, :stream_entry).flat_map { |status| [status] + status.reblogs.includes(:account, :stream_entry).to_a }
+    statuses = Status.where(id: statuses.map(&:id)).includes(:account).flat_map { |status| [status] + status.reblogs.includes(:account).to_a }
 
     @mentions = statuses.each_with_object({}) { |s, h| h[s.id] = s.active_mentions.includes(:account).to_a }
     @tags     = statuses.each_with_object({}) { |s, h| h[s.id] = s.tags.pluck(:name) }
 
-    @stream_entry_batches  = []
-    @salmon_batches        = []
-    @json_payloads         = statuses.each_with_object({}) { |s, h| h[s.id] = Oj.dump(event: :delete, payload: s.id.to_s) }
-    @activity_xml          = {}
+    @json_payloads = statuses.each_with_object({}) { |s, h| h[s.id] = Oj.dump(event: :delete, payload: s.id.to_s) }
 
     # Ensure that rendered XML reflects destroyed state
     statuses.each do |status|
@@ -39,28 +35,16 @@ class BatchedRemoveStatusService < BaseService
 
       unpush_from_home_timelines(account, account_statuses)
       unpush_from_list_timelines(account, account_statuses)
-
-      batch_stream_entries(account, account_statuses) if account.local?
     end
 
     # Cannot be batched
     statuses.each do |status|
       unpush_from_public_timelines(status)
-      batch_salmon_slaps(status) if status.local?
     end
-
-    Pubsubhubbub::RawDistributionWorker.push_bulk(@stream_entry_batches) { |batch| batch }
-    NotificationWorker.push_bulk(@salmon_batches) { |batch| batch }
   end
 
   private
 
-  def batch_stream_entries(account, statuses)
-    statuses.each do |status|
-      @stream_entry_batches << [build_xml(status.stream_entry), account.id]
-    end
-  end
-
   def unpush_from_home_timelines(account, statuses)
     recipients = account.followers_for_local_distribution.to_a
 
@@ -96,25 +80,9 @@ class BatchedRemoveStatusService < BaseService
       end
 
       @tags[status.id].each do |hashtag|
-        redis.publish("timeline:hashtag:#{hashtag}", payload)
-        redis.publish("timeline:hashtag:#{hashtag}:local", payload) if status.local?
+        redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}", payload)
+        redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}:local", payload) if status.local?
       end
     end
   end
-
-  def batch_salmon_slaps(status)
-    return if @mentions[status.id].empty?
-
-    recipients = @mentions[status.id].map(&:account).reject(&:local?).select(&:ostatus?).uniq(&:domain).map(&:id)
-
-    recipients.each do |recipient_id|
-      @salmon_batches << [build_xml(status.stream_entry), status.account_id, recipient_id]
-    end
-  end
-
-  def build_xml(stream_entry)
-    return @activity_xml[stream_entry.id] if @activity_xml.key?(stream_entry.id)
-
-    @activity_xml[stream_entry.id] = stream_entry_to_xml(stream_entry)
-  end
 end
diff --git a/app/services/block_domain_service.rb b/app/services/block_domain_service.rb
index c6eef04d420fe75a7ee8d4e79e965a16ecf719f1..ae461abf248e89e5502ba5e5fc21d057f9223442 100644
--- a/app/services/block_domain_service.rb
+++ b/app/services/block_domain_service.rb
@@ -3,13 +3,22 @@
 class BlockDomainService < BaseService
   attr_reader :domain_block
 
-  def call(domain_block)
+  def call(domain_block, update = false)
     @domain_block = domain_block
     process_domain_block!
+    process_retroactive_updates! if update
   end
 
   private
 
+  def process_retroactive_updates!
+    # If the domain block severity has been changed, undo the appropriate limitations
+    scope = Account.by_domain_and_subdomains(domain_block.domain)
+
+    scope.where(silenced_at: domain_block.created_at).in_batches.update_all(silenced_at: nil) unless domain_block.silence?
+    scope.where(suspended_at: domain_block.created_at).in_batches.update_all(suspended_at: nil) unless domain_block.suspend?
+  end
+
   def process_domain_block!
     clear_media! if domain_block.reject_media?
 
@@ -44,8 +53,7 @@ class BlockDomainService < BaseService
 
   def suspend_accounts!
     blocked_domain_accounts.without_suspended.reorder(nil).find_each do |account|
-      UnsubscribeService.new.call(account) if account.subscribed?
-      SuspendAccountService.new.call(account, suspended_at: @domain_block.created_at)
+      SuspendAccountService.new.call(account, reserve_username: true, suspended_at: @domain_block.created_at)
     end
   end
 
diff --git a/app/services/block_service.rb b/app/services/block_service.rb
index 0d9a6eccda47e47ed190285c467b79e2964d7e36..266a0f4b9d0384b107800f61c2e1c37bf91afc4e 100644
--- a/app/services/block_service.rb
+++ b/app/services/block_service.rb
@@ -13,25 +13,17 @@ class BlockService < BaseService
     block = account.block!(target_account)
 
     BlockWorker.perform_async(account.id, target_account.id)
-    create_notification(block) unless target_account.local?
+    create_notification(block) if !target_account.local? && target_account.activitypub?
     block
   end
 
   private
 
   def create_notification(block)
-    if block.target_account.ostatus?
-      NotificationWorker.perform_async(build_xml(block), block.account_id, block.target_account_id)
-    elsif block.target_account.activitypub?
-      ActivityPub::DeliveryWorker.perform_async(build_json(block), block.account_id, block.target_account.inbox_url)
-    end
+    ActivityPub::DeliveryWorker.perform_async(build_json(block), block.account_id, block.target_account.inbox_url)
   end
 
   def build_json(block)
     Oj.dump(serialize_payload(block, ActivityPub::BlockSerializer))
   end
-
-  def build_xml(block)
-    OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.block_salmon(block))
-  end
 end
diff --git a/app/services/bootstrap_timeline_service.rb b/app/services/bootstrap_timeline_service.rb
index db2c83e5d1ced84ab75a70fdd61cd257ec498bb8..c489601c155b72ce40d07fdb6aad1ff544d61945 100644
--- a/app/services/bootstrap_timeline_service.rb
+++ b/app/services/bootstrap_timeline_service.rb
@@ -17,7 +17,11 @@ class BootstrapTimelineService < BaseService
 
   def autofollow_bootstrap_timeline_accounts!
     bootstrap_timeline_accounts.each do |target_account|
-      FollowService.new.call(@source_account, target_account)
+      begin
+        FollowService.new.call(@source_account, target_account)
+      rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
+        nil
+      end
     end
   end
 
@@ -40,7 +44,9 @@ class BootstrapTimelineService < BaseService
 
   def local_unlocked_accounts(usernames)
     Account.local
+           .without_suspended
            .where(username: usernames)
            .where(locked: false)
+           .where(moved_to_account_id: nil)
   end
 end
diff --git a/app/services/concerns/author_extractor.rb b/app/services/concerns/author_extractor.rb
deleted file mode 100644
index c2419e9ecb23d6520897848a6bcd4396d87246de..0000000000000000000000000000000000000000
--- a/app/services/concerns/author_extractor.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-module AuthorExtractor
-  def author_from_xml(xml, update_profile = true)
-    return nil if xml.nil?
-
-    # Try <email> for acct
-    acct = xml.at_xpath('./xmlns:author/xmlns:email', xmlns: OStatus::TagManager::XMLNS)&.content
-
-    # Try <name> + <uri>
-    if acct.blank?
-      username = xml.at_xpath('./xmlns:author/xmlns:name', xmlns: OStatus::TagManager::XMLNS)&.content
-      uri      = xml.at_xpath('./xmlns:author/xmlns:uri', xmlns: OStatus::TagManager::XMLNS)&.content
-
-      return nil if username.blank? || uri.blank?
-
-      domain = Addressable::URI.parse(uri).normalized_host
-      acct   = "#{username}@#{domain}"
-    end
-
-    ResolveAccountService.new.call(acct, update_profile: update_profile)
-  end
-end
diff --git a/app/services/concerns/payloadable.rb b/app/services/concerns/payloadable.rb
index 13d9c3548353e538be69e52a9abaf69b3da7283a..7f9f21c4b95f49eb82ca4cf4e5aa9b14ca0ed556 100644
--- a/app/services/concerns/payloadable.rb
+++ b/app/services/concerns/payloadable.rb
@@ -14,6 +14,6 @@ module Payloadable
   end
 
   def signing_enabled?
-    true
+    ENV['AUTHORIZED_FETCH'] != 'true' && !Rails.configuration.x.whitelist_mode
   end
 end
diff --git a/app/services/concerns/stream_entry_renderer.rb b/app/services/concerns/stream_entry_renderer.rb
deleted file mode 100644
index 9f6c8a082aed307f16306251276be56417f84fe6..0000000000000000000000000000000000000000
--- a/app/services/concerns/stream_entry_renderer.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-module StreamEntryRenderer
-  def stream_entry_to_xml(stream_entry)
-    OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.entry(stream_entry, true))
-  end
-end
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index f3e9c855dc55441652a6e9cdf2fa3f3773c78b72..95336ef45023586f33640e1ccc763b29f0e265bd 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -72,8 +72,8 @@ class FanOutOnWriteService < BaseService
     Rails.logger.debug "Delivering status #{status.id} to hashtags"
 
     status.tags.pluck(:name).each do |hashtag|
-      Redis.current.publish("timeline:hashtag:#{hashtag}", @payload)
-      Redis.current.publish("timeline:hashtag:#{hashtag}:local", @payload) if status.local?
+      Redis.current.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}", @payload)
+      Redis.current.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}:local", @payload) if status.local?
     end
   end
 
diff --git a/app/services/favourite_service.rb b/app/services/favourite_service.rb
index 128a24ad61e59f484ebda27e432d4f7ee32d96e3..02b26458a6fa09e7934354c22443ec1768d55d1b 100644
--- a/app/services/favourite_service.rb
+++ b/app/services/favourite_service.rb
@@ -30,8 +30,6 @@ class FavouriteService < BaseService
 
     if status.account.local?
       NotifyService.new.call(status.account, favourite)
-    elsif status.account.ostatus?
-      NotificationWorker.perform_async(build_xml(favourite), favourite.account_id, status.account_id)
     elsif status.account.activitypub?
       ActivityPub::DeliveryWorker.perform_async(build_json(favourite), favourite.account_id, status.account.inbox_url)
     end
@@ -46,8 +44,4 @@ class FavouriteService < BaseService
   def build_json(favourite)
     Oj.dump(serialize_payload(favourite, ActivityPub::LikeSerializer))
   end
-
-  def build_xml(favourite)
-    OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.favourite_salmon(favourite))
-  end
 end
diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_atom_service.rb
deleted file mode 100644
index d6508a9888a69fad1b695a3cd59a4cb971120cac..0000000000000000000000000000000000000000
--- a/app/services/fetch_atom_service.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-# frozen_string_literal: true
-
-class FetchAtomService < BaseService
-  include JsonLdHelper
-
-  def call(url)
-    return if url.blank?
-
-    result = process(url)
-
-    # retry without ActivityPub
-    result ||= process(url) if @unsupported_activity
-
-    result
-  rescue OpenSSL::SSL::SSLError => e
-    Rails.logger.debug "SSL error: #{e}"
-    nil
-  rescue HTTP::ConnectionError => e
-    Rails.logger.debug "HTTP ConnectionError: #{e}"
-    nil
-  end
-
-  private
-
-  def process(url, terminal = false)
-    @url = url
-    perform_request { |response| process_response(response, terminal) }
-  end
-
-  def perform_request(&block)
-    accept = 'text/html'
-    accept = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/atom+xml, ' + accept unless @unsupported_activity
-
-    Request.new(:get, @url).add_headers('Accept' => accept).perform(&block)
-  end
-
-  def process_response(response, terminal = false)
-    return nil if response.code != 200
-
-    if response.mime_type == 'application/atom+xml'
-      [@url, { prefetched_body: response.body_with_limit }, :ostatus]
-    elsif ['application/activity+json', 'application/ld+json'].include?(response.mime_type)
-      body = response.body_with_limit
-      json = body_to_json(body)
-      if supported_context?(json) && equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) && json['inbox'].present?
-        [json['id'], { prefetched_body: body, id: true }, :activitypub]
-      elsif supported_context?(json) && expected_type?(json)
-        [json['id'], { prefetched_body: body, id: true }, :activitypub]
-      else
-        @unsupported_activity = true
-        nil
-      end
-    elsif !terminal
-      link_header = response['Link'] && parse_link_header(response)
-
-      if link_header&.find_link(%w(rel alternate))
-        process_link_headers(link_header)
-      elsif response.mime_type == 'text/html'
-        process_html(response)
-      end
-    end
-  end
-
-  def expected_type?(json)
-    equals_or_includes_any?(json['type'], ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES)
-  end
-
-  def process_html(response)
-    page = Nokogiri::HTML(response.body_with_limit)
-
-    json_link = page.xpath('//link[@rel="alternate"]').find { |link| ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(link['type']) }
-    atom_link = page.xpath('//link[@rel="alternate"]').find { |link| link['type'] == 'application/atom+xml' }
-
-    result ||= process(json_link['href'], terminal: true) unless json_link.nil? || @unsupported_activity
-    result ||= process(atom_link['href'], terminal: true) unless atom_link.nil?
-
-    result
-  end
-
-  def process_link_headers(link_header)
-    json_link = link_header.find_link(%w(rel alternate), %w(type application/activity+json)) || link_header.find_link(%w(rel alternate), ['type', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'])
-    atom_link = link_header.find_link(%w(rel alternate), %w(type application/atom+xml))
-
-    result ||= process(json_link.href, terminal: true) unless json_link.nil? || @unsupported_activity
-    result ||= process(atom_link.href, terminal: true) unless atom_link.nil?
-
-    result
-  end
-
-  def parse_link_header(response)
-    LinkHeader.parse(response['Link'].is_a?(Array) ? response['Link'].first : response['Link'])
-  end
-end
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index 494aaed759c3899961a15f1a06fb3e545df37675..f0b1169db8516ebf17a21b4df2e3e764a1bab4f2 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -22,14 +22,14 @@ class FetchLinkCardService < BaseService
     RedisLock.acquire(lock_options) do |lock|
       if lock.acquired?
         @card = PreviewCard.find_by(url: @url)
-        process_url if @card.nil? || @card.updated_at <= 2.weeks.ago
+        process_url if @card.nil? || @card.updated_at <= 2.weeks.ago || @card.missing_image?
       else
         raise Mastodon::RaceConditionError
       end
     end
 
     attach_card if @card&.persisted?
-  rescue HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
+  rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
     Rails.logger.debug "Error fetching link #{@url}: #{e}"
     nil
   end
@@ -39,12 +39,6 @@ class FetchLinkCardService < BaseService
   def process_url
     @card ||= PreviewCard.new(url: @url)
 
-    failed = Request.new(:head, @url).perform do |res|
-      res.code != 405 && res.code != 501 && (res.code != 200 || res.mime_type != 'text/html')
-    end
-
-    return if failed
-
     Request.new(:get, @url).perform do |res|
       if res.code == 200 && res.mime_type == 'text/html'
         @html = res.body_with_limit
@@ -84,7 +78,7 @@ class FetchLinkCardService < BaseService
 
   def mention_link?(a)
     @status.mentions.any? do |mention|
-      a['href'] == TagManager.instance.url_for(mention.account)
+      a['href'] == ActivityPub::TagManager.instance.url_for(mention.account)
     end
   end
 
diff --git a/app/services/fetch_remote_account_service.rb b/app/services/fetch_remote_account_service.rb
index cfc560022fe6d6f7aae8fb122c798aab0bb4dde3..3cd06e30f6ea75d9884b16e786143105221dd967 100644
--- a/app/services/fetch_remote_account_service.rb
+++ b/app/services/fetch_remote_account_service.rb
@@ -1,45 +1,17 @@
 # frozen_string_literal: true
 
 class FetchRemoteAccountService < BaseService
-  include AuthorExtractor
-
   def call(url, prefetched_body = nil, protocol = :ostatus)
     if prefetched_body.nil?
-      resource_url, resource_options, protocol = FetchAtomService.new.call(url)
+      resource_url, resource_options, protocol = FetchResourceService.new.call(url)
     else
       resource_url     = url
       resource_options = { prefetched_body: prefetched_body }
     end
 
     case protocol
-    when :ostatus
-      process_atom(resource_url, **resource_options)
     when :activitypub
       ActivityPub::FetchRemoteAccountService.new.call(resource_url, **resource_options)
     end
   end
-
-  private
-
-  def process_atom(url, prefetched_body:)
-    xml = Nokogiri::XML(prefetched_body)
-    xml.encoding = 'utf-8'
-
-    account = author_from_xml(xml.at_xpath('/xmlns:feed', xmlns: OStatus::TagManager::XMLNS), false)
-
-    UpdateRemoteProfileService.new.call(xml, account) if account.present? && trusted_domain?(url, account)
-
-    account
-  rescue TypeError
-    Rails.logger.debug "Unparseable URL given: #{url}"
-    nil
-  rescue Nokogiri::XML::XPath::SyntaxError
-    Rails.logger.debug 'Invalid XML or missing namespace'
-    nil
-  end
-
-  def trusted_domain?(url, account)
-    domain = Addressable::URI.parse(url).normalized_host
-    domain.casecmp(account.domain).zero? || domain.casecmp(Addressable::URI.parse(account.remote_url.presence || account.uri).normalized_host).zero?
-  end
 end
diff --git a/app/services/fetch_remote_status_service.rb b/app/services/fetch_remote_status_service.rb
index 9c3008035d5431e07d1ef0f8c86fdcec0e378e96..208dc7809c1082d5f8c9b6dc438f2f6eede3952e 100644
--- a/app/services/fetch_remote_status_service.rb
+++ b/app/services/fetch_remote_status_service.rb
@@ -1,45 +1,17 @@
 # frozen_string_literal: true
 
 class FetchRemoteStatusService < BaseService
-  include AuthorExtractor
-
   def call(url, prefetched_body = nil, protocol = :ostatus)
     if prefetched_body.nil?
-      resource_url, resource_options, protocol = FetchAtomService.new.call(url)
+      resource_url, resource_options, protocol = FetchResourceService.new.call(url)
     else
       resource_url     = url
       resource_options = { prefetched_body: prefetched_body }
     end
 
     case protocol
-    when :ostatus
-      process_atom(resource_url, **resource_options)
     when :activitypub
       ActivityPub::FetchRemoteStatusService.new.call(resource_url, **resource_options)
     end
   end
-
-  private
-
-  def process_atom(url, prefetched_body:)
-    Rails.logger.debug "Processing Atom for remote status at #{url}"
-
-    xml = Nokogiri::XML(prefetched_body)
-    xml.encoding = 'utf-8'
-
-    account = author_from_xml(xml.at_xpath('/xmlns:entry', xmlns: OStatus::TagManager::XMLNS))
-    domain  = Addressable::URI.parse(url).normalized_host
-
-    return nil unless !account.nil? && confirmed_domain?(domain, account)
-
-    statuses = ProcessFeedService.new.call(prefetched_body, account)
-    statuses.first
-  rescue Nokogiri::XML::XPath::SyntaxError
-    Rails.logger.debug 'Invalid XML or missing namespace'
-    nil
-  end
-
-  def confirmed_domain?(domain, account)
-    account.domain.nil? || domain.casecmp(account.domain).zero? || domain.casecmp(Addressable::URI.parse(account.remote_url.presence || account.uri).normalized_host).zero?
-  end
 end
diff --git a/app/services/fetch_resource_service.rb b/app/services/fetch_resource_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3676d899d254bc25d692fa392169c3b00fab0307
--- /dev/null
+++ b/app/services/fetch_resource_service.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+class FetchResourceService < BaseService
+  include JsonLdHelper
+
+  ACCEPT_HEADER = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams", text/html'
+
+  def call(url)
+    return if url.blank?
+
+    process(url)
+  rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
+    Rails.logger.debug "Error fetching resource #{@url}: #{e}"
+    nil
+  end
+
+  private
+
+  def process(url, terminal = false)
+    @url = url
+
+    perform_request { |response| process_response(response, terminal) }
+  end
+
+  def perform_request(&block)
+    Request.new(:get, @url).add_headers('Accept' => ACCEPT_HEADER).on_behalf_of(Account.representative).perform(&block)
+  end
+
+  def process_response(response, terminal = false)
+    return nil if response.code != 200
+
+    if ['application/activity+json', 'application/ld+json'].include?(response.mime_type)
+      body = response.body_with_limit
+      json = body_to_json(body)
+
+      [json['id'], { prefetched_body: body, id: true }, :activitypub] if supported_context?(json) && (equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) || expected_type?(json))
+    elsif !terminal
+      link_header = response['Link'] && parse_link_header(response)
+
+      if link_header&.find_link(%w(rel alternate))
+        process_link_headers(link_header)
+      elsif response.mime_type == 'text/html'
+        process_html(response)
+      end
+    end
+  end
+
+  def expected_type?(json)
+    equals_or_includes_any?(json['type'], ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES)
+  end
+
+  def process_html(response)
+    page      = Nokogiri::HTML(response.body_with_limit)
+    json_link = page.xpath('//link[@rel="alternate"]').find { |link| ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(link['type']) }
+
+    process(json_link['href'], terminal: true) unless json_link.nil?
+  end
+
+  def process_link_headers(link_header)
+    json_link = link_header.find_link(%w(rel alternate), %w(type application/activity+json)) || link_header.find_link(%w(rel alternate), ['type', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'])
+
+    process(json_link.href, terminal: true) unless json_link.nil?
+  end
+
+  def parse_link_header(response)
+    LinkHeader.parse(response['Link'].is_a?(Array) ? response['Link'].first : response['Link'])
+  end
+end
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
index 17fbb9cb4fa3dc68af2e9993291d378aa676e4de..1941c2e2d8864a352e2de04239391ea2ee5bdee2 100644
--- a/app/services/follow_service.rb
+++ b/app/services/follow_service.rb
@@ -13,7 +13,7 @@ class FollowService < BaseService
     target_account = ResolveAccountService.new.call(target_account, skip_webfinger: true)
 
     raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended?
-    raise Mastodon::NotPermittedError  if target_account.blocking?(source_account) || source_account.blocking?(target_account) || target_account.moved? || source_account.domain_blocking?(target_account.domain)
+    raise Mastodon::NotPermittedError  if target_account.blocking?(source_account) || source_account.blocking?(target_account) || target_account.moved? || (!target_account.local? && target_account.ostatus?) || source_account.domain_blocking?(target_account.domain)
 
     if source_account.following?(target_account)
       # We're already following this account, but we'll call follow! again to
@@ -30,9 +30,9 @@ class FollowService < BaseService
 
     ActivityTracker.increment('activity:interactions')
 
-    if target_account.locked? || target_account.activitypub?
+    if target_account.locked? || source_account.silenced? || target_account.activitypub?
       request_follow(source_account, target_account, reblogs: reblogs)
-    else
+    elsif target_account.local?
       direct_follow(source_account, target_account, reblogs: reblogs)
     end
   end
@@ -44,9 +44,6 @@ class FollowService < BaseService
 
     if target_account.local?
       LocalNotificationWorker.perform_async(target_account.id, follow_request.id, follow_request.class.name)
-    elsif target_account.ostatus?
-      NotificationWorker.perform_async(build_follow_request_xml(follow_request), source_account.id, target_account.id)
-      AfterRemoteFollowRequestWorker.perform_async(follow_request.id)
     elsif target_account.activitypub?
       ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), source_account.id, target_account.inbox_url)
     end
@@ -57,27 +54,12 @@ class FollowService < BaseService
   def direct_follow(source_account, target_account, reblogs: true)
     follow = source_account.follow!(target_account, reblogs: reblogs)
 
-    if target_account.local?
-      LocalNotificationWorker.perform_async(target_account.id, follow.id, follow.class.name)
-    else
-      Pubsubhubbub::SubscribeWorker.perform_async(target_account.id) unless target_account.subscribed?
-      NotificationWorker.perform_async(build_follow_xml(follow), source_account.id, target_account.id)
-      AfterRemoteFollowWorker.perform_async(follow.id)
-    end
-
+    LocalNotificationWorker.perform_async(target_account.id, follow.id, follow.class.name)
     MergeWorker.perform_async(target_account.id, source_account.id)
 
     follow
   end
 
-  def build_follow_request_xml(follow_request)
-    OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.follow_request_salmon(follow_request))
-  end
-
-  def build_follow_xml(follow)
-    OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.follow_salmon(follow))
-  end
-
   def build_json(follow_request)
     Oj.dump(serialize_payload(follow_request, ActivityPub::FollowSerializer))
   end
diff --git a/app/services/hashtag_query_service.rb b/app/services/hashtag_query_service.rb
index 5773d78c6e2d2a38275558dce58b59878d59b001..196de0639205b20596ea5968210234ab390c5b08 100644
--- a/app/services/hashtag_query_service.rb
+++ b/app/services/hashtag_query_service.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class HashtagQueryService < BaseService
+  LIMIT_PER_MODE = 4
+
   def call(tag, params, account = nil, local = false)
     tags = tags_for(Array(tag.name) | Array(params[:any])).pluck(:id)
     all  = tags_for(params[:all])
@@ -14,7 +16,7 @@ class HashtagQueryService < BaseService
 
   private
 
-  def tags_for(tags)
-    Tag.where(name: tags.map(&:downcase)) if tags.presence
+  def tags_for(names)
+    Tag.matching_name(Array(names).take(LIMIT_PER_MODE)) if names.present?
   end
 end
diff --git a/app/services/move_service.rb b/app/services/move_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..da0c62c4eb24eb61595bd9a3fe5b6be8efee72d6
--- /dev/null
+++ b/app/services/move_service.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class MoveService < BaseService
+  def call(migration)
+    @migration      = migration
+    @source_account = migration.account
+    @target_account = migration.target_account
+
+    update_redirect!
+    process_local_relationships!
+    distribute_update!
+    distribute_move!
+  end
+
+  private
+
+  def update_redirect!
+    @source_account.update!(moved_to_account: @target_account)
+  end
+
+  def process_local_relationships!
+    MoveWorker.perform_async(@source_account.id, @target_account.id)
+  end
+
+  def distribute_update!
+    ActivityPub::UpdateDistributionWorker.perform_async(@source_account.id)
+  end
+
+  def distribute_move!
+    ActivityPub::MoveDistributionWorker.perform_async(@migration.id)
+  end
+end
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 36b9c9e84b2b3e2e4b4aa9153b00a9ec363f1bb8..b59982d84531c2369e6e94ec79933c0abb824176 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -95,7 +95,6 @@ class PostStatusService < BaseService
     LinkCrawlWorker.perform_async(@status.id) unless @status.spoiler_text?
     DistributionWorker.perform_async(@status.id)
     unless @status.local_only?
-      Pubsubhubbub::DistributionWorker.perform_async(@status.stream_entry.id)
       ActivityPub::DistributionWorker.perform_async(@status.id)
       PollExpirationNotifyWorker.perform_at(@status.poll.expires_at, @status.poll.id) if @status.poll
     end
@@ -184,7 +183,7 @@ class PostStatusService < BaseService
   def poll_attributes
     return if @options[:poll].blank?
 
-    @options[:poll].merge(account: @account)
+    @options[:poll].merge(account: @account, voters_count: 0)
   end
 
   def scheduled_options
diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb
deleted file mode 100644
index 30a9dd85ebcb176abc9b7bd2df31603f1546556a..0000000000000000000000000000000000000000
--- a/app/services/process_feed_service.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-class ProcessFeedService < BaseService
-  def call(body, account, **options)
-    @options = options
-
-    xml = Nokogiri::XML(body)
-    xml.encoding = 'utf-8'
-
-    update_author(body, account)
-    process_entries(xml, account)
-  end
-
-  private
-
-  def update_author(body, account)
-    RemoteProfileUpdateWorker.perform_async(account.id, body.force_encoding('UTF-8'), true)
-  end
-
-  def process_entries(xml, account)
-    xml.xpath('//xmlns:entry', xmlns: OStatus::TagManager::XMLNS).reverse_each.map { |entry| process_entry(entry, account) }.compact
-  end
-
-  def process_entry(xml, account)
-    activity = OStatus::Activity::General.new(xml, account, @options)
-    activity.specialize&.perform if activity.status?
-  rescue ActiveRecord::RecordInvalid => e
-    Rails.logger.debug "Nothing was saved for #{activity.id} because: #{e}"
-    nil
-  end
-end
diff --git a/app/services/process_hashtags_service.rb b/app/services/process_hashtags_service.rb
index d5ec076a8b24f4cdb34885fc82961cc11fdb5a29..e8e139b058b77a3ea25e7a7b29bc0896a888c841 100644
--- a/app/services/process_hashtags_service.rb
+++ b/app/services/process_hashtags_service.rb
@@ -5,16 +5,14 @@ class ProcessHashtagsService < BaseService
     tags    = Extractor.extract_hashtags(status.text) if status.local?
     records = []
 
-    tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |name|
-      tag = Tag.where(name: name).first_or_create(name: name)
-
+    Tag.find_or_create_by_names(tags) do |tag|
       status.tags << tag
       records << tag
 
       TrendingTags.record_use!(tag, status.account, status.created_at) if status.public_visibility?
     end
 
-    return unless status.public_visibility? || status.unlisted_visibility?
+    return unless status.distributable?
 
     status.account.featured_tags.where(tag_id: records.map(&:id)).each do |featured_tag|
       featured_tag.increment(status.created_at)
diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb
deleted file mode 100644
index 1fca3832b74fc65251111d3d5b716aabf226204e..0000000000000000000000000000000000000000
--- a/app/services/process_interaction_service.rb
+++ /dev/null
@@ -1,151 +0,0 @@
-# frozen_string_literal: true
-
-class ProcessInteractionService < BaseService
-  include AuthorExtractor
-  include Authorization
-
-  # Record locally the remote interaction with our user
-  # @param [String] envelope Salmon envelope
-  # @param [Account] target_account Account the Salmon was addressed to
-  def call(envelope, target_account)
-    body = salmon.unpack(envelope)
-
-    xml = Nokogiri::XML(body)
-    xml.encoding = 'utf-8'
-
-    account = author_from_xml(xml.at_xpath('/xmlns:entry', xmlns: OStatus::TagManager::XMLNS))
-
-    return if account.nil? || account.suspended?
-
-    if salmon.verify(envelope, account.keypair)
-      RemoteProfileUpdateWorker.perform_async(account.id, body.force_encoding('UTF-8'), true)
-
-      case verb(xml)
-      when :follow
-        follow!(account, target_account) unless target_account.locked? || target_account.blocking?(account) || target_account.domain_blocking?(account.domain)
-      when :request_friend
-        follow_request!(account, target_account) unless !target_account.locked? || target_account.blocking?(account) || target_account.domain_blocking?(account.domain)
-      when :authorize
-        authorize_follow_request!(account, target_account)
-      when :reject
-        reject_follow_request!(account, target_account)
-      when :unfollow
-        unfollow!(account, target_account)
-      when :favorite
-        favourite!(xml, account)
-      when :unfavorite
-        unfavourite!(xml, account)
-      when :post
-        add_post!(body, account) if mentions_account?(xml, target_account)
-      when :share
-        add_post!(body, account) unless status(xml).nil?
-      when :delete
-        delete_post!(xml, account)
-      when :block
-        reflect_block!(account, target_account)
-      when :unblock
-        reflect_unblock!(account, target_account)
-      end
-    end
-  rescue HTTP::Error, OStatus2::BadSalmonError, Mastodon::NotPermittedError
-    nil
-  end
-
-  private
-
-  def mentions_account?(xml, account)
-    xml.xpath('/xmlns:entry/xmlns:link[@rel="mentioned"]', xmlns: OStatus::TagManager::XMLNS).each { |mention_link| return true if [OStatus::TagManager.instance.uri_for(account), OStatus::TagManager.instance.url_for(account)].include?(mention_link.attribute('href').value) }
-    false
-  end
-
-  def verb(xml)
-    raw = xml.at_xpath('//activity:verb', activity: OStatus::TagManager::AS_XMLNS).content
-    OStatus::TagManager::VERBS.key(raw)
-  rescue
-    :post
-  end
-
-  def follow!(account, target_account)
-    follow = account.follow!(target_account)
-    FollowRequest.find_by(account: account, target_account: target_account)&.destroy
-    NotifyService.new.call(target_account, follow)
-  end
-
-  def follow_request!(account, target_account)
-    return if account.requested?(target_account)
-
-    follow_request = FollowRequest.create!(account: account, target_account: target_account)
-    NotifyService.new.call(target_account, follow_request)
-  end
-
-  def authorize_follow_request!(account, target_account)
-    follow_request = FollowRequest.find_by(account: target_account, target_account: account)
-    follow_request&.authorize!
-    Pubsubhubbub::SubscribeWorker.perform_async(account.id) unless account.subscribed?
-  end
-
-  def reject_follow_request!(account, target_account)
-    follow_request = FollowRequest.find_by(account: target_account, target_account: account)
-    follow_request&.reject!
-  end
-
-  def unfollow!(account, target_account)
-    account.unfollow!(target_account)
-    FollowRequest.find_by(account: account, target_account: target_account)&.destroy
-  end
-
-  def reflect_block!(account, target_account)
-    UnfollowService.new.call(target_account, account) if target_account.following?(account)
-    account.block!(target_account)
-  end
-
-  def reflect_unblock!(account, target_account)
-    UnblockService.new.call(account, target_account)
-  end
-
-  def delete_post!(xml, account)
-    status = Status.find(xml.at_xpath('//xmlns:id', xmlns: OStatus::TagManager::XMLNS).content)
-
-    return if status.nil?
-
-    authorize_with account, status, :destroy?
-
-    RemovalWorker.perform_async(status.id)
-  end
-
-  def favourite!(xml, from_account)
-    current_status = status(xml)
-
-    return if current_status.nil?
-
-    favourite = current_status.favourites.where(account: from_account).first_or_create!(account: from_account)
-    NotifyService.new.call(current_status.account, favourite)
-  end
-
-  def unfavourite!(xml, from_account)
-    current_status = status(xml)
-
-    return if current_status.nil?
-
-    favourite = current_status.favourites.where(account: from_account).first
-    favourite&.destroy
-  end
-
-  def add_post!(body, account)
-    ProcessingWorker.perform_async(account.id, body.force_encoding('UTF-8'))
-  end
-
-  def status(xml)
-    uri = activity_id(xml)
-    return nil unless OStatus::TagManager.instance.local_id?(uri)
-    Status.find(OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Status'))
-  end
-
-  def activity_id(xml)
-    xml.at_xpath('//activity:object', activity: OStatus::TagManager::AS_XMLNS).at_xpath('./xmlns:id', xmlns: OStatus::TagManager::XMLNS).content
-  end
-
-  def salmon
-    @salmon ||= OStatus2::Salmon.new
-  end
-end
diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb
index 1804e0c93ff53fae4bd8812948f1a405b2fa0a83..19de377174a724897466c95a1d7720c95d6d1900 100644
--- a/app/services/process_mentions_service.rb
+++ b/app/services/process_mentions_service.rb
@@ -1,7 +1,6 @@
 # frozen_string_literal: true
 
 class ProcessMentionsService < BaseService
-  include StreamEntryRenderer
   include Payloadable
 
   # Scan status for mentions and fetch remote mentioned users, create
@@ -34,6 +33,7 @@ class ProcessMentionsService < BaseService
     end
 
     status.save!
+    check_for_spam(status)
 
     mentions.each { |mention| create_notification(mention) }
   end
@@ -41,7 +41,7 @@ class ProcessMentionsService < BaseService
   private
 
   def mention_undeliverable?(mentioned_account)
-    mentioned_account.nil? || (!mentioned_account.local? && mentioned_account.ostatus? && @status.stream_entry.hidden?)
+    mentioned_account.nil? || (!mentioned_account.local? && mentioned_account.ostatus?)
   end
 
   def create_notification(mention)
@@ -49,17 +49,11 @@ class ProcessMentionsService < BaseService
 
     if mentioned_account.local?
       LocalNotificationWorker.perform_async(mentioned_account.id, mention.id, mention.class.name)
-    elsif mentioned_account.ostatus? && !@status.stream_entry.hidden? && !@status.local_only?
-      NotificationWorker.perform_async(ostatus_xml, @status.account_id, mentioned_account.id)
     elsif mentioned_account.activitypub? && !@status.local_only?
       ActivityPub::DeliveryWorker.perform_async(activitypub_json, mention.status.account_id, mentioned_account.inbox_url)
     end
   end
 
-  def ostatus_xml
-    @ostatus_xml ||= stream_entry_to_xml(@status.stream_entry)
-  end
-
   def activitypub_json
     return @activitypub_json if defined?(@activitypub_json)
     @activitypub_json = Oj.dump(serialize_payload(@status, ActivityPub::ActivitySerializer, signer: @status.account))
@@ -68,4 +62,8 @@ class ProcessMentionsService < BaseService
   def resolve_account_service
     ResolveAccountService.new
   end
+
+  def check_for_spam(status)
+    SpamCheck.perform(status)
+  end
 end
diff --git a/app/services/pubsubhubbub/subscribe_service.rb b/app/services/pubsubhubbub/subscribe_service.rb
deleted file mode 100644
index 550da632812a5163d091ed35ca5b95d10c7cac3b..0000000000000000000000000000000000000000
--- a/app/services/pubsubhubbub/subscribe_service.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-class Pubsubhubbub::SubscribeService < BaseService
-  URL_PATTERN = /\A#{URI.regexp(%w(http https))}\z/
-
-  attr_reader :account, :callback, :secret,
-              :lease_seconds, :domain
-
-  def call(account, callback, secret, lease_seconds, verified_domain = nil)
-    @account       = account
-    @callback      = Addressable::URI.parse(callback).normalize.to_s
-    @secret        = secret
-    @lease_seconds = lease_seconds
-    @domain        = verified_domain
-
-    process_subscribe
-  end
-
-  private
-
-  def process_subscribe
-    if account.nil?
-      ['Invalid topic URL', 422]
-    elsif !valid_callback?
-      ['Invalid callback URL', 422]
-    elsif blocked_domain?
-      ['Callback URL not allowed', 403]
-    else
-      confirm_subscription
-      ['', 202]
-    end
-  end
-
-  def confirm_subscription
-    subscription = locate_subscription
-    Pubsubhubbub::ConfirmationWorker.perform_async(subscription.id, 'subscribe', secret, lease_seconds)
-  end
-
-  def valid_callback?
-    callback.present? && callback =~ URL_PATTERN
-  end
-
-  def blocked_domain?
-    DomainBlock.blocked? Addressable::URI.parse(callback).host
-  end
-
-  def locate_subscription
-    subscription = Subscription.find_or_initialize_by(account: account, callback_url: callback)
-    subscription.domain = domain
-    subscription.save!
-    subscription
-  end
-end
diff --git a/app/services/pubsubhubbub/unsubscribe_service.rb b/app/services/pubsubhubbub/unsubscribe_service.rb
deleted file mode 100644
index 646150f7bb450a1fed75ffc8a16cef23685da841..0000000000000000000000000000000000000000
--- a/app/services/pubsubhubbub/unsubscribe_service.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-class Pubsubhubbub::UnsubscribeService < BaseService
-  attr_reader :account, :callback
-
-  def call(account, callback)
-    @account  = account
-    @callback = Addressable::URI.parse(callback).normalize.to_s
-
-    process_unsubscribe
-  end
-
-  private
-
-  def process_unsubscribe
-    if account.nil?
-      ['Invalid topic URL', 422]
-    else
-      confirm_unsubscribe unless subscription.nil?
-      ['', 202]
-    end
-  end
-
-  def confirm_unsubscribe
-    Pubsubhubbub::ConfirmationWorker.perform_async(subscription.id, 'unsubscribe')
-  end
-
-  def subscription
-    @_subscription ||= Subscription.find_by(account: account, callback_url: callback)
-  end
-end
diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb
index 09403bae0f45e0cb2c94d97e29602cc0d9ab4657..9c409da801e1f6257756c3f04ad0b71e489d6cff 100644
--- a/app/services/reblog_service.rb
+++ b/app/services/reblog_service.rb
@@ -2,7 +2,6 @@
 
 class ReblogService < BaseService
   include Authorization
-  include StreamEntryRenderer
   include Payloadable
 
   # Reblog a status and notify its remote author
@@ -24,9 +23,7 @@ class ReblogService < BaseService
     reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility)
 
     DistributionWorker.perform_async(reblog.id)
-
     unless reblogged_status.local_only?
-      Pubsubhubbub::DistributionWorker.perform_async(reblog.stream_entry.id)
       ActivityPub::DistributionWorker.perform_async(reblog.id)
     end
 
@@ -43,8 +40,6 @@ class ReblogService < BaseService
 
     if reblogged_status.account.local?
       LocalNotificationWorker.perform_async(reblogged_status.account_id, reblog.id, reblog.class.name)
-    elsif reblogged_status.account.ostatus?
-      NotificationWorker.perform_async(stream_entry_to_xml(reblog.stream_entry), reblog.account_id, reblogged_status.account_id)
     elsif reblogged_status.account.activitypub? && !reblogged_status.account.following?(reblog.account)
       ActivityPub::DeliveryWorker.perform_async(build_json(reblog), reblog.account_id, reblogged_status.account.inbox_url)
     end
diff --git a/app/services/reject_follow_service.rb b/app/services/reject_follow_service.rb
index f87d0ba914f6382fc5904f885b21da6384c26543..bc0000c8c886b08698d0aae7b9892cc3e58909c7 100644
--- a/app/services/reject_follow_service.rb
+++ b/app/services/reject_follow_service.rb
@@ -6,25 +6,17 @@ class RejectFollowService < BaseService
   def call(source_account, target_account)
     follow_request = FollowRequest.find_by!(account: source_account, target_account: target_account)
     follow_request.reject!
-    create_notification(follow_request) unless source_account.local?
+    create_notification(follow_request) if !source_account.local? && source_account.activitypub?
     follow_request
   end
 
   private
 
   def create_notification(follow_request)
-    if follow_request.account.ostatus?
-      NotificationWorker.perform_async(build_xml(follow_request), follow_request.target_account_id, follow_request.account_id)
-    elsif follow_request.account.activitypub?
-      ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), follow_request.target_account_id, follow_request.account.inbox_url)
-    end
+    ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), follow_request.target_account_id, follow_request.account.inbox_url)
   end
 
   def build_json(follow_request)
     Oj.dump(serialize_payload(follow_request, ActivityPub::RejectFollowSerializer))
   end
-
-  def build_xml(follow_request)
-    OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request))
-  end
 end
diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb
index 81adc5aae54dcb0fe8c4c7f1849cd0e493db3dc2..f9352ed3d319f8ed030af066baf2e065bf5eb182 100644
--- a/app/services/remove_status_service.rb
+++ b/app/services/remove_status_service.rb
@@ -1,19 +1,23 @@
 # frozen_string_literal: true
 
 class RemoveStatusService < BaseService
-  include StreamEntryRenderer
   include Redisable
   include Payloadable
 
+  # Delete a status
+  # @param   [Status] status
+  # @param   [Hash] options
+  # @option  [Boolean] :redraft
+  # @option  [Boolean] :immediate
+  # @option [Boolean] :original_removed
   def call(status, **options)
-    @payload      = Oj.dump(event: :delete, payload: status.id.to_s)
-    @status       = status
-    @account      = status.account
-    @tags         = status.tags.pluck(:name).to_a
-    @mentions     = status.active_mentions.includes(:account).to_a
-    @reblogs      = status.reblogs.includes(:account).to_a
-    @stream_entry = status.stream_entry
-    @options      = options
+    @payload  = Oj.dump(event: :delete, payload: status.id.to_s)
+    @status   = status
+    @account  = status.account
+    @tags     = status.tags.pluck(:name).to_a
+    @mentions = status.active_mentions.includes(:account).to_a
+    @reblogs  = status.reblogs.includes(:account).to_a
+    @options  = options
 
     RedisLock.acquire(lock_options) do |lock|
       if lock.acquired?
@@ -25,8 +29,10 @@ class RemoveStatusService < BaseService
         remove_from_hashtags
         remove_from_public
         remove_from_media if status.media_attachments.any?
+        remove_from_spam_check
+        remove_media
 
-        @status.destroy!
+        @status.destroy! if @options[:immediate] || !@status.reported?
       else
         raise Mastodon::RaceConditionError
       end
@@ -78,11 +84,6 @@ class RemoveStatusService < BaseService
     target_accounts << @status.reblog.account if @status.reblog? && !@status.reblog.account.local?
     target_accounts.uniq!(&:id)
 
-    # Ostatus
-    NotificationWorker.push_bulk(target_accounts.select(&:ostatus?).uniq(&:domain)) do |target_account|
-      [salmon_xml, @account.id, target_account.id]
-    end
-
     # ActivityPub
     ActivityPub::DeliveryWorker.push_bulk(target_accounts.select(&:activitypub?).uniq(&:preferred_inbox_url)) do |target_account|
       [signed_activity_json, @account.id, target_account.preferred_inbox_url]
@@ -90,9 +91,6 @@ class RemoveStatusService < BaseService
   end
 
   def remove_from_remote_followers
-    # OStatus
-    Pubsubhubbub::RawDistributionWorker.perform_async(salmon_xml, @account.id)
-
     # ActivityPub
     ActivityPub::DeliveryWorker.push_bulk(@account.followers.inboxes) do |inbox_url|
       [signed_activity_json, @account.id, inbox_url]
@@ -111,10 +109,6 @@ class RemoveStatusService < BaseService
     end
   end
 
-  def salmon_xml
-    @salmon_xml ||= stream_entry_to_xml(@stream_entry)
-  end
-
   def signed_activity_json
     @signed_activity_json ||= Oj.dump(serialize_payload(@status, @status.reblog? ? ActivityPub::UndoAnnounceSerializer : ActivityPub::DeleteSerializer, signer: @account))
   end
@@ -137,8 +131,8 @@ class RemoveStatusService < BaseService
     return unless @status.public_visibility?
 
     @tags.each do |hashtag|
-      redis.publish("timeline:hashtag:#{hashtag}", @payload)
-      redis.publish("timeline:hashtag:#{hashtag}:local", @payload) if @status.local?
+      redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}", @payload)
+      redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}:local", @payload) if @status.local?
     end
   end
 
@@ -156,6 +150,16 @@ class RemoveStatusService < BaseService
     redis.publish('timeline:public:local:media', @payload) if @status.local?
   end
 
+  def remove_media
+    return if @options[:redraft] || (!@options[:immediate] && @status.reported?)
+
+    @status.media_attachments.destroy_all
+  end
+
+  def remove_from_spam_check
+    redis.zremrangebyscore("spam_check:#{@status.account_id}", @status.id, @status.id)
+  end
+
   def lock_options
     { redis: Redis.current, key: "distribute:#{@status.id}" }
   end
diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb
index e557706da5c12a6d06798f8a77163b6188c0f2cd..12e6544a0f6d3ab4ed035bc206cfb27d4db9e4f5 100644
--- a/app/services/resolve_account_service.rb
+++ b/app/services/resolve_account_service.rb
@@ -1,89 +1,113 @@
 # frozen_string_literal: true
 
 class ResolveAccountService < BaseService
-  include OStatus2::MagicKey
   include JsonLdHelper
+  include DomainControlHelper
 
-  DFRN_NS = 'http://purl.org/macgirvin/dfrn/1.0'
+  class WebfingerRedirectError < StandardError; end
 
-  # Find or create a local account for a remote user.
-  # When creating, look up the user's webfinger and fetch all
-  # important information from their feed
-  # @param [String, Account] uri User URI in the form of username@domain
+  # Find or create an account record for a remote user. When creating,
+  # look up the user's webfinger and fetch ActivityPub data
+  # @param [String, Account] uri URI in the username@domain format or account record
   # @param [Hash] options
+  # @option options [Boolean] :redirected Do not follow further Webfinger redirects
+  # @option options [Boolean] :skip_webfinger Do not attempt to refresh account data
   # @return [Account]
   def call(uri, options = {})
+    return if uri.blank?
+
+    process_options!(uri, options)
+
+    # First of all we want to check if we've got the account
+    # record with the URI already, and if so, we can exit early
+
+    return if domain_not_allowed?(@domain)
+
+    @account ||= Account.find_remote(@username, @domain)
+
+    return @account if @account&.local? || !webfinger_update_due?
+
+    # At this point we are in need of a Webfinger query, which may
+    # yield us a different username/domain through a redirect
+
+    process_webfinger!(@uri)
+
+    # Because the username/domain pair may be different than what
+    # we already checked, we need to check if we've already got
+    # the record with that URI, again
+
+    return if domain_not_allowed?(@domain)
+
+    @account ||= Account.find_remote(@username, @domain)
+
+    return @account if @account&.local? || !webfinger_update_due?
+
+    # Now it is certain, it is definitely a remote account, and it
+    # either needs to be created, or updated from fresh data
+
+    process_account!
+  rescue Goldfinger::Error, WebfingerRedirectError, Oj::ParseError => e
+    Rails.logger.debug "Webfinger query for #{@uri} failed: #{e}"
+    nil
+  end
+
+  private
+
+  def process_options!(uri, options)
     @options = options
 
     if uri.is_a?(Account)
       @account  = uri
       @username = @account.username
       @domain   = @account.domain
-      uri       = "#{@username}@#{@domain}"
-
-      return @account if @account.local? || !webfinger_update_due?
     else
       @username, @domain = uri.split('@')
-
-      return Account.find_local(@username) if TagManager.instance.local_domain?(@domain)
-
-      @account = Account.find_remote(@username, @domain)
-
-      return @account unless webfinger_update_due?
     end
 
-    Rails.logger.debug "Looking up webfinger for #{uri}"
+    @domain = begin
+      if TagManager.instance.local_domain?(@domain)
+        nil
+      else
+        TagManager.instance.normalize_domain(@domain)
+      end
+    end
 
-    @webfinger = Goldfinger.finger("acct:#{uri}")
+    @uri = [@username, @domain].compact.join('@')
+  end
 
+  def process_webfinger!(uri, redirected = false)
+    @webfinger                           = Goldfinger.finger("acct:#{uri}")
     confirmed_username, confirmed_domain = @webfinger.subject.gsub(/\Aacct:/, '').split('@')
 
     if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
       @username = confirmed_username
       @domain   = confirmed_domain
-    elsif options[:redirected].nil?
-      return call("#{confirmed_username}@#{confirmed_domain}", options.merge(redirected: true))
+      @uri      = uri
+    elsif !redirected
+      return process_webfinger!("#{confirmed_username}@#{confirmed_domain}", true)
     else
-      Rails.logger.debug 'Requested and returned acct URIs do not match'
-      return
+      raise WebfingerRedirectError, "The URI #{uri} tries to hijack #{@username}@#{@domain}"
     end
 
-    return if links_missing? || auto_suspend?
-    return Account.find_local(@username) if TagManager.instance.local_domain?(@domain)
+    @domain = nil if TagManager.instance.local_domain?(@domain)
+  end
+
+  def process_account!
+    return unless activitypub_ready?
 
     RedisLock.acquire(lock_options) do |lock|
       if lock.acquired?
         @account = Account.find_remote(@username, @domain)
 
-        if activitypub_ready? || @account&.activitypub?
-          handle_activitypub
-        else
-          handle_ostatus
-        end
+        next if (@account.present? && !@account.activitypub?) || actor_json.nil?
+
+        @account = ActivityPub::ProcessAccountService.new.call(@username, @domain, actor_json)
       else
         raise Mastodon::RaceConditionError
       end
     end
 
     @account
-  rescue Goldfinger::Error => e
-    Rails.logger.debug "Webfinger query for #{uri} unsuccessful: #{e}"
-    nil
-  end
-
-  private
-
-  def links_missing?
-    !(activitypub_ready? || ostatus_ready?)
-  end
-
-  def ostatus_ready?
-    !(@webfinger.link('http://schemas.google.com/g/2010#updates-from').nil? ||
-      @webfinger.link('salmon').nil? ||
-      @webfinger.link('http://webfinger.net/rel/profile-page').nil? ||
-      @webfinger.link('magic-public-key').nil? ||
-      canonical_uri.nil? ||
-      hub_url.nil?)
   end
 
   def webfinger_update_due?
@@ -91,113 +115,13 @@ class ResolveAccountService < BaseService
   end
 
   def activitypub_ready?
-    !@webfinger.link('self').nil? &&
-      ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@webfinger.link('self').type) &&
-      !actor_json.nil? &&
-      actor_json['inbox'].present?
-  end
-
-  def handle_ostatus
-    create_account if @account.nil?
-    update_account
-    update_account_profile if update_profile?
-  end
-
-  def update_profile?
-    @options[:update_profile]
-  end
-
-  def handle_activitypub
-    return if actor_json.nil?
-
-    @account = ActivityPub::ProcessAccountService.new.call(@username, @domain, actor_json)
-  rescue Oj::ParseError
-    nil
-  end
-
-  def create_account
-    Rails.logger.debug "Creating new remote account for #{@username}@#{@domain}"
-
-    @account = Account.new(username: @username, domain: @domain)
-    @account.suspended_at = domain_block.created_at if auto_suspend?
-    @account.silenced_at  = domain_block.created_at if auto_silence?
-    @account.private_key  = nil
-  end
-
-  def update_account
-    @account.last_webfingered_at = Time.now.utc
-    @account.protocol            = :ostatus
-    @account.remote_url          = atom_url
-    @account.salmon_url          = salmon_url
-    @account.url                 = url
-    @account.public_key          = public_key
-    @account.uri                 = canonical_uri
-    @account.hub_url             = hub_url
-    @account.save!
-  end
-
-  def auto_suspend?
-    domain_block&.suspend?
-  end
-
-  def auto_silence?
-    domain_block&.silence?
-  end
-
-  def domain_block
-    return @domain_block if defined?(@domain_block)
-    @domain_block = DomainBlock.rule_for(@domain)
-  end
-
-  def atom_url
-    @atom_url ||= @webfinger.link('http://schemas.google.com/g/2010#updates-from').href
-  end
-
-  def salmon_url
-    @salmon_url ||= @webfinger.link('salmon').href
+    !@webfinger.link('self').nil? && ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@webfinger.link('self').type)
   end
 
   def actor_url
     @actor_url ||= @webfinger.link('self').href
   end
 
-  def url
-    @url ||= @webfinger.link('http://webfinger.net/rel/profile-page').href
-  end
-
-  def public_key
-    @public_key ||= magic_key_to_pem(@webfinger.link('magic-public-key').href)
-  end
-
-  def canonical_uri
-    return @canonical_uri if defined?(@canonical_uri)
-
-    author_uri = atom.at_xpath('/xmlns:feed/xmlns:author/xmlns:uri')
-
-    if author_uri.nil?
-      owner      = atom.at_xpath('/xmlns:feed').at_xpath('./dfrn:owner', dfrn: DFRN_NS)
-      author_uri = owner.at_xpath('./xmlns:uri') unless owner.nil?
-    end
-
-    @canonical_uri = author_uri.nil? ? nil : author_uri.content
-  end
-
-  def hub_url
-    return @hub_url if defined?(@hub_url)
-
-    hubs     = atom.xpath('//xmlns:link[@rel="hub"]')
-    @hub_url = hubs.empty? || hubs.first['href'].nil? ? nil : hubs.first['href']
-  end
-
-  def atom_body
-    return @atom_body if defined?(@atom_body)
-
-    @atom_body = Request.new(:get, atom_url).perform do |response|
-      raise Mastodon::UnexpectedResponseError, response unless response.code == 200
-      response.body_with_limit
-    end
-  end
-
   def actor_json
     return @actor_json if defined?(@actor_json)
 
@@ -205,15 +129,6 @@ class ResolveAccountService < BaseService
     @actor_json = supported_context?(json) && equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) ? json : nil
   end
 
-  def atom
-    return @atom if defined?(@atom)
-    @atom = Nokogiri::XML(atom_body)
-  end
-
-  def update_account_profile
-    RemoteProfileUpdateWorker.perform_async(@account.id, atom_body.force_encoding('UTF-8'), false)
-  end
-
   def lock_options
     { redis: Redis.current, key: "resolve:#{@username}@#{@domain}" }
   end
diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb
index b98759bf68b698d5bd5fce4d5f74e380ab7a8725..aa883597a467e3d133cc2627cff34aaac785ee0a 100644
--- a/app/services/resolve_url_service.rb
+++ b/app/services/resolve_url_service.rb
@@ -4,64 +4,51 @@ class ResolveURLService < BaseService
   include JsonLdHelper
   include Authorization
 
-  attr_reader :url
-
   def call(url, on_behalf_of: nil)
-    @url = url
+    @url          = url
     @on_behalf_of = on_behalf_of
 
-    return process_local_url if local_url?
-
-    process_url unless fetched_atom_feed.nil?
+    if local_url?
+      process_local_url
+    elsif !fetched_resource.nil?
+      process_url
+    end
   end
 
   private
 
   def process_url
-    if equals_or_includes_any?(type, %w(Application Group Organization Person Service))
-      FetchRemoteAccountService.new.call(atom_url, body, protocol)
-    elsif equals_or_includes_any?(type, %w(Note Article Image Video Page Question))
-      FetchRemoteStatusService.new.call(atom_url, body, protocol)
+    if equals_or_includes_any?(type, ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES)
+      FetchRemoteAccountService.new.call(resource_url, body, protocol)
+    elsif equals_or_includes_any?(type, ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES)
+      status = FetchRemoteStatusService.new.call(resource_url, body, protocol)
+      authorize_with @on_behalf_of, status, :show? unless status.nil?
+      status
     end
   end
 
-  def fetched_atom_feed
-    @_fetched_atom_feed ||= FetchAtomService.new.call(url)
+  def fetched_resource
+    @fetched_resource ||= FetchResourceService.new.call(@url)
   end
 
-  def atom_url
-    fetched_atom_feed.first
+  def resource_url
+    fetched_resource.first
   end
 
   def body
-    fetched_atom_feed.second[:prefetched_body]
+    fetched_resource.second[:prefetched_body]
   end
 
   def protocol
-    fetched_atom_feed.third
+    fetched_resource.third
   end
 
   def type
     return json_data['type'] if protocol == :activitypub
-
-    case xml_root
-    when 'feed'
-      'Person'
-    when 'entry'
-      'Note'
-    end
   end
 
   def json_data
-    @_json_data ||= body_to_json(body)
-  end
-
-  def xml_root
-    xml_data.root.name
-  end
-
-  def xml_data
-    @_xml_data ||= Nokogiri::XML(body, nil, 'utf-8')
+    @json_data ||= body_to_json(body)
   end
 
   def local_url?
@@ -73,10 +60,7 @@ class ResolveURLService < BaseService
 
     return unless recognized_params[:action] == 'show'
 
-    if recognized_params[:controller] == 'stream_entries'
-      status = StreamEntry.find_by(id: recognized_params[:id])&.status
-      check_local_status(status)
-    elsif recognized_params[:controller] == 'statuses'
+    if recognized_params[:controller] == 'statuses'
       status = Status.find_by(id: recognized_params[:id])
       check_local_status(status)
     elsif recognized_params[:controller] == 'accounts'
@@ -86,10 +70,10 @@ class ResolveURLService < BaseService
 
   def check_local_status(status)
     return if status.nil?
+
     authorize_with @on_behalf_of, status, :show?
     status
   rescue Mastodon::NotPermittedError
-    # Do not disclose the existence of status the user is not authorized to see
     nil
   end
 end
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index e0da61dac5b81545ffce7e158d1c8c7b408b8f39..3a498dcf4757afb1bbf296ec0a49c8623fbfba96 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -11,7 +11,7 @@ class SearchService < BaseService
 
     default_results.tap do |results|
       if url_query?
-        results.merge!(url_resource_results) unless url_resource.nil?
+        results.merge!(url_resource_results) unless url_resource.nil? || (@options[:type].present? && url_resource_symbol != @options[:type].to_sym)
       elsif @query.present?
         results[:accounts] = perform_accounts_search! if account_searchable?
         results[:statuses] = perform_statuses_search! if full_text_searchable?
@@ -33,8 +33,7 @@ class SearchService < BaseService
   end
 
   def perform_statuses_search!
-    definition = StatusesIndex.filter(term: { searchable_by: @account.id })
-                              .query(multi_match: { type: 'most_fields', query: @query, operator: 'and', fields: %w(text text.stemmed) })
+    definition = parsed_query.apply(StatusesIndex.filter(term: { searchable_by: @account.id }))
 
     if @options[:account_id].present?
       definition = definition.filter(term: { account_id: @options[:account_id] })
@@ -53,15 +52,16 @@ class SearchService < BaseService
     preloaded_relations = relations_map_for_account(@account, account_ids, account_domains)
 
     results.reject { |status| StatusFilter.new(status, @account, preloaded_relations).filtered? }
-  rescue Faraday::ConnectionFailed
+  rescue Faraday::ConnectionFailed, Parslet::ParseFailed
     []
   end
 
   def perform_hashtags_search!
-    Tag.search_for(
-      @query.gsub(/\A#/, ''),
-      @limit,
-      @offset
+    TagSearchService.new.call(
+      @query,
+      limit: @limit,
+      offset: @offset,
+      exclude_unreviewed: @options[:exclude_unreviewed]
     )
   end
 
@@ -70,7 +70,7 @@ class SearchService < BaseService
   end
 
   def url_query?
-    @options[:type].blank? && @query =~ /\Ahttps?:\/\//
+    @resolve && @query =~ /\Ahttps?:\/\//
   end
 
   def url_resource_results
@@ -120,4 +120,8 @@ class SearchService < BaseService
       domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id),
     }
   end
+
+  def parsed_query
+    SearchQueryTransformer.new.apply(SearchQueryParser.new.parse(@query))
+  end
 end
diff --git a/app/services/send_interaction_service.rb b/app/services/send_interaction_service.rb
deleted file mode 100644
index 3419043e5668fd5edf67958558f08ba59f7b2f54..0000000000000000000000000000000000000000
--- a/app/services/send_interaction_service.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-class SendInteractionService < BaseService
-  # Send an Atom representation of an interaction to a remote Salmon endpoint
-  # @param [String] Entry XML
-  # @param [Account] source_account
-  # @param [Account] target_account
-  def call(xml, source_account, target_account)
-    @xml            = xml
-    @source_account = source_account
-    @target_account = target_account
-
-    return if !target_account.ostatus? || block_notification?
-
-    build_request.perform do |delivery|
-      raise Mastodon::UnexpectedResponseError, delivery unless delivery.code > 199 && delivery.code < 300
-    end
-  end
-
-  private
-
-  def build_request
-    request = Request.new(:post, @target_account.salmon_url, body: envelope)
-    request.add_headers('Content-Type' => 'application/magic-envelope+xml')
-    request
-  end
-
-  def envelope
-    salmon.pack(@xml, @source_account.keypair)
-  end
-
-  def block_notification?
-    DomainBlock.blocked?(@target_account.domain)
-  end
-
-  def salmon
-    @salmon ||= OStatus2::Salmon.new
-  end
-end
diff --git a/app/services/subscribe_service.rb b/app/services/subscribe_service.rb
deleted file mode 100644
index 83fd64396a1057d981ed67ca6f7c2e506ca78aac..0000000000000000000000000000000000000000
--- a/app/services/subscribe_service.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# frozen_string_literal: true
-
-class SubscribeService < BaseService
-  def call(account)
-    return if account.hub_url.blank?
-
-    @account        = account
-    @account.secret = SecureRandom.hex
-
-    build_request.perform do |response|
-      if response_failed_permanently? response
-        # We're not allowed to subscribe. Fail and move on.
-        @account.secret = ''
-        @account.save!
-      elsif response_successful? response
-        # The subscription will be confirmed asynchronously.
-        @account.save!
-      else
-        # The response was either a 429 rate limit, or a 5xx error.
-        # We need to retry at a later time. Fail loudly!
-        raise Mastodon::UnexpectedResponseError, response
-      end
-    end
-  end
-
-  private
-
-  def build_request
-    request = Request.new(:post, @account.hub_url, form: subscription_params)
-    request.on_behalf_of(some_local_account) if some_local_account
-    request
-  end
-
-  def subscription_params
-    {
-      'hub.topic': @account.remote_url,
-      'hub.mode': 'subscribe',
-      'hub.callback': api_subscription_url(@account.id),
-      'hub.verify': 'async',
-      'hub.secret': @account.secret,
-      'hub.lease_seconds': 7.days.seconds,
-    }
-  end
-
-  def some_local_account
-    @some_local_account ||= Account.local.without_suspended.first
-  end
-
-  # Any response in the 3xx or 4xx range, except for 429 (rate limit)
-  def response_failed_permanently?(response)
-    (response.status.redirect? || response.status.client_error?) && !response.status.too_many_requests?
-  end
-
-  # Any response in the 2xx range
-  def response_successful?(response)
-    response.status.success?
-  end
-end
diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb
index 5dd01da52582b3440aa675b3955b1c78b2d59a73..ecc893931d596d9fd00a8a0911ce910be7bf9f95 100644
--- a/app/services/suspend_account_service.rb
+++ b/app/services/suspend_account_service.rb
@@ -15,7 +15,6 @@ class SuspendAccountService < BaseService
     favourites
     follow_requests
     list_accounts
-    media_attachments
     mute_relationships
     muted_by_relationships
     notifications
@@ -24,8 +23,6 @@ class SuspendAccountService < BaseService
     report_notes
     scheduled_statuses
     status_pins
-    stream_entries
-    subscriptions
   ).freeze
 
   ASSOCIATIONS_ON_DESTROY = %w(
@@ -34,14 +31,26 @@ class SuspendAccountService < BaseService
     targeted_reports
   ).freeze
 
-  # Suspend an account and remove as much of its data as possible
+  # Suspend or remove an account and remove as much of its data
+  # as possible. If it's a local account and it has not been confirmed
+  # or never been approved, then side effects are skipped and both
+  # the user and account records are removed fully. Otherwise,
+  # it is controlled by options.
   # @param [Account]
   # @param [Hash] options
-  # @option [Boolean] :including_user Remove the user record as well
-  # @option [Boolean] :destroy Remove the account record instead of suspending
+  # @option [Boolean] :reserve_email Keep user record. Only applicable for local accounts
+  # @option [Boolean] :reserve_username Keep account record
+  # @option [Boolean] :skip_side_effects Side effects are ActivityPub and streaming API payloads
+  # @option [Time]    :suspended_at Only applicable when :reserve_username is true
   def call(account, **options)
     @account = account
-    @options = options
+    @options = { reserve_username: true, reserve_email: true }.merge(options)
+
+    if @account.local? && @account.user_unconfirmed_or_pending?
+      @options[:reserve_email]     = false
+      @options[:reserve_username]  = false
+      @options[:skip_side_effects] = true
+    end
 
     reject_follows!
     purge_user!
@@ -62,26 +71,39 @@ class SuspendAccountService < BaseService
   def purge_user!
     return if !@account.local? || @account.user.nil?
 
-    if @options[:including_user]
-      @account.user.destroy
-    else
+    if @options[:reserve_email]
       @account.user.disable!
       @account.user.invites.where(uses: 0).destroy_all
+    else
+      @account.user.destroy
     end
   end
 
   def purge_content!
-    distribute_delete_actor! if @account.local? && !@options[:skip_distribution]
+    distribute_delete_actor! if @account.local? && !@options[:skip_side_effects]
 
     @account.statuses.reorder(nil).find_in_batches do |statuses|
-      BatchedRemoveStatusService.new.call(statuses, skip_side_effects: @options[:destroy])
+      statuses.reject! { |status| reported_status_ids.include?(status.id) } if @options[:reserve_username]
+      BatchedRemoveStatusService.new.call(statuses, skip_side_effects: @options[:skip_side_effects])
+    end
+
+    @account.media_attachments.reorder(nil).find_each do |media_attachment|
+      next if @options[:reserve_username] && reported_status_ids.include?(media_attachment.status_id)
+
+      media_attachment.destroy
+    end
+
+    @account.polls.reorder(nil).find_each do |poll|
+      next if @options[:reserve_username] && reported_status_ids.include?(poll.status_id)
+
+      poll.destroy
     end
 
     associations_for_destruction.each do |association_name|
       destroy_all(@account.public_send(association_name))
     end
 
-    @account.destroy if @options[:destroy]
+    @account.destroy unless @options[:reserve_username]
   end
 
   def purge_profile!
@@ -89,11 +111,13 @@ class SuspendAccountService < BaseService
     # there is no point wasting time updating
     # its values first
 
-    return if @options[:destroy]
+    return unless @options[:reserve_username]
 
     @account.silenced_at      = nil
     @account.suspended_at     = @options[:suspended_at] || Time.now.utc
     @account.locked           = false
+    @account.memorial         = false
+    @account.discoverable     = false
     @account.display_name     = ''
     @account.note             = ''
     @account.fields           = []
@@ -101,6 +125,7 @@ class SuspendAccountService < BaseService
     @account.followers_count  = 0
     @account.following_count  = 0
     @account.moved_to_account = nil
+    @account.trust_level      = :untrusted
     @account.avatar.destroy
     @account.header.destroy
     @account.save!
@@ -136,11 +161,15 @@ class SuspendAccountService < BaseService
     Account.inboxes - delivery_inboxes
   end
 
+  def reported_status_ids
+    @reported_status_ids ||= Report.where(target_account: @account).unresolved.pluck(:status_ids).flatten.uniq
+  end
+
   def associations_for_destruction
-    if @options[:destroy]
-      ASSOCIATIONS_ON_SUSPEND + ASSOCIATIONS_ON_DESTROY
-    else
+    if @options[:reserve_username]
       ASSOCIATIONS_ON_SUSPEND
+    else
+      ASSOCIATIONS_ON_SUSPEND + ASSOCIATIONS_ON_DESTROY
     end
   end
 end
diff --git a/app/services/tag_search_service.rb b/app/services/tag_search_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b78d65625d5f75746f3121bd24018b15e16e35b8
--- /dev/null
+++ b/app/services/tag_search_service.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+class TagSearchService < BaseService
+  def call(query, options = {})
+    @query   = query.strip.gsub(/\A#/, '')
+    @offset  = options.delete(:offset).to_i
+    @limit   = options.delete(:limit).to_i
+    @options = options
+
+    results   = from_elasticsearch if Chewy.enabled?
+    results ||= from_database
+
+    results
+  end
+
+  private
+
+  def from_elasticsearch
+    query = {
+      function_score: {
+        query: {
+          multi_match: {
+            query: @query,
+            fields: %w(name.edge_ngram name),
+            type: 'most_fields',
+            operator: 'and',
+          },
+        },
+
+        functions: [
+          {
+            field_value_factor: {
+              field: 'usage',
+              modifier: 'log2p',
+              missing: 0,
+            },
+          },
+
+          {
+            gauss: {
+              last_status_at: {
+                scale: '7d',
+                offset: '14d',
+                decay: 0.5,
+              },
+            },
+          },
+        ],
+
+        boost_mode: 'multiply',
+      },
+    }
+
+    filter = {
+      bool: {
+        should: [
+          {
+            term: {
+              reviewed: {
+                value: true,
+              },
+            },
+          },
+
+          {
+            match: {
+              name: {
+                query: @query,
+              },
+            },
+          },
+        ],
+      },
+    }
+
+    definition = TagsIndex.query(query)
+    definition = definition.filter(filter) if @options[:exclude_unreviewed]
+
+    definition.limit(@limit).offset(@offset).objects.compact
+  rescue Faraday::ConnectionFailed, Parslet::ParseFailed
+    nil
+  end
+
+  def from_database
+    Tag.search_for(@query, @limit, @offset, @options)
+  end
+end
diff --git a/app/services/unallow_domain_service.rb b/app/services/unallow_domain_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bd1ad328dacae06933039aae85da51644e8a4a90
--- /dev/null
+++ b/app/services/unallow_domain_service.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class UnallowDomainService < BaseService
+  def call(domain_allow)
+    Account.where(domain: domain_allow.domain).find_each do |account|
+      SuspendAccountService.new.call(account, reserve_username: false)
+    end
+
+    domain_allow.destroy
+  end
+end
diff --git a/app/services/unblock_domain_service.rb b/app/services/unblock_domain_service.rb
index fc262a50ada223034602d078824ff55dba62ceb4..d502d9e4925cf1227cb7bbbf0454e2938e2bcb98 100644
--- a/app/services/unblock_domain_service.rb
+++ b/app/services/unblock_domain_service.rb
@@ -10,24 +10,9 @@ class UnblockDomainService < BaseService
   end
 
   def process_retroactive_updates
-    blocked_accounts.in_batches.update_all(update_options) unless domain_block.noop?
-  end
-
-  def blocked_accounts
     scope = Account.by_domain_and_subdomains(domain_block.domain)
 
-    if domain_block.silence?
-      scope.where(silenced_at: @domain_block.created_at)
-    else
-      scope.where(suspended_at: @domain_block.created_at)
-    end
-  end
-
-  def update_options
-    { domain_block_impact => nil }
-  end
-
-  def domain_block_impact
-    domain_block.silence? ? :silenced_at : :suspended_at
+    scope.where(silenced_at: domain_block.created_at).in_batches.update_all(silenced_at: nil) unless domain_block.noop?
+    scope.where(suspended_at: domain_block.created_at).in_batches.update_all(suspended_at: nil) if domain_block.suspend?
   end
 end
diff --git a/app/services/unblock_service.rb b/app/services/unblock_service.rb
index 95a858e9f59a8174502a40fe705ef9125ecdf1a3..c263ac8afe054842756b311ca1d37efd70ae4e68 100644
--- a/app/services/unblock_service.rb
+++ b/app/services/unblock_service.rb
@@ -7,25 +7,17 @@ class UnblockService < BaseService
     return unless account.blocking?(target_account)
 
     unblock = account.unblock!(target_account)
-    create_notification(unblock) unless target_account.local?
+    create_notification(unblock) if !target_account.local? && target_account.activitypub?
     unblock
   end
 
   private
 
   def create_notification(unblock)
-    if unblock.target_account.ostatus?
-      NotificationWorker.perform_async(build_xml(unblock), unblock.account_id, unblock.target_account_id)
-    elsif unblock.target_account.activitypub?
-      ActivityPub::DeliveryWorker.perform_async(build_json(unblock), unblock.account_id, unblock.target_account.inbox_url)
-    end
+    ActivityPub::DeliveryWorker.perform_async(build_json(unblock), unblock.account_id, unblock.target_account.inbox_url)
   end
 
   def build_json(unblock)
     Oj.dump(serialize_payload(unblock, ActivityPub::UndoBlockSerializer))
   end
-
-  def build_xml(block)
-    OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.unblock_salmon(block))
-  end
 end
diff --git a/app/services/unfavourite_service.rb b/app/services/unfavourite_service.rb
index dcc890b7de5f40e6fb3a8a439cf5af0dee31280c..37917a64f1bc52035932d8a136c95ac1ad376fe7 100644
--- a/app/services/unfavourite_service.rb
+++ b/app/services/unfavourite_service.rb
@@ -6,7 +6,7 @@ class UnfavouriteService < BaseService
   def call(account, status)
     favourite = Favourite.find_by!(account: account, status: status)
     favourite.destroy!
-    create_notification(favourite) unless status.local?
+    create_notification(favourite) if !status.account.local? && status.account.activitypub?
     favourite
   end
 
@@ -14,19 +14,10 @@ class UnfavouriteService < BaseService
 
   def create_notification(favourite)
     status = favourite.status
-
-    if status.account.ostatus?
-      NotificationWorker.perform_async(build_xml(favourite), favourite.account_id, status.account_id)
-    elsif status.account.activitypub?
-      ActivityPub::DeliveryWorker.perform_async(build_json(favourite), favourite.account_id, status.account.inbox_url)
-    end
+    ActivityPub::DeliveryWorker.perform_async(build_json(favourite), favourite.account_id, status.account.inbox_url)
   end
 
   def build_json(favourite)
     Oj.dump(serialize_payload(favourite, ActivityPub::UndoLikeSerializer))
   end
-
-  def build_xml(favourite)
-    OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.unfavourite_salmon(favourite))
-  end
 end
diff --git a/app/services/unfollow_service.rb b/app/services/unfollow_service.rb
index 17dc29735f9d5006fd7ece962c2c22b6d97de635..151f3674fd63a32334b7dd1048b954f09190ec2e 100644
--- a/app/services/unfollow_service.rb
+++ b/app/services/unfollow_service.rb
@@ -6,9 +6,12 @@ class UnfollowService < BaseService
   # Unfollow and notify the remote user
   # @param [Account] source_account Where to unfollow from
   # @param [Account] target_account Which to unfollow
-  def call(source_account, target_account)
+  # @param [Hash] options
+  # @option [Boolean] :skip_unmerge
+  def call(source_account, target_account, options = {})
     @source_account = source_account
     @target_account = target_account
+    @options        = options
 
     unfollow! || undo_follow_request!
   end
@@ -21,9 +24,11 @@ class UnfollowService < BaseService
     return unless follow
 
     follow.destroy!
-    create_notification(follow) unless @target_account.local?
-    create_reject_notification(follow) if @target_account.local? && !@source_account.local?
-    UnmergeWorker.perform_async(@target_account.id, @source_account.id)
+
+    create_notification(follow) if !@target_account.local? && @target_account.activitypub?
+    create_reject_notification(follow) if @target_account.local? && !@source_account.local? && @source_account.activitypub?
+    UnmergeWorker.perform_async(@target_account.id, @source_account.id) unless @options[:skip_unmerge]
+
     follow
   end
 
@@ -33,21 +38,17 @@ class UnfollowService < BaseService
     return unless follow_request
 
     follow_request.destroy!
+
     create_notification(follow_request) unless @target_account.local?
+
     follow_request
   end
 
   def create_notification(follow)
-    if follow.target_account.ostatus?
-      NotificationWorker.perform_async(build_xml(follow), follow.account_id, follow.target_account_id)
-    elsif follow.target_account.activitypub?
-      ActivityPub::DeliveryWorker.perform_async(build_json(follow), follow.account_id, follow.target_account.inbox_url)
-    end
+    ActivityPub::DeliveryWorker.perform_async(build_json(follow), follow.account_id, follow.target_account.inbox_url)
   end
 
   def create_reject_notification(follow)
-    # Rejecting an already-existing follow request
-    return unless follow.account.activitypub?
     ActivityPub::DeliveryWorker.perform_async(build_reject_json(follow), follow.target_account_id, follow.account.inbox_url)
   end
 
@@ -58,8 +59,4 @@ class UnfollowService < BaseService
   def build_reject_json(follow)
     Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer))
   end
-
-  def build_xml(follow)
-    OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.unfollow_salmon(follow))
-  end
 end
diff --git a/app/services/unsubscribe_service.rb b/app/services/unsubscribe_service.rb
deleted file mode 100644
index 95c1fb4fc01acf28e241d1cd6903734d880880c3..0000000000000000000000000000000000000000
--- a/app/services/unsubscribe_service.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-class UnsubscribeService < BaseService
-  def call(account)
-    return if account.hub_url.blank?
-
-    @account = account
-
-    begin
-      build_request.perform do |response|
-        Rails.logger.debug "PuSH unsubscribe for #{@account.acct} failed: #{response.status}" unless response.status.success?
-      end
-    rescue HTTP::Error, OpenSSL::SSL::SSLError => e
-      Rails.logger.debug "PuSH unsubscribe for #{@account.acct} failed: #{e}"
-    end
-
-    @account.secret = ''
-    @account.subscription_expires_at = nil
-    @account.save!
-  end
-
-  private
-
-  def build_request
-    Request.new(:post, @account.hub_url, form: subscription_params)
-  end
-
-  def subscription_params
-    {
-      'hub.topic': @account.remote_url,
-      'hub.mode': 'unsubscribe',
-      'hub.callback': api_subscription_url(@account.id),
-      'hub.verify': 'async',
-    }
-  end
-end
diff --git a/app/services/update_account_service.rb b/app/services/update_account_service.rb
index 01756a73d4bbeafdc8cef1af40849c59b916a575..4172d577408f4ae88aaeb91ab2fc8b64548f7313 100644
--- a/app/services/update_account_service.rb
+++ b/app/services/update_account_service.rb
@@ -20,7 +20,9 @@ class UpdateAccountService < BaseService
   private
 
   def authorize_all_follow_requests(account)
-    AuthorizeFollowWorker.push_bulk(FollowRequest.where(target_account: account).select(:account_id, :target_account_id)) do |req|
+    follow_requests = FollowRequest.where(target_account: account)
+    follow_requests = follow_requests.preload(:account).select { |req| !req.account.silenced? }
+    AuthorizeFollowWorker.push_bulk(follow_requests) do |req|
       [req.account_id, req.target_account_id]
     end
   end
diff --git a/app/services/update_remote_profile_service.rb b/app/services/update_remote_profile_service.rb
deleted file mode 100644
index 403395a0ddb1b0f620f61048f65b5ff17d99a71d..0000000000000000000000000000000000000000
--- a/app/services/update_remote_profile_service.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# frozen_string_literal: true
-
-class UpdateRemoteProfileService < BaseService
-  attr_reader :account, :remote_profile
-
-  def call(body, account, resubscribe = false)
-    @account        = account
-    @remote_profile = RemoteProfile.new(body)
-
-    return if remote_profile.root.nil?
-
-    update_account unless remote_profile.author.nil?
-
-    old_hub_url     = account.hub_url
-    account.hub_url = remote_profile.hub_link if remote_profile.hub_link.present? && remote_profile.hub_link != old_hub_url
-
-    account.save_with_optional_media!
-
-    Pubsubhubbub::SubscribeWorker.perform_async(account.id) if resubscribe && account.hub_url != old_hub_url
-  end
-
-  private
-
-  def update_account
-    account.display_name = remote_profile.display_name || ''
-    account.note         = remote_profile.note         || ''
-    account.locked       = remote_profile.locked?
-
-    if !account.suspended? && !DomainBlock.reject_media?(account.domain)
-      if remote_profile.avatar.present?
-        account.avatar_remote_url = remote_profile.avatar
-      else
-        account.avatar_remote_url = ''
-        account.avatar.destroy
-      end
-
-      if remote_profile.header.present?
-        account.header_remote_url = remote_profile.header
-      else
-        account.header_remote_url = ''
-        account.header.destroy
-      end
-
-      save_emojis if remote_profile.emojis.present?
-    end
-  end
-
-  def save_emojis
-    do_not_download = DomainBlock.reject_media?(account.domain)
-
-    return if do_not_download
-
-    remote_profile.emojis.each do |link|
-      next unless link['href'] && link['name']
-
-      shortcode = link['name'].delete(':')
-      emoji     = CustomEmoji.find_by(shortcode: shortcode, domain: account.domain)
-
-      next unless emoji.nil?
-
-      emoji = CustomEmoji.new(shortcode: shortcode, domain: account.domain)
-      emoji.image_remote_url = link['href']
-      emoji.save
-    end
-  end
-end
diff --git a/app/services/verify_salmon_service.rb b/app/services/verify_salmon_service.rb
deleted file mode 100644
index 205b35d8b19d2b3894b48914cd6a7298267cf40b..0000000000000000000000000000000000000000
--- a/app/services/verify_salmon_service.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-class VerifySalmonService < BaseService
-  include AuthorExtractor
-
-  def call(payload)
-    body = salmon.unpack(payload)
-
-    xml = Nokogiri::XML(body)
-    xml.encoding = 'utf-8'
-
-    account = author_from_xml(xml.at_xpath('/xmlns:entry', xmlns: OStatus::TagManager::XMLNS))
-
-    if account.nil?
-      false
-    else
-      salmon.verify(payload, account.keypair)
-    end
-  end
-
-  private
-
-  def salmon
-    @salmon ||= OStatus2::Salmon.new
-  end
-end
diff --git a/app/services/vote_service.rb b/app/services/vote_service.rb
index 0eeb8fd56a95f5a6c2dbb87a9638037ba1b1ce27..cb7dce6e8a41caa51d9ff49c363e063a854d69db 100644
--- a/app/services/vote_service.rb
+++ b/app/services/vote_service.rb
@@ -12,12 +12,24 @@ class VoteService < BaseService
     @choices = choices
     @votes   = []
 
-    ApplicationRecord.transaction do
-      @choices.each do |choice|
-        @votes << @poll.votes.create!(account: @account, choice: choice)
+    already_voted = true
+
+    RedisLock.acquire(lock_options) do |lock|
+      if lock.acquired?
+        already_voted = @poll.votes.where(account: @account).exists?
+
+        ApplicationRecord.transaction do
+          @choices.each do |choice|
+            @votes << @poll.votes.create!(account: @account, choice: choice)
+          end
+        end
+      else
+        raise Mastodon::RaceConditionError
       end
     end
 
+    increment_voters_count! unless already_voted
+
     ActivityTracker.increment('activity:interactions')
 
     if @poll.account.local?
@@ -53,4 +65,18 @@ class VoteService < BaseService
   def build_json(vote)
     Oj.dump(serialize_payload(vote, ActivityPub::VoteSerializer))
   end
+
+  def increment_voters_count!
+    unless @poll.voters_count.nil?
+      @poll.voters_count = @poll.voters_count + 1
+      @poll.save
+    end
+  rescue ActiveRecord::StaleObjectError
+    @poll.reload
+    retry
+  end
+
+  def lock_options
+    { redis: Redis.current, key: "vote:#{@poll.id}:#{@account.id}" }
+  end
 end
diff --git a/app/validators/disallowed_hashtags_validator.rb b/app/validators/disallowed_hashtags_validator.rb
index ee06b20f6b49bd03f5f71a86daafa4c5dda3a748..d745b767f6d937c095416483b969f1c771304c92 100644
--- a/app/validators/disallowed_hashtags_validator.rb
+++ b/app/validators/disallowed_hashtags_validator.rb
@@ -4,24 +4,7 @@ class DisallowedHashtagsValidator < ActiveModel::Validator
   def validate(status)
     return unless status.local? && !status.reblog?
 
-    @status = status
-    tags    = select_tags
-
-    status.errors.add(:text, I18n.t('statuses.disallowed_hashtags', tags: tags.join(', '), count: tags.size)) unless tags.empty?
-  end
-
-  private
-
-  def select_tags
-    tags = Extractor.extract_hashtags(@status.text)
-    tags.keep_if { |tag| disallowed_hashtags.include? tag.downcase }
-  end
-
-  def disallowed_hashtags
-    return @disallowed_hashtags if @disallowed_hashtags
-
-    @disallowed_hashtags = Setting.disallowed_hashtags.nil? ? [] : Setting.disallowed_hashtags
-    @disallowed_hashtags = @disallowed_hashtags.split(' ') if @disallowed_hashtags.is_a? String
-    @disallowed_hashtags = @disallowed_hashtags.map(&:downcase)
+    disallowed_hashtags = Tag.matching_name(Extractor.extract_hashtags(status.text)).reject(&:usable?)
+    status.errors.add(:text, I18n.t('statuses.disallowed_hashtags', tags: disallowed_hashtags.map(&:name).join(', '), count: disallowed_hashtags.size)) unless disallowed_hashtags.empty?
   end
 end
diff --git a/app/validators/domain_validator.rb b/app/validators/domain_validator.rb
index ae07f17980b1c894e52b78025c8c1d86112d44c1..6e4a854ff25195f40966835932446f09eca575c3 100644
--- a/app/validators/domain_validator.rb
+++ b/app/validators/domain_validator.rb
@@ -4,14 +4,22 @@ class DomainValidator < ActiveModel::EachValidator
   def validate_each(record, attribute, value)
     return if value.blank?
 
-    record.errors.add(attribute, I18n.t('domain_validator.invalid_domain')) unless compliant?(value)
+    domain = begin
+      if options[:acct]
+        value.split('@').last
+      else
+        value
+      end
+    end
+
+    record.errors.add(attribute, I18n.t('domain_validator.invalid_domain')) unless compliant?(domain)
   end
 
   private
 
   def compliant?(value)
     Addressable::URI.new.tap { |uri| uri.host = value }
-  rescue Addressable::URI::InvalidURIError
+  rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
     false
   end
 end
diff --git a/app/validators/email_mx_validator.rb b/app/validators/email_mx_validator.rb
index 96fbedcfcf9b813dccba10d44238a3b1f6226ddd..9b5009966833ac391ce42e631e836aefd7e43525 100644
--- a/app/validators/email_mx_validator.rb
+++ b/app/validators/email_mx_validator.rb
@@ -14,6 +14,7 @@ class EmailMxValidator < ActiveModel::Validator
 
     return true if domain.nil?
 
+    domain    = TagManager.instance.normalize_domain(domain)
     hostnames = []
     ips       = []
 
@@ -29,6 +30,8 @@ class EmailMxValidator < ActiveModel::Validator
     end
 
     ips.empty? || on_blacklist?(hostnames + ips)
+  rescue Addressable::URI::InvalidURIError
+    true
   end
 
   def on_blacklist?(values)
diff --git a/app/validators/unique_username_validator.rb b/app/validators/unique_username_validator.rb
index fb67105dd6ba6e3116b24b57e2884aa2e62fb3ca..4e24e3f5fe90f4c9224be1ce9505c457bf5d7b38 100644
--- a/app/validators/unique_username_validator.rb
+++ b/app/validators/unique_username_validator.rb
@@ -1,10 +1,12 @@
 # frozen_string_literal: true
 
+# See also: USERNAME_RE in the Account class
+
 class UniqueUsernameValidator < ActiveModel::Validator
   def validate(account)
     return if account.username.nil?
 
-    normalized_username = account.username.downcase.delete('.')
+    normalized_username = account.username.downcase
 
     scope = Account.where(domain: nil).where('lower(username) = ?', normalized_username)
     scope = scope.where.not(id: account.id) if account.persisted?
diff --git a/app/views/about/_domain_blocks.html.haml b/app/views/about/_domain_blocks.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..e0c5df41d632aa82219911c14db70af38e5be4f3
--- /dev/null
+++ b/app/views/about/_domain_blocks.html.haml
@@ -0,0 +1,12 @@
+%table
+  %thead
+    %tr
+      %th= t('about.unavailable_content_description.domain')
+      %th= t('about.unavailable_content_description.reason')
+  %tbody
+    - domain_blocks.each do |domain_block|
+      %tr
+        %td.nowrap
+          %span{ title: domain_block.domain }= domain_block.domain
+        %td
+          = domain_block.public_comment if display_blocks_rationale?
diff --git a/app/views/about/_login.html.haml b/app/views/about/_login.html.haml
index d286f0d3c6e2411c81759802373e7cc9fceff6e3..fa58f04d7369ae0d67cd31be2bc2c1878dfb9235 100644
--- a/app/views/about/_login.html.haml
+++ b/app/views/about/_login.html.haml
@@ -1,4 +1,4 @@
-= simple_form_for(new_user, url: user_session_path) do |f|
+= simple_form_for(new_user, url: user_session_path, namespace: 'login') do |f|
   .fields-group
     - if use_seamless_external_login?
       = f.input :email, placeholder: t('simple_form.labels.defaults.username_or_email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.username_or_email') }, hint: false
diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml
index ff32ec8c46caec1f8bb8cf6c541daea6de49c7b3..1333c68c4f1c7aaa1eb6ea07b536f4cbf94d79ef 100644
--- a/app/views/about/_registration.html.haml
+++ b/app/views/about/_registration.html.haml
@@ -1,4 +1,4 @@
-= simple_form_for(new_user, url: user_registration_path) do |f|
+= simple_form_for(new_user, url: user_registration_path, namespace: 'registration') do |f|
   .simple_form__overlay-area
     %p.lead= t('about.federation_hint_html', instance: content_tag(:strong, site_hostname))
 
diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml
index b248ed1d2300c28f4df6ed9059cb16217b7847b6..7e156db61b636765da490232c17c7b2694a73328 100644
--- a/app/views/about/more.html.haml
+++ b/app/views/about/more.html.haml
@@ -5,7 +5,7 @@
   = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
   = render partial: 'shared/og'
 
-.grid-3
+.grid-4
   .column-0
     .public-account-header.public-account-header--no-bar
       .public-account-header__image
@@ -28,20 +28,53 @@
             = image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'), alt: ''
 
   .column-2
-    .landing-page__information.contact-widget
-      %p
-        %strong= t 'about.administered_by'
+    .contact-widget
+      %h4= t 'about.administered_by'
 
       = account_link_to(@instance_presenter.contact_account)
 
       - if @instance_presenter.site_contact_email.present?
-        %p.contact-widget__mail
-          %strong
-            = succeed ':' do
-              = t 'about.contact'
-          %br/
-          = mail_to @instance_presenter.site_contact_email, nil, title: @instance_presenter.site_contact_email
+        %h4
+          = succeed ':' do
+            = t 'about.contact'
+
+        = mail_to @instance_presenter.site_contact_email, nil, title: @instance_presenter.site_contact_email
 
   .column-3
-    .box-widget
-      .rich-formatting= @instance_presenter.site_extended_description.html_safe.presence || t('about.extended_description_html')
+    = render 'application/flashes'
+
+    - if @contents.blank? && (!display_blocks? || @blocks&.empty?)
+      = nothing_here
+    - else
+      .box-widget
+        .rich-formatting
+          = @contents.html_safe
+
+          - if display_blocks? && !@blocks.empty?
+            %h2#unavailable-content= t('about.unavailable_content')
+
+            %p= t('about.unavailable_content_html')
+
+            - if (blocks = @blocks.select(&:reject_media?)) && !blocks.empty?
+              %p= t('about.unavailable_content_description.rejecting_media')
+              = render partial: 'domain_blocks', locals: { domain_blocks: blocks }
+            - if (blocks = @blocks.select(&:silence?)) && !blocks.empty?
+              %p= t('about.unavailable_content_description.silenced')
+              = render partial: 'domain_blocks', locals: { domain_blocks: blocks }
+            - if (blocks = @blocks.select(&:suspend?)) && !blocks.empty?
+              %p= t('about.unavailable_content_description.suspended')
+              = render partial: 'domain_blocks', locals: { domain_blocks: blocks }
+
+  .column-4
+    %ul.table-of-contents
+      - @table_of_contents.each do |item|
+        %li
+          = link_to item.title, "##{item.anchor}"
+
+          - unless item.children.empty?
+            %ul
+              - item.children.each do |sub_item|
+                %li= link_to sub_item.title, "##{sub_item.anchor}"
+
+      - if display_blocks? && !@blocks.empty?
+        %li= link_to t('about.unavailable_content'), '#unavailable-content'
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index f24f4e1957d91d5f8430e1f11e7dc8cab2c5509c..80f4cd8287279a9b236b27a1ecf0e4b2a90ba51b 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -52,13 +52,12 @@
         .hero-widget__img
           = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
 
-        - if @instance_presenter.site_short_description.present?
-          .hero-widget__text
-            %p
-              = @instance_presenter.site_short_description.html_safe.presence
-              = link_to about_more_path do
-                = t('about.learn_more')
-                = fa_icon 'angle-double-right'
+        .hero-widget__text
+          %p
+            = @instance_presenter.site_short_description.html_safe.presence || t('about.about_mastodon_html')
+            = link_to about_more_path do
+              = t('about.learn_more')
+              = fa_icon 'angle-double-right'
 
         .hero-widget__footer
           .hero-widget__footer__column
diff --git a/app/views/accounts/_moved.html.haml b/app/views/accounts/_moved.html.haml
index 7a777bfea84f1c9e1644e4dd198eeaf39d2e502c..02fd7bf429d7f0c152a79c9ad84c88aa3d3db51f 100644
--- a/app/views/accounts/_moved.html.haml
+++ b/app/views/accounts/_moved.html.haml
@@ -3,10 +3,10 @@
 .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)])), 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.acct)])), ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'mention'))
 
   .moved-account-widget__card
-    = link_to TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener' do
+    = link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener' do
       .detailed-status__display-avatar
         .account__avatar-overlay
           .account__avatar-overlay-base{ style: "background-image: url('#{moved_to_account.avatar.url(:original)}')" }
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index 8f8647536b1969ce81b8580efc92ae35a351e77f..9c26dbabce7f093009c8b59e10b2d154c2eb4955 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -7,9 +7,7 @@
   - if @account.user&.setting_noindex
     %meta{ name: 'robots', content: 'noindex, noarchive' }/
 
-  %link{ rel: 'salmon', href: api_salmon_url(@account.id) }/
-  %link{ rel: 'alternate', type: 'application/atom+xml', href: account_url(@account, format: 'atom') }/
-  %link{ rel: 'alternate', type: 'application/rss+xml', href: account_url(@account, format: 'rss') }/
+  %link{ rel: 'alternate', type: 'application/rss+xml', href: @rss_url }/
   %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/
 
   - if @older_url
@@ -40,12 +38,12 @@
       - else
         .activity-stream.activity-stream--under-tabs
           - if params[:page].to_i.zero?
-            = render partial: 'stream_entries/status', collection: @pinned_statuses, as: :status, locals: { pinned: true }
+            = render partial: 'statuses/status', collection: @pinned_statuses, as: :status, locals: { pinned: true }
 
           - if @newer_url
             .entry= link_to_more @newer_url
 
-          = render partial: 'stream_entries/status', collection: @statuses, as: :status
+          = render partial: 'statuses/status', collection: @statuses, as: :status
 
           - if @older_url
             .entry= link_to_more @older_url
@@ -58,24 +56,33 @@
 
     = render 'bio', account: @account
 
-    - unless @endorsed_accounts.empty?
+    - if @endorsed_accounts.empty? && @account.id == current_account&.id
+      .placeholder-widget= t('accounts.endorsements_hint')
+    - elsif !@endorsed_accounts.empty?
       .endorsements-widget
         %h4= t 'accounts.choices_html', name: content_tag(:bdi, display_name(@account, custom_emojify: true))
 
         - @endorsed_accounts.each do |account|
           = account_link_to account
 
-    - @account.featured_tags.order(statuses_count: :desc).each do |featured_tag|
-      .directory__tag{ class: params[:tag] == featured_tag.name ? 'active' : nil }
-        = link_to short_account_tag_path(@account, featured_tag.tag) do
-          %h4
-            = fa_icon 'hashtag'
-            = featured_tag.name
-            %small
-              - if featured_tag.last_status_at.nil?
-                = t('accounts.nothing_here')
-              - else
-                %time.formatted{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at
-          .trends__item__current= number_to_human featured_tag.statuses_count, strip_insignificant_zeros: true
+    - if @featured_hashtags.empty? && @account.id == current_account&.id
+      .placeholder-widget
+        = t('accounts.featured_tags_hint')
+        = link_to settings_featured_tags_path do
+          = t('featured_tags.add_new')
+          = fa_icon 'chevron-right fw'
+    - else
+      - @featured_hashtags.each do |featured_tag|
+        .directory__tag{ class: params[:tag] == featured_tag.name ? 'active' : nil }
+          = link_to short_account_tag_path(@account, featured_tag.tag) do
+            %h4
+              = fa_icon 'hashtag'
+              = featured_tag.name
+              %small
+                - if featured_tag.last_status_at.nil?
+                  = t('accounts.nothing_here')
+                - else
+                  %time.formatted{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at
+            .trends__item__current= number_to_human featured_tag.statuses_count, strip_insignificant_zeros: true
 
     = render 'application/sidebar'
diff --git a/app/views/admin/account_actions/new.html.haml b/app/views/admin/account_actions/new.html.haml
index 97286c8e5b5ef1edd35d7b6ddd14a450d24dce71..20fbeef335bebb9fb5217aeea2ad4ec5ab3bb2f6 100644
--- a/app/views/admin/account_actions/new.html.haml
+++ b/app/views/admin/account_actions/new.html.haml
@@ -13,6 +13,10 @@
     .fields-group
       = f.input :send_email_notification, as: :boolean, wrapper: :with_label
 
+    - if params[:report_id].present?
+      .fields-group
+        = f.input :include_statuses, as: :boolean, wrapper: :with_label
+
     %hr.spacer/
 
     - unless @warning_presets.empty?
diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml
index eba3ad804123bbd56363a8e980473aec022b811f..b057d3e42e0e327b6fbf538fb747dcf2eeb3de87 100644
--- a/app/views/admin/accounts/_account.html.haml
+++ b/app/views/admin/accounts/_account.html.haml
@@ -19,4 +19,4 @@
       = table_link_to 'times', t('admin.accounts.reject'), reject_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:reject, account.user)
     - else
       = table_link_to 'circle', t('admin.accounts.web'), web_path("accounts/#{account.id}")
-      = table_link_to 'globe', t('admin.accounts.public'), TagManager.instance.url_for(account)
+      = table_link_to 'globe', t('admin.accounts.public'), ActivityPub::TagManager.instance.url_for(account)
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index 7494c9fa2fd6d3a3be53f6e066e1583311f70054..40a936e86933babae3a98d24d46a63c7fcd03e67 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -3,6 +3,34 @@
 
 = render 'application/card', account: @account
 
+- account = @account
+- proofs = account.identity_proofs.active
+- fields = account.fields
+- unless fields.empty? && proofs.empty? && account.note.blank?
+  .admin-account-bio
+    - unless fields.empty? && proofs.empty?
+      %div
+        .account__header__fields
+          - proofs.each do |proof|
+            %dl
+              %dt= proof.provider.capitalize
+              %dd.verified
+                = link_to fa_icon('check'), proof.badge.proof_url, class: 'verified__mark', title: t('accounts.link_verified_on', date: l(proof.updated_at))
+                = link_to proof.provider_username, proof.badge.profile_url
+
+          - fields.each do |field|
+            %dl
+              %dt.emojify{ title: field.name }= Formatter.instance.format_field(account, field.name, custom_emojify: true)
+              %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)
+
+      - if account.note.present?
+        %div
+          .account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
+
 .dashboard__counters{ style: 'margin-top: 10px' }
   %div
     = link_to admin_account_statuses_path(@account.id) do
@@ -174,7 +202,7 @@
 
       - unless @account.local?
         - if DomainBlock.where(domain: @account.domain).exists?
-          = link_to t('admin.domain_blocks.undo'), admin_instance_path(@account.domain), class: 'button'
+          = link_to t('admin.domain_blocks.view'), admin_instance_path(@account.domain), class: 'button'
         - else
           = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive'
 
diff --git a/app/views/admin/custom_emojis/_custom_emoji.html.haml b/app/views/admin/custom_emojis/_custom_emoji.html.haml
index fbaa9a1749066f8c6b79597fbd32a73cb36e13e4..526c844e925d90e972e0b2b903d83fcd8e635740 100644
--- a/app/views/admin/custom_emojis/_custom_emoji.html.haml
+++ b/app/views/admin/custom_emojis/_custom_emoji.html.haml
@@ -1,28 +1,35 @@
-%tr
-  %td
-    = custom_emoji_tag(custom_emoji)
-  %td
-    %samp= ":#{custom_emoji.shortcode}:"
-  %td
-    - if custom_emoji.local?
-      = t('admin.accounts.location.local')
-    - else
-      = link_to custom_emoji.domain, admin_custom_emojis_path(by_domain: custom_emoji.domain)
-  %td
-    - if custom_emoji.local?
-      - if custom_emoji.visible_in_picker
-        = table_link_to 'eye', t('admin.custom_emojis.listed'), admin_custom_emoji_path(custom_emoji, custom_emoji: { visible_in_picker: false }, page: params[:page], **@filter_params), method: :patch
+.batch-table__row
+  %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
+    = f.check_box :custom_emoji_ids, { multiple: true, include_hidden: false }, custom_emoji.id
+  .batch-table__row__content.batch-table__row__content--with-image
+    .batch-table__row__content__image
+      = custom_emoji_tag(custom_emoji, animate = current_account&.user&.setting_auto_play_gif)
+
+    .batch-table__row__content__text
+      %samp= ":#{custom_emoji.shortcode}:"
+
+      - if custom_emoji.local?
+        %span.account-role.bot= custom_emoji.category&.name || t('admin.custom_emojis.uncategorized')
+
+    .batch-table__row__content__extra
+      - if custom_emoji.local?
+        = t('admin.accounts.location.local')
       - else
-        = table_link_to 'eye-slash', t('admin.custom_emojis.unlisted'), admin_custom_emoji_path(custom_emoji, custom_emoji: { visible_in_picker: true }, page: params[:page], **@filter_params), method: :patch
-    - else
-      - if custom_emoji.local_counterpart.present?
-        = link_to safe_join([custom_emoji_tag(custom_emoji.local_counterpart), t('admin.custom_emojis.overwrite')]), copy_admin_custom_emoji_path(custom_emoji, page: params[:page], **@filter_params), method: :post, class: 'table-action-link'
+        = custom_emoji.domain
+
+        - if custom_emoji.local_counterpart.present?
+          &bull;
+          = t('admin.accounts.location.local')
+
+      %br/
+
+      - if custom_emoji.disabled?
+        = t('admin.custom_emojis.disabled')
       - else
-        = table_link_to 'copy', t('admin.custom_emojis.copy'), copy_admin_custom_emoji_path(custom_emoji, page: params[:page], **@filter_params), method: :post
-  %td
-    - if custom_emoji.disabled?
-      = table_link_to 'power-off', t('admin.custom_emojis.enable'), enable_admin_custom_emoji_path(custom_emoji, page: params[:page], **@filter_params), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }
-    - else
-      = table_link_to 'power-off', t('admin.custom_emojis.disable'), disable_admin_custom_emoji_path(custom_emoji, page: params[:page], **@filter_params), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }
-  %td
-    = table_link_to 'times', t('admin.custom_emojis.delete'), admin_custom_emoji_path(custom_emoji, page: params[:page], **@filter_params), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
+        = t('admin.custom_emojis.enabled')
+      - if custom_emoji.local?
+        &bull;
+        - if custom_emoji.visible_in_picker?
+          = t('admin.custom_emojis.listed')
+        - else
+          = t('admin.custom_emojis.unlisted')
diff --git a/app/views/admin/custom_emojis/index.html.haml b/app/views/admin/custom_emojis/index.html.haml
index 3a119276ca7e92110ddebbf2f2347e608eaba567..7320ce1bbda7db07c9b2203131aaaaf4acda1ce4 100644
--- a/app/views/admin/custom_emojis/index.html.haml
+++ b/app/views/admin/custom_emojis/index.html.haml
@@ -1,6 +1,9 @@
 - content_for :page_title do
   = t('admin.custom_emojis.title')
 
+- content_for :header_tags do
+  = javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
+
 .filters
   .filter-subset
     %strong= t('admin.accounts.location.title')
@@ -20,8 +23,7 @@
 = form_tag admin_custom_emojis_url, method: 'GET', class: 'simple_form' do
   .fields-group
     - Admin::FilterHelper::CUSTOM_EMOJI_FILTERS.each do |key|
-      - if params[key].present?
-        = hidden_field_tag key, params[key]
+      = hidden_field_tag key, params[key] if params[key].present?
 
     - %i(shortcode by_domain).each do |key|
       .input.string.optional
@@ -31,18 +33,54 @@
       %button= t('admin.accounts.search')
       = link_to t('admin.accounts.reset'), admin_custom_emojis_path, class: 'button negative'
 
-.table-wrapper
-  %table.table
-    %thead
-      %tr
-        %th= t('admin.custom_emojis.emoji')
-        %th= t('admin.custom_emojis.shortcode')
-        %th= t('admin.accounts.domain')
-        %th
-        %th
-        %th
-    %tbody
-      = render @custom_emojis
+= form_for(@form, url: batch_admin_custom_emojis_path) do |f|
+  = hidden_field_tag :page, params[:page] || 1
+
+  - Admin::FilterHelper::CUSTOM_EMOJI_FILTERS.each do |key|
+    = hidden_field_tag key, params[key] if params[key].present?
+
+  .batch-table
+    .batch-table__toolbar
+      %label.batch-table__toolbar__select.batch-checkbox-all
+        = check_box_tag :batch_checkbox_all, nil, false
+      .batch-table__toolbar__actions
+        - if params[:local] == '1'
+          = f.button safe_join([fa_icon('save'), t('generic.save_changes')]), name: :update, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
+          = f.button safe_join([fa_icon('eye'), t('admin.custom_emojis.list')]), name: :list, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
+          = f.button safe_join([fa_icon('eye-slash'), t('admin.custom_emojis.unlist')]), name: :unlist, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
+        = f.button safe_join([fa_icon('power-off'), t('admin.custom_emojis.enable')]), name: :enable, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
+        = f.button safe_join([fa_icon('power-off'), t('admin.custom_emojis.disable')]), name: :disable, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
+        = f.button safe_join([fa_icon('times'), t('admin.custom_emojis.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
+        - unless params[:local] == '1'
+          = f.button safe_join([fa_icon('copy'), t('admin.custom_emojis.copy')]), name: :copy, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
+    - if params[:local] == '1'
+      .batch-table__form.simple_form
+        .fields-row
+          .fields-group.fields-row__column.fields-row__column-6
+            .input.select.optional
+              .label_input
+                = f.select :category_id, options_from_collection_for_select(CustomEmojiCategory.all, 'id', 'name'), prompt: t('admin.custom_emojis.assign_category'), class: 'select optional', 'aria-label': t('admin.custom_emojis.assign_category')
+
+          .fields-group.fields-row__column.fields-row__column-6
+            .input.string.optional
+              .label_input
+                = f.text_field :category_name, class: 'string optional', placeholder: t('admin.custom_emojis.create_new_category'), 'aria-label': t('admin.custom_emojis.create_new_category')
+
+    .batch-table__body
+      - if @custom_emojis.empty?
+        = nothing_here 'nothing-here--under-tabs'
+      - else
+        = render partial: 'custom_emoji', collection: @custom_emojis, locals: { f: f }
 
 = paginate @custom_emojis
+
+%hr.spacer/
+
 = link_to t('admin.custom_emojis.upload'), new_admin_custom_emoji_path, class: 'button'
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index d448e386297e2e4492d438ccdccef0db41f291c3..2849f07aab8ce2b8b146fdb537a376983b8f86dd 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -4,27 +4,43 @@
 .dashboard__counters
   %div
     = link_to admin_accounts_url(local: 1, recent: 1) do
-      .dashboard__counters__num= number_with_delimiter @users_count
+      .dashboard__counters__num{ title: number_with_delimiter(@users_count, strip_insignificant_zeros: true) }
+        = number_to_human @users_count, strip_insignificant_zeros: true
       .dashboard__counters__label= t 'admin.dashboard.total_users'
   %div
     %div
-      .dashboard__counters__num= number_with_delimiter @registrations_week
+      .dashboard__counters__num{ title: number_with_delimiter(@registrations_week, strip_insignificant_zeros: true) }
+        = number_to_human @registrations_week, strip_insignificant_zeros: true
       .dashboard__counters__label= t 'admin.dashboard.week_users_new'
   %div
     %div
-      .dashboard__counters__num= number_with_delimiter @logins_week
+      .dashboard__counters__num{ title: number_with_delimiter(@logins_week, strip_insignificant_zeros: true) }
+        = number_to_human @logins_week, strip_insignificant_zeros: true
       .dashboard__counters__label= t 'admin.dashboard.week_users_active'
   %div
-    %div
-      .dashboard__counters__num= number_with_delimiter @interactions_week
-      .dashboard__counters__label= t 'admin.dashboard.week_interactions'
+    = link_to admin_pending_accounts_path do
+      .dashboard__counters__num{ title: number_with_delimiter(@pending_users_count, strip_insignificant_zeros: true) }
+        = number_to_human @pending_users_count, strip_insignificant_zeros: true
+      .dashboard__counters__label= t 'admin.dashboard.pending_users'
   %div
     = link_to admin_reports_url do
-      .dashboard__counters__num= number_with_delimiter @reports_count
+      .dashboard__counters__num{ title: number_with_delimiter(@reports_count, strip_insignificant_zeros: true) }
+        = number_to_human @reports_count, strip_insignificant_zeros: true
       .dashboard__counters__label= t 'admin.dashboard.open_reports'
+  %div
+    = link_to admin_tags_path(pending_review: '1') do
+      .dashboard__counters__num{ title: number_with_delimiter(@pending_tags_count, strip_insignificant_zeros: true) }
+        = number_to_human @pending_tags_count, strip_insignificant_zeros: true
+      .dashboard__counters__label= t 'admin.dashboard.pending_tags'
+  %div
+    %div
+      .dashboard__counters__num{ title: number_with_delimiter(@interactions_week, strip_insignificant_zeros: true) }
+        = number_to_human @interactions_week, strip_insignificant_zeros: true
+      .dashboard__counters__label= t 'admin.dashboard.week_interactions'
   %div
     = link_to sidekiq_url do
-      .dashboard__counters__num= number_with_delimiter @queue_backlog
+      .dashboard__counters__num{ title: number_with_delimiter(@queue_backlog, strip_insignificant_zeros: true) }
+        = number_to_human @queue_backlog, strip_insignificant_zeros: true
       .dashboard__counters__label= t 'admin.dashboard.backlog'
 
 .dashboard__widgets
@@ -49,8 +65,12 @@
           = feature_hint(link_to(t('admin.dashboard.feature_profile_directory'), edit_admin_settings_path), @profile_directory)
         %li
           = feature_hint(link_to(t('admin.dashboard.feature_timeline_preview'), edit_admin_settings_path), @timeline_preview)
+        %li
+          = feature_hint(link_to(t('admin.dashboard.trends'), edit_admin_settings_path), @trends_enabled)
         %li
           = feature_hint(link_to(t('admin.dashboard.feature_relay'), admin_relays_path), @relay_enabled)
+        %li
+          = feature_hint(link_to(t('admin.dashboard.feature_spam_check'), edit_admin_settings_path), @spam_check_enabled)
 
   .dashboard__widgets__versions
     %div
@@ -88,6 +108,10 @@
           = feature_hint(t('admin.dashboard.search'), @search_enabled)
         %li
           = feature_hint(t('admin.dashboard.single_user_mode'), @single_user_mode)
+        %li
+          = feature_hint(t('admin.dashboard.authorized_fetch_mode'), @authorized_fetch)
+        %li
+          = feature_hint(t('admin.dashboard.whitelist_mode'), @whitelist_enabled)
         %li
           = feature_hint('LDAP', @ldap_enabled)
         %li
@@ -105,5 +129,5 @@
       %ul
         - @trending_hashtags.each do |tag|
           %li
-            = link_to "##{tag.name}", web_url("timelines/tag/#{tag.name}")
+            = link_to content_tag(:span, "##{tag.name}", class: !tag.trendable? && !tag.reviewed? ? 'warning-hint' : (!tag.trendable? ? 'negative-hint' : nil)), admin_tag_path(tag.id)
             %span.pull-right= number_with_delimiter(tag.history[0][:accounts].to_i)
diff --git a/app/views/admin/domain_allows/new.html.haml b/app/views/admin/domain_allows/new.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..52599857aabc43b8a55733c84e6c0200a272059c
--- /dev/null
+++ b/app/views/admin/domain_allows/new.html.haml
@@ -0,0 +1,14 @@
+- content_for :header_tags do
+  = javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+  = t('admin.domain_allows.add_new')
+
+= simple_form_for @domain_allow, url: admin_domain_allows_path do |f|
+  = render 'shared/error_messages', object: @domain_allow
+
+  .fields-group
+    = f.input :domain, wrapper: :with_label, label: t('admin.domain_blocks.domain'), required: true
+
+  .actions
+    = f.button :button, t('admin.domain_allows.add_new'), type: :submit
diff --git a/app/views/admin/domain_blocks/edit.html.haml b/app/views/admin/domain_blocks/edit.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..29e47ef3bdd78af7919145bd00f498f13449752b
--- /dev/null
+++ b/app/views/admin/domain_blocks/edit.html.haml
@@ -0,0 +1,30 @@
+- content_for :header_tags do
+  = javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
+
+- content_for :page_title do
+  = t('admin.domain_blocks.edit')
+
+= simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :put do |f|
+  = render 'shared/error_messages', object: @domain_block
+
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :domain, wrapper: :with_label, label: t('admin.domain_blocks.domain'), hint: t('admin.domain_blocks.new.hint'), required: true, readonly: true, disabled: true
+
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |type| t("admin.domain_blocks.new.severity.#{type}") }, hint: t('admin.domain_blocks.new.severity.desc_html')
+
+  .fields-group
+    = f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint')
+
+  .fields-group
+    = f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint')
+
+  .field-group
+    = f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), rows: 6
+
+  .field-group
+    = f.input :public_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.public_comment'), hint: t('admin.domain_blocks.public_comment_hint'), rows: 6
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/domain_blocks/new.html.haml b/app/views/admin/domain_blocks/new.html.haml
index 055d2fbd7e44dc81cb490ccbb64d7ec4844b238a..ed1581936a8fac4e9b60df1c57a13e337e3f9592 100644
--- a/app/views/admin/domain_blocks/new.html.haml
+++ b/app/views/admin/domain_blocks/new.html.haml
@@ -20,5 +20,11 @@
   .fields-group
     = f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint')
 
+  .field-group
+    = f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), rows: 6
+
+  .field-group
+    = f.input :public_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.public_comment'), hint: t('admin.domain_blocks.public_comment_hint'), rows: 6
+
   .actions
     = f.button :button, t('.create'), type: :submit
diff --git a/app/views/admin/domain_blocks/show.html.haml b/app/views/admin/domain_blocks/show.html.haml
index dca4dbac777f9302dd0ebbba1a6b7fd1fe82b042..e64aaa6295f81308f8ffd74af01af5f5ae6af911 100644
--- a/app/views/admin/domain_blocks/show.html.haml
+++ b/app/views/admin/domain_blocks/show.html.haml
@@ -1,6 +1,18 @@
 - content_for :page_title do
   = t('admin.domain_blocks.show.title', domain: @domain_block.domain)
 
+- if @domain_block.private_comment.present?
+  .speech-bubble
+    .speech-bubble__bubble
+      = simple_format(h(@domain_block.private_comment))
+    .speech-bubble__owner= t 'admin.instances.private_comment'
+
+- if @domain_block.public_comment.present?
+  .speech-bubble
+    .speech-bubble__bubble
+      = simple_format(h(@domain_block.public_comment))
+    .speech-bubble__owner= t 'admin.instances.public_comment'
+
 = simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :delete do |f|
 
   - unless (@domain_block.noop?)
diff --git a/app/views/admin/instances/index.html.haml b/app/views/admin/instances/index.html.haml
index 61e578409a4ae0478e01fe1dab17f030677c4712..1d85aa75e018d5c08321c76e74987466930408dd 100644
--- a/app/views/admin/instances/index.html.haml
+++ b/app/views/admin/instances/index.html.haml
@@ -6,24 +6,30 @@
     %strong= t('admin.instances.moderation.title')
     %ul
       %li= filter_link_to t('admin.instances.moderation.all'), limited: nil
-      %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
+
+      - unless whitelist_mode?
+        %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
 
   %div{ style: 'flex: 1 1 auto; text-align: right' }
-    = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path, class: 'button'
+    - if whitelist_mode?
+      = link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button'
+    - else
+      = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path, class: 'button'
 
-= form_tag admin_instances_url, method: 'GET', class: 'simple_form' do
-  .fields-group
-    - Admin::FilterHelper::INSTANCES_FILTERS.each do |key|
-      - if params[key].present?
-        = hidden_field_tag key, params[key]
+- unless whitelist_mode?
+  = form_tag admin_instances_url, method: 'GET', class: 'simple_form' do
+    .fields-group
+      - Admin::FilterHelper::INSTANCES_FILTERS.each do |key|
+        - if params[key].present?
+          = hidden_field_tag key, params[key]
 
-    - %i(by_domain).each do |key|
-      .input.string.optional
-        = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.instances.#{key}")
+      - %i(by_domain).each do |key|
+        .input.string.optional
+          = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.instances.#{key}")
 
-    .actions
-      %button= t('admin.accounts.search')
-      = link_to t('admin.accounts.reset'), admin_instances_path, class: 'button negative'
+      .actions
+        %button= t('admin.accounts.search')
+        = link_to t('admin.accounts.reset'), admin_instances_path, class: 'button negative'
 
 %hr.spacer/
 
@@ -38,17 +44,21 @@
             - if !instance.domain_block.noop?
               = t("admin.domain_blocks.severity.#{instance.domain_block.severity}")
               - first_item = false
-            - if instance.domain_block.reject_media?
-              - unless first_item
-                &bull;
-              = t('admin.domain_blocks.rejecting_media')
-              - first_item = false
-            - if instance.domain_block.reject_reports?
-              - unless first_item
-                &bull;
-              = t('admin.domain_blocks.rejecting_reports')
+            - unless instance.domain_block.suspend?
+              - if instance.domain_block.reject_media?
+                - unless first_item
+                  &bull;
+                = t('admin.domain_blocks.rejecting_media')
+                - first_item = false
+              - if instance.domain_block.reject_reports?
+                - unless first_item
+                  &bull;
+                = t('admin.domain_blocks.rejecting_reports')
+          - elsif whitelist_mode?
+            = t('admin.accounts.whitelisted')
           - else
             = t('admin.accounts.no_limits_imposed')
       - if instance.countable?
         .trends__item__current{ title: t('admin.instances.known_accounts', count: instance.accounts_count) }= number_to_human instance.accounts_count, strip_insignificant_zeros: true
+
 = paginate paginated_instances
diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml
index c7992a490d09b576e8cc9bf595bfbc9cbdcfda86..294c9495db8096797329ed3866ada2f51b1e2f02 100644
--- a/app/views/admin/instances/show.html.haml
+++ b/app/views/admin/instances/show.html.haml
@@ -31,6 +31,18 @@
           = fa_icon 'times'
       .dashboard__counters__label= t 'admin.instances.delivery_available'
 
+- if @private_comment.present?
+  .speech-bubble
+    .speech-bubble__bubble
+      = simple_format(h(@private_comment))
+    .speech-bubble__owner= t 'admin.instances.private_comment'
+
+- if @public_comment.present?
+  .speech-bubble
+    .speech-bubble__bubble
+      = simple_format(h(@public_comment))
+    .speech-bubble__owner= t 'admin.instances.public_comment'
+
 %hr.spacer/
 
 %div{ style: 'overflow: hidden' }
@@ -38,7 +50,10 @@
     = link_to t('admin.accounts.title'), admin_accounts_path(remote: '1', by_domain: @instance.domain), class: 'button'
 
   %div{ style: 'float: right' }
-    - if @domain_block
+    - if @domain_allow
+      = link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
+    - elsif @domain_block
+      = link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@domain_block), class: 'button'
       = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@domain_block), class: 'button'
     - else
       = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'
diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml
index 9bb751a9c2bc0e616596faa84c56777b3a5433e1..6640f30e0fa661d3670f067ce58c01f3acc9deb4 100644
--- a/app/views/admin/reports/_status.html.haml
+++ b/app/views/admin/reports/_status.html.haml
@@ -16,11 +16,14 @@
         - video = status.proper.media_attachments.first
         = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: !current_account&.user&.show_all_media? && status.proper.sensitive? || current_account&.user&.hide_all_media?, width: 610, height: 343, inline: true, alt: video.description
       - else
-        = react_component :media_gallery, height: 343, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
+        = react_component :media_gallery, height: 343, sensitive: !current_account&.user&.show_all_media? && status.proper.sensitive? || current_account&.user&.hide_all_media?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
 
     .detailed-status__meta
-      = link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener' do
+      = link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener' do
         %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
+      - if status.discarded?
+        ·
+        %span.negative-hint= t('admin.statuses.deleted')
       ·
       - if status.reblog?
         = fa_icon('retweet fw')
diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml
index d73faccb0d9d971b6c4a8144bfdb95f9e9e0ae01..bfbd32108e468e725aa31fe052cb1248b51cce70 100644
--- a/app/views/admin/reports/index.html.haml
+++ b/app/views/admin/reports/index.html.haml
@@ -28,7 +28,9 @@
       - reports.each do |report|
         .report-card__summary__item
           .report-card__summary__item__reported-by
-            - if report.account.local?
+            - if report.account.instance_actor?
+              = site_hostname
+            - elsif report.account.local?
               = admin_account_link_to report.account
             - else
               = report.account.domain
diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml
index 863dada9e27c1e5acc9bbe5b462705e777e97087..bbe83c979a0fe88ab6f431054b24bee6ce0b599f 100644
--- a/app/views/admin/reports/show.html.haml
+++ b/app/views/admin/reports/show.html.haml
@@ -29,7 +29,9 @@
         %td= table_link_to 'file', pluralize(@report.target_account.targeted_moderation_notes.count, t('admin.reports.account.note')), admin_reports_path(target_account_id: @report.target_account.id)
       %tr
         %th= t('admin.reports.reported_by')
-        - if @report.account.local?
+        - if @report.account.instance_actor?
+          %td{ colspan: 3 }= site_hostname
+        - elsif @report.account.local?
           %td= admin_account_link_to @report.account
           %td= table_link_to 'flag', pluralize(@report.account.targeted_reports.count, t('admin.reports.account.report')), admin_reports_path(target_account_id: @report.account.id)
           %td= table_link_to 'file', pluralize(@report.account.targeted_moderation_notes.count, t('admin.reports.account.note')), admin_reports_path(target_account_id: @report.account.id)
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index a67e6a2c82300eb060defcfd6860a103a120e88c..6282bb39c0a1a9b736bc2800c5e88a5a9d564378 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -20,10 +20,10 @@
       = f.input :site_contact_email, wrapper: :with_label, label: t('admin.settings.contact_information.email')
 
   .fields-group
-    = f.input :site_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description.title'), hint: t('admin.settings.site_description.desc_html'), input_html: { rows: 4 }
+    = f.input :site_short_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_short_description.title'), hint: t('admin.settings.site_short_description.desc_html'), input_html: { rows: 2 }
 
   .fields-group
-    = f.input :site_short_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_short_description.title'), hint: t('admin.settings.site_short_description.desc_html'), input_html: { rows: 2 }
+    = f.input :site_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description.title'), hint: t('admin.settings.site_description.desc_html'), input_html: { rows: 2 }
 
   .fields-row
     .fields-row__column.fields-row__column-6.fields-group
@@ -42,11 +42,12 @@
 
   %hr.spacer/
 
-  .fields-group
-    = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html')
+  - unless whitelist_mode?
+    .fields-group
+      = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html')
 
-  .fields-group
-    = f.input :show_known_fediverse_at_about_page, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_known_fediverse_at_about_page.title'), hint: t('admin.settings.show_known_fediverse_at_about_page.desc_html')
+    .fields-group
+      = f.input :show_known_fediverse_at_about_page, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_known_fediverse_at_about_page.title'), hint: t('admin.settings.show_known_fediverse_at_about_page.desc_html')
 
   .fields-group
     = f.input :show_staff_badge, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_staff_badge.title'), hint: t('admin.settings.show_staff_badge.desc_html')
@@ -54,26 +55,45 @@
   .fields-group
     = f.input :open_deletion, as: :boolean, wrapper: :with_label, label: t('admin.settings.registrations.deletion.title'), hint: t('admin.settings.registrations.deletion.desc_html')
 
-  .fields-group
-    = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html')
+  - unless whitelist_mode?
+    .fields-group
+      = f.input :activity_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.activity_api_enabled.title'), hint: t('admin.settings.activity_api_enabled.desc_html')
 
-  .fields-group
-    = f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.peers_api_enabled.title'), hint: t('admin.settings.peers_api_enabled.desc_html')
+    .fields-group
+      = f.input :peers_api_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.peers_api_enabled.title'), hint: t('admin.settings.peers_api_enabled.desc_html')
 
-  .fields-group
-    = f.input :preview_sensitive_media, as: :boolean, wrapper: :with_label, label: t('admin.settings.preview_sensitive_media.title'), hint: t('admin.settings.preview_sensitive_media.desc_html')
+    .fields-group
+      = f.input :preview_sensitive_media, as: :boolean, wrapper: :with_label, label: t('admin.settings.preview_sensitive_media.title'), hint: t('admin.settings.preview_sensitive_media.desc_html')
+
+    .fields-group
+      = f.input :profile_directory, as: :boolean, wrapper: :with_label, label: t('admin.settings.profile_directory.title'), hint: t('admin.settings.profile_directory.desc_html')
+
+    .fields-group
+      = f.input :trends, as: :boolean, wrapper: :with_label, label: t('admin.settings.trends.title'), hint: t('admin.settings.trends.desc_html')
+
+    .fields-group
+      = 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 :noindex, as: :boolean, wrapper: :with_label, label: t('admin.settings.default_noindex.title'), hint: t('admin.settings.default_noindex.desc_html')
 
   .fields-group
-    = f.input :profile_directory, as: :boolean, wrapper: :with_label, label: t('admin.settings.profile_directory.title'), hint: t('admin.settings.profile_directory.desc_html')
+    = f.input :spam_check_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.spam_check_enabled.title'), hint: t('admin.settings.spam_check_enabled.desc_html')
 
   %hr.spacer/
 
   .fields-group
     = f.input :min_invite_role, wrapper: :with_label, collection: %i(disabled user moderator admin), label: t('admin.settings.registrations.min_invite_role.title'), label_method: lambda { |role| role == :disabled ? t('admin.settings.registrations.min_invite_role.disabled') : t("admin.accounts.roles.#{role}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
 
+  .fields-row
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :show_domain_blocks, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+    .fields-row__column.fields-row__column-6.fields-group
+      = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks_rationale.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
+
   .fields-group
+    = f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 } unless whitelist_mode?
     = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, label: t('admin.settings.registrations.closed_message.title'), hint: t('admin.settings.registrations.closed_message.desc_html'), input_html: { rows: 8 }
-    = f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 }
     = f.input :site_terms, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_terms.title'), hint: t('admin.settings.site_terms.desc_html'), input_html: { rows: 8 }
     = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }, label: t('admin.settings.custom_css.title'), hint: t('admin.settings.custom_css.desc_html')
 
diff --git a/app/views/admin/subscriptions/_subscription.html.haml b/app/views/admin/subscriptions/_subscription.html.haml
deleted file mode 100644
index 1dec8e39623463178140d5807311105542aaefc4..0000000000000000000000000000000000000000
--- a/app/views/admin/subscriptions/_subscription.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-%tr
-  %td
-    %samp= subscription.account.acct
-  %td
-    %samp= subscription.callback_url
-  %td
-    - if subscription.confirmed?
-      %i.fa.fa-check
-  %td{ style: "color: #{subscription.expired? ? 'red' : 'inherit'};" }
-    %time.time-ago{ datetime: subscription.expires_at.iso8601, title: l(subscription.expires_at) }
-      = precede subscription.expired? ? '-' : '' do
-        = time_ago_in_words(subscription.expires_at)
-  %td
-    - if subscription.last_successful_delivery_at?
-      %time.formatted{ datetime: subscription.last_successful_delivery_at.iso8601, title: l(subscription.last_successful_delivery_at) }
-        = l subscription.last_successful_delivery_at
-    - else
-      %i.fa.fa-times
diff --git a/app/views/admin/subscriptions/index.html.haml b/app/views/admin/subscriptions/index.html.haml
deleted file mode 100644
index 83704c8ee5bbff940f92baf48906af87b19a6ec8..0000000000000000000000000000000000000000
--- a/app/views/admin/subscriptions/index.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-- content_for :page_title do
-  = t('admin.subscriptions.title')
-
-.table-wrapper
-  %table.table
-    %thead
-      %tr
-        %th= t('admin.subscriptions.topic')
-        %th= t('admin.subscriptions.callback_url')
-        %th= t('admin.subscriptions.confirmed')
-        %th= t('admin.subscriptions.expires_in')
-        %th= t('admin.subscriptions.last_delivery')
-    %tbody
-      = render @subscriptions
-
-= paginate @subscriptions
diff --git a/app/views/admin/tags/_tag.html.haml b/app/views/admin/tags/_tag.html.haml
index 961b83f93c23c1b4f1d235e390d50eb284db3c44..670f3bc0598ddfc0cdf39f72bf38189def0fd099 100644
--- a/app/views/admin/tags/_tag.html.haml
+++ b/app/views/admin/tags/_tag.html.haml
@@ -1,12 +1,20 @@
-%tr
-  %td
-    = link_to explore_hashtag_path(tag) do
-      = fa_icon 'hashtag'
-      = tag.name
-  %td
-    = t('directories.people', count: tag.accounts_count)
-  %td
-    - if tag.hidden?
-      = table_link_to 'eye', t('admin.tags.unhide'), unhide_admin_tag_path(tag.id, **@filter_params), method: :post
-    - else
-      = table_link_to 'eye-slash', t('admin.tags.hide'), hide_admin_tag_path(tag.id, **@filter_params), method: :post
+.batch-table__row
+  %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
+    = f.check_box :tag_ids, { multiple: true, include_hidden: false }, tag.id
+
+  .directory__tag
+    = link_to admin_tag_path(tag.id) do
+      %h4
+        = fa_icon 'hashtag'
+        = tag.name
+
+        %small
+          = t('admin.tags.in_directory', count: tag.accounts_count)
+          &bull;
+          = t('admin.tags.unique_uses_today', count: tag.history.first[:accounts])
+
+          - if tag.trending?
+            = fa_icon 'fire fw'
+            = t('admin.tags.trending_right_now')
+
+      .trends__item__current= number_to_human tag.history.first[:uses], strip_insignificant_zeros: true
diff --git a/app/views/admin/tags/index.html.haml b/app/views/admin/tags/index.html.haml
index 4ba3958605d69e1d989ce05bc83417f824010d0a..8b1182dbb11f70954bc5ce03f07b6dd3bff5966e 100644
--- a/app/views/admin/tags/index.html.haml
+++ b/app/views/admin/tags/index.html.haml
@@ -1,19 +1,79 @@
 - content_for :page_title do
   = t('admin.tags.title')
 
+- content_for :header_tags do
+  = javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
+
 .filters
   .filter-subset
-    %strong= t('admin.reports.status')
+    %strong= t('admin.tags.context')
+    %ul
+      %li= filter_link_to t('generic.all'), directory: nil
+      %li= filter_link_to t('admin.tags.directory'), directory: '1'
+
+  .filter-subset
+    %strong= t('admin.tags.review')
+    %ul
+      %li= filter_link_to t('generic.all'), reviewed: nil, unreviewed: nil, pending_review: nil
+      %li= filter_link_to t('admin.tags.unreviewed'), unreviewed: '1', reviewed: nil, pending_review: nil
+      %li= filter_link_to t('admin.tags.reviewed'), reviewed: '1', unreviewed: nil, pending_review: nil
+      %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{Tag.pending_review.count})"], ' '), pending_review: '1', reviewed: nil, unreviewed: nil
+
+  .filter-subset
+    %strong= t('generic.order_by')
     %ul
-      %li= filter_link_to t('admin.tags.visible'), hidden: nil
-      %li= filter_link_to t('admin.tags.hidden'), hidden: '1'
-
-.table-wrapper
-  %table.table
-    %thead
-      %tr
-        %th= t('admin.tags.name')
-        %th= t('admin.tags.accounts')
-        %th
-    %tbody
-      = render @tags
+      %li= filter_link_to t('admin.tags.most_recent'), popular: nil, active: nil
+      %li= filter_link_to t('admin.tags.most_popular'), popular: '1', active: nil
+      %li= filter_link_to t('admin.tags.last_active'), active: '1', popular: nil
+
+= form_tag admin_tags_url, method: 'GET', class: 'simple_form' do
+  .fields-group
+    - Admin::FilterHelper::TAGS_FILTERS.each do |key|
+      = hidden_field_tag key, params[key] if params[key].present?
+
+    - %i(name).each do |key|
+      .input.string.optional
+        = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.tags.#{key}")
+
+    .actions
+      %button= t('admin.accounts.search')
+      = link_to t('admin.accounts.reset'), admin_tags_path, class: 'button negative'
+
+%hr.spacer/
+
+= form_for(@form, url: batch_admin_tags_path) do |f|
+  = hidden_field_tag :page, params[:page] || 1
+  = hidden_field_tag :name, params[:name] if params[:name].present?
+
+  - Admin::FilterHelper::TAGS_FILTERS.each do |key|
+    = hidden_field_tag key, params[key] if params[key].present?
+
+  .batch-table
+    .batch-table__toolbar
+      %label.batch-table__toolbar__select.batch-checkbox-all
+        = check_box_tag :batch_checkbox_all, nil, false
+      .batch-table__toolbar__actions
+        - if params[:pending_review] == '1'
+          = f.button safe_join([fa_icon('check'), t('admin.accounts.approve')]), name: :approve, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+
+          = f.button safe_join([fa_icon('times'), t('admin.accounts.reject')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+        - else
+          %span.neutral-hint= t('generic.no_batch_actions_available')
+
+    .batch-table__body
+      - if @tags.empty?
+        = nothing_here 'nothing-here--under-tabs'
+      - else
+        = render partial: 'tag', collection: @tags, locals: { f: f }
+
+= paginate @tags
+
+- if params[:pending_review] == '1'
+  %hr.spacer/
+
+  %div{ style: 'overflow: hidden' }
+    %div{ style: 'float: right' }
+      = link_to t('admin.accounts.reject_all'), reject_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
+
+    %div
+      = link_to t('admin.accounts.approve_all'), approve_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
diff --git a/app/views/admin/tags/show.html.haml b/app/views/admin/tags/show.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..5799e5973d9fbc095c4ae9637f9d973b654949fc
--- /dev/null
+++ b/app/views/admin/tags/show.html.haml
@@ -0,0 +1,52 @@
+- content_for :page_title do
+  = "##{@tag.name}"
+
+.dashboard__counters
+  %div
+    = link_to tag_url(@tag), target: '_blank', rel: 'noopener' do
+      .dashboard__counters__num= number_with_delimiter @accounts_today
+      .dashboard__counters__label= t 'admin.tags.accounts_today'
+  %div
+    %div
+      .dashboard__counters__num= number_with_delimiter @accounts_week
+      .dashboard__counters__label= t 'admin.tags.accounts_week'
+  %div
+    - if @tag.accounts_count > 0
+      = link_to explore_hashtag_path(@tag) do
+        .dashboard__counters__num= number_with_delimiter @tag.accounts_count
+        .dashboard__counters__label= t 'admin.tags.directory'
+    - else
+      %div
+        .dashboard__counters__num= number_with_delimiter @tag.accounts_count
+        .dashboard__counters__label= t 'admin.tags.directory'
+
+%hr.spacer/
+
+= simple_form_for @tag, url: admin_tag_path(@tag.id) do |f|
+  = render 'shared/error_messages', object: @tag
+
+  .fields-group
+    = f.input :name, wrapper: :with_block_label
+
+  .fields-group
+    = f.input :usable, as: :boolean, wrapper: :with_label
+    = f.input :trendable, as: :boolean, wrapper: :with_label, disabled: !Setting.trends
+    = f.input :listable, as: :boolean, wrapper: :with_label, disabled: !Setting.profile_directory
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
+
+%hr.spacer/
+
+%h3= t 'admin.tags.breakdown'
+
+.table-wrapper
+  %table.table
+    %tbody
+      - total = @usage_by_domain.sum(&:last).to_f
+
+      - @usage_by_domain.each do |(domain, count)|
+        %tr
+          %th= domain || site_hostname
+          %td= number_to_percentage((count / total) * 100, precision: 1)
+          %td= number_with_delimiter count
diff --git a/app/views/admin_mailer/new_trending_tag.text.erb b/app/views/admin_mailer/new_trending_tag.text.erb
new file mode 100644
index 0000000000000000000000000000000000000000..e4bfdc5912ebbcadeef0412000499b7ef351d80a
--- /dev/null
+++ b/app/views/admin_mailer/new_trending_tag.text.erb
@@ -0,0 +1,5 @@
+<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
+
+<%= raw t('admin_mailer.new_trending_tag.body', name: @tag.name) %>
+
+<%= raw t('application_mailer.view')%> <%= admin_tags_url(pending_review: '1') %>
diff --git a/app/views/application/_card.html.haml b/app/views/application/_card.html.haml
index e6059b03507aa0e7322474cbd973f263fe0ac59b..8719ce48444048f138ff9f762efbe7d2062c65b9 100644
--- a/app/views/application/_card.html.haml
+++ b/app/views/application/_card.html.haml
@@ -1,4 +1,4 @@
-- account_url = local_assigns[:admin] ? admin_account_path(account.id) : TagManager.instance.url_for(account)
+- account_url = local_assigns[:admin] ? admin_account_path(account.id) : ActivityPub::TagManager.instance.url_for(account)
 
 .card.h-card
   = link_to account_url, target: '_blank', rel: 'noopener' do
@@ -9,7 +9,7 @@
         = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo'
 
       .display-name
-        %span{id: "default_account_display_name", style: "display:none;"}= account.username
+        %span{ id: "default_account_display_name", style: "display: none" }= account.username
         %bdi
           %strong.emojify.p-name= display_name(account, custom_emojify: true)
         %span
diff --git a/app/views/application/_sidebar.html.haml b/app/views/application/_sidebar.html.haml
index b5ce5845e3eb884c990a0e9a31ee6c5920df7d8a..7ec91c06aa32cba5474c7744c1e969942e832c9c 100644
--- a/app/views/application/_sidebar.html.haml
+++ b/app/views/application/_sidebar.html.haml
@@ -3,4 +3,14 @@
     = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
 
   .hero-widget__text
-    %p= @instance_presenter.site_short_description.html_safe.presence || @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
+    %p= @instance_presenter.site_short_description.html_safe.presence || t('about.about_mastodon_html')
+
+- if Setting.trends && !(user_signed_in? && !current_user.setting_trends)
+  - trends = TrendingTags.get(3)
+
+  - unless trends.empty?
+    .endorsements-widget.trends-widget
+      %h4.emojify= t('footer.trending_now')
+
+      - trends.each do |tag|
+        = react_component :hashtag, hashtag: ActiveModelSerializers::SerializableResource.new(tag, serializer: REST::TagSerializer).as_json
diff --git a/app/views/auth/challenges/new.html.haml b/app/views/auth/challenges/new.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..9aef2c35d6e5015d317a8045fe226c500e6944ec
--- /dev/null
+++ b/app/views/auth/challenges/new.html.haml
@@ -0,0 +1,15 @@
+- content_for :page_title do
+  = t('challenge.prompt')
+
+= simple_form_for @challenge, url: request.get? ? auth_challenge_path : '' do |f|
+  = f.input :return_to, as: :hidden
+
+  .field-group
+    = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off', :autofocus => true }, label: t('challenge.prompt'), required: true
+
+  .actions
+    = f.button :button, t('challenge.confirm'), type: :submit
+
+  %p.hint.subtle-hint= t('challenge.hint_html')
+
+.form-footer= render 'auth/shared/links'
diff --git a/app/views/auth/confirmations/finish_signup.html.haml b/app/views/auth/confirmations/finish_signup.html.haml
deleted file mode 100644
index 9d09b74e1627515eed3641c78374041ddcdd36af..0000000000000000000000000000000000000000
--- a/app/views/auth/confirmations/finish_signup.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-- content_for :page_title do
-  = t('auth.confirm_email')
-
-= simple_form_for(current_user, as: 'user', url: finish_signup_path, html: { role: 'form'}) do |f|
-  - if @show_errors && current_user.errors.any?
-    #error_explanation
-      - current_user.errors.full_messages.each do |msg|
-        = msg
-        %br
-
-  .fields-group
-    = f.input :email, wrapper: :with_label, required: true, hint: false
-
-  .actions
-    = f.submit t('auth.confirm_email'), class: 'button'
diff --git a/app/views/auth/registrations/_sessions.html.haml b/app/views/auth/registrations/_sessions.html.haml
index d7d96a1bb3338617854e9063e5b88eb344142187..395e36a9fd6d07373a3e4ca01bad451bab6b31d5 100644
--- a/app/views/auth/registrations/_sessions.html.haml
+++ b/app/views/auth/registrations/_sessions.html.haml
@@ -1,6 +1,8 @@
-%h4= t 'sessions.title'
+%h3= t 'sessions.title'
 %p.muted-hint= t 'sessions.explanation'
 
+%hr.spacer/
+
 .table-wrapper
   %table.table.inline-table
     %thead
diff --git a/app/views/auth/registrations/_status.html.haml b/app/views/auth/registrations/_status.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..47112dae07cc6474206ff7cc65dbaa14e512b941
--- /dev/null
+++ b/app/views/auth/registrations/_status.html.haml
@@ -0,0 +1,22 @@
+%h3= t('auth.status.account_status')
+
+.simple_form
+  %p.hint
+    - if @user.account.suspended?
+      %span.negative-hint= t('user_mailer.warning.explanation.suspend')
+    - elsif @user.disabled?
+      %span.negative-hint= t('user_mailer.warning.explanation.disable')
+    - elsif @user.account.silenced?
+      %span.warning-hint= t('user_mailer.warning.explanation.silence')
+    - elsif !@user.confirmed?
+      %span.warning-hint= t('auth.status.confirming')
+      = link_to t('auth.didnt_get_confirmation'), new_user_confirmation_path
+    - elsif !@user.approved?
+      %span.warning-hint= t('auth.status.pending')
+    - elsif @user.account.moved_to_account_id.present?
+      %span.positive-hint= t('auth.status.redirecting_to', acct: @user.account.moved_to_account.acct)
+      = link_to t('migrations.cancel'), settings_migration_path
+    - else
+      %span.positive-hint= t('auth.status.functional')
+
+%hr.spacer/
diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml
index 694461fdf2ec4f71161201e20e010e08ca5bbece..a155c75c987e9c69d220e905a66c3d0093f97543 100644
--- a/app/views/auth/registrations/edit.html.haml
+++ b/app/views/auth/registrations/edit.html.haml
@@ -1,25 +1,28 @@
 - content_for :page_title do
-  = t('auth.security')
+  = t('settings.account_settings')
+
+= render 'status'
+
+%h3= t('auth.security')
 
 = simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit' }) do |f|
   = render 'shared/error_messages', object: resource
 
   - if !use_seamless_external_login? || resource.encrypted_password.present?
-    .fields-group
-      = f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, hint: false
-
-    .fields-group
-      = f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, required: true
-
-    .fields-group
-      = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }, hint: false
-
-    .fields-group
-      = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
-
+    .fields-row
+      .fields-row__column.fields-group.fields-row__column-6
+        = f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, disabled: current_account.suspended?
+      .fields-row__column.fields-group.fields-row__column-6
+        = f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, required: true, disabled: current_account.suspended?, hint: false
+
+    .fields-row
+      .fields-row__column.fields-group.fields-row__column-6
+        = f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended?
+      .fields-row__column.fields-group.fields-row__column-6
+        = f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }, disabled: current_account.suspended?
 
     .actions
-      = f.button :button, t('generic.save_changes'), type: :submit
+      = f.button :button, t('generic.save_changes'), type: :submit, class: 'button', disabled: current_account.suspended?
   - else
     %p.hint= t('users.seamless_external_login')
 
@@ -27,7 +30,18 @@
 
 = render 'sessions'
 
-- if open_deletion?
+%hr.spacer/
+
+%h3= t('auth.migrate_account')
+%p.muted-hint= t('auth.migrate_account_html', path: settings_migration_path)
+
+%hr.spacer/
+
+%h3= t('migrations.incoming_migrations')
+%p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path)
+
+- if open_deletion? && !current_account.suspended?
   %hr.spacer/
-  %h4= t('auth.delete_account')
+
+  %h3= t('auth.delete_account')
   %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)
diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml
index b4a7cced55c69d6fab5164908d0bbe6ae050bac8..e807c8d86764e31dcf3e118699c7488df843e755 100644
--- a/app/views/auth/registrations/new.html.haml
+++ b/app/views/auth/registrations/new.html.haml
@@ -2,7 +2,7 @@
   = t('auth.register')
 
 - content_for :header_tags do
-  = render partial: 'shared/og'
+  = render partial: 'shared/og', locals: { description: description_for_sign_up }
 
 = simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
   = render 'shared/error_messages', object: resource
@@ -33,7 +33,7 @@
   = f.input :invite_code, as: :hidden
 
   .fields-group
-    = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path)
+    = f.input :agreement, as: :boolean, wrapper: :with_label, label: whitelist_mode? ? t('auth.checkbox_agreement_without_rules_html', terms_path: terms_path) : t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path)
 
   .actions
     = f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit
diff --git a/app/views/auth/setup/show.html.haml b/app/views/auth/setup/show.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..c14fed56f8b1e5b837538875b71e765a12153846
--- /dev/null
+++ b/app/views/auth/setup/show.html.haml
@@ -0,0 +1,20 @@
+- content_for :page_title do
+  = t('auth.setup.title')
+
+- if missing_email?
+  = simple_form_for(@user, url: auth_setup_path) do |f|
+    = render 'shared/error_messages', object: @user
+
+    .fields-group
+      %p.hint= t('auth.setup.email_below_hint_html')
+
+    .fields-group
+      = f.input :email, required: true, hint: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }
+
+    .actions
+      = f.submit t('admin.accounts.change_email.label'), class: 'button'
+- else
+  .simple_form
+    %p.hint= t('auth.setup.email_settings_hint_html', email: content_tag(:strong, @user.email))
+
+.form-footer= render 'auth/shared/links'
diff --git a/app/views/auth/shared/_links.html.haml b/app/views/auth/shared/_links.html.haml
index 3c68ccd222086da4f0ffde12cfbc0a7a0fa5a718..66ed5b93f38e7e63535c01601e706288bc5bbe94 100644
--- a/app/views/auth/shared/_links.html.haml
+++ b/app/views/auth/shared/_links.html.haml
@@ -1,12 +1,18 @@
 %ul.no-list
-  - if controller_name != 'sessions'
-    %li= link_to t('auth.login'), new_session_path(resource_name)
+  - if user_signed_in?
+    %li= link_to t('settings.account_settings'), edit_user_registration_path
+  - else
+    - if controller_name != 'sessions'
+      %li= link_to t('auth.login'), new_user_session_path
 
-  - if devise_mapping.registerable? && controller_name != 'registrations'
-    %li= link_to t('auth.register'), available_sign_up_path
+    - if controller_name != 'registrations'
+      %li= link_to t('auth.register'), available_sign_up_path
 
-  - if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations'
-    %li= link_to t('auth.forgot_password'), new_password_path(resource_name)
+    - if controller_name != 'passwords' && controller_name != 'registrations'
+      %li= link_to t('auth.forgot_password'), new_user_password_path
 
-  - if devise_mapping.confirmable? && controller_name != 'confirmations'
-    %li= link_to t('auth.didnt_get_confirmation'), new_confirmation_path(resource_name)
+  - if controller_name != 'confirmations' && (!user_signed_in? || !current_user.confirmed? || current_user.unconfirmed_email.present?)
+    %li= link_to t('auth.didnt_get_confirmation'), new_user_confirmation_path
+
+  - if user_signed_in? && controller_name != 'setup'
+    %li= link_to t('auth.logout'), destroy_user_session_path, data: { method: :delete }
diff --git a/app/views/authorize_interactions/_post_follow_actions.html.haml b/app/views/authorize_interactions/_post_follow_actions.html.haml
index 561c60137fc4ac7a01f201499297fd73555b7fe7..dd71160e2d7c50d07922b28beee6384fd157a566 100644
--- a/app/views/authorize_interactions/_post_follow_actions.html.haml
+++ b/app/views/authorize_interactions/_post_follow_actions.html.haml
@@ -1,4 +1,4 @@
 .post-follow-actions
   %div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@resource.id}"), class: 'button button--block'
-  %div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@resource), class: 'button button--block'
+  %div= link_to t('authorize_follow.post_follow.return'), ActivityPub::TagManager.instance.url_for(@resource), class: 'button button--block'
   %div= t('authorize_follow.post_follow.close')
diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml
index a8aa68cc434e1e6c1d35fa25d0c03e9c276f5b2a..6bf2ec81ea922afab07ad45a08c7398308647a4b 100644
--- a/app/views/directories/index.html.haml
+++ b/app/views/directories/index.html.haml
@@ -14,58 +14,43 @@
   %h1= t('directories.explore_mastodon', title: site_title)
   %p= t('directories.explanation')
 
-.grid
-  .column-0
-    - if @accounts.empty?
-      = nothing_here
-    - else
-      .directory
-        %table.accounts-table
-          %tbody
-            - @accounts.each do |account|
-              %tr
-                %td= account_link_to account
-                %td.accounts-table__count.optional
-                  = number_to_human account.statuses_count, strip_insignificant_zeros: true
-                  %small= t('accounts.posts', count: account.statuses_count).downcase
-                %td.accounts-table__count.optional
-                  = number_to_human account.followers_count, strip_insignificant_zeros: true
-                  %small= t('accounts.followers', count: account.followers_count).downcase
-                %td.accounts-table__count
-                  - if account.last_status_at.present?
-                    %time.time-ago{ datetime: account.last_status_at.iso8601, title: l(account.last_status_at) }= l account.last_status_at
-                  - else
-                    \-
-                  %small= t('accounts.last_active')
-
-      = paginate @accounts
-
-  .column-1
-    - if user_signed_in?
-      .box-widget.notice-widget
-        - if current_account.discoverable?
-          - if current_account.followers_count < Account::MIN_FOLLOWERS_DISCOVERY
-            %p= t('directories.enabled_but_waiting', min_followers: Account::MIN_FOLLOWERS_DISCOVERY)
-          - else
-            %p= t('directories.enabled')
-        - else
-          %p= t('directories.how_to_enable')
-
-          = link_to settings_profile_path do
-            = t('settings.edit_profile')
-            = fa_icon 'chevron-right fw'
-
-    - if @tags.empty? && !user_signed_in?
-      .nothing-here
-    - else
-      - @tags.each do |tag|
-        .directory__tag{ class: tag.id == @tag&.id ? 'active' : nil }
-          = link_to explore_hashtag_path(tag) do
-            %h4
-              = fa_icon 'hashtag'
-              = tag.name
-              %small= t('directories.people', count: tag.accounts_count)
-
-            .avatar-stack
-              - tag.cached_sample_accounts.each do |account|
-                = image_tag current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url, width: 48, height: 48, alt: '', class: 'account__avatar'
+- if @accounts.empty?
+  = nothing_here
+- else
+  .directory__list
+    - @accounts.each do |account|
+      .directory__card
+        .directory__card__img
+          = image_tag account.header.url, alt: ''
+        .directory__card__bar
+          = link_to TagManager.instance.url_for(account), class: 'directory__card__bar__name' do
+            .avatar
+              = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo'
+
+            .display-name
+              %span{ id: "default_account_display_name", style: "display: none" }= account.username
+              %bdi
+                %strong.emojify.p-name= display_name(account, custom_emojify: true)
+              %span= acct(account)
+          .directory__card__bar__relationship.account__relationship
+            = minimal_account_action_button(account)
+
+        .directory__card__extra
+          .account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
+
+        .directory__card__extra
+          .accounts-table__count
+            = number_to_human account.statuses_count, strip_insignificant_zeros: true
+            %small= t('accounts.posts', count: account.statuses_count).downcase
+          .accounts-table__count
+            = number_to_human account.followers_count, strip_insignificant_zeros: true
+            %small= t('accounts.followers', count: account.followers_count).downcase
+          .accounts-table__count
+            - if account.last_status_at.present?
+              %time.time-ago{ datetime: account.last_status_at.iso8601, title: l(account.last_status_at) }= l account.last_status_at
+            - else
+              = t('accounts.never_active')
+
+            %small= t('accounts.last_active')
+
+  = paginate @accounts
diff --git a/app/views/errors/400.html.haml b/app/views/errors/400.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..11fbdd40cf104e33ca048a7c1aca9116e35c97a5
--- /dev/null
+++ b/app/views/errors/400.html.haml
@@ -0,0 +1,5 @@
+- content_for :page_title do
+  = t('errors.400')
+
+- content_for :content do
+  = t('errors.400')
diff --git a/app/views/errors/406.html.haml b/app/views/errors/406.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..0ef815df323b42bee8db3a53fb2afc1d473a02f6
--- /dev/null
+++ b/app/views/errors/406.html.haml
@@ -0,0 +1,5 @@
+- content_for :page_title do
+  = t('errors.406')
+
+- content_for :content do
+  = t('errors.406')
diff --git a/app/views/errors/503.html.haml b/app/views/errors/503.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..b0c895aa5d78cd98b165f3b32fcfc633a0849daf
--- /dev/null
+++ b/app/views/errors/503.html.haml
@@ -0,0 +1,5 @@
+- content_for :page_title do
+  = t('errors.503')
+
+- content_for :content do
+  = t('errors.503')
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
index 4c7fac0b6ae3f7f5afb034211facec0addd74c2d..30c7aab194f3aa6365f1839ebe5634cfbc184051 100644
--- a/app/views/home/index.html.haml
+++ b/app/views/home/index.html.haml
@@ -5,8 +5,7 @@
   = preload_link_tag asset_pack_path('features/notifications.js'), crossorigin: 'anonymous'
 
   %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
-  %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
-
+  = render_initial_state
   = javascript_pack_tag 'application', integrity: true, crossorigin: 'anonymous'
 
 .app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 083f2fac7644dd78f66538c4f30b7555f797f434..57bda45e23ac42a551fceb4d8200c0be2aab6701 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -4,11 +4,21 @@
 - content_for :content do
   .admin-wrapper
     .sidebar-wrapper
-      .sidebar
-        = link_to root_path do
-          = image_pack_tag 'logo.svg', class: 'logo', alt: 'Mastodon'
+      .sidebar-wrapper__inner
+        .sidebar
+          = link_to root_path do
+            = image_pack_tag 'logo.svg', class: 'logo', alt: 'Mastodon'
+
+          .sidebar__toggle
+            .sidebar__toggle__logo
+              = link_to root_path do
+                = svg_logo_full
+
+            = link_to '#', class: 'sidebar__toggle__icon' do
+              = fa_icon 'bars'
+
+          = render_navigation
 
-        = render_navigation
     .content-wrapper
       .content
         %h2= yield :page_title
@@ -17,4 +27,6 @@
 
         = yield
 
+    .sidebar-wrapper.sidebar-wrapper--empty
+
 = render template: 'layouts/application'
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
index 2929ac599cce8b7762c78d51c2bf873225659033..b9179e23d17c528ab0530800e64a14e500395d50 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -1,4 +1,5 @@
 - content_for :header_tags do
+  = render_initial_state
   = javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
 
 - content_for :content do
@@ -10,10 +11,13 @@
             = link_to root_url, class: 'brand' do
               = svg_logo_full
 
-            = link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory
-            = link_to t('about.about_this'), about_more_path, class: 'nav-link optional'
-            = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional'
+            - unless whitelist_mode?
+              = link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory
+              = link_to t('about.about_this'), about_more_path, class: 'nav-link optional'
+              = link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link optional'
+
           .nav-center
+
           .nav-right
             - if user_signed_in?
               = link_to t('settings.back'), root_url, class: 'nav-link nav-button webapp-btn'
diff --git a/app/views/notification_mailer/_status.html.haml b/app/views/notification_mailer/_status.html.haml
index 57b5688bd6fffec7a95de1ddce00df5fe1687a5c..e992e5563dc82a51a6ccddf6811fbee4fa143c8e 100644
--- a/app/views/notification_mailer/_status.html.haml
+++ b/app/views/notification_mailer/_status.html.haml
@@ -1,4 +1,5 @@
 - i ||= 0
+- highlighted ||= false
 
 %table.email-table{ cellspacing: 0, cellpadding: 0, dir: 'ltr' }
   %tbody
@@ -14,7 +15,7 @@
                       %table.column{ cellspacing: 0, cellpadding: 0 }
                         %tbody
                           %tr
-                            %td.column-cell.padded.status
+                            %td.column-cell.padded.status{ class: highlighted ? 'status--highlighted' : '' }
                               %table.status-header{ cellspacing: 0, cellpadding: 0 }
                                 %tbody
                                   %tr
@@ -32,5 +33,13 @@
                               %div{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }
                                 = Formatter.instance.format(status)
 
+                                - if status.media_attachments.size > 0
+                                  %p
+                                    - status.media_attachments.each do |a|
+                                      - if status.local?
+                                        = link_to medium_url(a), medium_url(a)
+                                      - else
+                                        = link_to a.remote_url, a.remote_url
+
                               %p.status-footer
                                 = link_to l(status.created_at), web_url("statuses/#{status.id}")
diff --git a/app/views/oauth/authorized_applications/index.html.haml b/app/views/oauth/authorized_applications/index.html.haml
index 19af5f55db28456bac549ecba8be2b5a249178f3..7203d758da4e90cfc3549538599da4fcc62455cd 100644
--- a/app/views/oauth/authorized_applications/index.html.haml
+++ b/app/views/oauth/authorized_applications/index.html.haml
@@ -17,7 +17,7 @@
               = application.name
             - else
               = link_to application.name, application.website, target: '_blank', rel: 'noopener'
-          %th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join('<br />')
+          %th!= application.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.join(', ')
           %td= l application.created_at
           %td
             - unless application.superapp?
diff --git a/app/views/public_timelines/show.html.haml b/app/views/public_timelines/show.html.haml
index 913d5d855d2eb2e57d1daa20315775c158c7910f..07215efdfdf389dab83c03d409fcac9e3e06223f 100644
--- a/app/views/public_timelines/show.html.haml
+++ b/app/views/public_timelines/show.html.haml
@@ -3,7 +3,6 @@
 
 - content_for :header_tags do
   %meta{ name: 'robots', content: 'noindex' }/
-  %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
   = javascript_pack_tag 'about', integrity: true, crossorigin: 'anonymous'
 
 .page-header
diff --git a/app/views/remote_interaction/new.html.haml b/app/views/remote_interaction/new.html.haml
index c8c08991f059b13075b0b7037b08b676447e3810..2cc0fcb93d6091c312eee8822003920d1a1e9f13 100644
--- a/app/views/remote_interaction/new.html.haml
+++ b/app/views/remote_interaction/new.html.haml
@@ -7,7 +7,7 @@
 
     .public-layout
       .activity-stream.activity-stream--highlighted
-        = render 'stream_entries/status', status: @status
+        = render 'statuses/status', status: @status
 
   = simple_form_for @remote_follow, as: :remote_follow, url: remote_interaction_path(@status) do |f|
     = render 'shared/error_messages', object: @remote_follow
diff --git a/app/views/remote_unfollows/_card.html.haml b/app/views/remote_unfollows/_card.html.haml
deleted file mode 100644
index 9abcfd37e153ed2f18f79f05f722b50f34fa5d9f..0000000000000000000000000000000000000000
--- a/app/views/remote_unfollows/_card.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-.account-card
-  .detailed-status__display-name
-    %div
-      = image_tag account.avatar.url(:original), alt: '', width: 48, height: 48, class: 'avatar'
-
-    %span.display-name
-      - account_url = local_assigns[:admin] ? admin_account_path(account.id) : TagManager.instance.url_for(account)
-      = link_to account_url, class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'noopener' do
-        %strong.emojify= display_name(account, custom_emojify: true)
-        %span @#{account.acct}
-
-  - if account.note?
-    .account__header__content.emojify= Formatter.instance.simplified_format(account)
diff --git a/app/views/remote_unfollows/_post_follow_actions.html.haml b/app/views/remote_unfollows/_post_follow_actions.html.haml
deleted file mode 100644
index 2a9c062e9c78e3a4975e390ddef01f6a5bf57bc8..0000000000000000000000000000000000000000
--- a/app/views/remote_unfollows/_post_follow_actions.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-.post-follow-actions
-  %div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@account.id}"), class: 'button button--block'
-  %div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@account), class: 'button button--block'
-  %div= t('authorize_follow.post_follow.close')
diff --git a/app/views/remote_unfollows/error.html.haml b/app/views/remote_unfollows/error.html.haml
deleted file mode 100644
index cb63f02be31a24c6d351cc12727539655857f528..0000000000000000000000000000000000000000
--- a/app/views/remote_unfollows/error.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-.form-container
-  .flash-message#error_explanation
-    = t('remote_unfollow.error')
diff --git a/app/views/remote_unfollows/success.html.haml b/app/views/remote_unfollows/success.html.haml
deleted file mode 100644
index b007eedc7e43c6f5de7295e8f3f904b08a5326ac..0000000000000000000000000000000000000000
--- a/app/views/remote_unfollows/success.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-- content_for :page_title do
-  = t('remote_unfollow.title', acct: @account.acct)
-
-.form-container
-  .follow-prompt
-    %h2= t('remote_unfollow.unfollowed')
-
-    = render 'application/card', account: @account
-
-  = render 'post_follow_actions'
diff --git a/app/views/settings/aliases/index.html.haml b/app/views/settings/aliases/index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..5b698636815f423e0aef794a491050ad03444626
--- /dev/null
+++ b/app/views/settings/aliases/index.html.haml
@@ -0,0 +1,29 @@
+- content_for :page_title do
+  = t('settings.aliases')
+
+= simple_form_for @alias, url: settings_aliases_path do |f|
+  = render 'shared/error_messages', object: @alias
+
+  %p.hint= t('aliases.hint_html')
+
+  %hr.spacer/
+
+  .fields-group
+    = f.input :acct, wrapper: :with_block_label, input_html: { autocapitalize: 'none', autocorrect: 'off' }
+
+  .actions
+    = f.button :button, t('aliases.add_new'), type: :submit, class: 'button'
+
+%hr.spacer/
+
+.table-wrapper
+  %table.table.inline-table
+    %thead
+      %tr
+        %th= t('simple_form.labels.account_alias.acct')
+        %th
+    %tbody
+      - @aliases.each do |account_alias|
+        %tr
+          %td= account_alias.acct
+          %td= table_link_to 'trash', t('aliases.remove'), settings_alias_path(account_alias), data: { method: :delete }
diff --git a/app/views/settings/deletes/show.html.haml b/app/views/settings/deletes/show.html.haml
index b246f83a16eb3bb8d4d02b228529f391318af4db..08792e0afdefc9c3c73b30fd706593f46914896a 100644
--- a/app/views/settings/deletes/show.html.haml
+++ b/app/views/settings/deletes/show.html.haml
@@ -2,15 +2,28 @@
   = t('settings.delete')
 
 = simple_form_for @confirmation, url: settings_delete_path, method: :delete do |f|
-  .warning
-    %strong
-      = fa_icon('warning')
-      = t('deletes.warning_title')
-    = t('deletes.warning_html')
+  %p.hint= t('deletes.warning.before')
 
-  %p.hint= t('deletes.description_html')
+  %ul.hint
+    - if current_user.confirmed? && current_user.approved?
+      %li.warning-hint= t('deletes.warning.irreversible')
+      %li.warning-hint= t('deletes.warning.username_unavailable')
+      %li.warning-hint= t('deletes.warning.data_removal')
+      %li.warning-hint= t('deletes.warning.caches')
+    - else
+      %li.positive-hint= t('deletes.warning.email_change_html', path: edit_user_registration_path)
+      %li.positive-hint= t('deletes.warning.email_reconfirmation_html', path: new_user_confirmation_path)
+      %li.positive-hint= t('deletes.warning.email_contact_html', email: Setting.site_contact_email)
+      %li.positive-hint= t('deletes.warning.username_available')
 
-  = f.input :password, placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }, hint: t('deletes.confirm_password')
+  %p.hint= t('deletes.warning.more_details_html', terms_path: terms_path)
+
+  %hr.spacer/
+
+  - if current_user.encrypted_password.present?
+    = f.input :password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, hint: t('deletes.confirm_password')
+  - else
+    = f.input :username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, hint: t('deletes.confirm_username')
 
   .actions
     = f.button :button, t('deletes.proceed'), type: :submit, class: 'negative'
diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml
index b13cea976a1de6e1a4b20a6418259f033dd0053c..76ff76bd9c6550c08f4aab80bbdd9bf8d0abbc19 100644
--- a/app/views/settings/exports/show.html.haml
+++ b/app/views/settings/exports/show.html.haml
@@ -37,12 +37,16 @@
         %td= number_with_delimiter @export.total_domain_blocks
         %td= table_link_to 'download', t('exports.csv'), settings_exports_domain_blocks_path(format: :csv)
 
+%hr.spacer/
+
 %p.muted-hint= t('exports.archive_takeout.hint_html')
 
 - if policy(:backup).create?
   %p= link_to t('exports.archive_takeout.request'), settings_export_path, class: 'button', method: :post
 
 - unless @backups.empty?
+  %hr.spacer/
+
   .table-wrapper
     %table.table
       %thead
diff --git a/app/views/settings/featured_tags/index.html.haml b/app/views/settings/featured_tags/index.html.haml
index 5f69517f3af8bb5525ea9a93143cd35e0bd60b1d..6734d027c5409dbb9bdb5e3766680ce527b4dcc6 100644
--- a/app/views/settings/featured_tags/index.html.haml
+++ b/app/views/settings/featured_tags/index.html.haml
@@ -1,6 +1,10 @@
 - content_for :page_title do
   = t('settings.featured_tags')
 
+%p= t('featured_tags.hint_html')
+
+%hr.spacer/
+
 = simple_form_for @featured_tag, url: settings_featured_tags_path do |f|
   = render 'shared/error_messages', object: @featured_tag
 
diff --git a/app/views/settings/migration/redirects/new.html.haml b/app/views/settings/migration/redirects/new.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..017450f4b94030b9418f3668d8f0a54452ae9789
--- /dev/null
+++ b/app/views/settings/migration/redirects/new.html.haml
@@ -0,0 +1,27 @@
+- content_for :page_title do
+  = t('settings.migrate')
+
+= simple_form_for @redirect, url: settings_migration_redirect_path do |f|
+  %p.hint= t('migrations.warning.before')
+
+  %ul.hint
+    %li.warning-hint= t('migrations.warning.redirect')
+    %li.warning-hint= t('migrations.warning.other_data')
+    %li.warning-hint= t('migrations.warning.disabled_account')
+
+  %hr.spacer/
+
+  = render 'shared/error_messages', object: @redirect
+
+  .fields-row
+    .fields-row__column.fields-group.fields-row__column-6
+      = f.input :acct, wrapper: :with_block_label, input_html: { autocapitalize: 'none', autocorrect: 'off' }, label: t('simple_form.labels.account_migration.acct'), hint: t('simple_form.hints.account_migration.acct')
+
+    .fields-row__column.fields-group.fields-row__column-6
+      - if current_user.encrypted_password.present?
+        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true
+      - else
+        = f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true
+
+  .actions
+    = f.button :button, t('migrations.set_redirect'), type: :submit, class: 'button button--destructive'
diff --git a/app/views/settings/migrations/show.html.haml b/app/views/settings/migrations/show.html.haml
index c69061d50bcd023a2b0c82e7ad6e5f253b1db4c7..078eaebc6c18525b513f8e7d0a3f904d24e95ea8 100644
--- a/app/views/settings/migrations/show.html.haml
+++ b/app/views/settings/migrations/show.html.haml
@@ -1,17 +1,89 @@
 - content_for :page_title do
   = t('settings.migrate')
 
-= simple_form_for @migration, as: :migration, url: settings_migration_path, html: { method: :put } do |f|
-  - if @migration.account
-    %p.hint= t('migrations.currently_redirecting')
+.simple_form
+  - if current_account.moved_to_account.present?
+    .fields-row
+      .fields-row__column.fields-group.fields-row__column-6
+        = 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)
 
-    .fields-group
-      = render partial: 'application/card', locals: { account: @migration.account }
+        %p.hint= t('migrations.cancel_explanation')
+
+        %p.hint= link_to t('migrations.cancel'), settings_migration_redirect_path, data: { method: :delete }
+  - else
+    %p.hint
+      %span.positive-hint= t('migrations.not_redirecting')
+
+%hr.spacer/
+
+%h3= t('auth.migrate_account')
+
+= simple_form_for @migration, url: settings_migration_path do |f|
+  - if on_cooldown?
+    %p.hint
+      %span.warning-hint= t('migrations.on_cooldown', count: ((@cooldown.cooldown_at - Time.now.utc) / 1.day.seconds).ceil)
+  - else
+    %p.hint= t('migrations.warning.before')
+
+    %ul.hint
+      %li.warning-hint= t('migrations.warning.followers')
+      %li.warning-hint= t('migrations.warning.redirect')
+      %li.warning-hint= t('migrations.warning.other_data')
+      %li.warning-hint= t('migrations.warning.backreference_required')
+      %li.warning-hint= t('migrations.warning.cooldown')
+      %li.warning-hint= t('migrations.warning.disabled_account')
+
+  %p.hint= t('migrations.warning.only_redirect_html', path: new_settings_migration_redirect_path)
+
+  %hr.spacer/
 
   = render 'shared/error_messages', object: @migration
 
-  .fields-group
-    = f.input :acct, placeholder: t('migrations.acct')
+  .fields-row
+    .fields-row__column.fields-group.fields-row__column-6
+      = f.input :acct, wrapper: :with_block_label, input_html: { autocapitalize: 'none', autocorrect: 'off' }, disabled: on_cooldown?
+
+    .fields-row__column.fields-group.fields-row__column-6
+      - if current_user.encrypted_password.present?
+        = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true, disabled: on_cooldown?
+      - else
+        = f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true, disabled: on_cooldown?
 
   .actions
-    = f.button :button, t('migrations.proceed'), type: :submit, class: 'negative'
+    = f.button :button, t('migrations.proceed_with_move'), type: :submit, class: 'button button--destructive', disabled: on_cooldown?
+
+- unless @migrations.empty?
+  %hr.spacer/
+
+  %h3= t 'migrations.past_migrations'
+
+  %hr.spacer/
+
+  .table-wrapper
+    %table.table.inline-table
+      %thead
+        %tr
+          %th= t('migrations.acct')
+          %th= t('migrations.followers_count')
+          %th
+      %tbody
+        - @migrations.each do |migration|
+          %tr
+            %td
+              - if migration.target_account.present?
+                = compact_account_link_to migration.target_account
+              - else
+                = migration.acct
+
+            %td= number_with_delimiter migration.followers_count
+
+            %td
+              %time.time-ago{ datetime: migration.created_at.iso8601, title: l(migration.created_at) }= l(migration.created_at)
+
+%hr.spacer/
+
+%h3= t 'migrations.incoming_migrations'
+%p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path)
diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml
index 10f0092647a42a7b6cfee888e071110e59bc55fe..d6ee1933f86dbefcfaf96b35c8f86de725d18c45 100644
--- a/app/views/settings/preferences/appearance/show.html.haml
+++ b/app/views/settings/preferences/appearance/show.html.haml
@@ -17,11 +17,19 @@
 
   %h4= t 'appearance.animations_and_accessibility'
 
+  .fields-group
+    = f.input :setting_use_pending_items, as: :boolean, wrapper: :with_label
+
   .fields-group
     = f.input :setting_auto_play_gif, as: :boolean, wrapper: :with_label, recommended: true
     = f.input :setting_reduce_motion, as: :boolean, wrapper: :with_label
     = f.input :setting_system_font_ui, as: :boolean, wrapper: :with_label
 
+  %h4= t 'appearance.discovery'
+
+  .fields-group
+    = f.input :setting_trends, as: :boolean, wrapper: :with_label
+
   %h4= t 'appearance.confirmation_dialogs'
 
   .fields-group
@@ -34,6 +42,9 @@
   .fields-group
     = f.input :setting_display_media, collection: ['default', 'show_all', 'hide_all'],label_method: lambda { |item| t("simple_form.hints.defaults.setting_display_media_#{item}") }, hint: false, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', wrapper: :with_floating_label
 
+  .fields-group
+    = f.input :setting_use_blurhash, as: :boolean, wrapper: :with_label
+
   .fields-group
     = f.input :setting_expand_spoilers, as: :boolean, wrapper: :with_label
 
diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml
index acc646fc3482cd534bd4c93711df805f93af8fa1..f666ae4ff6b1551b2c45d25f0ea2a38a6325f8f3 100644
--- a/app/views/settings/preferences/notifications/show.html.haml
+++ b/app/views/settings/preferences/notifications/show.html.haml
@@ -15,6 +15,7 @@
       - if current_user.staff?
         = ff.input :report, as: :boolean, wrapper: :with_label
         = ff.input :pending_account, as: :boolean, wrapper: :with_label
+        = ff.input :trending_tag, as: :boolean, wrapper: :with_label
 
   .fields-group
     = f.simple_fields_for :notification_emails, hash_to_object(current_user.settings.notification_emails) do |ff|
diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml
index f8a8fddd311e0035cf96faa294ac41e343372ad5..6929f54f30dbdff6f30c5e277b8b0464790194d2 100644
--- a/app/views/settings/profiles/show.html.haml
+++ b/app/views/settings/profiles/show.html.haml
@@ -28,7 +28,7 @@
 
   - if Setting.profile_directory
     .fields-group
-      = f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable_html', min_followers: Account::MIN_FOLLOWERS_DISCOVERY, path: explore_path), recommended: true
+      = f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable'), recommended: true
 
   %hr.spacer/
 
@@ -60,6 +60,11 @@
 %h6= t('auth.migrate_account')
 %p.muted-hint= t('auth.migrate_account_html', path: settings_migration_path)
 
+%hr.spacer/
+
+%h6= t 'migrations.incoming_migrations'
+%p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path)
+
 - if open_deletion?
   %hr.spacer/
 
diff --git a/app/views/settings/two_factor_authentication/confirmations/new.html.haml b/app/views/settings/two_factor_authentication/confirmations/new.html.haml
index e641552991c0bb6ed494214c8482404f1b830f72..86cf1f695effc8880200268ec00c8b34a4c2bde2 100644
--- a/app/views/settings/two_factor_authentication/confirmations/new.html.haml
+++ b/app/views/settings/two_factor_authentication/confirmations/new.html.haml
@@ -12,7 +12,7 @@
       %samp.qr-alternative__code= current_user.otp_secret.scan(/.{4}/).join(' ')
 
   .fields-group
-    = f.input :code, wrapper: :with_label, hint: t('two_factor_authentication.code_hint'), label: t('simple_form.labels.defaults.otp_attempt'), input_html: { :autocomplete => 'off' }, required: true
+    = f.input :otp_attempt, wrapper: :with_label, hint: t('two_factor_authentication.code_hint'), label: t('simple_form.labels.defaults.otp_attempt'), input_html: { :autocomplete => 'off' }, required: true
 
   .actions
     = f.button :button, t('two_factor_authentication.enable'), type: :submit
diff --git a/app/views/settings/two_factor_authentications/show.html.haml b/app/views/settings/two_factor_authentications/show.html.haml
index 259bcd1ef33aeb5256d8d50ff810b5d2c6aa751b..f1eecd0002d5ee4216a6884462fc751e643051b0 100644
--- a/app/views/settings/two_factor_authentications/show.html.haml
+++ b/app/views/settings/two_factor_authentications/show.html.haml
@@ -2,33 +2,35 @@
   = t('settings.two_factor_authentication')
 
 - if current_user.otp_required_for_login
-  %p.positive-hint
-    = fa_icon 'check'
-    = ' '
-    = t 'two_factor_authentication.enabled'
+  %p.hint
+    %span.positive-hint
+      = fa_icon 'check'
+      = ' '
+      = t 'two_factor_authentication.enabled'
 
-  %hr/
+  %hr.spacer/
 
   = simple_form_for @confirmation, url: settings_two_factor_authentication_path, method: :delete do |f|
-    = f.input :code, wrapper: :with_label, hint: t('two_factor_authentication.code_hint'), label: t('simple_form.labels.defaults.otp_attempt'), input_html: { :autocomplete => 'off' }, required: true
+    .fields-group
+      = f.input :otp_attempt, wrapper: :with_block_label, hint: t('two_factor_authentication.code_hint'), label: t('simple_form.labels.defaults.otp_attempt'), input_html: { :autocomplete => 'off' }, required: true
 
     .actions
-      = f.button :button, t('two_factor_authentication.disable'), type: :submit
+      = f.button :button, t('two_factor_authentication.disable'), type: :submit, class: 'negative'
 
-  %hr/
+  %hr.spacer/
 
-  %h6= t('two_factor_authentication.recovery_codes')
-  %p.muted-hint
-    = t('two_factor_authentication.lost_recovery_codes')
-    = link_to t('two_factor_authentication.generate_recovery_codes'),
-      settings_two_factor_authentication_recovery_codes_path,
-      data: { method: :post }
+  %h3= t('two_factor_authentication.recovery_codes')
+  %p.muted-hint= t('two_factor_authentication.lost_recovery_codes')
+
+  %hr.spacer/
+
+  .simple_form
+    = link_to t('two_factor_authentication.generate_recovery_codes'), settings_two_factor_authentication_recovery_codes_path, data: { method: :post }, class: 'block-button'
 
 - else
   .simple_form
     %p.hint= t('two_factor_authentication.description_html')
 
-    = link_to t('two_factor_authentication.setup'),
-      settings_two_factor_authentication_path,
-      data: { method: :post },
-      class: 'block-button'
+    %hr.spacer/
+
+    = link_to t('two_factor_authentication.setup'), settings_two_factor_authentication_path, data: { method: :post }, class: 'block-button'
diff --git a/app/views/shared/_og.html.haml b/app/views/shared/_og.html.haml
index 67238fc8bf892d3f2709ccb3875410defa21fcd0..c8f12974e29dff9b18b1945be5007f7ef3bc9108 100644
--- a/app/views/shared/_og.html.haml
+++ b/app/views/shared/_og.html.haml
@@ -1,5 +1,5 @@
-- thumbnail = @instance_presenter.thumbnail
-- description = strip_tags(@instance_presenter.site_short_description.presence || @instance_presenter.site_description.presence || t('about.about_mastodon_html'))
+- thumbnail     = @instance_presenter.thumbnail
+- description ||= strip_tags(@instance_presenter.site_short_description.presence || t('about.about_mastodon_html'))
 
 %meta{ name: 'description', content: description }/
 
diff --git a/app/views/shares/show.html.haml b/app/views/shares/show.html.haml
index 44b6f145f68a639cf4bbee17e48005f5244a9e5b..f2f5479a79c4a6d9f4331d9f486e37d6ec189e10 100644
--- a/app/views/shares/show.html.haml
+++ b/app/views/shares/show.html.haml
@@ -1,5 +1,5 @@
 - content_for :header_tags do
-  %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
+  = render_initial_state
   = javascript_pack_tag 'share', integrity: true, crossorigin: 'anonymous'
 
 #mastodon-compose{ data: { props: Oj.dump(default_props) } }
diff --git a/app/views/stream_entries/_attachment_list.html.haml b/app/views/statuses/_attachment_list.html.haml
similarity index 100%
rename from app/views/stream_entries/_attachment_list.html.haml
rename to app/views/statuses/_attachment_list.html.haml
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml
similarity index 78%
rename from app/views/stream_entries/_detailed_status.html.haml
rename to app/views/statuses/_detailed_status.html.haml
index 78367d82bab1276dc5aa184b6c0324e5feeceb0c..e6ea6c2a7d42a3edb8989e5454bb34f6db2a4c53 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/statuses/_detailed_status.html.haml
@@ -1,6 +1,6 @@
 .detailed-status.detailed-status--flex
   .p-author.h-card
-    = link_to TagManager.instance.url_for(status.account), class: 'detailed-status__display-name u-url', target: stream_link_target, rel: 'noopener' do
+    = link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'detailed-status__display-name u-url', target: stream_link_target, rel: 'noopener' do
       .detailed-status__display-avatar
         - if current_account&.user&.setting_auto_play_gif || autoplay
           = image_tag status.account.avatar_original_url, width: 48, height: 48, alt: '', class: 'account__avatar u-photo'
@@ -20,27 +20,31 @@
       %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }<
         %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}&nbsp;
         %button.status__content__spoiler-link= t('statuses.show_more')
-    .e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }
+    .e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }
       = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
       - if status.preloadable_poll
         = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
-          = render partial: 'stream_entries/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay }
+          = render partial: 'statuses/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay }
 
   - if !status.media_attachments.empty?
-    - if status.media_attachments.first.audio_or_video?
+    - if status.media_attachments.first.video?
       - video = status.media_attachments.first
       = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 670, height: 380, detailed: true, inline: true, alt: video.description do
-        = render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
+        = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
+    - elsif status.media_attachments.first.audio?
+      - audio = status.media_attachments.first
+      = react_component :audio, src: audio.file.url(:original), height: 130, alt: audio.description, preload: true, duration: audio.file.meta.dig(:original, :duration) do
+        = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
     - else
       = react_component :media_gallery, height: 380, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif || autoplay, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
-        = render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
+        = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
   - elsif status.preview_card
     = react_component :card, 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json
 
   .detailed-status__meta
     %data.dt-published{ value: status.created_at.to_time.iso8601 }
 
-    = link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener' do
+    = link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener' do
       %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
     ·
     - if status.application && @account.user&.setting_show_application
diff --git a/app/views/stream_entries/_og_description.html.haml b/app/views/statuses/_og_description.html.haml
similarity index 100%
rename from app/views/stream_entries/_og_description.html.haml
rename to app/views/statuses/_og_description.html.haml
diff --git a/app/views/stream_entries/_og_image.html.haml b/app/views/statuses/_og_image.html.haml
similarity index 100%
rename from app/views/stream_entries/_og_image.html.haml
rename to app/views/statuses/_og_image.html.haml
diff --git a/app/views/stream_entries/_poll.html.haml b/app/views/statuses/_poll.html.haml
similarity index 53%
rename from app/views/stream_entries/_poll.html.haml
rename to app/views/statuses/_poll.html.haml
index ba34890dfee5e46b95940fa8e5a32599a80bd86f..d1aba6ef9cee1a7f9abe60342e56dba5631fa6e5 100644
--- a/app/views/stream_entries/_poll.html.haml
+++ b/app/views/statuses/_poll.html.haml
@@ -1,15 +1,20 @@
 - show_results = (user_signed_in? && poll.voted?(current_account)) || poll.expired?
+- own_votes = user_signed_in? ? poll.own_votes(current_account) : []
+- total_votes_count = poll.voters_count || poll.votes_count
 
 .poll
   %ul
-    - poll.loaded_options.each do |option|
+    - poll.loaded_options.each_with_index do |option, index|
       %li
         - if show_results
-          - percent = poll.votes_count > 0 ? 100 * option.votes_count / poll.votes_count : 0
+          - percent = total_votes_count > 0 ? 100 * option.votes_count / total_votes_count : 0
           %span.poll__chart{ style: "width: #{percent}%" }
 
           %label.poll__text><
-            %span.poll__number= percent.round
+            %span.poll__number><
+              - if own_votes.include?(index)
+                %i.poll__vote__mark.fa.fa-check
+              = percent.round
             = Formatter.instance.format_poll_option(status, option, autoplay: autoplay)
         - else
           %label.poll__text><
@@ -20,7 +25,10 @@
       %button.button.button-secondary{ disabled: true }
         = t('statuses.poll.vote')
 
-    %span= t('statuses.poll.total_votes', count: poll.votes_count)
+    - if poll.voters_count.nil?
+      %span= t('statuses.poll.total_votes', count: poll.votes_count)
+    - else
+      %span= t('statuses.poll.total_people', count: poll.voters_count)
 
     - unless poll.expires_at.nil?
       ·
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml
similarity index 74%
rename from app/views/stream_entries/_simple_status.html.haml
rename to app/views/statuses/_simple_status.html.haml
index 0381f8d781a861eddf71002093f4ec0a74126b05..9b89e30a92db5b6bf02865b01ea54ee81ea4242e 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/statuses/_simple_status.html.haml
@@ -1,11 +1,11 @@
 .status
   .status__info
-    = link_to TagManager.instance.url_for(status), class: 'status__relative-time u-url u-uid', target: stream_link_target, rel: 'noopener' do
+    = link_to ActivityPub::TagManager.instance.url_for(status), class: 'status__relative-time u-url u-uid', target: stream_link_target, rel: 'noopener' do
       %time.time-ago{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
     %data.dt-published{ value: status.created_at.to_time.iso8601 }
 
     .p-author.h-card
-      = link_to TagManager.instance.url_for(status.account), class: 'status__display-name u-url', target: stream_link_target, rel: 'noopener' do
+      = link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'status__display-name u-url', target: stream_link_target, rel: 'noopener' do
         .status__avatar
           %div
             - if current_account&.user&.setting_auto_play_gif || autoplay
@@ -24,20 +24,24 @@
       %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }<
         %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}&nbsp;
         %button.status__content__spoiler-link= t('statuses.show_more')
-    .e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }
+    .e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }
       = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
       - if status.preloadable_poll
         = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
-          = render partial: 'stream_entries/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay }
+          = render partial: 'statuses/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay }
 
   - if !status.media_attachments.empty?
-    - if status.media_attachments.first.audio_or_video?
+    - if status.media_attachments.first.video?
       - video = status.media_attachments.first
       = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 610, height: 343, inline: true, alt: video.description do
-        = render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
+        = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
+    - elsif status.media_attachments.first.audio?
+      - audio = status.media_attachments.first
+      = react_component :audio, src: audio.file.url(:original), height: 110, alt: audio.description, duration: audio.file.meta.dig(:original, :duration) do
+        = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
     - else
       = react_component :media_gallery, height: 343, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif || autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
-        = render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
+        = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
   - elsif status.preview_card
     = react_component :card, 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json
 
@@ -50,9 +54,9 @@
           = fa_icon 'reply-all fw'
       .status__action-bar__counter__label= obscured_counter status.replies_count
     = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do
-      - if status.public_visibility? || status.unlisted_visibility?
+      - if status.distributable?
         = fa_icon 'retweet fw'
-      - elsif status.private_visibility?
+      - elsif status.private_visibility? || status.limited_visibility?
         = fa_icon 'lock fw'
       - else
         = fa_icon 'envelope fw'
diff --git a/app/views/stream_entries/_status.html.haml b/app/views/statuses/_status.html.haml
similarity index 74%
rename from app/views/stream_entries/_status.html.haml
rename to app/views/statuses/_status.html.haml
index 83887cd87437f7a73548a5e69062c5cd3038f5da..0e3652503067836203082446e3c3e125ee072f43 100644
--- a/app/views/stream_entries/_status.html.haml
+++ b/app/views/statuses/_status.html.haml
@@ -17,9 +17,9 @@
 - if status.reply? && include_threads
   - if @next_ancestor
     .entry{ class: entry_classes }
-      = link_to_more TagManager.instance.url_for(@next_ancestor)
+      = link_to_more ActivityPub::TagManager.instance.url_for(@next_ancestor)
 
-  = render partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id }, autoplay: autoplay
+  = render partial: 'statuses/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id }, autoplay: autoplay
 
 .entry{ class: entry_classes }
 
@@ -28,7 +28,7 @@
       .status__prepend-icon-wrapper
         %i.status__prepend-icon.fa.fa-fw.fa-retweet
       %span
-        = link_to TagManager.instance.url_for(status.account), class: 'status__display-name muted' do
+        = link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'status__display-name muted' do
           %bdi
             %strong.emojify= display_name(status.account, custom_emojify: true)
         = t('stream_entries.reblogged')
@@ -39,18 +39,18 @@
       %span
         = t('stream_entries.pinned')
 
-  = render (centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status'), status: status.proper, autoplay: autoplay
+  = render (centered ? 'statuses/detailed_status' : 'statuses/simple_status'), status: status.proper, autoplay: autoplay
 
 - if include_threads
   - if @since_descendant_thread_id
     .entry{ class: entry_classes }
       = link_to_more short_account_status_url(status.account.username, status, max_descendant_thread_id: @since_descendant_thread_id + 1)
   - @descendant_threads.each do |thread|
-    = render partial: 'stream_entries/status', collection: thread[:statuses], as: :status, locals: { is_successor: true, parent_id: status.id }, autoplay: autoplay
+    = render partial: 'statuses/status', collection: thread[:statuses], as: :status, locals: { is_successor: true, parent_id: status.id }, autoplay: autoplay
 
     - if thread[:next_status]
       .entry{ class: entry_classes }
-        = link_to_more TagManager.instance.url_for(thread[:next_status])
+        = link_to_more ActivityPub::TagManager.instance.url_for(thread[:next_status])
   - if @next_descendant_thread
     .entry{ class: entry_classes }
       = link_to_more short_account_status_url(status.account.username, status, since_descendant_thread_id: @max_descendant_thread_id - 1)
diff --git a/app/views/statuses/embed.html.haml b/app/views/statuses/embed.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..6f2ec646fe66704529189e126fcb338e17791094
--- /dev/null
+++ b/app/views/statuses/embed.html.haml
@@ -0,0 +1,3 @@
+- cache @status do
+  .activity-stream.activity-stream--headless
+    = render 'status', status: @status, centered: true, autoplay: @autoplay
diff --git a/app/views/stream_entries/embed.html.haml b/app/views/stream_entries/embed.html.haml
deleted file mode 100644
index 4871c101e1cbf38e2f6fc574d8850fdd3fab787e..0000000000000000000000000000000000000000
--- a/app/views/stream_entries/embed.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-- cache @stream_entry.activity do
-  .activity-stream.activity-stream--headless
-    = render "stream_entries/#{@type}", @type.to_sym => @stream_entry.activity, centered: true, autoplay: @autoplay
diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml
deleted file mode 100644
index 1a6567512493a6f6f678e9fbf2aba884c4aeba4d..0000000000000000000000000000000000000000
--- a/app/views/stream_entries/show.html.haml
+++ /dev/null
@@ -1,25 +0,0 @@
-- content_for :page_title do
-  = t('statuses.title', name: display_name(@account), quote: truncate(@stream_entry.activity.spoiler_text.presence || @stream_entry.activity.text, length: 50, omission: '…', escape: false))
-
-- content_for :header_tags do
-  - if @account.user&.setting_noindex
-    %meta{ name: 'robots', content: 'noindex, noarchive' }/
-
-  %link{ rel: 'alternate', type: 'application/atom+xml', href: account_stream_entry_url(@account, @stream_entry, format: 'atom') }/
-  %link{ rel: 'alternate', type: 'application/json+oembed', href: api_oembed_url(url: account_stream_entry_url(@account, @stream_entry), format: 'json') }/
-  %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@stream_entry.activity) }/
-
-  = opengraph 'og:site_name', site_title
-  = opengraph 'og:type', 'article'
-  = opengraph 'og:title', "#{display_name(@account)} (@#{@account.local_username_and_domain})"
-  = opengraph 'og:url', short_account_status_url(@account, @stream_entry.activity)
-
-  = render 'stream_entries/og_description', activity: @stream_entry.activity
-  = render 'stream_entries/og_image', activity: @stream_entry.activity, account: @account
-
-.grid
-  .column-0
-    .activity-stream.h-entry
-      = render partial: "stream_entries/#{@type}", locals: { @type.to_sym => @stream_entry.activity, include_threads: true }
-  .column-1
-    = render 'application/sidebar'
diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml
index cf42468224fa801c3c3e0b94092ef0b551d40ba9..6307022779422530707c1adef13dfcd3d130d169 100644
--- a/app/views/tags/show.html.haml
+++ b/app/views/tags/show.html.haml
@@ -5,7 +5,6 @@
   %meta{ name: 'robots', content: 'noindex' }/
   %link{ rel: 'alternate', type: 'application/rss+xml', href: tag_url(@tag, format: 'rss') }/
 
-  %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
   = javascript_pack_tag 'about', integrity: true, crossorigin: 'anonymous'
   = render 'og'
 
diff --git a/app/views/user_mailer/two_factor_disabled.html.haml b/app/views/user_mailer/two_factor_disabled.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..651c6f940e0dea9bb9da5f5b851652881149fd16
--- /dev/null
+++ b/app/views/user_mailer/two_factor_disabled.html.haml
@@ -0,0 +1,43 @@
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell.hero
+                  .email-row
+                    .col-6
+                      %table.column{ cellspacing: 0, cellpadding: 0 }
+                        %tbody
+                          %tr
+                            %td.column-cell.text-center.padded
+                              %table.hero-icon.alert-icon{ align: 'center', cellspacing: 0, cellpadding: 0 }
+                                %tbody
+                                  %tr
+                                    %td
+                                      = image_tag full_pack_url('media/images/mailer/icon_lock_open.png'), alt: ''
+
+                              %h1= t 'devise.mailer.two_factor_disabled.title'
+                              %p.lead= t 'devise.mailer.two_factor_disabled.explanation'
+
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell.content-start
+                  %table.column{ cellspacing: 0, cellpadding: 0 }
+                    %tbody
+                      %tr
+                        %td.column-cell.button-cell
+                          %table.button{ align: 'center', cellspacing: 0, cellpadding: 0 }
+                            %tbody
+                              %tr
+                                %td.button-primary
+                                  = link_to edit_user_registration_url do
+                                    %span= t('settings.account_settings')
diff --git a/app/views/user_mailer/two_factor_disabled.text.erb b/app/views/user_mailer/two_factor_disabled.text.erb
new file mode 100644
index 0000000000000000000000000000000000000000..73be1ddc2618d92fa06d9dba4ef968390328c5dc
--- /dev/null
+++ b/app/views/user_mailer/two_factor_disabled.text.erb
@@ -0,0 +1,7 @@
+<%= t 'devise.mailer.two_factor_disabled.title' %>
+
+===
+
+<%= t 'devise.mailer.two_factor_disabled.explanation' %>
+
+=> <%= edit_user_registration_url %>
diff --git a/app/views/user_mailer/two_factor_enabled.html.haml b/app/views/user_mailer/two_factor_enabled.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..fc31bd979f50206f5422243494912b7887c07f1c
--- /dev/null
+++ b/app/views/user_mailer/two_factor_enabled.html.haml
@@ -0,0 +1,43 @@
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell.hero
+                  .email-row
+                    .col-6
+                      %table.column{ cellspacing: 0, cellpadding: 0 }
+                        %tbody
+                          %tr
+                            %td.column-cell.text-center.padded
+                              %table.hero-icon{ align: 'center', cellspacing: 0, cellpadding: 0 }
+                                %tbody
+                                  %tr
+                                    %td
+                                      = image_tag full_pack_url('media/images/mailer/icon_lock_open.png'), alt: ''
+
+                              %h1= t 'devise.mailer.two_factor_enabled.title'
+                              %p.lead= t 'devise.mailer.two_factor_enabled.explanation'
+
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell.content-start
+                  %table.column{ cellspacing: 0, cellpadding: 0 }
+                    %tbody
+                      %tr
+                        %td.column-cell.button-cell
+                          %table.button{ align: 'center', cellspacing: 0, cellpadding: 0 }
+                            %tbody
+                              %tr
+                                %td.button-primary
+                                  = link_to edit_user_registration_url do
+                                    %span= t('settings.account_settings')
diff --git a/app/views/user_mailer/two_factor_enabled.text.erb b/app/views/user_mailer/two_factor_enabled.text.erb
new file mode 100644
index 0000000000000000000000000000000000000000..4319dddbfc49559563a2121d021a921e8dd75544
--- /dev/null
+++ b/app/views/user_mailer/two_factor_enabled.text.erb
@@ -0,0 +1,7 @@
+<%= t 'devise.mailer.two_factor_enabled.title' %>
+
+===
+
+<%= t 'devise.mailer.two_factor_enabled.explanation' %>
+
+=> <%= edit_user_registration_url %>
diff --git a/app/views/user_mailer/two_factor_recovery_codes_changed.html.haml b/app/views/user_mailer/two_factor_recovery_codes_changed.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..833708868badd5f871b4d8a22cd82c2acfef60ab
--- /dev/null
+++ b/app/views/user_mailer/two_factor_recovery_codes_changed.html.haml
@@ -0,0 +1,43 @@
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell.hero
+                  .email-row
+                    .col-6
+                      %table.column{ cellspacing: 0, cellpadding: 0 }
+                        %tbody
+                          %tr
+                            %td.column-cell.text-center.padded
+                              %table.hero-icon.alert-icon{ align: 'center', cellspacing: 0, cellpadding: 0 }
+                                %tbody
+                                  %tr
+                                    %td
+                                      = image_tag full_pack_url('media/images/mailer/icon_lock_open.png'), alt: ''
+
+                              %h1= t 'devise.mailer.two_factor_recovery_codes_changed.title'
+                              %p.lead= t 'devise.mailer.two_factor_recovery_codes_changed.explanation'
+
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell.content-start
+                  %table.column{ cellspacing: 0, cellpadding: 0 }
+                    %tbody
+                      %tr
+                        %td.column-cell.button-cell
+                          %table.button{ align: 'center', cellspacing: 0, cellpadding: 0 }
+                            %tbody
+                              %tr
+                                %td.button-primary
+                                  = link_to edit_user_registration_url do
+                                    %span= t('settings.account_settings')
diff --git a/app/views/user_mailer/two_factor_recovery_codes_changed.text.erb b/app/views/user_mailer/two_factor_recovery_codes_changed.text.erb
new file mode 100644
index 0000000000000000000000000000000000000000..6ed12fc08a20315fa1997941ea334dc1d269f2a7
--- /dev/null
+++ b/app/views/user_mailer/two_factor_recovery_codes_changed.text.erb
@@ -0,0 +1,7 @@
+<%= t 'devise.mailer.two_factor_recovery_codes_changed.title' %>
+
+===
+
+<%= t 'devise.mailer.two_factor_recovery_codes_changed.explanation' %>
+
+=> <%= edit_user_registration_url %>
diff --git a/app/views/user_mailer/warning.html.haml b/app/views/user_mailer/warning.html.haml
index 72ea5e5d28f935650c2f886fcdceda46d2ac8a56..5a2911ecba574e3ab7808089df7c7527ed267a45 100644
--- a/app/views/user_mailer/warning.html.haml
+++ b/app/views/user_mailer/warning.html.haml
@@ -42,6 +42,14 @@
                               - unless @warning.text.blank?
                                 = Formatter.instance.linkify(@warning.text)
 
+                              - if !@statuses.nil? && !@statuses.empty?
+                                %p
+                                  %strong= t('user_mailer.warning.statuses')
+
+- if !@statuses.nil? && !@statuses.empty?
+  - @statuses.each_with_index do |status, i|
+    = render 'notification_mailer/status', status: status, i: i + 1, highlighted: true
+
 %table.email-table{ cellspacing: 0, cellpadding: 0 }
   %tbody
     %tr
@@ -50,7 +58,7 @@
           %table.content-section{ cellspacing: 0, cellpadding: 0 }
             %tbody
               %tr
-                %td.content-cell
+                %td.content-cell{ class: @statuses.nil? || @statuses.empty? ? '' : 'content-start' }
                   %table.column{ cellspacing: 0, cellpadding: 0 }
                     %tbody
                       %tr
@@ -61,3 +69,20 @@
                                 %td.button-primary
                                   = link_to about_more_url do
                                     %span= t 'user_mailer.warning.review_server_policies'
+
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell
+                  .email-row
+                    .col-6
+                      %table.column{ cellspacing: 0, cellpadding: 0 }
+                        %tbody
+                          %tr
+                            %td.column-cell.text-center
+                              %p= t 'user_mailer.warning.get_in_touch', instance: @instance
diff --git a/app/views/user_mailer/warning.text.erb b/app/views/user_mailer/warning.text.erb
index b4f2402cb3743e0d2f237f3e23e4bfcb1f3473d9..bb6610c79bd2476a71b6ce510b87c0f7137d44e2 100644
--- a/app/views/user_mailer/warning.text.erb
+++ b/app/views/user_mailer/warning.text.erb
@@ -7,3 +7,16 @@
 
 <% end %>
 <%= @warning.text %>
+<% if !@statuses.nil? && !@statuses.empty? %>
+<%= t('user_mailer.warning.statuses') %>
+
+<% @statuses.each do |status| %>
+
+<%= render 'notification_mailer/status', status: status %>
+---
+<% end %>
+<% else %>
+---
+<% end %>
+
+<%= t 'user_mailer.warning.get_in_touch', instance: @instance %>
diff --git a/app/views/well_known/webfinger/show.xml.ruby b/app/views/well_known/webfinger/show.xml.ruby
deleted file mode 100644
index 968c8c1380c0d70543e60ce8f9348e18a5f1735d..0000000000000000000000000000000000000000
--- a/app/views/well_known/webfinger/show.xml.ruby
+++ /dev/null
@@ -1,44 +0,0 @@
-doc = Ox::Document.new(version: '1.0')
-
-doc << Ox::Element.new('XRD').tap do |xrd|
-  xrd['xmlns'] = 'http://docs.oasis-open.org/ns/xri/xrd-1.0'
-
-  xrd << (Ox::Element.new('Subject') << @account.to_webfinger_s)
-  xrd << (Ox::Element.new('Alias') << short_account_url(@account))
-  xrd << (Ox::Element.new('Alias') << account_url(@account))
-
-  xrd << Ox::Element.new('Link').tap do |link|
-    link['rel']      = 'http://webfinger.net/rel/profile-page'
-    link['type']     = 'text/html'
-    link['href']     = short_account_url(@account)
-  end
-
-  xrd << Ox::Element.new('Link').tap do |link|
-    link['rel']      = 'http://schemas.google.com/g/2010#updates-from'
-    link['type']     = 'application/atom+xml'
-    link['href']     = account_url(@account, format: 'atom')
-  end
-
-  xrd << Ox::Element.new('Link').tap do |link|
-    link['rel']      = 'self'
-    link['type']     = 'application/activity+json'
-    link['href']     = account_url(@account)
-  end
-
-  xrd << Ox::Element.new('Link').tap do |link|
-    link['rel']      = 'salmon'
-    link['href']     = api_salmon_url(@account.id)
-  end
-
-  xrd << Ox::Element.new('Link').tap do |link|
-    link['rel']      = 'magic-public-key'
-    link['href']     = "data:application/magic-public-key,#{@account.magic_key}"
-  end
-
-  xrd << Ox::Element.new('Link').tap do |link|
-    link['rel']      = 'http://ostatus.org/schema/1.0/subscribe'
-    link['template'] = "#{authorize_interaction_url}?acct={uri}"
-  end
-end
-
-('<?xml version="1.0" encoding="UTF-8"?>' + Ox.dump(doc, effort: :tolerant)).force_encoding('UTF-8')
diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb
index 00b5c6b7e7fac89174b41afa455236b930669932..5457d9d4b05a8aab344ebaa4eed248c3530bd226 100644
--- a/app/workers/activitypub/delivery_worker.rb
+++ b/app/workers/activitypub/delivery_worker.rb
@@ -2,6 +2,7 @@
 
 class ActivityPub::DeliveryWorker
   include Sidekiq::Worker
+  include JsonLdHelper
 
   STOPLIGHT_FAILURE_THRESHOLD = 10
   STOPLIGHT_COOLDOWN = 60
@@ -17,27 +18,35 @@ class ActivityPub::DeliveryWorker
     @json           = json
     @source_account = Account.find(source_account_id)
     @inbox_url      = inbox_url
+    @host           = Addressable::URI.parse(inbox_url).normalized_site
+    @performed      = false
 
     perform_request
-
-    failure_tracker.track_success!
-  rescue => e
-    failure_tracker.track_failure!
-    raise e.class, "Delivery failed for #{inbox_url}: #{e.message}", e.backtrace[0]
+  ensure
+    if @performed
+      failure_tracker.track_success!
+    else
+      failure_tracker.track_failure!
+    end
   end
 
   private
 
-  def build_request
-    request = Request.new(:post, @inbox_url, body: @json)
-    request.on_behalf_of(@source_account, :uri, sign_with: @options[:sign_with])
-    request.add_headers(HEADERS)
+  def build_request(http_client)
+    Request.new(:post, @inbox_url, body: @json, http_client: http_client).tap do |request|
+      request.on_behalf_of(@source_account, :uri, sign_with: @options[:sign_with])
+      request.add_headers(HEADERS)
+    end
   end
 
   def perform_request
     light = Stoplight(@inbox_url) do
-      build_request.perform do |response|
-        raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response)
+      request_pool.with(@host) do |http_client|
+        build_request(http_client).perform do |response|
+          raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response)
+
+          @performed = true
+        end
       end
     end
 
@@ -46,15 +55,11 @@ class ActivityPub::DeliveryWorker
          .run
   end
 
-  def response_successful?(response)
-    (200...300).cover?(response.code)
-  end
-
-  def response_error_unsalvageable?(response)
-    response.code == 501 || ((400...500).cover?(response.code) && ![401, 408, 429].include?(response.code))
-  end
-
   def failure_tracker
     @failure_tracker ||= DeliveryFailureTracker.new(@inbox_url)
   end
+
+  def request_pool
+    RequestPool.current
+  end
 end
diff --git a/app/workers/activitypub/move_distribution_worker.rb b/app/workers/activitypub/move_distribution_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bf1c0e7ae467ebd82282ef24dd6d8d3251242497
--- /dev/null
+++ b/app/workers/activitypub/move_distribution_worker.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class ActivityPub::MoveDistributionWorker
+  include Sidekiq::Worker
+  include Payloadable
+
+  sidekiq_options queue: 'push'
+
+  def perform(migration_id)
+    @migration = AccountMigration.find(migration_id)
+    @account   = @migration.account
+
+    ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
+      [signed_payload, @account.id, inbox_url]
+    end
+
+    ActivityPub::DeliveryWorker.push_bulk(Relay.enabled.pluck(:inbox_url)) do |inbox_url|
+      [signed_payload, @account.id, inbox_url]
+    end
+  rescue ActiveRecord::RecordNotFound
+    true
+  end
+
+  private
+
+  def inboxes
+    @inboxes ||= @migration.account.followers.inboxes
+  end
+
+  def signed_payload
+    @signed_payload ||= Oj.dump(serialize_payload(@migration, ActivityPub::MoveSerializer, signer: @account))
+  end
+end
diff --git a/app/workers/admin/suspension_worker.rb b/app/workers/admin/suspension_worker.rb
index ae8b24d8c3e34823caf1cbfc0ab2b7c083d6cb5c..83c815efd7c1ed624248b0b2213a9688a2aed2aa 100644
--- a/app/workers/admin/suspension_worker.rb
+++ b/app/workers/admin/suspension_worker.rb
@@ -6,6 +6,6 @@ class Admin::SuspensionWorker
   sidekiq_options queue: 'pull'
 
   def perform(account_id, remove_user = false)
-    SuspendAccountService.new.call(Account.find(account_id), including_user: remove_user)
+    SuspendAccountService.new.call(Account.find(account_id), reserve_username: true, reserve_email: !remove_user)
   end
 end
diff --git a/app/workers/after_remote_follow_request_worker.rb b/app/workers/after_remote_follow_request_worker.rb
index 84eb6ade23171f59780787f8440ef21fe7a7cbd2..ce9c65834c399d2b368beac36803adcfab9045e2 100644
--- a/app/workers/after_remote_follow_request_worker.rb
+++ b/app/workers/after_remote_follow_request_worker.rb
@@ -5,27 +5,5 @@ class AfterRemoteFollowRequestWorker
 
   sidekiq_options queue: 'pull', retry: 5
 
-  attr_reader :follow_request
-
-  def perform(follow_request_id)
-    @follow_request = FollowRequest.find(follow_request_id)
-    process_follow_service if processing_required?
-  rescue ActiveRecord::RecordNotFound
-    true
-  end
-
-  private
-
-  def process_follow_service
-    follow_request.destroy
-    FollowService.new.call(follow_request.account, updated_account.acct)
-  end
-
-  def processing_required?
-    !updated_account.nil? && !updated_account.locked?
-  end
-
-  def updated_account
-    @_updated_account ||= FetchRemoteAccountService.new.call(follow_request.target_account.remote_url)
-  end
+  def perform(follow_request_id); end
 end
diff --git a/app/workers/after_remote_follow_worker.rb b/app/workers/after_remote_follow_worker.rb
index edab83f853a6b66fc3640eae5c5c2cae1d91f02e..d9719f2bf8af418e6ddd315aaed30b117d05bbdd 100644
--- a/app/workers/after_remote_follow_worker.rb
+++ b/app/workers/after_remote_follow_worker.rb
@@ -5,27 +5,5 @@ class AfterRemoteFollowWorker
 
   sidekiq_options queue: 'pull', retry: 5
 
-  attr_reader :follow
-
-  def perform(follow_id)
-    @follow = Follow.find(follow_id)
-    process_follow_service if processing_required?
-  rescue ActiveRecord::RecordNotFound
-    true
-  end
-
-  private
-
-  def process_follow_service
-    follow.destroy
-    FollowService.new.call(follow.account, updated_account.acct)
-  end
-
-  def updated_account
-    @_updated_account ||= FetchRemoteAccountService.new.call(follow.target_account.remote_url)
-  end
-
-  def processing_required?
-    !updated_account.nil? && updated_account.locked?
-  end
+  def perform(follow_id); end
 end
diff --git a/app/workers/domain_block_worker.rb b/app/workers/domain_block_worker.rb
index 8844778291d41bf96b100f3f09e83a63bd8c94a9..35518d6b5e8c687c42abd21f8eded36385011a6d 100644
--- a/app/workers/domain_block_worker.rb
+++ b/app/workers/domain_block_worker.rb
@@ -3,8 +3,8 @@
 class DomainBlockWorker
   include Sidekiq::Worker
 
-  def perform(domain_block_id)
-    BlockDomainService.new.call(DomainBlock.find(domain_block_id))
+  def perform(domain_block_id, update = false)
+    BlockDomainService.new.call(DomainBlock.find(domain_block_id), update)
   rescue ActiveRecord::RecordNotFound
     true
   end
diff --git a/app/workers/maintenance/destroy_media_worker.rb b/app/workers/maintenance/destroy_media_worker.rb
deleted file mode 100644
index cde33d6d79a53df409cdb49c72ff7ea10e3e9351..0000000000000000000000000000000000000000
--- a/app/workers/maintenance/destroy_media_worker.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-class Maintenance::DestroyMediaWorker
-  include Sidekiq::Worker
-
-  sidekiq_options queue: 'pull'
-
-  def perform(media_attachment_id)
-    media = media_attachment_id.is_a?(MediaAttachment) ? media_attachment_id : MediaAttachment.find(media_attachment_id)
-    media.destroy
-  rescue ActiveRecord::RecordNotFound
-    true
-  end
-end
diff --git a/app/workers/maintenance/redownload_account_media_worker.rb b/app/workers/maintenance/redownload_account_media_worker.rb
deleted file mode 100644
index 6afbe6e19cef35261697b5e92be3e1d438e36717..0000000000000000000000000000000000000000
--- a/app/workers/maintenance/redownload_account_media_worker.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-class Maintenance::RedownloadAccountMediaWorker
-  include Sidekiq::Worker
-
-  sidekiq_options queue: 'pull', retry: false
-
-  def perform(account_id)
-    account = account_id.is_a?(Account) ? account_id : Account.find(account_id)
-    account.reset_avatar!
-    account.reset_header!
-    account.save
-  rescue ActiveRecord::RecordNotFound
-    true
-  end
-end
diff --git a/app/workers/maintenance/uncache_media_worker.rb b/app/workers/maintenance/uncache_media_worker.rb
deleted file mode 100644
index 4bc62ef755c30e712420a494fd71a82fcf623b09..0000000000000000000000000000000000000000
--- a/app/workers/maintenance/uncache_media_worker.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-class Maintenance::UncacheMediaWorker
-  include Sidekiq::Worker
-
-  sidekiq_options queue: 'pull'
-
-  def perform(media_attachment_id)
-    media = media_attachment_id.is_a?(MediaAttachment) ? media_attachment_id : MediaAttachment.find(media_attachment_id)
-
-    return if media.file.blank?
-
-    media.file.destroy
-    media.save
-  rescue ActiveRecord::RecordNotFound
-    true
-  end
-end
diff --git a/app/workers/move_worker.rb b/app/workers/move_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..83f7090eec1198f0d1d9472524304e769171cadb
--- /dev/null
+++ b/app/workers/move_worker.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class MoveWorker
+  include Sidekiq::Worker
+
+  def perform(source_account_id, target_account_id)
+    @source_account = Account.find(source_account_id)
+    @target_account = Account.find(target_account_id)
+
+    if @target_account.local?
+      rewrite_follows!
+    else
+      queue_follow_unfollows!
+    end
+  rescue ActiveRecord::RecordNotFound
+    true
+  end
+
+  private
+
+  def rewrite_follows!
+    @source_account.passive_relationships
+                   .where(account: Account.local)
+                   .in_batches
+                   .update_all(target_account_id: @target_account.id)
+  end
+
+  def queue_follow_unfollows!
+    @source_account.followers.local.select(:id).find_in_batches do |accounts|
+      UnfollowFollowWorker.push_bulk(accounts.map(&:id)) { |follower_id| [follower_id, @source_account.id, @target_account.id] }
+    end
+  end
+end
diff --git a/app/workers/notification_worker.rb b/app/workers/notification_worker.rb
index da1d6ab455cbba498997f807bdd266857a26b702..1c0f001cf20fb9dedc084ce8e7272859f2352025 100644
--- a/app/workers/notification_worker.rb
+++ b/app/workers/notification_worker.rb
@@ -5,7 +5,5 @@ class NotificationWorker
 
   sidekiq_options queue: 'push', retry: 5
 
-  def perform(xml, source_account_id, target_account_id)
-    SendInteractionService.new.call(xml, Account.find(source_account_id), Account.find(target_account_id))
-  end
+  def perform(xml, source_account_id, target_account_id); end
 end
diff --git a/app/workers/processing_worker.rb b/app/workers/processing_worker.rb
index 978c3aba26f2d0238fe0266868ca91ff6fc84d54..cf3bd83979a8c3182ad6e5906e3c2ccccf5a4ea4 100644
--- a/app/workers/processing_worker.rb
+++ b/app/workers/processing_worker.rb
@@ -5,7 +5,5 @@ class ProcessingWorker
 
   sidekiq_options backtrace: true
 
-  def perform(account_id, body)
-    ProcessFeedService.new.call(body, Account.find(account_id), override_timestamps: true)
-  end
+  def perform(account_id, body); end
 end
diff --git a/app/workers/pubsubhubbub/confirmation_worker.rb b/app/workers/pubsubhubbub/confirmation_worker.rb
index c0e7b677e41e0f2255bd63bba2e8bbdbc269e893..783a8c95fb0ffb70e1467845a53b62b695ed78d9 100644
--- a/app/workers/pubsubhubbub/confirmation_worker.rb
+++ b/app/workers/pubsubhubbub/confirmation_worker.rb
@@ -2,81 +2,8 @@
 
 class Pubsubhubbub::ConfirmationWorker
   include Sidekiq::Worker
-  include RoutingHelper
 
   sidekiq_options queue: 'push', retry: false
 
-  attr_reader :subscription, :mode, :secret, :lease_seconds
-
-  def perform(subscription_id, mode, secret = nil, lease_seconds = nil)
-    @subscription = Subscription.find(subscription_id)
-    @mode = mode
-    @secret = secret
-    @lease_seconds = lease_seconds
-    process_confirmation
-  end
-
-  private
-
-  def process_confirmation
-    prepare_subscription
-
-    callback_get_with_params
-    logger.debug "Confirming PuSH subscription for #{subscription.callback_url} with challenge #{challenge}: #{@callback_response_body}"
-
-    update_subscription
-  end
-
-  def update_subscription
-    if successful_subscribe?
-      subscription.save!
-    elsif successful_unsubscribe?
-      subscription.destroy!
-    end
-  end
-
-  def successful_subscribe?
-    subscribing? && response_matches_challenge?
-  end
-
-  def successful_unsubscribe?
-    (unsubscribing? && response_matches_challenge?) || !subscription.confirmed?
-  end
-
-  def response_matches_challenge?
-    @callback_response_body == challenge
-  end
-
-  def subscribing?
-    mode == 'subscribe'
-  end
-
-  def unsubscribing?
-    mode == 'unsubscribe'
-  end
-
-  def callback_get_with_params
-    Request.new(:get, subscription.callback_url, params: callback_params).perform do |response|
-      @callback_response_body = response.body_with_limit
-    end
-  end
-
-  def callback_params
-    {
-      'hub.topic': account_url(subscription.account, format: :atom),
-      'hub.mode': mode,
-      'hub.challenge': challenge,
-      'hub.lease_seconds': subscription.lease_seconds,
-    }
-  end
-
-  def prepare_subscription
-    subscription.secret = secret
-    subscription.lease_seconds = lease_seconds
-    subscription.confirmed = true
-  end
-
-  def challenge
-    @_challenge ||= SecureRandom.hex
-  end
+  def perform(subscription_id, mode, secret = nil, lease_seconds = nil); end
 end
diff --git a/app/workers/pubsubhubbub/delivery_worker.rb b/app/workers/pubsubhubbub/delivery_worker.rb
index 619bfa48aad215c36f357114e608c62d8cbee336..1260060bd5543cb3e91db9bc7810994cad0e6400 100644
--- a/app/workers/pubsubhubbub/delivery_worker.rb
+++ b/app/workers/pubsubhubbub/delivery_worker.rb
@@ -2,80 +2,8 @@
 
 class Pubsubhubbub::DeliveryWorker
   include Sidekiq::Worker
-  include RoutingHelper
 
   sidekiq_options queue: 'push', retry: 3, dead: false
 
-  sidekiq_retry_in do |count|
-    5 * (count + 1)
-  end
-
-  attr_reader :subscription, :payload
-
-  def perform(subscription_id, payload)
-    @subscription = Subscription.find(subscription_id)
-    @payload = payload
-    process_delivery unless blocked_domain?
-  rescue => e
-    raise e.class, "Delivery failed for #{subscription&.callback_url}: #{e.message}", e.backtrace[0]
-  end
-
-  private
-
-  def process_delivery
-    callback_post_payload do |payload_delivery|
-      raise Mastodon::UnexpectedResponseError, payload_delivery unless response_successful? payload_delivery
-    end
-
-    subscription.touch(:last_successful_delivery_at)
-  end
-
-  def callback_post_payload(&block)
-    request = Request.new(:post, subscription.callback_url, body: payload)
-    request.add_headers(headers)
-    request.perform(&block)
-  end
-
-  def blocked_domain?
-    DomainBlock.blocked?(host)
-  end
-
-  def host
-    Addressable::URI.parse(subscription.callback_url).normalized_host
-  end
-
-  def headers
-    {
-      'Content-Type' => 'application/atom+xml',
-      'Link' => link_header,
-    }.merge(signature_headers.to_h)
-  end
-
-  def link_header
-    LinkHeader.new([hub_link_header, self_link_header]).to_s
-  end
-
-  def hub_link_header
-    [api_push_url, [%w(rel hub)]]
-  end
-
-  def self_link_header
-    [account_url(subscription.account, format: :atom), [%w(rel self)]]
-  end
-
-  def signature_headers
-    { 'X-Hub-Signature' => payload_signature } if subscription.secret?
-  end
-
-  def payload_signature
-    "sha1=#{hmac_payload_digest}"
-  end
-
-  def hmac_payload_digest
-    OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), subscription.secret, payload)
-  end
-
-  def response_successful?(payload_delivery)
-    payload_delivery.code > 199 && payload_delivery.code < 300
-  end
+  def perform(subscription_id, payload); end
 end
diff --git a/app/workers/pubsubhubbub/distribution_worker.rb b/app/workers/pubsubhubbub/distribution_worker.rb
index fed5e917d3dd2bd54a7b6634d31b8b4360fc39f6..75bac5d6fadbc7252b54f7ca8f80b11f182bfe4b 100644
--- a/app/workers/pubsubhubbub/distribution_worker.rb
+++ b/app/workers/pubsubhubbub/distribution_worker.rb
@@ -5,28 +5,5 @@ class Pubsubhubbub::DistributionWorker
 
   sidekiq_options queue: 'push'
 
-  def perform(stream_entry_ids)
-    stream_entries = StreamEntry.where(id: stream_entry_ids).includes(:status).reject { |e| e.status.nil? || e.status.hidden? }
-
-    return if stream_entries.empty?
-
-    @account       = stream_entries.first.account
-    @subscriptions = active_subscriptions.to_a
-
-    distribute_public!(stream_entries)
-  end
-
-  private
-
-  def distribute_public!(stream_entries)
-    @payload = OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, stream_entries))
-
-    Pubsubhubbub::DeliveryWorker.push_bulk(@subscriptions) do |subscription_id|
-      [subscription_id, @payload]
-    end
-  end
-
-  def active_subscriptions
-    Subscription.where(account: @account).active.pluck(:id)
-  end
+  def perform(stream_entry_ids); end
 end
diff --git a/app/workers/pubsubhubbub/raw_distribution_worker.rb b/app/workers/pubsubhubbub/raw_distribution_worker.rb
index 16962a623ce72ad3d3381cec93a0711dbc4dc84a..ece9c80ac5d0bee1834ddd168eaa14d1b4426ab6 100644
--- a/app/workers/pubsubhubbub/raw_distribution_worker.rb
+++ b/app/workers/pubsubhubbub/raw_distribution_worker.rb
@@ -5,18 +5,5 @@ class Pubsubhubbub::RawDistributionWorker
 
   sidekiq_options queue: 'push'
 
-  def perform(xml, source_account_id)
-    @account       = Account.find(source_account_id)
-    @subscriptions = active_subscriptions.to_a
-
-    Pubsubhubbub::DeliveryWorker.push_bulk(@subscriptions) do |subscription|
-      [subscription.id, xml]
-    end
-  end
-
-  private
-
-  def active_subscriptions
-    Subscription.where(account: @account).active.select('id, callback_url, domain')
-  end
+  def perform(xml, source_account_id); end
 end
diff --git a/app/workers/pubsubhubbub/subscribe_worker.rb b/app/workers/pubsubhubbub/subscribe_worker.rb
index 2e176d1c106f3d8f7a8f5d8de8ec56c89b6bd49e..b861b5e67a1856d74fac071e537b97e40e4bcb50 100644
--- a/app/workers/pubsubhubbub/subscribe_worker.rb
+++ b/app/workers/pubsubhubbub/subscribe_worker.rb
@@ -5,30 +5,5 @@ class Pubsubhubbub::SubscribeWorker
 
   sidekiq_options queue: 'push', retry: 10, unique: :until_executed, dead: false
 
-  sidekiq_retry_in do |count|
-    case count
-    when 0
-      30.minutes.seconds
-    when 1
-      2.hours.seconds
-    when 2
-      12.hours.seconds
-    else
-      24.hours.seconds * (count - 2)
-    end
-  end
-
-  sidekiq_retries_exhausted do |msg, _e|
-    account = Account.find(msg['args'].first)
-    Sidekiq.logger.error "PuSH subscription attempts for #{account.acct} exhausted. Unsubscribing"
-    ::UnsubscribeService.new.call(account)
-  end
-
-  def perform(account_id)
-    account = Account.find(account_id)
-    logger.debug "PuSH re-subscribing to #{account.acct}"
-    ::SubscribeService.new.call(account)
-  rescue => e
-    raise e.class, "Subscribe failed for #{account&.acct}: #{e.message}", e.backtrace[0]
-  end
+  def perform(account_id); end
 end
diff --git a/app/workers/pubsubhubbub/unsubscribe_worker.rb b/app/workers/pubsubhubbub/unsubscribe_worker.rb
index a271715b7a0e2bfb0796c6e2feaf01a51466acc6..0c1c263f6e07ee71e92157c5776bb3f502c845dc 100644
--- a/app/workers/pubsubhubbub/unsubscribe_worker.rb
+++ b/app/workers/pubsubhubbub/unsubscribe_worker.rb
@@ -5,11 +5,5 @@ class Pubsubhubbub::UnsubscribeWorker
 
   sidekiq_options queue: 'push', retry: false, unique: :until_executed, dead: false
 
-  def perform(account_id)
-    account = Account.find(account_id)
-    logger.debug "PuSH unsubscribing from #{account.acct}"
-    ::UnsubscribeService.new.call(account)
-  rescue ActiveRecord::RecordNotFound
-    true
-  end
+  def perform(account_id); end
 end
diff --git a/app/workers/redownload_media_worker.rb b/app/workers/redownload_media_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..98e99591898e0e672769eb67c8f01b0fb0169cf1
--- /dev/null
+++ b/app/workers/redownload_media_worker.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RedownloadMediaWorker
+  include Sidekiq::Worker
+  include ExponentialBackoff
+
+  sidekiq_options queue: 'pull', retry: 3
+
+  def perform(id)
+    media_attachment = MediaAttachment.find(id)
+
+    return if media_attachment.remote_url.blank?
+
+    media_attachment.reset_file!
+    media_attachment.save
+  rescue ActiveRecord::RecordNotFound
+    true
+  end
+end
diff --git a/app/workers/remote_profile_update_worker.rb b/app/workers/remote_profile_update_worker.rb
index 03585ad2d6e2485b5dc59a69d98be14f745cc7ba..01e8daf8f282fa40013f55b47a9c1bf74388be7b 100644
--- a/app/workers/remote_profile_update_worker.rb
+++ b/app/workers/remote_profile_update_worker.rb
@@ -5,9 +5,5 @@ class RemoteProfileUpdateWorker
 
   sidekiq_options queue: 'pull'
 
-  def perform(account_id, body, resubscribe)
-    UpdateRemoteProfileService.new.call(body, Account.find(account_id), resubscribe)
-  rescue ActiveRecord::RecordNotFound
-    true
-  end
+  def perform(account_id, body, resubscribe); end
 end
diff --git a/app/workers/removal_worker.rb b/app/workers/removal_worker.rb
index 19a660dd3e0190f19a6cfab2bc7389b3c57606ef..2a1eaa89b061d34b4b4fc7b0d07f04e00d81de2b 100644
--- a/app/workers/removal_worker.rb
+++ b/app/workers/removal_worker.rb
@@ -3,8 +3,8 @@
 class RemovalWorker
   include Sidekiq::Worker
 
-  def perform(status_id)
-    RemoveStatusService.new.call(Status.find(status_id))
+  def perform(status_id, options = {})
+    RemoveStatusService.new.call(Status.with_discarded.find(status_id), **options.symbolize_keys)
   rescue ActiveRecord::RecordNotFound
     true
   end
diff --git a/app/workers/salmon_worker.rb b/app/workers/salmon_worker.rb
index d37d4043234b2e451865efbbd433da06acd7e478..10200b06cf95b937585ab44c85c6f0606763bc46 100644
--- a/app/workers/salmon_worker.rb
+++ b/app/workers/salmon_worker.rb
@@ -5,9 +5,5 @@ class SalmonWorker
 
   sidekiq_options backtrace: true
 
-  def perform(account_id, body)
-    ProcessInteractionService.new.call(body, Account.find(account_id))
-  rescue Nokogiri::XML::XPath::SyntaxError, ActiveRecord::RecordNotFound
-    true
-  end
+  def perform(account_id, body); end
 end
diff --git a/app/workers/scheduler/ip_cleanup_scheduler.rb b/app/workers/scheduler/ip_cleanup_scheduler.rb
index 42620332e7ee8cad0e42ae6622c0d96d1d59159c..4f44078d8e903971dab4be0a4897822dd5be0ef6 100644
--- a/app/workers/scheduler/ip_cleanup_scheduler.rb
+++ b/app/workers/scheduler/ip_cleanup_scheduler.rb
@@ -9,7 +9,7 @@ class Scheduler::IpCleanupScheduler
 
   def perform
     time_ago = RETENTION_PERIOD.ago
-    SessionActivation.where('updated_at < ?', time_ago).destroy_all
-    User.where('last_sign_in_at < ?', time_ago).update_all(last_sign_in_ip: nil)
+    SessionActivation.where('updated_at < ?', time_ago).in_batches.destroy_all
+    User.where('last_sign_in_at < ?', time_ago).where.not(last_sign_in_ip: nil).in_batches.update_all(last_sign_in_ip: nil)
   end
 end
diff --git a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb
index 5fba120f6ab339ccfede1b8b232a762b84f9f815..75fe681a9c3cfc72331ceb8a6ae109eafbae9f72 100644
--- a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb
+++ b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb
@@ -5,7 +5,5 @@ class Scheduler::SubscriptionsCleanupScheduler
 
   sidekiq_options unique: :until_executed, retry: 0
 
-  def perform
-    Subscription.expired.in_batches.delete_all
-  end
+  def perform; end
 end
diff --git a/app/workers/scheduler/subscriptions_scheduler.rb b/app/workers/scheduler/subscriptions_scheduler.rb
index d5873bccb0ea2bfe9d392e827305a6e3a23b46b0..6903cadc7607f49a9ab3228608b0d4abb4b6b63f 100644
--- a/app/workers/scheduler/subscriptions_scheduler.rb
+++ b/app/workers/scheduler/subscriptions_scheduler.rb
@@ -5,13 +5,5 @@ class Scheduler::SubscriptionsScheduler
 
   sidekiq_options unique: :until_executed, retry: 0
 
-  def perform
-    Pubsubhubbub::SubscribeWorker.push_bulk(expiring_accounts.pluck(:id))
-  end
-
-  private
-
-  def expiring_accounts
-    Account.expiring(1.day.from_now).partitioned
-  end
+  def perform; end
 end
diff --git a/app/workers/scheduler/trending_tags_scheduler.rb b/app/workers/scheduler/trending_tags_scheduler.rb
new file mode 100644
index 0000000000000000000000000000000000000000..77f0d574757c920dab85212602053830c2434153
--- /dev/null
+++ b/app/workers/scheduler/trending_tags_scheduler.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class Scheduler::TrendingTagsScheduler
+  include Sidekiq::Worker
+
+  sidekiq_options unique: :until_executed, retry: 0
+
+  def perform
+    TrendingTags.update! if Setting.trends
+  end
+end
diff --git a/app/workers/unfollow_follow_worker.rb b/app/workers/unfollow_follow_worker.rb
index 50d3bf034adfb9acd9713465f921a7b6ffc245ef..95549e107ad70c760ea04d492e71574f8d2bad51 100644
--- a/app/workers/unfollow_follow_worker.rb
+++ b/app/workers/unfollow_follow_worker.rb
@@ -11,7 +11,7 @@ class UnfollowFollowWorker
     new_target_account = Account.find(new_target_account_id)
 
     FollowService.new.call(follower_account, new_target_account)
-    UnfollowService.new.call(follower_account, old_target_account)
+    UnfollowService.new.call(follower_account, old_target_account, skip_unmerge: true)
   rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
     true
   end
diff --git a/app/workers/web/push_notification_worker.rb b/app/workers/web/push_notification_worker.rb
index 90104397581aacdcf9f21c58f518c1849bc2c6e0..46aeaa30b1d08ea58b5e3532f379ea2ff5c00380 100644
--- a/app/workers/web/push_notification_worker.rb
+++ b/app/workers/web/push_notification_worker.rb
@@ -11,7 +11,13 @@ class Web::PushNotificationWorker
 
     subscription.push(notification) unless notification.activity.nil?
   rescue Webpush::ResponseError => e
-    subscription.destroy! if (400..499).cover?(e.response.code.to_i)
+    code = e.response.code.to_i
+
+    if (400..499).cover?(code) && ![408, 429].include?(code)
+      subscription.destroy!
+    else
+      raise e
+    end
   rescue ActiveRecord::RecordNotFound
     true
   end
diff --git a/babel.config.js b/babel.config.js
index a506ad8ce6e27031c5f021fd6e771991fc323b16..3f7ff84dbd13f5e8294109115ce599579d0823b2 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -20,6 +20,12 @@ module.exports = (api) => {
       ['react-intl', { messagesDir: './build/messages' }],
       'preval',
     ],
+    overrides: [{
+      test: /tesseract\.js/,
+      presets: [
+        ['@babel/env', { ...envOptions, modules: 'commonjs' }],
+      ],
+    }],
   };
 
   switch (env) {
diff --git a/boxfile.yml b/boxfile.yml
index 08526a57c918e9c09d4c56c87faeaa551b26ec29..1bc3929c86d2feff4fc640461ccc0335b765b3a6 100644
--- a/boxfile.yml
+++ b/boxfile.yml
@@ -67,6 +67,9 @@ deploy.config:
             bin/tootctl search deploy
           fi
       - bin/tootctl cache clear
+  after_live:
+    worker.sidekiq:
+      - bin/tootctl search deploy
 
 
 web.web:
diff --git a/config/application.rb b/config/application.rb
index f49deffbb6d4e14b78c5c10ff5087fe4a2e403e6..9be41b1a75c0fdf84bf2d8832d3ec33aef6e465a 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -13,7 +13,9 @@ require_relative '../lib/paperclip/video_transcoder'
 require_relative '../lib/paperclip/type_corrector'
 require_relative '../lib/mastodon/snowflake'
 require_relative '../lib/mastodon/version'
-require_relative '../lib/devise/ldap_authenticatable'
+require_relative '../lib/devise/two_factor_ldap_authenticatable'
+require_relative '../lib/devise/two_factor_pam_authenticatable'
+require_relative '../lib/chewy/strategy/custom_sidekiq'
 
 Dotenv::Railtie.load
 
@@ -37,11 +39,11 @@ module Mastodon
     # All translations from config/locales/*.rb,yml are auto loaded.
     # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
     config.i18n.available_locales = [
-      :en,
       :ar,
       :ast,
       :bg,
       :bn,
+      :br,
       :ca,
       :co,
       :cs,
@@ -49,8 +51,11 @@ module Mastodon
       :da,
       :de,
       :el,
+      :en,
       :eo,
+      :'es-AR',
       :es,
+      :et,
       :eu,
       :fa,
       :fi,
@@ -71,20 +76,22 @@ module Mastodon
       :ko,
       :lt,
       :lv,
+      :mk,
       :ms,
       :nl,
+      :nn,
       :no,
       :oc,
       :pl,
-      :pt,
       :'pt-BR',
+      :'pt-PT',
       :ro,
       :ru,
       :sk,
       :sl,
       :sq,
-      :sr,
       :'sr-Latn',
+      :sr,
       :sv,
       :ta,
       :te,
diff --git a/config/deploy.rb b/config/deploy.rb
index f0db50788c26b6c602dce3d2a907c3941898bb73..4dc36c65c29a8a1ad519555e9e60fab9a9b97dbb 100644
--- a/config/deploy.rb
+++ b/config/deploy.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-lock '3.11.0'
+lock '3.11.2'
 
 set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
 set :branch, ENV.fetch('BRANCH', 'master')
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 70baa6ad1a7b7d993f8a08989487e8bda723510f..29d6194ddabdacd7eb0050a1449f952040b41371 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -71,13 +71,22 @@ Rails.application.configure do
   # Better log formatting
   config.lograge.enabled = true
 
+  config.lograge.custom_payload do |controller|
+    if controller.respond_to?(:signed_request?) && controller.signed_request?
+      { key: controller.signature_key_id }
+    end
+  end
+
   # Do not dump schema after migrations.
   config.active_record.dump_schema_after_migration = false
 
   config.action_mailer.perform_caching = false
 
   # E-mails
-  config.action_mailer.default_options = { from: ENV.fetch('SMTP_FROM_ADDRESS', 'notifications@localhost') }
+  config.action_mailer.default_options = {
+    from: ENV.fetch('SMTP_FROM_ADDRESS', 'notifications@localhost'),
+    reply_to: ENV['SMTP_REPLY_TO']
+  }
 
   config.action_mailer.smtp_settings = {
     :port                 => ENV['SMTP_PORT'],
diff --git a/config/initializers/2_whitelist_mode.rb b/config/initializers/2_whitelist_mode.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a17ad07a2cfb052487d3562832e02f1d6fcd19f2
--- /dev/null
+++ b/config/initializers/2_whitelist_mode.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+Rails.application.configure do
+  config.x.whitelist_mode = ENV['WHITELIST_MODE'] == 'true'
+end
diff --git a/config/initializers/active_model_serializers.rb b/config/initializers/active_model_serializers.rb
index 329a5fb2c3a005688bdf9a5e023815008a62f974..0e69e1d96c408ded0bb5d4d08a5b132408709771 100644
--- a/config/initializers/active_model_serializers.rb
+++ b/config/initializers/active_model_serializers.rb
@@ -3,22 +3,3 @@ ActiveModelSerializers.config.tap do |config|
 end
 
 ActiveSupport::Notifications.unsubscribe(ActiveModelSerializers::Logging::RENDER_EVENT)
-
-class ActiveModel::Serializer::Reflection
-  # We monkey-patch this method so that when we include associations in a serializer,
-  # the nested serializers can send information about used contexts upwards back to
-  # the root. We do this via instance_options because the nesting can be dynamic.
-  def build_association(parent_serializer, parent_serializer_options, include_slice = {})
-    serializer = options[:serializer]
-
-    parent_serializer_options.merge!(named_contexts: serializer._named_contexts, context_extensions: serializer._context_extensions) if serializer.respond_to?(:_named_contexts)
-
-    association_options = {
-      parent_serializer: parent_serializer,
-      parent_serializer_options: parent_serializer_options,
-      include_slice: include_slice,
-    }
-
-    ActiveModel::Serializer::Association.new(self, association_options)
-  end
-end
diff --git a/config/initializers/chewy.rb b/config/initializers/chewy.rb
index d5347f2bfd0bcd049b5396489fbe42a0816f75b1..9ff0dccc1b499042ec80560fa23fcc2d6d46b3d9 100644
--- a/config/initializers/chewy.rb
+++ b/config/initializers/chewy.rb
@@ -12,8 +12,9 @@ Chewy.settings = {
   sidekiq: { queue: 'pull' },
 }
 
-Chewy.root_strategy    = enabled ? :sidekiq : :bypass
-Chewy.request_strategy = enabled ? :sidekiq : :bypass
+Chewy.root_strategy              = :custom_sidekiq
+Chewy.request_strategy           = :custom_sidekiq
+Chewy.use_after_commit_callbacks = false
 
 module Chewy
   class << self
diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb
index 59cfbba173c33606540ae0e21b3bd7263d867ef1..af7d16aafc521f39556012e185b8af3e865b2761 100644
--- a/config/initializers/content_security_policy.rb
+++ b/config/initializers/content_security_policy.rb
@@ -2,9 +2,19 @@
 # For further information see the following documentation
 # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
 
-base_host     = Rails.configuration.x.web_domain
+def host_to_url(str)
+  "http#{Rails.configuration.x.use_https ? 's' : ''}://#{str}" unless str.blank?
+end
+
+base_host = Rails.configuration.x.web_domain
+
 assets_host   = Rails.configuration.action_controller.asset_host
-assets_host ||= "http#{Rails.configuration.x.use_https ? 's' : ''}://#{base_host}"
+assets_host ||= host_to_url(base_host)
+
+media_host   = host_to_url(ENV['S3_ALIAS_HOST'])
+media_host ||= host_to_url(ENV['S3_CLOUDFRONT_HOST'])
+media_host ||= host_to_url(ENV['S3_HOSTNAME']) if ENV['S3_ENABLED'] == 'true'
+media_host ||= assets_host
 
 Rails.application.config.content_security_policy do |p|
   p.base_uri        :none
@@ -20,11 +30,13 @@ Rails.application.config.content_security_policy do |p|
   if Rails.env.development?
     webpacker_urls = %w(ws http).map { |protocol| "#{protocol}#{Webpacker.dev_server.https? ? 's' : ''}://#{Webpacker.dev_server.host_with_port}" }
 
-    p.connect_src :self, :blob, assets_host, Rails.configuration.x.streaming_api_base_url, *webpacker_urls
+    p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url, *webpacker_urls
     p.script_src  :self, :unsafe_inline, :unsafe_eval, assets_host
+    p.worker_src  :self, :blob, assets_host
   else
-    p.connect_src :self, :blob, assets_host, Rails.configuration.x.streaming_api_base_url
+    p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url
     p.script_src  :self, assets_host
+    p.worker_src  :self, :blob, assets_host
   end
 end
 
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index cd9bacf68033e609abf0e662b9958b8b00555499..fd9a5a8b9b96f14663fe38ddce0e990dcd3a6623 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -71,8 +71,8 @@ end
 
 Devise.setup do |config|
   config.warden do |manager|
-    manager.default_strategies(scope: :user).unshift :ldap_authenticatable if Devise.ldap_authentication
-    manager.default_strategies(scope: :user).unshift :pam_authenticatable  if Devise.pam_authentication
+    manager.default_strategies(scope: :user).unshift :two_factor_ldap_authenticatable if Devise.ldap_authentication
+    manager.default_strategies(scope: :user).unshift :two_factor_pam_authenticatable  if Devise.pam_authentication
     manager.default_strategies(scope: :user).unshift :two_factor_authenticatable
     manager.default_strategies(scope: :user).unshift :two_factor_backupable
   end
diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6f1e78fed995f583300a1c04a9615a1a7b704151
--- /dev/null
+++ b/config/initializers/health_check.rb
@@ -0,0 +1,8 @@
+HealthCheck.setup do |config|
+  config.uri = 'health'
+
+  config.standard_checks = %w(database migrations cache)
+  config.full_checks = %w(database migrations cache)
+  
+  config.include_error_in_response_body = false
+end
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
index bf0cb52a30a2ac2832408f712a7e51dfa015885c..c65153b0a7a19d712a77666d346e53d72b81c59c 100644
--- a/config/initializers/inflections.rb
+++ b/config/initializers/inflections.rb
@@ -18,4 +18,5 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
   inflect.acronym 'PubSubHubbub'
   inflect.acronym 'ActivityStreams'
   inflect.acronym 'JsonLd'
+  inflect.acronym 'NodeInfo'
 end
diff --git a/config/initializers/json_ld.rb b/config/initializers/json_ld.rb
index d5575d135464f18fddbed4cd8353b3d59044c1d1..3ed3c4b31a31bb50c4281d1d514d36ac0c79ee8f 100644
--- a/config/initializers/json_ld.rb
+++ b/config/initializers/json_ld.rb
@@ -1,3 +1,4 @@
 # frozen_string_literal: true
 
 require_relative '../../lib/json_ld/security'
+require_relative '../../lib/json_ld/identity'
diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb
index ce4185e024dbf92e7c02357e0dd37276a948ed3d..d3602e655e6083d74d14143c3afd52a898d8a921 100644
--- a/config/initializers/paperclip.rb
+++ b/config/initializers/paperclip.rb
@@ -1,10 +1,11 @@
 # frozen_string_literal: true
 
-Paperclip.options[:read_timeout] = 60
-
 Paperclip.interpolates :filename do |attachment, style|
-  return attachment.original_filename if style == :original
-  [basename(attachment, style), extension(attachment, style)].delete_if(&:blank?).join('.')
+  if style == :original
+    attachment.original_filename
+  else
+    [basename(attachment, style), extension(attachment, style)].delete_if(&:blank?).join('.')
+  end
 end
 
 Paperclip::Attachment.default_options.merge!(
@@ -24,21 +25,27 @@ if ENV['S3_ENABLED'] == 'true'
     storage: :s3,
     s3_protocol: s3_protocol,
     s3_host_name: s3_hostname,
+
     s3_headers: {
+      'X-Amz-Multipart-Threshold' => ENV.fetch('S3_MULTIPART_THRESHOLD') { 15.megabytes }.to_i,
       'Cache-Control' => 'public, max-age=315576000, immutable',
     },
+
     s3_permissions: ENV.fetch('S3_PERMISSION') { 'public-read' },
     s3_region: s3_region,
+
     s3_credentials: {
       bucket: ENV['S3_BUCKET'],
       access_key_id: ENV['AWS_ACCESS_KEY_ID'],
       secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
     },
+
     s3_options: {
       signature_version: ENV.fetch('S3_SIGNATURE_VERSION') { 'v4' },
       http_open_timeout: 5,
       http_read_timeout: 5,
       http_idle_timeout: 5,
+      retry_limit: 0,
     }
   )
 
@@ -47,6 +54,7 @@ if ENV['S3_ENABLED'] == 'true'
       endpoint: ENV['S3_ENDPOINT'],
       force_path_style: true
     )
+
     Paperclip::Attachment.default_options[:url] = ':s3_path_url'
   end
 
@@ -72,6 +80,7 @@ elsif ENV['SWIFT_ENABLED'] == 'true'
       openstack_region: ENV['SWIFT_REGION'],
       openstack_cache_ttl: ENV.fetch('SWIFT_CACHE_TTL') { 60 },
     },
+
     fog_directory: ENV['SWIFT_CONTAINER'],
     fog_host: ENV['SWIFT_OBJECT_URL'],
     fog_public: true
@@ -80,7 +89,7 @@ else
   Paperclip::Attachment.default_options.merge!(
     storage: :filesystem,
     use_timestamp: true,
-    path: (ENV['PAPERCLIP_ROOT_PATH'] || ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename',
-    url: (ENV['PAPERCLIP_ROOT_URL'] || '/system') + '/:class/:attachment/:id_partition/:style/:filename',
+    path: ENV.fetch('PAPERCLIP_ROOT_PATH', ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename',
+    url: ENV.fetch('PAPERCLIP_ROOT_URL', '/system') + '/:class/:attachment/:id_partition/:style/:filename',
   )
 end
diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb
index 24ba16ae36f5463d796a0345371c81fb29991e99..273cac9ca3d437c3108deeaf75a38095cf214d39 100644
--- a/config/initializers/rack_attack.rb
+++ b/config/initializers/rack_attack.rb
@@ -65,7 +65,7 @@ class Rack::Attack
     req.authenticated_user_id if req.post? && req.path.start_with?('/api/v1/media')
   end
 
-  throttle('throttle_media_proxy', limit: 30, period: 30.minutes) do |req|
+  throttle('throttle_media_proxy', limit: 30, period: 10.minutes) do |req|
     req.remote_ip if req.path.start_with?('/media_proxy')
   end
 
diff --git a/config/locales/activerecord.bg.yml b/config/locales/activerecord.bg.yml
index d0e375da96f10971c15d25ff3e8579573a9e6405..a1f60b45a797fc4d6d5b9701f9078dafc8f3f5c9 100644
--- a/config/locales/activerecord.bg.yml
+++ b/config/locales/activerecord.bg.yml
@@ -1 +1,7 @@
+---
 bg:
+  activerecord:
+    attributes:
+      poll:
+        expires_at: Краен срок
+        options: Избори
diff --git a/config/locales/activerecord.bn.yml b/config/locales/activerecord.bn.yml
index 152c698290639f4688cdb5184962daafc705fc52..e0e6ac90c2af66dba9aa6624de17525a9dba7ff5 100644
--- a/config/locales/activerecord.bn.yml
+++ b/config/locales/activerecord.bn.yml
@@ -1 +1,17 @@
+---
 bn:
+  activerecord:
+    attributes:
+      poll:
+        expires_at: শেষ হবে
+        options: বিকল্প
+    errors:
+      models:
+        account:
+          attributes:
+            username:
+              invalid: শুধুমাত্র অক্ষর, সংখ্যা এবং _ বেবহার করা যাবে
+        status:
+          attributes:
+            reblog:
+              taken: লেখাটি ইতিপূর্বে ছিল
diff --git a/config/locales/activerecord.br.yml b/config/locales/activerecord.br.yml
new file mode 100644
index 0000000000000000000000000000000000000000..77fc4f2a1125d7fbbfd66b2168a3fbc43fa7afe5
--- /dev/null
+++ b/config/locales/activerecord.br.yml
@@ -0,0 +1,13 @@
+---
+br:
+  activerecord:
+    attributes:
+      poll:
+        expires_at: Deiziad termen
+        options: Dibaboù
+    errors:
+      models:
+        account:
+          attributes:
+            username:
+              invalid: nemet lizherennoù, niverennoù ha isbarrennigoù
diff --git a/config/locales/activerecord.cy.yml b/config/locales/activerecord.cy.yml
index 19547df986a48c5a4338158cc1b2335376b7282a..92fba043fb191dcbb3c96220aa8aeb8237af27c0 100644
--- a/config/locales/activerecord.cy.yml
+++ b/config/locales/activerecord.cy.yml
@@ -3,7 +3,7 @@ cy:
   activerecord:
     attributes:
       poll:
-        expires_at: Terfyn
+        expires_at: Terfyn amser
         options: Dewisiadau
     errors:
       models:
diff --git a/config/locales/activerecord.da.yml b/config/locales/activerecord.da.yml
index 5e7266ef10b0630f49a2bc6a8ac453ab393860bf..e7367a4af7ab0b891ab761ab044eedf0e41f50c5 100644
--- a/config/locales/activerecord.da.yml
+++ b/config/locales/activerecord.da.yml
@@ -1,6 +1,10 @@
 ---
 da:
   activerecord:
+    attributes:
+      poll:
+        expires_at: Deadline
+        options: Valgmuligheder
     errors:
       models:
         account:
diff --git a/config/locales/activerecord.es-AR.yml b/config/locales/activerecord.es-AR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4cdd90278736f5006e50203d5cbd0be99033145e
--- /dev/null
+++ b/config/locales/activerecord.es-AR.yml
@@ -0,0 +1,17 @@
+---
+es-AR:
+  activerecord:
+    attributes:
+      poll:
+        expires_at: Fecha límite
+        options: Opciones
+    errors:
+      models:
+        account:
+          attributes:
+            username:
+              invalid: sólo letras, números y subguiones ("_")
+        status:
+          attributes:
+            reblog:
+              taken: del estado ya existe
diff --git a/config/locales/activerecord.et.yml b/config/locales/activerecord.et.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5679a91fe206a936fc660ec33069a4dbde640273
--- /dev/null
+++ b/config/locales/activerecord.et.yml
@@ -0,0 +1,17 @@
+---
+et:
+  activerecord:
+    attributes:
+      poll:
+        expires_at: Tähtaeg
+        options: Valikud
+    errors:
+      models:
+        account:
+          attributes:
+            username:
+              invalid: ainult tähtmärgid, numbrid ja alakriipsud
+        status:
+          attributes:
+            reblog:
+              taken: staatusel juba eksisteerib
diff --git a/config/locales/activerecord.hu.yml b/config/locales/activerecord.hu.yml
index bbc18bc33cb2ba3052fd09971182dd2d5c5a864f..ca5fbacb464edb0e8718352b241a69a9f9023109 100644
--- a/config/locales/activerecord.hu.yml
+++ b/config/locales/activerecord.hu.yml
@@ -11,3 +11,7 @@ hu:
           attributes:
             username:
               invalid: csak betűk, számok vagy alávonás
+        status:
+          attributes:
+            reblog:
+              taken: már létezik ehhez a bejegyzéshez
diff --git a/config/locales/activerecord.id.yml b/config/locales/activerecord.id.yml
index 9fa093e9dd94e34ed4f8f7c20d097bc04fc50db1..673d002e36a00b282608eefbe2d09978452a7fca 100644
--- a/config/locales/activerecord.id.yml
+++ b/config/locales/activerecord.id.yml
@@ -1,6 +1,10 @@
 ---
 id:
   activerecord:
+    attributes:
+      poll:
+        expires_at: Tenggat waktu
+        options: Pilihan
     errors:
       models:
         account:
diff --git a/config/locales/activerecord.mk.yml b/config/locales/activerecord.mk.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8b9144a988941222eba3d4c61c19a62a8b057db4
--- /dev/null
+++ b/config/locales/activerecord.mk.yml
@@ -0,0 +1 @@
+mk:
diff --git a/config/locales/activerecord.nn.yml b/config/locales/activerecord.nn.yml
new file mode 100644
index 0000000000000000000000000000000000000000..777f4e600f65daa6a550f2cf0f04551a49cf4fcd
--- /dev/null
+++ b/config/locales/activerecord.nn.yml
@@ -0,0 +1 @@
+nn:
diff --git a/config/locales/activerecord.pt-BR.yml b/config/locales/activerecord.pt-BR.yml
index 85150c1e75a7fc7ef4e1cd4d2d52d8e7182d0a1c..3f2b6aaaa4edd7ab6de5244615366414a169df50 100644
--- a/config/locales/activerecord.pt-BR.yml
+++ b/config/locales/activerecord.pt-BR.yml
@@ -10,7 +10,7 @@ pt-BR:
         account:
           attributes:
             username:
-              invalid: apenas letras, números e underscores
+              invalid: apenas letras, números e '_' são permitidos
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.pt.yml b/config/locales/activerecord.pt-PT.yml
similarity index 97%
rename from config/locales/activerecord.pt.yml
rename to config/locales/activerecord.pt-PT.yml
index 556fcfc4fd9caabbdf4e62d682a67feef16696e9..cbb053a9005979179c92c95980dcf562558ba5f0 100644
--- a/config/locales/activerecord.pt.yml
+++ b/config/locales/activerecord.pt-PT.yml
@@ -1,5 +1,5 @@
 ---
-pt:
+pt-PT:
   activerecord:
     errors:
       models:
diff --git a/config/locales/activerecord.sv.yml b/config/locales/activerecord.sv.yml
index 0ae4906d7d6d7b03075f0513bda530cf354ced09..8d142e7acd9aa0259e12980952b56e2358f83fe5 100644
--- a/config/locales/activerecord.sv.yml
+++ b/config/locales/activerecord.sv.yml
@@ -1,9 +1,13 @@
 ---
 sv:
   activerecord:
+    attributes:
+      poll:
+        expires_at: Tidsgräns
+        options: Val
     errors:
       models:
         account:
           attributes:
             username:
-              invalid: enbart bokstäver, siffror och understreck
+              invalid: endast bokstäver, siffror och understrykning
diff --git a/config/locales/activerecord.tr.yml b/config/locales/activerecord.tr.yml
index 1cc2d98760a7522c4d42458c590a7a8d8d2a436d..8ce55599cb3b96c656effaf35bdf402b3de8fcae 100644
--- a/config/locales/activerecord.tr.yml
+++ b/config/locales/activerecord.tr.yml
@@ -1,6 +1,10 @@
 ---
 tr:
   activerecord:
+    attributes:
+      poll:
+        expires_at: Son Teslim Tarihi
+        options: Seçenekler
     errors:
       models:
         account:
diff --git a/config/locales/activerecord.uk.yml b/config/locales/activerecord.uk.yml
index 00b2b8d8b11629bc63666cf8581e022d2ff07141..ffbf5934670ed42aac60879ff8a7ddbf7d07e79a 100644
--- a/config/locales/activerecord.uk.yml
+++ b/config/locales/activerecord.uk.yml
@@ -1,12 +1,16 @@
 ---
 uk:
   activerecord:
+    attributes:
+      poll:
+        expires_at: Кінцевий термін
+        options: Варіанти вибору
     errors:
       models:
         account:
           attributes:
             username:
-              invalid: тільки букви, цифри та нижні підкреслювання
+              invalid: тільки літери, цифри та підкреслення
         status:
           attributes:
             reblog:
diff --git a/config/locales/activerecord.zh-TW.yml b/config/locales/activerecord.zh-TW.yml
index cb82c0526113b986978e7a8f16024f9075f98428..4f938a7f5ebb29401dce3674ef8e522e4a0efe92 100644
--- a/config/locales/activerecord.zh-TW.yml
+++ b/config/locales/activerecord.zh-TW.yml
@@ -1 +1,17 @@
+---
 zh-TW:
+  activerecord:
+    attributes:
+      poll:
+        expires_at: 截止時間
+        options: 選擇
+    errors:
+      models:
+        account:
+          attributes:
+            username:
+              invalid: 只能有字母、數字及底線
+        status:
+          attributes:
+            reblog:
+              taken: 嘟文已經存在
diff --git a/config/locales/ar.yml b/config/locales/ar.yml
index eb6a5ef06c321c4f8a71f14eded723cf74a53705..2bf6789be1d7a65968e0c3b1b15fd35a97974501 100644
--- a/config/locales/ar.yml
+++ b/config/locales/ar.yml
@@ -5,18 +5,19 @@ ar:
     about_mastodon_html: ماستدون شبكة اجتماعية مبنية على أسُس بروتوكولات برمجيات الويب الحرة و مفتوحة المصدر. و هو لامركزي تمامًا كالبريد الإلكتروني.
     about_this: عن مثيل الخادوم هذا
     active_count_after: نشط
+    active_footnote: مستخدم نشيط شهريا (MAU)
     administered_by: 'يُديره:'
     api: واجهة برمجة التطبيقات
     apps: تطبيقات الأجهزة المحمولة
+    apps_platforms: إستخدم ماستودون في iOS، أندرويد وأنظمة أخرى
+    browse_directory: تصفح دليل الصفحات التعريفية وصفّي بحسب الإهتمام
+    browse_public_posts: تصفح تيارًا مباشرًا مِن منشورات عامة على ماستدون
     contact: للتواصل معنا
     contact_missing: لم يتم تعيينه
     contact_unavailable: غير متوفر
     discover_users: اكتشف مستخدِمين
     documentation: الدليل
-    extended_description_html: |
-      <h3>مكان جيد للقواعد</h3>
-      <p>لم يتم بعد إدخال الوصف الطويل.</p>
-    generic_description: "%{domain} هو سيرفر من بين سيرفرات الشبكة"
+    federation_hint_html: بواسطة حساب في %{instance} ستتمكن من تتبع أناس في أي خادم ماستدون وأكثر.
     get_apps: جرّب تطبيقا على الموبايل
     hosted_on: ماستدون مُستضاف على %{domain}
     learn_more: تعلم المزيد
@@ -34,6 +35,10 @@ ar:
     status_count_before: نشروا
     tagline: اتبع أصدقائك وصديقاتك واكتشف آخرين وأخريات
     terms: شروط الخدمة
+    unavailable_content: محتوى غير متوفر
+    unavailable_content_description:
+      domain: الخادم
+      reason: 'السبب:'
     user_count_after:
       few: مستخدمين
       many: مستخدمين
@@ -60,6 +65,7 @@ ar:
     media: الوسائط
     moved_html: "%{name} إنتقلَ إلى %{new_profile_link}:"
     network_hidden: إنّ المعطيات غير متوفرة
+    never_active: أبدا
     nothing_here: لا يوجد أي شيء هنا!
     people_followed_by: الأشخاص الذين يتبعهم %{name}
     people_who_follow: الأشخاص الذين يتبعون %{name}
@@ -79,7 +85,7 @@ ar:
       admin: المدير
       bot: روبوت
       moderator: مُشرِف
-    unavailable: الحساب غير متوفر
+    unavailable: الصفحة التعريفية غير متوفرة
     unfollow: إلغاء المتابعة
   admin:
     account_actions:
@@ -151,12 +157,12 @@ ar:
       outbox_url: رابط صندوق الصادر
       pending: في انتظار المراجعة
       perform_full_suspension: تعليق الحساب
-      profile_url: رابط الملف الشخصي
+      profile_url: رابط الصفحة التعريفية
       promote: ترقية
       protocol: البروتوكول
       public: عمومي
       push_subscription_expires: انتهاء الاشتراك ”PuSH“
-      redownload: تحديث الصفحة الشخصية
+      redownload: انعش الصفحة التعريفية
       reject: ارفض
       reject_all: ارفض الكل
       remove_avatar: حذف الصورة الرمزية
@@ -194,6 +200,7 @@ ar:
       username: اسم المستخدم
       warn: تحذير
       web: الويب
+      whitelisted: في القائمة البيضاء
     action_logs:
       actions:
         assigned_to_self_report: قام %{name} بتعيين التقرير %{target} لأنفسهم
@@ -229,19 +236,24 @@ ar:
       deleted_status: "(منشور محذوف)"
       title: سِجلّ التفتيش و المعاينة
     custom_emojis:
+      assign_category: اسند فئة
       by_domain: النطاق
       copied_msg: تم إنشاء نسخة محلية للإيموجي بنجاح
       copy: نسخ
       copy_failed_msg: فشلت عملية إنشاء نسخة محلية لهذا الإيموجي
+      create_new_category: انشئ فئة جديدة
       created_msg: تم إنشاء الإيموجي بنجاح!
       delete: حذف
       destroyed_msg: تمت عملية تدمير الإيموجي بنجاح!
       disable: تعطيل
+      disabled: مُعطَّل
       disabled_msg: تمت عملية تعطيل ذلك الإيموجي بنجاح
       emoji: إيموجي
       enable: تفعيل
+      enabled: مُشغَّل
       enabled_msg: تم تنشيط ذاك الإيموجي بنجاح
       image_hint: ملف PNG إلى غاية حجم 50 ك.ب
+      list: القائمة
       listed: مُدرَج
       new:
         title: إضافة إيموجي خاص جديد
@@ -249,22 +261,27 @@ ar:
       shortcode: الترميز المُصَغّر
       shortcode_hint: على الأقل حرفين، و فقط رموز أبجدية عددية و أسطر سفلية
       title: الإيموجي الخاصة
+      uncategorized: غير مصنّف
       unlisted: غير مدرج
       update_failed_msg: تعذرت عملية تحديث ذاك الإيموجي
       updated_msg: تم تحديث الإيموجي بنجاح!
       upload: رفع
     dashboard:
+      authorized_fetch_mode: الوضع الآمن
       backlog: الأعمال المتراكمة
       config: الإعداد
       feature_deletions: الحسابات المحذوفة
       feature_invites: روابط الدعوات
-      feature_profile_directory: دليل الحسابات
+      feature_profile_directory: دليل الملفات التعريفية
       feature_registrations: التسجيلات
       feature_relay: المُرحّل الفديرالي
+      feature_spam_check: مكافح البريد المزعج
       feature_timeline_preview: معاينة الخيط الزمني
       features: الميّزات
       hidden_service: الفيديرالية مع الخدمات الخفية
-      open_reports: فتح الشكاوى
+      open_reports: شكاوي مفتوحة
+      pending_tags: وسوم في انتظار المراجعة
+      pending_users: مستخدمين في انتظار مراجعة
       recent_users: أحدث المستخدِمين
       search: البحث النصي الكامل
       single_user_mode: وضع المستخدِم الأوحد
@@ -272,24 +289,33 @@ ar:
       space: المساحة المستخدَمة
       title: لوح المراقبة
       total_users: إجمالي المستخدِمين
-      trends: المؤشرات
+      trends: المتداوَلة
       week_interactions: تفاعُلات هذا الأسبوع
-      week_users_active: نشط هذا الأسبوع
+      week_users_active: نشطاء هذا الأسبوع
       week_users_new: مستخدِمين هذا الأسبوع
+      whitelist_mode: وضع القائمة البيضاء
+    domain_allows:
+      add_new: إضافة النطاق إلى القائمة البيضاء
+      created_msg: تمت إضافة النطاق إلى القائمة البيضاء بنجاح
+      destroyed_msg: تمت إزالة النطاق مِن القائمة البيضاء
+      undo: إزالة من القائمة البيضاء
     domain_blocks:
       add_new: إضافة حجب جديد لنطاق
       created_msg: إنّ حجب النطاق حيز التشغيل
       destroyed_msg: تم إلغاء الحجب المفروض على النطاق
       domain: النطاق
+      edit: تحرير النطاق المحظور
       new:
         create: إنشاء حظر
         hint: لن تمنع كتلة المجال إنشاء إدخالات حساب في قاعدة البيانات ، ولكنها ستطبق طرق الإشراف المحددة بأثر رجعي وتلقائي على هذه الحسابات.
         severity:
-          desc_html: "<strong>Silence</strong> سيجعل مشاركات الحساب غير مرئية لأي شخص لا يتبعها. <strong>Suspend</strong> سيزيل كل محتوى الحساب ووسائطه وبيانات ملفه الشخصي. Use <strong>None</strong>  إذا كنت تريد فقط رفض ملفات الوسائط."
+          desc_html: "<strong>Silence</strong> سيجعل مشاركات الحساب غير مرئية لأي شخص لا يتبعها. <strong>Suspend</strong> سيزيل كل محتوى الحساب ووسائطه وبيانات ملفه التعريفي. Use <strong>None</strong> إذا كنت تريد فقط رفض ملفات الوسائط."
           noop: لا شيء
           silence: كتم
           suspend: تعليق
         title: حجب نطاق جديد
+      private_comment: تعليق خاص
+      public_comment: تعليق للعلن
       reject_media: رفض ملفات الوسائط
       reject_media_hint: يزيل ملفات الوسائط المخزنة محليًا ويرفض تنزيل أي ملفات في المستقبل. غير ذي صلة للتعليق
       reject_reports: رفض التقارير
@@ -337,6 +363,8 @@ ar:
         all: كافتها
         limited: محدود
         title: الإشراف
+      private_comment: تعليق خاص
+      public_comment: تعليق للعلن
       title: الفديرالية
       total_blocked_by_us: المحجوبة مِن طرفنا
       total_followed_by_them: يُتابِعونها
@@ -413,6 +441,10 @@ ar:
       custom_css:
         desc_html: يقوم بتغيير المظهر بواسطة سي أس أس يُحمَّل على كافة الصفحات
         title: سي أس أس مخصص
+      domain_blocks:
+        all: للجميع
+      domain_blocks_rationale:
+        title: اظهر السبب
       hero:
         desc_html: معروض على الصفحة الأولى. لا يقل عن 600 × 100 بكسل. عند عدم التعيين ، تعود الصورة إلى النسخة المصغرة على سبيل المثال
         title: الصورة الرأسية
@@ -424,7 +456,7 @@ ar:
         title: إظهار الصور الحساسة في مُعاينات أوبن غراف
       profile_directory:
         desc_html: السماح للمستخدمين الكشف عن حساباتهم
-        title: تفعيل سجل الملفات الشخصية
+        title: تفعيل دليل الصفحات التعريفية
       registrations:
         closed_message:
           desc_html: يتم عرضه على الصفحة الرئيسية عندما يتم غلق تسجيل الحسابات الجديدة. يمكنكم إستخدام علامات الأيتش تي أم أل HTML
@@ -437,6 +469,7 @@ ar:
           title: المستخدِمون المصرح لهم لإرسال الدعوات
       registrations_mode:
         modes:
+          approved: طلب الموافقة لازم عند إنشاء حساب
           none: لا أحد يمكنه إنشاء حساب
           open: يمكن للجميع إنشاء حساب
         title: طريقة إنشاء الحسابات
@@ -458,6 +491,8 @@ ar:
         desc_html: يمكنك كتابة سياسة الخصوصية الخاصة بك ، شروط الخدمة أو غيرها من القوانين. يمكنك استخدام علامات HTML
         title: شروط الخدمة المخصصة
       site_title: اسم مثيل الخادم
+      spam_check_enabled:
+        title: مكافحة البريد المزعج
       thumbnail:
         desc_html: يستخدم للعروض السابقة عبر Open Graph و API. 1200x630px موصى به
         title: الصورة الرمزية المصغرة لمثيل الخادوم
@@ -465,12 +500,15 @@ ar:
         desc_html: عرض الخيط العمومي على صفحة الاستقبال
         title: مُعاينة الخيط العام
       title: إعدادات الموقع
+      trends:
+        title: الوسوم المتداولة
     statuses:
       back_to_account: العودة إلى صفحة الحساب
       batch:
         delete: حذف
         nsfw_off: تعيينه كمنشور غير حساس
         nsfw_on: تعيينه كمنشور حساس
+      deleted: محذوف
       failed_to_execute: خطأ في التفعيل
       media:
         title: الوسائط
@@ -478,20 +516,21 @@ ar:
       no_status_selected: لم يطرأ أي تغيير على أي منشور بما أنه لم يتم اختيار أي واحد
       title: منشورات الحساب
       with_media: تحتوي على وسائط
-    subscriptions:
-      callback_url: عاود الاتصال بالعنوان
-      confirmed: مؤكَّد
-      expires_in: تنتهي مدة صلاحيتها في
-      last_delivery: آخر إيداع
-      topic: الموضوع
     tags:
-      accounts: الحسابات
-      hidden: المخفية
-      hide: الإخفاء عن سجل الحسابات
+      context: السياق
+      directory: في دليل حسابات المستخدمين
+      in_directory: "%{count} في سجل حسابات المستخدمين"
+      last_active: آخر نشاط
+      most_popular: الأكثر شعبية
+      most_recent: الأحدث
       name: الوسم
+      review: حالة المراجعة
+      reviewed: مُراجَع
       title: الوسوم
-      unhide: إظهاره في سجل حسابات المستخدمين
-      visible: ظاهر
+      trending_right_now: متداول اللحظة
+      unique_uses_today: "%{count} منشورات اليوم"
+      unreviewed: غير مُراجَع
+      updated_msg: تم تحديث إعدادات الوسوم بنجاح
     title: الإدارة
     warning_presets:
       add_new: إضافة واحد جديد
@@ -508,13 +547,15 @@ ar:
       subject: تقرير جديد ل%{instance} (#%{id})
   appearance:
     advanced_web_interface: واجهة الويب المتقدمة
+    animations_and_accessibility: الإتاحة والحركة
     confirmation_dialogs: نوافذ التأكيد
+    discovery: استكشاف
     sensitive_content: محتوى حساس
   application_mailer:
     notification_preferences: تعديل خيارات البريد الإلكتروني
     salutation: "%{name}،"
     settings: 'تغيير تفضيلات البريد الإلكتروني: %{link}'
-    view_profile: عرض الملف الشخصي
+    view_profile: اعرض الصفحة التعريفية
     view_status: عرض المنشور
   applications:
     created: تم إنشاء التطبيق بنجاح
@@ -528,9 +569,12 @@ ar:
     apply_for_account: اطلب دعوة
     change_password: الكلمة السرية
     checkbox_agreement_html: أوافق على <a href="%{rules_path}" target="_blank">قواعد الخادم</a> و <a href="%{terms_path}" target="_blank">شروط الخدمة</a>
-    confirm_email: تأكيد عنوان البريد الإلكتروني
+    checkbox_agreement_without_rules_html: أوافق على <a href="%{terms_path}" target="_blank">شروط الخدمة</a>
     delete_account: حذف حساب
     delete_account_html: إن كنت ترغب في حذف حسابك يُمكنك <a href="%{path}">المواصلة هنا</a>. سوف يُطلَبُ منك التأكيد قبل الحذف.
+    description:
+      prefix_invited_by_user: يدعوك @%{name} للاتحاق بخادم ماستدون هذا!
+      prefix_sign_up: أنشئ حسابًا على ماستدون اليوم!
     didnt_get_confirmation: لم تتلق تعليمات التأكيد ؟
     forgot_password: نسيت كلمة المرور ؟
     invalid_reset_password_token: رمز إعادة تعيين كلمة المرور غير صالح أو منتهي الصلاحية. يرجى طلب واحد جديد.
@@ -548,6 +592,12 @@ ar:
     reset_password: إعادة تعيين كلمة المرور
     security: الأمان
     set_new_password: إدخال كلمة مرور جديدة
+    setup:
+      title: الضبط
+    status:
+      account_status: حالة الحساب
+      functional: حسابك جاهز.
+      redirecting_to: حسابك غير نشط لأنه تم تحويله حاليا إلى %{acct}.
     trouble_logging_in: هل صادفتكم مشكلة في الولوج؟
   authorize_follow:
     already_following: أنت تتابع بالفعل هذا الحساب
@@ -557,9 +607,13 @@ ar:
     following: 'مرحى! أنت الآن تتبع:'
     post_follow:
       close: أو يمكنك إغلاق هذه النافذة.
-      return: عرض الملف الشخصي للمستخدم
+      return: اظهر الملف التعريفي للمستخدم
       web: واصل إلى الويب
     title: إتباع %{acct}
+  challenge:
+    confirm: واصل
+    invalid_password: الكلمة السرية خاطئة
+    prompt: أكِّد الكلمة السرية للمواصلة
   datetime:
     distance_in_words:
       about_x_hours: "%{count}سا"
@@ -575,26 +629,30 @@ ar:
       x_months: "%{count} شه"
       x_seconds: "%{count}Ø«"
   deletes:
-    bad_password_msg: محاولة جيدة يا هاكرز! كلمة السر خاطئة
+    challenge_not_passed: المعلومات التي أدخلتها لم تكن صحيحة
     confirm_password: قم بإدخال كلمتك السرية الحالية للتحقق من هويتك
+    confirm_username: ادخل اسم المستخدم الخاص بك لتأكيد الإجراء
     proceed: حذف حساب
     success_msg: تم حذف حسابك بنجاح
-    warning_title: توافر المحتوى المنشور و المبعثَر
+    warning:
+      before: 'يرجى قراءة هذه الملاحظات بتأنّي قبل المواصلة:'
+      data_removal: سوف تُحذَف منشوراتك والبيانات الأخرى نهائيا
+      email_change_html: بإمكانك <a href="%{path}">تغيير عنوان بريدك الإلكتروني</a> دون أن يُحذف حسابك
+      irreversible: لن يكون بإمكانك استرجاع أو إعادة تنشيط حسابك
+      more_details_html: للمزيد مِن التفاصيل ، يرجى الإطلاع على <a href="%{terms_path}">سياسة الخصوصية</a>.
+      username_available: سيصبح اسم مستخدمك متوفرا ثانية
+      username_unavailable: سيبقى اسم المستخدم الخاص بك غير متوفر
   directories:
-    directory: سِجلّ الحسابات
-    enabled: إنّ حسابك الآن ضمن فهرس المستخدِمين.
+    directory: سِجلّ الصفحات التعريفية
     explanation: استكشف مستخدِمين آخرين حسب المواضيع التي تهمهم
     explore_mastodon: استكشف %{title}
-    people:
-      few: "%{count} شخص"
-      many: "%{count} شخص"
-      one: "%{count} شخص"
-      other: "%{count} شخص"
-      two: "%{count} شخص"
-      zero: "%{count} شخص"
+  domain_validator:
+    invalid_domain: ليس بإسم نطاق صالح
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': ليس لك الصلاحيات الكافية لعرض هذه الصفحة.
     '404': إنّ الصفحة التي تبحث عنها لا وجود لها أصلا.
+    '406': This page is not available in the requested format.
     '410': إنّ الصفحة التي تبحث عنها لم تعد موجودة.
     '422':
       content: فشل التحقق الآمن. ربما منعتَ كعكات الكوكيز؟
@@ -603,6 +661,7 @@ ar:
     '500':
       content: نحن متأسفون، لقد حدث خطأ ما مِن جانبنا.
       title: هذه الصفحة خاطئة
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: يرجى تفعيل الجافا سكريبت لاستخدام تطبيق الويب لماستدون، أو عِوض ذلك قوموا بتجريب إحدى <a href="%{apps_path}">التطبيقات الأصلية</a> الدّاعمة لماستدون على منصّتكم.
   existing_username_validator:
     not_found_multiple: تعذر العثور على %{usernames}
@@ -622,7 +681,7 @@ ar:
     mutes: قُمتَ بكتم
     storage: ذاكرة التخزين
   featured_tags:
-    add_new: إضافة واحد
+    add_new: أضف واحدًا جديدا
   filters:
     contexts:
       home: الخيط الزمني الرئيسي
@@ -643,6 +702,7 @@ ar:
     developers: المطورون
     more: المزيد…
     resources: الموارد
+    trending_now: المتداولة الآن
   generic:
     all: الكل
     changes_saved_msg: تم حفظ التعديلات بنجاح!
@@ -717,9 +777,21 @@ ar:
       too_many: لا يمكن إرفاق أكثر من 4 ملفات
   migrations:
     acct: username@domain للحساب الجديد
-    currently_redirecting: 'تم تحويل رابط ملفك الشخصي إلى:'
-    proceed: حفظ
-    updated_msg: تم تحديث إعدادات ترحيل حسابك بنجاح!
+    cancel: ألغِ التوجيه
+    cancelled_msg: تم إلغاء التوجيه بنجاح.
+    errors:
+      move_to_self: لا يمكنه أن يكون الحساب الحالي
+      not_found: تعذر العثور عليه
+      on_cooldown: إنّك في مرحلة الجمود
+    followers_count: المتابِعين عند الإنتقال
+    incoming_migrations: الانتقال مِن حساب آخر
+    not_redirecting: حاليا ، حسابك لا يقوم بالتحويل إلى أي حساب آخر.
+    past_migrations: التهجيرات السابقة
+    proceed_with_move: انقل مشارِكيك
+    redirecting_to: حسابك موجَّه إلى %{acct}.
+    set_redirect: تعين إعادة التوجيه
+    warning:
+      before: 'يرجى قراءة هذه الملاحظات بتأنّي قبل المواصلة:'
   moderation:
     title: الإشراف
   notification_mailer:
@@ -784,6 +856,7 @@ ar:
     errors:
       already_voted: لقد قمت بالتصويت على استطلاع الرأي هذا مِن قبل
       duplicate_options: يحتوي على عناصر مكررة
+      duration_too_long: بعيد جدا في المستقبَل
       duration_too_short: مبكّر جدا
       expired: لقد انتهى استطلاع الرأي
   preferences:
@@ -796,6 +869,7 @@ ar:
     last_active: آخر نشاط
     most_recent: الأحدث
     moved: هاجر
+    mutual: متبادل
     primary: رئيسي
     relationship: العلاقة
     remove_selected_domains: احذف كافة المتابِعين القادمين مِن النطاقات المختارة
@@ -818,10 +892,9 @@ ar:
     reply:
       proceed: المواصلة إلى الرد
       prompt: 'ترغب في الرد على هذا التبويق:'
-  remote_unfollow:
-    error: خطأ
-    title: العنوان
-    unfollowed: غير متابَع
+  scheduled_statuses:
+    over_total_limit: لقد بلغت حد الـ %{limit} مِن التبويقات المبرمَجة
+    too_soon: يجب أن يكون تاريخ البرمجة في المستقبَل
   sessions:
     activity: آخر نشاط
     browser: المتصفح
@@ -871,7 +944,7 @@ ar:
     back: عودة إلى ماستدون
     delete: حذف الحسابات
     development: التطوير
-    edit_profile: تعديل الملف الشخصي
+    edit_profile: عدّل الصفحة التعريفية
     export: تصدير البيانات
     featured_tags: الوسوم الشائعة
     identity_proofs: دلائل الهوية
@@ -880,9 +953,11 @@ ar:
     migrate: تهجير الحساب
     notifications: الإخطارات
     preferences: التفضيلات
-    profile: الملف الشخصي
+    profile: الملف التعريفي
     relationships: المتابِعون والمتابَعون
     two_factor_authentication: المُصادقة بخُطوَتَيْن
+  spam_check:
+    spam_detected: هذا إبلاغ تلقائي. تم اكتشاف منشورات عشوائية غير مرغوب فيها.
   statuses:
     attached:
       description: 'مُرفَق: %{attached}'
@@ -918,6 +993,13 @@ ar:
       private: لا يمكن تدبيس تبويق لم يُنشر للعامة
       reblog: لا يمكن تثبيت ترقية
     poll:
+      total_votes:
+        few: "%{count} أصوات"
+        many: "%{count} أصوات"
+        one: صوت واحد %{count}
+        other: "%{count} صوتا"
+        two: صوتين %{count}
+        zero: بدون صوت %{count}
       vote: صوّت
     show_more: أظهر المزيد
     sign_in_to_participate: قم بتسجيل الدخول للمشاركة في هذه المحادثة
@@ -939,6 +1021,10 @@ ar:
     contrast: ماستدون (تباين عالٍ)
     default: ماستدون (داكن)
     mastodon-light: ماستدون (فاتح)
+  time:
+    formats:
+      default: "%b %d, %Y, %H:%M"
+      month: "%b %Y"
   two_factor_authentication:
     code_hint: قم بإدخال الرمز المُوَلّد عبر تطبيق المصادقة للتأكيد
     description_html: في حال تفعيل <strong>المصادقة بخطوتين </strong>، فتسجيل الدخول يتطلب منك أن يكون بحوزتك هاتفك النقال قصد توليد الرمز الذي سيتم إدخاله.
@@ -961,6 +1047,7 @@ ar:
       title: المغادرة بأرشيف الحساب
     warning:
       review_server_policies: مراجعة شروط السيرفر
+      statuses: 'خصيصا لـ:'
       subject:
         disable: تم تجميد حسابك %{acct}
         none: تحذير إلى %{acct}
@@ -968,10 +1055,11 @@ ar:
       title:
         disable: الحساب مُجمَّد
         none: تحذير
+        silence: الحساب محدود
         suspend: الحساب مُعلَّق
     welcome:
-      edit_profile_action: تهيئة الملف الشخصي
-      edit_profile_step: يُمكنك·كي تخصيص ملفك الشخصي عن طريق تحميل صورة رمزية ورأسية و بتعديل اسمك·كي العلني وأكثر. و إن أردت·تي معاينة المتابِعين و المتابعات الجُدد قبيل السماح لهم·ن بمتابَعتك فيمكنك·كي تأمين حسابك·كي.
+      edit_profile_action: تهيئة الملف التعريفي
+      edit_profile_step: يُمكنك·كي تخصيص صفحتك التعريفية عن طريق تحميل صورة رمزية ورأسية و بتعديل اسمك·كي العلني وأكثر. و إن أردت·تي معاينة المتابِعين و المتابعات الجُدد قبيل السماح لهم·ن بمتابَعتك فيمكنك·كي تأمين حسابك·كي.
       explanation: ها هي بعض النصائح قبل بداية الاستخدام
       final_action: اشرَع في النشر
       final_step: |-
diff --git a/config/locales/ast.yml b/config/locales/ast.yml
index ec545ca5786f965aa5998258571b1b3707c11129..ed8aef235433dc643b604981dc2de8cf48c7fb5e 100644
--- a/config/locales/ast.yml
+++ b/config/locales/ast.yml
@@ -8,9 +8,6 @@ ast:
     contact_missing: Nun s'afitó
     contact_unavailable: N/D
     documentation: Documentación
-    extended_description_html: |
-      <h3>Un llugar bonu pa les regles</h3>
-      <p>Entá nun se configuró la descripción estendida.</p>
     hosted_on: Mastodon ta agospiáu en %{domain}
     learn_more: Deprendi más
     source_code: Códigu fonte
@@ -131,21 +128,19 @@ ast:
       half_a_minute: Púramente agora
       less_than_x_seconds: Púramente agora
   deletes:
-    bad_password_msg: "¡Bon intentu, crackers! Contraseña incorreuta"
     confirm_password: Introduz la contraseña pa verificar la to identidá
-  directories:
-    people:
-      one: "%{count} persona"
-      other: "%{count} persones"
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Nun tienes permisu pa ver esta páxina.
     '404': La páxina que tabes guetando nun esiste.
+    '406': This page is not available in the requested format.
     '410': La páxina que tabes guetando yá nun esiste.
     '422':
       content: Falló la verificación de seguranza. ¿Tas bloquiando les cookies?
       title: Falló la verificación de seguranza
     '429': Ficiéronse milenta solicitúes
     '500': 
+    '503': The page could not be served due to a temporary server failure.
   exports:
     archive_takeout:
       date: Data
@@ -201,7 +196,6 @@ ast:
       too_many: Nun puen axuntase más de 4 ficheros
   migrations:
     acct: nome_usuariu@dominiu de la cuenta nueva
-    proceed: Guardar
   notification_mailer:
     digest:
       body: Equí hai un resume de los mensaxes que nun viesti dende la última visita'l %{since}
@@ -227,8 +221,6 @@ ast:
     no_account_html: "¿Nun tienes una cuenta? Pues <a href='%{sign_up_path}' target='_blank'>rexistrate equí</a>"
     proceed: Siguir
     prompt: 'Vas siguir a:'
-  remote_unfollow:
-    error: Fallu
   sessions:
     browser: Restolador
     browsers:
diff --git a/config/locales/bg.yml b/config/locales/bg.yml
index e11340542fddec96cdf836bca27db8c8b236d27e..85431bc8dfd0cbd6f9f34d820e7ba9cc569a0052 100644
--- a/config/locales/bg.yml
+++ b/config/locales/bg.yml
@@ -48,12 +48,15 @@ bg:
       x_months: "%{count} м"
       x_seconds: "%{count} сек"
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': You don't have permission to view this page.
     '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
     '410': The page you were looking for doesn't exist here anymore.
     '422': 
     '429': Throttled
     '500': 
+    '503': The page could not be served due to a temporary server failure.
   exports:
     blocks: Вашите блокирания
     follows: Вашите следвания
diff --git a/config/locales/bn.yml b/config/locales/bn.yml
index b3eb0bd624a6874bef540e85eeb401640c4072c1..9fdc3ed297f70e3263b7dde61413bf5513c60110 100644
--- a/config/locales/bn.yml
+++ b/config/locales/bn.yml
@@ -17,11 +17,7 @@ bn:
     contact_unavailable: প্রযোজ্য নয়
     discover_users: ব্যবহারকারীদের দেখুন
     documentation: ব্যবহারবিলি
-    extended_description_html: |
-      <h3>নিয়মের জন্য উপযুক্ত জায়গা</h3>
-      <p>বিস্তারিত বিবরণ এখনো যুক্ত করা হয়নি</p>
     federation_hint_html: "%{instance}তে একটা নিবন্ধন থাকলে আপনি যেকোনো মাস্টাডন বা এধরণের অন্যান্য সার্ভারের মানুষের সাথে যুক্ত হতে পারবেন ।"
-    generic_description: নেটওয়ার্কের ভেতরে %{domain} একটি সার্ভার
     get_apps: মোবাইল এপ্প একটা ব্যবহার করতে পারেন
     hosted_on: এই মাস্টাডনটি আছে %{domain} এ
     learn_more: বিস্তারিত জানুন
@@ -140,12 +136,15 @@ bn:
       pending: পয্র্যবেক্ষণের অপেক্ষায় আছে
       perform_full_suspension: বাতিল করা
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': You don't have permission to view this page.
     '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
     '410': The page you were looking for doesn't exist here anymore.
     '422': 
     '429': Throttled
     '500': 
+    '503': The page could not be served due to a temporary server failure.
   invites:
     expires_in:
       '1800': 30 minutes
diff --git a/config/locales/br.yml b/config/locales/br.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3710084e7df2f382222859c0539bc7c83bdf9cb0
--- /dev/null
+++ b/config/locales/br.yml
@@ -0,0 +1,20 @@
+---
+br:
+  errors:
+    '400': The request you submitted was invalid or malformed.
+    '403': You don't have permission to view this page.
+    '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
+    '410': The page you were looking for doesn't exist here anymore.
+    '422': 
+    '429': Throttled
+    '500': 
+    '503': The page could not be served due to a temporary server failure.
+  invites:
+    expires_in:
+      '1800': 30 minutes
+      '21600': 6 hours
+      '3600': 1 hour
+      '43200': 12 hours
+      '604800': 1 week
+      '86400': 1 day
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index a5d96cc1caa55f6e83f680e01ab29d057a645abc..1d815adcfb2d2157005875d4304a1276e7821db3 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -17,13 +17,12 @@ ca:
     contact_unavailable: N/D
     discover_users: Descobreix usuaris
     documentation: Documentació
-    extended_description_html: |
-      <h3>Un bon lloc per les regles</h3>
-      <p>Encara no s'ha configurat la descripció ampliada.</p>
     federation_hint_html: Amb un compte de %{instance} podràs seguir persones de qualsevol servidor Mastodon i altres.
-    generic_description: "%{domain} és un servidor a la xarxa"
     get_apps: Prova una aplicació mòbil
     hosted_on: Mastodon allotjat a %{domain}
+    instance_actor_flash: |
+      Aquest compte és un actor virtual utilitzat per a representar al propi servidor i no cap usuari individual.
+      S'utilitza per a propòsits de federació i no ha de ser bloquejat si no voleu bloquejar tota la instància, en aquest cas hauríeu d'utilitzar un bloqueig de domini.
     learn_more: Més informació
     privacy_policy: Política de privacitat
     see_whats_happening: Mira què està passant
@@ -35,6 +34,14 @@ ca:
     status_count_before: Que han escrit
     tagline: Segueix els teus amics i descobreix-ne de nous
     terms: Termes del servei
+    unavailable_content: Contingut no disponible
+    unavailable_content_description:
+      domain: Servidor
+      reason: Raó
+      rejecting_media: 'Els arxius multimèdia d''aquests servidors no seran processats o emmagatzemats i cap miniatura serà mostrada, requerint clic manual a través de l''arxiu original:'
+      silenced: 'Les publicacions d''aquests servidors seran amagades en les línies de temps públiques i converses, i cap notificació serà generada de les interaccions dels seus usuaris, llevat que estiguis seguint-los:'
+      suspended: 'Cap dada d''aquests servidors serà processada, emmagatzemada o intercanviada, fent impossible qualsevol interacció o comunicació amb els usuaris d''aquests servidors:'
+    unavailable_content_html: Mastodon generalment et permet per veure contingut i interaccionar amb usuaris de qualsevol altre servidor en el fedivers. Aquestes són les excepcions que s'han fet en aquest servidor particular.
     user_count_after:
       one: usuari
       other: usuaris
@@ -42,6 +49,8 @@ ca:
     what_is_mastodon: Què és Mastodon?
   accounts:
     choices_html: 'Eleccions de %{name}:'
+    endorsements_hint: Pots recomanar persones que segueixes a l'interfície de web, que apareixeran aquí.
+    featured_tags_hint: Pots presentar etiquetes específiques que seràn mostrades aquí.
     follow: Segueix
     followers:
       one: Seguidor
@@ -53,6 +62,7 @@ ca:
     media: Mèdia
     moved_html: "%{name} s'ha mogut a %{new_profile_link}:"
     network_hidden: Aquesta informació no està disponible
+    never_active: Mai
     nothing_here: No hi ha res aquí!
     people_followed_by: Usuaris seguits per %{name}
     people_who_follow: Usuaris que segueixen %{name}
@@ -183,6 +193,7 @@ ca:
       username: Nom d'usuari
       warn: Avís
       web: Web
+      whitelisted: Llista blanca
     action_logs:
       actions:
         assigned_to_self_report: "%{name} han assignat l'informe %{target} a ells mateixos"
@@ -218,19 +229,24 @@ ca:
       deleted_status: "(toot suprimit)"
       title: Registre d'auditoria
     custom_emojis:
+      assign_category: Assigna una categoria
       by_domain: Domini
       copied_msg: S'ha creat correctament la còpia local de l'emoji
       copy: Copia
       copy_failed_msg: No s'ha pogut fer una còpia local d'aquest emoji
+      create_new_category: Crea una categoria nova
       created_msg: Emoji creat amb èxit!
       delete: Suprimeix
       destroyed_msg: Emojo s'ha destruït amb èxit!
       disable: Inhabilita
+      disabled: Desactivat
       disabled_msg: S'ha inhabilitat l'emoji amb èxit
       emoji: Emoji
       enable: Habilita
+      enabled: Activat
       enabled_msg: S'ha habilitat amb èxit emoji
       image_hint: PNG de fins a 50 KB
+      list: Llista
       listed: Enumerat
       new:
         title: Afegeix emoji personalitzat nou
@@ -238,11 +254,14 @@ ca:
       shortcode: Codi curt
       shortcode_hint: Com a mínim 2 caràcters, només caràcters alfanumèrics i guions baixos
       title: Emojis personalitzats
+      uncategorized: Sense categoria
+      unlist: No llistat
       unlisted: Sense classificar
       update_failed_msg: No s'ha pogut actualitzar aquest emoji
       updated_msg: Emoji s'ha actualitzat correctament!
       upload: Carrega
     dashboard:
+      authorized_fetch_mode: Mode d'obtenció autoritzat
       backlog: treballs en espera
       config: Configuració
       feature_deletions: Supressions del compte
@@ -250,10 +269,13 @@ ca:
       feature_profile_directory: Directori de perfils
       feature_registrations: Registres
       feature_relay: Relay de la Federació
+      feature_spam_check: Anti-spam
       feature_timeline_preview: Vista previa de línia de temps
       features: Característiques
       hidden_service: Federació amb serveis ocults
       open_reports: informes oberts
+      pending_tags: etiquetes pendents de revisar
+      pending_users: usuaris pendents de revisar
       recent_users: Usuaris recents
       search: Cerca de text complet
       single_user_mode: Mode d'usuari únic
@@ -265,11 +287,18 @@ ca:
       week_interactions: interaccions d'aquesta setmana
       week_users_active: usuaris actius aquesta setmana
       week_users_new: nous usuaris aquest setmana
+      whitelist_mode: Mode llista blanca
+    domain_allows:
+      add_new: Dominis autoritzats
+      created_msg: El domini ha estat correctament autoritzat
+      destroyed_msg: S'ha esborrat el domini de la llista blanca
+      undo: Treure de la llista blanca
     domain_blocks:
       add_new: Afegir nou bloqueig de domini
       created_msg: El bloqueig de domini ara s'està processant
       destroyed_msg: El bloqueig de domini s'ha desfet
       domain: Domini
+      edit: Editar el bloqueig del domini
       existing_domain_block_html: Ja has imposat uns limits més estrictes a %{name}, l'hauries de <a href="%{unblock_url}">desbloquejar-lo</a> primer.
       new:
         create: Crea un bloqueig
@@ -280,6 +309,10 @@ ca:
           silence: Silenci
           suspend: Suspensió
         title: Bloqueig de domini nou
+      private_comment: Comentari privat
+      private_comment_hint: Comentari sobre aquesta limitació del domini per a ús intern dels moderadors.
+      public_comment: Comentari públic
+      public_comment_hint: Comentari pel públic general sobre aquesta limitació del domini, si la publicació de la llista de limitacions de domini està habilitada.
       reject_media: Rebutja els fitxers multimèdia
       reject_media_hint: Elimina els fitxers multimèdia emmagatzemats localment i impedeix baixar-ne cap en el futur. Irrellevant en les suspensions
       reject_reports: Rebutja informes
@@ -299,6 +332,7 @@ ca:
         title: Desfés el bloqueig de domini de %{domain}
         undo: Desfés
       undo: Desfés el bloqueig del domini
+      view: Veure el bloqueig del domini
     email_domain_blocks:
       add_new: Afegir nou
       created_msg: S'ha creat el bloc de domini de correu electrònic
@@ -322,6 +356,8 @@ ca:
         all: Totes
         limited: Limitades
         title: Moderació
+      private_comment: Comentari privat
+      public_comment: Comentari públic
       title: Federació
       total_blocked_by_us: Bloquejats per nosaltres
       total_followed_by_them: Seguits per ells
@@ -351,6 +387,7 @@ ca:
       pending: S'està esperant l'aprovació del relay
       save_and_enable: Desa i activa
       setup: Configura una connexió de relay
+      signatures_not_enabled: Els relays no treballaran correctament mentre estiguin habilitats el mode segur o llista blanca
       status: Estat
       title: Relays
     report_notes:
@@ -399,6 +436,16 @@ ca:
       custom_css:
         desc_html: Modifica l'aspecte amb CSS carregat a cada pàgina
         title: CSS personalitzat
+      default_noindex:
+        desc_html: Afecta a tots els usuaris que no han canviat aquest ajustament ells mateixos
+        title: Configurar per defecte als usuaris fora de l'indexació dels motor de cerca
+      domain_blocks:
+        all: Per a tothom
+        disabled: Per a ningú
+        title: Mostra els bloquejos de domini
+        users: Per als usuaris locals en línia
+      domain_blocks_rationale:
+        title: Mostra el raonament
       hero:
         desc_html: Es mostra en pàgina frontal. Recomanat al menys 600x100px. Si no es configura es mostrarà el del servidor
         title: Imatge d’heroi
@@ -449,6 +496,9 @@ ca:
         desc_html: Pots escriure la teva pròpia política de privadesa, els termes del servei o d'altres normes legals. Pots utilitzar etiquetes HTML
         title: Termes del servei personalitzats
       site_title: Nom del servidor
+      spam_check_enabled:
+        desc_html: Mastodon pot auto-silenciar i informar automàticament de comptes basat en mesures com ara la detecció de comptes que envien missatges repetits no sol·licitats. Pot haver-hi falsos positius.
+        title: Anti-spam
       thumbnail:
         desc_html: S'utilitza per obtenir visualitzacions prèvies a través d'OpenGraph i API. Es recomana 1200x630px
         title: Miniatura del servidor
@@ -456,12 +506,19 @@ ca:
         desc_html: Mostra la línia de temps pública a la pàgina inicial
         title: Vista prèvia de la línia de temps
       title: Configuració del lloc
+      trendable_by_default:
+        desc_html: Afecta a les etiquetes que no s'havien rebutjat prèviament
+        title: Permet que les etiquetes passin a la tendència sense revisió prèvia
+      trends:
+        desc_html: Mostra públicament les etiquetes revisades anteriorment que actualment estan en tendència
+        title: Etiquetes tendència
     statuses:
       back_to_account: Torna a la pàgina del compte
       batch:
         delete: Suprimeix
         nsfw_off: Marcar com a no sensible
         nsfw_on: Marcar com a sensible
+      deleted: Esborrats
       failed_to_execute: No s'ha pogut executar
       media:
         title: Contingut multimèdia
@@ -469,21 +526,24 @@ ca:
       no_status_selected: No s’han canviat els estatus perquè cap no ha estat seleccionat
       title: Estats del compte
       with_media: Amb contingut multimèdia
-    subscriptions:
-      callback_url: URL de retorn
-      confirmed: Confirmat
-      expires_in: Expira en
-      last_delivery: Últim lliurament
-      title: WebSub
-      topic: Tema
     tags:
-      accounts: Comptes
-      hidden: Amagat
-      hide: Ocult del directori
+      accounts_today: Usos únics actuals
+      accounts_week: Usos únics d'aquesta setmana
+      breakdown: Desglossament de l’ús actual per origen
+      context: Context
+      directory: En el directori
+      in_directory: "%{count} en el directori"
+      last_active: Darrer actiu
+      most_popular: Més populars
+      most_recent: Més recent
       name: Etiqueta
+      review: Revisar l'estat
+      reviewed: Revisades
       title: Etiquetes
-      unhide: Mostra en el directori
-      visible: Visible
+      trending_right_now: Actualment en tendència
+      unique_uses_today: "%{count} publicacions d'avui"
+      unreviewed: Sense revisar
+      updated_msg: Ajustaments d'etiquetes actualitzats amb èxit
     title: Administració
     warning_presets:
       add_new: Afegir nou
@@ -499,11 +559,21 @@ ca:
       body: "%{reporter} ha informat de %{target}"
       body_remote: Algú des de el domini %{domain} ha informat sobre %{target}
       subject: Informe nou per a %{instance} (#%{id})
+    new_trending_tag:
+      body: 'La etiqueta #%{name} està actualment en tendència però no s''ha revisat prèviament. No es mostrarà públicament tret que ho permetis o guardis el formulari tal com està per a no tornar a escoltar res al respecte.'
+      subject: Nova etiqueta pendent de revisió a %{instance} (#%{name})
+  aliases:
+    add_new: Crear àlies
+    created_msg: Nou àlies creat amb èxit. Ara pots iniciar el moviment des de'l compte vell.
+    deleted_msg: Àlies eliminat amb èxit. Moure't des d'aquell compte a aquests ja no serà possible.
+    hint_html: Si et vols moure des d'un altre compte a aquest, aquí pots crear un àlies, el qual és requerit abans que puguis procedir a moure els seguidors del compte vell a aquest. Aquesta acció és per si mateixa <strong>inofensiva i reversible</strong>. <strong>La migració del compte és iniciada des de'l compte vell</strong>.
+    remove: Desvincular l'àlies
   appearance:
     advanced_web_interface: Interfície web avançada
     advanced_web_interface_hint: 'Si vols fer ús de tota l''amplada de la teva pantalla, l''interfície web avançada et permet configurar diverses columnes per a veure molta més informació al mateix temps: Inici, notificacions, línia de temps federada i qualsevol número de llistes i etiquetes.'
     animations_and_accessibility: Animacions i accessibilitat
     confirmation_dialogs: Diàlegs de confirmació
+    discovery: Descobriment
     sensitive_content: Contingut sensible
   application_mailer:
     notification_preferences: Canvia les preferències de correu
@@ -524,9 +594,13 @@ ca:
     apply_for_account: Demana una invitació
     change_password: Contrasenya
     checkbox_agreement_html: Estic d'acord amb les <a href="%{rules_path}" target="_blank">normes del servidor</a> i <a href="%{terms_path}" target="_blank"> els termes del servei</a>
-    confirm_email: Confirmar correu electrònic
+    checkbox_agreement_without_rules_html: Acepto els <a href="%{terms_path}" target="_blank">termes del servei</a>
     delete_account: Suprimeix el compte
     delete_account_html: Si vols suprimir el compte pots <a href="%{path}">fer-ho aquí</a>. Se't demanarà confirmació.
+    description:
+      prefix_invited_by_user: "@%{name} t'ha invitat a unir-te a aquest servidor de Mastodon!"
+      prefix_sign_up: Registra't avui a Mastodon!
+      suffix: Amb un compte seràs capaç de seguir persones, publicar i intercanviar missatges amb usuaris de qualsevol servidor de Mastodon i més!
     didnt_get_confirmation: No has rebut el correu de confirmació?
     forgot_password: Has oblidat la contrasenya?
     invalid_reset_password_token: L'enllaç de restabliment de la contrasenya no és vàlid o ha caducat. Torna-ho a provar.
@@ -544,6 +618,16 @@ ca:
     reset_password: Restableix la contrasenya
     security: Seguretat
     set_new_password: Estableix una contrasenya nova
+    setup:
+      email_below_hint_html: Si l’adreça de correu electrònic següent és incorrecta, podeu canviar-la aquí i rebre un nou correu electrònic de confirmació.
+      email_settings_hint_html: El correu electrònic de confirmació es va enviar a %{email}. Si aquesta adreça de correu electrònic no és correcta, la podeu canviar a la configuració del compte.
+      title: Configuració
+    status:
+      account_status: Estat del compte
+      confirming: Esperant que es completi la confirmació del correu electrònic.
+      functional: El teu compte és plenament operatiu.
+      pending: La vostra sol·licitud està pendent de revisió pel nostre personal. Això pot trigar una mica. Rebreu un correu electrònic quan sigui aprovada.
+      redirecting_to: El teu compte és inactiu perquè actualment està redirigint a %{acct}.
     trouble_logging_in: Problemes per iniciar la sessió?
   authorize_follow:
     already_following: Ja estàs seguint aquest compte
@@ -556,6 +640,11 @@ ca:
       return: Mostra el perfil de l'usuari
       web: Vés a la web
     title: Segueix %{acct}
+  challenge:
+    confirm: Continua
+    hint_html: "<strong>Pista:</strong> No et preguntarem un altre cop la teva contrasenya en la pròxima hora."
+    invalid_password: Contrasenya no vàlida
+    prompt: Confirmi la contrasenya per a continuar
   datetime:
     distance_in_words:
       about_x_hours: "%{count} h"
@@ -571,26 +660,33 @@ ca:
       x_months: "%{count} mesos"
       x_seconds: "%{count} s"
   deletes:
-    bad_password_msg: Bon intent hackers! La contrasenya no és correcta
+    challenge_not_passed: L'informació que has entrat no és correcte
     confirm_password: Introdueix la contrasenya actual per a verificar la identitat
-    description_html: Això eliminarà de forma <strong>irreversible i permanent</strong> el contingut del teu compte i el desactivarà. El teu nom d'usuari romandrà reservat per evitar que algú volgués fer-se passar per tu.
+    confirm_username: Escriu el teu nom d'usuari per a confirmar el procediment
     proceed: Suprimeix el compte
     success_msg: El compte s'ha eliminat correctament
-    warning_html: Només és garantida l'eliminació del contingut d'aquest servidor en particular. El contingut que s'ha compartit àmpliament deixa petjades. Els servidors fora de línia i els que ja no estan subscrits no actualitzaran les seves bases de dades.
-    warning_title: Disponibilitat de contingut disseminat
+    warning:
+      before: 'Abans de procedir si us plau llegeix amb cura aquestes notes:'
+      caches: El contingut que ha estat memoritzat en la memòria cau per altres servidors pot persistir
+      data_removal: Les teves publicacions i altres dades seran permanentment eliminades
+      email_change_html: Pots <a href="%{path}">canviar la teva adreça de correu electrònic</a> sense eliminar el teu compte
+      email_contact_html: Si encara no arriba pots enviar un correu electrònic a <a href="mailto:%{email}">%{email}</a> per a demanar ajuda
+      email_reconfirmation_html: Si no estàs rebent el correu electrònic de confirmació <a href="%{path}">pots demanar-lo un altre cop</a>
+      irreversible: No seràs capaç de restaurar o reactivar el teu compte
+      more_details_html: Per a més detalls, llegeix la <a href="%{terms_path}">política de privadesa</a>.
+      username_available: El teu nom d'usuari esdevindrà altre cop disponible
+      username_unavailable: El teu nom d'usuari quedarà inutilitzable
   directories:
     directory: Directori de perfils
-    enabled: Actualment estàs inclòs al directori.
-    enabled_but_waiting: Has optat per aparèixer al directori però encara no tens el nombre mínim de seguidors (%{min_followers}) per ser-hi.
     explanation: Descobreix usuaris segons els seus interessos
     explore_mastodon: Explora %{title}
-    how_to_enable: Actualment no tens activat ser al directori. Pots optar-hi a continuació. Utilitza etiquetes en el teu text bio per incloure't sota etiquetes especifiques!
-    people:
-      one: "%{count} persona"
-      other: "%{count} gent"
+  domain_validator:
+    invalid_domain: no es un nom de domini vàlid
   errors:
+    '400': La sol·licitud que vas emetre no era vàlida o no era correcta.
     '403': No tens permís per a veure aquesta pàgina.
     '404': La pàgina que estàs cercant no és aquí.
+    '406': Aquesta pàgina no está disponible en el format demanat.
     '410': La pàgina que estàs cercant ja no existeix.
     '422':
       content: La verificació de seguretat ha fallat. Tens les galetes blocades?
@@ -599,6 +695,7 @@ ca:
     '500':
       content: Ho sentim, però alguna cosa ha fallat a la nostra banda.
       title: Aquesta pàgina no es correcta
+    '503': La pàgina no podria ser servida a causa d'un error temporal del servidor.
     noscript_html: Per a utilitzar Mastodon, activa el JavaScript. També pots provar una de les <a href="%{apps_path}"> aplicacions natives</a> de Mastodon per a la vostra plataforma.
   existing_username_validator:
     not_found: no s'ha pogut trobar cap usuari local amb aquest nom d'usuari
@@ -622,6 +719,7 @@ ca:
     add_new: Afegir nova
     errors:
       limit: Ja has mostrat la quantitat màxima d'etiquetes
+    hint_html: "<strong>Què son les etiquetes destacades?</strong> Es mostren de manera destacada en el teu perfil públic i permeten a les persones navegar per les teves publicacions amb aquestes etiquetes. Són una gran eina per fer un seguiment de treballs creatius o de projectes a llarg termini."
   filters:
     contexts:
       home: Línia de temps Inici
@@ -642,10 +740,12 @@ ca:
     developers: Desenvolupadors
     more: Més…
     resources: Recursos
+    trending_now: En tendència
   generic:
     all: Tot
     changes_saved_msg: Els canvis s'han desat correctament!
     copy: Copia
+    no_batch_actions_available: Cap accions de lot disponibles en aquesta pàgina
     order_by: Ordena per
     save_changes: Desa els canvis
     validation_errors:
@@ -717,9 +817,34 @@ ca:
       too_many: No es poden adjuntar més de 4 fitxers
   migrations:
     acct: usuari@domini del nou compte
-    currently_redirecting: 'El teu perfil està configurat com a redirecció a:'
-    proceed: Desa
-    updated_msg: La configuració de la migració del compte s'ha actualitzat correctament!
+    cancel: Cancel·la redirecció
+    cancel_explanation: Cancel·lant la redirecció reactivará el teu compte actual però no recuperarà els seguidors que han estat moguts a aquell compte.
+    cancelled_msg: Redirecció cancel·lada amb èxit.
+    errors:
+      already_moved: és el mateix compte al que ja t'has mogut
+      missing_also_known_as: no fa referencia a aquest compte
+      move_to_self: no pot ser el compte actual
+      not_found: podria no ser trobat
+      on_cooldown: Estàs en temps de recuperació
+    followers_count: Seguidors en el moment del moviment
+    incoming_migrations: Movent des d'un compte diferent
+    incoming_migrations_html: Per a moure't des d'un altre compte a aquest, primer necessites <a href="%{path}">crear un àlies de compte</a>.
+    moved_msg: El teu compte ara està redirigint a %{acct} i els teus seguidors estan sent moguts.
+    not_redirecting: El teu compte no està redirigint actualment a cap altre.
+    on_cooldown: Recentment has emigrat el teu compte. Aquesta funció esdevindrà un altre cop disponible en %{count} dies.
+    past_migrations: Migracions passades
+    proceed_with_move: Moure seguidors
+    redirecting_to: El teu compte està redirigint a %{acct}.
+    set_redirect: Ajusta la redirecció
+    warning:
+      backreference_required: El compte nou primer ha de ser configurat per a referenciar aquest
+      before: 'Abans de procedir si us plau llegeix amb cura aquestes notes:'
+      cooldown: Després de moure't hi ha un període de recuperació durant el qual no seràs capaç de moure't un altre cop
+      disabled_account: El teu compte actual no serà plenament utilitzable després. Tanmateix, tindràs accés a exportació de dades així com reactivació.
+      followers: Aquesta acció mourà tots els seguidors des de l'actual al compte nou
+      only_redirect_html: Alternativament, pots <a href="%{path}">posar només una redirecció en el teu perfil</a>.
+      other_data: Cap altre dada serà moguda automàticament
+      redirect: El perfil del teu compte actual serà actualitzat amb un avís de redirecció i serà exclòs de les cerques
   moderation:
     title: Moderació
   notification_mailer:
@@ -816,10 +941,6 @@ ca:
     reply:
       proceed: Procedir a respondre
       prompt: 'Vols respondre a aquest toot:'
-  remote_unfollow:
-    error: Error
-    title: Títol
-    unfollowed: Sense seguir
   scheduled_statuses:
     over_daily_limit: Has superat el límit de %{limit} toots programats per a aquell dia
     over_total_limit: Has superat el limit de %{limit} toots programats
@@ -868,6 +989,7 @@ ca:
   settings:
     account: Compte
     account_settings: Ajustos del compte
+    aliases: Àlies de compte
     appearance: Aparènça
     authorized_apps: Aplicacions autoritzades
     back: Torna a Mastodon
@@ -908,6 +1030,9 @@ ca:
       private: No es pot fixar el toot no públic
       reblog: No es pot fixar un impuls
     poll:
+      total_people:
+        one: "%{count} persona"
+        other: "%{count} persones"
       total_votes:
         one: "%{count} vot"
         other: "%{count} vots"
@@ -926,6 +1051,8 @@ ca:
     pinned: Toot fixat
     reblogged: ha impulsat
     sensitive_content: Contingut sensible
+  tags:
+    does_not_match_previous_name: no coincideix amb el nom anterior
   terms:
     body_html: |
       <h2>Política de Privacitat</h2>
@@ -1043,7 +1170,9 @@ ca:
         disable: Mentre el teu compte estigui congelat les dades romandran intactes però no pots dur a terme cap acció fins que no estigui desbloquejat.
         silence: Mentre el teu compte estigui limitat només les persones que ja et segueixen veuen les teves dades en aquest servidor i pots ser exclòs de diverses llistes públiques. No obstant això, d'altres encara poden seguir-te manualment.
         suspend: El teu compte s'ha suspès i tots els teus toots i fitxers multimèdia penjats s'han eliminat irreversiblement d'aquest servidor i dels servidors on tenies seguidors.
+      get_in_touch: Pots respondre a aquest correu electrònic per a contactar amb el personal de %{instance}.
       review_server_policies: Revisa les polítiques del servidor
+      statuses: 'Concretament, per:'
       subject:
         disable: S'ha congelat el teu compte %{acct}
         none: Avís per a %{acct}
diff --git a/config/locales/co.yml b/config/locales/co.yml
index b3d14fdb538a194969c0b39a2dd5faab9214c729..41e2ccfbbf58312dc6093ba48842799cc174745d 100644
--- a/config/locales/co.yml
+++ b/config/locales/co.yml
@@ -17,13 +17,12 @@ co:
     contact_unavailable: Micca dispunibule
     discover_users: Scopre utilizatori
     documentation: Ducumentazione
-    extended_description_html: |
-      <h3>Una bona piazza per e regule</h3>
-      <p>A descrizzione stesa ùn hè micca stata riempiuta.</p>
     federation_hint_html: Cù un contu nant'à %{instance} puderete siguità ghjente da tutti i servori Mastodon è ancu più d'altri.
-    generic_description: "%{domain} hè un servore di a rete"
     get_apps: Pruvà un'applicazione di telefuninu
     hosted_on: Mastodon allughjatu nant’à %{domain}
+    instance_actor_flash: |
+      Stu contu ghjè un'attore virtuale chì ghjove à riprisentà u servore sanu è micca un veru utilizatore.
+      Hè utilizatu da a federazione è ùn deve micca esse bluccatu eccettu s'e voi vulete bluccà tuttu u servore, in quellu casu duvereste utilizà un blucchime di duminiu.
     learn_more: Amparà di più
     privacy_policy: Pulitica di vita privata
     see_whats_happening: Vede cio chì si passa
@@ -35,6 +34,14 @@ co:
     status_count_before: chì anu pubblicatu
     tagline: Siguità amichi è scopre ancu di più altri
     terms: Cundizione di u serviziu
+    unavailable_content: Cuntinutu micca dispunibule
+    unavailable_content_description:
+      domain: Servore
+      reason: 'Ragione:'
+      rejecting_media: I fugliali media da stu servore ùn saranu micca arregistrati è e vignette ùn saranu micca affissate, duverete cliccà manualmente per accede à l'altru servore è vedeli.
+      silenced: I statuti da stu servore ùn saranu mai visti tranne nant'a vostra pagina d'accolta s'e voi siguitate l'autore.
+      suspended: Ùn puderete micca siguità qualsiasi nant'à stu servore, i dati versu o da quallà ùn saranu mai accessi, scambiati o arregistrati.
+    unavailable_content_html: Mastodon vi parmette in generale di vede u cuntinutu è interagisce cù l'utilizatori di tutti l'altri servori di u fediversu. Quessi sò l'eccezzione fatte nant'à stu servore in particulare.
     user_count_after:
       one: utilizatore
       other: utilizatori
@@ -42,6 +49,8 @@ co:
     what_is_mastodon: Quale hè Mastodon?
   accounts:
     choices_html: "%{name} ricumanda:"
+    endorsements_hint: Pudete appughjà i conti chì siguitate dapoi l'interfaccia web, è saranu mustrati quì.
+    featured_tags_hint: Pudete mette in mostra qualchì hashtag chì saranu affissatu quì.
     follow: Siguità
     followers:
       one: Abbunatu·a
@@ -53,6 +62,7 @@ co:
     media: Media
     moved_html: "%{name} hà cambiatu di contu, avà hè nant’à %{new_profile_link}:"
     network_hidden: St'infurmazione ùn hè micca dispunibule
+    never_active: Mai
     nothing_here: Ùn c’hè nunda quì!
     people_followed_by: Seguitati da %{name}
     people_who_follow: Seguitanu %{name}
@@ -183,6 +193,7 @@ co:
       username: Cugnome
       warn: Averte
       web: Web
+      whitelisted: In a lista bianca
     action_logs:
       actions:
         assigned_to_self_report: "%{name} s’hè assignatu u signalamentu %{target}"
@@ -218,31 +229,39 @@ co:
       deleted_status: "(statutu sguassatu)"
       title: Ghjurnale d’audit
     custom_emojis:
+      assign_category: Aghjunghje categuria
       by_domain: Duminiu
       copied_msg: Copia lucale di l’emoji creata
       copy: Cupià
       copy_failed_msg: Ùn s’hè micca pussutu creà una copia di l’emoji
+      create_new_category: Creà nova categuria
       created_msg: L’emoji hè stata creata!
       delete: Toglie
       destroyed_msg: L’emoji hè stata tolta!
       disable: Disattivà
+      disabled: Disattivatu
       disabled_msg: L’emoji hè stata disattivata
       emoji: Emoji
       enable: Attivà
+      enabled: Attivate
       enabled_msg: L’emoji hè stata attivata
       image_hint: PNG di 50Ko o menu
+      list: Listà
       listed: Listata
       new:
-        title: Aghjustà una nov’emoji
+        title: Aghjunghje una nov’emoji
       overwrite: Soprascrive
       shortcode: Accorta
       shortcode_hint: 2 caratteri o più, solu lettere, numeri è liniette basse
       title: Emoji parsunalizate
+      uncategorized: Micca categurizatu
+      unlist: Slistà
       unlisted: Micca listata
       update_failed_msg: Ùn s’hè micca pussutu mette à ghjornu l’emoji
       updated_msg: L’emoji hè stata messa à ghjornu!
       upload: Caricà
     dashboard:
+      authorized_fetch_mode: Modu ricuperazione auturizata
       backlog: travagli in attesa
       config: Cunfigurazione
       feature_deletions: Sguassamenti di conti
@@ -250,10 +269,13 @@ co:
       feature_profile_directory: Annuariu di i prufili
       feature_registrations: Arregistramenti
       feature_relay: Ripetitore di federazione
+      feature_spam_check: Anti-spam
       feature_timeline_preview: Vista di a linea pubblica
       features: Funziunalità
       hidden_service: Federazione cù servizii piattati
       open_reports: signalamenti aperti
+      pending_tags: hashtag in attesa di verificazione
+      pending_users: utilizatori in attesa di rivista
       recent_users: Utilizatori ricenti
       search: Ricerca di testu sanu
       single_user_mode: Modu utilizatore unicu
@@ -265,11 +287,18 @@ co:
       week_interactions: interazzione sta settimana
       week_users_active: attivi sta settimana
       week_users_new: utilizatori sta settimana
+      whitelist_mode: Modu lista bianca
+    domain_allows:
+      add_new: Aghjunghje à a lista bianca
+      created_msg: U duminiu hè statu aghjuntu à a lista bianca
+      destroyed_msg: U duminiu hè statu sguassatu da a lista bianca
+      undo: Toglie di a lista bianca
     domain_blocks:
-      add_new: Aghjustà novu blucchime di duminiu
+      add_new: Aghjunghje novu blucchime di duminiu
       created_msg: U blucchime di u duminiu hè attivu
       destroyed_msg: U blucchime di u duminiu ùn hè più attivu
       domain: Duminiu
+      edit: Mudificà u blucchime di duminiu
       existing_domain_block_html: Avete digià impostu limite più strette nant'à %{name}, duvete <a href="%{unblock_url}">sbluccallu</a> primu.
       new:
         create: Creà un blucchime
@@ -280,6 +309,10 @@ co:
           silence: Silenzà
           suspend: Suspende
         title: Novu blucchime di duminiu
+      private_comment: Cummentariu privatu
+      private_comment_hint: Cummentariu nant'à a limitazione di stu duminiu per l'usu internu di i muderatori.
+      public_comment: Cummentariu pubblicu
+      public_comment_hint: Cummentariu nant'à a limitazione di stu duminiu per u pubblicu generale, s'ella hè attivata a rivelazione di a lista di limitazione di duminiu.
       reject_media: Righjittà i fugliali media
       reject_media_hint: Sguassa tutti i media caricati è ricusa caricamenti futuri. Inutile per una suspensione
       reject_reports: Righjittà i rapporti
@@ -299,8 +332,9 @@ co:
         title: Ùn bluccà più u duminiu %{domain}
         undo: Annullà
       undo: Annullà u blucchime di duminiu
+      view: Vede blucchime di duminiu
     email_domain_blocks:
-      add_new: Aghjustà
+      add_new: Aghjunghje
       created_msg: U blucchime di u duminiu d’e-mail hè attivu
       delete: Toglie
       destroyed_msg: U blucchime di u duminiu d’e-mail ùn hè più attivu
@@ -322,6 +356,8 @@ co:
         all: Tuttu
         limited: Limitatu
         title: Muderazione
+      private_comment: Cummentariu privatu
+      public_comment: Cummentariu pubblicu
       title: Federazione
       total_blocked_by_us: Bluccati da noi
       total_followed_by_them: Siguitati da elli
@@ -339,7 +375,7 @@ co:
     pending_accounts:
       title: Conti in attesa (%{count})
     relays:
-      add_new: Aghjustà un ripetitore
+      add_new: Aghjunghje un ripetitore
       delete: Sguassà
       description_html: Un <strong>ripetitore di federazione</strong> ghjè un servore intermediariu chì manda statuti pubblichi trà l'istanze abbunate. <strong>Pò aiutà l'istanze chjuche è mezane à scuprì u cuntinutu di u fediverse</strong> senza chì l'utilizatori appianu bisognu di seguità tutti i conti di l'altri servori.
       disable: Disattivà
@@ -351,6 +387,7 @@ co:
       pending: In attesa di l'apprubazione di u ripetitore
       save_and_enable: Salvà è attivà
       setup: Creà una cunnessione cù un ripetitore
+      signatures_not_enabled: I ripetitori ùn marchjeranu micca currettamente mentre chì u modu sicurizatu o à lista bianca hè attivatu
       status: Statutu
       title: Ripetitori
     report_notes:
@@ -399,6 +436,16 @@ co:
       custom_css:
         desc_html: Mudificà l'apparenza cù CSS caricatu nant'à ogni pagina
         title: CSS persunalizatu
+      default_noindex:
+        desc_html: Tocca tutti quelli ch'ùn anu micca cambiatu stu parametru
+        title: Ritirà l'utilizatori di l'indicazione nant'à i mutori di ricerca
+      domain_blocks:
+        all: À tutti
+        disabled: À nimu
+        title: Mustrà blucchime di duminiu
+        users: À l'utilizatori lucali cunnettati
+      domain_blocks_rationale:
+        title: Vede ragiò
       hero:
         desc_html: Affissatu nant’a pagina d’accolta. Ricumandemu almenu 600x100px. S’ellu ùn hè micca definiti, a vignetta di u servore sarà usata
         title: Ritrattu di cuprendula
@@ -449,6 +496,9 @@ co:
         desc_html: Quì pudete scrive e vostre regule di cunfidenzialità, cundizione d’usu o altre menzione legale. Pudete fà usu di marchi HTML
         title: Termini persunalizati
       site_title: Nome di u servore
+      spam_check_enabled:
+        desc_html: Mastodon pò autosilenzà è autosignalà conti, per esempiu ditettendu quelli chì mandanu missaghji micca sullicitati à ripetizione. Ci ponu esse falzi pusitivi.
+        title: Anti-spam
       thumbnail:
         desc_html: Utilizatu per viste cù OpenGraph è l’API. Ricumandemu 1200x630px
         title: Vignetta di u servore
@@ -456,37 +506,47 @@ co:
         desc_html: Vede a linea pubblica nant’a pagina d’accolta
         title: Vista di e linee
       title: Parametri di u situ
+      trendable_by_default:
+        desc_html: Ùn affetta micca quelli chì sò digià stati ricusati
+        title: Auturizà l'hashtag à esse in tindenze senza verificazione
+      trends:
+        desc_html: Mustrà à u pubblicu i hashtag chì sò stati digià verificati è chì sò in e tendenze avà
+        title: Tendenze di hashtag
     statuses:
       back_to_account: Ritornu à a pagina di u contu
       batch:
         delete: Toglie
         nsfw_off: Indicà cum’è micca sensibile
         nsfw_on: Indicà cum’è sensibile
+      deleted: Sguassatu
       failed_to_execute: Esecuzione impussibule
       media:
         title: Media
       no_media: Nisun media
       no_status_selected: I statuti ùn sò micca stati mudificati perchè manc'unu era selezziunatu
-      title: Statutu di u contu
+      title: Statuti di u contu
       with_media: Cù media
-    subscriptions:
-      callback_url: URL di richjama
-      confirmed: Cunfirmatu
-      expires_in: Spira in
-      last_delivery: Ultima arricata
-      title: WebSub
-      topic: Sughjettu
     tags:
-      accounts: Conti
-      hidden: Piattatu
-      hide: Piattà di l'annuariu
+      accounts_today: Usi unichi oghji
+      accounts_week: Usi unichi sta settimana
+      breakdown: Ditagli di l'usu d'oghji per origine
+      context: Cuntestu
+      directory: In l'annuariu
+      in_directory: "%{count} in l'annuariu"
+      last_active: Ultima attività
+      most_popular: Più pupulari
+      most_recent: Più ricente
       name: Hashtag
+      review: Statutu di verificazione
+      reviewed: Verificatu
       title: Hashtag
-      unhide: Mustrà in l'annuariu
-      visible: Visibile
+      trending_right_now: Tendenze avà
+      unique_uses_today: "%{count} pustendu oghji"
+      unreviewed: Micca verificatu
+      updated_msg: Parametri di i hashtag messi à ghjornu
     title: Amministrazione
     warning_presets:
-      add_new: Aghjustà nova
+      add_new: Aghjunghje
       delete: Sguassà
       edit: Cambià
       edit_preset: Cambià a preselezzione d'avertimentu
@@ -499,11 +559,21 @@ co:
       body: "%{reporter} hà palisatu %{target}"
       body_remote: Qualch’unu da %{domain} hà palisatu %{target}
       subject: Novu signalamentu nant’à %{instance} (#%{id})
+    new_trending_tag:
+      body: 'U hashtag #%{name} hè in e tendenze oghji, mà ùn hè micca verificatu. Ùn sarà micca mustratu à u pubblicu eccettu s''ellu hè auturizatu, o pudete ancu salvà u furmulariu cusì per ùn mai più avè à ne sente parlà.'
+      subject: Novu hashtag in attesa di rivista nant'à %{instance} (#%{name})
+  aliases:
+    add_new: Creà un pseudonimu
+    created_msg: Novu pseudonimu creatu. Pudete avà inizià u trasferimentu da u vechju contu.
+    deleted_msg: U pseudonimu hè statu sguassatu. Ùn si puderà più migrà da questu contu à quellu.
+    hint_html: Per traslucà da un altru contu à questu, quì pudete creà un pseudonimu o "alias", riquisitu per trasferì l'abbunati da u vechju contu à u novu. St'azzione sola <strong>ùn face nunda</strong> è pò esse <strong>annullata senza prublemi</strong>. <strong>A migrazione hè principiata dapoi u vechju contu</strong>.
+    remove: Sguassà u pseudonimu
   appearance:
     advanced_web_interface: Interfaccia web avanzata
     advanced_web_interface_hint: 'S''è voi vulete fà usu di a larghezza sana di u vostru screnu, l''interfaccia web avanzata vi permette di cunfigurà parechje culonne sfarente per vede tutta l''infurmazione chì vulete vede in listessu tempu: Accolta, nutificazione, linea pubblica, è tutti l''hashtag è liste chì vulete.'
     animations_and_accessibility: Animazione è accessibilità
     confirmation_dialogs: Pop-up di cunfirmazione
+    discovery: Scuperta
     sensitive_content: Cuntinutu sensibile
   application_mailer:
     notification_preferences: Cambià e priferenze e-mail
@@ -524,9 +594,13 @@ co:
     apply_for_account: Dumandà un'invitazione
     change_password: Chjave d’accessu
     checkbox_agreement_html: Sò d'accunsentu cù e <a href="%{rules_path}" target="_blank">regule di u servore</a> è i <a href="%{terms_path}" target="_blank">termini di u serviziu</a>
-    confirm_email: Cunfirmà l’e-mail
+    checkbox_agreement_without_rules_html: Accettu i <a href="%{terms_path}" target="_blank">termini di u serviziu</a>
     delete_account: Sguassà u contu
     delete_account_html: S’è voi vulete toglie u vostru contu <a href="%{path}">ghjè quì</a>. Duverete cunfirmà a vostra scelta.
+    description:
+      prefix_invited_by_user: "@%{name} v'invita à raghjunghje stu servore di Mastodon!"
+      prefix_sign_up: Arregistratevi nant'à Mastodon oghji!
+      suffix: Cù un contu, puderete siguità l'altri, pustà statuti è scambià missaghji cù l'utilizatori di tutti i servori Mastodon è ancu di più!
     didnt_get_confirmation: Ùn avete micca ricevutu l’istruzione di cunfirmazione?
     forgot_password: Chjave scurdata?
     invalid_reset_password_token: U ligame di riinizializazione di a chjave d’accessu hè spiratu o ùn hè micca validu. Pudete dumandà un'altru ligame.
@@ -544,6 +618,16 @@ co:
     reset_password: Cambià a chjave d’accessu
     security: Sicurità
     set_new_password: Creà una nova chjave d’accessu
+    setup:
+      email_below_hint_html: S'è l'indirizzu e-mail quì sottu ùn hè micca currettu, pudete cambiallu quì è riceve un novu e-mail di cunfirmazione.
+      email_settings_hint_html: L'e-mail di cunfirmazione hè statu mandatu à l'indirizzu %{email}. S'ellu ùn hè micca currettu, pudete cambiallu in i parametri di u contu.
+      title: Stallazione
+    status:
+      account_status: Statutu di u contu
+      confirming: In attesa di a cumplezzione di a cunfirmazione di l'e-mail.
+      functional: U vostru contu hè uperaziunale.
+      pending: A vostra dumanda hè in attesa di rivista da a squadra di muderazione. Quessa pò piglià un certu tempu. Avete da riceve un'e-mail s'ella hè appruvata.
+      redirecting_to: U vostru contu hè inattivu perchè riindirizza versu %{acct}.
     trouble_logging_in: Difficultà per cunnettavi?
   authorize_follow:
     already_following: Site digià abbunatu·a à stu contu
@@ -556,6 +640,11 @@ co:
       return: Vede u prufile di l’utilizatore
       web: Andà à l’interfaccia web
     title: Siguità %{acct}
+  challenge:
+    confirm: Cuntinuvà
+    hint_html: "<strong>Astuzia:</strong> Ùn avemu micca da dumandavvi stu codice per l'ore chì vene."
+    invalid_password: Chjave d'accessu micca curretta
+    prompt: Cunfirmà a chjave d'accessu per cuntinuvà
   datetime:
     distance_in_words:
       about_x_hours: "%{count}o"
@@ -571,26 +660,33 @@ co:
       x_months: "%{count}Me"
       x_seconds: "%{count}s"
   deletes:
-    bad_password_msg: È nò! Sta chjave ùn hè curretta
+    challenge_not_passed: L'infurmazione entrata ùn era micca curretta
     confirm_password: Entrate a vostra chjave d’accessu attuale per verificà a vostra identità
-    description_html: U contu sarà deattivatu è u cuntenutu sarà sguassatu di manera <strong>permanente è irreversibile</strong>. Ùn sarà micca pussibule piglià stu cugnome torna per evità l’impusture.
+    confirm_username: Entrà u vostru cugnome per cunfirmà a prucedura
     proceed: Sguassà u contu
     success_msg: U vostru contu hè statu sguassatu
-    warning_html: Pudete esse sicuru·a solu chì u cuntenutu sarà sguassatu di stu servore. S’ellu hè statu spartutu in altrò, sarà forse sempre quallà. I servori scunettati è quelli ch'ùn sò più abbunati à e vostre pubblicazione ùn anu micca da mette à ghjornu e so database.
-    warning_title: Dispunibilità di i cuntenuti sparsi
+    warning:
+      before: 'Nanz''à cuntinuà, leghjete ste note attentamente:'
+      caches: U cuntinutu indè u cache di l'altri servori sarà forse cunservatu
+      data_removal: I vostri posti è dati saranu sguassati di manera permanente
+      email_change_html: Pudete <a href="%{path}">cambià u vostr'indirizzu e-mail</a> senza toglie u vostru contu
+      email_contact_html: S'ellu ùn hè sempre micca rimettu, pudete dumandà aiutu à <a href="mailto:%{email}">%{email}</a>
+      email_reconfirmation_html: S'e voi ùn ricevete micca l'e-mail di cunfirmazione, pudete <a href="%{path}">richiestà un'altru</a>
+      irreversible: Ùn puderete micca ricuperà o riattivà u vostru contu
+      more_details_html: Per più di ditagli, videte a <a href="%{terms_path}">pulitica di vita privata</a>.
+      username_available: U vostru cugnome riduvinterà dispunibule
+      username_unavailable: U vostru cugnome ùn sarà sempre micca dispunibule
   directories:
     directory: Annuariu di i prufili
-    enabled: Site inscrittu·a indè l'annuariu.
-    enabled_but_waiting: Avete sceltu d'esse inscrittu·a indè l'annuariu, mà ùn avete micca ancu u numeru minimale d'abbunati (%{min_followers}) per esse listatu·a.
     explanation: Scopre utilizatori à partesi di i so centri d'interessu
     explore_mastodon: Scopre à %{title}
-    how_to_enable: Ùn site micca ancu inscrittu·a indè l'annuariu. Pudete inscrive vi quì sottu. Utilizate qualchi hashtag indè a vostra biugrafia per esse listatu·a indè tag specifichi!
-    people:
-      one: "%{count} persona"
-      other: "%{count} persone"
+  domain_validator:
+    invalid_domain: ùn hè micca un nome di duminiu currettu
   errors:
+    '400': A richiesta mandata ùn era micca valida o curretta.
     '403': Ùn site micca auturizatu·a à vede sta pagina.
     '404': Sta pagina ùn esiste micca quì.
+    '406': A pagina ùn hè micca dispunivule in u furmatu riquisitu.
     '410': Sta pagina ùn esiste più quì.
     '422':
       content: C’hè statu un prublemu cù a verificazione di sicurità. Forse bluccate cookies?
@@ -599,6 +695,7 @@ co:
     '500':
       content: Scusate, mà c’hè statu un prublemu cù u nostru servore.
       title: Sta pagina ùn hè curretta
+    '503': A pagina ùn hè micca stata servita per via di un prublemu timpuraneu di u servore.
     noscript_html: Mastodon nant’à u web hà bisognu di JavaScript per funziunà. Pudete ancu pruvà <a href="%{apps_path}">l’applicazione native</a> per a vostra piattaforma.
   existing_username_validator:
     not_found: ùn si pudeva micca truvà un'utilizatore lucale cù stu cugnome
@@ -619,9 +716,10 @@ co:
     mutes: Piattate
     storage: I vostri media
   featured_tags:
-    add_new: Aghjustà novu
+    add_new: Aghjunghje
     errors:
       limit: Avete digià messu in mostra u numeru massimale di hashtag
+    hint_html: "<strong>Quale sò i hashtag in mostra?</strong> Sò messi in vista nant'à u vostru prufile pubblicu è permettenu à a ghjente di vede i vostri statuti ch'annu stu hashtag. Sò una bona manere di mustrà e vostre opere creative o i prughjetti à longu termine."
   filters:
     contexts:
       home: Accolta
@@ -637,15 +735,17 @@ co:
       delete: Toglie
       title: Filtri
     new:
-      title: Aghjustà un novu filtru
+      title: Aghjunghje un novu filtru
   footer:
     developers: Sviluppatori
     more: Di più…
     resources: Risorze
+    trending_now: Tindenze d'avà
   generic:
     all: Tuttu
     changes_saved_msg: Cambiamenti salvati!
     copy: Cupià
+    no_batch_actions_available: Alcun'azzione di gruppu nant'à sta pagina
     order_by: Urdinà da
     save_changes: Salvà e mudificazione
     validation_errors:
@@ -674,7 +774,7 @@ co:
   imports:
     modes:
       merge: Unisce
-      merge_long: Cunservà i dati esistenti è aghjustà i novi
+      merge_long: Cunservà i dati esistenti è aghjunghje i novi
       overwrite: Soprascrive
       overwrite_long: Rimpiazzà i dati esistenti cù i novi
     preface: Pudete impurtà certi dati, cumu e persone chì seguitate o bluccate nant’à u vostru contu, nant’à stu servore à partesi di fugliali creati nant’à un’altru.
@@ -717,9 +817,34 @@ co:
       too_many: Ùn si pò micca aghjunghje più di 4 fugliali
   migrations:
     acct: cugnome@duminiu di u novu contu
-    currently_redirecting: 'U vostru prufile riindiriza tuttu versu à:'
-    proceed: Salvà
-    updated_msg: I paramettri di migrazione sò stati messi à ghjornu!
+    cancel: Annullà ridirezzione
+    cancel_explanation: L'annullazione di a ridirezzione hà da riattivà stu contu, mà ùn si puderà micca ricuperà l'abbunati chì sò digià stati trasferriti à l'altru contu.
+    cancelled_msg: Ridirezzione annullata.
+    errors:
+      already_moved: hè digià u contu induve avede traslucatu
+      missing_also_known_as: ùn fà micca riferenza à stu contu
+      move_to_self: ùn pò micca esse u contu attuale
+      not_found: ùn hè micca statu trovu
+      on_cooldown: Perioda di ricuperazione
+    followers_count: Abbunati à u mumentu di trasferimentu
+    incoming_migrations: Traslucà da un'altru contu
+    incoming_migrations_html: Per spustà da stu contu à un'altru, primu duvete <a href="%{path}">creà un pseudonimu di contu</a>.
+    moved_msg: Avà u vostru contu riindirizza versu %{acct} è i vostri abbunati sò in corsu di trasferimentu.
+    not_redirecting: U vostru contu ùn riindirizza micca ancu versu un'altru contu.
+    on_cooldown: Avede digià migratu u vostru contu. Sta funzionne sarà torna dispunibule in %{count} ghjorni.
+    past_migrations: Anziane migrazione
+    proceed_with_move: Trasferì l'abbunati
+    redirecting_to: U vostru contu riindirizza versu à %{acct}.
+    set_redirect: Creà ridirezzione
+    warning:
+      backreference_required: U novu contu deve prima esse cunfiguratu per fà rifirenza cù un pseudonimu à quessu contu
+      before: 'Nanz''à cuntinuà, leghjete ste note attentamente:'
+      cooldown: Dopu à a traslucazione, c'hè una perioda di ricuperazione in quella ùn puderete micca cambià torna di contu
+      disabled_account: U contu attuale ùn puderà più esse utilizatu dop'à st'azzione. Però, puderete accede à a spurtazione di dati o riattivà u contu.
+      followers: St'azzione hà da spiazzà tutti l'abbunati di u contu attuale nant'à u novu contu
+      only_redirect_html: Pudete ancu <a href="%{path}">mette solu una ridirezzione nant'à u vostru prufile</a>.
+      other_data: L'altri dati ùn saranu micca autumaticamente trasferiti
+      redirect: U prufile di u vostru contu attuale sarà messu à ghjornu cù una nutificazione di ridirezzione è sarà sclusu di e ricerche
   moderation:
     title: Muderazione
   notification_mailer:
@@ -809,17 +934,13 @@ co:
   remote_interaction:
     favourite:
       proceed: Cuntinuà per favurisce
-      prompt: 'Vulete aghjustà stu statutu à i vostri favuriti:'
+      prompt: 'Vulete aghjunghje stu statutu à i vostri favuriti:'
     reblog:
       proceed: Cuntinuà per sparte
       prompt: 'Vulete sparte stu statutu:'
     reply:
       proceed: Cuntinuà per risponde
       prompt: 'Vulete risponde à stu statutu:'
-  remote_unfollow:
-    error: Errore
-    title: Titulu
-    unfollowed: Disabbunatu
   scheduled_statuses:
     over_daily_limit: Avete trapassatu a limita di %{limit} statuti planificati per stu ghjornu
     over_total_limit: Avete trapassatu a limita di %{limit} statuti planificati
@@ -868,6 +989,7 @@ co:
   settings:
     account: Contu
     account_settings: Parametri di u contu
+    aliases: Pseudonimi di contu
     appearance: Apparenza
     authorized_apps: Applicazione auturizate
     back: Ritornu nant’à Mastodon
@@ -908,6 +1030,9 @@ co:
       private: Ùn pudete micca puntarulà un statutu ch’ùn hè micca pubblicu
       reblog: Ùn pudete micca puntarulà una spartera
     poll:
+      total_people:
+        one: "%{count} persona"
+        other: "%{count} persone"
       total_votes:
         one: "%{count} votu"
         other: "%{count} voti"
@@ -926,6 +1051,8 @@ co:
     pinned: Statutu puntarulatu
     reblogged: hà spartutu
     sensitive_content: Cuntenutu sensibile
+  tags:
+    does_not_match_previous_name: ùn currisponde micca à l'anzianu nome
   terms:
     body_html: |
       <h2>Politique de confidentialité</h2>
@@ -1031,7 +1158,7 @@ co:
     recovery_codes: Codici di ricuperazione
     recovery_codes_regenerated: Codici di ricuperazione ricreati
     recovery_instructions_html: Pudete fà usu di i codici quì sottu per sempre avè accessu à u vostru contu s’ellu hè statu persu u vostru telefuninu. <strong>Guardateli in una piazza sicura</strong>. Per esempiu, stampati è cunservati cù altri ducumenti impurtanti.
-    setup: Installà
+    setup: Attivà
     wrong_code: U codice ùn hè micca currettu! Site sicuru che l’ora di u telefuninu è di u servore sò esatte?
   user_mailer:
     backup_ready:
@@ -1043,7 +1170,9 @@ co:
         disable: Quandu u vostru contu hè ghjacciatu, i vostri dati stannu intatti, mà ùn pudete fà nunda fin'à ch'ellu sia sbluccatu.
         silence: Quandu u vostru contu hè limitatu, solu quelli chì sò digià abbunati à u vostru contu viderenu i vostri statuti nant'à quessu servore, è puderete esse esclusu·a di parechje liste pubbliche. Però, altri conti puderenu sempre seguitavi.
         suspend: U vostru contu hè statu suspesu, è tutti i vo statuti è fugliali media caricati sò stati sguassati di manera irreversibile di stu servore, è di i servori induve aviate abbunati.
+      get_in_touch: Pudete risponde à quest'e-mail per cuntattà a squadra di muderazione di %{instance}.
       review_server_policies: Leghje e pulitiche di u servore
+      statuses: 'Più pricisamente, per:'
       subject:
         disable: U vostru contu %{acct} hè statu ghjacciatu
         none: Avertimentu pè %{acct}
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index 0735a86981afa95f6d4bdba9ea7a5f1c91ab653a..8a3669a6c1a8b1714dbf557ed3c0ccfb07c569aa 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -2,7 +2,7 @@
 cs:
   about:
     about_hashtag_html: Tohle jsou veřejné tooty označené hashtagem <strong>#%{hashtag}</strong>. Pokud máte účet kdekoliv ve fedivesmíru, můžete s nimi interagovat.
-    about_mastodon_html: Mastodon je sociální síť založená na otevřených webových protokolech a svobodném, otevřeném softwaru. Je decentralizovaná jako e-mail.
+    about_mastodon_html: 'Sociální síť budoucnosti: žádné reklamy, žádné korporátní sledování, etický design a decentralizace! S Mastodonem vlastníte svoje data!'
     about_this: O tomto serveru
     active_count_after: aktivních
     active_footnote: Měsíční aktivní uživatelé (MAU)
@@ -17,13 +17,12 @@ cs:
     contact_unavailable: Neuvedeno
     discover_users: Objevujte uživatele
     documentation: Dokumentace
-    extended_description_html: |
-      <h3>Dobré místo pro pravidla</h3>
-      <p>Rozšířený popis ještě nebyl nastaven.</p>
     federation_hint_html: S účtem na %{instance} můžete sledovat lidi na jakémkoliv serveru Mastodon a jiných službách.
-    generic_description: "%{domain} je jedním ze serverů v síti"
     get_apps: Vyzkoušejte mobilní aplikaci
     hosted_on: Mastodon na adrese %{domain}
+    instance_actor_flash: |
+      Tento účet je virtuální aktér, který představuje server samotný a ne jedotlivého uživatele.
+      Používá se pro účely federace a nesmí být blokován, pokud nechcete blokovat celý server. V tomto případě použijte doménovou blokaci.
     learn_more: Zjistit více
     privacy_policy: Zásady soukromí
     see_whats_happening: Podívejte se, co se děje
@@ -37,6 +36,14 @@ cs:
     status_count_before: Kteří napsali
     tagline: Sledujte své přátele a objevujte nové
     terms: Podmínky používání
+    unavailable_content: Nedostupný obsah
+    unavailable_content_description:
+      domain: Server
+      reason: Důvod
+      rejecting_media: 'Mediální soubory z tohoto serveru nebudou zpracovány a nebudou zobrazeny žádné náhledy. Pro prohlédnutí médií bude třeba manuálně přejít na druhý server:'
+      silenced: 'Příspěvky z těchto serverů nebudou zobrazeny ve veřejných časových osách a konverzacích a nebudou generována oznámení o interakcích uživatelů z toho serveru, pokud je nesledujete:'
+      suspended: 'Žádná data z těchto serverů nebudou zpracována, ukládána ani vyměňována, čímž bude znemožněna jakákoliv interakce či komunikace s uživateli z těchto serverů:'
+    unavailable_content_html: Mastodon vám obvykle dovoluje prohlížet si obsah a komunikovat s uživateli z jakéhokoliv dalšího serveru ve fedivesmíru. Tohle jsou výjimky, které byly zavedeny na tomto konkrétním serveru.
     user_count_after:
       few: uživatelé
       many: uživatelů
@@ -46,6 +53,8 @@ cs:
     what_is_mastodon: Co je Mastodon?
   accounts:
     choices_html: 'Volby uživatele %{name}:'
+    endorsements_hint: Z webového rozhraní můžete podpořit lidi, které sledujete. Ti se poté zobrazí zde.
+    featured_tags_hint: Můžete vybrat konkrétní hashtagy, které se zobrazí zde.
     follow: Sledovat
     followers:
       few: Sledující
@@ -59,6 +68,7 @@ cs:
     media: Média
     moved_html: "%{name} se přesunul/a na %{new_profile_link}:"
     network_hidden: Tato informace není k dispozici
+    never_active: Nikdy
     nothing_here: Tady nic není!
     people_followed_by: Lidé, které sleduje %{name}
     people_who_follow: Lidé, kteří sledují uživatele %{name}
@@ -191,6 +201,7 @@ cs:
       username: Uživatelské jméno
       warn: Varovat
       web: Web
+      whitelisted: Na bílé listině
     action_logs:
       actions:
         assigned_to_self_report: "%{name} přidělil/a hlášení %{target} sobě"
@@ -226,19 +237,24 @@ cs:
       deleted_status: "(smazaný toot)"
       title: Záznam auditu
     custom_emojis:
+      assign_category: Přiřadit kategorii
       by_domain: Doména
       copied_msg: Místní kopie emoji byla úspěšně vytvořena
       copy: Kopírovat
       copy_failed_msg: Nebylo možné vytvořit místní kopii tohoto emoji
+      create_new_category: Vytvořit novou kategorii
       created_msg: Emoji úspěšně vytvořeno!
       delete: Smazat
       destroyed_msg: Emoji úspěšně zničeno!
       disable: Zakázat
+      disabled: Zakázáno
       disabled_msg: Emoji bylo úspěšně zakázáno
       emoji: Emoji
       enable: Povolit
+      enabled: Povoleno
       enabled_msg: Emoji bylo úspěšně povoleno
       image_hint: PNG až do 50 KB
+      list: Uvést
       listed: Uvedeno
       new:
         title: Přidat nové vlastní emoji
@@ -246,11 +262,14 @@ cs:
       shortcode: Zkratka
       shortcode_hint: Alespoň 2 znaky, pouze alfanumerické znaky a podtržítka
       title: Vlastní emoji
+      uncategorized: Nezařazená
+      unlist: Neuvést
       unlisted: Neuvedeno
       update_failed_msg: Nebylo možné aktualizovat toto emoji
       updated_msg: Emoji úspěšně aktualizováno!
       upload: Nahrát
     dashboard:
+      authorized_fetch_mode: Zabezpečený režim
       backlog: opožděné úlohy
       config: Konfigurace
       feature_deletions: Smazání účtů
@@ -258,10 +277,13 @@ cs:
       feature_profile_directory: Adresář profilů
       feature_registrations: Registrace
       feature_relay: Federovací most
+      feature_spam_check: Antispam
       feature_timeline_preview: Náhled časové osy
       features: Vlastnosti
       hidden_service: Federace se skrytými službami
       open_reports: otevřená hlášení
+      pending_tags: hashtagů čeká na posouzení
+      pending_users: uživatelů
       recent_users: Nedávní uživatelé
       search: Fulltextové vyhledávání
       single_user_mode: Režim jednoho uživatele
@@ -273,11 +295,18 @@ cs:
       week_interactions: interakcí tento týden
       week_users_active: aktivních tento týden
       week_users_new: uživatelů tento týden
+      whitelist_mode: Režim bílé listiny
+    domain_allows:
+      add_new: Přidat doménu na bílou listinu
+      created_msg: Doména byla úspěšně přidána na bílou listinu
+      destroyed_msg: Doména byla odstraněna z bílé listiny
+      undo: Odstranit z bílé listiny
     domain_blocks:
       add_new: Přidat novou blokaci domény
       created_msg: Blokace domény se právě vyřizuje
       destroyed_msg: Blokace domény byla zrušena
       domain: Doména
+      edit: Upravit doménovou blokaci
       existing_domain_block_html: Pro účet %{name} jste již nastavil/a přísnější omezení, musíte jej nejdříve <a href="%{unblock_url}">odblokovat</a>.
       new:
         create: Vytvořit blokaci
@@ -288,6 +317,10 @@ cs:
           silence: Utišit
           suspend: Pozastavit
         title: Nová doménová blokace
+      private_comment: Soukromý komentář
+      private_comment_hint: Komentář o tomto omezení domény pro vnitřní použití moderátory.
+      public_comment: Veřejný komentář
+      public_comment_hint: Komentář o tomto omezení domény pro obecnou veřejnost, pokud je povoleno zobrazování seznamu omezení domén.
       reject_media: Odmítat mediální soubory
       reject_media_hint: Odstraní lokálně uložené mediální soubory a odmítne jejich stahování v budoucnosti. Nepodstatné pro pozastavení
       reject_reports: Odmítat nahlášení
@@ -309,6 +342,7 @@ cs:
         title: Zrušit blokaci domény %{domain}
         undo: Odvolat
       undo: Odvolat blokaci domény
+      view: Zobrazit doménovou blokaci
     email_domain_blocks:
       add_new: Přidat nový
       created_msg: E-mailová doména úspěšně přidána na černou listinu
@@ -334,6 +368,8 @@ cs:
         all: Všechny
         limited: Omezené
         title: Moderování
+      private_comment: Soukromý komentář
+      public_comment: Veřejný komentář
       title: Federace
       total_blocked_by_us: Blokované námi
       total_followed_by_them: Sledované jimi
@@ -363,6 +399,7 @@ cs:
       pending: Čekám na souhlas mostu
       save_and_enable: Uložit a povolit
       setup: Nastavit připojení k mostu
+      signatures_not_enabled: Mosty nebudou fungovat správně, dokud je povolen zabezpečený režim nebo režim bílé listiny
       status: Stav
       title: Mosty
     report_notes:
@@ -411,11 +448,21 @@ cs:
       custom_css:
         desc_html: Pozměnit vzhled pomocí šablony CSS načtené na každé stránce
         title: Vlastní CSS
+      default_noindex:
+        desc_html: Ovlivňuje všechny uživatele, kteří toto nastavení sami nezměnili
+        title: Odhlásit uživatele z indexování vyhledávačemi ve výchozím stavu
+      domain_blocks:
+        all: Všem
+        disabled: Nikomu
+        title: Zobrazit doménové blokace
+        users: Přihlášeným místním uživatelům
+      domain_blocks_rationale:
+        title: Zobrazit odůvodnění
       hero:
         desc_html: Zobrazuje se na hlavní stránce. Doporučuje se rozlišení alespoň 600x100 px. Pokud toto není nastaveno, bude zobrazena miniatura serveru
         title: Hlavní obrázek
       mascot:
-        desc_html: Zobrazuje se na hlavní stránce. Doporučuje se rozlišení alespoň 293x205 px. Pokud toto není nastaveno, bude zobrazen výchozí maskot
+        desc_html: Zobrazuje se na několika stránkách. Doporučuje se rozlišení alespoň 293x205 px. Pokud toto není nastaveno, bude zobrazen výchozí maskot
         title: Obrázek maskota
       peers_api_enabled:
         desc_html: Domény, na které tento server narazil ve fedivesmíru
@@ -443,8 +490,8 @@ cs:
           open: Kdokoliv se může registrovat
         title: Režim registrací
       show_known_fediverse_at_about_page:
-        desc_html: Je-li tohle zapnuto, zobrazí se v náhledu tooty z celého známého fedivesmíru. Jinak budou zobrazeny pouze místní tooty.
-        title: Zobrazit na náhledu časové osy celý známý fedivesmír
+        desc_html: Je-li tohle vypnuto, bude veřejná časová osa, na kterou odkazuje hlavní stránka serveru, omezena pouze na místní obsah
+        title: Zobrazit na nepřihlášené stránce časové osy federovaný obsah
       show_staff_badge:
         desc_html: Zobrazit na stránce uživatele odznak člena personálu
         title: Zobrazit odznak personálu
@@ -455,25 +502,35 @@ cs:
         desc_html: Dobré místo pro vaše pravidla, pokyny a jiné věci, které váš server odlišují od ostatních. Lze použít HTML značky
         title: Vlastní rozšířené informace
       site_short_description:
-        desc_html: Zobrazen v postranním panelu a meta značkách. Popište, co je Mastodon a díky čemu je tento server zvláštní v jediném odstavci.
+        desc_html: Zobrazen v postranním panelu a meta značkách. Popište, co je Mastodon a díky čemu je tento server zvláštní, v jediném odstavci.
         title: Krátký popis serveru
       site_terms:
         desc_html: Můžete si napsat vlastní zásady soukromí, podmínky používání či jiné legality. Můžete použít HTML značky
         title: Vlastní podmínky používání
       site_title: Název serveru
+      spam_check_enabled:
+        desc_html: Mastodon může automaticky nahlašovat účty, které opakovaně odesílají nevyžádané zprávy. Mohou se najít špatné shody.
+        title: Antispamová automatizace
       thumbnail:
         desc_html: Používáno pro náhledy přes OpenGraph a API. Doporučuje se rozlišení 1200x630px
         title: Miniatura serveru
       timeline_preview:
-        desc_html: Zobrazit na hlavní straně veřejnou časovou osu
-        title: Náhled časové osy
+        desc_html: Zobrazit na hlavní straně odkaz na veřejnou časovou osu a povolit přístup na veřejnou časovou osu pomocí API bez autentizace
+        title: Povolit neautentizovaný přístup k časové ose
       title: Nastavení stránky
+      trendable_by_default:
+        desc_html: Ovlivňuje hashtagy, které nebyly dříve zakázány
+        title: Dovolit hashtagům zobrazit se na trendech bez předchozího posouzení
+      trends:
+        desc_html: Veřejně zobrazit předtím schválené hashtagy, které jsou aktuálně populární
+        title: Populární hashtagy
     statuses:
       back_to_account: Zpět na stránku účtu
       batch:
         delete: Smazat
         nsfw_off: Označit, že není citlivý
         nsfw_on: Označit jako citlivý
+      deleted: Smazáno
       failed_to_execute: Nepodařilo se vykonat
       media:
         title: Média
@@ -481,21 +538,24 @@ cs:
       no_status_selected: Nebyly změněny žádné tooty, neboť žádné nebyly vybrány
       title: Tooty účtu
       with_media: S médii
-    subscriptions:
-      callback_url: Zpáteční URL
-      confirmed: Potvrzeno
-      expires_in: Vyprší v
-      last_delivery: Poslední doručení
-      title: WebSub
-      topic: Téma
     tags:
-      accounts: Účty
-      hidden: Skryté
-      hide: Skrýt z adresáře
+      accounts_today: Jedinečná použití dnes
+      accounts_week: Jedinečná použití tento týden
+      breakdown: Přehled dnešního používání podle zdroje
+      context: Kontext
+      directory: V adresáři
+      in_directory: "%{count} v adresáři"
+      last_active: Naposledy aktivní
+      most_popular: Nejpopulárnější
+      most_recent: Nejnovější
       name: Hashtag
+      review: Stav schválení
+      reviewed: Schválen
       title: Hashtagy
-      unhide: Zobrazit v adresáři
-      visible: Viditelné
+      trending_right_now: Aktuální trendy
+      unique_uses_today: "%{count} dnes píše"
+      unreviewed: Neposouzeno
+      updated_msg: Nastavení hashtagu bylo úspěšně aktualizováno
     title: Administrace
     warning_presets:
       add_new: Přidat nové
@@ -511,11 +571,21 @@ cs:
       body: "%{reporter} nahlásil/a uživatele %{target}"
       body_remote: Někdo z %{domain} nahlásil uživatele %{target}
       subject: Nové nahlášení pro %{instance} (#%{id})
+    new_trending_tag:
+      body: 'Hashtag #%{name} je dnes populární, nebyl však dříve schválen. Nebude zobrazen veřejně, pokud to nedovolíte. Můžete také pouze uložit formulář tak, jak je, a nikdy o něm opět neslyšet.'
+      subject: Nový hashtag ke schválení na %{instance} (#%{name})
+  aliases:
+    add_new: Vytvořit alias
+    created_msg: Nový alias byl úspěšně vytvořen. Nyní můžete zahájit přesun ze starého účtu.
+    deleted_msg: Alias byl úspěšně odstraněn. Přesun z tamtoho účtu na tento již nebude možný.
+    hint_html: Chcete-li se přesunout z jiného účtu na tento, můžete si zde vytvořit alias, který je vyžadován předtím, než můžete pokračovat přesunem sledujících ze starého účtu na tento. Tato akce sama o sobě je <strong>neškodná a vratná</strong>. <strong>Přesun účtu se zahajuje ze starého účtu</strong>.
+    remove: Odpojit alias
   appearance:
     advanced_web_interface: Pokročilé webové rozhraní
     advanced_web_interface_hint: 'Chcete-li využít celé šířky vaší obrazovky, dovolí vám pokročilé webové rozhraní nastavit si mnoho různých sloupců, takže můžete vidět ve stejnou chvíli tolik informací, kolik chcete: domovskou časovou osu, oznámení, federovanou časovou osu a libovolný počet seznamů a hashtagů.'
     animations_and_accessibility: Animace a přístupnost
     confirmation_dialogs: Potvrzovací dialogy
+    discovery: Objevování
     sensitive_content: Citlivý obsah
   application_mailer:
     notification_preferences: Změnit volby e-mailu
@@ -536,9 +606,13 @@ cs:
     apply_for_account: Vyžádat si pozvánku
     change_password: Heslo
     checkbox_agreement_html: Souhlasím s <a href="%{rules_path}" target="_blank">pravidly serveru</a> a <a href="%{terms_path}" target="_blank">podmínkami používání</a>
-    confirm_email: Potvrdit e-mail
+    checkbox_agreement_without_rules_html: Souhlasím s <a href="%{terms_path}" target="_blank">podmínkami používání</a>
     delete_account: Odstranit účet
     delete_account_html: Chcete-li odstranit svůj účet, <a href="%{path}">pokračujte zde</a>. Budete požádán/a o potvrzení.
+    description:
+      prefix_invited_by_user: "@%{name} vás zve, abyste se přidal/a na tento server Mastodon!"
+      prefix_sign_up: Registrujte se na Mastodonu již dnes!
+      suffix: S účtem budete moci sledovat lidi, psát příspěvky a vyměňovat si zprávy s uživateli z kteréhokoliv serveru Mastodon a dalších služeb!
     didnt_get_confirmation: Neobdržel/a jste pokyny pro potvrzení?
     forgot_password: Zapomněl/a jste heslo?
     invalid_reset_password_token: Token pro obnovení hesla je buď neplatný, nebo vypršel. Prosím vyžádejte si nový.
@@ -556,6 +630,16 @@ cs:
     reset_password: Obnovit heslo
     security: Zabezpečení
     set_new_password: Nastavit nové heslo
+    setup:
+      email_below_hint_html: Pokud je níže uvedená e-mailová adresa nesprávná, můžete ji změnit zde a obdržet nový potvrzovací e-mail.
+      email_settings_hint_html: Potvrzovací e-mail byl odeslán na %{email}. Pokud je tato adresa nesprávná, můžete ji změnit v nastavení účtu.
+      title: Nastavení
+    status:
+      account_status: Stav účtu
+      confirming: Čekám na dokončení potvrzení e-mailu.
+      functional: Váš účet je zcela funkční.
+      pending: Váš požadavek čeká na schválení naším personálem. To může nějakou dobu trvat. Pokud bude váš požadavek schválen, obdržíte e-mail.
+      redirecting_to: Váš účet je neaktivní, protože právě přesměrovává na účet %{acct}.
     trouble_logging_in: Problémy s přihlašováním?
   authorize_follow:
     already_following: Tento účet již sledujete
@@ -568,6 +652,11 @@ cs:
       return: Zobrazit profil uživatele
       web: Přejít na web
     title: Sledovat uživatele %{acct}
+  challenge:
+    confirm: Pokračovat
+    hint_html: "<strong>Tip:</strong> Po dobu hodiny vás nebudeme znovu žádat o heslo."
+    invalid_password: Neplatné heslo
+    prompt: Pokračujte potvrzením hesla
   datetime:
     distance_in_words:
       about_x_hours: "%{count} hod"
@@ -583,28 +672,33 @@ cs:
       x_months: "%{count} mesíců"
       x_seconds: "%{count} s"
   deletes:
-    bad_password_msg: Dobrý pokus, hackeři! Nesprávné heslo
+    challenge_not_passed: Informace, které jste zadal/a, nejsou správné
     confirm_password: Zadejte svoje současné heslo pro ověření vaší identity
-    description_html: Tímto <strong>trvale a nenávratně</strong> odstraníte obsah z vašeho účtu a deaktivujete ho. Vaše uživatelské jméno zůstane rezervované pro zabránění budoucím napodobováním.
+    confirm_username: Zadáním svého uživatelského jména potvrdíte proces
     proceed: Odstranit účet
     success_msg: Váš účet byl úspěšně odstraněn
-    warning_html: Pouze vymazání obsahu z tohoto konkrétního serveru je zaručeno. Obsah, který byl široce sdílen, po sobě pravděpodobně zanechá stopy. U offline serverů a serverů, které vaše aktualizace již neodebírají, nebudou databáze aktualizovány.
-    warning_title: Dostupnost rozšířeného obsahu
+    warning:
+      before: 'Před pokračováním si prosím pečlivě přečtěte tyto poznámky:'
+      caches: Obsah, který byl uložen do cache jiných serverů, nemusí být smazán
+      data_removal: Vaše příspěvky a další data budou trvale smazána
+      email_change_html: Můžete si <a href="%{path}">změnit svou e-mailovou adresu</a> bez smazání účtu
+      email_contact_html: Pokud stále nepřijde, můžete požádat o pomoc zasláním e-mailu na <a href="mailto:%{email}">%{email}</a>
+      email_reconfirmation_html: Pokud neobdržíte potvrzovací e-mail, můžete si ho <a href="%{path}">vyžádat znovu</a>
+      irreversible: Nebudete moci obnovit nebo znovu aktivovat váš účet
+      more_details_html: Více detailů najdete v <a href="%{terms_path}">zásadách soukromí</a>.
+      username_available: Vaše uživatelské jméno bude opět dostupné
+      username_unavailable: Vaše uživatelské jméno zůstane nedostupným
   directories:
     directory: Adresář profilů
-    enabled: Aktuálně jste v adresáři uveden/a.
-    enabled_but_waiting: Přihlásil/a jste se k uvedení v adresáři, ale ještě nemáte minimální počet sledujících (%{min_followers}) pro uvedení.
     explanation: Objevujte uživatele podle jejich zájmů
     explore_mastodon: Prozkoumejte %{title}
-    how_to_enable: Aktuálně nejste přihlášen/a do adresáře. Přihlásit se můžete níže. Použijte ve svém popisu profilu hashtagy, abyste mohl/a být uveden/a pod konkrétními hashtagy!
-    people:
-      few: "%{count} lidé"
-      many: "%{count} lidí"
-      one: "%{count} člověk"
-      other: "%{count} lidí"
+  domain_validator:
+    invalid_domain: není platné doménové jméno
   errors:
+    '400': Požadavek, který jste odeslal/a, byl neplatný nebo poškozený.
     '403': Nemáte povolení zobrazit tuto stránku.
     '404': Stránka, kterou hledáte, tu není.
+    '406': Tato stránka není dostupná v požadovaném formátu.
     '410': Stránka, kterou hledáte, tu již neexistuje.
     '422':
       content: Bezpečnostní ověření selhalo. Neblokujete cookies?
@@ -613,6 +707,7 @@ cs:
     '500':
       content: Omlouváme se, ale něco se u nás pokazilo.
       title: Tato stránka není správná
+    '503': Stránku nelze načíst kvůli dočasnému selhání serveru.
     noscript_html: Pro použití webové aplikace Mastodon prosím povolte JavaScript. Nebo zkuste jednu z <a href="%{apps_path}">nativních aplikací</a> pro Mastodon pro vaši platformu.
   existing_username_validator:
     not_found: nelze najít místního uživatele s tímto uživatelským jménem
@@ -636,6 +731,7 @@ cs:
     add_new: Přidat nový
     errors:
       limit: Již jste zvýraznil/a maximální počet hashtagů
+    hint_html: "<strong>Co jsou zvýrazněné hashtagy?</strong> Zobrazují se prominentně na vašem veřejném profilu a dovolují lidem prohlížet si vaše veřejné příspěvky konkrétně pod těmi hashtagy. Je to skvělý nástroj pro sledování kreativních děl nebo dlouhodobých projektů."
   filters:
     contexts:
       home: Domovská časová osa
@@ -656,10 +752,12 @@ cs:
     developers: Vývojáři
     more: Více…
     resources: Zdroje
+    trending_now: Aktuální trendy
   generic:
     all: Všechny
     changes_saved_msg: Změny byly úspěšně uloženy!
     copy: Kopírovat
+    no_batch_actions_available: Pro tuto stránku nejsou dostupny žádné souhrnné akce
     order_by: Seřadit od
     save_changes: Uložit změny
     validation_errors:
@@ -713,7 +811,7 @@ cs:
       '604800': 1 týden
       '86400': 1 den
     expires_in_prompt: Nikdy
-    generate: Vygenerovat
+    generate: Vygenerovat pozvánku
     invited_by: 'Byl/a jste pozván/a uživatelem:'
     max_uses:
       few: "%{count} použití"
@@ -734,10 +832,35 @@ cs:
       images_and_video: K tootu, který již obsahuje obrázky, nelze připojit video
       too_many: Nelze připojit více než 4 soubory
   migrations:
-    acct: přezdívka@doména nového účtu
-    currently_redirecting: 'Váš profil má nastaveno přesměrování na:'
-    proceed: Uložit
-    updated_msg: Vaše nastavení přesunutí účtu bylo úspěšně aktualizováno!
+    acct: Přesunuto na
+    cancel: Zrušit přesměrování
+    cancel_explanation: Zrušením přesměrování znovu aktivujete svůj aktuální účet, ale nevrátí se vám sledující, kteří byli přesměrováni na druhý účet.
+    cancelled_msg: Přesměrování bylo úspěšně zrušeno.
+    errors:
+      already_moved: je stejný účet, na který jste se již přesunul/a
+      missing_also_known_as: neodkazuje na tento účet
+      move_to_self: nemůže být aktuální účet
+      not_found: nemohl být nalezen
+      on_cooldown: Probíhá období odpočinku
+    followers_count: Sledující v době přesunu
+    incoming_migrations: Přesun z jiného účtu
+    incoming_migrations_html: Chcete-li se přesunout z jiného účtu na tento, potřebujete si nejprve <a href="%{path}">vytvořit alias účtu</a>.
+    moved_msg: Váš účet nyní přesměrovává na účet %{acct} a vaši sledující se na něj přesouvají.
+    not_redirecting: Váš účet aktuálně nepřesměrovává na žádný jiný účet.
+    on_cooldown: Nedávno jste přesunul/a svůj účet. Tato funkce bude opět dostupná za %{count} dní.
+    past_migrations: Předchozí přesuny
+    proceed_with_move: Přesunout sledující
+    redirecting_to: Váš účet přesměrovává na účet %{acct}.
+    set_redirect: Nastavit přesměrování
+    warning:
+      backreference_required: Nový účet musí být nejprve nastaven, aby odkazoval zpátky na tento
+      before: 'Před pokračováním si prosím pečlivě přečtěte tyto poznámky:'
+      cooldown: Po přesunu nastane období odpočinku, kdy se nebudete moci opět přesunout
+      disabled_account: Váš aktuální účet nebude poté zcela použitelný. Budete však mít přístup k datovým exportům a budete ho moci znovu aktivovat.
+      followers: Touto akcí přesunete všechny sledující z aktuálního účtu na nový účet
+      only_redirect_html: Alternativně můžete <a href="%{path}">nastavit pouze přesměrování na váš profil</a>.
+      other_data: Žádná další data nebudou přesunuta automaticky
+      redirect: Profil vašeho aktuálního účtu bude aktualizován s oznámením o přesměrování a bude vyloučen z hledání
   moderation:
     title: Moderování
   notification_mailer:
@@ -838,10 +961,6 @@ cs:
     reply:
       proceed: Pokračovat k odpovězení
       prompt: 'Chcete odpovědět na tento toot:'
-  remote_unfollow:
-    error: Chyba
-    title: Nadpis
-    unfollowed: Už nesledujete
   scheduled_statuses:
     over_daily_limit: Překročil/a jste limit %{limit} plánovaných tootů pro tento den
     over_total_limit: Překročil/a jste limit %{limit} plánovaných tootů
@@ -890,6 +1009,7 @@ cs:
   settings:
     account: Účet
     account_settings: Nastavení účtu
+    aliases: Aliasy účtů
     appearance: Vzhled
     authorized_apps: Autorizované aplikace
     back: Zpět na Mastodon
@@ -901,12 +1021,14 @@ cs:
     identity_proofs: Důkazy identity
     import: Import
     import_and_export: Import a export
-    migrate: Přesunutí účtu
+    migrate: Přesun účtu
     notifications: Oznámení
     preferences: Předvolby
     profile: Profil
     relationships: Sledovaní a sledující
     two_factor_authentication: Dvoufázové ověřování
+  spam_check:
+    spam_detected: Tohle je automatizované nahlášení. Byl detekován spam.
   statuses:
     attached:
       description: 'Přiloženo: %{attached}'
@@ -936,6 +1058,11 @@ cs:
       private: Nelze připnout neveřejné tooty
       reblog: Nelze připnout boost
     poll:
+      total_people:
+        few: "%{count} lidé"
+        many: "%{count} lidí"
+        one: "%{count} člověk"
+        other: "%{count} lidí"
       total_votes:
         few: "%{count} hlasy"
         many: "%{count} hlasů"
@@ -956,6 +1083,8 @@ cs:
     pinned: Připnutý toot
     reblogged: boostnul/a
     sensitive_content: Citlivý obsah
+  tags:
+    does_not_match_previous_name: se neshoduje s předchozím názvem
   terms:
     body_html: |
       <h2>Zásady soukromí</h2>
@@ -1073,7 +1202,9 @@ cs:
         disable: Zatímco je váš účet zmražen, zůstávají data vašeho účtu nedotčená, ale nemůžete vykonávat žádné akce, dokud nebude odemčen.
         silence: Zatímco je váš účet omezen, mohou vaše tooty na tomto serveru vidět pouze lidé, kteří váš již sledují, a můžete být vyloučen/a z různých veřejných výpisů. Ostatní vás však pořád mohou manuálně sledovat.
         suspend: Váš účet byl pozastaven a všechny vaše tooty a vaše nahrané mediální soubory byly nenávratně odstraněny z tohoto serveru a serverů, na kterých jste měl/a sledující.
+      get_in_touch: Můžete odpovědět na tento e-mail a spojit se s personálem serveru %{instance}.
       review_server_policies: Posoudit politiku serveru
+      statuses: 'Konkrétně kvůli:'
       subject:
         disable: Váš účet %{acct} byl zmražen
         none: Varování pro uživatele %{acct}
diff --git a/config/locales/cy.yml b/config/locales/cy.yml
index 080e89214bd9ffd44c8c87e0967e324724cf4ac9..202e5de6ca63bb51f30d914b0b5412e3a3aef1d8 100644
--- a/config/locales/cy.yml
+++ b/config/locales/cy.yml
@@ -17,13 +17,12 @@ cy:
     contact_unavailable: Ddim yn berthnasol
     discover_users: Darganfod defnyddwyr
     documentation: Dogfennaeth
-    extended_description_html: |
-      <h3>Lle da ar gyfer rheolau</h3>
-      <p>Nid yw'r disgrifiad estynedig wedi ei osod eto.</p>
     federation_hint_html: Gyda cyfrif ar %{instance}, gallwch dilyn pobl ar unrhyw gweinydd Mastodon, a thu hwnt.
-    generic_description: Mae %{domain} yn un gweinydd yn y rhwydwaith
     get_apps: Rhowch gynnig ar ap dyfeis symudol
     hosted_on: Mastodon wedi ei weinyddu ar %{domain}
+    instance_actor_flash: |
+      Mae'r cyfrif hwn yn actor rhithwir a ddefnyddir i gynrychioli'r gweinydd ei hun ac nid unrhyw ddefnyddiwr unigol.
+      Fe'i defnyddir at ddibenion ffederasiwn ac ni ddylid ei rwystro oni bai eich bod am rwystro'r achos cyfan, ac os felly dylech ddefnyddio bloc parth.
     learn_more: Dysu mwy
     privacy_policy: Polisi preifatrwydd
     see_whats_happening: Gweld beth sy'n digwydd
@@ -39,6 +38,13 @@ cy:
     status_count_before: Ysgriffennwyd gan
     tagline: Dilyn ffrindiau a darganfod rhai newydd
     terms: Telerau gwasanaeth
+    unavailable_content: Cynnwys nad yw ar gael
+    unavailable_content_description:
+      reason: 'Rheswm:'
+      rejecting_media: Ni fydd ffeiliau cyfryngau o'r gweinydd hwn yn cael eu prosesu ac ni fydd unrhyw fawd yn cael eu harddangos, sy'n gofyn am glicio â llaw i'r gweinydd arall.
+      silenced: Ni fydd swyddi o'r gweinydd hwn yn ymddangos yn unman heblaw eich porthiant cartref os dilynwch yr awdur.
+      suspended: Ni fyddwch yn gallu dilyn unrhyw un o'r gweinydd hwn, ac ni fydd unrhyw ddata ohono'n cael ei brosesu na'i storio, ac ni chyfnewidir unrhyw ddata.
+    unavailable_content_html: Yn gyffredinol, mae Mastodon yn caniatáu ichi weld cynnwys gan unrhyw weinyddwr arall yn y ffederasiwn a rhyngweithio â hi. Dyma'r eithriadau a wnaed ar y gweinydd penodol hwn.
     user_count_after:
       few: defnyddwyr
       many: defnyddwyr
@@ -50,6 +56,8 @@ cy:
     what_is_mastodon: Beth yw Mastodon?
   accounts:
     choices_html: 'Dewisiadau %{name}:'
+    endorsements_hint: Gallwch gymeradwyo pobl rydych chi'n eu dilyn o'r rhyngwyneb gwe, a byddan nhw'n ymddangos yma.
+    featured_tags_hint: Gallwch ychwanegu hashnodau penodol a fydd yn cael eu harddangos yma.
     follow: Dilynwch
     followers:
       few: Dilynwyr
@@ -65,6 +73,7 @@ cy:
     media: Cyfryngau
     moved_html: 'Mae %{name} wedi symud i %{new_profile_link}:'
     network_hidden: Nid yw'r wybodaeth hyn ar gael
+    never_active: Peidiwch byth
     nothing_here: Does dim byd yma!
     people_followed_by: Pobl y mae %{name} yn ei ddilyn
     people_who_follow: Pobl sy'n dilyn %{name}
@@ -199,6 +208,7 @@ cy:
       username: Enw defnyddiwr
       warn: Rhybuddio
       web: Gwe
+      whitelisted: Rhestredig wen
     action_logs:
       actions:
         assigned_to_self_report: Aseiniodd %{name} adroddiad %{target} i'w hunan
@@ -234,10 +244,12 @@ cy:
       deleted_status: "(statws wedi ei ddileu)"
       title: Log archwilio
     custom_emojis:
+      assign_category: Neilltuo categori
       by_domain: Parth
       copied_msg: Llwyddwyd i greu copi lleol o'r emoji
       copy: Copïo
       copy_failed_msg: Methwyd i greu copi lleol o'r emoji hwnnw
+      create_new_category: Create new category
       created_msg: Llwyddwyd i greu emoji!
       delete: Dileu
       destroyed_msg: Llwyddwyd i ddinistrio emojo!
@@ -254,11 +266,13 @@ cy:
       shortcode: Byrgod
       shortcode_hint: O leiaf 2 nodyn, dim ond nodau alffaniwmerig a tanlinellau
       title: Emoji unigryw
+      uncategorized: Heb gategori
       unlisted: Heb eu rhestru
       update_failed_msg: Methwyd a diweddaru'r emoji hwnnw
       updated_msg: Llwyddwyd i ddiweddaru'r emoji!
       upload: Uwchlwytho
     dashboard:
+      authorized_fetch_mode: Modd nôl awdurdodedig
       backlog: tasgau heb eu cwblhau
       config: Cyfluniad
       feature_deletions: Dileadau cyfrif
@@ -266,10 +280,13 @@ cy:
       feature_profile_directory: Cyfeiriadur proffil
       feature_registrations: Cofrestriadau
       feature_relay: Relái ffederasiwn
+      feature_spam_check: Gwrth-sbam
       feature_timeline_preview: Rhagolwg o'r ffrwd
       features: Nodweddion
       hidden_service: Ffederasiwn a gwasanaethau cudd
       open_reports: adroddiadau agored
+      pending_tags: hashnodau yn aros am adolygiad
+      pending_users: defnyddwyr yn aros am adolygiad
       recent_users: Defnyddwyr diweddar
       search: Chwilio testun llawn
       single_user_mode: Modd un defnyddiwr
@@ -281,11 +298,18 @@ cy:
       week_interactions: ymadweithiau yr wythnos hon
       week_users_active: gweithredol yr wythnos hon
       week_users_new: defnyddwyr yr wythnos hon
+      whitelist_mode: Modd rhestr wen
+    domain_allows:
+      add_new: Rhestrwch parth
+      created_msg: Rhestrwyd wen parth yn llwyddiannus
+      destroyed_msg: Mae parth wedi'i dynnu o'r rhestr wen
+      undo: Tynnwch o'r rhestr wen
     domain_blocks:
       add_new: Ychwanegu bloc parth newydd
       created_msg: Mae'r bloc parth nawr yn cael ei brosesu
       destroyed_msg: Mae'r bloc parth wedi ei ddadwneud
       domain: Parth
+      edit: Golygu bloc parth
       existing_domain_block_html: Rydych yn barod wedi gosod cyfyngau fwy llym ar %{name}, mae rhaid i chi ei <a href="%{unblock_url}">ddadblocio</a> yn gyntaf.
       new:
         create: Creu bloc
@@ -296,6 +320,8 @@ cy:
           silence: Tawelwch
           suspend: Atal
         title: Blocio parth newydd
+      private_comment: Sylw preifat
+      public_comment: Sylw cyhoeddus
       reject_media: Gwrthod dogfennau cyfryngau
       reject_media_hint: Dileu dogfennau cyfryngau wedi eu cadw yn lleol ac yn gwrthod i lawrlwytho unrhyw rai yn y dyfodol. Amherthnasol i ataliadau
       reject_reports: Gwrthod adroddiadau
@@ -319,6 +345,7 @@ cy:
         title: Dadwneud blocio parth ar gyfer %{domain}
         undo: Dadwneud
       undo: Dadwneud bloc parth
+      view: Gweld bloc parth
     email_domain_blocks:
       add_new: Ychwanegu
       created_msg: Llwyddwyd i ychwanegu parth e-bost i'r gosbrestr
@@ -346,6 +373,8 @@ cy:
         all: Pob
         limited: Gyfyngedig
         title: Goruwchwyliad
+      private_comment: Sylw preifat
+      public_comment: Sylw cyhoeddus
       title: Ffederasiwn
       total_blocked_by_us: Wedi'i bloc gan ni
       total_followed_by_them: Yn dilyn ganynt
@@ -423,6 +452,8 @@ cy:
       custom_css:
         desc_html: Addasu gwedd gyda CSS wedi lwytho ar bob tudalen
         title: CSS wedi'i addasu
+      domain_blocks:
+        all: I bawb
       hero:
         desc_html: Yn cael ei arddangos ar y dudadlen flaen. Awgrymir 600x100px oleia. Pan nad yw wedi ei osod, mae'n ymddangos fel mân-lun yr achos
         title: Delwedd arwr
@@ -486,6 +517,7 @@ cy:
         delete: Dileu
         nsfw_off: Marcio fel nad yw'n sensitif
         nsfw_on: Marcio'n sensitif
+      deleted: Dilëwyd
       failed_to_execute: Methwyd a gweithredu
       media:
         title: Cyfryngau
@@ -493,21 +525,14 @@ cy:
       no_status_selected: Ni newidwyd dim statws achos ni ddewiswyd dim un
       title: Statysau cyfrif
       with_media: A chyfryngau
-    subscriptions:
-      callback_url: URL galw-nôl
-      confirmed: Wedi'i gadarnhau
-      expires_in: Dod i ben ymhen
-      last_delivery: Danfoniad diwethaf
-      title: WebSub
-      topic: Pwnc
     tags:
-      accounts: Cyfrifon
-      hidden: Cudd
-      hide: Cuddio o gyfeiriadur
+      context: Cyd-destun
+      last_active: Yn weithredol ddiwethaf
       name: Hashnod
+      reviewed: Wedi'i adolygu
       title: Hashnodau
-      unhide: Dangoswch yn y cyfeiriadur
-      visible: Gweladwy
+      trending_right_now: Yn tueddu nawr
+      unreviewed: Heb ei adolygu
     title: Gweinyddiaeth
     warning_presets:
       add_new: Ychwanegu newydd
@@ -548,7 +573,6 @@ cy:
     apply_for_account: Gofyn am wahoddiad
     change_password: Cyfrinair
     checkbox_agreement_html: Rydw i'n cytuno i'r <a href="%{rules_path}" target="_blank">rheolau'r gweinydd</a> a'r <a href="%{terms_path}" target="_blank">telerau gwasanaeth</a>
-    confirm_email: Cadarnhau e-bost
     delete_account: Dileu cyfrif
     delete_account_html: Os hoffech chi ddileu eich cyfrif, mae modd <a href="%{path}">parhau yma</a>. Bydd gofyn i chi gadarnhau.
     didnt_get_confirmation: Heb dderbyn cyfarwyddiadau cadarnhau?
@@ -568,6 +592,14 @@ cy:
     reset_password: Ailosod cyfrinair
     security: Diogelwch
     set_new_password: Gosod cyfrinair newydd
+    setup:
+      title: Gosodiad
+    status:
+      account_status: Statws cyfrif
+      confirming: Aros i gadarnhad e-bost gael ei gwblhau.
+      functional: Mae eich cyfrif yn gwbl weithredol.
+      pending: Mae'ch cais yn aros i gael ei adolygu gan ein staff. Gall hyn gymryd cryn amser. Byddwch yn derbyn e-bost os caiff eich cais ei gymeradwyo.
+      redirecting_to: Mae eich cyfrif yn anactif oherwydd ei fod ar hyn o bryd yn ailgyfeirio i %{acct}.
     trouble_logging_in: Trafferdd mewngofnodi?
   authorize_follow:
     already_following: Yr ydych yn dilyn y cyfrif hwn yn barod
@@ -580,6 +612,11 @@ cy:
       return: Dangos proffil y defnyddiwr
       web: I'r wê
     title: Dilyn %{acct}
+  challenge:
+    confirm: Parhau
+    hint_html: "<strong>Awgrym:</strong> Ni fyddwn yn gofyn i chi am eich cyfrinair eto am yr awr nesaf."
+    invalid_password: Cyfrinair annilys
+    prompt: Cadarnhewch gyfrinair i barhau
   datetime:
     distance_in_words:
       about_x_hours: "%{count}awr"
@@ -595,30 +632,27 @@ cy:
       x_months: "%{count}mis"
       x_seconds: "%{count}eiliad"
   deletes:
-    bad_password_msg: Go dda, hacwyr! Cyfrinair anghywir
+    challenge_not_passed: Nid oedd y wybodaeth a nodoch yn gywir
     confirm_password: Mewnbynnwch eich cyfrinair presennol i gadarnhau mai chi sydd yno
-    description_html: Bydd hyn yn cael gwared ar gynnwys o'ch cyfrif <strong>am byth heb fodd i'w adfer</strong> ac yn diffodd y cyfrif. Caiff eich eich enw defnyddiwr ei gadw i atal unrhyw ddynwarediadau yn y dyfodol.
+    confirm_username: Rhowch eich enw defnyddiwr i gadarnhau'r weithdrefn
     proceed: Dileu cyfrif
     success_msg: Llwyddwyd i ddileu eich cyfrif
-    warning_html: Dim ond dileu cynnwys o'r achos hwn ellid bod yn sicr ei fod wedi ei ddileu. Mae cynnwys sydd wedi ei rannu'n eang yn debygol o adael olion. Ni fydd gweinyddwyr all-lein a gweinyddwyr sydd wedi dad-danysgrifio o'ch diwedderiadau ddim yn diweddaru eu cronfeydd data.
-    warning_title: Argaeledd cynnwys wedi'i rannu
+    warning:
+      before: 'Cyn bwrw ymlaen, darllenwch y nodiadau hyn yn ofalus:'
+      irreversible: Ni fyddwch yn gallu adfer nac ail-greu eich cyfrif
+      username_available: Bydd eich enw defnyddiwr ar gael eto
+      username_unavailable: Ni fydd eich enw defnyddiwr ar gael
   directories:
     directory: Cyfeiriadur proffil
-    enabled: Rydych chi wedi'ch rhestru yn y cyfeiriadur ar hyn o bryd.
-    enabled_but_waiting: Rydych wedi dewis i chi gael eich rhestru yn y cyfeiriadur, ond nid oes gennych y nifer lleiaf o ddilynwyr (%{min_followers}) i'w rhestru eto.
     explanation: Darganfod defnyddwyr yn seiliedig ar eu diddordebau
     explore_mastodon: Archwilio %{title}
-    how_to_enable: Ar hyn o bryd nid ydych chi wedi dewis y cyfeiriadur. Gallwch ddewis i mewn isod. Defnyddiwch hashnodau yn eich bio-destun i'w restru dan hashnodau penodol!
-    people:
-      few: "%{count} o bobl"
-      many: "%{count} o bobl"
-      one: "%{count} berson"
-      other: "%{count} o bobl"
-      two: "%{count} o bobl"
-      zero: "%{count} person"
+  domain_validator:
+    invalid_domain: ddim yn enw parth dilys
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Nid oes gennych ganiatad i weld y dudalen hon.
     '404': Nid yw'r dudalen yr oeddech yn chwilio amdani'n bodoli.
+    '406': This page is not available in the requested format.
     '410': Nid yw'r dudalen yr oeddech yn chwilio amdani'n bodoli mwyach.
     '422':
       content: Methwyd i ddilysu diogelwch. A ydych chi'n blocio cwcîs?
@@ -627,6 +661,7 @@ cy:
     '500':
       content: Mae'n ddrwg gennym ni, ond fe aeth rhywbeth o'i le ar ein rhan ni.
       title: Nid yw'r dudalen hon yn gywir
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: I ddefnyddio ap gwe Mastodon, galluogwch JavaScript os gwlwch yn dda. Fel arall, gallwch drio un o'r <a href="%{apps_path}">apiau cynhenid</a> ar gyfer Mastodon ar eich platfform.
   existing_username_validator:
     not_found: ni ddarganfwyd defnyddiwr lleol gyda'r enw cyfrif hynny
@@ -670,6 +705,7 @@ cy:
     developers: Datblygwyr
     more: Mwy…
     resources: Adnoddau
+    trending_now: Yn tueddu nawr
   generic:
     all: Popeth
     changes_saved_msg: Llwyddwyd i gadw y newidiadau!
@@ -753,9 +789,12 @@ cy:
       too_many: Ni ellir ychwanegu mwy na 4 dogfen
   migrations:
     acct: enwdefnyddiwr@parth y cyfrif newydd
-    currently_redirecting: 'Mae eich proffil wedi ei osod i ailgyfeirio i:'
-    proceed: Cadw
-    updated_msg: Diweddarwyd gosodiad mudo eich cyfrif yn llwyddiannus!
+    cancel: Canslo ailgyfeirio
+    errors:
+      not_found: ni ellid dod o hyd iddo
+    past_migrations: Ymfudiadau yn y gorffennol
+    warning:
+      before: 'Cyn bwrw ymlaen, darllenwch y nodiadau hyn yn ofalus:'
   moderation:
     title: Goruwchwyliad
   notification_mailer:
@@ -862,10 +901,6 @@ cy:
     reply:
       proceed: Ymlaen i ateb
       prompt: 'Hoffech ateb y tŵt hon:'
-  remote_unfollow:
-    error: Gwall
-    title: Teitl
-    unfollowed: Dad-ddilynwyd
   scheduled_statuses:
     over_daily_limit: Rydych wedi rhagori'r cyfwng o %{limit} o dŵtiau rhestredig ar y dydd hynny
     over_total_limit: Rydych wedi rhagori'r cyfwng o %{limit} o dŵtiau rhestredig
@@ -914,6 +949,7 @@ cy:
   settings:
     account: Cyfrif
     account_settings: Gosodiadau'r cyfrif
+    aliases: Aliasau cyfrif
     appearance: Arddangosiad
     authorized_apps: Apiau awdurdodedig
     back: Yn ôl i Mastodon
@@ -988,6 +1024,8 @@ cy:
     pinned: Tŵt wedi'i binio
     reblogged: hybwyd
     sensitive_content: Cynnwys sensitif
+  tags:
+    does_not_match_previous_name: ddim yn cyfateb i'r enw blaenorol
   terms:
     body_html: |
       <h2>Polisi Preifatrwydd</h2>
@@ -1105,7 +1143,9 @@ cy:
         disable: Er bod eich cyfrif wedi'i rewi, mae eich data cyfrif yn parhau i fod yn gyfan, ond ni allwch chi berfformio unrhyw gamau nes ei ddatgloi.
         silence: Pan mae eich cyfrif yn gyfyngiedig, dim ond pobl sydd yn barod yn eich dilyn yn gweld eich tŵtiau ar y gweinydd hon, a efallai byddwch yn cael eich tynnu o restrau cyhoeddus. Er hyn, gall eraill eich dilyn chi wrth law.
         suspend: Mae eich cyfrif wedi cael ei wahardd, a mae gyd o'ch tŵtiau a'ch ffeiliau cyfrwng uwchlwythadwy wedi cael eu tynnu or gweinydd yn barhaol, ac o weinyddau ble yr oedd eich dilynwyr.
+      get_in_touch: Gallwch ymateb i'r e-bost hwn i gysylltu â staff %{instance}.
       review_server_policies: Adolygu polisïau'r gweinydd
+      statuses: 'Yn benodol, ar gyfer:'
       subject:
         disable: Mae'ch cyfrif %{acct} wedi'i rewi
         none: Rhybudd am %{acct}
diff --git a/config/locales/da.yml b/config/locales/da.yml
index da6ab1054514d89477b15549d8ae658ad00c291e..5e291154a80c2d376f522a063c119972cfd270db 100644
--- a/config/locales/da.yml
+++ b/config/locales/da.yml
@@ -4,23 +4,28 @@ da:
     about_hashtag_html: Disse er offentlige trut der indeholder tagget <strong>#%{hashtag}</strong>. Du kan interagere med dem hvis du har en konto hvor som helst i fediverset.
     about_mastodon_html: Mastodon er et socialt netværk der er baseret på åbne web protokoller og frit, open-source source software. Der er decentraliseret ligesom e-mail tjenester.
     about_this: Om
+    active_count_after: aktive
     administered_by: 'Administreret af:'
+    api: API
     apps: Apps til mobilen
     apps_platforms: Brug Mastodon på iOS, Android og andre platformer
     contact: Kontakt
     contact_missing: Ikke sat
     contact_unavailable: Ikke tilgængeligt
+    discover_users: Opdag brugere
     documentation: Dokumentation
-    extended_description_html: |
-      <h3>Et godt sted for regler</h3>
-      <p>Den udvidede beskrivelse er endnu ikke blevet opsat.</p>
-    generic_description: "%{domain} er en server i netværket"
+    get_apps: Prøv en mobil app
     hosted_on: Mostodon hostet på %{domain}
     learn_more: Lær mere
     privacy_policy: Privatlivspolitik
+    see_whats_happening: Se hvad der sker
     source_code: Kildekode
+    status_count_after:
+      one: status
+      other: statusser
     status_count_before: Som har skrevet
     terms: Vilkår for service
+    unavailable_content: Utilgængeligt indhold
     user_count_after:
       one: bruger
       other: brugere
@@ -34,10 +39,12 @@ da:
       other: Følgere
     following: Følger
     joined: Tilmeldt den %{date}
+    last_active: sidst aktiv
     link_verified_on: Ejerskabet af dette link blev tjekket den %{date}
     media: Medier
     moved_html: "%{name} er flyttet til %{new_profile_link}:"
     network_hidden: Denne information er ikke tilgængelig
+    never_active: Aldrig
     nothing_here: Der er intet her!
     people_followed_by: Folk som %{name} følger
     people_who_follow: Folk der følger %{name}
@@ -53,6 +60,7 @@ da:
       admin: Administrator
       bot: Robot
       moderator: Moderator
+    unavailable: Profil utilgængelig
     unfollow: Følg ikke længere
   admin:
     account_actions:
@@ -63,6 +71,8 @@ da:
       delete: Slet
       destroyed_msg: Moderator notat succesfuldt destrueret!
     accounts:
+      approve: Godkend
+      approve_all: Godkend alle
       are_you_sure: Er du sikker?
       avatar: Profilbillede
       by_domain: Domæne
@@ -76,6 +86,7 @@ da:
       confirm: Bekræft
       confirmed: Bekræftet
       confirming: Bekræfter
+      deleted: Slettet
       demote: Degrader
       disable: Deaktiver
       disable_two_factor_authentication: Deaktiver 2FA
@@ -90,7 +101,9 @@ da:
       followers_url: Link til følgere
       follows: Følger
       inbox_url: Link til indbakke
+      invited_by: Inviteret af
       ip: IP-adresse
+      joined: Tilmeldt den
       location:
         all: Alle
         local: Lokalt
@@ -100,7 +113,9 @@ da:
       media_attachments: Medie bilag
       memorialize: Omdan til et memoriam
       moderation:
+        active: Aktiv
         all: Alle
+        pending: Afventer
         silenced: Dæmpet
         suspended: Udelukket
         title: Moderasion
@@ -116,6 +131,8 @@ da:
       public: Offentligt
       push_subscription_expires: PuSH abonnement udløber
       redownload: Opdater profil
+      reject: Afvis
+      reject_all: Afvis alle
       remove_avatar: Fjern profilbillede
       resend_confirmation:
         already_confirmed: Denne bruger er allerede blevet bekræftet
@@ -147,11 +164,14 @@ da:
       undo_suspension: Fortryd udelukkelse
       unsubscribe: Abonner ikke længere
       username: Brugernavn
+      warn: Advar
+      whitelisted: Hvidlistet
     action_logs:
       actions:
         assigned_to_self_report: "%{name} tildelte anmeldelsen %{target} til sig selv"
         change_email_user: "%{name} ændrede email adressen for brugeren %{target}"
         confirm_user: "%{name} bekræftede %{target}s email adresse"
+        create_account_warning: "%{name} sendte en advarsel til %{target}"
         create_custom_emoji: "%{name} uploadede humørikonet %{target}"
         create_domain_block: "%{name} blokerede domænet %{target}"
         create_email_domain_block: "%{name} sortlistede email domænet %{target}"
@@ -184,6 +204,7 @@ da:
       copied_msg: Succesfuldt oprettede en lokal kopi af humørikonet
       copy: Kopier
       copy_failed_msg: Kunne ikke oprette en lokal kopi af dette humørikon
+      create_new_category: Opret ny kategori
       created_msg: Humørikon succesfuldt oprettet!
       delete: Slet
       destroyed_msg: Emojo succesfuldt destrueret!
@@ -209,6 +230,7 @@ da:
       config: Konfiguration
       feature_deletions: Konto sletninger
       feature_invites: Invitations links
+      feature_profile_directory: Profilliste
       feature_registrations: Registreringer
       feature_relay: Føderations relæ
       features: Funktioner
@@ -224,6 +246,8 @@ da:
       week_interactions: interaktioner denne uge
       week_users_active: aktive denne uge
       week_users_new: brugere denne uge
+    domain_allows:
+      undo: Fjern fra hvidliste
     domain_blocks:
       add_new: Tilføj ny domain block
       created_msg: Domæne blokade bliver nu behandlet
@@ -238,6 +262,8 @@ da:
           silence: Dæmp
           suspend: Udeluk
         title: Ny domæne blokering
+      private_comment: Privat kommentar
+      public_comment: Offentlig kommentar
       reject_media: Afvis medie filer
       reject_media_hint: Fjerner lokalt lagrede multimedie filer og nægter at hente nogen i fremtiden. Irrelevant for udelukkelser
       reject_reports: Afvis anmeldelser
@@ -263,8 +289,18 @@ da:
       title: Email sortliste
     followers:
       back_to_account: Tilbage til konto
+      title: "%{acct}'s følgere"
     instances:
+      by_domain: Domæne
+      moderation:
+        all: Alle
+        limited: Begrænset
+      private_comment: Privat kommentar
+      public_comment: Offentlig kommentar
       title: Førderation
+      total_blocked_by_us: Blokeret af os
+      total_followed_by_them: Fulgt af dem
+      total_followed_by_us: Fulgt af os
     invites:
       deactivate_all: Deaktiver alle
       filter:
@@ -286,6 +322,7 @@ da:
       pending: Venter på godkendelse fra relæet
       save_and_enable: Gem og aktiver
       setup: Opsæt en videresendelses forbindelse
+      status: Status
       title: Videresendelser
     report_notes:
       created_msg: Anmeldelse note blev oprettet!
@@ -315,6 +352,7 @@ da:
       reported_by: Anmeldt af
       resolved: Løst
       resolved_msg: Anmeldelse er sat til at være løst!
+      status: Status
       title: Anmeldelser
       unassign: Utildel
       unresolved: Uløst
@@ -332,6 +370,9 @@ da:
       custom_css:
         desc_html: Ændre udseendet med CSS indlæst på hver side
         title: Brugerdefineret CSS
+      domain_blocks:
+        all: Til alle
+        disabled: Til ingen
       hero:
         desc_html: Vist på forsiden. Mindst 600x100px anbefales. Hvis ikke sat, vil dette falde tilbage til billedet fra serveren
         title: Billede af helt
@@ -351,6 +392,10 @@ da:
         min_invite_role:
           disabled: Ingen
           title: Tillad invitationer af
+      registrations_mode:
+        modes:
+          none: Ingen kan tilmelde sig
+          open: Alle kan tilmelde sig
       show_known_fediverse_at_about_page:
         desc_html: Når slået til, vil det vise trut fra hele det kendte fedivers på forhåndsvisning. Ellers vil det kun vise lokale trut.
         title: Vis kendte fedivers på tidslinje forhåndsvisning
@@ -383,6 +428,7 @@ da:
         delete: Slet
         nsfw_off: Marker som værende ikke følsomt
         nsfw_on: Marker som værende følsomt
+      deleted: Slettet
       failed_to_execute: Udførelsen kunne ikke lade sig gøre
       media:
         title: Multimedier
@@ -390,23 +436,24 @@ da:
       no_status_selected: Ingen statusser blev ændret eller ingen blev valgt
       title: Konto statusser
       with_media: Med multimedier
-    subscriptions:
-      callback_url: Callback-URL
-      confirmed: Bekræftet
-      expires_in: Udløber om
-      last_delivery: Sidste levering
-      title: Websub
-      topic: Emne
     tags:
-      accounts: Kontoer
-      hidden: Skjult
+      accounts_today: Unikke brug i dag
+      accounts_week: Unikke brug denne uge
+      context: Kontekst
+    warning_presets:
+      delete: Slet
+      edit: Rediger
   admin_mailer:
     new_report:
       body: "%{reporter} har anmeldt %{target}"
       body_remote: Nogen fra %{domain} har anmeldt %{target}
       subject: Ny anmeldelse for %{instance} (#%{id})
+  appearance:
+    discovery: Opdagelse
+    sensitive_content: Følsomt indhold
   application_mailer:
     notification_preferences: Ændre email præferencer
+    salutation: "%{name}"
     settings: 'Ændre email præferencer: %{link}'
     view: 'Se:'
     view_profile: Se profil
@@ -420,10 +467,12 @@ da:
     warning: Vær meget forsigtig med disse data. Del dem aldrig med nogen!
     your_token: Din adgangs token
   auth:
+    apply_for_account: Anmod om en invitation
     change_password: Kodeord
-    confirm_email: Bekræft email
     delete_account: Slet konto
     delete_account_html: Hvis du ønsker at slette din konto, kan du <a href="%{path}">gøre det her</a>. Du vil blive bedt om bekræftelse.
+    description:
+      prefix_sign_up: Tilmeld dig Mastodon i dag!
     didnt_get_confirmation: Har du endnu ikke modtaget instrukser for bekræftelse?
     forgot_password: Glemt dit kodeord?
     invalid_reset_password_token: Adgangskode nulstillings token er ugyldig eller udløbet. Anmod venligst om en ny.
@@ -433,10 +482,14 @@ da:
     migrate_account_html: Hvis du ønsker at omdirigere denne konto til en anden, kan du <a href="%{path}">gøre det her</a>.
     or_log_in_with: Eller log in med
     register: Opret dig
+    registration_closed: "%{instance} accepterer ikke nye medlemmer"
     resend_confirmation: Gensend bekræftelses instrukser
     reset_password: Nulstil kodeord
     security: Sikkerhed
     set_new_password: Sæt et nyt kodeord
+    status:
+      account_status: Kontostatus
+    trouble_logging_in: Har du problemer med at logge på?
   authorize_follow:
     already_following: Du følger allerede denne konto
     error: Der opstod desværre en fejl under søgningen af denne fjerne konto
@@ -459,16 +512,20 @@ da:
       over_x_years: "%{count}Ã¥r"
       x_months: "%{count}md"
   deletes:
-    bad_password_msg: Godt forsøg, hackere! Forkert kodeord
     confirm_password: Indtast dit nuværende kodeord for at bekræfte din identitet
-    description_html: Dette vil <strong>permanent, uigenkaldeligt</strong> fjerne indhold fra din konto samt deaktivere den. Dit brugernavn vil forblive reserveret for at forhindre fremtidige efterligninger.
     proceed: Slet konto
     success_msg: Din konto er nu blevet slettet
-    warning_html: Kun sletning af indhold fra denne specifikke server er garanteret. Indhold der er blevet delt rundt omkring vil sandsynligvis efterlade spor. Offline servere og servere der ikke længere abonnerer på dine opdateringer vil ikke opdatere deres databaser.
-    warning_title: Tilgængelighed af delt indhold
+    warning:
+      username_available: Dit brugernavn vil blive tilgængeligt igen
+      username_unavailable: Dit brugernavn vil forblive utilgængeligt
+  directories:
+    directory: Profilliste
+    explore_mastodon: Uforsk %{title}
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Du har ikke tilladelse til at se denne side.
     '404': Den side du leder efter findes ikke.
+    '406': This page is not available in the requested format.
     '410': Den side du leder efter findes ikke mere.
     '422':
       content: Sikkerhedsbekræftelse mislykkedes. Blokerer du cookies?
@@ -477,7 +534,11 @@ da:
     '500':
       content: Beklager men der gik noget galt i vores ende.
       title: Siden er ikke korrekt
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: For at bruge Mastodon web applikationen, aktiver JavaScript. Alternativt kan du prøve en af disse <a href="%{apps_path}">apps</a> til Mastodon for din platform.
+  existing_username_validator:
+    not_found: kunne ikke finde en lokal bruger med dette brugenavn
+    not_found_multiple: kunne ikke finde %{usernames}
   exports:
     archive_takeout:
       date: Dato
@@ -487,7 +548,9 @@ da:
       request: Anmod om dit arkiv
       size: Størrelse
     blocks: Du blokerer
+    csv: CSV
     follows: Du følger
+    lists: Lister
     mutes: Du dæmper
     storage: Medie lager
   filters:
@@ -510,14 +573,22 @@ da:
     developers: Udviklere
     more: Mere…
     resources: Ressourcer
+    trending_now: Hot lige nu
   generic:
+    all: Alle
     changes_saved_msg: Ændringerne blev gemt!
     copy: Kopier
+    order_by: Sorter efter
     save_changes: Gem ændringer
     validation_errors:
       one: Der er noget der ikke er helt som det bør være! Tag lige et kig på følgende fejl forneden
       other: Der er noget der ikke er helt som det bør være! Tag lige et kig på følgende %{count} fejl forneden
+  identity_proofs:
+    i_am_html: Jeg er %{username} på %{service}.
+    identity: Identitet
   imports:
+    modes:
+      overwrite: Overskriv
     preface: Du kan importere data du har eksporteret fra en anden server, så som en liste over folk du følger eller blokerer.
     success: Dine data blev succesfuldt uploaded og vil nu blive behandlet hurtigst muligt
     types:
@@ -557,9 +628,6 @@ da:
       too_many: Kan ikke vedhæfte mere en 4 filer
   migrations:
     acct: username@domain af den nye konto
-    currently_redirecting: 'Din profil er sat til at henvise til:'
-    proceed: Gem
-    updated_msg: Dine konti migrærings indstillinger blev opdateret!
   moderation:
     title: Moderatering
   notification_mailer:
@@ -608,20 +676,28 @@ da:
     older: Ældre
     prev: Forrige
     truncate: "...&hellip;"
+  polls:
+    errors:
+      duration_too_long: er for langt ude i fremtiden
+      duration_too_short: er for tidligy
+      expired: Denne afstemning er allerede afsluttet
   preferences:
     other: Andet
+    public_timelines: Offentlige tidslinjer
+  relationships:
+    last_active: Sidst aktiv
+    moved: Flyttet
+    mutual: Fælles
+    primary: Primær
   remote_follow:
     acct: Indtast dit brugernavn@domæne du vil handle fra
     missing_resource: Kunne ikke finde det påkrævede omdirigerings link for din konto
     no_account_html: Har du ikke en konto? Du kan <a href='%{sign_up_path}' target='_blank'>oprette dig her</a>
     proceed: Fortsæt for at følge
     prompt: 'Du er ved at følge:'
-  remote_unfollow:
-    error: Fejl
-    title: Titel
-    unfollowed: Følger ikke længere
   sessions:
     activity: Sidste aktivitet
+    browser: Browser
     browsers:
       alipay: Ali-pay
       blackberry: Blackberry OS
@@ -643,11 +719,15 @@ da:
     current_session: Nuværrende session
     description: "%{browser} på %{platform}"
     explanation: Disse er de web browsere der på nuværende tidspunkt er logget ind på din Mastodon konto.
+    ip: IP
     platforms:
       adobe_air: Adobe air
+      android: Android
       blackberry: Blackberry OS
       chrome_os: Chromeos
       firefox_os: Firefox Os
+      ios: iOS
+      linux: Linux
       mac: Mac.
       other: ukendt platform
       windows: Microsoft windows
@@ -657,6 +737,9 @@ da:
     revoke_success: Sessionen blev tilbagekaldt
     title: Sessioner
   settings:
+    account: Konto
+    account_settings: Kontoindstillinger
+    appearance: Udseende
     authorized_apps: Godkendte apps
     back: Tilbage til Mastodon
     delete: Sletning af konto
@@ -664,9 +747,12 @@ da:
     edit_profile: Rediger profil
     export: Data eksportering
     import: Importer
+    import_and_export: Importer og eksporter
     migrate: Konto migrering
     notifications: Notifikationer
     preferences: Præferencer
+    profile: Profil
+    relationships: Følger og følgere
     two_factor_authentication: To-faktor godkendelse
   statuses:
     attached:
@@ -674,6 +760,9 @@ da:
       image:
         one: "%{count} billede"
         other: "%{count} billeder"
+      video:
+        one: "%{count} video"
+        other: "%{count} videoer"
     boosted_from_html: Fremhævet fra %{acct_link}
     content_warning: 'Advarsel om indhold: %{warning}'
     disallowed_hashtags:
@@ -687,6 +776,8 @@ da:
       ownership: Du kan ikke fastgøre en anden persons trut
       private: Ikke offentlige trut kan ikke blive fastgjort
       reblog: Fremhævede trut kan ikke fastgøres
+    poll:
+      vote: Stem
     show_more: Vis mere
     sign_in_to_participate: Log ind for at deltage i samtalen
     visibilities:
@@ -728,6 +819,9 @@ da:
       explanation: Din anmodning for fuld backup af din Mastodon konto. Den er nu klar til at blive hentet!
       subject: Dit arkiv er klar til at blive hentet ned
       title: Udpluk af arkiv
+    warning:
+      title:
+        none: Advarsel
     welcome:
       edit_profile_action: Opsæt profil
       edit_profile_step: Du kan skræddersy din profil ved at uploade et profilbillede, overskrift, ændre dit visningsnavn og mere. Hvis du kunne tænke dig at gennemse nye følgere før de må følge dig, kan du låse din konto.
@@ -751,3 +845,5 @@ da:
     otp_lost_help_html: Hvis du har mistet adgang til begge, kan du få kontakt via %{email}
     seamless_external_login: Du er logget ind via en ekstern service, så er kodeord og e-mail indstillinger ikke tilgængelige.
     signed_in_as: 'Logget ind som:'
+  verification:
+    verification: Verificering
diff --git a/config/locales/de.yml b/config/locales/de.yml
index cfdaacab0657d17a66674eb585cc8fedae6e954a..77c66fb713c9d3a7221957e18424683904104d6e 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -5,7 +5,7 @@ de:
     about_mastodon_html: Mastodon ist ein soziales Netzwerk. Es basiert auf offenen Web-Protokollen und freier, quelloffener Software. Es ist dezentral (so wie E-Mail!).
     about_this: Über diesen Server
     active_count_after: aktiv
-    active_footnote: Monatlich Aktive Nutzer_innen (MAU)
+    active_footnote: Monatlich Aktive User (MAU)
     administered_by: 'Betrieben von:'
     api: API
     apps: Mobile Apps
@@ -15,15 +15,14 @@ de:
     contact: Kontakt
     contact_missing: Nicht angegeben
     contact_unavailable: Nicht verfügbar
-    discover_users: Benutzer_innen entdecken
+    discover_users: Benutzer entdecken
     documentation: Dokumentation
-    extended_description_html: |
-      <h3>Ein hervorragender Ort für Regeln</h3>
-      <p>Die erweiterte Beschreibung wurde von dem Administrator noch nicht eingestellt.</p>
-    federation_hint_html: Mit einem Konto auf %{instance} wirst du in der Lage sein Nutzer_innen auf beliebigen Mastodon-Servern und darüber hinaus zu folgen.
-    generic_description: "%{domain} ist ein Server im Fediversum"
+    federation_hint_html: Mit einem Account auf %{instance} wirst du in der Lage sein Nutzern auf irgendeinem Mastodon-Server und darüber hinaus zu folgen.
     get_apps: Versuche eine mobile App
     hosted_on: Mastodon, gehostet auf %{domain}
+    instance_actor_flash: |
+      Dieses Konto ist ein virtueller Akteur, der den Server selbst und nicht einen einzelnen Benutzer repräsentiert.
+      Dieser wird für Föderationszwecke verwendet und sollte nicht blockiert werden, es sei denn du möchtest die gesamte Instanz blockieren.
     learn_more: Mehr erfahren
     privacy_policy: Datenschutzerklärung
     see_whats_happening: Finde heraus, was gerade in der Welt los ist
@@ -35,6 +34,14 @@ de:
     status_count_before: mit
     tagline: Finde deine Freunde und entdecke neue
     terms: Nutzungsbedingungen
+    unavailable_content: Nicht verfügbarer Inhalt
+    unavailable_content_description:
+      domain: Server
+      reason: 'Grund:'
+      rejecting_media: Mediendateien dieses Servers werden nicht verarbeitet und keine Thumbnails werden angezeigt, was manuelles anklicken auf den anderen Server erfordert.
+      silenced: Beiträge von diesem Server werden nirgends angezeigt, außer in deiner Startseite, wenn du der Person folgst, die den Beitrag verfasst hat.
+      suspended: Du kannst niemanden von diesem Server folgen, und keine Daten werden verarbeitet oder gespeichert und keine Daten ausgetauscht.
+    unavailable_content_html: Mastodon erlaubt es dir generell, mit Inhalten zu interagieren, diese anzuzeigen und mit anderen Nutzern im Fediversum über Server hinweg zu interagieren. Dies sind die Ausnahmen, die auf diesem bestimmten Server gemacht wurden.
     user_count_after:
       one: Profil
       other: Profile
@@ -42,10 +49,12 @@ de:
     what_is_mastodon: Was ist Mastodon?
   accounts:
     choices_html: "%{name} empfiehlt:"
+    endorsements_hint: Du kannst Personen, denen du über die Weboberfläche folgst, auswählen, und sie werden hier angezeigt.
+    featured_tags_hint: Du kannst spezifische Hashtags, die hier angezeigt werden, angeben.
     follow: Folgen
     followers:
-      one: Folger_innen
-      other: Folger_innen
+      one: Folgender
+      other: Folgende
     following: Folgt
     joined: Beigetreten am %{date}
     last_active: zuletzt aktiv
@@ -53,6 +62,7 @@ de:
     media: Medien
     moved_html: "%{name} ist auf %{new_profile_link} umgezogen:"
     network_hidden: Diese Informationen sind nicht verfügbar
+    never_active: Nie
     nothing_here: Hier gibt es nichts!
     people_followed_by: Profile, denen %{name} folgt
     people_who_follow: Profile, die %{name} folgen
@@ -108,8 +118,8 @@ de:
       enable: Freischalten
       enabled: Freigegeben
       feed_url: Feed-URL
-      followers: Folger_innen
-      followers_url: URL der Folger_innen
+      followers: Folgende
+      followers_url: URL des Folgenden
       follows: Folgt
       header: Titelbild
       inbox_url: Posteingangs-URL
@@ -183,6 +193,7 @@ de:
       username: Profilname
       warn: Warnen
       web: Web
+      whitelisted: Auf der Whitelist
     action_logs:
       actions:
         assigned_to_self_report: "%{name} hat sich die Meldung %{target} selbst zugewiesen"
@@ -218,19 +229,24 @@ de:
       deleted_status: "(gelöschter Beitrag)"
       title: Überprüfungsprotokoll
     custom_emojis:
+      assign_category: Kategorie zuweisen
       by_domain: Domain
       copied_msg: Eine lokale Kopie des Emojis wurde erstellt
       copy: Kopieren
       copy_failed_msg: Es konnte keine lokale Kopie des Emojis erstellt werden
+      create_new_category: Neue Kategorie erstellen
       created_msg: Emoji erstellt!
       delete: Löschen
       destroyed_msg: Emoji gelöscht!
       disable: Deaktivieren
+      disabled: Deaktiviert
       disabled_msg: Das Emoji wurde deaktiviert
       emoji: Emoji
       enable: Aktivieren
+      enabled: Aktiviert
       enabled_msg: Das Emoji wurde aktiviert
       image_hint: PNG bis zu 50 kB
+      list: Liste
       listed: Gelistet
       new:
         title: Eigenes Emoji hinzufügen
@@ -238,11 +254,14 @@ de:
       shortcode: Kürzel
       shortcode_hint: Mindestens 2 Zeichen, nur Buchstaben, Ziffern und Unterstriche
       title: Eigene Emojis
+      uncategorized: Nicht kategorisiert
+      unlist: Nicht listen
       unlisted: Ungelistet
       update_failed_msg: Konnte dieses Emoji nicht aktualisieren
       updated_msg: Emoji erfolgreich aktualisiert!
       upload: Hochladen
     dashboard:
+      authorized_fetch_mode: Autorisierter Abruf-Modus
       backlog: Rückständige Jobs
       config: Konfiguration
       feature_deletions: Kontolöschung
@@ -250,26 +269,36 @@ de:
       feature_profile_directory: Profilverzeichnis
       feature_registrations: Offene Anmeldung
       feature_relay: Föderationsrelais
+      feature_spam_check: Anti-Spam
       feature_timeline_preview: Zeitleistenvorschau
       features: Funktionen
       hidden_service: Föderation mit versteckten Diensten
       open_reports: Ausstehende Meldungen
+      pending_tags: Hashtags, die auf eine Überprüfung warten
+      pending_users: Benutzer, die auf eine Überprüfung warten
       recent_users: Neueste Nutzer
       search: Volltextsuche
       single_user_mode: Einzelnutzermodus
       software: Software
       space: Speicherverbrauch
       title: Übersicht
-      total_users: Benutzer_innen insgesamt
+      total_users: Benutzer insgesamt
       trends: Trends
       week_interactions: Interaktionen diese Woche
       week_users_active: Aktiv diese Woche
-      week_users_new: Benutzer_innen diese Woche
+      week_users_new: Benutzer diese Woche
+      whitelist_mode: Whitelist-Modus
+    domain_allows:
+      add_new: Whitelist-Domain
+      created_msg: Domain wurde erfolgreich zur Whitelist hinzugefügt
+      destroyed_msg: Domain wurde von der Whitelist entfernt
+      undo: Von der Whitelist entfernen
     domain_blocks:
       add_new: Neue Domainblockade hinzufügen
       created_msg: Die Domain-Blockade wird nun durchgeführt
       destroyed_msg: Die Domain-Blockade wurde rückgängig gemacht
       domain: Domain
+      edit: Domainblockade bearbeiten
       existing_domain_block_html: Es gibt schon eine Blockade für %{name}, diese muss erst <a href="%{unblock_url}">aufgehoben</a> werden.
       new:
         create: Blockade einrichten
@@ -280,6 +309,10 @@ de:
           silence: Stummschaltung
           suspend: Sperre
         title: Neue Domain-Blockade
+      private_comment: Privater Kommentar
+      private_comment_hint: Kommentar zu dieser Domain-Beschränkung für die interne Nutzung durch die Moderatoren.
+      public_comment: Öffentlicher Kommentar
+      public_comment_hint: Kommentar zu dieser Domain-Beschränkung für die allgemeine Öffentlichkeit, wenn das Veröffentlichen der Blockliste aktiviert ist.
       reject_media: Mediendateien ablehnen
       reject_media_hint: Entfernt lokal gespeicherte Mediendateien und verhindert deren künftiges Herunterladen. Für Sperren irrelevant
       reject_reports: Meldungen ablehnen
@@ -299,6 +332,7 @@ de:
         title: Domain-Blockade für %{domain} zurücknehmen
         undo: Zurücknehmen
       undo: Domainblockade zurücknehmen
+      view: Zeige Domain-Blockade
     email_domain_blocks:
       add_new: Neue hinzufügen
       created_msg: E-Mail-Domain-Blockade erfolgreich erstellt
@@ -311,7 +345,7 @@ de:
       title: E-Mail-Domain-Blockade
     followers:
       back_to_account: Zurück zum Konto
-      title: "%{acct}'s Folger_innen"
+      title: "%{acct}'s Follower"
     instances:
       by_domain: Domain
       delivery_available: Zustellung funktioniert
@@ -322,6 +356,8 @@ de:
         all: Alle
         limited: Beschränkt
         title: Moderation
+      private_comment: Privater Kommentar
+      public_comment: Öffentlicher Kommentar
       title: Föderation
       total_blocked_by_us: Von uns blockiert
       total_followed_by_them: Gefolgt von denen
@@ -351,6 +387,7 @@ de:
       pending: Warte auf Zustimmung des Relays
       save_and_enable: Speichern und aktivieren
       setup: Relaisverbindung einrichten
+      signatures_not_enabled: Relais funktionieren nicht korrekt, während der sichere Modus oder der Whitelist-Modus aktiviert ist
       status: Zustand
       title: Relais
     report_notes:
@@ -399,6 +436,16 @@ de:
       custom_css:
         desc_html: Verändere das Aussehen mit CSS, dass auf jeder Seite geladen wird
         title: Benutzerdefiniertes CSS
+      default_noindex:
+        desc_html: Beeinflusst alle Benutzer, die diese Einstellung nicht selbst geändert haben
+        title: Benutzer aus Suchmaschinen-Indizierung standardmäßig herausnehmen
+      domain_blocks:
+        all: An alle
+        disabled: An niemanden
+        title: Zeige Domain-Blockaden
+        users: Für angemeldete lokale Benutzer
+      domain_blocks_rationale:
+        title: Rationale anzeigen
       hero:
         desc_html: Wird auf der Startseite angezeigt. Mindestens 600x100px sind empfohlen. Wenn es nicht gesetzt wurde, wird das Server-Thumbnail dafür verwendet
         title: Bild für Einstiegsseite
@@ -449,6 +496,9 @@ de:
         desc_html: Hier kannst du deine eigenen Geschäftsbedingungen, Datenschutzerklärung und anderes rechtlich Relevante eintragen. Du kannst HTML-Tags nutzen
         title: Benutzerdefinierte Geschäftsbedingungen
       site_title: Name des Servers
+      spam_check_enabled:
+        desc_html: Mastodon kann automatisch Server stummschalten und automatisch Konten melden basierend auf Maßnahmen wie die Erkennung von Konten, die wiederholt unerwünschte Nachrichten senden. Es können hierbei mögliche Probleme auftreten.
+        title: Anti-Spam
       thumbnail:
         desc_html: Wird für die Vorschau via OpenGraph und API verwendet. 1200×630 px wird empfohlen
         title: Vorschaubild des Servers
@@ -456,12 +506,19 @@ de:
         desc_html: Auf der Einstiegsseite die öffentliche Zeitleiste anzeigen
         title: Zeitleisten-Vorschau
       title: Server-Einstellungen
+      trendable_by_default:
+        desc_html: Betroffene Hashtags, die bisher nicht gesperrt wurden
+        title: Hashtags ohne vorherige Überprüfung erlauben zu trenden
+      trends:
+        desc_html: Zuvor überprüfte Hashtags öffentlich anzeigen, die derzeit angesagt sind
+        title: Trendende Hashtags
     statuses:
       back_to_account: Zurück zum Konto
       batch:
         delete: Löschen
         nsfw_off: Als nicht heikel markieren
         nsfw_on: Als heikel markieren
+      deleted: Gelöscht
       failed_to_execute: Ausführen fehlgeschlagen
       media:
         title: Medien
@@ -469,21 +526,24 @@ de:
       no_status_selected: Keine Beiträge wurden geändert, weil keine ausgewählt wurden
       title: Beiträge des Kontos
       with_media: Mit Medien
-    subscriptions:
-      callback_url: Callback-URL
-      confirmed: Bestätigt
-      expires_in: Verfällt in
-      last_delivery: Letzte Zustellung
-      title: WebSub
-      topic: Thema
     tags:
-      accounts: Konten
-      hidden: Versteckt
-      hide: Vom Profilverzeichnis verstecken
+      accounts_today: Einzigartige Nutzungen heute
+      accounts_week: Einzigartige Nutzung dieser Woche
+      breakdown: Heruntergebrochene Statistiken der heutigen Nutzung nach Quelle
+      context: Kontext
+      directory: Im Verzeichnis
+      in_directory: "%{count} im Verzeichnis"
+      last_active: Zuletzt aktiv
+      most_popular: Am beliebtesten
+      most_recent: Neuste
       name: Hashtag
+      review: Prüfstatus
+      reviewed: Überprüft
       title: Hashtags
-      unhide: Zeige in Verzeichnis
-      visible: Sichtbar
+      trending_right_now: Aktuell in den Trends
+      unique_uses_today: "%{count} Beiträge heute"
+      unreviewed: Nicht überprüft
+      updated_msg: Hashtageinstellungen wurden erfolgreich aktualisiert
     title: Administration
     warning_presets:
       add_new: Neu hinzufügen
@@ -499,11 +559,21 @@ de:
       body: "%{reporter} hat %{target} gemeldet"
       body_remote: Jemand von %{domain} hat %{target} gemeldet
       subject: Neue Meldung auf %{instance} (#%{id})
+    new_trending_tag:
+      body: 'Der Hashtag #%{name} ist heute am trenden, aber wurde vorher noch nicht überprüft. Er wird nicht öffentlich angezeigt, es sei denn du erlaubst es oder speicherst das Formular ab und vergisst es.'
+      subject: Neuer Hashtag zur Überprüfung auf %{instance} verfügbar (#%{name})
+  aliases:
+    add_new: Alias erstellen
+    created_msg: Ein neuer Alias wurde erfolgreich erstellt. Du kannst nun den Wechsel vom alten Konto starten.
+    deleted_msg: Der Alias wurde erfolgreich entfernt. Aus diesem Konto zu diesem zu verschieben ist nicht mehr möglich.
+    hint_html: Wenn du von einem Konto zu einem anderem Konto wechseln möchtest, dann kannst du einen Alias erstellen, welcher benötigt wird bevor du deine Folgenden vom altem Account zu diesen migrierst. Die Aktion alleine ist <strong>harmlos und wi­der­ruf­lich</strong>. <strong>Die Kontenmigration wird vom altem Konto aus eingeleitet</strong>.
+    remove: Alle Aliase aufheben
   appearance:
     advanced_web_interface: Fortgeschrittene Benutzeroberfläche
     advanced_web_interface_hint: Wenn du mehr aus deiner Bildschirmbreite herausholen möchtest, erlaubt dir die fortgeschrittene Benutzeroberfläche viele unterschiedliche Spalten auf einmal zu sehen, wie z.B. deine Startseite, Benachrichtigungen, das gesamte bekannte Netz, deine Listen und beliebige Hashtags.
     animations_and_accessibility: Animationen und Barrierefreiheit
     confirmation_dialogs: Bestätigungsfenster
+    discovery: Entdecken
     sensitive_content: Heikle Inhalte
   application_mailer:
     notification_preferences: Ändere E-Mail-Einstellungen
@@ -524,9 +594,13 @@ de:
     apply_for_account: Eine Einladung anfragen
     change_password: Passwort
     checkbox_agreement_html: Ich akzeptiere die <a href="%{rules_path}" target="_blank">Server-Regeln</a> und die <a href="%{terms_path}" target="_blank">Nutzungsbedingungen</a>
-    confirm_email: E-Mail bestätigen
+    checkbox_agreement_without_rules_html: Ich stimme den <a href="%{terms_path}" target="_blank">Nutzungsbedingungen</a> zu
     delete_account: Konto löschen
     delete_account_html: Falls du dein Konto löschen willst, kannst du <a href="%{path}">hier damit fortfahren</a>. Du wirst um Bestätigung gebeten werden.
+    description:
+      prefix_invited_by_user: "@%{name} lädt dich ein, diesem Server von Mastodon beizutreten!"
+      prefix_sign_up: Melde dich heute bei Mastodon an!
+      suffix: Mit einem Konto kannst du Leuten folgen, Updates veröffentlichen und Nachrichten mit Benutzern von jedem Mastodon-Server austauschen und mehr!
     didnt_get_confirmation: Keine Bestätigungs-Mail erhalten?
     forgot_password: Passwort vergessen?
     invalid_reset_password_token: Das Token zum Zurücksetzen des Passworts ist ungültig oder abgelaufen. Bitte fordere ein neues an.
@@ -544,6 +618,16 @@ de:
     reset_password: Passwort zurücksetzen
     security: Sicherheit
     set_new_password: Neues Passwort setzen
+    setup:
+      email_below_hint_html: Wenn die unten stehende E-Mail-Adresse falsch ist, kannst du sie hier ändern und eine neue Bestätigungs-E-Mail erhalten.
+      email_settings_hint_html: Die Bestätigungs-E-Mail wurde an %{email} gesendet. Wenn diese E-Mail-Adresse nicht korrekt ist, kannst du sie in den Einstellungen ändern.
+      title: Konfiguration
+    status:
+      account_status: Kontostatus
+      confirming: Warte auf die Bestätigung der E-Mail.
+      functional: Dein Konto ist voll funktionsfähig.
+      pending: Deine Bewerbung wird von unseren Mitarbeitern noch überprüft. Dies kann einige Zeit dauern. Du erhältst eine E-Mail, wenn deine Bewerbung genehmigt wurde.
+      redirecting_to: Dein Konto ist inaktiv, da es derzeit zu %{acct} umgeleitet wird.
     trouble_logging_in: Schwierigkeiten beim Anmelden?
   authorize_follow:
     already_following: Du folgst diesem Konto bereits
@@ -556,6 +640,11 @@ de:
       return: Zeige das Profil
       web: In der Benutzeroberfläche öffnen
     title: "%{acct} folgen"
+  challenge:
+    confirm: Fortfahren
+    hint_html: "<strong>Hinweis:</strong> Wir werden dich für die nächste Stunde nicht erneut nach deinem Passwort fragen."
+    invalid_password: Ungültiges Passwort
+    prompt: Gib dein Passwort ein um fortzufahren
   datetime:
     distance_in_words:
       about_x_hours: "%{count}h"
@@ -571,26 +660,33 @@ de:
       x_months: "%{count}mo"
       x_seconds: "%{count}s"
   deletes:
-    bad_password_msg: Falsches Passwort
+    challenge_not_passed: Die eingegebenen Informationen waren nicht korrekt
     confirm_password: Gib dein derzeitiges Passwort ein, um deine Identität zu bestätigen
-    description_html: Hiermit wird <strong>dauerhaft und unwiederbringlich</strong> der Inhalt deines Kontos gelöscht und dein Konto deaktiviert. Dein Profilname wird reserviert, um künftige Imitationen zu verhindern.
+    confirm_username: Gib deinen Benutzernamen ein, um das Verfahren zu bestätigen
     proceed: Konto löschen
     success_msg: Dein Konto wurde erfolgreich gelöscht
-    warning_html: Wir können nur dafür garantieren, dass die Inhalte auf diesem einen Server gelöscht werden. Bei Inhalten, die weit verbreitet wurden, ist es wahrscheinlich, dass Spuren bleiben werden. Server, die offline sind oder keine Benachrichtigungen von deinem Konto mehr empfangen, werden ihre Datenbanken nicht bereinigen.
-    warning_title: Verfügbarkeit verstreuter Inhalte
+    warning:
+      before: 'Bevor du fortfährst, lese bitte diese Punkte sorgfältig durch:'
+      caches: Inhalte, die von anderen Servern zwischengespeichert wurden, können weiterhin bestehen
+      data_removal: Deine Beiträge und andere Daten werden dauerhaft entfernt
+      email_change_html: Du kannst <a href="%{path}">deine E-Mail-Adresse ändern</a>, ohne dein Konto zu löschen
+      email_contact_html: Wenn die Bestätigungs-E-Mail immer noch nicht ankam, kannst du eine E-Mail an <a href="mailto:%{email}">%{email}</a> senden, um weitere Hilfe zu erhalten
+      email_reconfirmation_html: Wenn du die Bestätigungs-E-Mail nicht erhalten hast, kannst du sie <a href="%{path}">erneut anfordern</a>
+      irreversible: Du kannst dein Konto nicht reaktivieren
+      more_details_html: Weitere Details findest du in der <a href="%{terms_path}">Datenschutzrichtlinie</a>.
+      username_available: Dein Benutzername wird wieder verfügbar
+      username_unavailable: Dein Benutzername bleibt nicht verfügbar
   directories:
     directory: Profilverzeichnis
-    enabled: Du bist gerade in dem Verzeichnis gelistet.
-    enabled_but_waiting: Du bist damit einverstanden im Verzeichnis aufgelistet zu werden, aber du hast noch nicht genug Folger_innen (%{min_followers}).
-    explanation: Entdecke Benutzer_innen basierend auf deren Interessen
+    explanation: Entdecke Benutzer basierend auf deren Interessen
     explore_mastodon: Entdecke %{title}
-    how_to_enable: Du hast dich gerade nicht dazu entschieden im Verzeichnis gelistet zu werden. Du kannst dich unten dafür eintragen. Benutze Hashtags in deiner Profilbeschreibung, um unter spezifischen Hashtags gelistet zu werden!
-    people:
-      one: "%{count} Person"
-      other: "%{count} Leute"
+  domain_validator:
+    invalid_domain: ist kein gültiger Domain-Name
   errors:
+    '400': Die Anfrage, die du gesendet hast, war ungültig oder fehlerhaft.
     '403': Dir fehlt die Befugnis, diese Seite sehen zu können.
     '404': Die Seite nach der du gesucht hast wurde nicht gefunden.
+    '406': Diese Seite ist im gewünschten Format nicht verfügbar.
     '410': Die Seite nach der du gesucht hast existiert hier nicht mehr.
     '422':
       content: Sicherheitsüberprüfung fehlgeschlagen. Blockierst du Cookies?
@@ -599,6 +695,7 @@ de:
     '500':
       content: Bitte verzeih, etwas ist bei uns schief gegangen.
       title: Diese Seite ist kaputt
+    '503': Die Seite konnte wegen eines temporären Serverfehlers nicht angezeigt werden.
     noscript_html: Bitte aktiviere JavaScript, um die Mastodon-Web-Anwendung zu verwenden. Alternativ kannst du auch eine der <a href="%{apps_path}">nativen Mastodon-Anwendungen</a> für deine Plattform probieren.
   existing_username_validator:
     not_found: kann lokalen Benutzer nicht mit diesem Nuternamen finden
@@ -622,6 +719,7 @@ de:
     add_new: Neu hinzufügen
     errors:
       limit: Du hast bereits die maximale Anzahl an empfohlenen Hashtags erreicht
+    hint_html: "<strong>Was sind empfohlene Hashtags?</strong> Sie werden in deinem öffentlichen Profil deutlich angezeigt und ermöglichen es den Menschen, deine öffentlichen Beiträge speziell unter diesen Hashtags zu durchsuchen. Sie sind ein großartiges Werkzeug, um kreative Werke oder langfristige Projekte zu verfolgen."
   filters:
     contexts:
       home: Startseite
@@ -642,10 +740,12 @@ de:
     developers: Entwickler
     more: Mehr…
     resources: Ressourcen
+    trending_now: In den Trends
   generic:
     all: Alle
     changes_saved_msg: Änderungen gespeichert!
     copy: Kopieren
+    no_batch_actions_available: Keine Massenaktionen auf dieser Seite verfügbar
     order_by: Sortieren nach
     save_changes: Änderungen speichern
     validation_errors:
@@ -717,9 +817,34 @@ de:
       too_many: Es können nicht mehr als 4 Dateien angehängt werden
   migrations:
     acct: benutzername@domain des neuen Kontos
-    currently_redirecting: 'Deine Profilweiterleitung wurde gesetzt auf:'
-    proceed: Speichern
-    updated_msg: Deine Konto-Migrationseinstellungen wurden erfolgreich aktualisiert!
+    cancel: Umleitung abbrechen
+    cancel_explanation: Das Abbrechen der Umleitung wird dein aktuelles Konto erneut aktivieren, aber keine Folgenden, die auf dieses Konto verschoben wurden, zurückholen.
+    cancelled_msg: Die Umleitung wurde erfolgreich abgebrochen.
+    errors:
+      already_moved: ist das gleiche Konto, zu dem du bereits umgezogen bist
+      missing_also_known_as: referenziert nicht zurück auf dieses Konto
+      move_to_self: darf nicht das aktuelles Konto sein
+      not_found: kann nicht gefunden werden
+      on_cooldown: Die Abklingzeit läuft gerade
+    followers_count: Folgende zur Zeit des Verschiebens
+    incoming_migrations: Ziehe von einem anderen Konto um
+    incoming_migrations_html: Um von einem anderen Konto zu diesem zu wechseln, musst du zuerst <a href="%{path}">einen Kontoalias</a> erstellen.
+    moved_msg: Dein Konto wird jetzt zu %{acct} weitergeleitet und deine Folgende werden verschoben.
+    not_redirecting: Dein Konto wird derzeit nicht auf ein anderes Konto weitergeleitet.
+    on_cooldown: Du hast dein Konto vor kurzem migriert. Diese Funktion wird in %{count} Tagen wieder verfügbar sein.
+    past_migrations: Vorherige Migrationen
+    proceed_with_move: Folgende verschieben
+    redirecting_to: Dein Konto wird zu %{acct} weitergeleitet.
+    set_redirect: Umleitung einrichten
+    warning:
+      backreference_required: Das neue Konto muss zuerst so konfiguriert werden, dass es auf das alte Konto referenziert
+      before: 'Bevor du fortfährst, lese bitte diese Hinweise sorgfältig durch:'
+      cooldown: Nach dem Migrieren wird es eine Abklingzeit geben, in der du das Konto nicht noch einmal migrieren kannst
+      disabled_account: Dein aktuelles Konto wird nachher nicht vollständig nutzbar sein. Du hast jedoch Zugriff auf den Datenexport sowie die Reaktivierung.
+      followers: Diese Aktion wird alle Folgende vom aktuellen Konto auf das neue Konto verschieben
+      only_redirect_html: Alternativ kannst du <a href="%{path}">nur eine Weiterleitung auf dein Profil</a> erstellen.
+      other_data: Keine anderen Daten werden automatisch verschoben
+      redirect: Das Profil deines aktuellen Kontos wird mit einer Weiterleitungsnachricht versehen und von Suchanfragen ausgeschlossen
   moderation:
     title: Moderation
   notification_mailer:
@@ -816,10 +941,6 @@ de:
     reply:
       proceed: Fortfahren zum Antworten
       prompt: 'Du möchtest auf diesen Beitrag antworten:'
-  remote_unfollow:
-    error: Fehler
-    title: Titel
-    unfollowed: Entfolgt
   scheduled_statuses:
     over_daily_limit: Du hast das Limit für geplante Beiträge, dass %{limit} beträgt, für heute erreicht
     over_total_limit: Du hast das Limit für geplante Beiträge, dass %{limit} beträgt, erreicht
@@ -868,6 +989,7 @@ de:
   settings:
     account: Konto
     account_settings: Konto & Sicherheit
+    aliases: Kontoaliase
     appearance: Aussehen
     authorized_apps: Autorisierte Anwendungen
     back: Zurück zu Mastodon
@@ -883,8 +1005,10 @@ de:
     notifications: Benachrichtigungen
     preferences: Einstellungen
     profile: Profil
-    relationships: Folger_innen und Gefolgte
+    relationships: Folgende und Gefolgte
     two_factor_authentication: Zwei-Faktor-Auth
+  spam_check:
+    spam_detected: Dies ist ein automatisierter Bericht. Es wurde Spam erkannt.
   statuses:
     attached:
       description: 'Angehängt: %{attached}'
@@ -908,6 +1032,9 @@ de:
       private: Du kannst nur öffentliche Beiträge anheften
       reblog: Du kannst keine geteilten Beiträge anheften
     poll:
+      total_people:
+        one: "%{count} Person"
+        other: "%{count} Personen"
       total_votes:
         one: "%{count} Stimme"
         other: "%{count} Stimmen"
@@ -926,15 +1053,17 @@ de:
     pinned: Angehefteter Beitrag
     reblogged: teilte
     sensitive_content: Heikle Inhalte
+  tags:
+    does_not_match_previous_name: entspricht nicht dem vorherigen Namen
   terms:
     body_html: |
       <h2>Datenschutzerklärung</h2>
       <h3 id="collect">Welche Informationen sammeln wir?</h3>
 
       <ul>
-      <li><em>Grundlegende Kontoinformationen</em>: Wenn du dich auf diesem Server registrierst, wirst du darum gebeten, einen Benutzer:innen-Namen, eine E-Mail-Adresse und ein Passwort einzugeben. Du kannst auch zusätzliche Profilinformationen wie etwa einen Anzeigenamen oder eine Biografie eingeben und ein Profilbild oder ein Headerbild hochladen. Der Benutzer:innen-Name, der Anzeigename, die Biografie, das Profilbild und das Headerbild werden immer öffentlich angezeigt.</li>
+      <li><em>Grundlegende Kontoinformationen</em>: Wenn du dich auf diesem Server registrierst, wirst du darum gebeten, einen Benutzernamen, eine E-Mail-Adresse und ein Passwort einzugeben. Du kannst auch zusätzliche Profilinformationen wie etwa einen Anzeigenamen oder eine Biografie eingeben und ein Profilbild oder ein Headerbild hochladen. Der Benutzername, der Anzeigename, die Biografie, das Profilbild und das Headerbild werden immer öffentlich angezeigt.</li>
       <li><em>Beiträge, Folge- und andere öffentliche Informationen</em>: Die Liste der Leute, denen du folgst, wird öffentlich gezeigt, das gleiche gilt für deine Folgenden (Follower). Sobald du eine Nachricht übermittelst, wird das Datum und die Uhrzeit gemeinsam mit der Information, welche Anwendung du dafür verwendet hast, gespeichert. Nachricht können Medienanhänge enthalten, etwa Bilder und Videos. Öffentliche und ungelistete Beiträge sind öffentlich verfügbar. Sobald du einen Beitrag auf deinem Profil anpinnst, sind dies auch öffentlich verfügbare Informationen. Deine Beiträge werden an deine Folgenden ausgeliefert, was in manchen Fällen bedeutet, dass sie an andere Server ausgeliefert werden und dort Kopien gespeichert werden. Sobald du Beiträge löschst, wird dies ebenso an deine Follower ausgeliefert. Die Handlungen des Teilens und Favorisieren eines anderen Beitrages ist immer öffentlich.</li>
-      <li><em>Direkte und "Nur Folgende"-Beiträge</em>: Alle Beiträge werden auf dem Server gespeichert und verarbeitet. "Nur Folgende"-Beiträge werden an deine Folgenden und an Benutzer:innen, die du in ihnen erwähnst, ausgeliefert, direkte Beiträge nur an in ihnen erwähnte Benutzer:innen. In manchen Fällen bedeutet dass, dass sie an andere Server ausgeliefert werden und dort Kopien gespeichert werden. Wir bemühen uns nach bestem Wissen und Gewissen, den Zugriff auf diese Beiträge auf nur autorisierte Personen einzuschränken, jedoch könnten andere Server dabei scheitern. Deswegen ist es wichtig, die Server, zu denen deine Folgenden gehören, zu überprüfen. Du kannst eine Option in den Einstellungen umschalten, um neue Folgenden manuell anzunehmen oder abzuweisen. <em>Bitte beachte, dass die Betreiber des Server und jedes empfangenden Servers solche Nachrichten anschauen könnten</em> und dass Empfänger von diesen eine Bildschirmkopie erstellen könnten, sie kopieren oder anderweitig weiterverteilen könnten. <em>Teile nicht irgendwelche gefährlichen Informationen über Mastodon.</em></li>
+      <li><em>Direkte und "Nur Folgende"-Beiträge</em>: Alle Beiträge werden auf dem Server gespeichert und verarbeitet. "Nur Folgende"-Beiträge werden an deine Folgenden und an Benutzer, die du in ihnen erwähnst, ausgeliefert, direkte Beiträge nur an in ihnen erwähnte Benutzer. In manchen Fällen bedeutet dass, dass sie an andere Server ausgeliefert werden und dort Kopien gespeichert werden. Wir bemühen uns nach bestem Wissen und Gewissen, den Zugriff auf diese Beiträge auf nur autorisierte Personen einzuschränken, jedoch könnten andere Server dabei scheitern. Deswegen ist es wichtig, die Server, zu denen deine Folgenden gehören, zu überprüfen. Du kannst eine Option in den Einstellungen umschalten, um neue Folgenden manuell anzunehmen oder abzuweisen. <em>Bitte beachte, dass die Betreiber des Server und jedes empfangenden Servers solche Nachrichten anschauen könnten</em> und dass Empfänger von diesen eine Bildschirmkopie erstellen könnten, sie kopieren oder anderweitig weiterverteilen könnten. <em>Teile nicht irgendwelche gefährlichen Informationen über Mastodon.</em></li>
       <li><em>Internet Protocol-Adressen (IP-Adressen) und andere Metadaten</em>: Sobald du dich anmeldest, erfassen wir sowohl die IP-Adresse, von der aus du dich anmeldest, als auch den Namen deine Browseranwendung. Alle angemeldeten Sitzungen (Sessions) sind für deine Überprüfung und Widerruf in den Einstellungen verfügbar. Die letzte verwendete IP-Adresse wird bis zu 12 Monate lang gespeichert. Wir könnten auch Serverprotokoll behalten, welche die IP-Adresse von jeder Anfrage an unseren Server enthalten.</li>
       </ul>
 
@@ -964,7 +1093,7 @@ de:
 
       <ul>
       <li>Serverprotokolle, die IP-Adressen von allen deinen Anfragen an diesen Server, falls solche Protokolle behalten werden, für nicht mehr als 90 Tage behalten.</li>
-      <li>registrierten Benutzer:innen zugeordnete IP-Adressen nicht länger als 12 Monate behalten.</li>
+      <li>registrierten Benutzer zugeordnete IP-Adressen nicht länger als 12 Monate behalten.</li>
       </ul>
 
       <p>Du kannst ein Archiv deines Inhalts anfordern und herunterladen, inkludierend deiner Beiträge, Medienanhänge, Profilbilder und Headerbilder.</p>
@@ -1045,7 +1174,9 @@ de:
         disable: Solange dein Konto eingefroren ist, sind deine Benutzerdaten intakt; aber du kannst nichts tun, bis dein Konto entsperrt wurde.
         silence: Solange dein Konto limitiert ist, können nur die Leute, die dir bereits folgen, deine Beiträge auf dem Server sehen und es könnte sein, dass du von verschiedenen öffentlichen Listungen ausgeschlossen wirst. Andererseits können andere dir manuell folgen.
         suspend: Dein Konto wurde gesperrt und alle deine Beiträge und hochgeladenen Medien wurden unwiderruflich vom Server und anderen Servern, bei denen du Folgende hattest, gelöscht.
+      get_in_touch: Du kannst auf diese E-Mail antworten, um mit dem Personal von %{instance} in Kontakt zu treten.
       review_server_policies: Serverrichtlinien ansehen
+      statuses: 'Besonders für:'
       subject:
         disable: Dein Konto %{acct} wurde eingefroren
         none: Warnung für %{acct}
diff --git a/config/locales/devise.ar.yml b/config/locales/devise.ar.yml
index 366bd81b94b38a5190bdaa32a7202138c02bf65a..4662cd030b7ad8659cedd95de15ba642b1eb748a 100644
--- a/config/locales/devise.ar.yml
+++ b/config/locales/devise.ar.yml
@@ -45,6 +45,12 @@ ar:
         extra: إن لم تكن صاحب هذا الطلب ، يُرجى عدم إعارة الاهتمام لهذه الرسالة. فكلِمَتُك السرية تبقى هي مِن غير أي تعديل إلّا و فقط إن قمت بالنقر على الرابط أعلاه قصد إنشاء كلمة سرية جديدة.
         subject: 'ماستدون: تعليمات استعادة كلمة المرور'
         title: إعادة تعيين كلمة السر
+      two_factor_disabled:
+        subject: 'ماستدون: نظام المصادقة بخطوتين مُعطّل'
+        title: إنّ 2FA معطّل
+      two_factor_enabled:
+        subject: 'ماستدون: تم تفعيل نظام المصادقة بخطوتين'
+        title: إنّ 2FA نشِط
       unlock_instructions:
         subject: 'ماستدون: تعليمات فك القفل'
     omniauth_callbacks:
diff --git a/config/locales/devise.bn.yml b/config/locales/devise.bn.yml
index 152c698290639f4688cdb5184962daafc705fc52..cb7179da6fccabd52906838ac6754646a5a0b665 100644
--- a/config/locales/devise.bn.yml
+++ b/config/locales/devise.bn.yml
@@ -1 +1,40 @@
+---
 bn:
+  devise:
+    confirmations:
+      confirmed: আপনার ইমেইলটি সঠিকভাবে নিশ্চিত করা হয়েছে।
+      send_instructions: আপনি একটি ইমেইল পাবেন যেটাতে কিভাবে আপনার ইমেইলটি নিশ্চিত করতে হবে সেটা পাঠানো হবে। যদি না পান, অনুগ্রহ করে আপনার স্প্যাম ফোল্ডারটি চেক করবেন।
+      send_paranoid_instructions: আমাদের ডাটাবেসে যদি আপনার ইমেইল থেকে থাকে, আপনার কাছে একটা ইমেইল পাঠানো হবে যেখানে কিভাবে আপনার ইমেইল নিশ্চিত করতে হবে লেখা থাকবে। যদি না পান, অনুগ্রহ করে আপনার স্প্যাম ফোল্ডারটি চেক করবেন।
+    failure:
+      already_authenticated: আপনি ইতিপূর্বে ভেতরে ঢুকেছেন (আবার লাগবে না)।
+      inactive: আনার নিবন্ধনটি এখনো চালু করা হয়নি।
+      invalid: ভুল %{authentication_keys} বা পাসওয়ার্ড ।
+      last_attempt: আপনার আর একবার চেষ্টা করার সুযোক আছে, তারপর আপনার নিবন্ধনে ঢোকার ক্ষেত্রে তালা দেওয়া হবে।
+      locked: নিবন্ধনে ঢোকার ক্ষেত্রে তালা দেওয়া হয়েছে।
+      not_found_in_database: ভুল %{authentication_keys} বা পাসওয়ার্ড।
+      pending: আপনার নিবন্ধনটি এখনো পর্যালোচনার জন্য অপেক্ষায় আছে।
+      timeout: আপনার সেশনটির সময় শেষ হয়ে গেছে। অনুগ্রহ করে আবার নিবন্ধনে ঢুকে চালাতে থাকেন।
+      unauthenticated: এটা ব্যবহার করতে আপনার আগে আপনার নিবন্ধনে ঢুকতে হবে অথবা নিবন্ধন তৈরি করতে হবে।
+      unconfirmed: এটা ব্যবহার করতে আপনার আগে আপনার ইমেইলটি নিশ্চিত করতে হবে।
+    mailer:
+      confirmation_instructions:
+        action: ইমেইলটি নিশ্চিত করুন
+        action_with_app: নিশ্চিত করুন এবং %{app} তে ফিরে যান
+        explanation: "%{host} তে এই ইমেইল ব্যবহার করে নিবন্ধন করতে হবে। আর একটা ক্লিক করলেই এটা চালু হয়ে যাবে। যদি আপনি এটা না পাঠিয়ে থাকেন, তাহলে অনুগ্রহ করে এই ইমেইলটি উপেক্ষা করুন।"
+      password_change:
+        extra: আপনি নিজে যদি পাসওয়ার্ডটি না বদলে থাকেন, খুব সম্ভব অন্যকেও আপনার  নিবন্ধনে প্রবেশ করে এটা করেছে। অনুগ্রহ করে যত দ্রুত সম্ভব আপনার পাসওয়ার্ডটি বদলান অথবা যদি  আপনি আপনার নিবন্ধনে আর না ঢুকতে পারেন, এই সার্ভারের পরিচালককে জানান।
+        subject: 'মাস্টাডন: পাসওয়ার্ড বদলানো হয়েছে'
+        title: পাসওয়ার্ড বদলানো হয়েছে
+      reconfirmation_instructions:
+        explanation: নতুন ইমেইলটি নিশ্চিত করুন।
+        extra: আপনি যদি এটা না চেয়ে থাকেন, এই ইমেইলটি উপেক্ষা করুন। উপরের লিংকটিতে না গেলে আপনার নিবন্ধনের সাথে যুক্ত ইমেইল বদলাবে না।
+        subject: 'মাস্টাডন: ইমেইল নিশ্চিত করুন %{instance} জন্য'
+        title: আপনার ইমেইলটি নিশ্চিত করুন
+      reset_password_instructions:
+        action: পাসওয়ার্ড বদলান
+        explanation: আপনি আপনার নিবন্ধনের জন্য নতুন পাসওয়ার্ড চেয়েছেন।
+        extra: আপনি যদি এটা না চেয়ে থাকেন, এই ইমেইলটি উপেক্ষা করুন। উপরের লিংকটিতে না গেলে আপনার পাসওয়ার্ড বদলাবে না।
+        subject: 'মাস্টাডন: পাসওয়ার্ড বদলানোর নির্দেশনা'
+        title: পাসওয়ার্ড বদলানো
+    registrations:
+      signed_up: স্বাগতম! আপনার নিবন্ধনটি সঠিকভাবে হয়েছে।
diff --git a/config/locales/devise.br.yml b/config/locales/devise.br.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3fe043754fbe9f79248bace8f921a47c815fdc10
--- /dev/null
+++ b/config/locales/devise.br.yml
@@ -0,0 +1,33 @@
+---
+br:
+  devise:
+    failure:
+      inactive: N'eo ket gweredekaet ho kont c'hoazh.
+      invalid: "%{authentication_keys} pe ger-tremen diwiriek."
+      last_attempt: Un esae a chom deoc'h a-raok ma vefe prennet ho kont.
+      locked: Prennet eo ho kont.
+      not_found_in_database: "%{authentication_keys} pe ger-tremen diwiriek."
+      pending: O vezañ asantet eo ho kont.
+    mailer:
+      confirmation_instructions:
+        action: Gwiriekaat ar chomlec'h postel
+        action_with_app: Kadarnaat ha distroiñ da %{app}
+        title: Gwiriekaat ar chomlec'h postel
+      email_changed:
+        subject: 'Mastodoñ : Postel kemmet'
+        title: Chomlec'h postel nevez
+      password_change:
+        explanation: Kemmet eo bet ger-tremen ho kont.
+        subject: 'Mastodoñ : Ger-tremen kemmet'
+        title: Ger-tremen kemmet
+      reconfirmation_instructions:
+        explanation: Kadarnait ar chomlec'h nevez evit cheñch ho postel.
+        subject: 'Mastodoñ : Kadarnait ar postel evit %{instance}'
+        title: Gwiriekaat ar chomlec'h postel
+      reset_password_instructions:
+        action: Cheñch ar ger-tremen
+        explanation: Goulennet ho peus ur ger-tremen nevez evit ho kont.
+    passwords:
+      updated_not_active: Kemmet eo bet ho ker-tremen ent reizh.
+    registrations:
+      signed_up: Donemat ! Kevreet oc'h.
diff --git a/config/locales/devise.ca.yml b/config/locales/devise.ca.yml
index 7f2df1f995f83c20faa2eb4060a15c294794c81a..eb176c7bde4a59c324ba0e6ce863b5434113c73f 100644
--- a/config/locales/devise.ca.yml
+++ b/config/locales/devise.ca.yml
@@ -48,6 +48,18 @@ ca:
         extra: Si no ho has sol·licitat, ignora aquest correu electrònic. La teva contrasenya no canviarà fins que accedeixis a l'enllaç de dalt i creis un de nou.
         subject: 'Mastodon: Instruccions per a reiniciar contrassenya'
         title: Contrasenya restablerta
+      two_factor_disabled:
+        explanation: L´autenticació de dos factors pel teu compte ha estat desactivat. L'inici de sessió és ara possible utilitzant només l'adreça de correu electrònic i la contrasenya.
+        subject: 'Mastodon: autenticació de dos factors desactivada'
+        title: 2FA desactivat
+      two_factor_enabled:
+        explanation: L'autenticació de dos factors ha estat habilitada pel teu compte. Un token generat pel emparellat TOTP app serà requerit per a iniciar sessió.
+        subject: 'Mastodon: autenticació de dos factors activada'
+        title: 2FA activat
+      two_factor_recovery_codes_changed:
+        explanation: Els codis de recuperació anteriors han estat invalidats i s'han generat uns de nous.
+        subject: 'Mastodon: codis de recuperació de Dos factors regenerats'
+        title: 2FA codis de recuperació canviats
       unlock_instructions:
         subject: 'Mastodon: Instruccions per a desblocar'
     omniauth_callbacks:
diff --git a/config/locales/devise.co.yml b/config/locales/devise.co.yml
index 16481737f1e8e478a615d131a46ab4ae9b8ab4ee..c9511d14d14f245490072202ce8d84b63d0626c8 100644
--- a/config/locales/devise.co.yml
+++ b/config/locales/devise.co.yml
@@ -46,6 +46,18 @@ co:
         extra: S’ellu ùn era micca voi, ignurate stu missaghju. A chjave d’accessu ùn cambiarà micca s’è voi ùn cliccate micca u ligame.
         subject: 'Mastodon: Cambià a chjave d’accessu'
         title: Cambià a chjave
+      two_factor_disabled:
+        explanation: L'autentificazione à dui fattori per u vostru contu hè stata disattivata. A cunnessione hè avà pussibule cù l'usu solu di u vostru e-mail è di a chjave d'accessu.
+        subject: 'Mastodon: Autentificazione à dui fattori disattivata'
+        title: A2F disattivata
+      two_factor_enabled:
+        explanation: L'autentificazione à dui fattori hè stata attivata per u vostru contu. Un codice, o fiscia, generata da l'applicazione TOTP assuciata sarà dumandata per cunnettavvi.
+        subject: 'Mastodon: Autentificazione à dui fattori attivata'
+        title: A2F attivata
+      two_factor_recovery_codes_changed:
+        explanation: I codici di ricuperazione pricidenti ùn sò più validi è un novu inseme di codici hè statu creatu.
+        subject: 'Mastodon: Rigenerazione di i codici à dui fattori di ricuperazione'
+        title: Cambiamentu di i codici di ricuperazione d'A2F
       unlock_instructions:
         subject: 'Mastodon: Riapre u contu'
     omniauth_callbacks:
diff --git a/config/locales/devise.cs.yml b/config/locales/devise.cs.yml
index 94c41ed986e8f6db36a43eddf23599397408f6df..25be731d03b72e67bc372188dfe32992821ad21c 100644
--- a/config/locales/devise.cs.yml
+++ b/config/locales/devise.cs.yml
@@ -8,7 +8,7 @@ cs:
     failure:
       already_authenticated: Již jste přihlášen/a.
       inactive: Váš účet ještě není aktivován.
-      invalid: Neplatný %{authentication_keys} nebo heslo.
+      invalid: Neplatné %{authentication_keys} nebo heslo.
       last_attempt: Máte ještě jeden pokus, než bude váš účet uzamčen.
       locked: Váš účet je uzamčen.
       not_found_in_database: Neplatné %{authentication_keys} nebo heslo.
@@ -21,7 +21,7 @@ cs:
         action: Potvrdit e-mailovou adresu
         action_with_app: Potvrdit a navrátit se do %{app}
         explanation: S touto e-mailovou adresou jste si vytvořil/a účet na %{host}. K jeho aktivaci vám zbývá jedno kliknutí. Pokud jste to nebyl/a vy, prosím ignorujte tento e-mail.
-        explanation_when_pending: S touto e-mailovou adresou jste si vyžádal/a pozvánku na %{host}. Jakmile svou e-mailovou adresu potvrdíte, posoudíme váš poadavek. Do té doby se nemůžete přihlásit. Pokud bude váš požadavek zamítnut, budou vaše data odstraněna, takže od vás nebude vyžadována žádná další akce. Pokud jste to nebyl/a vy, prosím ignorujte tento e-mail.
+        explanation_when_pending: S touto e-mailovou adresou jste si vyžádal/a pozvánku na %{host}. Jakmile svou e-mailovou adresu potvrdíte, posoudíme váš poadavek. Můžete se přihlásit, změnit si své detaily či smazat svůj účet, ale do schválení účtu nemáte přístup ke většině funkcí. Pokud bude váš požadavek zamítnut, budou vaše data odstraněna, takže od vás nebude vyžadována žádná další akce. Pokud jste to nebyl/a vy, prosím ignorujte tento e-mail.
         extra_html: Prosím podívejte se také na <a href="%{terms_path}">pravidla tohoto serveru</a> a <a href="%{policy_path}">naše podmínky používání</a>.
         subject: 'Mastodon: Potvrzovací instrukce pro %{instance}'
         title: Potvrďte e-mailovou adresu
@@ -46,6 +46,18 @@ cs:
         extra: Pokud jste tohle nevyžádal/a, prosím ignorujte tento e-mail. Vaše heslo nebude změněno, dokud nepřejdete na výše uvedenou adresu a nevytvoříte si nové.
         subject: 'Mastodon: Instrukce pro obnovení hesla'
         title: Obnovení hesla
+      two_factor_disabled:
+        explanation: Dvoufázové ověřování bylo pr váš účet zakázáno. Přihlašování je nyní možné pouze pomocí e-mailové adresy a hesla.
+        subject: 'Mastodon: Dvoufázové ověřování zakázáno'
+        title: 2FA zakázáno
+      two_factor_enabled:
+        explanation: Dvoufázové ověřování bylo pr váš účet povoleno. Pro přihlášení bude vyžadován token vygenerovaný spárovanou TOTP aplikací.
+        subject: 'Mastodon: Dvoufázové ověřování povoleno'
+        title: 2FA povoleno
+      two_factor_recovery_codes_changed:
+        explanation: Předchozí záložní kódy byly zrušeny a byly vygenerovány nové.
+        subject: 'Mastodon: Dvoufázové záložní kódy znovu vygenerovány'
+        title: Záložní kódy pro 2FA změněny
       unlock_instructions:
         subject: 'Mastodon: Instrukce pro odemčení účtu'
     omniauth_callbacks:
diff --git a/config/locales/devise.cy.yml b/config/locales/devise.cy.yml
index 727c71464494442060156c825d2eae40719d77b0..e5366f8cdf09314f19630cd6481ff5e1cb7a0cab 100644
--- a/config/locales/devise.cy.yml
+++ b/config/locales/devise.cy.yml
@@ -46,6 +46,18 @@ cy:
         extra: Os na wnaethoch gais am hyn, anwybyddwch yr e-bost hwn os gwelwch yn dda. Ni fydd eich cyfrinair yn newid nes i chi fynd at y ddolen uchod a chreu un newydd.
         subject: 'Mastodon: Ailosod cyfarwyddiadau cyfrinair'
         title: Ailosod cyfrinair
+      two_factor_disabled:
+        explanation: Mae dilysu dau ffactor ar gyfer eich cyfrif wedi'i anablu. Mae mewngofnodi bellach yn bosibl gan ddefnyddio cyfeiriad e-bost a chyfrinair yn unig.
+        subject: 'Mastodon: Dilysu dau ffactor yn anabl'
+        title: Dilysu dau ffactor yn anabl
+      two_factor_enabled:
+        explanation: Mae dilysu dau ffactor wedi'i alluogi ar gyfer eich cyfrif. Bydd angen tocyn a gynhyrchir gan yr ap TOTP pâr i fewngofnodi.
+        subject: 'Mastodon: mae dilysu dau ffactor wedi''i alluogi'
+        title: Mae dilysu dau ffactor wedi'i alluogi
+      two_factor_recovery_codes_changed:
+        explanation: Mae'r codau adfer blaenorol wedi'u hannilysu a chynhyrchwyd rhai newydd.
+        subject: 'Mastodon: Mae codau adfer dau ffactor wedi''u hadfywio'
+        title: Newidiodd codau adfer 2FA
       unlock_instructions:
         subject: 'Mastodon: Cyfarwyddiadau datgloi'
     omniauth_callbacks:
diff --git a/config/locales/devise.da.yml b/config/locales/devise.da.yml
index 4816d6456b7f63c112e24237a4067487fce4e324..1b05d77c1d6e847c375f8558b3c9e0102eab9ac2 100644
--- a/config/locales/devise.da.yml
+++ b/config/locales/devise.da.yml
@@ -2,7 +2,7 @@
 da:
   devise:
     confirmations:
-      confirmed: Din email adresse er blevet succesfuldt bekræftet.
+      confirmed: Din e-mail er nu bekræftet.
       send_instructions: Du vil modtage en mail med instrukser for hvordan du bekræfter din email adresse om få minutter. Tjek venligst din spam mappe hvis du ikke har modtaget denne email.
       send_paranoid_instructions: Hvis din email adresse allerede findes i vores database, vil du modtage en email med instrukser for hvordan du bekræfter din email adresse om få minutter. Tjek gerne din spam mappe hvis du ikke modtager denne email.
     failure:
@@ -12,6 +12,7 @@ da:
       last_attempt: Du har et forsøg tilbage før din konto låses.
       locked: Din konto er låst.
       not_found_in_database: Ugyldig %{authentication_keys} eller ugyldigt kodeord.
+      pending: Din konto er stadig under bedømmelse.
       timeout: Din session er udløbet. Log venligst ind igen for at fortsætte.
       unauthenticated: Du er nødt til at logge ind eller oprette dig for at fortsætte.
       unconfirmed: Du er nødt til at bekræfte din email adresse for at fortsætte.
@@ -20,6 +21,7 @@ da:
         action: Bekræft email adresse
         action_with_app: Bekræft og vend tilbage til %{app}
         explanation: Du har oprettet en konto på %{host} med denne email adresse. Du er et klik fra at aktivere din konto. Hvis du ikke har oprettet dig, ignorer venligst denne email.
+        explanation_when_pending: Du har ansøgt om en invitation til %{host} med denne mailadresse. Når du bekræfter din mailadresse vil vi bedømme din ansøgning. Indtil da kan du ikke logge ind. Din data slettes hvis din ansøgning bliver afvist så du behøver ikke foretage yderligere handlinger. Hvis dette ikke var dig kan du ignorere denne mail.
         extra_html: Tjek også <a href="%{terms_path}">reglerne for serveren</a> og <a href="%{policy_path}">vores betingelser</a>.
         subject: 'Mastodon: Bekræftelses instrukser for %{instance}'
         title: Bekræft email adresse
@@ -60,6 +62,7 @@ da:
       signed_up: Velkommen! Du har nu tilmeldt dig.
       signed_up_but_inactive: Du har nu oprettet dig. Vi kunne dog ikke logge dig ind da din konto endnu ikke er aktiveret.
       signed_up_but_locked: Du har nu oprettet dig. Vi kunne dog ikke logge dig ind da din konto er låst.
+      signed_up_but_pending: En besked med et bekræftelseslink er blevet sendt til din mailadresse. Vi vil behandle din ansøgning når du har trykket på dette link. Du får en meddelelse hvis din ansøgning bliver accepteret.
       signed_up_but_unconfirmed: En besked med et bekræftelses link er nu blevet sendt til din email adresse. Følg linket for at aktivere din konti. Tjek din spam mappe hvis du ikke har modtaget denne email.
       update_needs_confirmation: Du har succesfuldt opdateret din konto, men vi er nødt til at bekræfte din email adresse. Tjek venligst din email og følg bekræftelses linket for at bekræfte din nye email adresse. Tjek venligst din spam mappe hvis du ikke har modtaget denne email.
       updated: Din konto er nu blevet opdateret.
diff --git a/config/locales/devise.de.yml b/config/locales/devise.de.yml
index bd573dc3e9a50ea756d89036f2bb793451283cef..372090515f2152a75083fc762cd9326cf65d2351 100644
--- a/config/locales/devise.de.yml
+++ b/config/locales/devise.de.yml
@@ -46,6 +46,18 @@ de:
         extra: Wenn du diese Anfrage nicht gestellt hast, solltest du diese E-Mail ignorieren. Dein Passwort wird sich nicht ändern solange du den obigen Link anklickst und ein neues erstellst.
         subject: 'Mastodon: Passwort zurücksetzen'
         title: Passwort zurücksetzen
+      two_factor_disabled:
+        explanation: Zwei-Faktor-Authentifizierung für dein Konto wurde deaktiviert. Login ist jetzt nur mit E-Mail-Adresse und Passwort möglich.
+        subject: 'Mastodon: Zwei‐Faktor‐Authentifizierung deaktiviert'
+        title: 2FA deaktiviert
+      two_factor_enabled:
+        explanation: Zwei-Faktor-Authentifizierung wurde für dein Konto aktiviert. Ein Token, der von der gepaarten TOTP-App generiert wird, wird für den Login benötigt.
+        subject: 'Mastodon: Zwei‐Faktor‐Authentifizierung aktiviert'
+        title: 2FA aktiviert
+      two_factor_recovery_codes_changed:
+        explanation: Die vorherigen Wiederherstellungscodes wurden ungültig gemacht und es wurden neue generiert.
+        subject: 'Mastodon: Zwei-Faktor-Wiederherstellungscodes neu generiert'
+        title: 2FA Wiederherstellungscodes geändert
       unlock_instructions:
         subject: 'Mastodon: Konto entsperren'
     omniauth_callbacks:
diff --git a/config/locales/devise.el.yml b/config/locales/devise.el.yml
index 0b5c686368f7a17b7050a95a0e0cbd70ae3fefff..5dc7658c20f516d686cf03d0ff55acb08d0733b0 100644
--- a/config/locales/devise.el.yml
+++ b/config/locales/devise.el.yml
@@ -46,6 +46,18 @@ el:
         extra: Αν δεν ζήτησες εσύ αυτή την αλλαγή, παρακαλούμε αγνόησε αυτό το email. Το συνθηματικό σου δεν θα αλλάξει μέχρι να επισκεφτείς τον παραπάνω σύνδεσμο και να δημιουργήσεις ένα καινούριο.
         subject: 'Mastodon: Οδηγίες επαναφοράς συνθηματικού'
         title: Επαναφορά συνθηματικού
+      two_factor_disabled:
+        explanation: Ο έλεγχος ταυτότητας δυο παραγόντων (2FA) έχει απενεργοποιηθεί για το λογαριασμό σου. Η σύνδεση γίνεται απλά με το email και το συνθηματικό.
+        subject: 'Mastodon: Απενεργοποιήθηκε ο έλεγχος ταυτότητας δύο παραγόντων'
+        title: Απενεργοποιημένο 2FA
+      two_factor_enabled:
+        explanation: Έχει ενεργοποιηθεί η πιστοποίηση 2 παραγόντων για το λογαριασμό σου (2FA). Για να συνδεθείς θα απαιτηθεί πιστοποιημένο τεκμήριο από κάποια ζευγαρωμένη εφαρμογή.
+        subject: 'Mastodon: Ενεργοποιήθηκε η πιστοποίηση 2 παραγόντων (2FA)'
+        title: Επαλήθευση δύο βημάτων ενεργή
+      two_factor_recovery_codes_changed:
+        explanation: Οι προηγούμενοι κωδικοί ανάκτησης ακυρώθηκαν και δημιουργήθηκαν νέοι.
+        subject: 'Mastodon: Δημιουργήθηκαν νέοι κωδικοί ανάκτησης'
+        title: Οι κωδικοί ανάκτησης 2FA άλλαξαν
       unlock_instructions:
         subject: 'Mastodon: Οδηγίες ξεκλειδώματος'
     omniauth_callbacks:
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index 2930733c00cd1d076608e6cadef5c9c976d1b5bf..726d2426a77804095201b1ed845083c8d2d1ccec 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -21,7 +21,7 @@ en:
         action: Verify email address
         action_with_app: Confirm and return to %{app}
         explanation: You have created an account on %{host} with this email address. You are one click away from activating it. If this wasn't you, please ignore this email.
-        explanation_when_pending: You applied for an invite to %{host} with this email address. Once you confirm your e-mail address, we will review your application. You can't login until then. If your application is rejected, your data will be removed, so no further action will be required from you. If this wasn't you, please ignore this email.
+        explanation_when_pending: You applied for an invite to %{host} with this email address. Once you confirm your e-mail address, we will review your application. You can login to change your details or delete your account, but you cannot access most of the functions until your account is approved. If your application is rejected, your data will be removed, so no further action will be required from you. If this wasn't you, please ignore this email.
         extra_html: Please also check out <a href="%{terms_path}">the rules of the server</a> and <a href="%{policy_path}">our terms of service</a>.
         subject: 'Mastodon: Confirmation instructions for %{instance}'
         title: Verify email address
@@ -46,6 +46,18 @@ en:
         extra: If you didn't request this, please ignore this email. Your password won't change until you access the link above and create a new one.
         subject: 'Mastodon: Reset password instructions'
         title: Password reset
+      two_factor_disabled:
+        explanation: Two-factor authentication for your account has been disabled. Login is now possible using only e-mail address and password.
+        subject: 'Mastodon: Two-factor authentication disabled'
+        title: 2FA disabled
+      two_factor_enabled:
+        explanation: Two-factor authentication has been enabled for your account. A token generated by the paired TOTP app will be required for login.
+        subject: 'Mastodon: Two-factor authentication enabled'
+        title: 2FA enabled
+      two_factor_recovery_codes_changed:
+        explanation: The previous recovery codes have been invalidated and new ones generated.
+        subject: 'Mastodon: Two-factor recovery codes re-generated'
+        title: 2FA recovery codes changed
       unlock_instructions:
         subject: 'Mastodon: Unlock instructions'
     omniauth_callbacks:
diff --git a/config/locales/devise.eo.yml b/config/locales/devise.eo.yml
index d7b7b2d6c67cbd1e2c29efb13ec8d3e01cbe3ad8..7c67ac8facc2ee3c2f2b04919970c65fd57b8c22 100644
--- a/config/locales/devise.eo.yml
+++ b/config/locales/devise.eo.yml
@@ -46,6 +46,10 @@ eo:
         extra: Se vi ne petis ĉi tion, bonvolu ignori ĉi tiun retmesaĝon. Via pasvorto ne ŝanĝiĝos se vi ne aliras la supran ligilon kaj kreas novan.
         subject: 'Mastodon: Instrukcioj por ŝanĝi pasvorton'
         title: Pasvorto restarigita
+      two_factor_disabled:
+        title: la du-etapa aŭtentigo estas malŝaltita
+      two_factor_enabled:
+        title: la du-etapa aŭtentigo estas ŝaltita
       unlock_instructions:
         subject: 'Mastodon: Instrukcioj por malŝlosi'
     omniauth_callbacks:
diff --git a/config/locales/devise.es-AR.yml b/config/locales/devise.es-AR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bb229e8f5d10d39e5a3825c0f936b1e23ac3d01a
--- /dev/null
+++ b/config/locales/devise.es-AR.yml
@@ -0,0 +1,98 @@
+---
+es-AR:
+  devise:
+    confirmations:
+      confirmed: Se confirmó exitosamente tu dirección de correo electrónico.
+      send_instructions: En unos minutos, vas a recibir un correo electrónico con instrucciones sobre cómo confirmar tu dirección de correo. Si pasa el tiempo y no recibiste ningún mensaje, por favor, revisá tu carpeta de correo basura / no deseado / spam.
+      send_paranoid_instructions: Si tu dirección de correo electrónico existe en nuestra base de datos, en unos minutos, vas a recibir un correo electrónico con instrucciones sobre cómo confirmar tu dirección de correo. Si pasa el tiempo y no recibiste ningún mensaje, por favor, revisá tu carpeta de correo basura / no deseado / spam.
+    failure:
+      already_authenticated: Ya iniciaste sesión.
+      inactive: Tu cuenta todavía no está activada.
+      invalid: "%{authentication_keys} o contraseña no válidas."
+      last_attempt: Tenés un intento más antes de que se bloquee tu cuenta.
+      locked: Se bloqueó tu cuenta.
+      not_found_in_database: "%{authentication_keys} o contraseña no válidas."
+      pending: Tu cuenta todavía está bajo revisión.
+      timeout: Venció tu sesión. Por favor, volvé a iniciar sesión para continuar.
+      unauthenticated: Necesitás iniciar sesión o registrarte antes de continuar.
+      unconfirmed: Tenés que confirmar tu dirección de correo electrónico antes de continuar.
+    mailer:
+      confirmation_instructions:
+        action: Verificar dirección de correo electrónico
+        action_with_app: Confirmar y volver a %{app}
+        explanation: Te creaste una cuenta en %{host} con esta dirección de correo electrónico. Estás a un clic de activarla. Si vos no te creaste ninguna cuenta acá, por favor, simplemente ignorá este mensaje.
+        explanation_when_pending: Pediste una invitación para %{host} con esta dirección de correo electrónico. Una vez que confirmés esta dirección de correo, revisaremos tu pedido. Podés iniciar sesión para cambiar tus detalles o eliminar tu cuenta, pero no vas a poder acceder a la mayoría de las funciones hasta que no se apruebe tu cuenta. Si tu pedido es rechazado, se eliminarán tus datos, por lo que no vas a necesitar hacer nada en especial. Si vos no pediste ninguna cuenta acá, por favor, simplemente ignorá este mensaje.
+        extra_html: Por favor, también leé <a href="%{terms_path}">las reglas del servidor</a> y <a href="%{policy_path}">nuestros términos del servicio</a>.
+        subject: 'Mastodon: instrucciones de confirmación para %{instance}'
+        title: Verificar dirección de correo electrónico
+      email_changed:
+        explanation: 'La dirección de correo electrónico de tu cuenta está siendo cambiada a:'
+        extra: Si no cambiaste tu correo electrónico, es probable que alguien más haya obtenido acceso a tu cuenta. Por favor, cambiá tu contraseña inmediatamente o contactá con el administrador del servidor si no podés ingresar a tu cuenta.
+        subject: 'Mastodon: correo electrónico cambiado'
+        title: Nueva dirección de correo electrónico
+      password_change:
+        explanation: Se cambió la contraseña de tu cuenta.
+        extra: Si no cambiaste tu contraseña, es probable que alguien más haya obtenido acceso a tu cuenta. Por favor, cambiá tu contraseña inmediatamente o contactá con el administrador del servidor si no podés ingresar a tu cuenta.
+        subject: 'Mastodon: contraseña cambiada'
+        title: Contraseña cambiada
+      reconfirmation_instructions:
+        explanation: Confirmá la nueva dirección para cambiar tu correo electrónico.
+        extra: Si no pediste este cambio, por favor, ignorá este mensaje. No se cambiará la dirección de correo electrónico de tu cuenta de Mastodon hasta que no accedas al enlace de arriba.
+        subject: 'Mastodon: confirmar correo electrónico para %{instance}'
+        title: Verifique dirección de correo electrónico
+      reset_password_instructions:
+        action: Cambiar contraseña
+        explanation: Pediste una nueva contraseña para tu cuenta.
+        extra: Si no pediste este cambio, por favor, ignorá este mensaje. No se cambiará la contraseña de tu cuenta de Mastodon hasta que no accedas al enlace de arriba y crees una nueva.
+        subject: 'Mastodon: instrucciones para cambiar la contraseña'
+        title: Cambiar contraseña
+      two_factor_disabled:
+        explanation: La autenticación de dos factores para tu cuenta está deshabilitada. Ahora el inicio de sesión sólo es posible usando la dirección de correo electrónico y la contraseña.
+        subject: 'Mastodon: autenticación de dos factores, deshabilitada'
+        title: 2FA deshabilitada
+      two_factor_enabled:
+        explanation: La autenticación de dos factores para tu cuenta está habilitada. Se requiere una clave generada por la aplicación TOTP asociada para iniciar sesión.
+        subject: 'Mastodon: autenticación de dos factores, habilitada'
+        title: 2FA habilitada
+      two_factor_recovery_codes_changed:
+        explanation: Los códigos anteriores de recuperación fueron invalidados y se generaron unos nuevos.
+        subject: 'Mastodon: códigos de recuperación de dos factores, regenerados'
+        title: Códigos de recuperación 2FA cambiados
+      unlock_instructions:
+        subject: 'Mastodon: instrucciones de desbloqueo'
+    omniauth_callbacks:
+      failure: 'No se te pudo autenticar desde %{kind} debido a esto: "%{reason}".'
+      success: Se autenticó exitosamente para la cuenta %{kind}.
+    passwords:
+      no_token: No podés acceder a esta página sin venir desde un correo electrónico destinado al cambio de contraseña. Si venís desde dicho mensaje, por favor, asegurate que usaste toda la dirección web ofrecida.
+      send_instructions: Si tu dirección de correo electrónico existe en nuestra base de datos, en unos minutos, vas a recibir un correo electrónico con un enlace para cambiar tu contraseña. Si pasa el tiempo y no recibiste ningún mensaje, por favor, revisá tu carpeta de correo basura / no deseado / spam.
+      send_paranoid_instructions: Si tu dirección de correo electrónico existe en nuestra base de datos, en unos minutos, vas a recibir un correo electrónico con un enlace para cambiar tu contraseña. Si pasa el tiempo y no recibiste ningún mensaje, por favor, revisá tu carpeta de correo basura / no deseado / spam.
+      updated: Se cambió existosamente tu contraseña. Ya iniciaste sesión.
+      updated_not_active: Se cambió exitosamente tu contraseña.
+    registrations:
+      destroyed: "¡Chauchas! Se canceló exitosamente tu cuenta. Esperamos verte pronto de nuevo."
+      signed_up: "¡Bienvenido! Te registraste exitosamente."
+      signed_up_but_inactive: Te registraste exitosamente. Sin embargo, no podés iniciar sesión porque tu cuenta todavía no está activada.
+      signed_up_but_locked: Te registraste exitosamente. Sin embargo, no podés iniciar sesión porque tu cuenta está bloqueada.
+      signed_up_but_pending: Se envió un correo electrónico a tu dirección de correo con un enlace de confirmación. Después que hagás clic en ese enlace, revisaremos tu pedido. Si sos aprobado, serás notificado.
+      signed_up_but_unconfirmed: Se envió un correo electrónico a tu dirección de correo con un enlace de confirmación. Por favor, seguí ese enlace para activar tu cuenta. Si pasa el tiempo y no recibiste ningún mensaje, por favor, revisá tu carpeta de correo basura / no deseado / spam.
+      update_needs_confirmation: Actualizaste tu cuenta exitosamente. Sin embargo, necesitamos verificar tu nueva dirección de correo electrónico. Por favor, revisá tu correo electrónico y seguí el enlace de confirmación. Si pasa el tiempo y no recibiste ningún mensaje, por favor, revisá tu carpeta de correo basura / no deseado / spam.
+      updated: Se actualizó exitosamente tu cuenta.
+    sessions:
+      already_signed_out: Cerraste sesión exitosamente.
+      signed_in: Iniciaste sesión exitosamente.
+      signed_out: Cerraste sesión exitosamente.
+    unlocks:
+      send_instructions: En unos minutos, vas a recibir un correo electrónico con instrucciones sobre cómo desbloquear tu cuenta. Si pasa el tiempo y no recibiste ningún mensaje, por favor, revisá tu carpeta de correo basura / no deseado / spam.
+      send_paranoid_instructions: Si tu cuenta existe en nuestra base de datos, en unos minutos vas a recibir un correo electrónico con instrucciones sobre cómo desbloquear tu cuenta. Si pasa el tiempo y no recibiste ningún mensaje, por favor, revisá tu carpeta de correo basura / no deseado / spam.
+      unlocked: Se desbloqueó tu cuenta exitosamente. Por favor, iniciá sesión para continuar.
+  errors:
+    messages:
+      already_confirmed: ya se confirmó; por favor, intentá iniciar sesión
+      confirmation_period_expired: necesita confirmarse dentro de %{period}; por favor, solicitá una nueva
+      expired: venció; por favor, solicitá una nueva
+      not_found: no se encontró
+      not_locked: no se bloqueó
+      not_saved:
+        one: '1 error prohibió que este %{resource} se guarde:'
+        other: "%{count} errores prohibieron que este %{resource} se guarde:"
diff --git a/config/locales/devise.es.yml b/config/locales/devise.es.yml
index 8210415f21ba4e9f9e9c94c4e857848b511d287b..80d4380923339e75cf59b583431585a61f02e8f8 100644
--- a/config/locales/devise.es.yml
+++ b/config/locales/devise.es.yml
@@ -46,6 +46,18 @@ es:
         extra: Si no solicitaste esto, por favor ignora este correo. Tu contraseña no cambiará hasta que tu accedas al vinculo arriba y crees una nueva.
         subject: 'Mastodon: Instrucciones para reiniciar contraseña'
         title: Reiniciar contraseña
+      two_factor_disabled:
+        explanation: La autenticación de dos factores para tu cuenta ha sido deshabilitada. Ahora puedes conectarte solamente usando la dirección de correo electrónico y la contraseña.
+        subject: 'Mastodon: La autenticación de dos factores está deshabilitada'
+        title: 2FA desactivada
+      two_factor_enabled:
+        explanation: La autenticación de dos factores para tu cuenta ha sido habilitada. Se requiere un token generado por la aplicación TOTP emparejada para ingresar.
+        subject: 'Mastodon: La autenticación de dos factores está habilitada'
+        title: 2FA activada
+      two_factor_recovery_codes_changed:
+        explanation: Los códigos de recuperación previos han sido invalidados y se generaron códigos nuevos.
+        subject: 'Mastodon: Los códigos de recuperación de dos factores fueron regenerados'
+        title: Códigos de recuperación 2FA cambiados
       unlock_instructions:
         subject: 'Mastodon: Instrucciones para desbloquear'
     omniauth_callbacks:
diff --git a/config/locales/devise.et.yml b/config/locales/devise.et.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3c1b75f6cd75b80b978bc2f07f113dedee2ee4f6
--- /dev/null
+++ b/config/locales/devise.et.yml
@@ -0,0 +1,86 @@
+---
+et:
+  devise:
+    confirmations:
+      confirmed: Sinu e-postiaadress on edukalt kinnitatud.
+      send_instructions: Te saate paari minuti pärast e-kirja juhistega, kuidas oma e-posti aadressit kinnitada. Palun kontrollige oma rämpsposti kausta juhul, kui Te ei saanud seda e-kirja.
+      send_paranoid_instructions: Kui Teie e-postiaadress eksisteerib meie andmebaasis, saate paari minuti pärast e-kirja juhistega, kuidas oma e-posti aadressit kinnitada. Palun kontrollige oma rämpsposti kausta juhul, kui Te ei saanud seda e-kirja.
+    failure:
+      already_authenticated: Te olete juba sisse loginud.
+      inactive: Teie konto pole veel aktiveeritud.
+      invalid: Valed %{authentication_keys} või parool.
+      last_attempt: Teil on veel üks katse kuni teie konto on lukustatud.
+      locked: Sinu konto on lukustatud.
+      not_found_in_database: Valed %{authentication_keys} või parool.
+      pending: Teie konto on siiani läbivaatlusel.
+      timeout: Teie sessioon on aegunud. Jätkamiseks palun sisenege uuesti.
+      unauthenticated: Te peate sisenema või looma konto enne jätkamist.
+      unconfirmed: Te peate kinnitama oma e-postiaadressi enne jätkamist.
+    mailer:
+      confirmation_instructions:
+        action: Kinnita e-postiaadress
+        action_with_app: Kinnita ja naase %{app}
+        explanation: Te olete loonud konto %{host}'il selle e-postiaadressiga. Te olete ühe kliki kaugusel selle aktiveerimisest. Kui see polnud Teie, palun eirake seda kirja.
+        explanation_when_pending: Te esitasite soovi liituda %{host}-iga selle e-postiaadressiga. Siis, kui Te kinnitate oma e-posti aadressi, vaatame me Teie soovi üle. Te ei saa siseneda kuni selleni. Kui teie soov on tagasi lükatud, Teie andmed kustutatakse ning Te ei pea rohkem midagi tegema. Kui see ei olnud Teie, palun eirake seda kirja.
+        extra_html: Palun tutvu <a href="%{terms_path}">meie serveri reeglitega</a> ning <a href="%{policy_path}">meie kasutustingimustega</a>.
+        subject: 'Mastodon: %{instance} Kinnitamisjuhised'
+        title: Kinnita e-postiaadress
+      email_changed:
+        explanation: 'Teie konto e-postiaadress muudetakse:'
+        extra: Kui Te ei muutnud oma e-posti, on tõenäoline, et kellelgi on ligipääs Teie kontole. Palun muutke oma salasõna koheselt või võtke ühendust oma serveri administraatoriga, kui olete oma kontost välja lukustatud.
+        subject: 'Mastodon: E-post muudetud'
+        title: Uus e-postiaadress
+      password_change:
+        explanation: Teie konto parool on muudetud.
+        extra: Kui Te ei muutnud oma salasõna, on tõenäoline, et kellelgi on ligipääs Teie kontole. Palun muutke oma salasõna koheselt või võtke ühendust oma serveri administraatoriga, kui olete oma kontost välja lukustatud.
+        subject: 'Mastodon: Salasõna muudetud'
+        title: Salasõna muudetud
+      reconfirmation_instructions:
+        explanation: Kinnita oma uus aadress, et muuta oma e-posti.
+        extra: Kui see muudatus pole Teie poolt alustatud, palun eirake seda kirja. E-postiaadress sellele Mastodoni kontole ei muutu, kuni Te vajutate üleval asuvale lingile.
+        subject: 'Mastodon: Kinnitake e-post - %{instance}'
+        title: Kinnita e-postiaadress
+      reset_password_instructions:
+        action: Muuda salasõna
+        explanation: Te taotlesite oma kontole uut salasõna.
+        extra: Kui Te ei soovinud seda, palun eirake seda kirja. Teie salasõna ei muutu, kuni Te vajutate üleval olevale lingile ning loote uue.
+        subject: 'Mastodon: Salasõna lähtestamisjuhendid'
+        title: Salasõna lähtestamine
+      unlock_instructions:
+        subject: 'Mastodon: Lahti lukustamis juhendid'
+    omniauth_callbacks:
+      failure: Ei saanud Teid tuvastada %{kind} kaudu, kuna "%{reason}".
+      success: Tuvastamine %{kind} konto järgi õnnestus.
+    passwords:
+      no_token: Te ei saa sellele leheküljele ligi ilma tulemata salasõna lähtestamis e-kirjast. Kui Te tulete salasõna lähtestamis e-kirjast, palun olge kindel, et kasutasite tervet Teile antud URL-i.
+      send_instructions: Kui Teie e-postiaadress eksisteerib meie andmebaasis, saate paari minuti pärast e-kirja juhistega, kuidas oma salasõna taastada. Palun kontrollige oma rämpsposti kausta juhul, kui Te ei saanud seda e-kirja.
+      send_paranoid_instructions: Kui Teie e-postiaadress eksisteerib meie andmebaasis, saate paari minuti pärast e-kirja juhistega, kuidas oma salasõna taastada. Palun kontrollige oma rämpsposti kausta juhul, kui Te ei saanud seda e-kirja.
+      updated: Teie salasõna muutmine õnnestus. Te olete nüüd sisse loginud.
+      updated_not_active: Teie salasõna muutmine õnnestus.
+    registrations:
+      destroyed: Nägemist! Teie konto sulgemine õnnestus. Me loodame Teid varsti taas näha.
+      signed_up: Tere tulemast! Teie konto loomine õnnestus.
+      signed_up_but_inactive: Teie konto loodi edukalt, kuid me ei saanud Teid sisse logida, kuna teie konto pole veel aktiveeritud.
+      signed_up_but_locked: Teie konto loodi edukalt, kuid me ei saanud Teid sisse logida, kuna teie konto on lukustatud.
+      signed_up_but_pending: Kiri kinnituslingiga saadeti Teie e-postile. Pärast seda, kui te vajutate lingile, vaatame me Teie taotluse üle. Teid teavitatakse, kui see on vastu võetud.
+      signed_up_but_unconfirmed: Kiri kinnituslingiga saadeti Teie e-postile. Palun järgige linki, et aktiveerida oma konto. Palun kontrollige oma rämpsposti, kui Te ei saanud seda e-kirja.
+      update_needs_confirmation: Teie konto uuendamine õnnestus, kuid me peame Teie e-postiaadressit kinnitama. Palun kontrollige oma e-posti ning järgige linki, et kinnitada oma e-postiaadress. Palun kontrollige oma rämpsposti, kui Te ei saanud seda e-kirja.
+      updated: Teie konto uuendamine õnnestus.
+    sessions:
+      already_signed_out: Väljumine õnnestus.
+      signed_in: Sisenemine õnnestus.
+      signed_out: Väljumine õnnestus.
+    unlocks:
+      send_instructions: Te saate paari minuti pärast e-kirja juhistega, kuidas oma konto lukust lahti teha. Palun kontrollige oma rämpsposti kausta juhul, kui Te ei saanud seda e-kirja.
+      send_paranoid_instructions: Kui Teie konto eksisteerib, saate Te paari minuti pärast e-kirja juhistega, kuidas see lukust lahti teha. Palun kontrollige oma rämpsposti kausta juhul, kui Te ei saanud seda e-kirja.
+      unlocked: Teie konto lukust lahti võtmine õnnestus. Jätkamiseks, palun logige sisse.
+  errors:
+    messages:
+      already_confirmed: oli juba kinnitatud, palun proovige sisse logida
+      confirmation_period_expired: peab olema kinnitatud ajavahemikus %{period} - palun taotlege uus
+      expired: on aegunud, palun taotlege uus
+      not_found: ei leitud
+      not_locked: ei olnud lukustatud
+      not_saved:
+        one: '1 viga takistas seda %{resource} salvestamast:'
+        other: "%{count} viga takistas seda %{resource} salvestamast:"
diff --git a/config/locales/devise.eu.yml b/config/locales/devise.eu.yml
index 3526f2ab5373a22dd59c1cbe2d827513587ef4a0..2160f224314869a6bf289b9e17adc5a6635f9706 100644
--- a/config/locales/devise.eu.yml
+++ b/config/locales/devise.eu.yml
@@ -46,6 +46,10 @@ eu:
         extra: Ez baduzu hau eskatu, mesedez ezikusi e-mail hau. Zure pasahitza ez da aldatuko goiko estekara sartu eta berri bat sortzen ez baduzu.
         subject: 'Mastodon: Pasahitza berrezartzeko argibideak'
         title: Pasahitza berrezartzea
+      two_factor_disabled:
+        title: 2FA desgaituta
+      two_factor_enabled:
+        title: 2FA gaituta
       unlock_instructions:
         subject: 'Mastodon: Desblokeatzeko argibideak'
     omniauth_callbacks:
diff --git a/config/locales/devise.fa.yml b/config/locales/devise.fa.yml
index 963572e6b47e12233c8dd0cb17bfb2ec1ac8824c..0954c8484b99601dddf92221d421aae6c318d49a 100644
--- a/config/locales/devise.fa.yml
+++ b/config/locales/devise.fa.yml
@@ -46,6 +46,18 @@ fa:
         extra: اگر شما چنین درخواستی نکردید، لطفاً این ایمیل را نادیده بگیرید. تا زمانی که شما پیوند بالا را باز نکنید و رمز تازه‌ای نسازید، رمز شما عوض نخواهد شد.
         subject: 'ماستدون: راهنمایی برای بازنشانی رمز'
         title: بازنشانی رمز
+      two_factor_disabled:
+        explanation: ورود دومرحله‌ای برای حساب شما غیرفعال شده است. از الان می‌توانید تنها با نشانی ایمیل و رمز وارد حساب خود شوید.
+        subject: 'ماستدون: ورود دومرحله‌ای فعال نیست'
+        title: ورود دومرحله‌ای غیرفعال
+      two_factor_enabled:
+        explanation: ورود دومرحله‌ای برای حساب شما فعال شده است. برای ورود به کدی نیاز خواهید داشت که نرم‌افزار TOTP از پیش تنظیم شده برایتان می‌سازد.
+        subject: 'ماستدون: ورود دومرحله‌ای فعال است'
+        title: ورود دومرحله‌ای فعال
+      two_factor_recovery_codes_changed:
+        explanation: کدهای بازیابی قبلی نامعتبر شده‌اند و کدهای تازه‌ای ساخته شده‌اند.
+        subject: 'ماستدون: کدهای بازیابی برای ورود دومرحله‌ای دوباره ساخته شدند'
+        title: کدهای ورود دومرحله‌ای تغییر کرد
       unlock_instructions:
         subject: 'ماستدون: راهنمایی برای بازکردن قفل'
     omniauth_callbacks:
diff --git a/config/locales/devise.fr.yml b/config/locales/devise.fr.yml
index 2d3c86c1fb9c5c6062c0ec0e5fa9b2522427f4ca..37ebdaa0721ad7efc55ed2e5a72d6540d27cc195 100644
--- a/config/locales/devise.fr.yml
+++ b/config/locales/devise.fr.yml
@@ -9,7 +9,7 @@ fr:
       already_authenticated: Vous êtes déjà connecté⋅e.
       inactive: Votre compte n’est pas encore activé.
       invalid: "%{authentication_keys} ou mot de passe invalide."
-      last_attempt: Vous avez droit à une tentative avant que votre compte ne soit verrouillé.
+      last_attempt: Vous avez droit à une dernière tentative avant que votre compte ne soit verrouillé.
       locked: Votre compte est verrouillé.
       not_found_in_database: "%{authentication_keys} ou mot de passe invalide."
       pending: Votre compte est toujours en cours d'approbation.
@@ -21,7 +21,7 @@ fr:
         action: Vérifier l’adresse courriel
         action_with_app: Confirmer et retourner à %{app}
         explanation: Vous avez créé un compte sur %{host} avec cette adresse courriel. Vous êtes à un clic de l’activer. Si ce n’était pas vous, veuillez ignorer ce courriel.
-        explanation_when_pending: Vous avez demandé à vous inscrire à %{host} avec cette adresse courriel. Une fois que vous aurez confirmé cette adresse, nous étudierons votre demande. Vous ne pourrez pas vous connecté d'ici-là. Si votre demande est refusée, vos données seront supprimées du serveur, aucune action supplémentaire de votre part n'est donc requise. Si vous n'êtes pas à l'origine de cette demande, veuillez ignorer ce courriel.
+        explanation_when_pending: Vous avez demandé à vous inscrire à %{host} avec cette adresse de courriel. Une fois que vous aurez confirmé cette adresse, nous étudierons votre demande. Vous ne pourrez pas vous connecter d’ici-là. Si votre demande est refusée, vos données seront supprimées du serveur, aucune action supplémentaire de votre part n’est donc requise. Si vous n’êtes pas à l’origine de cette demande, veuillez ignorer ce message.
         extra_html: Merci de consultez également <a href="%{terms_path}">les règles du serveur</a> et <a href="%{policy_path}">nos conditions d’utilisation</a>.
         subject: 'Mastodon : Merci de confirmer votre inscription sur %{instance}'
         title: Vérifier l’adresse courriel
@@ -46,6 +46,18 @@ fr:
         extra: Si vous ne l’avez pas demandé, veuillez ignorer ce courriel. Votre mot de passe ne changera pas tant que vous n’aurez pas cliqué sur le lien ci-dessus et que vous n’en aurez pas créé un nouveau.
         subject: 'Mastodon : Instructions pour changer votre mot de passe'
         title: Réinitialisation du mot de passe
+      two_factor_disabled:
+        explanation: L'authentification à deux facteurs pour votre compte a été désactivée. La connexion est maintenant possible en utilisant uniquement l'adresse courriel et le mot de passe.
+        subject: 'Mastodon : authentification à deux facteurs désactivée'
+        title: 2FA désactivée
+      two_factor_enabled:
+        explanation: L'authentification à deux facteurs a été activée pour votre compte. Un jeton généré par l'application appariée TOTP sera nécessaire pour vous connecter.
+        subject: 'Mastodon : authentification à deux facteurs activée'
+        title: A2F activée
+      two_factor_recovery_codes_changed:
+        explanation: Les codes de récupération précédents ont été invalidés et de nouveaux sont générés.
+        subject: 'Mastodon : codes de récupération à deux facteurs ré-générés'
+        title: Codes de récupération 2FA modifiés
       unlock_instructions:
         subject: 'Mastodon : Instructions pour déverrouiller votre compte'
     omniauth_callbacks:
diff --git a/config/locales/devise.gl.yml b/config/locales/devise.gl.yml
index 60a935a8a2f36788d57817fb1b042b83477df298..0ce335576b71416c8f26ead14cff9c68bfdb68ea 100644
--- a/config/locales/devise.gl.yml
+++ b/config/locales/devise.gl.yml
@@ -46,6 +46,18 @@ gl:
         extra: Si non fixo esta solicitude, por favor ignore este correo. O seu contrasinal non cambiará ate que acceda a ligazón superior e cree unha nova.
         subject: 'Mastodon: Instruccións para restablecer o contrasinal'
         title: Restablecer contrasinal
+      two_factor_disabled:
+        explanation: Desactivouse o segundo factor de autenticación para túa conta. Agora xa podes conectarte utilizando só o correo-e e contrasinal.
+        subject: 'Mastodon: Autenticación con dobre factor desactivada'
+        title: 2FA desactivada
+      two_factor_enabled:
+        explanation: A autenticación con dobre factor foi activada para a túa conta. Pedirase o testemuño xerado pola aplicación TOTP emparellada.
+        subject: 'Mastodon: Activouse o dobre factor de autenticación'
+        title: 2FA activado
+      two_factor_recovery_codes_changed:
+        explanation: Os códigos de recuperación anteriores quedan anulados e os novos foron creados.
+        subject: 'Mastodon: Código de recuperación do dobre factor rexenerados'
+        title: Códigos de recuperación 2FA cambiados
       unlock_instructions:
         subject: 'Mastodon: Instruccións para desbloquear'
     omniauth_callbacks:
diff --git a/config/locales/devise.hu.yml b/config/locales/devise.hu.yml
index e495cb5f0e7b84e6447387e1c0bfa17d78b8daa8..62888be74e597d4567159217596f946535fe3a66 100644
--- a/config/locales/devise.hu.yml
+++ b/config/locales/devise.hu.yml
@@ -46,6 +46,18 @@ hu:
         extra: Amennyiben nem te kezdeményezted a módosítást, kérjük tekintsd ezt az e-mailt tárgytalannak. A Mastodon fiókodhoz tartozó jelszavad változatlan marad mindaddig, amíg újat nem hozol létre a fenti linkre kattintva.
         subject: 'Mastodon: Jelszó visszaállítási lépések'
         title: Jelszó visszaállítása
+      two_factor_disabled:
+        explanation: A fiókod kétlépcsős hitelesítését letiltottuk. A bejelentkezés most már csak e-mail címmel és jelszóval lehetséges.
+        subject: Kétlépcsős azonosítás letiltva
+        title: Kétlépcsős hitelesítés engedélyezve
+      two_factor_enabled:
+        explanation: Kétlépcsős hitelesítés engedélyezve van a fiókodban. Bejelentkezéshez a párosított TOTP alkalmazás által létrehozott tokenre lesz szükség.
+        subject: Kétlépcsős azonosítás engedélyezve
+        title: Kétlépcsős hitelesítés engedélyezve
+      two_factor_recovery_codes_changed:
+        explanation: A korábbi helyreállítási kódokat letiltottuk, és újakat generáltunk.
+        subject: Kétlépcsős helyreállítási kódok újra létrejöttek
+        title: A kétlépcsős kódok megváltozott
       unlock_instructions:
         subject: 'Mastodon: Feloldási lépések'
     omniauth_callbacks:
diff --git a/config/locales/devise.id.yml b/config/locales/devise.id.yml
index 5fa9020911358b2b165aac04723eba5ccf73413e..5b4e8af43da85803ea6898671182cd62e5f0e74b 100644
--- a/config/locales/devise.id.yml
+++ b/config/locales/devise.id.yml
@@ -12,16 +12,52 @@ id:
       last_attempt: Anda memiliki beberapa kali mencoba sebelum akun anda dikunci.
       locked: Akun anda dikunci.
       not_found_in_database: Ada %{authentication_keys} atau kata sandi yang tidak cocok.
+      pending: Akun Anda masih dalam peninjauan.
       timeout: Sesi anda telah berakhir. Silahkan coba masuk lagi.
       unauthenticated: Anda harus masuk atau mendaftar terlebih dahulu.
       unconfirmed: Anda harus mengkonfirmasi alamat email terlebih dahulu.
     mailer:
       confirmation_instructions:
+        action: Verifikasi alamat surel
+        action_with_app: Konfirmasi dan kembali ke %{app}
+        explanation: Anda membuat akun di %{host} dengan surel ini. Anda hanya perlu satu klik untuk mengaktifkannya. Jika ini bukan Anda, abaikan surel ini.
+        explanation_when_pending: Anda melamar undangan ke %{host} dengan surel ini. Saat Anda mengonfirmasi alamat surel Anda, kami akan meninjaunya. Anda dapat masuk untuk mengubah detail Anda atau menghapus akun Anda, tapi Anda tak dapat mengakses mayoritas fungsi sampai akun disetujui. Jika lamaran anda ditolak, data Anda akan dihapus, tak ada aksi lain yang dilakukan dari Anda. Jika ini bukan Anda, abaikan surel ini.
+        extra_html: Tolong cek juga <a href="%{terms_path}">peraturan server</a> dan <a href="%{policy_path}">ketentuan layanan kami</a>.
         subject: 'Mastodon: Petunjuk mengkonfirmasi untuk %{instance}'
+        title: Verifikasi alamat surel
+      email_changed:
+        explanation: 'Alamat surel akun Anda diubah menjadi:'
+        extra: Jika Anda tak mengganti surel Anda, mungkin seseorang telah mendapatkan akses ke Akun Anda. Mohon ubah kata sandi secepatnya atau hubungi admin server jika Anda dikunci dari akun Anda.
+        subject: 'Mastodon: Surel diganti'
+        title: Alamat surel baru
       password_change:
+        explanation: Kata sandi akun Anda telah diganti.
+        extra: Jika Anda tak mengubah kata sandi Anda, mungkin seseorang telah mendapatkan akses ke akun Anda. Mohon ubah kata sandi secepatnya atau hubungi admin server jika Anda dikunci dari akun Anda.
         subject: 'Mastodon: Kata sandi telah diubah'
+        title: Kata sandi diubah
+      reconfirmation_instructions:
+        explanation: Konfirmasi alamat baru untuk mengubah surel Anda.
+        extra: Jika perubahan ini tidak dimulai dari Anda, abaikan surel ini. Alamat surel untuk akun Mastodon tak berubah sampai Anda mengakses tautan di atas.
+        subject: 'Mastodon: Konfirmasi surel untuk %{instance}'
+        title: Verifikasi alamat surel
       reset_password_instructions:
+        action: Ubah kata sandi
+        explanation: Kata sandi baru yang diminta untuk akun Anda.
+        extra: Jika Anda tak meminta ini, abaikan surel ini. Kata sandi tak berubah sampai Anda mengakses tautan di atas dan membuatnya yang baru.
         subject: 'Mastodon: Petunjuk mereset kata sandi'
+        title: Reset kata sandi
+      two_factor_disabled:
+        explanation: Otentifikasi dua-faktor untuk akun Anda dimatikan. Kini Masuk dapat dilakukan hanya dengan alamat surel dan kata sandi.
+        subject: 'Mastodon: Otentifikasi dua-faktor dimatikan'
+        title: 2FA dimatikan
+      two_factor_enabled:
+        explanation: Otentifikasi dua-faktor telah dimatikan untuk Akun Anda. Token yang dibuat menggunakan aplikasi TOTP berpasangan akan diperlukan untuk masuk.
+        subject: 'Mastodon: Otentifikasi dua-faktor diaktifkan'
+        title: 2FA diaktifkan
+      two_factor_recovery_codes_changed:
+        explanation: Kode pemulihan sebelumnya telah dibatalkan dan yang baru telah dibuat.
+        subject: 'Mastodon: Kode pemulihan dua-faktor dibuat ulang'
+        title: Kode pemulihan 2FA diubah
       unlock_instructions:
         subject: 'Mastodon: Petunjuk membuka'
     omniauth_callbacks:
@@ -38,6 +74,7 @@ id:
       signed_up: Selamat datang! Pendaftaran anda berhasil.
       signed_up_but_inactive: Anda berhasil melakukan pendaftaran. Tetapi kami tidak dapat memasukkan anda karena akun anda belum diaktifkan.
       signed_up_but_locked: Anda berhasil melakukan pendaftaran. Tetapi kami tidak dapat memasukkan anda karena akun anda dikunci.
+      signed_up_but_pending: Pesan dengan tautan konfirmasi telah dikirim ke alamat surel Anda. Setelah Anda mengklik tautan, kami akan meninjau lamaran Anda. Anda akan diberitahu jika diterima.
       signed_up_but_unconfirmed: Sebuah pesan berisi link konfirmasi telah dikirim ke alamat email anda. Silakan ikuti link tersebut untuk mengaktifkan akun anda.
       update_needs_confirmation: Akun anda telah berhasil diubah, tetapi kami harus memverifikasi alamat email anda yang baru. Mohon cek email anda dan ikuti link untuk mengkonfirmasi alamat email anda yang baru.
       updated: Akun anda berhasil diubah.
diff --git a/config/locales/devise.it.yml b/config/locales/devise.it.yml
index b603e12c682e89cf55689b98b3990e8345c3b0dd..dca5e3caa3d88b224b94353829dcdddb5638cd7f 100644
--- a/config/locales/devise.it.yml
+++ b/config/locales/devise.it.yml
@@ -46,6 +46,17 @@ it:
         extra: Se questo cambiamento non è stato chiesto da te, ignora questa email. La tua password non verrà cambiata finché non accedi tramite il link qui sopra e ne crei una nuova.
         subject: 'Mastodon: Istruzioni per il reset della password'
         title: Ripristino password
+      two_factor_disabled:
+        explanation: L'autenticazione a due fattori per il tuo account è stata disattivata. Il login è ora possibile utilizzando solo l'indirizzo e-mail e la password.
+        subject: 'Mastodon: Autenticazione a due fattori disattivata'
+        title: 2FA disabilitata
+      two_factor_enabled:
+        explanation: L'autenticazione a due fattori è stata attivata per il tuo account. Un token generato dall'app TOTP collegata sarà richiesto per il login.
+        subject: 'Mastodon: Autenticazione a due fattori attivata'
+        title: 2FA abilitata
+      two_factor_recovery_codes_changed:
+        subject: 'Mastodon: codici di recupero a due fattori ri-generati'
+        title: Codici di recupero 2FA modificati
       unlock_instructions:
         subject: 'Mastodon: Istruzioni di sblocco'
     omniauth_callbacks:
diff --git a/config/locales/devise.ja.yml b/config/locales/devise.ja.yml
index dc147be624ece8b887165f6d2d076d6d81bae1f1..5f32b23815b9bf2aa02a9b2009e61ef48a7a6d38 100644
--- a/config/locales/devise.ja.yml
+++ b/config/locales/devise.ja.yml
@@ -21,7 +21,7 @@ ja:
         action: メールアドレスの確認
         action_with_app: 確認し %{app} に戻る
         explanation: このメールアドレスで%{host}にアカウントを作成しました。有効にするまであと一歩です。もし心当たりがない場合、申し訳ありませんがこのメールを無視してください。
-        explanation_when_pending: このメールアドレスで%{host}への登録を申請しました。あなたがメールアドレスを確認したら、サーバー管理者が申請を審査します。それまでログインできません。申請が却下された場合、あなたのデータは削除されますので以降の操作は必要ありません。もし心当たりがない場合、申し訳ありませんがこのメールを無視してください。
+        explanation_when_pending: このメールアドレスで %{host} への登録を申請しました。あなたがメールアドレスを確認したら、サーバー管理者が申請を審査します。ログインして一部設定を変更したりアカウントを削除できますが、ほとんどの機能は申請が承認されるまで利用できません。申請が却下された場合、あなたのデータは削除されますので以降の操作は必要ありません。もし心当たりがない場合、申し訳ありませんがこのメールを無視してください。
         extra_html: また <a href="%{terms_path}">サーバーのルール</a> と <a href="%{policy_path}">利用規約</a> もお読みください。
         subject: 'Mastodon: メールアドレスの確認 %{instance}'
         title: メールアドレスの確認
@@ -46,6 +46,18 @@ ja:
         extra: この要求に心当たりがない場合、このメールを無視してください。上記リンク先にアクセスし新しいものを作成するまでパスワードは変更されません。
         subject: 'Mastodon: パスワード再発行'
         title: パスワード再発行
+      two_factor_disabled:
+        explanation: あなたのアカウントの二段階認証が無効化されました。メールとパスワードのみでログインできます。
+        subject: 'Mastodon: 二段階認証が無効になりました'
+        title: 二段階認証が無効化されました
+      two_factor_enabled:
+        explanation: あなたのアカウントの二段階認証が有効化されました。ログインするには TOTP アプリで生成されたコードが必要です。
+        subject: 'Mastodon: 二段階認証が有効になりました'
+        title: 二段階認証が有効化されました
+      two_factor_recovery_codes_changed:
+        explanation: 以前のリカバリーコードが無効化され、新しいコードが生成されました。
+        subject: 'Mastodon: 二段階認証のリカバリーコードが再生成されました'
+        title: 二段階認証のリカバリーコードが変更されました
       unlock_instructions:
         subject: 'Mastodon: アカウントのロックの解除'
     omniauth_callbacks:
diff --git a/config/locales/devise.ko.yml b/config/locales/devise.ko.yml
index f48531246e1127dd763cffa99a5101e1fd6d2976..89dd12f1d7f994a5c0362ddd58eef3edac66c58c 100644
--- a/config/locales/devise.ko.yml
+++ b/config/locales/devise.ko.yml
@@ -46,6 +46,18 @@ ko:
         extra: 만약 당신이 시도한 것이 아니라면 이 메일을 무시해 주세요. 위 링크를 클릭해 패스워드를 새로 설정하기 전까지는 패스워드가 바뀌지 않습니다.
         subject: '마스토돈: 패스워드 재설정 방법'
         title: 패스워드 재설정
+      two_factor_disabled:
+        explanation: 당신의 계정에 설정된 이중 인증이 비활성화 되었습니다. 이제 이메일과 암호만으로 로그인이 가능합니다.
+        subject: '마스토돈: 이중 인증 비활성화'
+        title: 2FA 비활성화 됨
+      two_factor_enabled:
+        explanation: 당신의 계정에 이중 인증이 활성화되었습니다. 로그인시 페어링된 T-OTP 앱에서 생성된 토큰이 필요합니다.
+        subject: '마스토돈: 이중 인증 활성화'
+        title: 2FA 활성화 됨
+      two_factor_recovery_codes_changed:
+        explanation: 이전 복구 코드가 무효화되고 새 코드가 생성되었습니다
+        subject: '마스토돈: 이중 인증 복구 코드 재생성 됨'
+        title: 2FA 복구 코드 변경됨
       unlock_instructions:
         subject: '마스토돈: 잠금 해제 방법'
     omniauth_callbacks:
diff --git a/config/locales/devise.mk.yml b/config/locales/devise.mk.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8b9144a988941222eba3d4c61c19a62a8b057db4
--- /dev/null
+++ b/config/locales/devise.mk.yml
@@ -0,0 +1 @@
+mk:
diff --git a/config/locales/devise.nl.yml b/config/locales/devise.nl.yml
index 51a95403fdee475c688d5e866aaabef6314f4809..3ab4d9f11711f8a90d20637b9ecef9d269dab6e2 100644
--- a/config/locales/devise.nl.yml
+++ b/config/locales/devise.nl.yml
@@ -46,6 +46,18 @@ nl:
         extra: Wanneer jij dit niet hebt aangevraagd, mag je deze e-mail negeren. Jouw wachtwoord wordt pas gewijzigd nadat je de link hierboven hebt aangeklikt en een nieuw wachtwoord aanmaakt.
         subject: 'Mastodon: Wachtwoord opnieuw instellen'
         title: Wachtwoord opnieuw instellen
+      two_factor_disabled:
+        explanation: Tweestapsverificatie voor jouw account is uitgeschakeld. Je kunt nu alleen inloggen met een e-mailadres en wachtwoord.
+        subject: 'Mastodon: Tweestapsverificatie uitgeschakeld'
+        title: Tweestapsverificatie uitgeschakeld
+      two_factor_enabled:
+        explanation: Tweestapsverificatie voor jouw account is ingeschakeld. Om te kunnen aanmelden is een door een tweestapsverificatie-app genereerde aanmeldcode nodig.
+        subject: 'Mastodon: Tweestapsverificatie ingeschakeld'
+        title: Tweestapsverificatie ingeschakeld
+      two_factor_recovery_codes_changed:
+        explanation: De vorige herstelcodes zijn ongeldig gemaakt en nieuwe zijn aangemaakt.
+        subject: 'Mastodon: Tweestaps-herstelcodes zijn opnieuw aangemaakt'
+        title: Herstelcodes tweestapsverificatie veranderd
       unlock_instructions:
         subject: 'Mastodon: Instructies om opschorten account ongedaan te maken'
     omniauth_callbacks:
diff --git a/config/locales/devise.nn.yml b/config/locales/devise.nn.yml
new file mode 100644
index 0000000000000000000000000000000000000000..777f4e600f65daa6a550f2cf0f04551a49cf4fcd
--- /dev/null
+++ b/config/locales/devise.nn.yml
@@ -0,0 +1 @@
+nn:
diff --git a/config/locales/devise.oc.yml b/config/locales/devise.oc.yml
index 42be33f6b5b4c2de4dd03e503163fe6053bf355a..0fb259429a23bcc7fc6b2bba70a983f81f867ceb 100644
--- a/config/locales/devise.oc.yml
+++ b/config/locales/devise.oc.yml
@@ -46,6 +46,18 @@ oc:
         extra: S’avètz pas res demandat, fasquètz pas cas a aqueste corrièl. Vòstre senhal cambiarà pas se clicatz pas lo ligam e que ne causissètz pas un novèl.
         subject: Mastodon : consignas per reïnicializar lo senhal
         title: Reïnicializacion del senhal
+      two_factor_disabled:
+        explanation: L’autentificacion dos factors per vòstre compte es estada desactivada. La connexion es ara possibla solament amb l’adreça electronica e lo senhal.
+        subject: 'Mastodon : autentificacion dos factors desactivada'
+        title: 2FA desactivat
+      two_factor_enabled:
+        explanation: L’autentificacion dos factors es estada activada per vòstre compte. La connexion demandarà un geton generat per l’aplicacion TOTP associada.
+        subject: 'Mastodon : autentificacion dos factor activada'
+        title: 2FA activat
+      two_factor_recovery_codes_changed:
+        explanation: Los còdis de recuperacion precedents son ara invalids e de nòus son estats generats.
+        subject: 'Mastodon : còdis de recuperacion dos factors regenerats'
+        title: Còdis 2FA de recuperacion cambiats
       unlock_instructions:
         subject: Mastodon : consignas de desblocatge
     omniauth_callbacks:
diff --git a/config/locales/devise.pt.yml b/config/locales/devise.pt-PT.yml
similarity index 99%
rename from config/locales/devise.pt.yml
rename to config/locales/devise.pt-PT.yml
index 9b44bbf00c6e6dadb06cf72c565e3270e9e78d15..7d3f8fc5523818cd8f6768883ebcc883b33d09a9 100644
--- a/config/locales/devise.pt.yml
+++ b/config/locales/devise.pt-PT.yml
@@ -1,5 +1,5 @@
 ---
-pt:
+pt-PT:
   devise:
     confirmations:
       confirmed: O teu endereço de e-mail foi confirmado com sucesso.
diff --git a/config/locales/devise.sk.yml b/config/locales/devise.sk.yml
index 85de603d3e2068a4d159a0c31a96c05c3e7fd2b8..759d4874b7549b3294617866c871c837f6acd619 100644
--- a/config/locales/devise.sk.yml
+++ b/config/locales/devise.sk.yml
@@ -37,8 +37,8 @@ sk:
       reconfirmation_instructions:
         explanation: Potvrď novú emailovú adresu na ktorú chceš zmeniť svoj email.
         extra: Pokiaľ si túto akciu nevyžiadal/a, prosím ignoruj tento email. Emailová adresa pre tvoj Mastodon účet totiž nebude zmenená pokiaľ nepostúpiš na adresu uvedenú vyššie.
-        subject: 'Mastodon: Potvrďenie emailu pre %{instance}'
-        title: Overiť emailovú adresu
+        subject: 'Mastodon: Potvrď email pre %{instance}'
+        title: Over emailovú adresu
       reset_password_instructions:
         action: Zmeň svoje heslo
         explanation: Vyžiadal/a si si nové heslo pre svoj účet.
diff --git a/config/locales/devise.sl.yml b/config/locales/devise.sl.yml
index 7d1e05fdf0f9dd9b48a0dfbb913ba6e5427e7a32..dee1b91253887392ab1d4dc4e6c0c53c25135711 100644
--- a/config/locales/devise.sl.yml
+++ b/config/locales/devise.sl.yml
@@ -6,7 +6,7 @@ sl:
       send_instructions: V nekaj minutah boste prejeli e-poštno sporočilo z navodili za potrditev vašega e-poštnega naslova. Če niste prejeli e-poštnega sporočila, preverite mapo neželena pošta.
       send_paranoid_instructions: Če vaš e-poštni naslov obstaja v naši podatkovni bazi, boste v nekaj minutah prejeli e-poštno sporočilo z navodili za potrditev vašega e-poštnega naslova. Če niste prejeli e-poštnega sporočila, preverite mapo neželena pošta.
     failure:
-      already_authenticated: Prijavljeni ste že.
+      already_authenticated: Ste že prijavljeni.
       inactive: Vaš račun še ni aktiviran.
       invalid: Neveljavno %{authentication_keys} ali geslo.
       last_attempt: Pred zaklepom računa imate še en poskus.
@@ -45,9 +45,44 @@ sl:
         explanation: Zahtevali ste novo geslo za svoj račun.
         extra: Če tega niste zahtevali, prezrite to e-poštno sporočilo. Vaše geslo se ne bo spremenilo, dokler ne kliknete na zgornjo povezavo in ustvarite novega.
         subject: 'Mastodon: Navodila za ponastavitev gesla'
-        title: Ponastavitev gesla
+        title: Ponastavi geslo
       unlock_instructions:
         subject: 'Mastodon: Odkleni navodila'
     omniauth_callbacks:
       failure: Overitev iz %{kind} ni možna zaradi "%{reason}".
       success: Overitev iz računa %{kind} je bila uspešna.
+    passwords:
+      no_token: Do te strani ne morete dostopati, ne da bi prišli iz e-poštne za ponastavitev gesla. Če prihajate iz e-poštne za ponastavitev gesla, se prepričajte, da ste uporabili celoten navedeni URL.
+      send_instructions: Če vaš e-poštni naslov obstaja v naši bazi podatkov, boste v nekaj minutah na vaš e-poštni naslov prejeli povezavo za obnovitev gesla. Če niste prejeli e-pošte, preverite mapo z neželeno pošto.
+      send_paranoid_instructions: Če vaš e-poštni naslov obstaja v naši bazi podatkov, boste v nekaj minutah na vaš e-poštni naslov prejeli povezavo za obnovitev gesla. Če niste prejeli e-pošte, preverite mapo z neželeno pošto.
+      updated: Vaše geslo je bilo uspešno spremenjeno. Zdaj ste prijavljeni.
+      updated_not_active: Vaše geslo je bilo uspešno spremenjeno.
+    registrations:
+      destroyed: Adijo! Vaš račun je bil uspešno preklican. Upamo, da vas bomo kmalu spet videli.
+      signed_up: Dobrodošli! Uspešno ste se vpisali.
+      signed_up_but_inactive: Uspešno ste se vpisali. Vendar vas nismo mogli prijaviti, ker vaš račun še ni aktiviran.
+      signed_up_but_locked: Uspešno ste se vpisali. Vendar vas nismo mogli prijaviti, ker je vaš račun zaklenjen.
+      signed_up_but_pending: Na vaš e-poštni naslov je bilo poslano sporočilo s povezavo za potrditev. Ko kliknete na povezavo, bomo pregledali vašo prijavo. Obveščeni boste, če bo odobren.
+      signed_up_but_unconfirmed: Na vaš e-poštni naslov je bilo poslano sporočilo s povezavo za potrditev. Sledite povezavi, da aktivirate svoj račun. Če niste prejeli te e-pošte, preverite mapo z neželeno pošto.
+      update_needs_confirmation: Uspešno ste posodobili račun, vendar moramo potrditi vaš novi e-poštni naslov. Preverite svojo e-pošto in sledite povezavi za potrditev, da potrdite nov e-poštni naslov. Če niste prejeli te e-poše, preverite mapo z neželeno pošto.
+      updated: Vaš račun je bil uspešno posodobljen.
+    sessions:
+      already_signed_out: Uspešno ste se odjavili.
+      signed_in: Uspešno ste se prijavili.
+      signed_out: Uspešno ste se odjavili.
+    unlocks:
+      send_instructions: Prejeli boste e-pošto z navodili o tem, kako v nekaj minutah odklenete svoj račun. Če niste prejeli te e-pošte, preverite mapo z neželeno pošto.
+      send_paranoid_instructions: Če vaš račun obstaja, boste prejeli e-pošto z navodili za njegovo odklepanje v nekaj minutah. Če niste prejeli te e-pošte, preverite mapo z neželeno pošto.
+      unlocked: Vaš račun je bil uspešno odklenjen. Če želite nadaljevati, se prijavite.
+  errors:
+    messages:
+      already_confirmed: je bil potrjen, poskusite se prijaviti
+      confirmation_period_expired: mora biti potrjena v %{period}, zahtevajte novo
+      expired: je potekla, zahtevajte novo
+      not_found: ni najdeno
+      not_locked: ni bil zaklenjen
+      not_saved:
+        few: "%{count} napake so preprečile shranjevanje %{resource}:"
+        one: '1 napaka je preprečila shranjevanje %{resource}:'
+        other: "%{count} napak je preprečilo shranjevanje %{resource}:"
+        two: "%{count} napaki sta preprečili shranjevanje %{resource}:"
diff --git a/config/locales/devise.th.yml b/config/locales/devise.th.yml
index 8a9a65465ed5d6e9853b561ba376fb0c11626ba1..383c515138f9638d64d669b62130fb802e0a1313 100644
--- a/config/locales/devise.th.yml
+++ b/config/locales/devise.th.yml
@@ -9,7 +9,9 @@ th:
       already_authenticated: คุณได้ลงชื่อเข้าอยู่แล้ว
       inactive: ยังไม่ได้เปิดใช้งานบัญชีของคุณ
       invalid: "%{authentication_keys} หรือรหัสผ่านไม่ถูกต้อง"
+      locked: บัญชีของคุณถูกล็อก
       not_found_in_database: "%{authentication_keys} หรือรหัสผ่านไม่ถูกต้อง"
+      pending: บัญชีของคุณกำลังถูกตรวจสอบ
       timeout: เซสชันของคุณหมดอายุแล้ว โปรดลงชื่อเข้าอีกครั้งเพื่อดำเนินการต่อ
     mailer:
       email_changed:
@@ -19,10 +21,15 @@ th:
         title: เปลี่ยนรหัสผ่านแล้ว
       reset_password_instructions:
         action: เปลี่ยนรหัสผ่าน
+      two_factor_disabled:
+        title: ปิดใช้งาน 2FA แล้ว
+      two_factor_enabled:
+        title: เปิดใช้งาน 2FA แล้ว
     passwords:
       send_instructions: If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.
       send_paranoid_instructions: If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.
     registrations:
+      signed_up_but_pending: ข้อความที่ได้รับการยืนยันจะถูกส่งไปยังอีเมลของคุณ หลังจากนั้นให้ทำการคลิกลิงก์ เพื่อให้พวกเราตรวจสอบข้อมูลเสร็จเรียบร้อย และหลังจากนั้นคุณจะได้รับการยืนยัน
       signed_up_but_unconfirmed: A message with a confirmation link has been sent to your email address. Please follow the link to activate your account.
       update_needs_confirmation: You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address.
     sessions:
diff --git a/config/locales/devise.tr.yml b/config/locales/devise.tr.yml
index 0a504dd67635900ff497a05acbeaef792c885d93..30cedc1fc92f3514f1efa5854d8cf51c54e8b105 100644
--- a/config/locales/devise.tr.yml
+++ b/config/locales/devise.tr.yml
@@ -8,8 +8,91 @@ tr:
     failure:
       already_authenticated: Zaten oturum açtınız.
       inactive: Hesabınız henüz etkinleştirilmedi.
+      invalid: Geçersiz %{authentication_keys} ya da şifre.
       last_attempt: Hesabınız kilitlenmeden önce bir kez daha denemeniz gerekir.
       locked: Hesabınız kilitli.
+      not_found_in_database: Geçersiz %{authentication_keys} ya da şifre.
+      pending: Hesabınız hala inceleniyor.
+      timeout: Oturum süreniz sona erdi. Lütfen devam etmek için tekrar giriş yapınız.
+      unauthenticated: Devam etmeden önce oturum açmanız veya kayıt olmanız gerek.
+      unconfirmed: Devam etmeden önce e-posta adresini onaylamanız gerekli.
     mailer:
       confirmation_instructions:
         action: E-posta adresinizi doğrulayın
+        action_with_app: Onayla ve %{app}'a dön
+        explanation: Bu e-posta adresiyle %{host} bir hesap oluşturdunuz. Etkinleştirmekten bir tık uzaktasınız. Bu siz değilseniz, lütfen bu e-postayı dikkate almayın.
+        explanation_when_pending: Bu e-posta adresiyle %{host} adresine bir davetiye için başvuru yaptınız. E-posta adresinizi onayladıktan sonra başvurunuzu inceleyeceğiz. O zamana kadar giriş yapamazsınız. Başvurunuz reddedilirse, verileriniz silinecek, başka bir işlem yapmanız gerekmeyecek. Bu siz değilseniz, lütfen bu e-postayı dikkate almayın.
+        extra_html: Lütfen ayrıca <a href="%{terms_path}">sunucu kurallarını</a> ve <a href="%{policy_path}">hizmet şartlarımızı</a> inceleyin.
+        subject: 'Mastodon: %{instance} için onay talimatları'
+        title: E-posta adresini doğrulayın
+      email_changed:
+        explanation: 'Hesabınızın e-posta adresi şu şekilde değiştirildi:'
+        extra: E-posta adresinizi değiştirmediyseniz, büyük olasılıkla birileri hesabınıza erişti. Lütfen derhal parolanızı değiştirin veya hesabınız kilitlendiyse sunucu yöneticisine başvurun.
+        subject: 'Mastodon: E-posta deÄŸiÅŸti'
+        title: Yeni e-posta adresi
+      password_change:
+        explanation: Hesabınızın parolası değiştirildi.
+        extra: Parolanızı değiştirmediyseniz, büyük olasılıkla birileri hesabınıza erişmiş olabilir. Lütfen derhal parolanızı değiştirin veya hesabınız kilitlendiyse sunucu yöneticisine başvurun.
+        subject: 'Mastodon: Parola deÄŸiÅŸtirildi'
+        title: Parola deÄŸiÅŸtirildi
+      reconfirmation_instructions:
+        explanation: E-postanızı değiştirmek için yeni adresi onaylayın.
+        extra: Bu değişiklik sizin tarafınızdan başlatılmadıysa, lütfen bu e-postayı dikkate almayın. Mastodon hesabının e-posta adresi, yukarıdaki bağlantıya erişene kadar değişmez.
+        subject: 'Mastodon: %{instance} için e-postayı onayla'
+        title: E-posta adresinizi doğrulayın
+      reset_password_instructions:
+        action: Parolayı değiştir
+        explanation: Hesabınız için yeni bir parola istediniz.
+        extra: Bunu siz yapmadıysanız, lütfen bu e-postayı dikkate almayın. Parolanız yukarıdaki bağlantıya erişene ve yeni bir tane oluşturuncaya kadar değişmez.
+        subject: 'Mastodon: Parola sıfırlama talimatları'
+        title: Parola sıfırlama
+      two_factor_disabled:
+        explanation: Hesabınız için iki-adımlı kimlik doğrulama devre dışı bırakıldı. Şimdi sadece e-posta adresi ve parola kullanarak giriş yapabilirsiniz.
+        subject: 'Mastodon: İki-adımlı kimlik doğrulama devre dışı bırakıldı'
+        title: 2FA devre dışı bırakıldı
+      two_factor_enabled:
+        explanation: Hesabınız için iki-adımlı kimlik doğrulama etkinleştirildi. Giriş yapmak için eşleştirilmiş TOTP uygulaması tarafından oluşturulan bir belirteç gereklidir.
+        subject: 'Mastodon: İki-adımlı kimlik doğrulama etkinleştirildi'
+        title: 2FA etkinleÅŸtirildi
+      two_factor_recovery_codes_changed:
+        explanation: Önceki kurtarma kodları geçersiz kılındı ve yenileri oluşturuldu.
+        subject: 'Mastodon: İki-adımlı kurtarma kodları yeniden oluşturuldu'
+        title: 2FA kurtarma kodları değiştirildi
+      unlock_instructions:
+        subject: 'Mastodon: Engel kaldırma talimatları'
+    omniauth_callbacks:
+      failure: '%{kind}''den kimliğiniz doğrulanamadı çünkü "%{reason}".'
+      success: "%{kind} hesabından başarıyla kimlik doğrulaması yapıldı."
+    passwords:
+      no_token: Bu sayfaya şifre sıfırlama e-postasından gelmeden erişemezsiniz. Şifre sıfırlama e-postasından geliyorsanız lütfen sağlanan tam URL'yi kullandığınızdan emin olun.
+      send_instructions: E-posta adresiniz veritabanımızda varsa, e-posta adresinize birkaç dakika içinde bir parola kurtarma bağlantısı gönderilir. Bu e-postayı almadıysanız, lütfen spam klasörünüzü kontrol edin.
+      send_paranoid_instructions: E-posta adresiniz veritabanımızda varsa, e-posta adresinize birkaç dakika içinde bir parola kurtarma bağlantısı gönderilir. Bu e-postayı almadıysanız, lütfen spam klasörünüzü kontrol edin.
+      updated: Parolanız başarıyla değiştirildi. Şuan oturumunuz açıldı.
+      updated_not_active: Parolanız başarıyla değiştirildi.
+    registrations:
+      destroyed: Görüşürüz! hesabın başarıyla iptal edildi. Umarız seni sonra tekrar görürüz.
+      signed_up: Hoş geldiniz! Başarılı bir şekilde oturum açtınız.
+      signed_up_but_inactive: Başarıyla kaydoldun. Ancak, seni içeri alamıyoruz çünkü hesabın henüz aktif değil.
+      signed_up_but_locked: Başarıyla kaydoldun. Ancak, seni içeri alamıyoruz çünkü hesabın kilitli.
+      signed_up_but_pending: Onay bağlantısına sahip bir mesaj e-posta adresinize gönderildi. Bağlantıyı tıkladıktan sonra başvurunuzu inceleyeceğiz. Onaylanması durumunda size bilgi verilecektir.
+      signed_up_but_unconfirmed: Onay bağlantısına sahip bir mesaj e-posta adresinize gönderildi. Lütfen hesabınızı etkinleştirmek için bağlantıyı takip edin. Bu e-postayı almadıysanız, lütfen spam klasörünüzü kontrol edin.
+      update_needs_confirmation: Hesabınızı başarıyla güncellediniz, ancak yeni e-posta adresinizi doğrulamamız gerekiyor. Lütfen e-postanızı kontrol edin ve yeni e-posta adresinizi onaylamak için onay bağlantısını izleyin. Bu e-postayı almadıysanız, lütfen spam klasörünüzü kontrol edin.
+      updated: Hesabınız başarıyla güncellendi.
+    sessions:
+      already_signed_out: Başarıyla çıkış yapıldı.
+      signed_in: Başarıyla giriş yapıldı.
+      signed_out: Başarıyla çıkış yapıldı.
+    unlocks:
+      send_instructions: Hesabınızı birkaç dakika içinde nasıl açacağınıza ilişkin talimatları içeren bir e-posta alacaksınız. Bu e-postayı almadıysanız, lütfen spam klasörünüzü kontrol edin.
+      send_paranoid_instructions: Hesabınız varsa, birkaç dakika içinde nasıl kilidini açacağınıza ilişkin talimatları içeren bir e-posta alacaksınız. Bu e-postayı almadıysanız, lütfen spam klasörünüzü kontrol edin.
+      unlocked: Hesabınızın kilidi başarıyla açıldı. Devam etmek için lütfen oturum açın.
+  errors:
+    messages:
+      already_confirmed: zaten onaylanmış, lütfen tekrar oturum açmayı deneyin
+      confirmation_period_expired: "%{period} içinde onaylanması gerekli, lütfen yeni bir tane talep edin"
+      expired: süresi geçti, lütfen yeni bir tane talep edin
+      not_found: bulunamadı
+      not_locked: kilitlenmemiÅŸ
+      not_saved:
+        one: '1 hata bu %{resource} kaydedilmesini önledi:'
+        other: "%{count} hata bu %{resource} kaydedilmesini önledi:"
diff --git a/config/locales/devise.uk.yml b/config/locales/devise.uk.yml
index 6ec01b3d7cdd97fbebbe2339ab9e257e17b97369..e1eb2d1ae4333c4db15603171922334aee1d5c87 100644
--- a/config/locales/devise.uk.yml
+++ b/config/locales/devise.uk.yml
@@ -7,26 +7,53 @@ uk:
       send_paranoid_instructions: Якщо Ваша поштова скринька існує в нашій базі даних, то Ви отримаєте листа з інструкціями щодо підтвердження Вашої адреси через декілька хвилин.
     failure:
       already_authenticated: Ви вже увійшли.
-      inactive: Ваш акаунт ще не активований.
+      inactive: Ваш обліковий запис ще не активований.
       invalid: Неправильний %{authentication_keys} або пароль.
-      last_attempt: У вас є остання спроба, після якої вхід буде заблокований.
-      locked: Ваш акаунт заблокований.
+      last_attempt: У вас залишилась ще одна спроба, після якої ваш обліковий запис буде заблоковано.
+      locked: Ваш обліковий запис заблоковано.
       not_found_in_database: Неправильний %{authentication_keys} або пароль.
+      pending: Ваш обліковий запис ще перебуває на розгляді.
       timeout: Ваша сесія вичерпана. Будь ласка, зайдіть знову, щоб продовжити.
       unauthenticated: Для продовження Вам потрібно увійти або зареєструватися.
       unconfirmed: Для продовження Вам потрібно підтвердити Вашу поштову скриньку.
     mailer:
       confirmation_instructions:
+        action: Підтвердити адресу електронної пошти
+        action_with_app: Підтвердити та повернутися до %{app}
+        explanation: Ви створили обліковий запис на %{host} з цією адресою електронної пошти, і зараз на відстані одного кліку від його активації. Якщо це були не ви, проігноруйте цього листа, будь ласка.
+        extra_html: Також перегляньте <a href="%{terms_path}">правила серверу</a> та <a href="%{policy_path}">умови використання</a>.
         subject: 'Mastodon: Інструкції для підтвердження %{instance}'
+        title: Підтвердити адресу електронної пошти
+      email_changed:
+        explanation: 'Адреса електронної пошти для вашого облікового запису змінюється на:'
+        extra: Якщо ви не змінювали свою адресу електронної пошти, то хтось вірогідно отримав доступ до вашого облікового запису. Будь ласка, негайно змініть свій пароль або зв'яжіться з адміністратором серверу, якщо ви не маєте доступу до свого облікового запису.
+        subject: 'Mastodon: адресу електронної пошти змінено'
+        title: Нова адреса електронної пошти
       password_change:
+        explanation: Пароль до вашого облікового запису був змінений.
+        extra: Якщо ви не змінювали свій пароль, то хтось вірогідно отримав доступ до вашого облікового запису. Будь ласка, негайно змініть свій пароль або зв'яжіться з адміністратором серверу, якщо ви не маєте доступу до свого облікового запису.
         subject: 'Mastodon: Ваш пароль змінений'
+        title: Пароль змінено
+      reconfirmation_instructions:
+        explanation: Підтвердіть нову адресу електронної пошти, щоб змінити поточну.
+        extra: Якщо ця зміна не була ініційована вами, проігноруйте цього листа. Адреса електронної пошти для облікового запису Mastodon не зміниться, доки ви не перейдете за вищевказаним посиланням.
+        subject: 'Mastodon: Підтвердіть електронну адресу для %{instance}'
+        title: Підтвердіть адресу електронної пошти
       reset_password_instructions:
+        action: Змінити пароль
+        explanation: Ви запитали новий пароль для вашого облікового запису.
+        extra: Якщо ви не запитували зміну пароля, ігноруйте цей лист. Ваш пароль не буде змінено, допоки ви не перейдете за посиланням та не створите новий.
         subject: 'Mastodon: Інструкції для скидання паролю'
+        title: Скидання пароля
+      two_factor_disabled:
+        title: Двофакторна автентифікація вимкнена
+      two_factor_enabled:
+        title: Двофакторна автентифікація увімкнена
       unlock_instructions:
         subject: 'Mastodon: Інструкції для розблокування'
     omniauth_callbacks:
       failure: Нам не вдалося аутентифікувати Вас з %{kind} через те, що "%{reason}".
-      success: Успішно аутентифіковано з акаунту %{kind}.
+      success: Успішно аутентифіковано з облікового запису %{kind}.
     passwords:
       no_token: Ви не можете отримати доступ до цієї сторінки без переходу за посиланням з листа з інструкціями. Якщо ви дійсно перейшли з цього листа, переконайтеся, що ви перейшли за повним посиланням.
       send_instructions: Ви отримаєте листа з інструкціями щодо зміни паролю через декілька хвилин.
@@ -34,21 +61,22 @@ uk:
       updated: Ваш пароль було успішно змінено. Вхід виконано.
       updated_not_active: Ваш пароль було успішно змінено.
     registrations:
-      destroyed: До побачення! Ваш акаунт було успішно видалено. Сподіваємось, що Ви скоро повернетеся.
+      destroyed: До побачення! Ваш обліковий запис було успішно видалено. Сподіваємось, Ви скоро повернетеся.
       signed_up: Ласкаво просимо! Ви були успішно зареєстровані.
-      signed_up_but_inactive: Ви були успішно зареєстровані, але ми не можемо авторизувати вас, оскільки Ваш акаунт ще не активовано.
-      signed_up_but_locked: Ви були успішно зареєстровані, аале ми не можемо авторизувати вас, оскільки Ваш акаунт заблокований.
-      signed_up_but_unconfirmed: Повідомлення з посиланням на підтвердження будо відправлено на Вашу поштову скриньку. Будь ласка, перейдіть за посиланням, щоб активувати Ваш акаунт.
-      update_needs_confirmation: Ви успішно оновили Ваш акаунт, але нам потрібно підтвердити Вашу нову поштову адресу. Будь ласка, перевірте Вашу скриньку та перейдіть за посиланням, щоб активувати Вашу нову адресу.
-      updated: Ваш акаунт було успішно оновлено.
+      signed_up_but_inactive: Ви були успішно зареєстровані, але ми не можемо авторизувати вас, оскільки ваш обліковий запис ще не активовано.
+      signed_up_but_locked: Ви були успішно зареєстровані, але ми не можемо авторизувати вас, оскільки ваш обліковий запис заблокований.
+      signed_up_but_pending: Лист з посиланням для підтвердження було надіслано на вашу електронну пошту. Коли ви перейдете за цим посиланням, ми розглянемо ваш запит. Вас буде проінформовано, якщо запит буде схвалено.
+      signed_up_but_unconfirmed: Повідомлення з посиланням на підтвердження будо відправлено на вашу адресу електронної пошти. Будь ласка, перейдіть за посиланням, щоб активувати ваш обліковий запис. Якщо ви не отримали цього листа, перевірте теку зі спамом у вашій скринці.
+      update_needs_confirmation: Ваш обліковий запис оновлено, але необхідно підтвердити нову адресу електронної пошти. Будь ласка, перевірте свою електронну скриньку і перейдіть за посиланням "Підтвердити", шоб завершити оновлення адреси електронної пошти. Якщо ви не отримали цього листа, перевірте теку зі спамом у вашій скринці.
+      updated: Ваш обліковий запис було успішно оновлено.
     sessions:
       already_signed_out: Ви вже вийшли з акаунту.
       signed_in: Ви успішно увійшли в акаунт.
       signed_out: Ви успішно вийшли з акаунту.
     unlocks:
-      send_instructions: Ви отримаєте листа з інструкціями щодо розблокування Вашого акаунту через декілька хвилин.
-      send_paranoid_instructions: Якщо Ваш акаунт існує, Ви отримаєте листа з інструкціями щодо розблокування Вашого акаунту через декілька хвилин.
-      unlocked: Ваш акаунт було успішно активовано. Будь ласка, увійдіть, щоб продовжити.
+      send_instructions: За кілька хвилин ви отримаєте електронного листа з інструкціями щодо розблокування вашого облікового запису. Якщо ви не отримали цього листа, перевірте теку зі спамом у вашій скринці.
+      send_paranoid_instructions: Якщо ваш обліковий запис існує, за декілька хвилин ви отримаєте електронного листа з інструкціями щодо його розблокування. Якщо ви не отримали цього листа, перевірте теку зі спамом у вашій скринці.
+      unlocked: Ваш обліковий запис було успішно розблоковано. Будь ласка, увійдіть для продовження.
   errors:
     messages:
       already_confirmed: вже був підтверджений, будь ласка, спробуйте увійти
@@ -57,7 +85,7 @@ uk:
       not_found: не знайдено
       not_locked: не був заблокований
       not_saved:
-        few: "%{count} помилок не дало цьому %{resource} зберегтися:"
+        few: "%{count} помилки не дали цьому %{resource} зберегтися:"
         many: "%{count} помилок не дало цьому %{resource} зберегтися:"
         one: '1 помилка не дала цьому %{resource} зберегтися:'
         other: "%{count} помилок не дало цьому %{resource} зберегтися:"
diff --git a/config/locales/devise.zh-CN.yml b/config/locales/devise.zh-CN.yml
index 22fa130f60d49d54179b4b04abe36c399344eb3f..f9943238e3360e99ea6e804474b95eac20a4a702 100644
--- a/config/locales/devise.zh-CN.yml
+++ b/config/locales/devise.zh-CN.yml
@@ -58,7 +58,7 @@ zh-CN:
       updated: 你的密码已修改成功,你现在已登录。
       updated_not_active: 你的密码已修改成功。
     registrations:
-      destroyed: 再见!你的帐户已成功注销。我们希望很快可以再见到你。
+      destroyed: 再见!你的帐户已成功销毁。我们希望很快可以再见到你。
       signed_up: 欢迎!你已注册成功。
       signed_up_but_inactive: 你已注册,但尚未激活帐户。
       signed_up_but_locked: 你已注册,但帐户被锁定了。
diff --git a/config/locales/devise.zh-TW.yml b/config/locales/devise.zh-TW.yml
index cb989630eeb30aa1ad368ddb575f3e06503af050..895405f4a7151fb3a2ea94f0bd7ff7ac2fb0418b 100644
--- a/config/locales/devise.zh-TW.yml
+++ b/config/locales/devise.zh-TW.yml
@@ -46,6 +46,18 @@ zh-TW:
         extra: 若您並未請求,請忽略此信件。您的密碼在存取上方連結並建立新連結前不會變更。
         subject: Mastodon:重設密碼指引
         title: 重設密碼
+      two_factor_disabled:
+        explanation: 您帳戶的兩步驟驗證已停用。現在只能使用電子信箱位址及密碼登入。
+        subject: Mastodon:已停用兩步驟驗證
+        title: 已停用 2FA
+      two_factor_enabled:
+        explanation: 已對您的帳戶啟用兩步驟驗證。登入時將需要配對之 TOTP 應用程式所產生的 Token。
+        subject: Mastodon:已啟用兩步驟驗證
+        title: 已啟用 2FA
+      two_factor_recovery_codes_changed:
+        explanation: 上一次的復原碼已經失效,且已產生新的復原碼。
+        subject: Mastodon:兩步驟驗證復原碼已經重新產生
+        title: 2FA 復原碼已變更
       unlock_instructions:
         subject: Mastodon:帳戶解鎖指引
     omniauth_callbacks:
diff --git a/config/locales/doorkeeper.ar.yml b/config/locales/doorkeeper.ar.yml
index 6f9e38f8b3c6e3c0a2b75ee0963bbcc1217f3c9c..51d8b76b434c2368a0c0f18fa4e1df170af2e917 100644
--- a/config/locales/doorkeeper.ar.yml
+++ b/config/locales/doorkeeper.ar.yml
@@ -113,6 +113,11 @@ ar:
       application:
         title: طلب تصريح مفتوح OAuth
     scopes:
+      admin:read: قراءة كافة البيانات على الخادم
+      admin:read:accounts: قراءة المعلومات الحساسة عن كافة الحسابات
+      admin:write: تعديل كافة البيانات على الخادم
+      admin:write:accounts: اتخاذ إجراءات إشراف على الحسابات
+      admin:write:reports: اتخاذ إجراءات إشراف على الإبلاغات
       follow: تعديل علاقات الحساب
       push: تلقي إشعاراتك
       read: قراءة كافة بيانات حسابك
@@ -128,7 +133,7 @@ ar:
       read:search: البحث مكانك
       read:statuses: رؤية كافة المنشورات
       write: تغيير كافة بيانات حسابك
-      write:accounts: تعديل ملفك الشخصي
+      write:accounts: تعديل صفحتك التعريفية
       write:blocks: حجب الحسابات و النطاقات
       write:favourites: الإعجاب بمنشورات
       write:filters: إنشاء عوامل تصفية
diff --git a/config/locales/doorkeeper.br.yml b/config/locales/doorkeeper.br.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c7677c850c634b75b037ffd563bb1e1a6e36a5cf
--- /dev/null
+++ b/config/locales/doorkeeper.br.yml
@@ -0,0 +1 @@
+br:
diff --git a/config/locales/doorkeeper.cy.yml b/config/locales/doorkeeper.cy.yml
index 19798c4d9f1198e52f23f8e5e34718927f46c1a2..e29043e86ffc3bfeadd16c03de03f584990ad98f 100644
--- a/config/locales/doorkeeper.cy.yml
+++ b/config/locales/doorkeeper.cy.yml
@@ -114,6 +114,12 @@ cy:
       application:
         title: Mae awdurdodiad OAuth yn ofynnol
     scopes:
+      admin:read: darllenwch yr holl ddata ar y serfiwr
+      admin:read:accounts: darllen gwybodaeth sensitif o'r holl gyfrifon
+      admin:read:reports: darllen gwybodaeth sensitif am bob adroddiad a chyfrifon yr adroddir amdanynt
+      admin:write: addasu pob data ar y serfiwr
+      admin:write:accounts: cyflawni camau cymedroli ar gyfrifon
+      admin:write:reports: cyflawni camau cymedroli ar adroddiadau
       follow: addasu perthnasau cyfrif
       push: derbyn eich hysbysiadau gwthiadwy
       read: darllen holl ddata eich cyfrif
diff --git a/config/locales/doorkeeper.da.yml b/config/locales/doorkeeper.da.yml
index b0f50a8931e8209fd93e01d95064ab3e38768882..4d2c95affed6b3acf725cfe5c0e2c535f6dd742f 100644
--- a/config/locales/doorkeeper.da.yml
+++ b/config/locales/doorkeeper.da.yml
@@ -72,6 +72,7 @@ da:
       index:
         application: Applikation
         created_at: Godkendt
+        date_format: "%Y-%m-%d %H:%M:%S"
         scopes: Omfang
         title: Dine godkendte applikationer
     errors:
@@ -113,6 +114,12 @@ da:
       application:
         title: OAuth godkendelse påkrævet
     scopes:
+      admin:read: læs al 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:accounts: udfør modereringshandlinger på konti
+      admin:write:reports: udfør modereringshandlinger på anmeldelser
       follow: ændre din kontos forhold
       push: modtage dine push notifikationer
       read: læse alle din kontos data
diff --git a/config/locales/doorkeeper.el.yml b/config/locales/doorkeeper.el.yml
index c63688adeeef71868f704b45923f58db0878fc17..66bee0b0d835beec7beb3f38aa6951ff4712d08d 100644
--- a/config/locales/doorkeeper.el.yml
+++ b/config/locales/doorkeeper.el.yml
@@ -41,7 +41,7 @@ el:
         name: Όνομα
         new: Νέα εφαρμογή
         scopes: Εύρος εφαρμογής
-        show: Εμφάνισε
+        show: Εμφάνιση
         title: Οι εφαρμογές σου
       new:
         title: Νέα εφαρμογή
diff --git a/config/locales/doorkeeper.eo.yml b/config/locales/doorkeeper.eo.yml
index e80ba3236ae361737a0747bdd09cc9a7990ae76e..171e7d40464b46a4c9d6bba72490ecb8a1e834e5 100644
--- a/config/locales/doorkeeper.eo.yml
+++ b/config/locales/doorkeeper.eo.yml
@@ -114,6 +114,10 @@ eo:
       application:
         title: OAuth-a rajtigo bezonata
     scopes:
+      admin:read: legu ĉiujn datumojn en la servilo
+      admin:read:accounts: legas senteman informacion de ĉiuj kontoj
+      admin:read:reports: legas senteman informacion de ĉiuj raportoj kun raportis kontojn
+      admin:write: modifu ĉiujn datumojn en la servilo
       follow: ŝanĝi rilatojn al aliaj kontoj
       push: ricevi viajn puŝ-sciigojn
       read: legi ĉiujn datumojn de via konto
diff --git a/config/locales/doorkeeper.es-AR.yml b/config/locales/doorkeeper.es-AR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..515d5c1ed90e1b9725f1a1648a82ee512d0575ea
--- /dev/null
+++ b/config/locales/doorkeeper.es-AR.yml
@@ -0,0 +1 @@
+es-AR:
diff --git a/config/locales/doorkeeper.es.yml b/config/locales/doorkeeper.es.yml
index 752387d870d94ea205991cfbe72e0c1f7cb32b3f..1b03e33f2f086e82272a4829eab109ea89338920 100644
--- a/config/locales/doorkeeper.es.yml
+++ b/config/locales/doorkeeper.es.yml
@@ -114,7 +114,35 @@ es:
       application:
         title: OAuth autorización requerida
     scopes:
+      admin:read: leer todos los datos en el servidor
+      admin:read:accounts: leer información sensible de todas las cuentas
+      admin:read:reports: leer información sensible de todos los informes y cuentas reportadas
+      admin:write: modificar todos los datos en el servidor
+      admin:write:accounts: realizar acciones de moderación en cuentas
+      admin:write:reports: realizar acciones de moderación en informes
       follow: seguir, bloquear, desbloquear y dejar de seguir cuentas
+      push: recibir tus notificaciones push
       read: leer los datos de tu cuenta
+      read:accounts: ver información de cuentas
+      read:blocks: ver a quién has bloqueado
+      read:favourites: ver tus favoritos
+      read:filters: ver tus filtros
+      read:follows: ver a quién sigues
+      read:lists: ver tus listas
+      read:mutes: ver a quién has silenciado
+      read:notifications: ver tus notificaciones
+      read:reports: ver tus informes
+      read:search: buscar en su nombre
+      read:statuses: ver todos los estados
       write: publicar en tu nombre
+      write:accounts: modifica tu perfil
       write:blocks: bloquear cuentas y dominios
+      write:favourites: toots favoritos
+      write:filters: crear filtros
+      write:follows: seguir usuarios
+      write:lists: crear listas
+      write:media: subir archivos multimedia
+      write:mutes: silenciar usuarios y conversaciones
+      write:notifications: limpia tus notificaciones
+      write:reports: reportar a otras personas
+      write:statuses: publicar estados
diff --git a/config/locales/doorkeeper.et.yml b/config/locales/doorkeeper.et.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f2a33ad0a85f859399509b4ab66ab2599529a04a
--- /dev/null
+++ b/config/locales/doorkeeper.et.yml
@@ -0,0 +1,148 @@
+---
+et:
+  activerecord:
+    attributes:
+      doorkeeper/application:
+        name: Rakenduse nimi
+        redirect_uri: Suunamise URL
+        scopes: Ulatused
+        website: Rakenduse veebileht
+    errors:
+      models:
+        doorkeeper/application:
+          attributes:
+            redirect_uri:
+              fragment_present: ei tohi sisaldada fragmenti.
+              invalid_uri: peab olema õige URI.
+              relative_uri: peab olema absoluutne URI.
+              secured_uri: peab olema HTTPS/SSL URI.
+  doorkeeper:
+    applications:
+      buttons:
+        authorize: Autoriseeri
+        cancel: Katkesta
+        destroy: Hävita
+        edit: Redigeeri
+        submit: Saada
+      confirmations:
+        destroy: Olete kindel?
+      edit:
+        title: Redigeeri rakendust
+      form:
+        error: Oih! Kontrollige oma vormi võimalikest vigadest
+      help:
+        native_redirect_uri: Kasutage %{native_redirect_uri} kohalikeks testideks
+        redirect_uri: Kasuta ühte rida iga URI jaoks
+        scopes: Eralda ulatused tühikutega. Jäta tühjaks, et kasutada vaikimisi ulatusi.
+      index:
+        application: Rakendus
+        callback_url: Ümbersuunamise URL
+        delete: Kustuta
+        name: Nimi
+        new: Uus rakendus
+        scopes: Ulatused
+        show: Kuva
+        title: Teie rakendused
+      new:
+        title: Uus rakendus
+      show:
+        actions: Tegevused
+        application_id: Kliendi võti
+        callback_urls: Ümbersuunamise URLid
+        scopes: Ulatused
+        secret: Kliendi salasõna
+        title: 'Rakendus: %{name}'
+    authorizations:
+      buttons:
+        authorize: Autoriseeri
+        deny: Keeldu
+      error:
+        title: Ilmnes viga
+      new:
+        able_to: Sellel on võimalik
+        prompt: Rakendus %{client_name} soovib ligipääsu Teie kontole
+        title: Autoriseerimine vajalik
+      show:
+        title: Kopeeri see autoriseerimisvõti ja kleebi see rakendusse.
+    authorized_applications:
+      buttons:
+        revoke: Tühista
+      confirmations:
+        revoke: Olete kindel?
+      index:
+        application: Rakendus
+        created_at: Autoriseeritud
+        date_format: "%Y-%m-%d %H:%M:%S"
+        scopes: Ulatused
+        title: Autoriseeritud rakendused
+    errors:
+      messages:
+        access_denied: Ressursi omanik või autoriseerimisserver lükkas taotluse tagasi.
+        credential_flow_not_configured: Resource Owner Password Credentials vool ebaõnnestus, kuna Doorkeeper.configure.resource_owner_from_credentials ei ole seadistatud.
+        invalid_client: Kliendi autentimine ebaõnnestus, kuna tundmatu klient, puudulik autentimine või mitte toetatud autentimismeetod.
+        invalid_grant: Antud autoriseerimisluba on vale, aegunud, tagasi võetud, ei kattu kasutatud ümbersuunamise URLid või oli antud teisele kliendile.
+        invalid_redirect_uri: Antud ümbersuunamise URL ei ole õige.
+        invalid_request: Taotlusel puudub kohustuslik parameeter, sisaldab mitte toetatud parameetri väärtust või on kuidagi teisiti vale.
+        invalid_resource_owner: Antud ressursi omaniku andmed on valed või ressursi omanikku ei leitud
+        invalid_scope: Soovitud ulatus on vale, tundmatu või vigane.
+        invalid_token:
+          expired: Ligipääsuvõti on aegunud
+          revoked: Ligipääsuvõti on tagasi võetud
+          unknown: Ligipääsuvõti on vale
+        resource_owner_authenticator_not_configured: Ressursi omaniku leidmine ebaõnnestus, kuna Doorkeeper.configure.resource_owner_authenticator pole seadistatud.
+        server_error: Autoriseerimisserveril toimus ootamatu viga ning selle tulemusena ei läinud taotlus läbi.
+        temporarily_unavailable: Autoriseerimisserver ei ole hetkel võimeline taotlust vastu võtma, mis võib olla tingitud serveri ülekoormusest või hooldustöödest.
+        unauthorized_client: Klient ei ole lubatud seda taotlust vastu võtma kasutades seda meetodit.
+        unsupported_grant_type: Autoriseerimisloa tüüp ei ole toetatud antud serveri poolt.
+        unsupported_response_type: Autoriseerimisserver ei toeta seda vastuse tüüpi.
+    flash:
+      applications:
+        create:
+          notice: Rakendus loodud.
+        destroy:
+          notice: Rakendus kustutatud.
+        update:
+          notice: Rakendus uuendatud.
+      authorized_applications:
+        destroy:
+          notice: Rakendus tagasi lükatud.
+    layouts:
+      admin:
+        nav:
+          applications: Rakendused
+          oauth2_provider: OAuth2 pakkuja
+      application:
+        title: OAuth autoriseerimine vajalik
+    scopes:
+      admin:read: lugeda kõiki andmeid serveril
+      admin:read:accounts: lugeda privaatset informatsiooni kõikidel kontodel
+      admin:read:reports: lugeda privaatset informatsiooni kõikide teavituste ja teavitatud kasutajate kohta
+      admin:write: muuta kõiki andmeid serveril
+      admin:write:accounts: teostada moderaatori tegevusi kontodel
+      admin:write:reports: teostada moderaatori tegevusi teavitustel
+      follow: muuta kontode suhteid
+      push: saada Teie teateid
+      read: lugeda kogu Teie konto andmeid
+      read:accounts: näha konto informatsiooni
+      read:blocks: näha Teie blokeeringuid
+      read:favourites: näha Teie lemmikuid
+      read:filters: näha Teie filtreid
+      read:follows: näha Teie jälgimisi
+      read:lists: näha Teie nimekirju
+      read:mutes: näha Teie vaigistusi
+      read:notifications: näha Teie teateid
+      read:reports: näha Teie teavitusi
+      read:search: otsida Teie nimel
+      read:statuses: näha kõiki staatuseid
+      write: redigeerida kogu Teie konto andmeid
+      write:accounts: redigeerida Teie profiili
+      write:blocks: blokeerida kontosid ja domeene
+      write:favourites: lisada staatuseid lemmikuks
+      write:filters: luua filtreid
+      write:follows: jälgida inimesi
+      write:lists: luua nimekirju
+      write:media: üles laadida meediafaile
+      write:mutes: vaigista inimesi ja vestluseid
+      write:notifications: puhasta Teie teateid
+      write:reports: teavitada teistest inimestest
+      write:statuses: avaldada staatuseid
diff --git a/config/locales/doorkeeper.eu.yml b/config/locales/doorkeeper.eu.yml
index f98babae657e3d4160e2c6c65f193e5dcb4c3ee0..70e52e8ad90133319d265120ca203eb489720f05 100644
--- a/config/locales/doorkeeper.eu.yml
+++ b/config/locales/doorkeeper.eu.yml
@@ -5,7 +5,7 @@ eu:
       doorkeeper/application:
         name: Aplikazioaren izena
         redirect_uri: Birbideratu URIa
-        scopes: Esparruak
+        scopes: Irismena
         website: Aplikazioaren webgunea
     errors:
       models:
@@ -33,14 +33,14 @@ eu:
       help:
         native_redirect_uri: Erabili %{native_redirect_uri} proba lokaletarako
         redirect_uri: Erabili lerro bat URI bakoitzeko
-        scopes: Banandu esparruak espazioekin. Laga hutsik lehenetsitako esparruak erabiltzeko.
+        scopes: Banandu irismenak espazioekin. Laga hutsik lehenetsitako irismenak erabiltzeko.
       index:
         application: Aplikazioa
         callback_url: Itzulera URLa
         delete: Ezabatu
         name: Izena
         new: Aplikazio berria
-        scopes: Esparruak
+        scopes: Irismena
         show: Erakutsi
         title: Zure aplikazioak
       new:
@@ -49,7 +49,7 @@ eu:
         actions: Ekintzak
         application_id: Bezeroaren gakoa
         callback_urls: Itzulera URL-ak
-        scopes: Esparruak
+        scopes: Irismena
         secret: Bezeroaren sekretua
         title: 'Aplikazioa: %{name}'
     authorizations:
@@ -73,7 +73,7 @@ eu:
         application: Aplikazioa
         created_at: Baimenduta
         date_format: "%Y-%m-%d %H:%M:%S"
-        scopes: Esparruak
+        scopes: Irismena
         title: Zuk baimendutako aplikazioak
     errors:
       messages:
@@ -114,6 +114,12 @@ eu:
       application:
         title: OAuth autorizazioa behar da
     scopes:
+      admin:read: zerbitzariko datu guztiak irakurri
+      admin:read:accounts: kontu guztien informazio sentsiblea irakurri
+      admin:read:reports: salaketa guztietako eta salatutako kontu guztietako informazio sentsiblea irakurri
+      admin:write: zerbitzariko datu guztiak aldatu
+      admin:write:accounts: kontuetan moderazio ekintzak burutu
+      admin:write:reports: salaketetan moderazio ekintzak burutu
       follow: aldatu kontuaren erlazioak
       push: jaso push jakinarazpenak
       read: irakurri zure kontuko datu guztiak
diff --git a/config/locales/doorkeeper.fr.yml b/config/locales/doorkeeper.fr.yml
index 3525617680de88b67556d1215f8f57b93224c537..09908b89aab2de8ae764d5e71a35a66fac7d90d3 100644
--- a/config/locales/doorkeeper.fr.yml
+++ b/config/locales/doorkeeper.fr.yml
@@ -114,6 +114,12 @@ fr:
       application:
         title: Autorisation OAuth requise
     scopes:
+      admin:read: lire toutes les données du serveur
+      admin:read:accounts: lire les informations sensibles de tous les comptes
+      admin:read:reports: lire les informations sensibles de tous les signalements et des comptes signalés
+      admin:write: modifier toutes les données sur le serveur
+      admin:write:accounts: effectuer des actions de modération sur les comptes
+      admin:write:reports: effectuer des actions de modération sur les singnalements
       follow: modifier les relations avec les comptes
       push: recevoir vos notifications
       read: lire toutes les données de votre compte
diff --git a/config/locales/doorkeeper.hu.yml b/config/locales/doorkeeper.hu.yml
index 122392864d91feec13706834e5342c1ff90c7557..92b4e6839bb7a7d4284c80cecb6cdab42bd9c2bf 100644
--- a/config/locales/doorkeeper.hu.yml
+++ b/config/locales/doorkeeper.hu.yml
@@ -114,6 +114,12 @@ hu:
       application:
         title: OAuth engedély szükséges
     scopes:
+      admin:read: szerver minden adatának olvasása
+      admin:read:accounts: minden érzékeny fiókadat olvasása
+      admin:read:reports: minden bejelentés és bejelentett fiók érzékeny adatainak olvasása
+      admin:write: szerver minden adatának változtatása
+      admin:write:accounts: moderációs műveletek végzése fiókokon
+      admin:write:reports: moderációs műveletek végzése bejelentéseken
       follow: fiókok követése, letiltása, tiltás feloldása és követés abbahagyása
       push: push értesítések fogadása
       read: fiókod adatainak olvasása
diff --git a/config/locales/doorkeeper.id.yml b/config/locales/doorkeeper.id.yml
index 3f9dee2ac80173912c0bf3876c915d15385ad6a2..08d2470f054061d4bfbadb16e411809910af1bfe 100644
--- a/config/locales/doorkeeper.id.yml
+++ b/config/locales/doorkeeper.id.yml
@@ -62,6 +62,8 @@ id:
         able_to: Mempunyai akses untuk
         prompt: Aplikasi %{client_name} meminta akses pada akun anda
         title: Izin diperlukan
+      show:
+        title: Salin kode autorisasi dan tempelkan pada aplikasi.
     authorized_applications:
       buttons:
         revoke: Cabut izin
@@ -70,6 +72,7 @@ id:
       index:
         application: Aplikasi
         created_at: Diizinkan pada
+        date_format: "%d-%m-%Y %H:%M:%S"
         scopes: Scope
         title: Aplikasi yang anda izinkan
     errors:
@@ -111,6 +114,35 @@ id:
       application:
         title: Otorisasi OAuth diperlukan
     scopes:
+      admin:read: baca semua data di server
+      admin:read:accounts: baca informasi sensitif semua akun
+      admin:read:reports: baca informasi sensitif semua laporan dan akun terlapor
+      admin:write: ubah semua data di server
+      admin:write:accounts: lakukan aksi moderasi akun
+      admin:write:reports: lakukan aksi moderasi laporan
       follow: mengikuti, blokir, menghapus blokir, dan berhenti mengikuti akun
+      push: terima notifikasi dorong
       read: membaca data pada akun anda
+      read:accounts: lihat informasi akun
+      read:blocks: lihat blokiran Anda
+      read:favourites: lihat favorit Anda
+      read:filters: lihat saringan Anda
+      read:follows: lihat yang Anda ikuti
+      read:lists: lihat daftar Anda
+      read:mutes: lihat daftar bisu Anda
+      read:notifications: lihat notifikasi Anda
+      read:reports: lihat laporan Anda
+      read:search: cari atas nama Anda
+      read:statuses: lihat semua status
       write: memposting sebagai anda
+      write:accounts: ubah profil Anda
+      write:blocks: blokir akun dan domain
+      write:favourites: status favorit
+      write:filters: buat saringan
+      write:follows: ikuti orang
+      write:lists: buat daftar
+      write:media: unggah berkas media
+      write:mutes: bisukan orang dan percakapan
+      write:notifications: hapus notifikasi Anda
+      write:reports: laporkan orang lain
+      write:statuses: terbitkan status
diff --git a/config/locales/doorkeeper.ko.yml b/config/locales/doorkeeper.ko.yml
index 76e725debc8b0b17aa755c8ec9d9d809f50af470..8337325e24579045c9c8bd0ac7a5f89bccf983bc 100644
--- a/config/locales/doorkeeper.ko.yml
+++ b/config/locales/doorkeeper.ko.yml
@@ -78,12 +78,23 @@ ko:
     errors:
       messages:
         access_denied: 리소스 소유자 또는 권한 부여 서버가 요청을 거부했습니다.
+        credential_flow_not_configured: Doorkeeper.configure.resource_owner_from_credentials의 설정이 되어있지 않아 리소스 소유자 패스워드 자격증명이 실패하였습니다.
+        invalid_client: 알 수 없는 클라이언트이기 때문에 클라이언트 인증이 실패하였습니다, 클라이언트 자격증명이 포함되지 않았거나, 지원 되지 않는 메소드입니다.
+        invalid_grant: 제공된 권한 부여가 잘못되거나, 만료되었거나, 취소되었거나, 권한 부여 요청에 사용된 리디렉션 URI가 일치하지 않거나, 다른 클라이언트에 지정되었습니다.
         invalid_redirect_uri: 리디렉션 URI가 올바르지 않습니다
         invalid_request: 요청에 필요한 매개변수가 없거나, 지원 되지 않는 매개변수가 있거나, 형식이 잘못되었습니다.
+        invalid_resource_owner: 제공 된 리소스 소유자 자격증명이 올바르지 않거나 리소스 소유자를 찾을 수 없습니다
+        invalid_scope: 요청한 범위가 올바르지 않거나, 알 수 없거나, 잘못 된 형식입니다.
         invalid_token:
           expired: 액세스 토큰이 만료되었습니다.
           revoked: 액세스 토큰이 취소되었습니다.
           unknown: 액세스 토큰이 잘못되었습니다.
+        resource_owner_authenticator_not_configured: Doorkeeper.configure.resource_owner_authenticator가 설정되지 않아 리소스 소유자 찾기가 실패하였습니다.
+        server_error: 권한 부여 서버에 예기치 않은 상태가 발생하여, 요청을 수행할 수 없습니다.
+        temporarily_unavailable: 권한 부여 서버가 일시적인 과부하 또는 유지 관리로 인해 요청을 현재 처리할 수 없습니다.
+        unauthorized_client: 클라이언트가 이 방법을 사용하여 해당 요청을 실행하도록 허용되지 않았습니다.
+        unsupported_grant_type: 권한 부여 유형이 권한 부여 서버에 의해 지원되지 않습니다.
+        unsupported_response_type: 권한 부여 서버가 이 응답 유형을 지원하지 않습니다.
     flash:
       applications:
         create:
@@ -103,6 +114,12 @@ ko:
       application:
         title: OAuth 인증이 필요합니다
     scopes:
+      admin:read: 서버의 모든 데이터 읽기
+      admin:read:accounts: 모든 계정의 민감한 정보 읽기
+      admin:read:reports: 신고와 신고 된 계정의 민감한 정보 읽기
+      admin:write: 서버의 모든 데이터 수정
+      admin:write:accounts: 계정에 모더레이션 조치 취하기
+      admin:write:reports: 신고에 모더레이션 조치 취하기
       follow: 계정의 관계를 수정
       push: 푸시 알림을 받기
       read: 계정의 모든 데이터를 읽기
diff --git a/config/locales/doorkeeper.mk.yml b/config/locales/doorkeeper.mk.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8b9144a988941222eba3d4c61c19a62a8b057db4
--- /dev/null
+++ b/config/locales/doorkeeper.mk.yml
@@ -0,0 +1 @@
+mk:
diff --git a/config/locales/doorkeeper.nl.yml b/config/locales/doorkeeper.nl.yml
index aa37ea190ad38e4bfaef1bc39e46e812c98effd8..1fabfc1239276d6455c1411a18f2d24907950ca0 100644
--- a/config/locales/doorkeeper.nl.yml
+++ b/config/locales/doorkeeper.nl.yml
@@ -114,6 +114,12 @@ nl:
       application:
         title: OAuth-autorisatie vereist
     scopes:
+      admin:read: lees alle gegevens op de server
+      admin:read:accounts: lees gevoelige informatie van alle accounts
+      admin:read:reports: lees gevoelige informatie van alle rapportages en gerapporteerde accounts
+      admin:write: wijzig alle gegevens op de server
+      admin:write:accounts: moderatieacties op accounts uitvoeren
+      admin:write:reports: moderatieacties op rapportages uitvoeren
       follow: relaties tussen accounts bewerken
       push: ontvang jouw pushmeldingen
       read: alle gegevens van jouw account lezen
diff --git a/config/locales/doorkeeper.nn.yml b/config/locales/doorkeeper.nn.yml
new file mode 100644
index 0000000000000000000000000000000000000000..777f4e600f65daa6a550f2cf0f04551a49cf4fcd
--- /dev/null
+++ b/config/locales/doorkeeper.nn.yml
@@ -0,0 +1 @@
+nn:
diff --git a/config/locales/doorkeeper.oc.yml b/config/locales/doorkeeper.oc.yml
index d97c2f600e57ee91829b004411d248ec232ee904..e715cc7d52d8da36cab5a54f5a6b6eb40df0abfa 100644
--- a/config/locales/doorkeeper.oc.yml
+++ b/config/locales/doorkeeper.oc.yml
@@ -114,6 +114,12 @@ oc:
       application:
         title: Cal una autorizacion OAuth
     scopes:
+      admin:read: lectura de totas las donadas del servidor
+      admin:read:accounts: lectura de las informacions sensiblas dels comptes
+      admin:read:reports: lectura de las informacions sensiblas dels senhalaments e dels comptes senhalats
+      admin:write: modificacion de las donadas del servidor
+      admin:write:accounts: realizacion d’accions de moderacion suls comptes
+      admin:write:reports: realizacion d’accions suls senhalaments
       follow: modificar las relacions del compte
       push: recebre vòstras notificacions push
       read: legir totas las donadas de vòstre compte
diff --git a/config/locales/doorkeeper.pt.yml b/config/locales/doorkeeper.pt-PT.yml
similarity index 99%
rename from config/locales/doorkeeper.pt.yml
rename to config/locales/doorkeeper.pt-PT.yml
index f21e84d17f34338898398ed1641d966334b95850..42068e0a0b1b6b05fcf11a714b636afcb17c756a 100644
--- a/config/locales/doorkeeper.pt.yml
+++ b/config/locales/doorkeeper.pt-PT.yml
@@ -1,5 +1,5 @@
 ---
-pt:
+pt-PT:
   activerecord:
     attributes:
       doorkeeper/application:
diff --git a/config/locales/doorkeeper.ru.yml b/config/locales/doorkeeper.ru.yml
index ebe90a18939bd76e909f7087e426834ce7749d8b..bd493f793b9b2ff256cd46015018d0c54e776717 100644
--- a/config/locales/doorkeeper.ru.yml
+++ b/config/locales/doorkeeper.ru.yml
@@ -114,6 +114,12 @@ ru:
       application:
         title: Требуется авторизация OAuth
     scopes:
+      admin:read: читать все данные на сервере
+      admin:read:accounts: читать конфиденциальную информацию всех аккаунтов
+      admin:read:reports: читать конфиденциальную информацию о всех жалобах и аккаунтах с жалобами
+      admin:write: модифицировать все данные на сервере
+      admin:write:accounts: производить модерацию аккаунтов
+      admin:write:reports: производить модерацию жалоб
       follow: подписываться, отписываться, блокировать и разблокировать аккаунты
       push: принимать push-уведомления для Вашего аккаунта
       read: читать данные Вашего аккаунта
diff --git a/config/locales/doorkeeper.sk.yml b/config/locales/doorkeeper.sk.yml
index f54eb6d48dca1f3e22cf3b1b5746c8a15694e2fb..a08a386f2c247ef44fdd22fa72134cb99febf1c6 100644
--- a/config/locales/doorkeeper.sk.yml
+++ b/config/locales/doorkeeper.sk.yml
@@ -19,34 +19,34 @@ sk:
   doorkeeper:
     applications:
       buttons:
-        authorize: Overiť
-        cancel: Zrušiť
+        authorize: Autorizuj
+        cancel: Zruš
         destroy: Zničiť
-        edit: Upraviť
-        submit: Poslať
+        edit: Uprav
+        submit: Pošli
       confirmations:
         destroy: Si si istý/á?
       edit:
-        title: Upraviť aplikáciu
+        title: Uprav aplikáciu
       form:
-        error: No teda! Pozrite formulár pre prípadné chyby
+        error: No teda! Skontroluj formulár pre prípadné chyby
       help:
-        native_redirect_uri: Použite %{native_redirect_uri} pre lokálne testy
-        redirect_uri: Iba jedna URI na riadok
-        scopes: Oprávnenia oddeľujte medzerami. Nechajte prázdne pre štandardné oprávnenia.
+        native_redirect_uri: Použi %{native_redirect_uri} pre lokálne testy
+        redirect_uri: Použi jeden riadok pre každú URI
+        scopes: Oprávnenia oddeľuj medzerami. Nechaj prázdne pre štandardné oprávnenia.
       index:
         application: Aplikácia
         callback_url: Návratová URL
-        delete: Zmazať
+        delete: Vymaž
         name: Názov
         new: Nová aplikácia
         scopes: Oprávnenia
-        show: Ukázať
-        title: Vaše aplikácie
+        show: Ukáž
+        title: Tvoje aplikácie
       new:
         title: Nová aplikácia
       show:
-        actions: Akcie
+        actions: Úkony
         application_id: Kľúč klienta
         callback_urls: Návratové URL adresy
         scopes: Oprávnenia
@@ -54,7 +54,7 @@ sk:
         title: 'Aplikácia: %{name}'
     authorizations:
       buttons:
-        authorize: Overiť
+        authorize: Over
         deny: Zamietni
       error:
         title: Nastala chyba
@@ -91,7 +91,7 @@ sk:
         resource_owner_authenticator_not_configured: Resource Owner zlyhal pretože Doorkeeper.configure.resource_owner_authenticator nebol nakonfigurovaný.
         server_error: Nastala neočakávaná chyba na autorizačnom serveri ktorá zabránila vykonať požiadavku.
         temporarily_unavailable: Autorizačný server ťa teraz nemôže obslúžiť, pretože prebieha údržba alebo je dočasne preťažený.
-        unauthorized_client: Klient nie je autorizovaný vykonať danú požiadavku takouto metódou.
+        unauthorized_client: Klient nie je autorizovaný vykonať danú požiadavku týmto spôsobom.
         unsupported_grant_type: Tento typ oprávnenia nie je podporovaný autorizačným serverom.
         unsupported_response_type: Autorizačný server nepodporuje typ tejto odpovede.
     flash:
@@ -113,6 +113,12 @@ sk:
       application:
         title: Požadovaná OAuth autorizácia
     scopes:
+      admin:read: prezeraj všetky dáta na serveri
+      admin:read:accounts: prezeraj chúlostivé informácie na všetkých účtoch
+      admin:read:reports: čítaj chulostivé informácie o všetkých hláseniach a nahlásených účtoch
+      admin:write: uprav všetky dáta na serveri
+      admin:write:accounts: urob moderovacie úkony na účtoch
+      admin:write:reports: urob moderovacie úkony voči hláseniam
       follow: uprav vzťahy svojho účtu
       push: dostávaj oboznámenia ohľadom tvojho účtu na obrazovku
       read: prezri si všetky dáta ohľadom svojho účetu
diff --git a/config/locales/doorkeeper.sv.yml b/config/locales/doorkeeper.sv.yml
index 4fd246eff2fb3b3375e9aca13d4e5502fb90d7fb..47b11b7fde8ae6937efa0e4ab559eb06be8e8094 100644
--- a/config/locales/doorkeeper.sv.yml
+++ b/config/locales/doorkeeper.sv.yml
@@ -113,7 +113,35 @@ sv:
       application:
         title: OAuth-behörighet krävs
     scopes:
+      admin:read: läs all data på servern
+      admin:read:accounts: läs känslig information från alla konton
+      admin:read:reports: läs känslig information från alla rapporter och rapporterade konton
+      admin:write: ändra all data på servern
+      admin:write:accounts: utför alla aktiviteter för moderering på konton
+      admin:write:reports: utför alla aktiviteter för moderering i rapporter
       follow: följa, blockera, ta bort blockerade och sluta följa konton
       push: ta emot push aviseringar för ditt konto
       read: läsa dina kontodata
+      read:accounts: se kontoinformation
+      read:blocks: se dina block
+      read:favourites: se dina favoriter
+      read:filters: se dina filter
+      read:follows: se vem du följer
+      read:lists: se dina listor
+      read:mutes: se dina tystningar
+      read:notifications: se dina notifieringar
+      read:reports: se dina rapporter
+      read:search: sök å dina vägnar
+      read:statuses: se alla statusar
       write: posta åt dig
+      write:accounts: ändra din profil
+      write:blocks: blockera konton och domäner
+      write:favourites: favoritmarkera statusar
+      write:filters: skapa filter
+      write:follows: följ människor
+      write:lists: skapa listor
+      write:media: ladda upp mediafiler
+      write:mutes: tysta människor och konversationer
+      write:notifications: rensa dina notifieringar
+      write:reports: rapportera andra människor
+      write:statuses: publicera statusar
diff --git a/config/locales/doorkeeper.tr.yml b/config/locales/doorkeeper.tr.yml
index 686a018e0a6a6355dc8b4ae02347ab199e21dc77..120689a3a8009fe82e023bb6dbe2f4d8383a13bb 100644
--- a/config/locales/doorkeeper.tr.yml
+++ b/config/locales/doorkeeper.tr.yml
@@ -4,7 +4,18 @@ tr:
     attributes:
       doorkeeper/application:
         name: Uygulama adı
+        redirect_uri: Yönlendirme URI'si
+        scopes: Kapsamlar
         website: Uygulama web sitesi
+    errors:
+      models:
+        doorkeeper/application:
+          attributes:
+            redirect_uri:
+              fragment_present: parça içeremez.
+              invalid_uri: geçerli bir URI olmalıdır.
+              relative_uri: mutlak bir URI olmalıdır.
+              secured_uri: HTTPS/SSL URI olması gerekir.
   doorkeeper:
     applications:
       buttons:
@@ -17,3 +28,121 @@ tr:
         destroy: Emin misiniz?
       edit:
         title: Uygulamayı düzenle
+      form:
+        error: Tüh! Muhtemel hatalar için formunuzu kontrol edin
+      help:
+        native_redirect_uri: Yerel testler için %{native_redirect_uri} kullanın
+        redirect_uri: URl başına bir satır kullanın
+        scopes: Kapsamları boşluklarla ayırın. Varsayılan kapsamları kullanmak için boş bırakın.
+      index:
+        application: Uygulama
+        callback_url: Geri Dönüş URL
+        delete: Sil
+        name: İsim
+        new: Yeni uygulama
+        scopes: Kapsam
+        show: Göster
+        title: Uygulamalarınız
+      new:
+        title: Yeni uygulama
+      show:
+        actions: Eylemler
+        application_id: İstemci anahtarı
+        callback_urls: Callback URL'si
+        scopes: Kapsamlar
+        secret: İstemci anahtarı
+        title: 'Uygulama: %{name}'
+    authorizations:
+      buttons:
+        authorize: Yetkilendir
+        deny: Reddet
+      error:
+        title: Bir hata oluÅŸtu
+      new:
+        able_to: Şunları yapabilecek
+        prompt: "%{client_name} uygulaması hesabınıza erişim istiyor"
+        title: Yetkilendirme gerekli
+      show:
+        title: Bu yetki kodunu kopyalayın ve uygulamaya yapıştırın.
+    authorized_applications:
+      buttons:
+        revoke: İptal
+      confirmations:
+        revoke: Emin misiniz?
+      index:
+        application: Uygulama
+        created_at: Yetkili
+        date_format: "%Y-%m-%d %H:%M:%S"
+        scopes: Kapsamlar
+        title: Yetkili uygulamalarınız
+    errors:
+      messages:
+        access_denied: Kaynak sahibi veya yetkilendirme sunucusu isteÄŸi reddetti.
+        credential_flow_not_configured: Kaynak Sahibi Şifresinin Bilgi akışı Doorkeeper.configure.resource_owner_from_credentials bilgilerinin yapılandırılmamış olması nedeniyle başarısız oldu.
+        invalid_client: İstemcinin kimlik doğrulaması bilinmeyen istemci, istemci kimlik doğrulamasının dahil olmaması veya desteklenmeyen kimlik doğrulama yöntemi nedeniyle başarısız oldu.
+        invalid_grant: Sağlanan yetkilendirme izni geçersiz, süresi dolmuş, iptal edilmiş, yetkilendirme isteğinde kullanılan yönlendirme URI'siyle eşleşmiyor veya başka bir müşteriye verilmiş.
+        invalid_redirect_uri: Dahil edilmiş yönlendirme Uri'si geçersiz.
+        invalid_request: İstekte gerekli bir parametre eksik, desteklenmeyen bir parametre değeri içeriyor veya başka türlü hatalı biçimlendirilmiş.
+        invalid_resource_owner: Sağlanan kaynak sahibi kimlik bilgileri geçerli değil veya kaynak sahibi bulunamıyor
+        invalid_scope: İstenen kapsam geçersiz, bilinmeyen veya hatalı biçimlendirilmiş olabilir.
+        invalid_token:
+          expired: Erişim belirtecinin süresi dolmuş
+          revoked: EriÅŸim belirteci iptal edildi
+          unknown: Erişim belirteci geçersiz
+        resource_owner_authenticator_not_configured: Kaynak Sahibi yapılandırılmamış Doorkeeper.configure.resource_owner_authenticator nedeniyle başarısız oldu.
+        server_error: Yetkilendirme sunucusu, isteği yerine getirmesini engelleyen beklenmeyen bir koşulla karşılaştı.
+        temporarily_unavailable: Yetkilendirme sunucusu şu anda sunucunun geçici bir aşırı yüklenmesi veya bakımı nedeniyle isteği yerine getiremiyor.
+        unauthorized_client: İstemci bu yöntemi kullanarak bu isteği gerçekleştirmek için yetkili değil.
+        unsupported_grant_type: Yetkilendirme izni türü, yetkilendirme sunucusu tarafından desteklenmiyor.
+        unsupported_response_type: Yetkilendirme sunucusu bu yanıt türünü desteklemiyor.
+    flash:
+      applications:
+        create:
+          notice: Uygulama oluÅŸturuldu.
+        destroy:
+          notice: Uygulama silindi.
+        update:
+          notice: Uygulama güncellendi.
+      authorized_applications:
+        destroy:
+          notice: Uygulama iptal edildi.
+    layouts:
+      admin:
+        nav:
+          applications: Uygulamalar
+          oauth2_provider: OAuth2 Sağlayıcısı
+      application:
+        title: OAuth yetkilendirme gerekli
+    scopes:
+      admin:read: sunucudaki tüm verileri oku
+      admin:read:accounts: tüm hesapların hassas bilgilerini oku
+      admin:read:reports: tüm raporların ve raporlanan hesapların hassas bilgilerini oku
+      admin:write: sunucudaki tüm verileri değiştirin
+      admin:write:accounts: hesaplar üzerinde denetleme eylemleri gerçekleştirin
+      admin:write:reports: raporlar üzerinde denetleme eylemleri gerçekleştirin
+      follow: hesap iliÅŸkilerini deÄŸiÅŸtirin
+      push: anlık bildirimlerizi alın
+      read: hesabınızın tüm verilerini okuyun
+      read:accounts: hesap bilgilerini gör
+      read:blocks: engellemelerinizi görün
+      read:favourites: favorilerini gör
+      read:filters: filtrelerinizi görün
+      read:follows: izlerini gör
+      read:lists: listelerinizi görün
+      read:mutes: sessize aldıklarınızı görün
+      read:notifications: bildirimlerinizi görün
+      read:reports: şikayetlerinizi görün
+      read:search: kendi adınıza arayın
+      read:statuses: tüm durumları görün
+      write: hesabınızın tüm verilerini değiştirin
+      write:accounts: profilini deÄŸiÅŸtir
+      write:blocks: hesapları ve alan adlarını engelleyin
+      write:favourites: favori durumlar
+      write:filters: filtre oluÅŸtur
+      write:follows: insanları takip et
+      write:lists: liste oluÅŸtur
+      write:media: medya dosyalarını yükle
+      write:mutes: insanları ve konuşmaları sustur
+      write:notifications: bildirimlerinizi temizleyin
+      write:reports: diğer insanları bildir
+      write:statuses: durumları yayınlayın
diff --git a/config/locales/doorkeeper.uk.yml b/config/locales/doorkeeper.uk.yml
index 305a5c1d6f53052587c4fe157fbf4272dc3ed715..6e5dc1e426ac34094233d8a297fcb8da263f0850 100644
--- a/config/locales/doorkeeper.uk.yml
+++ b/config/locales/doorkeeper.uk.yml
@@ -3,7 +3,7 @@ uk:
   activerecord:
     attributes:
       doorkeeper/application:
-        name: Ім'я
+        name: Назва додатку
         redirect_uri: URI перенаправлення
         scopes: Рамки
         website: Веб-сайт додатку
@@ -13,9 +13,9 @@ uk:
           attributes:
             redirect_uri:
               fragment_present: не може містити фрагмент.
-              invalid_uri: повинен бути дійсним URI.
-              relative_uri: повинен бути абсолютним URI.
-              secured_uri: повинен бути HTTPS/SSL URI.
+              invalid_uri: має бути дійсним URI.
+              relative_uri: має бути абсолютним URI.
+              secured_uri: має бути URI HTTPS/SSL.
   doorkeeper:
     applications:
       buttons:
@@ -35,9 +35,13 @@ uk:
         redirect_uri: Використовуйте одну стрічку на URI
         scopes: Відділяйте області видимості пробілами. Залишайте порожніми, щоб використовувати області видимості за промовчуванням.
       index:
+        application: Додаток
         callback_url: URL зворотнього виклику
+        delete: Видалити
         name: Назва
         new: Новий додаток
+        scopes: Області видимості
+        show: Показати
         title: Ваші додатки
       new:
         title: Новий додаток
@@ -56,8 +60,10 @@ uk:
         title: Сталася помилка
       new:
         able_to: Він зможе
-        prompt: Податок %{client_name} просить доступу до вашого акаунту
+        prompt: Додаток %{client_name} запитує доступ до вашого облікового запису
         title: Необхідна авторизація
+      show:
+        title: Скопіюйте цей код авторизації та вставте його у додаток.
     authorized_applications:
       buttons:
         revoke: Відкликати авторизацію
@@ -78,7 +84,7 @@ uk:
         invalid_redirect_uri: Включений URI перенаправлення не є дійсним.
         invalid_request: У запиті відсутній обов'язковий параметр, міститься непідтримуване значення параметра, або він сформований неправильно.
         invalid_resource_owner: Надані дані власника ресурсу не є дійсними, або власника ресурсу неможливо знайти
-        invalid_scope: Запитуваний дозвыд недійсний, невідомий, або неправильно сформований.
+        invalid_scope: Запитуваний дозвіл недійсний, невідомий, або неправильно сформований.
         invalid_token:
           expired: Токен доступу прострочено
           revoked: Токен доступу було відкликано
@@ -108,6 +114,34 @@ uk:
       application:
         title: Необхідна авторизація OAuth
     scopes:
-      follow: підписуватися, відписуватися, блокувати та розблоковувати акаунти
-      read: Читати дані Вашого акаунта
-      write: Публікувати від Вашого імені
+      admin:read: читати всі дані на сервері
+      admin:read:accounts: читати конфіденційну інформацію усіх акаунтів
+      admin:read:reports: читати дражливу інформацію усіх скарг та облікових записів зі скаргами
+      admin:write: модифікувати всі дані на сервері
+      admin:write:accounts: модерувати облікові записи
+      admin:write:reports: модерувати скарги
+      follow: змінювати стосунки облікового запису
+      push: отримувати Ваші Push-повідомлення
+      read: читати усі дані вашого облікового запису
+      read:accounts: бачити інформацію про облікові записи
+      read:blocks: бачити Ваші блокування
+      read:favourites: бачити Ваші вподобані пости
+      read:filters: бачити Ваші фільтри
+      read:follows: бачити Ваші підписки
+      read:lists: бачити Ваші списки
+      read:mutes: бачити ваші заглушення
+      read:notifications: бачити Ваші сповіщення
+      read:reports: бачити Ваші скарги
+      read:statuses: бачити всі статуси
+      write: змінювати усі дані вашого облікового запису
+      write:accounts: змінювати ваш профіль
+      write:blocks: блокувати облікові записи і домени
+      write:favourites: вподобані статуси
+      write:filters: створювати фільтри
+      write:follows: підписуйтесь на людей
+      write:lists: створювайте списки
+      write:media: завантажити медіафайли
+      write:mutes: заглушити людей або бесіди
+      write:notifications: очищувати Ваші сповіщення
+      write:reports: надіслати скаргу про людей
+      write:statuses: публікувати статуси
diff --git a/config/locales/doorkeeper.zh-CN.yml b/config/locales/doorkeeper.zh-CN.yml
index dd9337904acc1017dee5e19b7dc11a8b6f9292db..015d2c0ce1c9eb92ba89c488a3fa74089f7c9e07 100644
--- a/config/locales/doorkeeper.zh-CN.yml
+++ b/config/locales/doorkeeper.zh-CN.yml
@@ -72,6 +72,7 @@ zh-CN:
       index:
         application: 应用
         created_at: 授权时间
+        date_format: "%H:%M:%S"
         scopes: 权限范围
         title: 已授权的应用列表
     errors:
diff --git a/config/locales/doorkeeper.zh-TW.yml b/config/locales/doorkeeper.zh-TW.yml
index 41dd17264072aa3d507391c25d6f9a3722dc034a..0b2624aa9582b3bbf620e746c0bf0d11fa5e6a1d 100644
--- a/config/locales/doorkeeper.zh-TW.yml
+++ b/config/locales/doorkeeper.zh-TW.yml
@@ -3,9 +3,9 @@ zh-TW:
   activerecord:
     attributes:
       doorkeeper/application:
-        name: 名稱
+        name: 應用程式名稱
         redirect_uri: 重新導向 URI
-        scopes: 權限範圍
+        scopes: 範圍
         website: 應用程式網頁
     errors:
       models:
@@ -13,9 +13,9 @@ zh-TW:
           attributes:
             redirect_uri:
               fragment_present: 不能包含 fragment。
-              invalid_uri: 必需有正確的 URI.
-              relative_uri: 必需為絕對 URI.
-              secured_uri: 必需使用有 HTTPS/SSL 加密的 URI.
+              invalid_uri: 必須是正確的 URI。
+              relative_uri: 必須為絕對 URI。
+              secured_uri: 必須是 HTTPS/SSL URI。
   doorkeeper:
     applications:
       buttons:
@@ -29,29 +29,29 @@ zh-TW:
       edit:
         title: 編輯應用程式
       form:
-        error: 唉呦!請檢查表單錯誤訊息
+        error: 唉呦!請看看表單以排查錯誤
       help:
-        native_redirect_uri: 請使用 %{native_redirect_uri} 作測試
+        native_redirect_uri: 請使用 %{native_redirect_uri} 作本機測試
         redirect_uri: 每行輸入一個 URI
-        scopes: 請用半形空格分開權限範圍 (scope)。留空表示使用預設的權限範圍。
+        scopes: 請用半形空格分開範圍。空白表示使用預設的範圍。
       index:
         application: 應用程式
         callback_url: 回傳網址
         delete: 刪除
         name: 名稱
         new: 新增應用程式
-        scopes: 權限範圍
+        scopes: 範圍
         show: 顯示
         title: 你的應用程式
       new:
         title: 新增應用程式
       show:
         actions: 動作
-        application_id: 應用程式 ID
+        application_id: 客戶端金鑰
         callback_urls: 回傳網址
-        scopes: 權限範圍
-        secret: 密碼
-        title: 應用程式︰ %{name}
+        scopes: 範圍
+        secret: 客戶端密碼
+        title: 應用程式︰%{name}
     authorizations:
       buttons:
         authorize: 授權
@@ -59,61 +59,90 @@ zh-TW:
       error:
         title: 發生錯誤
       new:
-        able_to: 要求取得權限
-        prompt: 應用程式 %{client_name} 要求取得您帳號的部份權限
+        able_to: 這將允許其作:
+        prompt: 應用程式 %{client_name} 要求取得您帳號的存取權限
         title: 需要授權
       show:
         title: 複製此授權碼並貼上到應用程式中。
     authorized_applications:
       buttons:
-        revoke: 撤銷授權
+        revoke: 撤銷
       confirmations:
-        revoke: 您確定要撤銷這個授權?
+        revoke: 確定撤銷?
       index:
         application: 應用程式
-        created_at: 授權時間
-        scopes: 權限範圍
+        created_at: 授權於
+        date_format: "%Y-%m-%d %H:%M:%S"
+        scopes: 範圍
         title: 已授權的應用程式
     errors:
       messages:
-        access_denied: 資源擁有者或認證伺服器不接受請求。
-        credential_flow_not_configured: 資源擁有者密碼認證程序失敗,由於 Doorkeeper.configure.resource_owner_from_credentials 沒有設定。
-        invalid_client: 客戶端驗證失敗,可能是未知的客戶端程式、未包含客戶端驗證、或使用了不支援的認證方法。
+        access_denied: 資源持有者或授權伺服器拒絕請求。
+        credential_flow_not_configured: 因為 Doorkeeper.configure.resource_owner_from_credentials 未設定,所以資源持有者密碼認證程序失敗。
+        invalid_client: 客戶端驗證失敗,可能是因為未知的客戶端程式、未包含客戶端驗證、或使用了不支援的認證方法。
         invalid_grant: 授權申請不正確、逾期、已被取消、與授權請求內的重新導向 URI 不符、或屬於別的客戶端程式。
-        invalid_redirect_uri: 不正確的重新導向網址。
-        invalid_request: 請求缺少必要的參數、包含不支援的參數、或其他輸入錯誤。
-        invalid_resource_owner: 資源擁有者的登入資訊錯誤、或無法找到該資源擁有者
-        invalid_scope: 請求的權限範圍無效、未定義、或輸入錯誤。
+        invalid_redirect_uri: 包含的重新導向 URI 是不正確的。
+        invalid_request: 請求缺少必要的參數、有不支援的參數、或其他格式錯誤。
+        invalid_resource_owner: 資源擁有者的登入資訊錯誤,或無法找到該資源擁有者
+        invalid_scope: 請求的範圍錯誤、未定義、或格式錯誤。
         invalid_token:
-          expired: access token 已過期
-          revoked: access token 已被取消
-          unknown: access token 不正確
-        resource_owner_authenticator_not_configured: 無法找到資源擁有者,由於 Doorkeeper.configure.resource_owner_authenticator 沒有設定。
+          expired: 存取憑證已過期
+          revoked: 存取憑證已撤銷
+          unknown: 存取憑證不正確
+        resource_owner_authenticator_not_configured: 因為未設定 Doorkeeper.configure.resource_owner_authenticator,所以資源持有者尋找失敗。
         server_error: 認證伺服器發生未知錯誤。
         temporarily_unavailable: 認證伺服器暫時無法使用。
-        unauthorized_client: 客戶端程式無權使用此方法進行請求。
+        unauthorized_client: 客戶端程式沒有權限使用此方法請求。
         unsupported_grant_type: 認證伺服器不支援這個授權類型。
         unsupported_response_type: 認證伺服器不支援這個回應類型。
     flash:
       applications:
         create:
-          notice: 已新增應用程式。
+          notice: 已建立應用程式。
         destroy:
           notice: 已刪除應用程式。
         update:
           notice: 已更新應用程式。
       authorized_applications:
         destroy:
-          notice: 已撤銷應用程式授權。
+          notice: 已撤銷應用程式。
     layouts:
       admin:
         nav:
           applications: 應用程式
-          oauth2_provider: OAuth2 供應者
+          oauth2_provider: OAuth2 提供者
       application:
         title: 需要 OAuth 授權
     scopes:
-      follow: 關注、封鎖、解除封鎖及取消關注帳號
-      push: 接收你帳號的推送通知
-      read: 讀取您的帳號資料
-      write: 以您的名義發佈嘟文
+      admin:read: 讀取伺服器的所有資料
+      admin:read:accounts: 讀取所有帳戶的敏感資訊
+      admin:read:reports: 讀取所有回報 / 被回報之帳戶的敏感資訊
+      admin:write: 修改伺服器的所有資料
+      admin:write:accounts: 對帳戶進行仲裁管理動作
+      admin:write:reports: 對報告進行仲裁管理動作
+      follow: 修改帳戶關係
+      push: 接收帳號的推送通知
+      read: 讀取您所有的帳號資料
+      read:accounts: 檢視帳戶資訊
+      read:blocks: 檢視您的封鎖名單
+      read:favourites: 檢視您的收藏項目
+      read:filters: 檢視您的過濾條件
+      read:follows: 檢視您關注的人
+      read:lists: 檢視您的名單
+      read:mutes: 檢視您靜音的人
+      read:notifications: 檢視您的通知
+      read:reports: 檢視您的檢舉
+      read:search: 以你的身份搜尋
+      read:statuses: 檢視所有嘟文
+      write: 修改您帳號的所有資料
+      write:accounts: 修改您的個人檔案
+      write:blocks: 封鎖帳戶及站台
+      write:favourites: 收藏嘟文
+      write:filters: 建立過濾條件
+      write:follows: 關注其他人
+      write:lists: 建立名單
+      write:media: 上傳媒體檔案
+      write:mutes: 靜音使用者及對話
+      write:notifications: 清除您的通知
+      write:reports: 檢舉其他人
+      write:statuses: 發布嘟文
diff --git a/config/locales/el.yml b/config/locales/el.yml
index a08ec71416f5f88c0cc0faf0250cfb45ed858d7c..caaab41f870641bb899fb62f53e4051e27758fb2 100644
--- a/config/locales/el.yml
+++ b/config/locales/el.yml
@@ -10,20 +10,19 @@ el:
     api: API
     apps: Εφαρμογές κινητών
     apps_platforms: Χρησιμοποίησε το Mastodon από το iOS, το Android και αλλού
-    browse_directory: Ξεφύλλισε ένα κατάλογο χρηστών και φίλτραρε ανά ενδιαφέροντα
-    browse_public_posts: Κοίταξε μια ζωντανή ροή δημοσιεύσεων στο Mastodon
+    browse_directory: Ξεφύλλισε τον κατάλογο χρηστών και ψάξε ανά ενδιαφέροντα
+    browse_public_posts: Ξεφύλλισε τη ζωντανή ροή του Mastodon
     contact: Επικοινωνία
     contact_missing: Δεν έχει οριστεί
     contact_unavailable: Μ/Δ
     discover_users: Ανακάλυψε χρήστες
     documentation: Τεκμηρίωση
-    extended_description_html: |
-      <h3>Ένα καλό σημείο για κανόνες</h3>
-      <p>Η αναλυτική περιγραφή δεν έχει ακόμα οριστεί</p>
     federation_hint_html: Με ένα λογαριασμό στο %{instance} θα μπορείς να ακολουθείς ανθρώπους σε οποιοδήποτε κόμβο στο Mastodon αλλά και αλλού.
-    generic_description: "%{domain} είναι ένας εξυπηρετητής στο δίκτυο"
     get_apps: Δοκίμασε μια εφαρμογή κινητού
     hosted_on: Το Mastodon φιλοξενείται στο %{domain}
+    instance_actor_flash: |
+      Αυτός ο λογαριασμός είναι εικονικός και απεικονίζει τον κόμβο, όχι κάποιο συγκεκριμένο χρήστη.
+      Χρησιμεύει στη λειτουργία της ομοσπονδίας και δε θα πρέπει να αποκλειστεί, εκτός κι αν είναι επιθυμητός ο αποκλεισμός ολόκληρου του κόμβου. Σε αυτή την περίπτωση θα πρέπει να χρησιμοποιηθεί η λειτουργία αποκλεισμού τομέα.
     learn_more: Μάθε περισσότερα
     privacy_policy: Πολιτική απορρήτου
     see_whats_happening: Μάθε τι συμβαίνει
@@ -35,6 +34,14 @@ el:
     status_count_before: Που έγραψαν
     tagline: Ακολούθησε τους γνωστούς σου και ανακάλυψε νέους ανθρώπους
     terms: Όροι χρήσης
+    unavailable_content: Μη διαθέσιμο
+    unavailable_content_description:
+      domain: Διακομιστής
+      reason: 'Αιτία:'
+      rejecting_media: 'Τα αρχεία πολυμέσων αυτών των διακομιστών δεν θα επεξεργάζονται, δεν θα αποθηκεύονται και δεν θα εμφανίζεται η προεπισκόπησή τους, απαιτώντας χειροκίνητη επιλογή μέχρι το αρχικό αρχείο:'
+      silenced: 'Οι δημοσιεύσεις αυτών των διακομιστών θα είναι κρυμμένες από τις δημόσιες ροές και συζητήσεις, ενώ δεν θα δημιουργούνται ειδοποιήσεις για τις ενέργειες των χρηστών τους, εκτός κι αν τους ακολουθείς:'
+      suspended: 'Κανένα δεδομένο δε θα επεξεργάζεται, δε θα αποθηκεύεται και δε θα ανταλλάσσεται για αυτούς τους διακομιστές, καθιστώντας οποιαδήποτε αλληλεπίδραση ή επικοινωνία με χρήστες από αυτούς τους διακομιστές αδύνατη:'
+    unavailable_content_html: Το Mastodon γενικά επιτρέπει να δεις περιεχόμενο και να αλληλεπιδράσεις με χρήστες από οποιονδήποτε διακομιστή στο fediverse. Εδώ είναι οι εξαιρέσεις που ισχύουν σε αυτόν τον συγκεκριμένο διακομιστή.
     user_count_after:
       one: χρήστης
       other: χρήστες
@@ -42,6 +49,8 @@ el:
     what_is_mastodon: Τι είναι το Mastodon;
   accounts:
     choices_html: 'Επιλογές από %{name}:'
+    endorsements_hint: Μπορεις να εγκρίνεις ανθρώπους που ακολουθείς μέσω της δικτυακής εφαρμογής και αυτοί θα εμφανίζονται εδώ.
+    featured_tags_hint: Μπορείς να επιλέξεις συγκεκριμένες ταμπέλες που θα εμφανίζονται εδώ.
     follow: Ακολούθησε
     followers:
       one: Ακόλουθος
@@ -53,6 +62,7 @@ el:
     media: Πολυμέσα
     moved_html: 'Ο/Η %{name} μετακόμισε στο %{new_profile_link}:'
     network_hidden: Αυτή η πληροφορία δεν είναι διαθέσιμη
+    never_active: Ποτέ
     nothing_here: Δεν υπάρχει τίποτα εδώ!
     people_followed_by: Χρήστες που ακολουθεί ο/η %{name}
     people_who_follow: Χρήστες που ακολουθούν τον/την %{name}
@@ -183,6 +193,7 @@ el:
       username: Όνομα χρήστη
       warn: Προειδοποίηση
       web: Διαδίκτυο
+      whitelisted: Εγκεκριμένοι
     action_logs:
       actions:
         assigned_to_self_report: Ο/Η %{name} ανάθεσε την καταγγελία %{target} στον εαυτό του/της
@@ -218,19 +229,24 @@ el:
       deleted_status: "(διαγραμμένη δημοσίευση)"
       title: Αρχείο ελέγχου
     custom_emojis:
+      assign_category: Κατηγορία
       by_domain: Τομέας
       copied_msg: Επιτυχής δημιουργία τοπικού αντίγραφου του emoji
       copy: Αντιγραφή
       copy_failed_msg: Αδυναμία δημιουργίας τοπικού αντίγραφου αυτού του emoji
+      create_new_category: Νέα κατηγορία
       created_msg: Επιτυχής δημιουργία του emoji!
       delete: Διαγραφή
       destroyed_msg: Επιτυχής καταστροφή του emojo!
       disable: Απενεργοποίηση
+      disabled: Απενεργοποιημένα
       disabled_msg: Επιτυχής απενεργοποίηση αυτού του emoji
       emoji: Emoji
       enable: Ενεργοποίηση
+      enabled: Ενεργοποιημένα
       enabled_msg: Επιτυχής ενεργοποίηση αυτού του emoji
       image_hint: PNG έως 50KB
+      list: Εμφάνιση
       listed: Αναφερθέντα
       new:
         title: Προσθήκη νέου προσαρμοσμένου emoji
@@ -238,11 +254,14 @@ el:
       shortcode: Σύντομος κωδικός
       shortcode_hint: Τουλάχιστον 2 χαρακτήρες, μόνο αλφαριθμητικοί και κάτω παύλες
       title: Προσαρμοσμένα emoji
+      uncategorized: Χωρίς κατηγορία
+      unlist: Απόκρυψη
       unlisted: Μη καταχωρημένα
       update_failed_msg: Αδυναμία ενημέρωσης του emoji
       updated_msg: Επιτυχής ενημέρωση του emoji!
       upload: Ανέβασμα
     dashboard:
+      authorized_fetch_mode: Λειτουργία εγκεκριμένης ανάκτησης
       backlog: χρονοκαθυστερημένες εργασίες
       config: Διαμόρφωση
       feature_deletions: Διαγραφή λογαριασμών
@@ -250,10 +269,13 @@ el:
       feature_profile_directory: Κατάλογος χρηστών
       feature_registrations: Εγγραφές
       feature_relay: Ανταποκριτής ομοσπονδίας
+      feature_spam_check: Καταπολέμηση ανεπιθύμητης επικοινωνίας
       feature_timeline_preview: Προεπισκόπιση ροής
       features: Λειτουργίες
       hidden_service: Ομοσπονδία με κρυμμένες υπηρεσίες
       open_reports: ανοιχτές καταγγελίες
+      pending_tags: ταμπέλες προς έγκριση
+      pending_users: χρήστες προς έγκριση
       recent_users: Πρόσφατοι χρήστες
       search: Αναζήτηση πλήρους κειμένου
       single_user_mode: Λειτουργία μοναδιαίου χρήστη
@@ -265,11 +287,18 @@ el:
       week_interactions: αλληλεπιδράσεις αυτή την εβδομάδα
       week_users_active: ενεργοί αυτή την εβδομάδα
       week_users_new: χρήστες αυτή την εβδομάδα
+      whitelist_mode: Λειτουργία εγκρίσεων
+    domain_allows:
+      add_new: Έγκριση τομέα
+      created_msg: Ο τομέας εγκρίθηκε με επιτυχία
+      destroyed_msg: Ο τομέας δεν είναι πια εγκεκριμένος
+      undo: Αφαίρεση έγκρισης
     domain_blocks:
       add_new: Προσθήκη νέου
       created_msg: Ο αποκλεισμός τομέα είναι υπό επεξεργασία
       destroyed_msg: Ο αποκλεισμός τομέα άρθηκε
       domain: Τομέας
+      edit: Διαχείρηση αποκλεισμένου τομέα
       existing_domain_block_html: Έχεις ήδη επιβάλλει αυστηρότερους περιορισμούς στο %{name}, πρώτα θα πρέπει να τους <a href="%{unblock_url}">αναιρέσεις</a>.
       new:
         create: Δημιουργία αποκλεισμού
@@ -280,6 +309,10 @@ el:
           silence: Σίγαση
           suspend: Αναστολή
         title: Αποκλεισμός νέου τομέα
+      private_comment: Ιδιωτικό σχόλιο
+      private_comment_hint: Σχόλιο για τον περιορισμό αυτού του τομέα για εσωτερική χρήση από τους διαχειριστές.
+      public_comment: Δημόσιο σχόλιο
+      public_comment_hint: Σχόλιο σχετικά με τον περιορισμό αυτού του τομέα προς το κοινό, αν η λειτουργία δημοσιοποίησης των περιορισμένων τομέων είναι ενεργοποιημένη.
       reject_media: Απόρριψη πολυμέσων
       reject_media_hint: Αφαιρεί τα τοπικά αποθηκευμένα αρχεία πολυμέσων και αποτρέπει τη λήψη άλλων στο μέλλον. Δεν έχει σημασία για τις αναστολές
       reject_reports: Απόρριψη καταγγελιών
@@ -299,6 +332,7 @@ el:
         title: Αναίρεση αποκλεισμού για τον τομέα %{domain}
         undo: Αναίρεση
       undo: Αναίρεση
+      view: Εμφάνιση αποκλεισμού τομέα
     email_domain_blocks:
       add_new: Πρόσθεση νέου
       created_msg: Επιτυχής πρόσθεση email τομέα σε μαύρη λίστα
@@ -322,6 +356,8 @@ el:
         all: Όλα
         limited: Περιορισμένα
         title: Διαμεσολάβηση
+      private_comment: Ιδιωτικό σχόλιο
+      public_comment: Δημόσιο σχόλιο
       title: Γνωστοί κόμβοι
       total_blocked_by_us: Μπλοκάρονται από εμάς
       total_followed_by_them: Ακολουθούνται από εκείνους
@@ -351,6 +387,7 @@ el:
       pending: Περιμένοντας την έγκριση του ανταποκριτή
       save_and_enable: Αποθήκευση και ενεργοποίηση
       setup: Όρισε μια σύνδεση ανταπόκρισης
+      signatures_not_enabled: Οι ανταποκριτές δεν θα λειτουργούν σωστά όσο είναι ενεργοποιημένες οι επιλογές ασφαλούς λειτουργίας ή επιτρεπόμενων συνδέσεων
       status: Κατάσταση
       title: Ανταποκριτές
     report_notes:
@@ -399,6 +436,16 @@ el:
       custom_css:
         desc_html: Τροποποίηση της εμφάνισης μέσω CSS που φορτώνεται σε κάθε σελίδα
         title: Προσαρμοσμένο CSS
+      default_noindex:
+        desc_html: Επηρεάζει όσους χρήστες δεν έχουν αλλάξει αυτή τη ρύθμιση
+        title: Εξαίρεση χρηστών από τις μηχανές αναζήτησης
+      domain_blocks:
+        all: Για όλους
+        disabled: Για κανέναν
+        title: Εμφάνιση αποκλεισμένων τομέων
+        users: Προς συνδεδεμένους τοπικούς χρήστες
+      domain_blocks_rationale:
+        title: Εμφάνιση σκεπτικού
       hero:
         desc_html: Εμφανίζεται στην μπροστινή σελίδα. Συνίσταται τουλάχιστον 600x100px. Όταν λείπει, χρησιμοποιείται η μικρογραφία του κόμβου
         title: Εικόνα ήρωα
@@ -449,6 +496,9 @@ el:
         desc_html: Μπορείς να γράψεις τη δική σου πολιτική απορρήτου, όρους χρήσης ή άλλους νομικούς όρους. Μπορείς να χρησιμοποιήσεις HTML tags
         title: Προσαρμοσμένοι όροι χρήσης της υπηρεσίας
       site_title: Όνομα κόμβου
+      spam_check_enabled:
+        desc_html: Το Mastodon μπορεί να αποσιωπεί και να καταγγέλει αυτόματα λογαριασμούς βάσει της συμπεριφοράς τους όπως για παράδειγμα επαναλαμβανόμενη αποστολή ανεπιθύμητων μηνυμάτων. Μπορεί να υπάρξουν και λανθασμένες ανιχνεύσεις.
+        title: Καταπολέμηση ανεπιθύμητης επικοινωνίας
       thumbnail:
         desc_html: Χρησιμοποιείται για προεπισκοπήσεις μέσω του OpenGraph και του API. Συστήνεται 1200x630px
         title: Μικρογραφία κόμβου
@@ -456,12 +506,19 @@ el:
         desc_html: Εμφάνισε τη δημόσια ροή στην αρχική σελίδα
         title: Προεπισκόπιση ροής
       title: Ρυθμίσεις ιστότοπου
+      trendable_by_default:
+        desc_html: Επηρεάζει τις ταμπέλες που δεν είχαν απαγορευτεί νωρίτερα
+        title: Επέτρεψε στις ταμπέλες να εμφανιστούν στις τάσεις χωρίς προηγούμενη έγκριση
+      trends:
+        desc_html: Δημόσια εμφάνιση ταμπελών που έχουν ήδη εγκριθεί και είναι δημοφιλείς
+        title: Δημοφιλείς ταμπέλες
     statuses:
       back_to_account: Επιστροφή στη σελίδα λογαριασμού
       batch:
         delete: Διαγραφή
         nsfw_off: Σημείωσε ως μη ευαίσθητο
         nsfw_on: Σημείωσε ως ευαίσθητο
+      deleted: Διαγραμμένα
       failed_to_execute: Αποτυχία εκτέλεσης
       media:
         title: Πολυμέσα
@@ -469,21 +526,24 @@ el:
       no_status_selected: Καμία δημοσίευση δεν άλλαξε αφού καμία δεν ήταν επιλεγμένη
       title: Καταστάσεις λογαριασμού
       with_media: Με πολυμέσα
-    subscriptions:
-      callback_url: URL επιστροφής (Callback)
-      confirmed: Επιβεβαιωμένες
-      expires_in: Λήγει σε
-      last_delivery: Τελευταία παράδοση
-      title: Πρωτόκολλο WebSub
-      topic: Θέμα
     tags:
-      accounts: Λογαριασμοί
-      hidden: Κρυμμένες
-      hide: Απόκρυψη από κατάλογο
+      accounts_today: Μοναδικές χρήσεις ημέρας
+      accounts_week: Μοναδικές χρήσεις εβδομάδας
+      breakdown: Ανάλυση σημερινής χρήσης ανα πηγή
+      context: Συνάφεια
+      directory: Στον κατάλογο
+      in_directory: "%{count} στον κατάλογο"
+      last_active: Τελευταία δραστηριότητα
+      most_popular: Δημοφιλέστερες
+      most_recent: Πιο πρόσφατες
       name: Ταμπέλα
+      review: Κατάσταση έγκρισης
+      reviewed: Εγκεκριμένες
       title: Ταμπέλες
-      unhide: Εμφάνιση σε κατάλογο
-      visible: Εμφανείς
+      trending_right_now: Δημοφιλείς αυτή τη στιγμή
+      unique_uses_today: "%{count} σημερινές δημοσιεύσεις"
+      unreviewed: Εκκρεμεί έγκριση
+      updated_msg: Οι ρυθμίσεις των ταμπελών ενημερώθηκαν επιτυχώς
     title: Διαχείριση
     warning_presets:
       add_new: Πρόσθεση νέου
@@ -499,11 +559,20 @@ el:
       body: Ο/Η %{reporter} κατήγγειλε τον/την %{target}
       body_remote: Κάποιος/α από τον τομέα %{domain} κατήγγειλε τον/την %{target}
       subject: Νέα καταγγελία για %{instance} (#%{id})
+    new_trending_tag:
+      body: 'Η ταμπέλα #%{name} είναι δημοφιλής σήμερα, αλλά δεν έχει εγκριθεί μέχρι τώρα. Δεν θα εμφανίζεται δημοσίως μέχρι να δοθεί έγκρισή, αλλιώς αποθηκεύστε τη φόρμα ως έχει για να μην την δείτε ξανά.'
+      subject: Νέα ταμπέλα προς έγκριση στο %{instance} (#%{name})
+  aliases:
+    add_new: Δημιουργία ψευδώνυμου
+    created_msg: Δημιουργήθηκε νέο ψευδώνυμο. Τώρα μπορείς να ξεκινήσεις τη μεταφορά από τον παλιό λογαριασμό.
+    deleted_msg: Αφαιρέθηκε το ψευδώνυμο. Η μεταφορά από εκείνον τον λογαριασμό σε αυτόν εδώ δε θα είναι πλέον δυνατή.
+    remove: Αφαίρεση ψευδώνυμου
   appearance:
     advanced_web_interface: Προηγμένη λειτουργία χρήσης
     advanced_web_interface_hint: 'Αν θέλεις να χρησιμοποιήσεις ολόκληρο το πλάτος της οθόνης σου, η προηγμένη λειτουργία χρήσης σου επιτρέπει να ορίσεις πολλαπλές κολώνες ώστε να βλέπεις ταυτόχρονα όση πληροφορία θέλεις: Την αρχική ροή, τις ειδοποιήσεις, την ομοσπονδιακή ροή και όσες λίστες και ταμπέλες θέλεις.'
     animations_and_accessibility: Κίνηση και προσβασιμότητα
     confirmation_dialogs: Ερωτήσεις επιβεβαίωσης
+    discovery: Εξερεύνηση
     sensitive_content: Ευαίσθητο περιεχόμενο
   application_mailer:
     notification_preferences: Αλλαγή προτιμήσεων email
@@ -524,9 +593,13 @@ el:
     apply_for_account: Αίτηση πρόσκλησης
     change_password: Συνθηματικό
     checkbox_agreement_html: Συμφωνώ με τους <a href="%{rules_path}" target="_blank">κανονισμούς του κόμβου</a> και <a href="%{terms_path}" target="_blank">τους όρους χρήσης</a>
-    confirm_email: Επιβεβαίωση email
+    checkbox_agreement_without_rules_html: Συμφωνώ με τους <a href="%{terms_path}" target="_blank">όρους χρήσης</a>
     delete_account: Διαγραφή email
     delete_account_html: Αν θέλεις να διαγράψεις το λογαριασμό σου, μπορείς <a href="%{path}">να συνεχίσεις εδώ</a>. Θα σου ζητηθεί επιβεβαίωση.
+    description:
+      prefix_invited_by_user: Ο/Η @%{name} σε προσκαλεί να συνδεθείς με αυτό τον διακομιστή του Mastodon!
+      prefix_sign_up: Άνοιξε λογαριασμό στο Mastodon σήμερα!
+      suffix: Ανοίγοντας λογαριασμό θα μπορείς να ακολουθείς άλλους, να ανεβάζεις ενημερώσεις και να ανταλλάζεις μηνύματα με χρήστες σε οποιοδήποτε διακομιστή Mastodon, καθώς και άλλα!
     didnt_get_confirmation: Δεν έλαβες τις οδηγίες επιβεβαίωσης;
     forgot_password: Ξέχασες το συνθηματικό σου;
     invalid_reset_password_token: Το διακριτικό επαναφοράς συνθηματικού είναι άκυρο ή ληγμένο. Παρακαλώ αιτήσου νέο.
@@ -544,6 +617,15 @@ el:
     reset_password: Επαναφορά συνθηματικού
     security: Ασφάλεια
     set_new_password: Ορισμός νέου συνθηματικού
+    setup:
+      email_below_hint_html: Αν η παρακάτω διεύθυνση email είναι λανθασμένη, μπορείτε να την ενημερώσετε και να λάβετε νέο email επιβεβαίωσης.
+      email_settings_hint_html: Το email επιβεβαίωσης στάλθηκε στο %{email}. Αν η διεύθυνση αυτή δεν είναι σωστή, μπορείτε να την ενημερώσετε στις ρυθμίσεις λογαριασμού.
+      title: Ρυθμίσεις
+    status:
+      account_status: Κατάσταση λογαριασμού
+      confirming: Αναμονή για ολοκλήρωση επιβεβαίωσης του email.
+      functional: Ο λογαριασμός σου είναι πανέτοιμος.
+      pending: Η εφαρμογή σας εκκρεμεί έγκρισης, πιθανόν θα διαρκέσει κάποιο χρόνο. Θα λάβετε email αν εγκριθεί.
     trouble_logging_in: Πρόβλημα σύνδεσης;
   authorize_follow:
     already_following: Ήδη ακολουθείς αυτό το λογαριασμό
@@ -556,6 +638,10 @@ el:
       return: Δείξε το προφίλ του χρήστη
       web: Πήγαινε στο δίκτυο
     title: Ακολούθησε %{acct}
+  challenge:
+    confirm: Συνέχεια
+    invalid_password: Μη έγκυρο συνθηματικό
+    prompt: Επιβεβαίωση συνθηματικού για συνέχεια
   datetime:
     distance_in_words:
       about_x_hours: "%{count}ω"
@@ -571,26 +657,33 @@ el:
       x_months: "%{count}μ"
       x_seconds: "%{count}δ"
   deletes:
-    bad_password_msg: Καλή προσπάθεια χάκερς! Λάθος συνθηματικό
+    challenge_not_passed: Τα στοιχεία δεν ήταν σωστά
     confirm_password: Γράψε το τρέχον συνθηματικό σου για να πιστοποιήσεις την ταυτότητά σου
-    description_html: Αυτό θα διαγράψει <strong>οριστικά και αμετάκλητα</strong> το περιεχόμενο του λογαριασμού σου και θα τον απενεργοποιήσει. Το όνομα χρήστη θα παραμείνει δεσμευμένο για να αποφευχθούν μελλοντικές πλαστοπροσωπίες.
+    confirm_username: Γράψε το όνομα χρήστη σου για επιβεβαίωση
     proceed: Διαγραφή λογαριασμού
     success_msg: Ο λογαριασμός σου διαγράφηκε με επιτυχία
-    warning_html: Μόνο η διαγραφή περιεχομένου από αυτό τον συγκεκριμένο κόμβο είναι εγγυημένη. Το περιεχόμενο που έχει διαμοιραστεί ευρέως είναι πιθανό να αφήσει ίχνη. Όσοι διακομιστές είναι εκτός σύνδεσης και όσοι έχουν διακόψει τη λήψη των ενημερώσεων του κόμβου σου, δε θα ενημερώσουν τις βάσεις δεδομένων τους.
-    warning_title: Διαθεσιμότητα ήδη διανεμημένου περιεχομένου
+    warning:
+      before: 'Πριν συνεχίσεις, παρακαλούμε να διαβάσεις τις παρακάτω σημειώσεις προσεκτικά:'
+      caches: Οποίο περιεχόμενο έχει αποθηκευτεί προσωρινά σε άλλους διακομιστές μπορεί να παραμείνει
+      data_removal: Οι δημοσιεύσεις σου και άλλα δεδομένα θα διαγραφούν οριστικά
+      email_change_html: Μπορείς να <a href="%{path}">αλλάξεις τη διεύθυνση email σου</a> Χωρίς να διαγράψεις το λογαριασμό σου
+      email_contact_html: Αν και πάλι δεν εμφανιστεί, μπορείς να στείλεις email προς <a href="mailto:%{email}">%{email}</a> για βοήθεια
+      email_reconfirmation_html: Αν δεν έχεις λάβει το email επιβεβαίωσης, μπορείς να το <a href="%{path}">ζητήσεις ξανά</a>
+      irreversible: Δεν θα μπορείς να ανακτήσεις ή ενεργοποιήσεις ξανά το λογαριασμό σου
+      more_details_html: Για περισσότερες πληροφορίες, δες την <a href="%{terms_path}">πολιτική απορρήτου</a>.
+      username_available: Το όνομα χρήστη σου θα γίνει ξανά διαθέσιμο
+      username_unavailable: Το όνομα χρήστη σου θα παραμείνει μη διαθέσιμο
   directories:
     directory: Κατάλογος λογαριασμών
-    enabled: Περιλαμβάνεσαι στον κατάλογο.
-    enabled_but_waiting: Έχεις επιλέξει να εμφανίζεσαι στον κατάλογο μεν, αλλά ακόμα δεν έχεις τον ελάχιστο αριθμό ακόλουθων (%{min_followers}) που απαιτείται για να συμπεριληφθείς.
     explanation: Βρες χρήστες βάσει των ενδιαφερόντων τους
     explore_mastodon: Εξερεύνησε το %{title}
-    how_to_enable: Δεν έχεις επιλέξει να συμπεριληφθείς στον καταλογο. Μπορείς να επιλέξεις παρακάτω. Χρησιμοποίησε ταμπέλες στο κείμενο του βιογραφικού σου για να εμφανίζεσαι κάτω από συγκεκριμένες ταμπέλες!
-    people:
-      one: "%{count} άτομο"
-      other: "%{count} άτομα"
+  domain_validator:
+    invalid_domain: δεν είναι έγκυρο όνομα τομέα
   errors:
+    '400': Το αίτημα ήταν άκυρο ή με λάθος μορφή.
     '403': Δεν έχεις δικαίωμα πρόσβασης σε αυτή τη σελίδα.
     '404': Η σελίδα που ψάχνεις δεν υπάρχει.
+    '406': Η σελίδα αυτή δεν είναι διαθέσιμη στην αιτούμενη μορφή.
     '410': Η σελίδα που έψαχνες δεν υπάρχει πια εδώ.
     '422':
       content: Απέτυχε η επιβεβαίωση ασφαλείας. Μήπως μπλοκάρεις τα cookies;
@@ -599,6 +692,7 @@ el:
     '500':
       content: Λυπούμαστε, κάτι πήγε στραβά από τη δική μας μεριά.
       title: Η σελίδα αυτή δεν είναι σωστή
+    '503': Η σελίδα δε μπόρεσε να εμφανιστεί λόγω προσωρινού σφάλματος του διακομιστή.
     noscript_html: Για να χρησιμοποιήσετε τη δικτυακή εφαρμογή του Mastodon, ενεργοποίησε την Javascript. Εναλλακτικά, δοκίμασε μια από τις <a href="%{apps_path}">εφαρμογές</a> για το Mastodon στην πλατφόρμα σου.
   existing_username_validator:
     not_found: δεν βρέθηκε τοπικός χρήστης με αυτό το όνομα
@@ -616,12 +710,13 @@ el:
     domain_blocks: Μπλοκαρίσματα κόμβων
     follows: Ακολουθείς
     lists: Λίστες
-    mutes: Αποσιωπάς
+    mutes: Αποσιωπήσεις
     storage: Αποθήκευση πολυμέσων
   featured_tags:
     add_new: Προσθήκη νέας
     errors:
       limit: Έχεις ήδη προσθέσει το μέγιστο αριθμό ταμπελών
+    hint_html: "<strong>Τι είναι οι προβεβλημένες ταμπέλες;</strong> Προβάλλονται στο δημόσιο προφίλ σου επιτρέποντας σε όποιον το βλέπει να χαζέψει τις δημοσιεύσεις που τις χρησιμοποιούν. Είναι ωραίος τρόπος να παρακολουθείς κάποια δημιουργία ή ένα μακροπρόθεσμο έργο."
   filters:
     contexts:
       home: Αρχική ροή
@@ -642,12 +737,14 @@ el:
     developers: Ανάπτυξη
     more: Περισσότερα…
     resources: Πόροι
+    trending_now: Τάσεις
   generic:
     all: Όλα
     changes_saved_msg: Οι αλλαγές αποθηκεύτηκαν!
     copy: Αντιγραφή
+    no_batch_actions_available: Δεν υπάρχουν ομαδικές ενέργειες σε αυτή τη σελίδα
     order_by: Ταξινόμηση κατά
-    save_changes: Αποθήκευσε τις αλλαγές
+    save_changes: Αποθήκευση αλλαγών
     validation_errors:
       one: Κάτι δεν είναι εντάξει ακόμα! Για κοίταξε το παρακάτω σφάλμα
       other: Κάτι δεν είναι εντάξει ακόμα! Για κοίταξε τα παρακάτω %{count} σφάλματα
@@ -717,9 +814,26 @@ el:
       too_many: Δεν γίνεται να προσθέσεις περισσότερα από 4 αρχεία
   migrations:
     acct: ΌνομαΧρήστη@Τομέας του νέου λογαριασμού
-    currently_redirecting: 'Το προφίλ σου έχει ρυθμιστεί να ανακατευθύνει στο:'
-    proceed: Αποθήκευση
-    updated_msg: Οι ρυθμίσεις μετακόμισης του λογαριασμού σου ενημερώθηκαν!
+    cancel: Ακύρωση ανακατεύθυνσης
+    errors:
+      move_to_self: δεν επιτρέπεται να είναι ο τρέχων λογαριασμός
+      not_found: δεν βρέθηκε
+      on_cooldown: Είσαι σε περίοδο προσαρμογής
+    followers_count: Ακόλουθοι τη στιγμή της μεταφοράς
+    incoming_migrations: Μεταφορά από διαφορετικό λογαριασμό
+    past_migrations: Προηγούμενες μετακινήσεις
+    proceed_with_move: Μετακίνηση ακολούθων
+    redirecting_to: Ο λογαριασμός σου ανακατευθύνει στο %{acct}.
+    set_redirect: Όρισε ανακατεύθυνση
+    warning:
+      backreference_required: Θα πρέπει πρώτα να ρυθμιστεί μια αναφορά από τον νέο λογαριασμό προς αυτόν
+      before: 'Πριν συνεχίσεις, παρακαλούμε διάβασε αυτές τις σημειώσεις προσεκτικά:'
+      cooldown: Άπαξ και μεταφερθείς υπάρχει μια περίοδος προσαρμογής κατά την οποία δε θα μπορείς να μετακινηθείς ξανά
+      disabled_account: Ο τρέχων λογαριασμός σου δε θα είναι πλήρως ενεργός μετά. Πάντως θα έχεις πρόσβαση στην εξαγωγή δεδομένων καθώς και στην επανενεργοποίηση.
+      followers: Αυτή η ενέργεια θα μεταφέρει όλους τους ακόλουθούς σου από τον τρέχοντα λογαριασμός στον νέο λογαριασμό
+      only_redirect_html: Εναλλακτικά, μπορείς <a href="%{path}">απλά να προσθέσεις μια ανακατατεύθυνση στο προφίλ σου</a>.
+      other_data: Κανένα άλλο δεδομένο δε θα μεταφερθεί αυτόματα
+      redirect: Το προφίλ του τρέχοντος λογαριασμό σου θα ενημερωθεί με μια σημείωση ανακατεύθυνσης και θα εξαιρεθεί από τα αποτελέσματα αναζητήσεων
   moderation:
     title: Συντονισμός
   notification_mailer:
@@ -783,7 +897,7 @@ el:
       too_few_options: πρέπει να έχει περισσότερες από μια επιλογές
       too_many_options: δεν μπορεί να έχει περισσότερες από %{max} επιλογές
   preferences:
-    other: Άλλο
+    other: Άλλες
     posting_defaults: Προεπιλογές δημοσίευσης
     public_timelines: Δημόσιες ροές
   relationships:
@@ -816,10 +930,6 @@ el:
     reply:
       proceed: Συνέχισε για να απαντήσεις
       prompt: 'Θέλεις να απαντήσεις σε αυτό το τουτ:'
-  remote_unfollow:
-    error: Σφάλμα
-    title: Τίτλος
-    unfollowed: Σταμάτησες να ακολουθείς
   scheduled_statuses:
     over_daily_limit: Έχεις υπερβεί το όριο των %{limit} προγραμματισμένων τουτ για εκείνη τη μέρα
     over_total_limit: Έχεις υπερβεί το όριο των %{limit} προγραμματισμένων τουτ
@@ -868,6 +978,7 @@ el:
   settings:
     account: Λογαριασμός
     account_settings: Ρυθμίσεις λογαριασμού
+    aliases: Ψευδώνυμα λογαριασμών
     appearance: Εμφάνιση
     authorized_apps: Εγκεκριμένες εφαρμογές
     back: Πίσω στο Mastodon
@@ -908,6 +1019,9 @@ el:
       private: Τα μη δημόσια τουτ δεν καρφιτσώνονται
       reblog: Οι προωθήσεις δεν καρφιτσώνονται
     poll:
+      total_people:
+        one: "%{count} άτομο"
+        other: "%{count} άτομα"
       total_votes:
         one: "%{count} ψήφος"
         other: "%{count} ψήφοι"
@@ -926,6 +1040,8 @@ el:
     pinned: Καρφιτσωμένο τουτ
     reblogged: προωθημένο
     sensitive_content: Ευαίσθητο περιεχόμενο
+  tags:
+    does_not_match_previous_name: δεν ταιριάζει με το προηγούμενο όνομα
   terms:
     body_html: |
       <h2>Πολιτική Απορρήτου</h2>
@@ -1043,7 +1159,9 @@ el:
         disable: Όσο ο λογαριασμός σου είναι παγωμένος, τα στοιχεία του παραμένουν άθικτα αλλά δεν μπορείς να κανείς καμία ενέργεια μέχρι να ξεκλειδωθείς.
         silence: Όσο ο λογαριασμός σου είναι περιορισμένος, μόνο όσοι σε ακολουθούν ήδη θα βλέπουν τα τουτ σου σε αυτό τον κόμβο ενώ μπορεί να εξαιρεθείς από διάφορες δημόσιες απαριθμήσεις. Πάντως, θα μπορούν να σε ακολουθήσουν χειροκίνητα.
         suspend: Ο λογαριασμός σου αναστάλθηκε μόνιμα, όλα τα τουτ και τα ανεβασμένα πολυμέσα σου διαγράφηκαν αμετάκλητα από αυτόν τον κόμβο και σε όσους άλλους είχες ακόλουθους.
+      get_in_touch: Μπορείς να απαντήσεις σε αυτό το email για να επικοινωνήσεις με το προσωπικό του %{instance}.
       review_server_policies: Αναθεώρηση πολιτικής του κόμβου
+      statuses: 'Συγκεκριμένα, για:'
       subject:
         disable: Ο λογαριασμός σου %{acct} έχει παγώσει
         none: Προειδοποίηση προς %{acct}
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 6fe82a412e1b2b09b9c664cecbca279e7bd24a9e..faf1d0dd1570cad510dd3b58eee5c00f90f30637 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -2,7 +2,7 @@
 en:
   about:
     about_hashtag_html: These are public toots tagged with <strong>#%{hashtag}</strong>. You can interact with them if you have an account anywhere in the fediverse.
-    about_mastodon_html: Mastodon is a social network based on open web protocols and free, open-source software. It is decentralized like e-mail.
+    about_mastodon_html: 'The social network of the future: No ads, no corporate surveillance, ethical design, and decentralization! Own your data with Mastodon!'
     about_this: About
     active_count_after: active
     active_footnote: Monthly Active Users (MAU)
@@ -17,13 +17,12 @@ en:
     contact_unavailable: N/A
     discover_users: Discover users
     documentation: Documentation
-    extended_description_html: |
-      <h3>A good place for rules</h3>
-      <p>The extended description has not been set up yet.</p>
     federation_hint_html: With an account on %{instance} you'll be able to follow people on any Mastodon server and beyond.
-    generic_description: "%{domain} is one server in the network"
     get_apps: Try a mobile app
     hosted_on: Mastodon hosted on %{domain}
+    instance_actor_flash: |
+      This account is a virtual actor used to represent the server itself and not any individual user.
+      It is used for federation purposes and should not be blocked unless you want to block the whole instance, in which case you should use a domain block.
     learn_more: Learn more
     privacy_policy: Privacy policy
     see_whats_happening: See what's happening
@@ -35,6 +34,14 @@ en:
     status_count_before: Who authored
     tagline: Follow friends and discover new ones
     terms: Terms of service
+    unavailable_content: Unavailable content
+    unavailable_content_description:
+      domain: Server
+      reason: Reason
+      rejecting_media: 'Media files from these servers will not be processed or stored, and and no thumbnails will be displayed, requiring manual click-through to the original file:'
+      silenced: 'Posts from these servers will be hidden in public timelines and conversations, and no notifications will be generated from their users'' interactions, unless you are following them:'
+      suspended: 'No data from these servers will be processed, stored or exchanged, making any interaction or communication with users from these servers impossible:'
+    unavailable_content_html: Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server.
     user_count_after:
       one: user
       other: users
@@ -42,6 +49,8 @@ en:
     what_is_mastodon: What is Mastodon?
   accounts:
     choices_html: "%{name}'s choices:"
+    endorsements_hint: You can endorse people you follow from the web interface, and they will show up here.
+    featured_tags_hint: You can feature specific hashtags that will be displayed here.
     follow: Follow
     followers:
       one: Follower
@@ -53,6 +62,7 @@ en:
     media: Media
     moved_html: "%{name} has moved to %{new_profile_link}:"
     network_hidden: This information is not available
+    never_active: Never
     nothing_here: There is nothing here!
     people_followed_by: People whom %{name} follows
     people_who_follow: People who follow %{name}
@@ -183,6 +193,7 @@ en:
       username: Username
       warn: Warn
       web: Web
+      whitelisted: Whitelisted
     action_logs:
       actions:
         assigned_to_self_report: "%{name} assigned report %{target} to themselves"
@@ -218,19 +229,24 @@ en:
       deleted_status: "(deleted status)"
       title: Audit log
     custom_emojis:
+      assign_category: Assign category
       by_domain: Domain
       copied_msg: Successfully created local copy of the emoji
       copy: Copy
       copy_failed_msg: Could not make a local copy of that emoji
+      create_new_category: Create new category
       created_msg: Emoji successfully created!
       delete: Delete
       destroyed_msg: Emojo successfully destroyed!
       disable: Disable
+      disabled: Disabled
       disabled_msg: Successfully disabled that emoji
       emoji: Emoji
       enable: Enable
+      enabled: Enabled
       enabled_msg: Successfully enabled that emoji
       image_hint: PNG up to 50KB
+      list: List
       listed: Listed
       new:
         title: Add new custom emoji
@@ -238,11 +254,14 @@ en:
       shortcode: Shortcode
       shortcode_hint: At least 2 characters, only alphanumeric characters and underscores
       title: Custom emojis
+      uncategorized: Uncategorized
+      unlist: Unlist
       unlisted: Unlisted
       update_failed_msg: Could not update that emoji
       updated_msg: Emoji successfully updated!
       upload: Upload
     dashboard:
+      authorized_fetch_mode: Secure mode
       backlog: backlogged jobs
       config: Configuration
       feature_deletions: Account deletions
@@ -250,10 +269,13 @@ en:
       feature_profile_directory: Profile directory
       feature_registrations: Registrations
       feature_relay: Federation relay
+      feature_spam_check: Anti-spam
       feature_timeline_preview: Timeline preview
       features: Features
       hidden_service: Federation with hidden services
       open_reports: open reports
+      pending_tags: hashtags waiting for review
+      pending_users: users waiting for review
       recent_users: Recent users
       search: Full-text search
       single_user_mode: Single user mode
@@ -265,11 +287,18 @@ en:
       week_interactions: interactions this week
       week_users_active: active this week
       week_users_new: users this week
+      whitelist_mode: Whitelist mode
+    domain_allows:
+      add_new: Whitelist domain
+      created_msg: Domain has been successfully whitelisted
+      destroyed_msg: Domain has been removed from the whitelist
+      undo: Remove from whitelist
     domain_blocks:
       add_new: Add new domain block
       created_msg: Domain block is now being processed
       destroyed_msg: Domain block has been undone
       domain: Domain
+      edit: Edit domain block
       existing_domain_block_html: You have already imposed stricter limits on %{name}, you need to <a href="%{unblock_url}">unblock it</a> first.
       new:
         create: Create block
@@ -280,6 +309,10 @@ en:
           silence: Silence
           suspend: Suspend
         title: New domain block
+      private_comment: Private comment
+      private_comment_hint: Comment about this domain limitation for internal use by the moderators.
+      public_comment: Public comment
+      public_comment_hint: Comment about this domain limitation for the general public, if advertising the list of domain limitations is enabled.
       reject_media: Reject media files
       reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions
       reject_reports: Reject reports
@@ -299,6 +332,7 @@ en:
         title: Undo domain block for %{domain}
         undo: Undo
       undo: Undo domain block
+      view: View domain block
     email_domain_blocks:
       add_new: Add new
       created_msg: Successfully added e-mail domain to blacklist
@@ -322,6 +356,8 @@ en:
         all: All
         limited: Limited
         title: Moderation
+      private_comment: Private comment
+      public_comment: Public comment
       title: Federation
       total_blocked_by_us: Blocked by us
       total_followed_by_them: Followed by them
@@ -351,6 +387,7 @@ en:
       pending: Waiting for relay's approval
       save_and_enable: Save and enable
       setup: Setup a relay connection
+      signatures_not_enabled: Relays will not work correctly while secure mode or whitelist mode is enabled
       status: Status
       title: Relays
     report_notes:
@@ -399,6 +436,16 @@ en:
       custom_css:
         desc_html: Modify the look with CSS loaded on every page
         title: Custom CSS
+      default_noindex:
+        desc_html: Affects all users who have not changed this setting themselves
+        title: Opt users out of search engine indexing by default
+      domain_blocks:
+        all: To everyone
+        disabled: To no one
+        title: Show domain blocks
+        users: To logged-in local users
+      domain_blocks_rationale:
+        title: Show rationale
       hero:
         desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to server thumbnail
         title: Hero image
@@ -431,8 +478,8 @@ en:
           open: Anyone can sign up
         title: Registrations mode
       show_known_fediverse_at_about_page:
-        desc_html: When toggled, it will show toots from all the known fediverse on preview. Otherwise it will only show local toots.
-        title: Show known fediverse on timeline preview
+        desc_html: When disabled, restricts the public timeline linked from the landing page to showing only local content
+        title: Include federated content on unauthenticated public timeline page
       show_staff_badge:
         desc_html: Show a staff badge on a user page
         title: Show staff badge
@@ -449,19 +496,29 @@ en:
         desc_html: You can write your own privacy policy, terms of service or other legalese. You can use HTML tags
         title: Custom terms of service
       site_title: Server name
+      spam_check_enabled:
+        desc_html: Mastodon can auto-report accounts that send repeated unsolicited messages. There may be false positives.
+        title: Anti-spam automation
       thumbnail:
         desc_html: Used for previews via OpenGraph and API. 1200x630px recommended
         title: Server thumbnail
       timeline_preview:
-        desc_html: Display public timeline on landing page
-        title: Timeline preview
+        desc_html: Display link to public timeline on landing page and allow API access to the public timeline without authentication
+        title: Allow unauthenticated access to public timeline
       title: Site settings
+      trendable_by_default:
+        desc_html: Affects hashtags that have not been previously disallowed
+        title: Allow hashtags to trend without prior review
+      trends:
+        desc_html: Publicly display previously reviewed hashtags that are currently trending
+        title: Trending hashtags
     statuses:
       back_to_account: Back to account page
       batch:
         delete: Delete
         nsfw_off: Mark as not sensitive
         nsfw_on: Mark as sensitive
+      deleted: Deleted
       failed_to_execute: Failed to execute
       media:
         title: Media
@@ -469,21 +526,24 @@ en:
       no_status_selected: No statuses were changed as none were selected
       title: Account statuses
       with_media: With media
-    subscriptions:
-      callback_url: Callback URL
-      confirmed: Confirmed
-      expires_in: Expires in
-      last_delivery: Last delivery
-      title: WebSub
-      topic: Topic
     tags:
-      accounts: Accounts
-      hidden: Hidden
-      hide: Hide from directory
+      accounts_today: Unique uses today
+      accounts_week: Unique uses this week
+      breakdown: Breakdown of today's usage by source
+      context: Context
+      directory: In directory
+      in_directory: "%{count} in directory"
+      last_active: Last active
+      most_popular: Most popular
+      most_recent: Most recent
       name: Hashtag
+      review: Review status
+      reviewed: Reviewed
       title: Hashtags
-      unhide: Show in directory
-      visible: Visible
+      trending_right_now: Trending right now
+      unique_uses_today: "%{count} posting today"
+      unreviewed: Not reviewed
+      updated_msg: Hashtag settings updated successfully
     title: Administration
     warning_presets:
       add_new: Add new
@@ -499,11 +559,21 @@ en:
       body: "%{reporter} has reported %{target}"
       body_remote: Someone from %{domain} has reported %{target}
       subject: New report for %{instance} (#%{id})
+    new_trending_tag:
+      body: 'The hashtag #%{name} is trending today, but has not been previously reviewed. It will not be displayed publicly unless you allow it to, or just save the form as it is to never hear about it again.'
+      subject: New hashtag up for review on %{instance} (#%{name})
+  aliases:
+    add_new: Create alias
+    created_msg: Successfully created a new alias. You can now initiate the move from the old account.
+    deleted_msg: Successfully remove the alias. Moving from that account to this one will no longer be possible.
+    hint_html: If you want to move from another account to this one, here you can create an alias, which is required before you can proceed with moving followers from the old account to this one. This action by itself is <strong>harmless and reversible</strong>. <strong>The account migration is initiated from the old account</strong>.
+    remove: Unlink alias
   appearance:
     advanced_web_interface: Advanced web interface
     advanced_web_interface_hint: 'If you want to make use of your entire screen width, the advanced web interface allows you to configure many different columns to see as much information at the same time as you want: Home, notifications, federated timeline, any number of lists and hashtags.'
     animations_and_accessibility: Animations and accessibility
     confirmation_dialogs: Confirmation dialogs
+    discovery: Discovery
     sensitive_content: Sensitive content
   application_mailer:
     notification_preferences: Change e-mail preferences
@@ -524,9 +594,13 @@ en:
     apply_for_account: Request an invite
     change_password: Password
     checkbox_agreement_html: I agree to the <a href="%{rules_path}" target="_blank">server rules</a> and <a href="%{terms_path}" target="_blank">terms of service</a>
-    confirm_email: Confirm email
+    checkbox_agreement_without_rules_html: I agree to the <a href="%{terms_path}" target="_blank">terms of service</a>
     delete_account: Delete account
     delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation.
+    description:
+      prefix_invited_by_user: "@%{name} invites you to join this server of Mastodon!"
+      prefix_sign_up: Sign up on Mastodon today!
+      suffix: With an account, you will be able to follow people, post updates and exchange messages with users from any Mastodon server and more!
     didnt_get_confirmation: Didn't receive confirmation instructions?
     forgot_password: Forgot your password?
     invalid_reset_password_token: Password reset token is invalid or expired. Please request a new one.
@@ -544,6 +618,16 @@ en:
     reset_password: Reset password
     security: Security
     set_new_password: Set new password
+    setup:
+      email_below_hint_html: If the below e-mail address is incorrect, you can change it here and receive a new confirmation e-mail.
+      email_settings_hint_html: The confirmation e-mail was sent to %{email}. If that e-mail address is not correct, you can change it in account settings.
+      title: Setup
+    status:
+      account_status: Account status
+      confirming: Waiting for e-mail confirmation to be completed.
+      functional: Your account is fully operational.
+      pending: Your application is pending review by our staff. This may take some time. You will receive an e-mail if your application is approved.
+      redirecting_to: Your account is inactive because it is currently redirecting to %{acct}.
     trouble_logging_in: Trouble logging in?
   authorize_follow:
     already_following: You are already following this account
@@ -556,6 +640,11 @@ en:
       return: Show the user's profile
       web: Go to web
     title: Follow %{acct}
+  challenge:
+    confirm: Continue
+    hint_html: "<strong>Tip:</strong> We won't ask you for your password again for the next hour."
+    invalid_password: Invalid password
+    prompt: Confirm password to continue
   datetime:
     distance_in_words:
       about_x_hours: "%{count}h"
@@ -571,28 +660,33 @@ en:
       x_months: "%{count}mo"
       x_seconds: "%{count}s"
   deletes:
-    bad_password_msg: Nice try, hackers! Incorrect password
+    challenge_not_passed: The information you entered was not correct
     confirm_password: Enter your current password to verify your identity
-    description_html: This will <strong>permanently, irreversibly</strong> remove content from your account and deactivate it. Your username will remain reserved to prevent future impersonations.
+    confirm_username: Enter your username to confirm the procedure
     proceed: Delete account
     success_msg: Your account was successfully deleted
-    warning_html: Only deletion of content from this particular server is guaranteed. Content that has been widely shared is likely to leave traces. Offline servers and servers that have unsubscribed from your updates will not update their databases.
-    warning_title: Disseminated content availability
+    warning:
+      before: 'Before proceeding, please read these notes carefully:'
+      caches: Content that has been cached by other servers may persist
+      data_removal: Your posts and other data will be permanently removed
+      email_change_html: You can <a href="%{path}">change your e-mail address</a> without deleting your account
+      email_contact_html: If it still doesn't arrive, you can e-mail <a href="mailto:%{email}">%{email}</a> for help
+      email_reconfirmation_html: If you are not receiving the confirmation e-mail, you can <a href="%{path}">request it again</a>
+      irreversible: You will not be able to restore or reactivate your account
+      more_details_html: For more details, see the <a href="%{terms_path}">privacy policy</a>.
+      username_available: Your username will become available again
+      username_unavailable: Your username will remain unavailable
   directories:
     directory: Profile directory
-    enabled: You are currently listed in the directory.
-    enabled_but_waiting: You have opted-in to be listed in the directory, but you do not have the minimum number of followers (%{min_followers}) to be listed yet.
     explanation: Discover users based on their interests
     explore_mastodon: Explore %{title}
-    how_to_enable: You are not currently opted-in to the directory. You can opt-in below. Use hashtags in your bio text to be listed under specific hashtags!
-    people:
-      one: "%{count} person"
-      other: "%{count} people"
   domain_validator:
     invalid_domain: is not a valid domain name
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': You don't have permission to view this page.
     '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
     '410': The page you were looking for doesn't exist here anymore.
     '422':
       content: Security verification failed. Are you blocking cookies?
@@ -601,6 +695,7 @@ en:
     '500':
       content: We're sorry, but something went wrong on our end.
       title: This page is not correct
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: To use the Mastodon web application, please enable JavaScript. Alternatively, try one of the <a href="%{apps_path}">native apps</a> for Mastodon for your platform.
   existing_username_validator:
     not_found: could not find a local user with that username
@@ -624,6 +719,7 @@ en:
     add_new: Add new
     errors:
       limit: You have already featured the maximum amount of hashtags
+    hint_html: "<strong>What are featured hashtags?</strong> They are displayed prominently on your public profile and allow people to browse your public posts specifically under those hashtags. They are a great tool for keeping track of creative works or long-term projects."
   filters:
     contexts:
       home: Home timeline
@@ -644,10 +740,12 @@ en:
     developers: Developers
     more: More…
     resources: Resources
+    trending_now: Trending now
   generic:
     all: All
     changes_saved_msg: Changes successfully saved!
     copy: Copy
+    no_batch_actions_available: No batch actions available on this page
     order_by: Order by
     save_changes: Save changes
     validation_errors:
@@ -699,7 +797,7 @@ en:
       '604800': 1 week
       '86400': 1 day
     expires_in_prompt: Never
-    generate: Generate
+    generate: Generate invite link
     invited_by: 'You were invited by:'
     max_uses:
       one: 1 use
@@ -718,10 +816,35 @@ en:
       images_and_video: Cannot attach a video to a status that already contains images
       too_many: Cannot attach more than 4 files
   migrations:
-    acct: username@domain of the new account
-    currently_redirecting: 'Your profile is set to redirect to:'
-    proceed: Save
-    updated_msg: Your account migration setting successfully updated!
+    acct: Moved to
+    cancel: Cancel redirect
+    cancel_explanation: Cancelling the redirect will re-activate your current account, but will not bring back followers that have been moved to that account.
+    cancelled_msg: Successfully cancelled the redirect.
+    errors:
+      already_moved: is the same account you have already moved to
+      missing_also_known_as: is not back-referencing this account
+      move_to_self: cannot be current account
+      not_found: could not be found
+      on_cooldown: You are on cooldown
+    followers_count: Followers at time of move
+    incoming_migrations: Moving from a different account
+    incoming_migrations_html: To move from another account to this one, first you need to <a href="%{path}">create an account alias</a>.
+    moved_msg: Your account is now redirecting to %{acct} and your followers are being moved over.
+    not_redirecting: Your account is not redirecting to any other account currently.
+    on_cooldown: You have recently migrated your account. This function will become available again in %{count} days.
+    past_migrations: Past migrations
+    proceed_with_move: Move followers
+    redirecting_to: Your account is redirecting to %{acct}.
+    set_redirect: Set redirect
+    warning:
+      backreference_required: The new account must first be configured to back-reference this one
+      before: 'Before proceeding, please read these notes carefully:'
+      cooldown: After moving there is a cooldown period during which you will not be able to move again
+      disabled_account: Your current account will not be fully usable afterwards. However, you will have access to data export as well as re-activation.
+      followers: This action will move all followers from the current account to the new account
+      only_redirect_html: Alternatively, you can <a href="%{path}">only put up a redirect on your profile</a>.
+      other_data: No other data will be moved automatically
+      redirect: Your current account's profile will be updated with a redirect notice and be excluded from searches
   moderation:
     title: Moderation
   notification_mailer:
@@ -819,10 +942,6 @@ en:
     reply:
       proceed: Proceed to reply
       prompt: 'You want to reply to this toot:'
-  remote_unfollow:
-    error: Error
-    title: Title
-    unfollowed: Unfollowed
   scheduled_statuses:
     over_daily_limit: You have exceeded the limit of %{limit} scheduled toots for that day
     over_total_limit: You have exceeded the limit of %{limit} scheduled toots
@@ -871,6 +990,7 @@ en:
   settings:
     account: Account
     account_settings: Account settings
+    aliases: Account aliases
     appearance: Appearance
     authorized_apps: Authorized apps
     back: Back to Mastodon
@@ -888,6 +1008,8 @@ en:
     profile: Profile
     relationships: Follows and followers
     two_factor_authentication: Two-factor Auth
+  spam_check:
+    spam_detected: This is an automated report. Spam has been detected.
   statuses:
     attached:
       description: 'Attached: %{attached}'
@@ -912,6 +1034,9 @@ en:
       private: Non-public toot cannot be pinned
       reblog: A boost cannot be pinned
     poll:
+      total_people:
+        one: "%{count} person"
+        other: "%{count} people"
       total_votes:
         one: "%{count} vote"
         other: "%{count} votes"
@@ -930,6 +1055,8 @@ en:
     pinned: Pinned toot
     reblogged: boosted
     sensitive_content: Sensitive content
+  tags:
+    does_not_match_previous_name: does not match the previous name
   terms:
     body_html: |
       <h2>Privacy Policy</h2>
@@ -1047,7 +1174,9 @@ en:
         disable: While your account is frozen, your account data remains intact, but you cannot perform any actions until it is unlocked.
         silence: While your account is limited, only people who are already following you will see your toots on this server, and you may be excluded from various public listings. However, others may still manually follow you.
         suspend: Your account has been suspended, and all of your toots and your uploaded media files have been irreversibly removed from this server, and servers where you had followers.
+      get_in_touch: You can reply to this e-mail to get in touch with the staff of %{instance}.
       review_server_policies: Review server policies
+      statuses: 'Specifically, for:'
       subject:
         disable: Your account %{acct} has been frozen
         none: Warning for %{acct}
diff --git a/config/locales/eo.yml b/config/locales/eo.yml
index c71b42fdd8161feef7ab8ab12f3ddb897fbff9a7..dd3ca1079b02eda3dcc5a17ffec2203df4040b05 100644
--- a/config/locales/eo.yml
+++ b/config/locales/eo.yml
@@ -1,7 +1,7 @@
 ---
 eo:
   about:
-    about_hashtag_html: Ĉi tiuj estas la publikaj fajfoj markitaj per <strong>#%{hashtag}</strong>. Vi povas interagi kun ili se vi havas konton ie ajn en la fediverse.
+    about_hashtag_html: Ĉi tiuj estas la publikaj mesaĝoj markitaj per <strong>#%{hashtag}</strong>. Vi povas interagi kun ili se vi havas konton ie ajn en la fediverse.
     about_mastodon_html: Mastodon estas socia reto bazita sur malfermitaj retaj protokoloj kaj sur libera malfermitkoda programo. Ĝi estas sencentra kiel retmesaĝoj.
     about_this: Pri
     active_count_after: aktiva
@@ -17,13 +17,12 @@ eo:
     contact_unavailable: Ne disponebla
     discover_users: Malkovri uzantojn
     documentation: Dokumentado
-    extended_description_html: |
-      <h3>Bona loko por reguloj</h3>
-      <p>La detala priskribo ne estis elektita.</p>
     federation_hint_html: Per konto ĉe %{instance}, vi povos sekvi homojn ĉe iu ajn Mastodon nodo kaj preter.
-    generic_description: "%{domain} estas unu servilo en la reto"
     get_apps: Provu telefonan aplikaĵon
     hosted_on: "%{domain} estas nodo de Mastodon"
+    instance_actor_flash: |
+      Ĉi tiu konto estas virtuala ulo uzata por reprezenti la servilon mem kaj ne iun apartan uzanton.
+      Ĝi estas uzata por frataraj celoj kaj ĝi ne devus esti blokita krom se vi volas bloki la tutan servilon, tiuokaze vi devus uzi domajnan blokadon.
     learn_more: Lerni pli
     privacy_policy: Privateca politiko
     see_whats_happening: Vidi kio okazas
@@ -35,6 +34,10 @@ eo:
     status_count_before: Kie skribiĝis
     tagline: Sekvi amikojn kaj trovi novan onin
     terms: Uzkondiĉoj
+    unavailable_content: Nedisponebla enhavo
+    unavailable_content_description:
+      domain: Servilo
+      reason: 'Kialo:'
     user_count_after:
       one: uzanto
       other: uzantoj
@@ -53,6 +56,7 @@ eo:
     media: Aŭdovidaĵoj
     moved_html: "%{name} moviĝis al %{new_profile_link}:"
     network_hidden: Tiu informo ne estas disponebla
+    never_active: Neniam
     nothing_here: Estas nenio ĉi tie!
     people_followed_by: Sekvatoj de %{name}
     people_who_follow: Sekvantoj de %{name}
@@ -174,6 +178,7 @@ eo:
       statuses: Mesaĝoj
       subscribe: Aboni
       suspended: Haltigita
+      time_in_queue: Atendado en atendovico %{time}
       title: Kontoj
       unconfirmed_email: Nekonfirmita retadreso
       undo_silenced: Malfari kaŝon
@@ -182,6 +187,7 @@ eo:
       username: Uzantnomo
       warn: Averti
       web: Reto
+      whitelisted: En la blanka listo
     action_logs:
       actions:
         assigned_to_self_report: "%{name} asignis signalon %{target} al si mem"
@@ -225,11 +231,14 @@ eo:
       delete: Forigi
       destroyed_msg: Emoĝio sukcese forigita!
       disable: Malebligi
+      disabled: Malebligita
       disabled_msg: Emoĝio sukcese malebligita
       emoji: Emoĝio
       enable: Ebligi
+      enabled: Ebligita
       enabled_msg: Tiu emoĝio estis sukcese ebligita
       image_hint: PNG ĝis 50KB
+      list: Listo
       listed: Listigita
       new:
         title: Aldoni novan propran emoĝion
@@ -237,6 +246,7 @@ eo:
       shortcode: Mallonga kodo
       shortcode_hint: AlmenaÅ­ 2 signoj, nur literoj, ciferoj kaj substrekoj
       title: Propraj emoĝioj
+      unlist: Nelistigi
       unlisted: Nelistigita
       update_failed_msg: Ĝisdatigi tiun emoĝion ne eblis
       updated_msg: Emoĝio sukcese ĝisdatigita!
@@ -249,6 +259,7 @@ eo:
       feature_profile_directory: Profilujo
       feature_registrations: Registriĝoj
       feature_relay: Federacia ripetilo
+      feature_spam_check: KontraÅ­-spamo
       feature_timeline_preview: Templinio antaÅ­vidi
       features: Funkcioj
       hidden_service: Federacio kun kaŝitaj servoj
@@ -260,15 +271,23 @@ eo:
       space: Memorspaca uzado
       title: Kontrolpanelo
       total_users: uzantoj sume
-      trends: Furoroj
+      trends: Furoraĵoj
       week_interactions: interagoj tiusemajne
       week_users_active: aktivaj tiusemajne
       week_users_new: uzantoj tiusemajne
+      whitelist_mode: En la blanka listo
+    domain_allows:
+      add_new: En la blanka listo domajno
+      created_msg: Domajno sukcese blanklistigita
+      destroyed_msg: Domajno estis forigita de la blanklisto
+      undo: Forigi de la blanklisto
     domain_blocks:
       add_new: Aldoni novan
       created_msg: Domajna blokado en traktado
       destroyed_msg: Domajna blokado malfarita
       domain: Domajno
+      edit: Redakti domajna blokado
+      existing_domain_block_html: Vi jam trudis pli striktajn limojn al %{name}, vi devas <a href="%{unblock_url}">malbloki ĝin</a> unue.
       new:
         create: Krei blokadon
         hint: La domajna blokado ne evitigos kreadon de novaj kontoj en la datumbazo, sed aplikos specifajn kontrolajn agojn sur ĉi tiujn kontojn aŭtomate kaj retroaktive.
@@ -278,6 +297,8 @@ eo:
           silence: Kaŝi
           suspend: Haltigi
         title: Nova domajna blokado
+      private_comment: Privata komento
+      public_comment: Publika komento
       reject_media: Malakcepti aÅ­dovidajn dosierojn
       reject_media_hint: Forigas aŭdovidaĵojn loke konservitajn kaj rifuzas alŝuti ajnan estonte. Senzorge pri haltigoj
       reject_reports: Malakcepti raportojn
@@ -297,6 +318,7 @@ eo:
         title: Malfari domajnan blokadon por %{domain}
         undo: Malfari
       undo: Malfari
+      view: Vidi domajna blokado
     email_domain_blocks:
       add_new: Aldoni novan
       created_msg: Retadreso sukcese aldonita al la nigra listo
@@ -320,6 +342,8 @@ eo:
         all: Ĉiuj
         limited: Limigita
         title: Kontrolo
+      private_comment: Privata komento
+      public_comment: Publika komento
       title: Federacio
       total_blocked_by_us: Blokitaj de ni
       total_followed_by_them: Sekvataj de ili
@@ -334,6 +358,8 @@ eo:
         expired: Eksvalida
         title: Filtri
       title: Invitoj
+    pending_accounts:
+      title: Pritraktataj kontoj (%{count})
     relays:
       add_new: Aldoni novan ripetilon
       delete: Forigi
@@ -395,6 +421,11 @@ eo:
       custom_css:
         desc_html: Ŝanĝi la aspekton per CSS ŝargita en ĉiu pago
         title: Propra CSS
+      domain_blocks:
+        all: Al ciuj
+        disabled: Al neniu
+        title: Vidi domajna blokado
+        users: Al ensalutintaj lokaj uzantoj
       hero:
         desc_html: Montrata en la ĉefpaĝo. Almenaŭ 600x100px rekomendita. Kiam ne agordita, la bildeto de la servilo estos uzata
         title: Kapbildo
@@ -445,6 +476,8 @@ eo:
         desc_html: Vi povas skribi vian propran privatecan politikon, viajn uzkondiĉojn aŭ aliajn leĝaĵojn. Vi povas uzi HTML-etikedojn
         title: Propraj uzkondiĉoj
       site_title: Nomo de la servilo
+      spam_check_enabled:
+        title: KontraÅ­-spamo aÅ­tomatige
       thumbnail:
         desc_html: Uzata por antaÅ­vidoj per OpenGraph kaj per API. 1200x630px rekomendita
         title: Bildeto de la servilo
@@ -452,12 +485,16 @@ eo:
         desc_html: Montri publikan templinion en komenca paĝo
         title: Tempolinia antaÅ­vido
       title: Retejaj agordoj
+      trends:
+        desc_html: Publike montri antaÅ­e kontrolitajn kradvortojn, kiuj nune furoras
+        title: Furoraj kradvortoj
     statuses:
       back_to_account: Reveni al konta paĝo
       batch:
         delete: Forigi
-        nsfw_off: Marki ne tikla
+        nsfw_off: Marki netikla
         nsfw_on: Marki tikla
+      deleted: Forigita
       failed_to_execute: Ekigo malsukcesa
       media:
         title: Aŭdovidaĵoj
@@ -465,21 +502,21 @@ eo:
       no_status_selected: Neniu mesaĝo estis ŝanĝita ĉar neniu estis elektita
       title: Mesaĝoj de la konto
       with_media: Kun aŭdovidaĵoj
-    subscriptions:
-      callback_url: Revena URL
-      confirmed: Konfirmita
-      expires_in: Eksvalidiĝas je
-      last_delivery: Lasta livero
-      title: WebSub
-      topic: Temo
     tags:
-      accounts: Kontoj
-      hidden: Kaŝitaj
-      hide: Kaŝi de la profilujo
+      context: Kunteksto
+      directory: En la adresaro
+      in_directory: "%{count} en adresaro"
+      last_active: Lasta aktiva
+      most_popular: La plej populara
+      most_recent: Plej lasta
       name: Kradvorto
+      review: La statuso de la recenzo
+      reviewed: Recenzis
       title: Kradvortoj
-      unhide: Montri en la profilujo
-      visible: Videblaj
+      trending_right_now: Nunaj furoraĵoj
+      unique_uses_today: "%{count} uzas hodiaÅ­"
+      unreviewed: Ne recenzis
+      updated_msg: Kradvorto agordoj ĝisdatigis sukcese
     title: Administrado
     warning_presets:
       add_new: Aldoni novan
@@ -495,9 +532,20 @@ eo:
       body: "%{reporter} signalis %{target}"
       body_remote: Iu de %{domain} signalis %{target}
       subject: Nova signalo por %{instance} (#%{id})
+    new_trending_tag:
+      body: 'La kradvorto #%{name} furoras hodiaÅ­, sed ankoraÅ­ ne estis kontrolita. Äœi ne aperos publike sen via aprobo. Se vi ne volas tion, simple konservu la formularon tiel kiel.'
+      subject: Nova kradvorto kontrolebla en %{instance} (#%{name})
+  aliases:
+    add_new: Krei alinomon
+    created_msg: Kreis novan alinomon sukcese. Vi povas inici la transloki el la malnovan konton nun.
+    deleted_msg: Forigis la alinomon sukcese. Transloki el tiu konto al ĉi tiu ne plu eblos.
+    remove: Malligili alinomon
   appearance:
     advanced_web_interface: Altnivela retpaĝa interfaco
-    confirmation_dialogs: Konfirmaj dialogoj
+    advanced_web_interface_hint: 'Se vi volas uzi la tutan larĝecon de via ekrano, la kompleksa reta interfaco permesas al vi agordi multajn malsamajn kolumnojn por vidi tiom da informoj kiom vi volas samtempe: Hejmo, sciigoj, fratara tempolinio, kaj ajna kvanto de listoj kaj kradvortoj.'
+    animations_and_accessibility: Animacioj kaj alirebleco
+    confirmation_dialogs: Konfirmaj fenestroj
+    discovery: Eltrovo
     sensitive_content: Tikla enhavo
   application_mailer:
     notification_preferences: Ŝanĝi retmesaĝajn preferojn
@@ -518,9 +566,12 @@ eo:
     apply_for_account: Peti inviton
     change_password: Pasvorto
     checkbox_agreement_html: Mi samopinii al la <a href="%{rules_path}" target="_blank">Servo reguloj</a> kaj <a href="%{terms_path}" target="_blank">kondiĉo al servadon</a>
-    confirm_email: Konfirmi retadreson
+    checkbox_agreement_without_rules_html: Mi konsenti la <a href="%{terms_path}" target="_blank">reguloj de servado</a>
     delete_account: Forigi konton
     delete_account_html: Se vi deziras forigi vian konton, vi povas <a href="%{path}">fari tion ĉi tie</a>. Vi bezonos konfirmi vian peton.
+    description:
+      prefix_invited_by_user: "@%{name} invitigi vin aligiĝi ĉi tiu servilo de Mastodon!"
+      prefix_sign_up: Registriĝi ĉe Mastodon hodiaŭ!
     didnt_get_confirmation: Ĉu vi ne ricevis la instrukciojn por konfirmi?
     forgot_password: Pasvorto forgesita?
     invalid_reset_password_token: Ä´etono por restarigi pasvorton nevalida aÅ­ eksvalida. Bonvolu peti novan.
@@ -538,6 +589,11 @@ eo:
     reset_password: Ŝanĝi pasvorton
     security: Sekureco
     set_new_password: Elekti novan pasvorton
+    setup:
+      title: Agordi
+    status:
+      account_status: Statuso de la konto
+      functional: Via konto estas plene funkcianta.
     trouble_logging_in: Äœeni ensaluti?
   authorize_follow:
     already_following: Vi jam sekvas tiun konton
@@ -550,6 +606,10 @@ eo:
       return: Montri la profilon de la uzanto
       web: Iri al reto
     title: Sekvi %{acct}
+  challenge:
+    confirm: DaÅ­rigi
+    invalid_password: Nevalida pasvorto
+    prompt: Konfirmi pasvorton por daÅ­rigi
   datetime:
     distance_in_words:
       about_x_hours: "%{count}h"
@@ -565,26 +625,21 @@ eo:
       x_months: "%{count}mo"
       x_seconds: "%{count}s"
   deletes:
-    bad_password_msg: Malĝusta pasvorto
     confirm_password: Enmetu vian nunan pasvorton por konfirmi vian identecon
-    description_html: Tio <strong>porĉiame kaj neŝanĝeble</strong> forigos la enhavon de via konto kaj malaktivigos ĝin. Via uzantnomo restos rezervita por eviti postajn trompojn pri identeco.
+    confirm_username: Enigi vian uzantnomon por konfirmi la procedo
     proceed: Forigi konton
     success_msg: Via konto estis sukcese forigita
-    warning_html: La forigo de la enhavo estas certa nur por ĉi tiu aparta servilo. Enhavo, kiu estis disvastigita verŝajne lasos spurojn. Eksterretaj serviloj kaj serviloj, kiuj ne abonas viajn ĝisdatigojn ne ĝisdatigos siajn datumbazojn.
-    warning_title: Disponebleco de disvastigita enhavo
   directories:
     directory: Profilujo
-    enabled: Vi estas listigata en la profilujo.
-    enabled_but_waiting: Vi elektis esti listigata en la profilujo, sed vi ankoraÅ­ ne havas la minimuman kvanton da sekvantoj (%{min_followers}) por esti listigata.
     explanation: Malkovru uzantojn per iliaj interesoj
     explore_mastodon: Esplori %{title}
-    how_to_enable: Vi ankoraŭ ne donis permeson listigi vin en la profilujo. Vi povas doni permeson ĉi-sube. Uzu kradvortojn en via biografia teksto por esti listigata sub specifaj kradvortoj!
-    people:
-      one: "%{count} persono"
-      other: "%{count} personoj"
+  domain_validator:
+    invalid_domain: ne estas valida domajna nomo
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Vi ne havas la rajton por vidi ĉi tiun paĝon.
     '404': La paĝo ke kiun vi serĉas ne ekzistas ĉi tie.
+    '406': This page is not available in the requested format.
     '410': La paĝo, kiun vi serĉas, ne plu ekzistas ĉi tie.
     '422':
       content: Sekureca konfirmo malsukcesa. Ĉu vi blokas kuketojn?
@@ -593,6 +648,7 @@ eo:
     '500':
       content: Ni bedaÅ­ras, io malsukcesis niaflanke.
       title: Ĉi tiu paĝo ne estas ĝusta
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: |-
       Por uzi la retan aplikaĵon de Mastodon, bonvolu ebligi JavaScript. Alimaniere, provu unu el la
       <a href="%{apps_path}">operaciumaj aplikaĵoj</a> por Mastodon por via platformo.
@@ -618,6 +674,7 @@ eo:
     add_new: Aldoni novan
     errors:
       limit: Vi jam elstarigis la maksimuman kvanton da kradvortoj
+    hint_html: "<strong>Kio estas la trajtaj kradvortoj?</strong> Ili bone videblas en via publika profilo kaj permesas al homoj trarigardi viajn publikajn mesaĝojn specife laŭ tiuj kradvortoj. Ili estas bonaj iloj por sekvi la evoluon de kreadaj laboroj aŭ longdaŭraj projektoj."
   filters:
     contexts:
       home: Hejma templinio
@@ -638,6 +695,7 @@ eo:
     developers: Programistoj
     more: Pli…
     resources: Rimedoj
+    trending_now: Nunaj furoraĵoj
   generic:
     all: Ĉio
     changes_saved_msg: Ŝanĝoj sukcese konservitaj!
@@ -648,7 +706,7 @@ eo:
       one: Io mise okazis! Bonvolu konsulti la suban erar-raporton
       other: Io mise okazis! Bonvolu konsulti la subajn %{count} erar-raportojn
   html_validator:
-    invalid_markup: 'havas malvalida HTML markado: %{error}'
+    invalid_markup: 'havas nevalidan HTML-markadon: %{error}'
   identity_proofs:
     active: Aktiva
     authorize: Jes, permesi
@@ -713,9 +771,7 @@ eo:
       too_many: Aldoni pli ol 4 dosierojn ne eblas
   migrations:
     acct: uzantnomo@domajno de la nova konto
-    currently_redirecting: 'Via profilo alidirektos al:'
-    proceed: Konservi
-    updated_msg: Via agordo pri konta migrado estis sukcese ĝisdatigita!
+    proceed_with_move: Translokigi sekvantoj
   moderation:
     title: Kontrolado
   notification_mailer:
@@ -780,7 +836,10 @@ eo:
       too_many_options: ne povas enhavi pli da %{max} proponoj
   preferences:
     other: Aliaj aferoj
+    posting_defaults: Afiŝadoj defaŭltoj
+    public_timelines: Publikaj templinioj
   relationships:
+    activity: Konto aktiveco
     dormant: Dormanta
     last_active: Lasta aktiva
     most_recent: Plej lasta
@@ -788,6 +847,9 @@ eo:
     mutual: Reciproka
     primary: Primara
     relationship: Rilato
+    remove_selected_domains: Forigi ĉiuj sekvantojn el la selektitajn domajnojn
+    remove_selected_followers: Forigi selektitajn sekvantojn
+    remove_selected_follows: Malsekvi selektitajn uzantojn
     status: Statuso de la konto
   remote_follow:
     acct: Enmetu vian uzantnomo@domajno de kie vi volas agi
@@ -806,10 +868,6 @@ eo:
     reply:
       proceed: Konfirmi la respondon
       prompt: 'Vi volas respondi al ĉi tiu mesaĝo:'
-  remote_unfollow:
-    error: Eraro
-    title: Titolo
-    unfollowed: Ne plu sekvita
   scheduled_statuses:
     over_daily_limit: Vi transpasis la limigon al %{limit} samtage planitaj mesaĝoj
     over_total_limit: Vi transpasis la limigon al %{limit} planitaj mesaĝoj
@@ -858,6 +916,7 @@ eo:
   settings:
     account: Konto
     account_settings: Agordoj de konto
+    aliases: Kontoj alinomoj
     appearance: Apero
     authorized_apps: Rajtigitaj aplikaĵoj
     back: Reveni al Mastodon
@@ -953,6 +1012,7 @@ eo:
         silence: Dum via konto estas limigita, nur tiuj, kiuj jam sekvas vin, vidos viajn mesaĝojn en ĉi tiu servilo, kaj vi povus esti ekskludita de diversaj publikaj listoj. Tamen, aliaj ankoraŭ povas mane sekvi vin.
         suspend: Via konto estis haltigita, kaj ĉiuj el viaj mesaĝoj kaj alŝutitaj aŭdovidaj dosieroj estis nemalfareble forigitaj de ĉi tiu servilo, kaj de la serviloj, kie vi havis sekvantojn.
       review_server_policies: Superrigardi servilajn politikojn
+      statuses: 'Specife, per:'
       subject:
         disable: Via konto %{acct} estas frostigita
         none: Averto por %{acct}
diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d70007d271b6da125d3da18840fbb4cf96aba5dc
--- /dev/null
+++ b/config/locales/es-AR.yml
@@ -0,0 +1,402 @@
+---
+es-AR:
+  about:
+    about_hashtag_html: Estos son toots públicos etiquetados con <strong>#%{hashtag}</strong>. Si tenés una cuenta en cualquier parte del fediverso, podés interactuar con ellos.
+    about_mastodon_html: Mastodon es una red social basada en protocolos abiertos de la web y es software libre y de código abierto. Es descentralizada, como el correo electrónico.
+    about_this: Acerca de Mastodon
+    active_count_after: activo
+    active_footnote: Usuarios activos mensualmente (MAU)
+    administered_by: 'Administrado por:'
+    api: API
+    apps: Aplicaciones móviles
+    apps_platforms: Usá Mastodon desde iOS, Android y otras plataformas
+    browse_directory: Explorá el directorio de perfiles y filtrá por intereses
+    browse_public_posts: Explorá un flujo en tiempo real de toots públicos en Mastodon
+    contact: Contacto
+    contact_missing: No establecido
+    contact_unavailable: No disponible
+    discover_users: Descubrir usuarios
+    documentation: Documentación
+    federation_hint_html: Con una cuenta en %{instance} vas a poder seguir a gente de cualquier servidor de Mastodon y más allá.
+    get_apps: Probá una aplicación móvil
+    hosted_on: Mastodon alojado en %{domain}
+    instance_actor_flash: |
+      Esta cuenta es un actor virtual usado para representar al propio servidor y no a ningún usuario individual.
+      Se usa para fines federativos y no debe ser bloqueado a menos que quieras bloquear toda la instancia, en cuyo caso deberías usar un bloqueo de dominio.
+    learn_more: Aprendé más
+    privacy_policy: Política de privacidad
+    see_whats_happening: Esto es lo que está pasando ahora
+    server_stats: 'Estadísticas del servidor:'
+    source_code: Código fuente
+    status_count_after:
+      one: estado
+      other: estados
+    status_count_before: Que enviaron
+    tagline: Seguí a tus amigos y descubrí nueva gente
+    terms: Términos del servicio
+    unavailable_content: Contenido no disponible
+    unavailable_content_description:
+      domain: Servidor
+      reason: Razón
+      rejecting_media: 'Los archivos de medios de este servidor no van a ser procesados y no se mostrarán miniaturas, lo que requiere un clic manual hacia el archivo original:'
+      silenced: 'Los toots de estos servidores se ocultarán en las líneas temporales y conversaciones públicas, y no se generarán notificaciones de las interacciones de sus usuarios, a menos que los estés siguiendo:'
+      suspended: 'No se procesarán, almacenarán o intercambiarán datos de estos servidores, haciendo imposible cualquier interacción o comunicación con los usuarios de estos servidores:'
+    unavailable_content_html: Mastodon generalmente te permite ver contenido e interactuar con usuarios de cualquier otro servidor en el fediverso. Estas son las excepciones que se hicieron en este servidor en particular.
+    user_count_after:
+      one: usuario
+      other: usuarios
+    user_count_before: Hogar de
+    what_is_mastodon: "¿Qué es Mastodon?"
+  accounts:
+    choices_html: 'Recomendados de %{name}:'
+    endorsements_hint: Podés recomendar a gente que seguís desde la interface web, y van a aparecerán acá.
+    featured_tags_hint: Pdés destacar etiquetas específicas que se mostrarán acá.
+    follow: Seguir
+    followers:
+      one: Seguidor
+      other: Seguidores
+    following: Siguiendo
+    joined: Se unió en %{date}
+    last_active: última vez activo
+    link_verified_on: La propiedad de este enlace fue verificada el %{date}
+    media: Medios
+    moved_html: "%{name} se mudó a %{new_profile_link}:"
+    network_hidden: Esta información no está disponible
+    never_active: Nunca
+    nothing_here: "¡No hay nada acá!"
+    people_followed_by: "%{name} sigue a estas personas"
+    people_who_follow: Estas personas siguen a %{name}
+    pin_errors:
+      following: Ya tenés que estar siguiendo a la persona que querés recomendar
+    posts:
+      one: Toot
+      other: Toots
+    posts_tab_heading: Toots
+    posts_with_replies: Toots con respuestas
+    reserved_username: El nombre de usuario está reservado
+    roles:
+      admin: Administrador
+      bot: Bot
+      moderator: Moderador
+    unavailable: Perfil no disponible
+    unfollow: Dejar de seguir
+  admin:
+    account_actions:
+      action: Ejecutar acción
+      title: Ejecutar acción de moderación en %{acct}
+    account_moderation_notes:
+      create: Dejar nota
+      created_msg: "¡Nota de moderación creada exitosamente!"
+      delete: Eliminar
+      destroyed_msg: "¡Nota de moderación destruída exitosamente!"
+    accounts:
+      approve: Aprobar
+      approve_all: Aprobar todas
+      are_you_sure: "¿Estás seguro?"
+      avatar: Avatar
+      by_domain: Dominio
+      change_email:
+        changed_msg: "¡Correo electrónico de cuenta cambiado exitosamente!"
+        current_email: Correo electrónico actual
+        label: Cambiar correo electrónico
+        new_email: Nuevo correo electrónico
+        submit: Cambiar correo electrónico
+        title: Cambiar correo electrónico para %{username}
+      confirm: Confirmar
+      confirmed: Confirmado
+      confirming: Confirmando
+      deleted: Eliminado
+      demote: Bajar de nivel
+      disable: Deshabilitar
+      disable_two_factor_authentication: Deshabilitar 2FA
+      disabled: Deshabilitada
+      display_name: Nombre para mostrar
+      domain: Dominio
+      edit: Editar
+      email: Correo electrónico
+      email_status: Estado del correo
+      enable: Habilitar
+      enabled: Habilitada
+      feed_url: Dirección de la fuente web
+      followers: Seguidores
+      followers_url: Dirección web de los seguidores
+      header: Cabecera
+      inbox_url: Dirección web de la bandeja de entrada
+      invited_by: Invitado por
+      ip: Dirección IP
+      joined: Se unió en
+      location:
+        all: Todas
+        local: Local
+        remote: Remota
+        title: Ubicación
+      login_status: Estado del inicio de sesión
+      media_attachments: Adjuntos
+      memorialize: Convertir en recordatorio
+      moderation:
+        active: Activa
+        all: Todas
+        pending: Pendiente
+        silenced: Silenciados
+        suspended: Suspendidos
+        title: Moderación
+      moderation_notes: Notas de moderación
+      most_recent_activity: Actividad más reciente
+      most_recent_ip: Direcciones IP más recientes
+      no_account_selected: No se cambió ninguna cuenta ya que ninguna fue seleccionada
+      no_limits_imposed: Sin límites impuestos
+      not_subscribed: No suscripto
+      outbox_url: Dirección web de la bandeja de salida
+      pending: Revisión pendiente
+      perform_full_suspension: Suspender
+      profile_url: Dirección web del perfil
+      promote: Promocionar
+      protocol: Protocolo
+      public: Pública
+      push_subscription_expires: La suscripción PuSH vence
+      redownload: Recargar perfil
+      reject: Rechazar
+      reject_all: Rechazar todas
+      remove_avatar: Quitar avatar
+      remove_header: Quitar cabecera
+      resend_confirmation:
+        already_confirmed: Este usuario ya está confirmado
+        send: Reenviar correo electrónico de confirmación
+        success: "¡Correo electrónico de confirmación enviado exitosamente!"
+      reset: Restablecer
+      reset_password: Cambiar contraseña
+      resubscribe: Resuscribir
+      role: Permisos
+      roles:
+        admin: Administrador
+        moderator: Moderador
+        staff: Equipo
+        user: Usuario
+      search: Buscar
+      shared_inbox_url: Dirección web de la bandeja de entrada compartida
+      show:
+        created_reports: Informes hechos
+        targeted_reports: Denunciado por otros
+      silence: Silenciar
+      silenced: Silenciadas
+      statuses: Estados
+      subscribe: Suscribirse
+      suspended: Suspendidas
+      title: Cuentas
+      unconfirmed_email: Correo electrónico sin confirmar
+      undo_silenced: Deshacer silenciado
+      undo_suspension: Deshacer suspensión
+      unsubscribe: Desuscribirse
+      username: Nombre de usuario
+      warn: Advertir
+      web: Web
+      whitelisted: Aprobadas
+    action_logs:
+      actions:
+        assigned_to_self_report: "%{name} se asignó la denuncia %{target} a sí"
+        change_email_user: "%{name} cambió la dirección de correo electrónico del usuario %{target}"
+        confirm_user: "%{name} confirmó la dirección de correo del usuario %{target}"
+        create_account_warning: "%{name} envió una advertencia a %{target}"
+        create_custom_emoji: "%{name} subió nuevo emoji %{target}"
+        create_domain_block: "%{name} bloqueó el dominio %{target}"
+        create_email_domain_block: "%{name} desaprobó el dominio de correo electrónico %{target}"
+        demote_user: "%{name} bajó de nivel al usuario %{target}"
+        destroy_custom_emoji: "%{name} destruyó el emoji %{target}"
+        destroy_domain_block: "%{name} desbloqueó el dominio %{target}"
+        destroy_email_domain_block: "%{name} aprobó el dominio de correo electrónico %{target}"
+        destroy_status: "%{name} eliminó el estado de %{target}"
+        disable_2fa_user: "%{name} deshabilitó el requerimiento de dos factores para el usuario %{target}"
+        disable_custom_emoji: "%{name} deshabilitó el emoji %{target}"
+        disable_user: "%{name} deshabilitó el inicio de sesión para el usuario %{target}"
+        enable_custom_emoji: "%{name} habilitó el emoji %{target}"
+        enable_user: "%{name} habilitó el inicio de sesión para el usuario %{target}"
+        memorialize_account: "%{name} convirtió la cuenta de %{target} en una página de recordatorio"
+        remove_avatar_user: "%{name} quitó el avatar de %{target}"
+        reopen_report: "%{name} reabrió la denuncia %{target}"
+        reset_password_user: "%{name} cambió la contraseña del usuario %{target}"
+        resolve_report: "%{name} resolvió la denuncia %{target}"
+        silence_account: "%{name} silenció la cuenta de %{target}"
+        suspend_account: "%{name} suspendió la cuenta de %{target}"
+        unassigned_report: "%{name} desasignó la denuncia %{target}"
+        unsilence_account: "%{name} quitó el silenciado de la cuenta de %{target}"
+        unsuspend_account: "%{name} quitó la suspensión de la cuenta de %{target}"
+        update_custom_emoji: "%{name} actualizó el emoji %{target}"
+        update_status: "%{name} actualizó el estado de %{target}"
+      deleted_status: "(estado borrado)"
+      title: Registro de auditoría
+    custom_emojis:
+      assign_category: Asignar categoría
+      by_domain: Dominio
+      copied_msg: Copia local del emoji creada exitosamente
+      copy: Copiar
+      copy_failed_msg: No se pudo realizar una copia local de ese emoji
+      create_new_category: Crear nueva categoría
+      created_msg: "¡Emoji creado exitosamente!"
+      delete: Eliminar
+      destroyed_msg: "¡Emoji destruido exitosamente!"
+      disable: Deshabilitar
+      disabled: Deshabilitado
+      disabled_msg: Se deshabilitó ese emoji exitosamente
+      emoji: Emoji
+      enable: Habilitar
+      enabled: Habilitado
+      enabled_msg: Se habilitó ese emoji exitosamente
+      image_hint: PNG de hasta 50KB
+      list: Lista
+      listed: Listados
+      new:
+        title: Agregar nuevo emoji personalizado
+      overwrite: Sobreescribir
+      shortcode: Código corto
+      shortcode_hint: Al menos 2 caracteres, sólo caracteres alfanuméricos y subguiones ("_")
+      title: Emojis personalizados
+      uncategorized: Sin categoría
+      unlist: No agregar a lista
+      unlisted: No listado
+      update_failed_msg: No se pudo actualizar ese emoji
+      updated_msg: "¡Emoji actualizado exitosamente!"
+      upload: Subir
+    dashboard:
+      authorized_fetch_mode: Modo seguro
+      backlog: trabajos registrados
+      config: Configuración
+      feature_deletions: Eliminaciones de cuenta
+      feature_invites: Enlaces de invitación
+      feature_profile_directory: Directorio de perfiles
+      feature_registrations: Registros
+      feature_relay: Relé de federación
+      feature_spam_check: Anti-spam
+      feature_timeline_preview: Previsualización de la línea temporal
+      features: Funciones
+      hidden_service: Federación con servicios ocultos
+      open_reports: abrir denuncias
+      pending_tags: etiquetas esperando revisión
+      pending_users: usuarios esperando revisión
+      recent_users: Usuarios recientes
+      search: Búsqueda de texto completo
+      single_user_mode: Modo de usuario único
+      software: Software
+      space: Uso del espacio
+      title: Panel
+      total_users: usuarios en total
+      trends: Tendencias
+      week_interactions: interacciones esta semana
+      week_users_active: activos esta semana
+      week_users_new: usuarios esta semana
+      whitelist_mode: Modo de aprobación
+    domain_allows:
+      add_new: Aprobar dominio
+      created_msg: El dominio se aprobó exitosamente
+      destroyed_msg: El dominio no se aprobó
+      undo: No aprobado
+    domain_blocks:
+      add_new: Agregar nuevo bloquedo de dominio
+      created_msg: Ahora se está procesando el bloquedo de dominio
+      destroyed_msg: Se deshizo el bloqueo de dominio
+      domain: Dominio
+      edit: Editar bloqueo de dominio
+      existing_domain_block_html: Ya le aplicaste límites más estrictos a %{name}, por lo que primero necesitás <a href="%{unblock_url}">desbloquearlo</a>.
+      new:
+        create: Crear bloqueo
+        hint: El bloqueo de dominio no va a prevenir la creación de toots de cuenta en la base de datos, pero se aplicarán métodos específicos de moderación en esas cuentas, retroactiva y automáticamente.
+        severity:
+          desc_html: "<strong>Silenciar</strong> hará que los toots de la cuenta sean invisibles a quienes no estén siguiendo esa cuenta. <strong>Suspender</strong> quitará todo el contenido, archivos de medio y datos de perfil de la cuenta. Usá <strong>Ninguno</strong> si simplemente querés rechazar archivos de medios."
+          noop: Ninguno
+          silence: Silenciar
+          suspend: Suspender
+        title: Nuevo bloquedo de dominio
+      private_comment: Comentario privado
+      private_comment_hint: Comentario sobre la limitación de este dominio, para uso interno de los moderadores.
+      public_comment: Comentario público
+      public_comment_hint: Comentario sobre la limitación de este dominio para el público en general, si está habilitada la publicación de lista de limitaciones de dominio.
+      reject_media: Rechazar archivos de medio
+      reject_media_hint: Quita los archivos de medio almacenados e impide la descarga en el futuro. Irrelevante para suspensiones.
+      reject_reports: Rechazar denuncias
+      reject_reports_hint: Ignora todas las denuncias que vengan de este dominio. Irrelevante para suspensiones.
+      rejecting_media: rechazando archivos de medio
+      rejecting_reports: rechazando denuncias
+      severity:
+        silence: silenciado
+        suspend: suspendido
+      show:
+        affected_accounts:
+          one: Una cuenta afectada en la base de datos
+          other: "%{count} cuentas afectadas en la base de datos"
+        retroactive:
+          silence: Quitar silenciado de las cuentas existentes afectadas de este dominio
+          suspend: Quitar suspensión de las cuentas existentes afectadas de este dominio
+        title: Deshacer el bloqueo de dominio para %{domain}
+        undo: Deshacer
+      undo: Deshacer bloqueo de dominio
+      view: Ver bloqueo de dominio
+    email_domain_blocks:
+      add_new: Agregar nuevo
+      created_msg: Se desaprobó dominio de correo electrónico exitosamente
+      delete: Eliminar
+      destroyed_msg: Se aprobó dominio de correo electrónico exitosamente
+      domain: Dominio
+      new:
+        create: Agregar dominio
+        title: Nueva desaprobación de correo electrónico
+      title: Desaprobación de correo electrónico
+    followers:
+      back_to_account: Volver a la cuenta
+      title: Seguidores de %{acct}
+    instances:
+      by_domain: Dominio
+      delivery_available: La entrega está disponible
+      known_accounts:
+        one: "%{count} cuenta conocida"
+        other: "%{count} cuentas conocidas"
+      moderation:
+        all: Todas
+        limited: Limitadas
+        title: Moderación
+      private_comment: Comentario privado
+      public_comment: Comentario público
+      title: Federación
+      total_blocked_by_us: Bloqueada por nosotros
+      total_followed_by_them: Seguidos por ellos
+      total_followed_by_us: Seguidos por nosotros
+      total_reported: Denuncias sobre ellos
+      total_storage: Adjuntos
+    invites:
+      deactivate_all: Desactivar todas
+      filter:
+        all: Todas
+        available: Disponibles
+        expired: Vencidas
+        title: Filtrar
+      title: Invitaciones
+    pending_accounts:
+      title: Cuentas pendientes (%{count})
+    relays:
+      add_new: Agregar nuevo relé
+      delete: Eliminar
+      description_html: Un <strong>relé de federación</strong> es un servidor intermedio que intercambia grandes volúmenes de toots públicos entre servidores que se suscriben y publican en él. <strong>Puede ayudar a servidores chicos y medianos a descubrir contenido del fediverso</strong>, que de otra manera requeriría que los usuarios locales siguiesen manualmente a personas de servidores remotos.
+      disable: Deshabilitar
+      disabled: Deshabilitado
+      enable: Habilitar
+      enable_hint: Una vez habilitado, tu servidor se suscribirá a todos los toots públicos desde este relé, y comenzará a enviar los toots públicos de este servidor al relé.
+      enabled: Habilitado
+      inbox_url: Dirección web del relé
+      pending: Esperando aprobación del relé
+      save_and_enable: Guardar y habilitar
+  errors:
+    '400': The request you submitted was invalid or malformed.
+    '403': You don't have permission to view this page.
+    '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
+    '410': The page you were looking for doesn't exist here anymore.
+    '422': 
+    '429': Throttled
+    '500': 
+    '503': The page could not be served due to a temporary server failure.
+  invites:
+    expires_in:
+      '1800': 30 minutes
+      '21600': 6 hours
+      '3600': 1 hour
+      '43200': 12 hours
+      '604800': 1 week
+      '86400': 1 day
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 49765cd0a300477c95dee7608cd19951645cb9d9..8a194fdc17252405be083c67d7b8e9a072f2efe2 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -1,25 +1,28 @@
 ---
 es:
   about:
-    about_hashtag_html: Estos son toots públicos etiquetados con <strong>#%{hashtag}</strong>. Puedes interactuar con ellos si tienes una cuenta en cualquier parte del fediverso.
-    about_mastodon_html: Mastodon es un servidor de red social <em>libre y de código abierto</em>. Una alternativa <em>descentralizada</em> a plataformas comerciales, que evita el riesgo de que una única compañía monopolice tu comunicación. Cualquiera puede ejecutar Mastodon y participar sin problemas en la <em>red social</em>.
-    about_this: Acerca de esta instancia
+    about_hashtag_html: Estos son toots públicos etiquetados con <strong>#%{hashtag}</strong>. Puedes interactuar con ellos si tienes una cuenta en el fediverso.
+    about_mastodon_html: Mastodon es una red social basada en protocolos web abiertos y software libre y de código abierto. Está descentralizado como correo electrónico.
+    about_this: Información
     active_count_after: activo
+    active_footnote: Usuarios Activos Mensuales (UAM)
     administered_by: 'Administrado por:'
     api: API
     apps: Aplicaciones móviles
+    apps_platforms: Utiliza Mastodon desde iOS, Android y otras plataformas
+    browse_directory: Navega por el directorio de perfiles y filtra por intereses
+    browse_public_posts: Navega por un transmisión en vivo de publicaciones públicas en Mastodon
     contact: Contacto
     contact_missing: No especificado
     contact_unavailable: N/A
     discover_users: Descubrir usuarios
     documentation: Documentación
-    extended_description_html: |
-      <h3>Un buen lugar para las reglas</h3>
-      <p>La descripción extendida no se ha colocado aún.</p>
     federation_hint_html: Con una cuenta en %{instance} usted podrá seguir a las personas en cualquier servidor de Mastodon y más allá.
-    generic_description: "%{domain} es un servidor en la red"
     get_apps: Probar una aplicación móvil
     hosted_on: Mastodon hosteado en %{domain}
+    instance_actor_flash: |
+      Esta cuenta es un actor virtual usado para representar al servidor y no a ningún usuario individual.
+      Se usa para fines federativos y no debe ser bloqueado a menos que usted quiera bloquear toda la instancia, en cuyo caso se debe utilizar un bloque de dominio.
     learn_more: Aprende más
     privacy_policy: Política de privacidad
     see_whats_happening: Ver lo que está pasando
@@ -31,6 +34,14 @@ es:
     status_count_before: Qué han escrito
     tagline: Seguir a amigos existentes y descubre nuevos
     terms: Condiciones de servicio
+    unavailable_content: Contenido no disponible
+    unavailable_content_description:
+      domain: Servidor
+      reason: 'Motivo:'
+      rejecting_media: Los archivos multimedia de este servidor no serán procesados y no se mostrarán miniaturas, lo que requiere un clic manual en el otro servidor.
+      silenced: Las publicaciones de este servidor no se mostrarán en ningún lugar salvo en el Inicio si sigues al autor.
+      suspended: No podrás seguir a nadie de este servidor, y ningún dato de este será procesado o almacenado, y no se intercambiarán datos.
+    unavailable_content_html: Mastodon generalmente le permite ver contenido e interactuar con usuarios de cualquier otro servidor en el fediverso. Estas son las excepciones que se han hecho en este servidor en particular.
     user_count_after:
       one: usuario
       other: usuarios
@@ -38,6 +49,8 @@ es:
     what_is_mastodon: "¿Qué es Mastodon?"
   accounts:
     choices_html: 'Elecciones de %{name}:'
+    endorsements_hint: Puedes recomendar a gente que sigues desde la interfaz web, y aparecerán allí.
+    featured_tags_hint: Puede presentar hashtags específicos que se mostrarán aquí.
     follow: Seguir
     followers:
       one: Seguidor
@@ -49,6 +62,7 @@ es:
     media: Multimedia
     moved_html: "%{name} se ha trasladado a %{new_profile_link}:"
     network_hidden: Esta información no está disponible
+    never_active: Nunca
     nothing_here: "¡No hay nada aquí!"
     people_followed_by: Usuarios a quien %{name} sigue
     people_who_follow: Usuarios que siguen a %{name}
@@ -111,6 +125,7 @@ es:
       inbox_url: URL de la bandeja de entrada
       invited_by: Invitado por
       ip: IP
+      joined: Unido
       location:
         all: Todos
         local: Local
@@ -178,6 +193,7 @@ es:
       username: Nombre de usuario
       warn: Adevertir
       web: Web
+      whitelisted: Añadido a la lista blanca
     action_logs:
       actions:
         assigned_to_self_report: "%{name} se ha asignado la denuncia %{target} a sí mismo"
@@ -213,19 +229,24 @@ es:
       deleted_status: "(estado borrado)"
       title: Log de auditoría
     custom_emojis:
+      assign_category: Asignar categoría
       by_domain: Dominio
       copied_msg: Copia local del emoji creada con éxito
       copy: Copiar
       copy_failed_msg: No se pudo realizar una copia local de ese emoji
+      create_new_category: Crear una nueva categoría
       created_msg: "¡Emoji creado con éxito!"
       delete: Borrar
       destroyed_msg: "¡Emojo destruido con éxito!"
       disable: Deshabilitar
+      disabled: Desactivado
       disabled_msg: Se deshabilitó con éxito ese emoji
       emoji: Emoji
       enable: Habilitar
+      enabled: Activado
       enabled_msg: Se habilitó con éxito ese emoji
       image_hint: PNG de hasta 50KB
+      list: Lista
       listed: Listados
       new:
         title: Añadir nuevo emoji personalizado
@@ -233,11 +254,14 @@ es:
       shortcode: Código de atajo
       shortcode_hint: Al menos 2 caracteres, solo caracteres alfanuméricos y guiones bajos
       title: Emojis personalizados
+      uncategorized: Sin clasificar
+      unlist: No listado
       unlisted: Sin listar
       update_failed_msg: No se pudo actualizar ese emoji
       updated_msg: "¡Emoji actualizado con éxito!"
       upload: Subir
     dashboard:
+      authorized_fetch_mode: Modo seguro
       backlog: trabajos de backlog
       config: Configuración
       feature_deletions: Borrados de cuenta
@@ -245,9 +269,13 @@ es:
       feature_profile_directory: Directorio de perfil
       feature_registrations: Registros
       feature_relay: Relés de federación
+      feature_spam_check: Contra-spam
+      feature_timeline_preview: Vista previa de la línea de tiempo
       features: Características
       hidden_service: Federación con servicios ocultos
       open_reports: informes abiertos
+      pending_tags: hashtags esperando revisión
+      pending_users: usuarios esperando por revisión
       recent_users: Usuarios recientes
       search: Búsqueda por texto completo
       single_user_mode: Modo único usuario
@@ -259,11 +287,18 @@ es:
       week_interactions: interacciones esta semana
       week_users_active: activo esta semana
       week_users_new: usuarios esta semana
+      whitelist_mode: En la lista blanca
+    domain_allows:
+      add_new: Añadir dominio a la lista blanca
+      created_msg: Dominio añadido a la lista blanca con éxito
+      destroyed_msg: Dominio quitado de la lista blanca con éxito
+      undo: Quitar de la lista blanca
     domain_blocks:
       add_new: Añadir nuevo
       created_msg: El bloque de dominio está siendo procesado
       destroyed_msg: El bloque de dominio se deshizo
       domain: Dominio
+      edit: Editar nuevo dominio bloqueado
       existing_domain_block_html: Ya ha impuesto límites más estrictos a %{name}, necesita <a href="%{unblock_url}">desbloquearlo primero</a>.
       new:
         create: Crear bloque
@@ -274,11 +309,16 @@ es:
           silence: Silenciar
           suspend: Suspender
         title: Nuevo bloque de dominio
+      private_comment: Comentario privado
+      private_comment_hint: Comentario sobre esta limitación de dominio para el uso interno por parte de los moderadores.
+      public_comment: Comentario público
+      public_comment_hint: Comentario sobre esta limitación de dominio para el público en general, si la publicidad de la lista de limitaciones de dominio está habilitada.
       reject_media: Rechazar archivos multimedia
       reject_media_hint: Remueve localmente archivos multimedia almacenados para descargar cualquiera en el futuro. Irrelevante para suspensiones
       reject_reports: Rechazar informes
       reject_reports_hint: Ignore todos los reportes de este dominio. Irrelevante para suspensiones
       rejecting_media: rechazar archivos multimedia
+      rejecting_reports: rechazando informes
       severity:
         silence: silenciado
         suspend: susependido
@@ -292,6 +332,7 @@ es:
         title: Deshacer bloque de dominio para %{domain}
         undo: Deshacer
       undo: Deshacer
+      view: Ver dominio bloqueado
     email_domain_blocks:
       add_new: Añadir nuevo
       created_msg: Dominio de correo añadido a la lista negra con éxito
@@ -304,16 +345,24 @@ es:
       title: Lista negra de correo
     followers:
       back_to_account: Volver a la cuenta
+      title: Seguidores de %{acct}
     instances:
       by_domain: Dominio
+      delivery_available: Entrega disponible
+      known_accounts:
+        one: "%{count} cuenta conocida"
+        other: "%{count} cuentas conocidas"
       moderation:
         all: Todos
         limited: Limitado
         title: Moderación
+      private_comment: Comentario privado
+      public_comment: Comentario público
       title: Instancias conocidas
       total_blocked_by_us: Bloqueado por nosotros
       total_followed_by_them: Seguidos por ellos
       total_followed_by_us: Seguido por nosotros
+      total_reported: Informes sobre ellas
       total_storage: Archivos multimedia
     invites:
       deactivate_all: Desactivar todos
@@ -323,6 +372,8 @@ es:
         expired: Expiradas
         title: Filtrar
       title: Invitaciones
+    pending_accounts:
+      title: Cuentas pendientes (%{count})
     relays:
       add_new: Añadir un nuevo relés
       delete: Borrar
@@ -336,6 +387,7 @@ es:
       pending: Esperando la aprobación del relés
       save_and_enable: Guardar y conectar
       setup: Preparar una conexión de relés
+      signatures_not_enabled: Los relés no funcionarán correctamente mientras el modo seguro o el modo de lista blanca estén habilitados
       status: Estado
       title: Releses
     report_notes:
@@ -384,6 +436,16 @@ es:
       custom_css:
         desc_html: Modificar el aspecto con CSS cargado en cada página
         title: CSS personalizado
+      default_noindex:
+        desc_html: Afecta a todos los usuarios que no han cambiado esta configuración por sí mismos
+        title: Optar por los usuarios fuera de la indexación en los motores de búsqueda por defecto
+      domain_blocks:
+        all: A todos
+        disabled: A nadie
+        title: Mostrar dominios bloqueados
+        users: Para los usuarios locales que han iniciado sesión
+      domain_blocks_rationale:
+        title: Mostrar la razón de ser
       hero:
         desc_html: Mostrado en la página principal. Recomendable al menos 600x100px. Por defecto se establece a la miniatura de la instancia
         title: Imagen de portada
@@ -396,6 +458,9 @@ es:
       preview_sensitive_media:
         desc_html: Los enlaces de vistas previas en otras web mostrarán una miniatura incluso si el medio está marcado como contenido sensible
         title: Mostrar contenido sensible en previews de OpenGraph
+      profile_directory:
+        desc_html: Permitir que los usuarios puedan ser descubiertos
+        title: Habilitar directorio de perfiles
       registrations:
         closed_message:
           desc_html: Se muestra en la portada cuando los registros están cerrados. Puedes usar tags HTML
@@ -408,8 +473,10 @@ es:
           title: Permitir invitaciones de
       registrations_mode:
         modes:
+          approved: Se requiere aprobación para registrarse
           none: Nadie puede registrarse
           open: Cualquiera puede registrarse
+        title: Modo de registros
       show_known_fediverse_at_about_page:
         desc_html: Cuando esté activado, se mostrarán toots de todo el fediverso conocido en la vista previa. En otro caso, se mostrarán solamente toots locales.
         title: Mostrar fediverso conocido en la vista previa de la historia
@@ -429,6 +496,9 @@ es:
         desc_html: Puedes escribir tus propias políticas de privacidad, términos de servicio u otras legalidades. Puedes usar tags HTML
         title: Términos de servicio personalizados
       site_title: Nombre de instancia
+      spam_check_enabled:
+        desc_html: Mastodon puede silenciar y reportar cuentas automáticamente usando medidas como detectar cuentas que envían mensajes no solicitados repetidos. Puede que haya falsos positivos.
+        title: Contra-spam
       thumbnail:
         desc_html: Se usa para muestras con OpenGraph y APIs. Se recomienda 1200x630px
         title: Portada de instancia
@@ -436,12 +506,19 @@ es:
         desc_html: Mostrar línea de tiempo pública en la portada
         title: Previsualización
       title: Ajustes del sitio
+      trendable_by_default:
+        desc_html: Afecta a etiquetas que no han sido previamente rechazadas
+        title: Permitir que las etiquetas sean tendencia sin revisión previa
+      trends:
+        desc_html: Mostrar públicamente hashtags previamente revisados que son tendencia
+        title: Hashtags de tendencia
     statuses:
       back_to_account: Volver a la cuenta
       batch:
         delete: Eliminar
         nsfw_off: Marcar contenido como no sensible
         nsfw_on: Marcar contenido como sensible
+      deleted: Eliminado
       failed_to_execute: Falló al ejecutar
       media:
         title: Multimedia
@@ -449,26 +526,58 @@ es:
       no_status_selected: No se cambió ningún estado al no seleccionar ninguno
       title: Estado de las cuentas
       with_media: Con multimedia
-    subscriptions:
-      callback_url: URL del callback
-      confirmed: Confirmado
-      expires_in: Expira en
-      last_delivery: Última entrega
-      topic: Tópico
+    tags:
+      accounts_today: Usos únicos de hoy
+      accounts_week: Usos únicos esta semana
+      breakdown: Desglose del consumo actual por fuentes
+      context: Contexto
+      directory: En el directorio
+      in_directory: "%{count} en el directorio"
+      last_active: Última actividad
+      most_popular: Más popular
+      most_recent: Más reciente
+      name: Hashtag
+      review: Estado de revisión
+      reviewed: Revisado
+      title: Etiquetas
+      trending_right_now: En tendencia ahora mismo
+      unique_uses_today: "%{count} publicando hoy"
+      unreviewed: No revisado
+      updated_msg: Hashtags actualizados exitosamente
     title: Administración
     warning_presets:
       add_new: Añadir nuevo
       delete: Borrar
       edit: Editar
+      edit_preset: Editar aviso predeterminado
+      title: Editar configuración predeterminada de avisos
   admin_mailer:
     new_pending_account:
       body: Los detalles de la nueva cuenta están abajos. Puedes aprobar o rechazar esta aplicación.
+      subject: Nueva cuenta para revisión en %{instance} (%{username})
     new_report:
       body: "%{reporter} ha reportado a %{target}"
       body_remote: Alguien de %{domain} a reportado a %{target}
       subject: Nuevo reporte para la %{instance} (#%{id})
+    new_trending_tag:
+      body: 'El hashtag #%{name} está en tendencia hoy, pero no ha sido revisado previamente. No se mostrará públicamente a menos que lo permita, o simplemente guarde el formulario como para no volver a ver esto.'
+      subject: Nuevo hashtag para revisión en %{instance} (#%{name})
+  aliases:
+    add_new: Crear alias
+    created_msg: El nuevo alias se ha creado correctamente. Ahora puedes empezar el traslado desde la cuenta antigua.
+    deleted_msg: Elimina el alias correctamente. El traslado de esa cuenta a esta ya no será posible.
+    hint_html: Si quieres migrar de otra cuenta a esta, aquí puedes crear un alias, es necesario proceder antes de empezar a mover seguidores de la cuenta anterior a esta. Esta acción por sí misma es <strong>inofensiva y reversible</strong>. <strong>La migración de la cuenta se inicia desde la cuenta antigua</strong>.
+    remove: Desvincular alias
+  appearance:
+    advanced_web_interface: Interfaz web avanzada
+    advanced_web_interface_hint: 'Si desea utilizar todo el ancho de pantalla, la interfaz web avanzada le permite configurar varias columnas diferentes para ver tanta información al mismo tiempo como quiera: Inicio, notificaciones, línea de tiempo federada, cualquier número de listas y etiquetas.'
+    animations_and_accessibility: Animaciones y accesibilidad
+    confirmation_dialogs: Diálogos de confirmación
+    discovery: Descubrir
+    sensitive_content: Contenido sensible
   application_mailer:
     notification_preferences: Cambiar preferencias de correo electrónico
+    salutation: "%{name},"
     settings: 'Cambiar preferencias de correo: %{link}'
     view: 'Vista:'
     view_profile: Ver perfil
@@ -482,11 +591,16 @@ es:
     warning: Ten mucho cuidado con estos datos. ¡No los compartas con nadie!
     your_token: Tu token de acceso
   auth:
+    apply_for_account: Solicitar una invitación
     change_password: Contraseña
     checkbox_agreement_html: Acepto <a href="%{rules_path}" target="_blank">las reglas del servidor</a> y <a href="%{terms_path}" target="_blank">términos de servicio</a>
-    confirm_email: Confirmar email
+    checkbox_agreement_without_rules_html: Acepto los <a href="%{terms_path}" target="_blank">términos de servicio</a>
     delete_account: Borrar cuenta
     delete_account_html: Si desea eliminar su cuenta, puede <a href="%{path}">proceder aquí</a>. Será pedido de una confirmación.
+    description:
+      prefix_invited_by_user: "¡@%{name} te invita a unirte a este servidor de Mastodon!"
+      prefix_sign_up: "¡Únete a Mastodon hoy!"
+      suffix: "¡Con una cuenta podrás seguir a gente, publicar novedades e intercambiar mensajes con usuarios de cualquier servidor de Mastodon y más!"
     didnt_get_confirmation: "¿No recibió el correo de confirmación?"
     forgot_password: "¿Olvidaste tu contraseña?"
     invalid_reset_password_token: El token de reinicio de contraseña es inválido o expiró. Por favor pide uno nuevo.
@@ -495,12 +609,26 @@ es:
     migrate_account: Mudarse a otra cuenta
     migrate_account_html: Si deseas redireccionar esta cuenta a otra distinta, puedes <a href="%{path}">configurarlo aquí</a>.
     or_log_in_with: O inicia sesión con
+    providers:
+      cas: CAS
+      saml: SAML
     register: Registrarse
     registration_closed: "%{instance} no está aceptando nuevos miembros"
     resend_confirmation: Volver a enviar el correo de confirmación
     reset_password: Restablecer contraseña
     security: Cambiar contraseña
     set_new_password: Establecer nueva contraseña
+    setup:
+      email_below_hint_html: Si la dirección de correo electrónico que aparece a continuación es incorrecta, se puede cambiarla aquí y recibir un nuevo correo electrónico de confirmación.
+      email_settings_hint_html: El correo electrónico de confirmación fue enviado a %{email}. Si esa dirección de correo electrónico no sea correcta, se puede cambiarla en la configuración de la cuenta.
+      title: Configuración
+    status:
+      account_status: Estado de la cuenta
+      confirming: Esperando confirmación de correo electrónico.
+      functional: Su cuenta está totalmente operativa.
+      pending: Su solicitud está pendiente de revisión por nuestros administradores. Eso puede tardar algún tiempo. Usted recibirá un correo electrónico si el solicitud sea aprobada.
+      redirecting_to: Tu cuenta se encuentra inactiva porque está siendo redirigida a %{acct}.
+    trouble_logging_in: "¿Problemas para iniciar sesión?"
   authorize_follow:
     already_following: Ya estás siguiendo a esta cuenta
     error: Desafortunadamente, ha ocurrido un error buscando la cuenta remota
@@ -512,29 +640,53 @@ es:
       return: Regresar al perfil del usuario
       web: Ir al sitio web
     title: Seguir a %{acct}
+  challenge:
+    confirm: Continuar
+    hint_html: "<strong>Tip:</strong> No volveremos a preguntarte por la contraseña durante la siguiente hora."
+    invalid_password: Contraseña incorrecta
+    prompt: Confirmar contraseña para seguir
   datetime:
     distance_in_words:
+      about_x_hours: "%{count}h"
       about_x_months: "%{count}m"
+      about_x_years: "%{count}a"
+      almost_x_years: "%{count}a"
       half_a_minute: Justo ahora
+      less_than_x_minutes: "%{count}m"
       less_than_x_seconds: Justo ahora
+      over_x_years: "%{count}a"
+      x_days: "%{count}d"
+      x_minutes: "%{count}m"
       x_months: "%{count}m"
+      x_seconds: "%{count}s"
   deletes:
-    bad_password_msg: "¡Buen intento, hackers! Contraseña incorrecta"
+    challenge_not_passed: Los datos introducidos son incorrectos
     confirm_password: Ingresa tu contraseña actual para demostrar tu identidad
-    description_html: Esto removerá el contenido de tu cuenta y la desactivará <strong>permanente e irrevesiblemente</strong>. Tu nombre de usuario quedará reservado para prevenir futuros robos de identidad.
+    confirm_username: Escribe tu nombre de usuario para confirmar
     proceed: Eliminar cuenta
     success_msg: Tu cuenta se eliminó con éxito
-    warning_html: Se garantiza únicamente la eliminación del contenido de esta instancia. El contenido que se haya compartido extensamente dejará sus huellas. Los servidores fuera de línea y los que se hayan desuscrito de tus actualizaciones ya no actualizarán sus bases de datos.
-    warning_title: Disponibilidad diseminada del contenido
+    warning:
+      before: 'Antes de continuar, por favor lee con atención las siguientes notas:'
+      caches: El contenido que ha sido almacenado en caché por otros servidores puede persistir
+      data_removal: Tus publicaciones y el resto de datos se eliminarán definitivamente
+      email_change_html: Puedes <a href="%{path}"> cambiar tu dirección de correo electrónico</a> sin eliminar tu cuenta
+      email_contact_html: Si aún no te ha llegado, puedes escribir a <a href="mailto:%{email}">%{email}</a> para pedir ayuda
+      email_reconfirmation_html: Si no te ha llegado el correo de confirmación, puedes <a href="%{path}"> volver a solicitarlo</a>
+      irreversible: No podrás restaurar ni reactivar tu cuenta
+      more_details_html: Para más detalles, ver <a href="%{terms_path}"> la política de privacidad</a>.
+      username_available: Tu nombre de usuario volverá a estar disponible
+      username_unavailable: Tu nombre de usuario no estará disponible
   directories:
+    directory: Directorio de perfiles
+    explanation: Descubre usuarios según sus intereses
     explore_mastodon: Explorar %{title}
-    how_to_enable: Usted no está registrado por el directorio. Puede registrar por abajo. ¡Utilice hashtags en su bio para aparecer bajo hashtags específicos!
-    people:
-      one: "%{count} persona"
-      other: "%{count} personas"
+  domain_validator:
+    invalid_domain: no es un nombre de dominio válido
   errors:
+    '400': La solicitud que has enviado no es valida o estaba malformada.
     '403': No tienes permiso para acceder a esta página.
     '404': La página que estabas buscando no existe.
+    '406': Esta página no está disponible en el formato solicitado.
     '410': La página que estabas buscando no existe más.
     '422':
       content: Verificación de seguridad fallida. ¿Estás bloqueando algunas cookies?
@@ -543,6 +695,7 @@ es:
     '500':
       content: Lo sentimos, algo ha funcionado mal por nuestra parte.
       title: Esta página no es correcta
+    '503': La página no se ha podido cargar debido a un fallo temporal del servidor.
     noscript_html: Para usar la aplicación web de Mastodon, por favor activa Javascript. Alternativamente, prueba alguna de las <a href="%{apps_path}">aplicaciones nativas</a> para Mastodon para tu plataforma.
   existing_username_validator:
     not_found: no pudo encontrar un usuario local con ese nombre de usuario
@@ -557,6 +710,7 @@ es:
       size: Tamaño
     blocks: Personas que has bloqueado
     csv: CSV
+    domain_blocks: Bloqueos de dominios
     follows: Personas que sigues
     lists: Listas
     mutes: Tienes en silencio
@@ -565,6 +719,7 @@ es:
     add_new: Añadir nuevo
     errors:
       limit: Ya has alcanzado la cantidad máxima de hashtags
+    hint_html: "<strong>¿Qué son las etiquetas destacadas?</strong> Se muestran de forma prominente en tu perfil público y permiten a los usuarios navegar por tus publicaciones públicas específicamente bajo esas etiquetas. Son una gran herramienta para hacer un seguimiento de trabajos creativos o proyectos a largo plazo."
   filters:
     contexts:
       home: Timeline propio
@@ -585,15 +740,19 @@ es:
     developers: Desarrolladores
     more: Mas…
     resources: Recursos
+    trending_now: Tendencia ahora
   generic:
     all: Todos
     changes_saved_msg: "¡Cambios guardados con éxito!"
     copy: Copiar
+    no_batch_actions_available: No hay acciones por lotes disponibles en esta página
     order_by: Ordenar por
     save_changes: Guardar cambios
     validation_errors:
       one: "¡Algo no está bien! Por favor, revisa el error"
       other: "¡Algo no está bien! Por favor, revise %{count} errores más abajo"
+  html_validator:
+    invalid_markup: 'contiene código HTML no válido: %{error}'
   identity_proofs:
     active: Activo
     authorize: Sí, autorizar
@@ -603,18 +762,26 @@ es:
       keybase:
         invalid_token: Los tokens de Keybase son hashes de firmas y deben tener 66 caracteres hex
         verification_failed: Keybase no reconoce este token como una firma del usuario de Keybase %{kb_username}. Por favor, inténtelo de nuevo desde Keybase.
+      wrong_user: No se puede crear una prueba para %{proving} mientras se inicia sesión como %{current}. Inicia sesión como %{proving} e inténtalo de nuevo.
+    explanation_html: Aquí puedes conectar criptográficamente sus otras identidades, como un perfil de Keybase. Esto permite a otras personas enviarle mensajes encriptados y confiar en el contenido que les envías.
+    i_am_html: Soy %{username} en %{service}.
     identity: Identidad
     inactive: Inactivo
+    publicize_checkbox: 'Y tootee esto:'
+    publicize_toot: "¡Comprobado! Soy %{username} en %{service}: %{url}"
     status: Estado de la verificación
     view_proof: Ver prueba
   imports:
     modes:
       merge: Unir
+      merge_long: Mantener registros existentes y añadir nuevos
       overwrite: Sobrescribir
+      overwrite_long: Reemplazar registros actuales con los nuevos
     preface: Puedes importar ciertos datos, como todas las personas que estás siguiendo o bloqueando en tu cuenta en esta instancia, desde archivos exportados de otra instancia.
     success: Sus datos se han cargado correctamente y serán procesados en brevedad
     types:
       blocking: Lista de bloqueados
+      domain_blocking: Lista de dominios bloqueados
       following: Lista de seguidos
       muting: Lista de silenciados
     upload: Cargar
@@ -650,9 +817,34 @@ es:
       too_many: No se pueden adjuntar más de 4 archivos
   migrations:
     acct: username@domain de la nueva cuenta
-    currently_redirecting: 'Tu perfil está redireccionado a:'
-    proceed: Guardar
-    updated_msg: "¡La configuración de migración de tu cuenta  ha sido actualizada con éxito!"
+    cancel: Cancelar redireccionamiento
+    cancel_explanation: Al cancelar el redireccionamiento se reactivará tu cuenta actual, pero no recuperarás los seguidores que hayan sido trasladados a la otra cuenta.
+    cancelled_msg: El redireccionamiento se ha cancelado correctamente.
+    errors:
+      already_moved: es la misma cuenta a la que ya has migrado
+      missing_also_known_as: no está haciendo referencia a esta cuenta
+      move_to_self: no puede ser la cuenta actual
+      not_found: no se pudo encontrar
+      on_cooldown: Estás en tiempo de reutilización
+    followers_count: Seguidores al momento de migrar
+    incoming_migrations: Migrar de una cuenta diferente
+    incoming_migrations_html: Para migrar de otra cuenta a esta, primero necesitas <a href="%{path}">crear un alias de la cuenta</a>.
+    moved_msg: Tu cuenta ahora se está redirigiendo a %{acct} y tus seguidores se están migrando.
+    not_redirecting: Tu cuenta no se está redirigiendo a ninguna otra cuenta actualmente.
+    on_cooldown: Has migrado tu cuenta recientemente. Esta función estará disponible de nuevo en %{count} días.
+    past_migrations: Migraciones pasadas
+    proceed_with_move: Migrar seguidores
+    redirecting_to: Tu cuenta se está redirigiendo a %{acct}.
+    set_redirect: Establecer redirección
+    warning:
+      backreference_required: La nueva cuenta debe ser configurada primero para hacer referencia a esta
+      before: 'Antes de continuar, por favor lee con atención las siguientes notas:'
+      cooldown: Después de migrar hay un período de espera durante el cual no podrás volver a migrar
+      disabled_account: Tu cuenta actual no será completamente utilizable después. Sin embargo, tendrás acceso a la exportación de datos así como a la reactivación.
+      followers: Esta acción migrará a todos los seguidores de la cuenta actual a la nueva cuenta
+      only_redirect_html: Alternativamente, solo puedes <a href="%{path}">poner una redirección en tu perfil</a>.
+      other_data: No se moverán otros datos automáticamente
+      redirect: El perfil de tu cuenta actual se actualizará con un aviso de redirección y será excluido de las búsquedas
   moderation:
     title: Moderación
   notification_mailer:
@@ -689,26 +881,70 @@ es:
       body: "%{name} ha retooteado tu estado:"
       subject: "%{name} ha retooteado tu estado"
       title: Nueva difusión
+  number:
+    human:
+      decimal_units:
+        format: "%n%u"
+        units:
+          billion: B
+          million: M
+          quadrillion: Q
+          thousand: m
+          trillion: T
   pagination:
     newer: Más nuevo
     next: Próximo
     older: Más antiguo
     prev: Anterior
+    truncate: "&hellip;"
+  polls:
+    errors:
+      already_voted: Ya has votado en esta encuesta
+      duplicate_options: contiene elementos duplicados
+      duration_too_long: está demasiado lejos en el futuro
+      duration_too_short: es demasiado pronto
+      expired: La encuesta ya ha terminado
+      over_character_limit: no puede exceder %{max} caracteres cada uno
+      too_few_options: debe tener más de un elemento
+      too_many_options: no puede contener más de %{max} elementos
   preferences:
     other: Otros
+    posting_defaults: Configuración por defecto de publicaciones
+    public_timelines: Líneas de tiempo públicas
   relationships:
+    activity: Actividad de la cuenta
+    dormant: Inactivo
     last_active: Última actividad
     most_recent: Más reciente
+    moved: Movido
+    mutual: Mutuo
+    primary: Principal
+    relationship: Relación
+    remove_selected_domains: Eliminar todos los seguidores de los dominios seleccionados
+    remove_selected_followers: Eliminar los seguidores seleccionados
+    remove_selected_follows: Dejar de seguir a los usuarios seleccionados
+    status: Estado de la cuenta
   remote_follow:
     acct: Ingesa tu usuario@dominio desde el que quieres seguir
     missing_resource: No se pudo encontrar la URL de redirección requerida para tu cuenta
     no_account_html: "¿No tienes una cuenta? Puedes <a href='%{sign_up_path}' target='_blank'>registrarte aqui</a>"
     proceed: Proceder a seguir
     prompt: 'Vas a seguir a:'
-  remote_unfollow:
-    error: Error
-    title: Título
-    unfollowed: Ha dejado de seguirse
+    reason_html: "¿<strong>¿Por qué es necesario este paso?</strong> <code>%{instance}</code> puede que no sea el servidor donde estás registrado, así que necesitamos redirigirte primero a tu servidor de origen."
+  remote_interaction:
+    favourite:
+      proceed: Proceder a marcar como favorito
+      prompt: 'Quieres marcar como favorito este toot:'
+    reblog:
+      proceed: Proceder a retootear
+      prompt: 'Quieres retootear este toot:'
+    reply:
+      proceed: Proceder a responder
+      prompt: 'Quieres responder a este toot:'
+  scheduled_statuses:
+    over_daily_limit: Ha superado el límite de %{limit} toots programados para ese día
+    over_total_limit: Ha superado el límite de %{limit} toots programados
+    too_soon: La fecha programada debe estar en el futuro
   sessions:
     activity: Última actividad
     browser: Navegador
@@ -753,6 +989,7 @@ es:
   settings:
     account: Cuenta
     account_settings: Ajustes de la cuenta
+    aliases: Alias de la cuenta
     appearance: Apariencia
     authorized_apps: Aplicaciones autorizadas
     back: Volver al inicio
@@ -761,11 +998,17 @@ es:
     edit_profile: Editar perfil
     export: Exportar información
     featured_tags: Hashtags destacados
+    identity_proofs: Pruebas de identidad
     import: Importar
+    import_and_export: Importar y exportar
     migrate: Migración de cuenta
     notifications: Notificaciones
     preferences: Preferencias
+    profile: Perfil
+    relationships: Siguiendo y seguidores
     two_factor_authentication: Autenticación de dos factores
+  spam_check:
+    spam_detected: Este es un informe automatizado. Se ha detectado Spam.
   statuses:
     attached:
       description: 'Adjunto: %{attached}'
@@ -788,8 +1031,17 @@ es:
       ownership: El toot de alguien más no puede fijarse
       private: Los toots no-públicos no pueden fijarse
       reblog: Un boost no puede fijarse
+    poll:
+      total_people:
+        one: persona %{count}
+        other: "%{count} gente"
+      total_votes:
+        one: "%{count} voto"
+        other: "%{count} votos"
+      vote: Vota
     show_more: Mostrar más
     sign_in_to_participate: Regístrate para participar en la conversación
+    title: '%{name}: "%{quote}"'
     visibilities:
       private: Sólo mostrar a seguidores
       private_long: Solo mostrar a tus seguidores
@@ -801,7 +1053,90 @@ es:
     pinned: Toot fijado
     reblogged: retooteado
     sensitive_content: Contenido sensible
+  tags:
+    does_not_match_previous_name: no coincide con el nombre anterior
   terms:
+    body_html: |
+      <h2>Política de Privacidad</h2>
+      <h3 id="collect">¿Qué información recogemos?</h3>
+
+      <ul>
+      <li><em>Información básica sobre su cuenta</em>: Si se registra en este servidor, se le requerirá un nombre de usuario, una dirección de correo electrónico y una contraseña. Además puede incluir información adicional en el perfil como un nombre de perfil y una biografía, y subir una foto de perfil y una imagen de cabecera. El nombre de usuario, nombre de perfil, biografía, foto de perfil e imagen de cabecera siempre son visibles públicamente</li>
+      <li><em>Publicaciones, seguimiento y otra información pública</em>: La lista de gente a la que sigue es mostrada públicamente, al igual que sus seguidores. Cuando publica un mensaje, la fecha y hora es almacenada, así como la aplicación desde la cual publicó el mensaje. Los mensajes pueden contener archivos adjuntos multimedia, como imágenes y vídeos. Las publicaciones públicas y no listadas están disponibles públicamente. Cuando destaca una entrada en su perfil, también es información disponible públicamente. Sus publicaciones son entregadas a sus seguidores, en algunos casos significa que son entregadas a diferentes servidores y las copias son almacenadas allí. Cuando elimina publicaciones, esto también se transfiere a sus seguidores. La acción de rebloguear o marcar como favorito otra publicación es siempre pública.</li>
+      <li><em>Publicaciones directas y sólo para seguidores</em>: Todos los mensajes se almacenan y procesan en el servidor. Los mensajes sólo para seguidores se entregan a los seguidores y usuarios que se mencionan en ellos, y los mensajes directos se entregan sólo a los usuarios que se mencionan en ellos. En algunos casos significa que se entregan a diferentes servidores y que las copias se almacenan allí. Hacemos un esfuerzo de buena fe para limitar el acceso a esas publicaciones sólo a las personas autorizadas, pero otros servidores pueden no hacerlo. Por lo tanto, es importante revisar los servidores a los que pertenecen sus seguidores. Puede cambiar una opción para aprobar y rechazar nuevos seguidores manualmente en la configuración <em>Por favor, tenga en cuenta que los operadores del servidor y de cualquier servidor receptor pueden ver dichos mensajes</em>, y que los destinatarios pueden capturarlos, copiarlos o volver a compartirlos de alguna otra manera. <em>No comparta ninguna información peligrosa en Mastodon.</em></li>
+      <li><em>Direcciones IP y otros metadatos</em>: Al iniciar sesión, registramos la dirección IP desde la que se ha iniciado sesión, así como el nombre de la aplicación de su navegador. Todas las sesiones iniciadas están disponibles para su revisión y revocación en los ajustes. La última dirección IP utilizada se almacena hasta 12 meses. También podemos conservar los registros del servidor que incluyen la dirección IP de cada solicitud a nuestro servidor.</li>
+      </ul>
+
+      <hr class="spacer" />
+
+      <h3 id="use">¿Para qué utilizamos su información?</h3>
+
+      <p>Toda la información que obtenemos de usted puede ser utilizada de las siguientes maneras:</p>
+
+      <ul>
+      <li>Para proporcionar la funcionalidad principal de Mastodon. Sólo puedes interactuar con el contenido de otras personas y publicar tu propio contenido cuando estés conectado. Por ejemplo, puedes seguir a otras personas para ver sus mensajes combinados en tu propia línea de tiempo personalizada.</li>
+      <li>Para ayudar a la moderación de la comunidad, por ejemplo, comparando su dirección IP con otras conocidas para determinar la evasión de prohibiciones u otras violaciones.</li>
+      <li>La dirección de correo electrónico que nos proporcione podrá utilizarse para enviarle información, notificaciones sobre otras personas que interactúen con su contenido o para enviarle mensajes, así como para responder a consultas y/u otras solicitudes o preguntas.</li>
+      </ul>
+
+      <hr class="spacer" />
+
+      <h3 id="protect">¿Cómo protegemos su información?</h3>
+
+      <p>Implementamos una variedad de medidas de seguridad para mantener la seguridad de su información personal cuando usted ingresa, envía o accede a su información personal. Entre otras cosas, la sesión de su navegador, así como el tráfico entre sus aplicaciones y la API, están protegidos con SSL, y su contraseña está protegida mediante un algoritmo unidireccional fuerte. Puede habilitar la autenticación de dos factores para un acceso más seguro a su cuenta.</p>
+
+      <hr class="spacer" />
+
+      <h3 id="data-retention">¿Cuál es nuestra política de retención de datos?</h3>
+
+      <p>Haremos un esfuerzo de buena fe para:</p>
+
+      <ul>
+      <li>Conservar los registros del servidor que contengan la dirección IP de todas las peticiones a este servidor, en la medida en que se mantengan dichos registros, no más de 90 días.</li>
+      <li>Conservar las direcciones IP asociadas a los usuarios registrados no más de 12 meses.</li>
+      </ul>
+
+      <p>Puede solicitar y descargar un archivo de su contenido, incluidos sus mensajes, archivos adjuntos multimedia, foto de perfil e imagen de cabecera.</p>
+
+      <p>Usted puede borrar su cuenta de forma irreversible en cualquier momento.</p>
+
+      <hr class="spacer"/>
+
+      <h3 id="cookies">¿Utilizamos cookies?</h3>
+
+      <p>Sí. Las cookies son pequeños archivos que un sitio o su proveedor de servicios transfiere al disco duro de su ordenador a través de su navegador web (si usted lo permite). Estas cookies permiten al sitio reconocer su navegador y, si tiene una cuenta registrada, asociarla con su cuenta registrada.</p>
+
+      <p>Utilizamos cookies para entender y guardar sus preferencias para futuras visitas.</p>
+
+      <hr class="spacer" />
+
+      <h3 id="disclose">¿Revelamos alguna información a terceros?</h3>
+
+      <p>No vendemos, comerciamos ni transferimos a terceros su información personal identificable. Esto no incluye a los terceros de confianza que nos asisten en la operación de nuestro sitio, en la realización de nuestros negocios o en la prestación de servicios, siempre y cuando dichas partes acuerden mantener la confidencialidad de esta información. También podemos divulgar su información cuando creamos que es apropiado para cumplir con la ley, hacer cumplir las políticas de nuestro sitio, o proteger nuestros u otros derechos, propiedad o seguridad.</p>
+
+      <p>Su contenido público puede ser descargado por otros servidores de la red. Tus mensajes públicos y sólo para seguidores se envían a los servidores donde residen tus seguidores, y los mensajes directos se envían a los servidores de los destinatarios, en la medida en que dichos seguidores o destinatarios residan en un servidor diferente.</p>
+
+      <p>Cuando usted autoriza a una aplicación a usar su cuenta, dependiendo del alcance de los permisos que usted apruebe, puede acceder a la información de su perfil público, su lista de seguimiento, sus seguidores, sus listas, todos sus mensajes y sus favoritos. Las aplicaciones nunca podrán acceder a su dirección de correo electrónico o contraseña.</p>
+
+      <hr class="spacer" />
+
+      <h3 id="children">Uso del sitio por parte de los niños</h3>
+
+      <p>Si este servidor está en la UE o en el EEE: Nuestro sitio, productos y servicios están dirigidos a personas mayores de 16 años. Si es menor de 16 años, según los requisitos de la GDPR (<a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation_Data_Protection_Regulation">General Data Protection Regulation</a>) no utilice este sitio.</p>
+
+      <p>Si este servidor está en los EE.UU.: Nuestro sitio, productos y servicios están todos dirigidos a personas que tienen al menos 13 años de edad. Si usted es menor de 13 años, según los requisitos de COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) no utilice este sitio.</p>
+
+      <p>Los requisitos legales pueden ser diferentes si este servidor está en otra jurisdicción.</p>
+
+      <hr class="spacer" />
+
+      <h3 id="changes">Cambios en nuestra Política de Privacidad</h3>
+
+      <p>Si decidimos cambiar nuestra política de privacidad, publicaremos esos cambios en esta página.</p>
+
+      <p>Este documento es CC-BY-SA. Fue actualizado por última vez el 7 de marzo de 2018.</p>
+
+      <p>Adaptado originalmente desde <a href="https://github.com/discourse/discourse">la política de privacidad de Discourse</a>.</p>
     title: Términos del Servicio y Políticas de Privacidad de %{instance}
   themes:
     contrast: Alto contraste
@@ -810,6 +1145,7 @@ es:
   time:
     formats:
       default: "%d de %b del %Y, %H:%M"
+      month: "%b %Y"
   two_factor_authentication:
     code_hint: Ingresa el código generado por tu aplicación de autenticación para confirmar
     description_html: Si habilitas la <strong>autenticación de dos factores</strong>, se requerirá estar en posesión de su teléfono, lo que generará tokens para que usted pueda iniciar sesión.
@@ -831,6 +1167,24 @@ es:
       explanation: Has solicitado una copia completa de tu cuenta de Mastodon. ¡Ya está preparada para descargar!
       subject: Tu archivo está preparado para descargar
       title: Descargar archivo
+    warning:
+      explanation:
+        disable: Mientras su cuenta esté congelada, la información de su cuenta permanecerá intacta, pero no puede realizar ninguna acción hasta que se desbloquee.
+        silence: Mientras su cuenta está limitada, sólo las personas que ya le están siguiendo verán sus toots en este servidor, y puede que se le excluya de varios listados públicos. Sin embargo, otros pueden seguirle manualmente.
+        suspend: Su cuenta ha sido suspendida, y todos tus toots y tus archivos multimedia subidos han sido irreversiblemente eliminados de este servidor, y de los servidores donde tenías seguidores.
+      get_in_touch: Puede responder a esta dirección de correo electrónico para ponerse en contacto con el personal de %{instance}.
+      review_server_policies: Revisar las políticas del servidor
+      statuses: 'Específicamente, para:'
+      subject:
+        disable: Su cuenta %{acct} ha sido congelada
+        none: Advertencia para %{acct}
+        silence: Su cuenta %{acct} ha sido limitada
+        suspend: Su cuenta %{acct} ha sido suspendida
+      title:
+        disable: Cuenta congelada
+        none: Advertencia
+        silence: Cuenta limitada
+        suspend: Cuenta suspendida
     welcome:
       edit_profile_action: Configurar el perfil
       edit_profile_step: Puedes personalizar tu perfil subiendo un avatar, una cabecera, cambiando tu nombre de usuario y más cosas. Si quieres revisar a tus nuevos seguidores antes de que se les permita seguirte, puedes bloquear tu cuenta.
@@ -846,6 +1200,7 @@ es:
       tip_following: Sigues a tus administradores de servidor por defecto. Para encontrar más gente interesante, revisa las lineas de tiempo local y federada.
       tip_local_timeline: La linea de tiempo local is una vista de la gente en %{instance}. Estos son tus vecinos inmediatos!
       tip_mobile_webapp: Si el navegador de tu dispositivo móvil ofrece agregar Mastodon a tu página de inicio, puedes recibir notificaciones. Actúa como una aplicación nativa en muchas formas!
+      tips: Consejos
       title: Te damos la bienvenida a bordo, %{name}!
   users:
     follow_limit_reached: No puedes seguir a más de %{limit} personas
diff --git a/config/locales/et.yml b/config/locales/et.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6b814a23c8a220b934fd61a7b2bf0092c49a9d35
--- /dev/null
+++ b/config/locales/et.yml
@@ -0,0 +1,1000 @@
+---
+et:
+  about:
+    about_hashtag_html: Need on avalikud tuututused sildistatud sildiga <strong>#%{hashtag}</strong>. Te saate suhelda nendega, kui Teil on konto üks kõik kus terves fediversumis.
+    about_mastodon_html: Mastodon on sotsiaalvõrgustik, mis põhineb avatud protokollidel ja avatud lähtekoodiga tarkvaral. See on detsentraliseeritud nagu e-post.
+    about_this: Meist
+    active_count_after: aktiivne
+    active_footnote: Igakuiselt aktiivseid kasutajaid (MAU)
+    administered_by: 'Administraator:'
+    api: API
+    apps: Mobiilrakendused
+    apps_platforms: Kasuta Mastodoni iOS-is, Androidis ja teistel platvormidel
+    browse_directory: Sirvi profiilide kataloogi ja filtreeri huvide alusel
+    browse_public_posts: Sirvi reaalajas voogu avalikest postitustest Mastodonis
+    contact: Kontakt
+    contact_missing: Määramata
+    contact_unavailable: Pole saadaval
+    discover_users: Avasta kasutajaid
+    documentation: Dokumentatsioon
+    federation_hint_html: Kui Teil on kasutaja %{instance}-is, saate Te jälgida inimesi üks kõik millisel Mastodoni serveril ja kaugemalgi.
+    get_apps: Proovi mobiilirakendusi
+    hosted_on: Mastodon majutatud %{domain}-is
+    instance_actor_flash: |
+      See konto on virtuaalne näitleja, mis esindab tervet serverit ning mitte ühtegi kindlat isikut.
+      Seda kasutatakse föderatiivsetel põhjustel ning seda ei tohiks blokeerida, välja arvatud juhul, kui soovite blokeerida tervet serverit, kuid sellel juhul soovitame hoopis kasutada domeeni blokeerimist.
+    learn_more: Lisateave
+    privacy_policy: Privaatsuspoliitika
+    see_whats_happening: Vaata, mis toimub
+    server_stats: 'Serveri statistika:'
+    source_code: Lähtekood
+    status_count_after:
+      one: staatust
+      other: staatuseid
+    status_count_before: Kes omavad
+    tagline: Jälgi sõpru ja leia uusi
+    terms: Kasutustingimused
+    user_count_after:
+      one: kasutajale
+      other: kasutajale
+    user_count_before: Koduks
+    what_is_mastodon: Mis on Mastodon?
+  accounts:
+    choices_html: "%{name}-i valikud:"
+    follow: Jälgi
+    followers:
+      one: Jälgija
+      other: Jälgijaid
+    following: Jälgib
+    joined: Liitus %{date}
+    last_active: viimati aktiivne
+    link_verified_on: Selle lingi autorsust kontrolliti %{date}
+    media: Meedia
+    moved_html: "%{name} kolis %{new_profile_link}:"
+    network_hidden: Neid andmeid pole saadaval
+    nothing_here: Siin pole midagi!
+    people_followed_by: Inimesed, keda %{name} jälgib
+    people_who_follow: Inimesed, kes jälgivad kasutajat %{name}
+    pin_errors:
+      following: Te peate juba olema selle kasutaja jälgija, keda te heaks kiidate
+    posts:
+      one: Tuut
+      other: Tuututused
+    posts_tab_heading: Tuututused
+    posts_with_replies: Tuututused ja vastused
+    reserved_username: Kasutajanimi on reserveeritud
+    roles:
+      admin: Administraator
+      bot: Robot
+      moderator: Moderaator
+    unavailable: Profiil pole saadaval
+    unfollow: Lõpeta jälgimine
+  admin:
+    account_actions:
+      action: Täida tegevus
+      title: Rakenda moderaatori tegevus kasutajale %{acct}
+    account_moderation_notes:
+      create: Jäta teade
+      created_msg: Moderatsiooniteade edukalt koostatud!
+      delete: Kustuta
+      destroyed_msg: Moderatsiooniteade edukalt kustutatud!
+    accounts:
+      approve: Kinnita
+      approve_all: Kinnita kõik
+      are_you_sure: Olete kindel?
+      avatar: Profiilipilt
+      by_domain: Domeen
+      change_email:
+        changed_msg: Konto e-postiaadress edukalt muudetud!
+        current_email: Praegune e-postiaadress
+        label: Muuda e-posti aadressi
+        new_email: Uus е-posti aadress
+        submit: Muuda e-posti aadressi
+        title: Muuda e-postiaadressi kasutajale %{username}
+      confirm: Kinnita
+      confirmed: Kinnitatud
+      confirming: Kinnitamine
+      deleted: Kustutatud
+      demote: Alanda
+      disable: Keela
+      disable_two_factor_authentication: Keela 2FA
+      disabled: Keelatud
+      display_name: Kuvanimi
+      domain: Domeen
+      edit: Redigeeri
+      email: E-post
+      email_status: E-posti staatus
+      enable: Luba
+      enabled: Lubatud
+      feed_url: Voogu URL
+      followers: Jälgijad
+      followers_url: Jälgijate URL
+      follows: Jälgib
+      header: Päis
+      inbox_url: Sisendkausta URL
+      invited_by: Kutsuja
+      ip: IP
+      joined: Liitus
+      location:
+        all: Kõik
+        local: Kohalik
+        remote: Kaug
+        title: Asukoht
+      login_status: Sisselogimise olek
+      media_attachments: Lisatud meedia
+      memorialize: Tee memoriaaliks
+      moderation:
+        active: Aktiivne
+        all: Kõik
+        pending: Ootel
+        silenced: Vaigistatud
+        suspended: Peatatud
+        title: Moderatsioon
+      moderation_notes: Moderatsiooniteated
+      most_recent_activity: Kõige hiljutisem aktiivsus
+      most_recent_ip: Kõige hiljutisem IP
+      no_account_selected: Mitte ühtegi kontot muudeti sest midagi polnud valitud
+      no_limits_imposed: Mitte ühtegi limiiti kehtestatud
+      not_subscribed: Ei ole tellitud
+      outbox_url: Väljundkausta URL
+      pending: Ootab ülevaatamist
+      perform_full_suspension: Peata
+      profile_url: Profiili URL
+      promote: Edenda
+      protocol: Protokoll
+      public: Avalik
+      push_subscription_expires: PuSH tellimus aegub
+      redownload: Värskenda profiili
+      reject: Hülga
+      reject_all: Hülga kõik
+      remove_avatar: Kustuta profiilipilt
+      remove_header: Kustuta päis
+      resend_confirmation:
+        already_confirmed: See kasutaja on juba kinnitatud
+        send: Saada kinnituskiri uuesti
+        success: Kinnituskiri saadetud edukalt!
+      reset: Lähtesta
+      reset_password: Lähtesta salasõna
+      resubscribe: Telli taas
+      role: Õigused
+      roles:
+        admin: Administraator
+        moderator: Moderaator
+        staff: Personal
+        user: Kasutaja
+      salmon_url: Salmoni URL
+      search: Otsi
+      shared_inbox_url: Jagatud sisendkausta URL
+      show:
+        created_reports: Loodud teated
+        targeted_reports: Teiste poolt teatatud
+      silence: Vaigista
+      silenced: Vaigistatud
+      statuses: Staatuseid
+      subscribe: Telli
+      suspended: Peatatud
+      time_in_queue: Ootab järjekorras %{time}
+      title: Kontod
+      unconfirmed_email: Kinnitamata e-post
+      undo_silenced: Võta vaigistus tagasi
+      undo_suspension: Võta peatamine tagasi
+      unsubscribe: Tühista tellimus
+      username: Kasutajanimi
+      warn: Hoiata
+      web: Veeb
+      whitelisted: Lubatud
+    action_logs:
+      actions:
+        assigned_to_self_report: "%{name} määras teabe %{target} iseendale"
+        change_email_user: "%{name} muutis kasutaja %{target} e-postiaadressit"
+        confirm_user: "%{name} kinnitas kasutaja %{target} e-postiaadressi"
+        create_account_warning: "%{name} saatis kasutajale %{target} hoiatuse"
+        create_custom_emoji: "%{name} laadis üles uue emotikooni %{target}"
+        create_domain_block: "%{name} blokeeris domeeni %{target}"
+        create_email_domain_block: "%{name} lisas e-posti domeeni %{target} musta nimekirja"
+        demote_user: "%{name} alandas kasutaja %{target}"
+        destroy_custom_emoji: "%{name} kustutas emotikooni %{target}"
+        destroy_domain_block: "%{name} eemaldas blokeeringu domeenilt %{target}"
+        destroy_email_domain_block: "%{name} lisas e-posti domeeni %{target} lubatute nimekirja"
+        destroy_status: "%{name} eemaldas %{target} staatuse"
+        disable_2fa_user: "%{name} eemaldas kaheastmelise autentimise kohustuse kasutajalt %{target}"
+        disable_custom_emoji: "%{name} keelas emotikooni %{target}"
+        disable_user: "%{name} keelas sisselogimise kasutajal %{target}"
+        enable_custom_emoji: "%{name} lubas emotikooni %{target}"
+        enable_user: "%{name} lubas sisselogimise kasutajal %{target}"
+        memorialize_account: "%{name} muutis %{target}-i kasutaja memoriaaliks"
+        promote_user: "%{name} edendas kasutajat %{target}"
+        remove_avatar_user: "%{name} kustutas kasutaja %{target} profiilipildi"
+        reopen_report: "%{name} taasavas teate %{target}"
+        reset_password_user: "%{name} lähtestas parooli kasutajal %{target}"
+        resolve_report: "%{name} lahendas teate %{target}"
+        silence_account: "%{name} vaigistas %{target}-i kasutaja"
+        suspend_account: "%{name} peatas %{target}-i kasutaja"
+        unassigned_report: "%{name} eemaldas määratluse teatelt %{target}"
+        unsilence_account: "%{name} eemaldas vaigistuse %{target}-i kontolt"
+        unsuspend_account: "%{name} eemaldas peatamise %{target}-i kontolt"
+        update_custom_emoji: "%{name} uuendas emotikooni %{target}"
+        update_status: "%{name} uuendas kasutaja %{target} staatust"
+      deleted_status: "(kustutatud staatus)"
+      title: Auditilogi
+    custom_emojis:
+      by_domain: Domeen
+      copied_msg: Kohaliku koopia loomine emotikonist õnnestus
+      copy: Kopeeri
+      copy_failed_msg: Kohaliku koopia loomine sellest emotikonist ebaõnnestus
+      created_msg: Emotikoni loomine õnnestus!
+      delete: Kustuta
+      destroyed_msg: Emotikoni kustutamine õnnestus!
+      disable: Keela
+      disabled_msg: Selle emotikoni keelamine õnnestus
+      emoji: Emotikonid
+      enable: Luba
+      enabled_msg: Selle emotikoni lubamine õnnestus
+      image_hint: PNG kuni 50KB
+      listed: Nimekirjastatud
+      new:
+        title: Lisa uus emotikon
+      overwrite: Kirjuta üle
+      shortcode: Lühikood
+      shortcode_hint: Vähemalt 2 tähemärki, ainult tähted, numbrid ja alakriipsud
+      title: Emotikonid
+      unlisted: Kirjendamata
+      update_failed_msg: Ei saanud seda emotikoni uuendada
+      updated_msg: Emotikoni uuendamine õnnestus!
+      upload: Lae üles
+    dashboard:
+      authorized_fetch_mode: Autoriseeritud tõmberežiim
+      backlog: mahajäänud tööd
+      config: Konfiguratsioon
+      feature_deletions: Kontode kustutamised
+      feature_invites: Kutselingid
+      feature_profile_directory: Profiilikataloog
+      feature_registrations: Registreerimised
+      feature_relay: Föderatsiooni relee
+      feature_spam_check: Rämpsposti filter
+      feature_timeline_preview: Ajajoone eelvaade
+      features: Omadused
+      hidden_service: Föderatsioon peidetud teenustega
+      open_reports: ava teavitused
+      pending_tags: sildid ootamas ülevaadet
+      pending_users: kasutajad ootamas ülevaadet
+      recent_users: Hiljutised kasutajad
+      search: Täis teksti otsing
+      single_user_mode: Üksiku kasutaja režiim
+      software: Tarkvara
+      space: Kettakasutus
+      title: Töölaud
+      total_users: kokku kasutajaid
+      trends: Trendid
+      week_interactions: interaktsioone see nädal
+      week_users_active: aktiivne see nädal
+      week_users_new: kasutajaid see nädal
+    domain_allows:
+      add_new: Luba domeen
+      created_msg: Domeeni lubamine õnnestus
+      destroyed_msg: Domeen eemaldati lubatute nimekirjast
+      undo: Eemalda lubatute nimekirjast
+    domain_blocks:
+      add_new: Lisa uus domeeniblokeering
+      created_msg: Domeeni blokeeringut töödeldakse
+      destroyed_msg: Domeeniblokeering on tagasi võetud
+      domain: Domeen
+      existing_domain_block_html: Te olete juba lisanud domeenile %{name} piiranguid, palun <a href="%{unblock_url}">eemaldage blokeering</a> enne jätkamist.
+      new:
+        create: Loo blokeering
+        hint: Domeeniblokeering ei takista kontode lisamist andmebaasi, aga lisab nendele kontodele tagasiulatuvalt ja automaatselt erinevaid moderatsioonimeetodeid.
+        severity:
+          desc_html: |-
+            <strong>Vaigista</strong> teeb konto postitused nähtamatuks kõigile, kes teda ei jälgi.
+            <strong>Peata</strong> eemaldab kogu konto sisu, meedia ja profiiliandmed.
+            <strong>Ei midagi</strong> kui Te soovite lihtsalt keelata meediafailid.
+          noop: Ei midagi
+          silence: Vaigista
+          suspend: Peata
+        title: Uus domeeniblokeering
+      reject_media: Keela meediafailid
+      reject_media_hint: Kustutab kohalikult salvestatud meediafailid ja keeldub tulevikus rohkem allalaadimast. Ei puuduta peatamisi
+      reject_reports: Lükka teavitused tagasi
+      reject_reports_hint: Eira kõik teavitused sellelt domeenilt. Ei puuduta peatamisi
+      rejecting_media: keelan meediafaile
+      rejecting_reports: keelan teavitusi
+      severity:
+        silence: vaigistatud
+        suspend: peatatud
+      show:
+        affected_accounts:
+          one: Üks kasutaja andmebaasis mõjutatud
+          other: "%{count} kasutajat andmebaasis mõjutatud"
+        retroactive:
+          silence: Eemalda vaigistus mõjutatud kasutajatelt sellel domeenil
+          suspend: Lõpeta mõjutatud kasutajate peatamine sellel domeenil
+        title: Eemalda domeeniblokeering %{domain}
+        undo: Võta tagasi
+      undo: Võta tagasi domeeniblokeering
+    email_domain_blocks:
+      add_new: Lisa uus
+      created_msg: E-posti aadressi keelunimekirja lisamine õnnestus
+      delete: Kustuta
+      destroyed_msg: E-posti aadressi keelunimekirjast kustutamine õnnestus
+      domain: Domeen
+      new:
+        create: Lisa domeen
+        title: Uus e-posti keelunimekirja sisend
+      title: E-posti keelunimekiri
+    followers:
+      back_to_account: Tagasi minu kontole
+      title: "%{acct}-i jälgijad"
+    instances:
+      by_domain: Domeen
+      delivery_available: Üleandmine on saadaval
+      known_accounts:
+        one: "%{count} teadaolev kasutaja"
+        other: "%{count} teadaolevat kasutajat"
+      moderation:
+        all: Kõik
+        limited: Piiratud
+        title: Moderatsioon
+      title: Föderatsioon
+      total_blocked_by_us: Meie poolt blokeeritud
+      total_followed_by_them: Nende poolt jälgitud
+      total_followed_by_us: Meie poolt jälgitud
+      total_reported: Nende kohta teateid
+      total_storage: Lisatud meedia
+    invites:
+      deactivate_all: Peata kõik
+      filter:
+        all: Kõik
+        available: Saadaval
+        expired: Aegunud
+        title: Filter
+      title: Kutsed
+    pending_accounts:
+      title: Ootel olevad kasutajad (%{count})
+    relays:
+      add_new: Lisa uus relee
+      delete: Kustuta
+      description_html: "<strong>Föderatsiooni relee</strong> on vahepealne server, mis vahetab suures koguses tuututusi serverite vahel, mis on selle tellijad ning mis sellele saadavad. <strong>See aitab väikestel ja keskmistel serveritel avastada sisu fediversumist</strong>, mis tavaliselt nõuab teisel serveril olevate inimeste jälgimist."
+      disable: Keela
+      disabled: Keelatud
+      enable: Luba
+      enable_hint: Kui lubatud, siis sinu server tellib kõik avalikud tuututused sellelt releelt, ning hakkab ka enda avalikke tuututusi sellele saatma.
+      enabled: Lubatud
+      inbox_url: Relee URL
+      pending: Ootab relee nõusolekut
+      save_and_enable: Salvesta ja luba
+      setup: Sea üles releeühendus
+      status: Staatus
+      title: Releed
+    report_notes:
+      created_msg: Teade edukalt koostatud!
+      destroyed_msg: Teade edukalt kustutatud!
+    reports:
+      account:
+        note: märkus
+        report: teavita
+      action_taken_by: Meetmeid kasutanud
+      are_you_sure: Olete kindel?
+      assign_to_self: Määra mulle
+      assigned: Määratud moderaator
+      comment:
+        none: Pole
+      created_at: Teavitatud
+      mark_as_resolved: Märgi lahendatuks
+      mark_as_unresolved: Märgi lahendamata
+      notes:
+        create: Lisa märkus
+        create_and_resolve: Lahenda märkusega
+        create_and_unresolve: Taasava märkusega
+        delete: Kustuta
+        placeholder: Kirjelda, mis on ette võetud või muid seotud uuendusi...
+      reopen: Taasava teavitus
+      report: 'Teavitus #%{id}'
+      reported_account: Teavitatud kontost
+      reported_by: Teavitatud
+      resolved: Lahendatud
+      resolved_msg: Teavituse lahendamine õnnestus!
+      status: Staatus
+      title: Teavitused
+      unassign: Eemalda määramine
+      unresolved: Lahendamata
+      updated_at: Uuendatud
+    settings:
+      activity_api_enabled:
+        desc_html: Kohalike postituste, aktiivsete kasutajate ja uute registreerimiste numbrid iganädalaste "ämbritena"
+        title: Avalda koondstatistikat selle kasutaja aktiivsusest
+      bootstrap_timeline_accounts:
+        desc_html: Eralda mitut kasutajanime komadega. Ainult kohalikud ja lukustamata kasutajate nimed töötavad. Kui tühi, on vaikesätteks kõik kohalikud administraatorid.
+        title: Vaikimisi jälgimised uutele kasutajatele
+      contact_information:
+        email: Äri e-post
+        username: Kontakt kasutajanimi
+      custom_css:
+        desc_html: Muuda kujundust CSSi abil, mis laetakse igal lehel
+        title: Kohandatud CSS
+      hero:
+        desc_html: Kuvatud kodulehel. Vähemalt 600x100px soovitatud. Kui pole seadistatud, kuvatakse serveri pisililt
+        title: Maskotipilt
+      mascot:
+        desc_html: Kuvatakse mitmel lehel. Vähemalt 293x205px soovitatud. Kui pole seadistatud, kuvatakse vaikimisi maskott
+        title: Maskotipilt
+      peers_api_enabled:
+        desc_html: Domeenid, mida see server on kohanud fediversumis
+        title: Avalda nimekiri avastatud serveritest
+      preview_sensitive_media:
+        desc_html: Lingi eelvaated teistel veebisaitidel kuvab pisipilti, isegi kui meedia on märgitud tundlikuks
+        title: Kuva tundlikku meediat OpenGraphi eelvaadetes
+      profile_directory:
+        desc_html: Luba kasutajate avastamine
+        title: Luba profiilikataloog
+      registrations:
+        closed_message:
+          desc_html: Kuvatud esilehel kui registreerimised on suletud. Te võite kasutada HTMLi silte
+          title: Suletud registreerimiste sõnum
+        deletion:
+          desc_html: Luba kasutajatel oma konto kustutada
+          title: Ava kontode kustutamine
+        min_invite_role:
+          disabled: Mitte keegi
+          title: Luba kutseid
+      registrations_mode:
+        modes:
+          approved: Kinnitus vajalik konto loomisel
+          none: Keegi ei saa kontoid luua
+          open: Kõik võivad kontoid luua
+        title: Registreerimisrežiim
+      show_known_fediverse_at_about_page:
+        desc_html: Kui lubatud, näitab kõiki teatud fediversumi tuututusi. Vastasel juhul näidatakse ainult kohalike tuututusi.
+        title: Näita teatud fediversumit ajajoone eelvaates
+      show_staff_badge:
+        desc_html: Näita personalimärki kasutaja profiilil
+        title: Näita personalimärki
+      site_description:
+        desc_html: Sissejuhatuslik lõik API kohta. Kirjelda, mis teeb selle Mastodoni serveri eriliseks ja ka muud tähtsat. Te saate kasutada HTMLi silte, peamiselt <code>&lt;a&gt;</code> ja <code>&lt;em&gt;</code>.
+        title: Serveri kirjeldus
+      site_description_extended:
+        desc_html: Hea koht käitumisreegliteks, reegliteks, suunisteks ja muuks, mis teevad Teie serveri eriliseks. Te saate kasutada HTML silte
+        title: Lisa informatsioon
+      site_short_description:
+        desc_html: Kuvatud küljeribal ja metasiltides. Kirjelda, mis on Mastodon ja mis on selles serveris erilist ühes lõigus.
+        title: Serveri lühikirjeldus
+      site_terms:
+        desc_html: Te saate kirjutada oma privaatsuspoliitika, kasutustingimused jm seaduslikku infot. Te saate kasutada HTMLi silte
+        title: Kasutustingimused
+      site_title: Serveri nimi
+      spam_check_enabled:
+        desc_html: Mastodon suudab automaatselt vaigistada ja teatada kasutajatest, kasutades erinevaid meetmeid, näiteks kui kasutaja saadab korduvalt ebasobivaid sõnumeid. Võib esineda ka valehäireid.
+        title: Rämpsposti filter
+      thumbnail:
+        desc_html: Kasutatud OpenGraph ja API eelvaadeteks. 1200x630px soovitatud
+        title: Serveri pisipilt
+      timeline_preview:
+        desc_html: Kuva avalikku ajajoont esilehel
+        title: Ajajoone eelvaade
+      title: Lehe seaded
+    statuses:
+      back_to_account: Tagasi konto lehele
+      batch:
+        delete: Kustuta
+        nsfw_off: Märgi kui mitte tundlik
+        nsfw_on: Märgi kui tundlik
+      failed_to_execute: Täitmine ebaõnnestus
+      media:
+        title: Meedia
+      no_media: Meedia puudub
+      no_status_selected: Mitte ühtegi staatust muudeti sest midagi polnud valitud
+      title: Konto staatused
+      with_media: Meediaga
+    tags:
+      title: Sildid
+    title: Administreerimine
+    warning_presets:
+      add_new: Lisa uus
+      delete: Kustuta
+      edit: Redigeeri
+      edit_preset: Redigeeri hoiatuse eelseadistust
+      title: Halda hoiatuste eelseadistusi
+  admin_mailer:
+    new_pending_account:
+      body: Uue konto üksikasjad on allpool. Te saate vastu võtta või tagasi lükata seda taotlust.
+      subject: Uus konto valmis ülevaatluseks serveril %{instance} (%{username})
+    new_report:
+      body: "%{reporter} teavitas kasutajast %{target}"
+      body_remote: Keegi domeenist %{domain} teavitas kasutajast %{target}
+      subject: Uus teavitus %{instance}-ile (#%{id})
+  appearance:
+    advanced_web_interface: Arenenud veebiliides
+    advanced_web_interface_hint: 'Kui soovite kasutada terve ekraani laiust, lubab arenenud veebiliides seadistada mitut erinevat veergu, et näha nii palju informatsiooni samal ajal kui võimalik: Kodu, teavitused, föderatsiooni ajajoon ning mis iganes arv nimekirju ja silte.'
+    animations_and_accessibility: Animatsioonid ja ligipääs
+    confirmation_dialogs: Kinnitusdialoogid
+    sensitive_content: Tundlik sisu
+  application_mailer:
+    notification_preferences: Muuda e-kirjade eelistusi
+    salutation: "%{name},"
+    settings: 'Muuda e-kirjade eelistusi: %{link}'
+    view: 'Vaade:'
+    view_profile: Vaata profiili
+    view_status: Vaata staatust
+  applications:
+    created: Rakenduse loomine õnnestus
+    destroyed: Rakenduse kustutamine õnnestus
+    invalid_url: Antud URL on vale
+    regenerate_token: Loo uus access token
+    token_regenerated: Access tokeni loomine õnnestus
+    warning: Olge nende andmetega ettevaatlikud. Ärge jagage neid kellegagi!
+    your_token: Sinu access token
+  auth:
+    apply_for_account: Taotle kutse
+    change_password: Salasõna
+    checkbox_agreement_html: Ma nõustun <a href="%{rules_path}" target="_blank">serveri reeglitega</a> ja <a href="%{terms_path}" target="_blank">kasutustingimustega</a>
+    checkbox_agreement_without_rules_html: Ma nõustun <a href="%{terms_path}" target="_blank">kasutustingimustega</a>
+    delete_account: Kustuta konto
+    delete_account_html: Kui Te soovite oma kontot kustutada, võite <a href="%{path}">jätkata siit</a>. Teilt küsitakse kinnitust.
+    didnt_get_confirmation: Ei saanud kinnituse juhendeid?
+    forgot_password: Unustasid oma salasõna?
+    invalid_reset_password_token: Salasõna lähtestusvõti on vale või aegunud. Palun taotle uus.
+    login: Logi sisse
+    logout: Logi välja
+    migrate_account: Koli teisele kasutajale
+    migrate_account_html: Kui Te soovite seda kontot ümber viia teisele, <a href="%{path}">saate teha seda siit</a>.
+    or_log_in_with: Või logi sisse koos
+    providers:
+      cas: CAS
+      saml: SAML
+    register: Loo konto
+    registration_closed: "%{instance} ei võta vastu uusi liikmeid"
+    resend_confirmation: Saada kinnitusjuhendid uuesti
+    reset_password: Lähtesta salasõna
+    security: Turvalisus
+    set_new_password: Määra uus salasõna
+    setup:
+      email_below_hint_html: Kui allolev e-posti aadress on vale, saate Te muuta seda siin ning Teile saadetakse uus kinnituskiri.
+      email_settings_hint_html: Kinnituskiri saadeti e-posti aadressile %{email}. Kui see aadress pole õige, saate Te muuta seda oma konto sätetest.
+      title: Seadistamine
+    status:
+      account_status: Konto olek
+      confirming: Ootan e-posti kinnitust.
+      pending: Teie taotlus ootab ülevaadet meie personali poolt. See võib võtta mõnda aega. Kui Teie taotlus on vastu võetud, saadetakse Teile e-kiri.
+    trouble_logging_in: Probleeme sisselogimisega?
+  authorize_follow:
+    already_following: Te juba jälgite seda kontot
+    error: Kahjuks ilmus viga kasutaja kaugserverist otsimisel
+    follow: Jälgi
+    follow_request: 'Te olete saatnud jälgimistaotluse kasutajale:'
+    following: 'Õnnestus! Te nüüd jälgite kasutajat:'
+    post_follow:
+      close: Või Te saate lihtsalt sulgeda seda akent.
+      return: Näita kasutaja profiili
+      web: Mine veebi
+    title: Jälgi %{acct}
+  datetime:
+    distance_in_words:
+      about_x_hours: "%{count}t"
+      about_x_months: "%{count}k"
+      about_x_years: "%{count}a"
+      almost_x_years: "%{count}a"
+      half_a_minute: Just praegu
+      less_than_x_minutes: "%{count}m"
+      less_than_x_seconds: Just praegu
+      over_x_years: "%{count}a"
+      x_days: "%{count}p"
+      x_minutes: "%{count}m"
+      x_months: "%{count}k"
+      x_seconds: "%{count}s"
+  deletes:
+    confirm_password: Sisesta oma praegune salasõna, et kinnitada oma identiteet
+    proceed: Kustuta konto
+    success_msg: Konto kustutamine õnnestus
+  directories:
+    directory: Profiilikataloog
+    explanation: Avasta kasutajaid nende huvide põhjal
+    explore_mastodon: Avasta %{title}
+  errors:
+    '400': The request you submitted was invalid or malformed.
+    '403': Sul puudub õigus seda lehte vaadata.
+    '404': Soovitud lehte ei leitud.
+    '406': This page is not available in the requested format.
+    '410': Soovitud lehekülge pole enam siin.
+    '422':
+      content: Turvalisuse kontroll ebaõnnestus. Kas Te blokeerite küpsiseid?
+      title: Turvalisuse kontroll ebaõnnestus
+    '429': Aeglustatud
+    '500':
+      content: Palume vabandust, midagi läks valesti meie pool.
+      title: See lehekülg pole õige
+    '503': The page could not be served due to a temporary server failure.
+    noscript_html: Et kasutada Mastodoni veebirakendust, palun lubage JavaScript. Alternatiivselt, proovige mõnda <a href="%{apps_path}">rakendust</a> Teie platvormile.
+  existing_username_validator:
+    not_found: ei suutnud leida kohalikku kasutajat selle kasutajanimega
+    not_found_multiple: ei suutnud leida %{usernames}
+  exports:
+    archive_takeout:
+      date: Kuupäev
+      download: Lae alla oma arhiiv
+      hint_html: Te saate taotleda arhiivi oma <strong>tuututustest ja üleslaetud meediast</strong>. Eksporditud andmed on ActivityPub-i formaadis, loetav vastava tarkvara poolt. Te saate taotleda arhiivi iga 7 päeva tagant.
+      in_progress: Loome Teie arhiivi...
+      request: Taotle oma arhiivi
+      size: Suurus
+    blocks: Teie blokeerite
+    csv: CSV
+    domain_blocks: Domeeni blokeeringud
+    follows: Teie jälgite
+    lists: Nimistud
+    mutes: Teie vaigistate
+    storage: Meedia hoidla
+  featured_tags:
+    add_new: Lisa uus
+    errors:
+      limit: Olete jõudnud maksimum lubatud siltide arvuni
+  filters:
+    contexts:
+      home: Kodu ajajoon
+      notifications: Teated
+      public: Avalikud ajajooned
+      thread: Vestlused
+    edit:
+      title: Muuda filtrit
+    errors:
+      invalid_context: Puudulik või vale kontekst
+      invalid_irreversible: Taastamatu filter töötab ainult kodu või teavituste kontekstis
+    index:
+      delete: Kustuta
+      title: Filterid
+    new:
+      title: Lisa uus filter
+  footer:
+    developers: Arendajad
+    more: Rohkem…
+    resources: Materjalid
+  generic:
+    all: Kõik
+    changes_saved_msg: Muudatuste salvestamine õnnestus!
+    copy: Kopeeri
+    order_by: Järjesta
+    save_changes: Salvesta muudatused
+    validation_errors:
+      one: Midagi pole ikka õigesti! Palun vaadake allolev veateade üle
+      other: Midagi pole ikka õigesti! Palun vaadake all olevad %{count} veateadet üle
+  html_validator:
+    invalid_markup: 'sisaldab valet HTMLi süntaksi: %{error}'
+  identity_proofs:
+    active: Aktiivne
+    authorize: Jah, luba
+    authorize_connection_prompt: Luba see krüptograafiline ühendus?
+    errors:
+      failed: Krüptograafiline ühendus ebaõnnestus. Palun proovi uuesti %{provider}-ist.
+      keybase:
+        invalid_token: Keybase võtmed on allkirjade hashid ja peavad olema 66 hex tähemärki
+        verification_failed: Keybase ei tunnista seda võtit kui kasutaja %{kb_username} omand. Palun proovige uuesti Keybasest.
+      wrong_user: Ei saa luua tõendit kontole %{proving} kui olete sisseloginud kui %{current}. Logige sisse kui %{proving} ja proovige uuesti.
+    explanation_html: Siin saate luua krüptograafilisi ühendusi oma teiste identiteetidega, nagu näiteks Keybase profiiliga. See võimaldab teistel kasutajatel saata Teile krüptitud sõnumeid ja usaldada sisu, mis Teie saadate neile.
+    i_am_html: Ma olen %{username} teenusel %{service}.
+    identity: Identiteet
+    inactive: Passiivne
+    publicize_checkbox: 'Ja tuututa seda:'
+    publicize_toot: 'See on tõestatud! Ma olen %{username} teenusel %{service}: %{url}'
+    status: Kinnituse staatus
+    view_proof: Vaata tõendit
+  imports:
+    modes:
+      merge: Lõimi
+      merge_long: Hoia olemasolevad andmed ja lisa uusi
+      overwrite: Kirjuta üle
+      overwrite_long: Vaheta praegused andmed uute vastu
+    preface: Te saate importida mis tahes andmeid, mis on eksporditud teisest serverist. Näiteks nimekiri inimestest, keda jälgid ja keda blokeerid.
+    success: Teie andmete üleslaadimine õnnestus ning neid töödeldakse
+    types:
+      blocking: Blokeeringute nimekiri
+      domain_blocking: Domeeniblokeeringute nimekiri
+      following: Jälgimiste nimekiri
+      muting: Vaigistuse nimekiri
+    upload: Lae üles
+  in_memoriam_html: Mälestamaks.
+  invites:
+    delete: Peata
+    expired: Aegunud
+    expires_in:
+      '1800': 30 minutit
+      '21600': 6 tundi
+      '3600': 1 tund
+      '43200': 12 tundi
+      '604800': 1 nädal
+      '86400': 1 päev
+    expires_in_prompt: Mitte kunagi
+    generate: Loo
+    invited_by: 'Teid kutsus:'
+    max_uses:
+      one: 1 kasutus
+      other: "%{count} kasutust"
+    max_uses_prompt: Piiranguteta
+    prompt: Loo ja jaga linke teistega, et lubada neil liituda selle serveriga
+    table:
+      expires_at: Aegub
+      uses: Kasutust
+    title: Kutsu inimesi
+  lists:
+    errors:
+      limit: Olete jõudnud maksimum lubatud nimekirjade arvuni
+  media_attachments:
+    validations:
+      images_and_video: Ei saa lisada video staatusele, millel on juba pildid
+      too_many: Ei saa lisada rohkem, kui 4 faili
+  migrations:
+    acct: uue konto kasutajanimi@domeen
+  moderation:
+    title: Moderatsioon
+  notification_mailer:
+    digest:
+      action: Vaata kõiki teateid
+      body: Siin on kiire ülevaade sellest, mis sõnumeid Te ei näinud pärast Teie viimast külastust %{since}
+      mention: "%{name} mainis sind postituses:"
+      new_followers_summary:
+        one: Ja veel, Te saite ühe uue jälgija kui Te olite eemal! Jee!
+        other: Ja veel, Te saite %{count} uut jälgijat kui Te olite eemal! Hämmastav!
+      subject:
+        one: "1 uus teavitus peale Teie eelmist külastust \U0001F418"
+        other: "%{count} uut teavitust peale Teie eelmist külastust \U0001F418"
+      title: Teie puudumisel...
+    favourite:
+      body: "%{name} lisas Teie staatuse lemmikutesse:"
+      subject: "%{name} märkis su staatuse lemmikuks"
+      title: Uus lemmik
+    follow:
+      body: "%{name} nüüd jälgib teid!"
+      subject: "%{name} nüüd jälgib teid"
+      title: Uus jälgija
+    follow_request:
+      action: Halda jälgimistaotlusi
+      body: "%{name} soovib Teid jälgida"
+      subject: 'Ootav jälgija: %{name}'
+      title: Uus jälgimistaotlus
+    mention:
+      action: Vasta
+      body: "%{name} mainis Teid:"
+      subject: "%{name} mainis Teid"
+      title: Uus mainimine
+    reblog:
+      body: "%{name} upitas Teie staatust:"
+      subject: "%{name} upitas su staatust"
+      title: Uus upitus
+  number:
+    human:
+      decimal_units:
+        format: "%n%u"
+        units:
+          billion: B
+          million: M
+          quadrillion: Q
+          thousand: T
+          trillion: Tr
+  pagination:
+    newer: Uuemad
+    next: Järgmine
+    older: Vanemad
+    prev: Eelm
+    truncate: "&hellip;"
+  polls:
+    errors:
+      already_voted: Olete siin juba hääletanud
+      duplicate_options: sisaldab samu asju mitu korda
+      duration_too_long: kestab liiga kaua
+      duration_too_short: on liiga vara
+      expired: See küsitlus on juba lõppenud
+      over_character_limit: igaüks ei saa olla rohkem kui %{max} tähemärki
+      too_few_options: peab olema rohkem kui üks vastus
+      too_many_options: ei saa sisaldada rohkem kui %{max} vastust
+  preferences:
+    other: Muu
+    posting_defaults: Postitamise vaikesätted
+    public_timelines: Avalikud ajajooned
+  relationships:
+    activity: Konto tegevus
+    dormant: Seisev
+    last_active: Viimati aktiivne
+    most_recent: Viimased
+    moved: Kolinud
+    mutual: Ühine
+    primary: Peamine
+    relationship: Seos
+    remove_selected_domains: Eemalda kõik jälgijad valitud domeenidelt
+    remove_selected_followers: Eemalda valitud jälgijad
+    remove_selected_follows: Lõpeta valitud kasutajate jälgimine
+    status: Konto olek
+  remote_follow:
+    acct: Sisestage oma kasutajanimi@domeen, kust te soovite jälgida
+    missing_resource: Ei suutnud leida vajalikku suunamise URLi Teie konto jaoks
+    no_account_html: Teil pole veel kontot? Saate <a href='%{sign_up_path}' target='_blank'>luua ühe siit</a>
+    proceed: Jätka jälgimiseks
+    prompt: 'Te hakkate jälgima:'
+    reason_html: |-
+      <strong>Miks on see samm vajalik?</strong>
+      <code>%{instance}</code> ei pruugi olla server, kus asub Teie konto, nii et me peame Teid suunama oma kodu serverile.
+  remote_interaction:
+    favourite:
+      proceed: Jätka lemmikuks lisamiseks
+      prompt: 'Te soovite lisada seda tuututust lemmikutesse:'
+    reblog:
+      proceed: Jätka upitamiseks
+      prompt: 'Te soovite seda tuututust upitada:'
+    reply:
+      proceed: Jätka vastamiseks
+      prompt: 'Te soovite vastata sellele tuututusele:'
+  scheduled_statuses:
+    over_daily_limit: Te olete jõudnud maksimum lubatud ajastatud tuututuste arvuni %{limit} selle päeva kohta
+    over_total_limit: Te olete jõudnud maksimum lubatud ajastatud tuututuste arvuni %{limit}
+    too_soon: Ajastatud tuututuse kuupäev peab olema tukevikus
+  sessions:
+    activity: Viimane aktiivsus
+    browser: Veebilehitseja
+    browsers:
+      alipay: Alipay
+      blackberry: Blackberry
+      chrome: Chrome
+      edge: Microsoft Edge
+      electron: Electron
+      firefox: Firefox
+      generic: Tundmatu veebilehitseja
+      ie: Internet Explorer
+      micro_messenger: MicroMessenger
+      nokia: Nokia S40 Ovi Browser
+      opera: Opera
+      otter: Otter
+      phantom_js: PhantomJS
+      qq: QQ Browser
+      safari: Safari
+      uc_browser: UCBrowser
+      weibo: Weibo
+    current_session: Praegune seanss
+    description: "%{browser} platvormil %{platform}"
+    explanation: Need on praegused veebilehitsejad, mis on sisse logitud sinu Mastodoni kontosse.
+    ip: IP
+    platforms:
+      adobe_air: Adobe Air
+      android: Android
+      blackberry: Blackberry
+      chrome_os: ChromeOS
+      firefox_os: Firefox OS
+      ios: iOS
+      linux: Linux
+      mac: Mac
+      other: tundmatu platvorm
+      windows: Windows
+      windows_mobile: Windows Mobile
+      windows_phone: Windows Phone
+    revoke: Tühista
+    revoke_success: Seanssi tühistamine õnnestus
+    title: Seanssid
+  settings:
+    account: Konto
+    account_settings: Kontosätted
+    appearance: Välimus
+    authorized_apps: Lubatud rakendused
+    back: Tagasi Mastodoni
+    delete: Konto kustutamine
+    development: Arendus
+    edit_profile: Muuda profiili
+    export: Andmete eksportimine
+    featured_tags: Esile toodud sildid
+    identity_proofs: Isikutõendid
+    import: Impordi
+    import_and_export: Import / eksport
+    migrate: Konto migreerumine
+    notifications: Teated
+    preferences: Eelistused
+    profile: Profiil
+    relationships: Jälgitud ja jälgijad
+    two_factor_authentication: Kahesammuline autentimine
+  statuses:
+    attached:
+      description: 'Manused: %{attached}'
+      image:
+        one: "%{count} pilt"
+        other: "%{count} pilti"
+      video:
+        one: "%{count} video"
+        other: "%{count} videot"
+    boosted_from_html: Upitatud %{acct_link}
+    content_warning: 'Sisu hoiatus: %{warning}'
+    disallowed_hashtags:
+      one: 'sisaldab ebasobivat silti: %{tags}'
+      other: 'sisaldab ebasobivaid silte: %{tags}'
+    language_detection: Automaatselt tuvasta keel
+    open_in_web: Ava veebis
+    over_character_limit: tähtmärkide limiit %{max} ületatud
+    pin_errors:
+      limit: Te olete juba maksimum arvu lubatud tuututusi kinnitanud
+      ownership: Kellegi teise tuututust ei saa kinnitada
+      private: Privaatset tuututust ei saa kinnitada
+      reblog: Upitust ei saa kinnitada
+    poll:
+      total_votes:
+        one: "%{count} hääl"
+        other: "%{count} häält"
+      vote: Hääleta
+    show_more: Näita rohkem
+    sign_in_to_participate: Logi sisse, et liituda vestlusega
+    title: '%{name}: "%{quote}"'
+    visibilities:
+      private: Ainult jälgijatele
+      private_long: Näita ainult jälgijatele
+      public: Avalik
+      public_long: Kõik saavad näha
+      unlisted: Kirjendamata
+      unlisted_long: Kõik saavad näha, aga ei ole saadaval avalikes ajajoontes
+  stream_entries:
+    pinned: Kinnitatud tuut
+    reblogged: upitatud
+    sensitive_content: Tundlik sisu
+  terms:
+    title: "%{instance} Kasutustingimused ja Privaatsuspoliitika"
+  themes:
+    contrast: Mastodon (Kõrge kontrast)
+    default: Mastodon (Tume)
+    mastodon-light: Mastodon (Hele)
+  time:
+    formats:
+      default: "%d. %B, %Y. aastal, kell %H:%M"
+      month: "%B %Y"
+  two_factor_authentication:
+    code_hint: Sisesta kaheastmelise autentimise kood, mille lõi Teie autentimisrakendus, et jätkata
+    description_html: Kui Te aktiveerite <strong>kaheastmelise autentimise</strong>, siis sisselogimisel peab teil olema telefon, mis loob Teile koode sisenemiseks.
+    disable: Lülita välja
+    enable: Lülita sisse
+    enabled: Kaheastmeline autentimine on sisse lülitatud
+    enabled_success: Kaheastmeline autentimine on edukalt sisse lülitatud
+    generate_recovery_codes: Loo taastuskoodid
+    instructions_html: "<strong>Skaneeri see QR kood kasutades rakendust Google Authenticator või muu TOTP rakendus Teie telefonis</strong>. Nüüdsest alates loob see rakendus Teile koode, mida peate sisestama sisselogimisel."
+    lost_recovery_codes: Taastuskoodide abil on Teil võimalik sisse logida kontosse, kui Te kaotate oma telefoni. Kui Te kaotate oma taastuskoodid, saate need uuesti luua siin. Teie vanad taastuskoodid tehakse kehtetuks.
+    manual_instructions: 'Kui Te ei saa seda QR koodi skaneerida ning peate sisestama selle käsitsi, on siin tekstiline salavõti:'
+    recovery_codes: Tagavara taastuskoodid
+    recovery_codes_regenerated: Taastuskoodid edukalt taasloodud
+    recovery_instructions_html: Kui Te juhtute kunagi kaotama oma telefoni, saate kasutada ühte allpool olevatest taastuskoodidest, et saada ligipääsu oma kontole. <strong>Hoidke taastuskoodid turvaliselt</strong>. Näiteks võite Te need välja printida ning hoida need koos teiste tähtsate dokumentidega.
+    setup: Sätesta
+    wrong_code: Sisestatud kood on vale! Kas serveri aeg ja seadme aeg on õiged?
+  user_mailer:
+    backup_ready:
+      explanation: Te taotlesite varukoopia oma Mastodoni kontost. See on nüüd valmis allalaadimiseks!
+      subject: Teie arhiiv on allalaadimiseks valmis
+      title: Arhiivi väljavõte
+    warning:
+      explanation:
+        disable: Kuniks Teie konto on lukus, ei saa Te teha sellega ühtegi tegevust kuni see on lukust lahti tehtud.
+        silence: Kuniks Teie konto on limiteeritud, ainult need inimesed, kes Teid juba jälgivad, näevad Teie tuututusi sellel serveril ning Teid jäetakse välja avalikest nimekirjadest. Sellest hoolimata võivad inimesed Teid siiski jälgida.
+        suspend: Teie konto on peatatud ning kõik Teie tuututused ja üleslaetud meediafailid on jäädavalt kustutatud sellelt serverilt ning serveritelt, kus Teil oli jälgijad.
+      review_server_policies: Vaadake üle serveri eeskirju
+      subject:
+        disable: Teie konto %{acct} on lukustatud
+        none: Hoiatus kasutajale %{acct}
+        silence: Teie kontole %{acct} on kehtestatud limiidid
+        suspend: Teie konto %{acct} on peatatud
+      title:
+        disable: Konto lukustatud
+        none: Hoiatus
+        silence: Konto limiteeritud
+        suspend: Konto peatatud
+    welcome:
+      edit_profile_action: Sea üles profiil
+      edit_profile_step: Te saate oma profiili isikupärastada näiteks lisades profiilipildi, päise, muutes oma kuvanime ja muud. Kui Te soovite üle vaadata inimesi, kes Teid jälgida soovivad, saate lukustada oma konto.
+      explanation: Siin on mõned nõuanded, mis aitavad sul alustada
+      final_action: Alusa postitamist
+      final_step: 'Alusta postitamist! Isegi ilma jälgijateta näevad teised Teie avalikke postitusi, näiteks kohalikul ajajoonel ning siltidest. Te võite ennast tutvustada kasutades silti #introductions.'
+      full_handle: Sinu täisnimi
+      full_handle_hint: See on mida oma sõpradega jagada, et nad saaksid Teile sõnumeid saata ning Teid jälgida teiselt serverilt.
+      review_preferences_action: Muuda eelistusi
+      review_preferences_step: Kindlasti seadistage oma sätted Teie maitse järgi, näiteks e-kirju, mida soovite saada, või millist privaatsustaset Te soovite vaikimisi. Kui Teil pole merehaigust, võite Te näiteks lubada GIFide automaatse mängimise.
+      subject: Tere tulemast Mastodoni
+      tip_federated_timeline: Föderatiivne ajajoon on reaalajas voogvaade tervest Mastodoni võrgust. Aga see sisaldab ainult inimesi, keda su naabrid tellivad, niiet see pole täiuslik.
+      tip_following: Vaikimisi, Te jälgite ainult oma serveri administraator(eid). Et leida rohkem huvitavamaid inimesi, vaadake kohalikke ja föderatiivseid ajajooni.
+      tip_local_timeline: Kohalik ajajoon on reaalajas voogvaade inimestest, kes on serveris %{instance}. Need on Teie lähimad naabrid!
+      tip_mobile_webapp: Kui Teie mobiilne veebilehitseja pakub Teile lisada meid Teie avaekraanile, saate Te reaalajas teateid. See töötab nagu tavaline mobiilirakendus mitmel moel!
+      tips: Nõuanded
+      title: Tere tulemast pardale, %{name}!
+  users:
+    follow_limit_reached: Te ei saa jälgida rohkem kui %{limit} inimest
+    invalid_email: See e-posti aadress on vale
+    invalid_otp_token: Vale kaheastmelise autentimise kood
+    otp_lost_help_html: Kui Te kaotasite ligipääsu mõlemale, saate võtta ühendust %{email}-iga
+    seamless_external_login: Te olete sisse loginud läbi väljaspool asuva teenusega, niiet salasõna ja e-posti sätted pole saadaval.
+    signed_in_as: 'Sisse logitud kasutajana:'
+  verification:
+    explanation_html: 'Te saate <strong>kinnitada ennast oma linkide omanikena oma profiili metaandmetes</strong>. Et seda teha, peab Teie lingitud veebilehel olema link tagasi Teie Mastodoni profiilile. Tagasi saatval lingil <strong>peab</strong> olema <code>rel="me"</code> atribuut. Tekstisisu lingil ei loe. Siin on üks näide:'
+    verification: Kinnitamine
diff --git a/config/locales/eu.yml b/config/locales/eu.yml
index 9b9c2c02719f731bbeb227af5a9af0233cea0575..1f2ac6644e35899b3575377cac5efc94e65fd2d2 100644
--- a/config/locales/eu.yml
+++ b/config/locales/eu.yml
@@ -4,7 +4,7 @@ eu:
     about_hashtag_html: Hauek  <strong>#%{hashtag}</strong> traola duten toot publikoak dira. Fedibertsoko edozein kontu baduzu harremanetan jarri zaitezke.
     about_mastodon_html: Mastodon web protokolo ireki eta libreak darabiltzan gizarte sare bat da. E-mail sarea bezala deszentralizatua da.
     about_this: Honi buruz
-    active_count_after: aktiboa
+    active_count_after: aktibo
     active_footnote: Hilabeteko erabiltzaile aktiboak (HEA)
     administered_by: 'Administratzailea(k):'
     api: APIa
@@ -17,13 +17,12 @@ eu:
     contact_unavailable: E/E
     discover_users: Aurkitu erabiltzaileak
     documentation: Dokumentazioa
-    extended_description_html: |
-      <h3>Arauentzako toki egoki bat</h3>
-      <p>Azalpen luzea ez da ezarri oraindik.</p>
     federation_hint_html: "%{instance} instantzian kontu bat izanda edozein Mastodon zerbitzariko jendea jarraitu ahal izango duzu, eta harago ere."
-    generic_description: "%{domain} sareko zerbitzari bat da"
     get_apps: Probatu mugikorrerako aplikazio bat
     hosted_on: Mastodon %{domain} domeinuan ostatatua
+    instance_actor_flash: 'Kontu hau zerbitzaria bera adierazten duen aktore birtual bat da, ez norbanako bat. Federaziorako erabiltzen da eta ez zenuke blokeatu behar instantzia osoa blokeatu nahi ez baduzu, kasu horretan domeinua blokeatzea egokia litzateke.
+
+'
     learn_more: Ikasi gehiago
     privacy_policy: Pribatutasun politika
     see_whats_happening: Ikusi zer gertatzen ari den
@@ -35,6 +34,14 @@ eu:
     status_count_before: Hauek
     tagline: Jarraitu lagunak eta egin berriak
     terms: Erabilera baldintzak
+    unavailable_content: Eduki eskuraezina
+    unavailable_content_description:
+      domain: Zerbitzaria
+      reason: Arrazoia
+      rejecting_media: 'Zerbitzari hauetako multimedia fitxategiak ez dira prozesatuko ez gordeko, eta ez dira iruditxoak bistaratuko, jatorrizko irudira joan behar izango da klik eginez:'
+      silenced: 'Zerbitzari hauetako mezuak denbora-lerro eta elkarrizketa publikoetan ezkutatuko dira, eta bere erabiltzaileen interakzioek ez dute jakinarazpenik sortuko ez badituzu jarraitzen:'
+      suspended: 'Ez da zerbitzari hauetako daturik prozesatuko, gordeko, edo partekatuko, zerbitzari hauetako erabiltzaileekin komunikatzea ezinezkoa eginez:'
+    unavailable_content_html: Mastodonek orokorrean fedibertsoko beste zerbitzarietako erabiltzaileen edukia ikustea eta beraiekin aritzea ahalbidetzen dizu. Salbuespena egin da zerbitzari zehatz honekin.
     user_count_after:
       one: erabiltzaile
       other: erabiltzaile
@@ -42,6 +49,8 @@ eu:
     what_is_mastodon: Zer da Mastodon?
   accounts:
     choices_html: "%{name}(r)en aukerak:"
+    endorsements_hint: Jarraitzen duzun jendea sustatu dezakezu web interfazearen bidez, eta hemen agertuko da.
+    featured_tags_hint: Hemen agertuko diren traolak nabarmendu ditzakezu.
     follow: Jarraitu
     followers:
       one: Jarraitzaile
@@ -51,8 +60,9 @@ eu:
     last_active: azkenekoz aktiboa
     link_verified_on: 'Esteka honen jabetzaren egiaztaketa data: %{date}'
     media: Multimedia
-    moved_html: "%{name} hona lekualdatu da %{new_profile_link}:"
+    moved_html: "%{name} hona migratu da %{new_profile_link}:"
     network_hidden: Informazio hau ez dago eskuragarri
+    never_active: Inoiz ez
     nothing_here: Ez dago ezer hemen!
     people_followed_by: "%{name}(e)k jarraitzen dituenak"
     people_who_follow: "%{name} jarraitzen dutenak"
@@ -61,8 +71,8 @@ eu:
     posts:
       one: Toot
       other: Toot
-    posts_tab_heading: Toot
-    posts_with_replies: Toot eta erantzunak
+    posts_tab_heading: Toot-ak
+    posts_with_replies: Toot-ak eta erantzunak
     reserved_username: Erabiltzaile-izena erreserbatuta dago
     roles:
       admin: Administratzailea
@@ -183,6 +193,7 @@ eu:
       username: Erabiltzaile-izena
       warn: Abisatu
       web: Weba
+      whitelisted: Zerrenda zurian
     action_logs:
       actions:
         assigned_to_self_report: "%{name}(e)k %{target} salaketa bere buruari esleitu dio"
@@ -218,19 +229,24 @@ eu:
       deleted_status: "(ezabatutako mezua)"
       title: Auditoria-egunkaria
     custom_emojis:
+      assign_category: Esleitu kategoria
       by_domain: Domeinua
       copied_msg: Ongi sortu da emoji-aren kopia lokala
       copy: Kopiatu
       copy_failed_msg: Ezin izan da emoji-aren kopia lokal bat sortu
+      create_new_category: Sortu kategoria berria
       created_msg: Emoji-a ongi sortu da!
       delete: Ezabatu
       destroyed_msg: Emoji-a ongi suntsitu da!
       disable: Desgaitu
+      disabled: Desgaituta
       disabled_msg: Emoji-a ongi desgaitu da
       emoji: Emojia
       enable: Gaitu
+      enabled: Gaituta
       enabled_msg: Emoji hori ongi gaitu da
       image_hint: PNG gehienez 50KB
+      list: Zerrendatu
       listed: Zerrendatua
       new:
         title: Gehitu emoji pertsonal berria
@@ -238,11 +254,14 @@ eu:
       shortcode: Laster-kodea
       shortcode_hint: Gutxienez 2 karaktere, alfanumerikoak eta azpimarra  besterik ez
       title: Emoji pertsonalak
+      uncategorized: Kategoriarik gabe
+      unlist: Kendu zerrendatik
       unlisted: Zerrendatu gabea
       update_failed_msg: Ezin izan da emoji hori eguneratu
       updated_msg: Emoji-a ongi eguneratu da!
       upload: Igo
     dashboard:
+      authorized_fetch_mode: Baimendutako eskuratze modua
       backlog: aurreikusitako lanak
       config: Konfigurazioa
       feature_deletions: Kontu ezabaketak
@@ -250,10 +269,13 @@ eu:
       feature_profile_directory: Profil-direktorioa
       feature_registrations: Izen emateak
       feature_relay: Federazio haria
+      feature_spam_check: Anti-spam
       feature_timeline_preview: Denbora-lerroaren aurrebista
       features: Ezaugarriak
       hidden_service: Federazioa ezkutuko zerbitzuekin
       open_reports: salaketa irekiak
+      pending_tags: berrikusketaren zain dauden etiketak
+      pending_users: berrikusketaren zain dauden erabiltzaileak
       recent_users: Azken erabiltzaileak
       search: Testu osoko bilaketa
       single_user_mode: Erabiltzaile bakarreko modua
@@ -265,11 +287,18 @@ eu:
       week_interactions: interakzio aste honetan
       week_users_active: aktibo aste honetan
       week_users_new: erabiltzaile aste honetan
+      whitelist_mode: Zerrenda zuria modua
+    domain_allows:
+      add_new: Sartu domeinua zerrenda zurian
+      created_msg: Domeinua ongi sartu da zerrenda zurian
+      destroyed_msg: Domeinua zerrenda zuritik kendu da
+      undo: Kendu zerrenda zuritik
     domain_blocks:
       add_new: Gehitu domeinuaren blokeo berria
       created_msg: Domeinuaren blokeoa orain prozesatzen ari da
       destroyed_msg: Domeinuaren blokeoa desegin da
       domain: Domeinua
+      edit: Editatu domeinu-blokeoa
       existing_domain_block_html: '%{name} domeinuan muga zorrotzagoak ezarri dituzu jada, aurretik <a href="%{unblock_url}">desblokeatu</a> beharko duzu.'
       new:
         create: Sortu blokeoa
@@ -280,6 +309,10 @@ eu:
           silence: Isilarazi
           suspend: Kanporatu
         title: Domeinuaren blokeo berria
+      private_comment: Iruzkin pribatua
+      private_comment_hint: Domeinu hau mugatzeari buruzko iruzkina moderatzaileen barne erabilerarako.
+      public_comment: Iruzkin publikoa
+      public_comment_hint: Domeinu hau mugatzeari buruzko iruzkina publiko orokorrarentzat, domeinuak mugatzea iragartzea gaituta badago.
       reject_media: Ukatu multimedia fitxategiak
       reject_media_hint: Lokalki gordetako multimedia fitxategiak ezabatzen ditu eta etorkizunean fitxategi berriak deskargatzeari uko egingo dio. Ez du garrantzirik kanporaketetan
       reject_reports: Errefusatu salaketak
@@ -299,6 +332,7 @@ eu:
         title: Desegin %{domain} domeinuko blokeoa
         undo: Desegin
       undo: Desegin domeinuaren blokeoa
+      view: Ikusi domeinuaren blokeoa
     email_domain_blocks:
       add_new: Gehitu berria
       created_msg: Ongi gehitu da e-mail helbidea domeinuen zerrenda beltzera
@@ -322,6 +356,8 @@ eu:
         all: Denak
         limited: Mugatua
         title: Moderazioa
+      private_comment: Iruzkin pribatua
+      public_comment: Iruzkin publikoa
       title: Federazioa
       total_blocked_by_us: Guk blokeatuta
       total_followed_by_them: Haiek jarraitua
@@ -399,6 +435,13 @@ eu:
       custom_css:
         desc_html: Aldatu itxura orri bakoitzean kargatutako CSS bidez
         title: CSS pertsonala
+      domain_blocks:
+        all: Guztiei
+        disabled: Inori ez
+        title: Erakutsi domeinu-blokeoak
+        users: Saioa hasita duten erabiltzaile lokalei
+      domain_blocks_rationale:
+        title: Erakutsi arrazoia
       hero:
         desc_html: Azaleko orrian bistaratua. Gutxienez 600x100px aholkatzen da. Ezartzen ez bada, zerbitzariaren irudia hartuko du
         title: Azaleko irudia
@@ -431,7 +474,7 @@ eu:
           open: Edonork eman dezake izena
         title: Erregistratzeko modua
       show_known_fediverse_at_about_page:
-        desc_html: Txandakatzean, fedibertsu ezagun osoko toot-ak bistaratuko ditu aurrebistan. Bestela, toot lokalak besterik ez ditu erakutsiko.
+        desc_html: Txandakatzean, fedibertso ezagun osoko toot-ak bistaratuko ditu aurrebistan. Bestela, toot lokalak besterik ez ditu erakutsiko.
         title: Erakutsi fedibertsu ezagun osoko denbora-lerroa aurrebistan
       show_staff_badge:
         desc_html: Erakutsi langile banda erabiltzailearen orrian
@@ -449,6 +492,9 @@ eu:
         desc_html: Zure pribatutasun politika, erabilera baldintzak eta bestelako testu legalak idatzi ditzakezu. HTML etiketak erabili ditzakezu
         title: Erabilera baldintza pertsonalizatuak
       site_title: Zerbitzariaren izena
+      spam_check_enabled:
+        desc_html: Mastodonek automatikoki isildu eta salatu ditzake kontuak neurriei jarraituz, esaterako eskatu gabeko mezuak behin eta berriro bidaltzen dituzten kontuak antzemanez. Positibo faltsuak gertatu daitezke.
+        title: Anti-spam
       thumbnail:
         desc_html: Aurrebistetarako erabilia OpenGraph eta API bidez. 1200x630px aholkatzen da
         title: Zerbitzariaren iruditxoa
@@ -456,12 +502,16 @@ eu:
         desc_html: Bistaratu denbora-lerro publikoa hasiera orrian
         title: Denbora-lerroaren aurrebista
       title: Gunearen ezarpenak
+      trends:
+        desc_html: Erakutsi publikoki orain joeran dauden aurretik errebisatutako traolak
+        title: Traolak joeran
     statuses:
       back_to_account: Atzera kontuaren orrira
       batch:
         delete: Ezabatu
         nsfw_off: Markatu ez hunkigarri gisa
         nsfw_on: Markatu hunkigarri gisa
+      deleted: Ezabatuta
       failed_to_execute: Ezin izan da burutu
       media:
         title: Multimedia
@@ -469,21 +519,23 @@ eu:
       no_status_selected: Ez da mezurik aldatu ez delako mezurik aukeratu
       title: Kontuaren mezuak
       with_media: Multimediarekin
-    subscriptions:
-      callback_url: Itzulera URL-a
-      confirmed: Berretsita
-      expires_in: Iraungitzea
-      last_delivery: Azken bidalketa
-      title: WebSub
-      topic: Mintzagaia
     tags:
-      accounts: Kontuak
-      hidden: Ezkutatuta
-      hide: Ezkutatu direktoriotik
+      accounts_today: Erabilera bakanak gaur
+      accounts_week: Erabilera bakanak aste honetan
+      context: Testuingurua
+      directory: Direktorioan
+      in_directory: "%{count} direktorioan"
+      last_active: Azkenekoz aktiboa
+      most_popular: Erabilienak
+      most_recent: Azkenak
       name: Traola
+      review: Berrikusketaren egoera
+      reviewed: Berrikusita
       title: Traolak
-      unhide: Erakutsi direktorioan
-      visible: Ikusgai
+      trending_right_now: Joera orain
+      unique_uses_today: "%{count} idazten gaur"
+      unreviewed: Berrikusi gabe
+      updated_msg: Traola-ezarpenak ongi eguneratu dira
     title: Administrazioa
     warning_presets:
       add_new: Gehitu berria
@@ -499,11 +551,20 @@ eu:
       body: "%{reporter}(e)k %{target} salatu du"
       body_remote: "%{domain} domeinuko norbaitek %{target} salatu du"
       subject: Salaketa berria %{instance} instantzian (#%{id})
+    new_trending_tag:
+      subject: Traola berria errebisatzeko %{instance} instantzian (#%{name})
+  aliases:
+    add_new: Sortu ezizena
+    created_msg: Ongi sortu da ezizena. Orain kontu zaharretik migratzen hasi zaitezke.
+    deleted_msg: Ongi kendu da ezizena. Orain ezin izango da aurreko kontutik hona migratu.
+    hint_html: Beste kontu batetik hona migratu nahi baduzu, hemen ezizen bat sortu dezakezu, hau beharrezkoa da kontu zaharreko jarraitzaileak hona ekartzeko. Ekintza hau berez <strong>kaltegabea eta desegingarria</strong> da. <strong>Kontuaren migrazioa kontu zaharretik abiatzen da</strong>.
+    remove: Deslotu ezizena
   appearance:
     advanced_web_interface: Web interfaze aurreratua
     advanced_web_interface_hint: 'Pantaila bere zabalera osoan erabili nahi baduzu, web interfaze aurreratuak hainbat zutabe desberdin konfiguratzea ahalbidetzen dizu, aldi berean nahi beste informazio ikusteko: Hasiera, jakinarazpenak, federatutako denbora-lerroa, edo nahi beste zerrenda eta traola.'
     animations_and_accessibility: Animazioak eta irisgarritasuna
     confirmation_dialogs: Berrespen dialogoak
+    discovery: Aurkitzea
     sensitive_content: Eduki hunkigarria
   application_mailer:
     notification_preferences: Aldatu e-mail hobespenak
@@ -524,15 +585,19 @@ eu:
     apply_for_account: Eskatu gonbidapen bat
     change_password: Pasahitza
     checkbox_agreement_html: <a href="%{rules_path}" target="_blank">Zerbitzariaren arauak</a> eta <a href="%{terms_path}" target="_blank">erabilera baldintzak</a> onartzen ditut
-    confirm_email: Berretsi e-mail helbidea
+    checkbox_agreement_without_rules_html: <a href="%{terms_path}" target="_blank">Erabilera baldintzak</a> onartzen ditut
     delete_account: Ezabatu kontua
     delete_account_html: Kontua ezabatu nahi baduzu, <a href="%{path}">jarraitu hemen</a>. Berrestea eskatuko zaizu.
+    description:
+      prefix_invited_by_user: "@%{name} erabiltzaileak Mastodon zerbitzari honetara elkartzera gonbidatzen zaitu!"
+      prefix_sign_up: Eman izena Mastodon-en!
+      suffix: Kontu bat baduzu, jendea jarraitu ahal izango duzu, mezuak bidali eta Mastodon zein kanpoko zerbitzarietako erabiltzaileekin elkarrizketan aritu!
     didnt_get_confirmation: Ez dituzu berresteko argibideak jaso?
     forgot_password: Pasahitza ahaztu duzu?
     invalid_reset_password_token: Pasahitza berrezartzeko token-a baliogabea da edo iraungitu du. Eskatu beste bat.
     login: Hasi saioa
     logout: Amaitu saioa
-    migrate_account: Lekualdatu beste kontu batera
+    migrate_account: Migratu beste kontu batera
     migrate_account_html: Kontu hau beste batera birbideratu nahi baduzu, <a href="%{path}">hemen konfiguratu</a> dezakezu.
     or_log_in_with: Edo hasi saioa honekin
     providers:
@@ -544,6 +609,16 @@ eu:
     reset_password: Berrezarri pasahitza
     security: Segurtasuna
     set_new_password: Ezarri pasahitza berria
+    setup:
+      email_below_hint_html: Beheko e-mail helbidea okerra bada, hemen aldatu dezakezu eta baieztapen e-mail berria jaso.
+      email_settings_hint_html: Baieztamen e-maila %{email} helbidera bidali da. E-mail helbide hori zuzena ez bada, kontuaren ezarpenetan aldatu dezakezu.
+      title: Ezarpena
+    status:
+      account_status: Kontuaren egoera
+      confirming: E-mail baieztapena osatu bitartean zain.
+      functional: Zure kontua guztiz erabilgarri dago.
+      pending: Zure eskaera gainbegiratzeko dago oraindik. Honek denbora behar lezake. Zure eskaera onartzen bada e-mail bat jasoko duzu.
+      redirecting_to: Zure kontua ez dago aktibo orain %{acct} kontura birbideratzen duelako.
     trouble_logging_in: Arazoak saioa hasteko?
   authorize_follow:
     already_following: Kontu hau aurretik jarraitzen duzu
@@ -556,6 +631,11 @@ eu:
       return: Erakutsi erabiltzailearen profila
       web: Joan webera
     title: Jarraitu %{acct}
+  challenge:
+    confirm: Jarraitu
+    hint_html: "<strong>Oharra:</strong> Ez dizugu pasahitza berriro eskatuko ordu batez."
+    invalid_password: Pasahitz baliogabea
+    prompt: Berretsi pasahitza jarraitzeko
   datetime:
     distance_in_words:
       about_x_hours: "%{count}o"
@@ -571,26 +651,29 @@ eu:
       x_months: "%{count} hilabete"
       x_seconds: "%{count}s"
   deletes:
-    bad_password_msg: Saiakera ona hacker! Pasahitz okerra
+    challenge_not_passed: Sartu duzun informazioa ez da zuzena
     confirm_password: Sartu zure oraingo pasahitza zure identitatea baieztatzeko
-    description_html: Honek <strong>behin betirako eta atzera egiteko aukera gabe</strong> zure kontuko edukia kendu eta hau desaktibatuko du. Zure erabiltzaile-izena erreserbatuko da etorkizunean inork zure itxurak ez egiteko.
+    confirm_username: Sartu zure erabiltzaile-izena prozedura berresteko
     proceed: Ezabatu kontua
     success_msg: Zure kontua ongi ezabatu da
-    warning_html: Zerbitzari honetako edukiak ezabatzea besterik ezin da bermatu. Asko partekatu den edukiaren arrastoak geratzea izan liteke. Deskonektatuta dauden zerbitzariak edo zure eguneraketetatik harpidetza kendu duten zerbitzariek ez dituzte beraien datu-baseak eguneratuko.
-    warning_title: Sakabanatutako edukiaren eskuragarritasuna
+    warning:
+      before: 'Jarraitu aurretik, irakurri adi ohar hauek:'
+      caches: Beste zerbitzariek cachean duten edukia mantentzea gerta daiteke
+      data_removal: Zure mezuak eta beste datuak behin betiko ezabatuko dira
+      more_details_html: Xehetasun gehiagorako, ikusi <a href="%{terms_path}">pribatutasun politika</a>.
+      username_available: Zure erabiltzaile-izena berriro eskuragarri egongo da
+      username_unavailable: Zure erabiltzaile-izena ez da eskuragarri egongo
   directories:
     directory: Profilen direktorioa
-    enabled: Direktorioan zerrendatuta zaude orain.
-    enabled_but_waiting: Direktorioan zerrendatuta izatea aukeratu duzu, baina ez duzu oraindik gutxieneko jarraitzaile kopurua (%{min_followers}) zerrendan agertzeko.
     explanation: Deskubritu erabiltzaileak interesen arabera
     explore_mastodon: Esploratu %{title}
-    how_to_enable: Ez duzu aukeratu direktorioan zerrendatua izatea aukeratu. Behean aukeratu dezakezu. Erabili traolak zure biografiaren testuan traola zehatzetan agertzeko!
-    people:
-      one: pertsona %{count}
-      other: "%{count} pertsona"
+  domain_validator:
+    invalid_domain: ez da domeinu izen baliogarria
   errors:
+    '400': Bidali duzun eskaria baliogabea da edo gaizki osatua dago.
     '403': Ez duzu orri hau ikusteko baimenik.
     '404': Bilatu duzun orria ez dago hemen.
+    '406': Orri hau ez dago eskatutako formatuan eskuragarri.
     '410': Bilatu duzun orria ez dago hemen jada.
     '422':
       content: Segurtasun egiaztaketak huts egin du. Cookie-ak blokeatzen dituzu?
@@ -599,6 +682,7 @@ eu:
     '500':
       content: Sentitzen dugu, zerbait okerra gertatu da gure aldean.
       title: Orri hau ez da zuzena
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: Mastodon web aplikazioa erabiltzeko, gaitu JavaScript. Bestela, probatu Mastodon plataformarako <a href="%{apps_path}">aplikazio natibo</a>ren bat.
   existing_username_validator:
     not_found: ezin izan da izen hori duen kide lokalik aurkitu
@@ -642,6 +726,7 @@ eu:
     developers: Garatzaileak
     more: Gehiago…
     resources: Baliabideak
+    trending_now: Joera orain
   generic:
     all: Denak
     changes_saved_msg: Aldaketak ongi gorde dira!
@@ -717,9 +802,33 @@ eu:
       too_many: Ezin dira 4 fitxategi baino gehiago erantsi
   migrations:
     acct: Kontu berriaren erabiltzaile@domeinua
-    currently_redirecting: 'Zure profila hona birbideratzeko ezarri da:'
-    proceed: Gorde
-    updated_msg: Kontuaren migrazio-ezarpenak ongi eguneratu dira!
+    cancel: Ezeztatu birbideratzea
+    cancel_explanation: Birbideratzea ezezteak zure kontu hau berriro aktibatuko du, baina ez ditu atzera ekarriko bestera aldatu diren jarraitzaileak.
+    cancelled_msg: Ongi ezeztatu da birbideratzea.
+    errors:
+      already_moved: kontu honetara migratu duzu dagoeneko
+      missing_also_known_as: ez dio kontu honi erreferentzia egiten
+      move_to_self: ezin da oraingo kontua izan
+      not_found: ezin izan da aurkitu
+      on_cooldown: Egonaldian zaude
+    followers_count: Jarraitzaileak migrazioaren unean
+    incoming_migrations: Beste kontu batetik migratuz
+    incoming_migrations_html: Beste kontu batetik hona migratzeko, aurretik <a href="%{path}">kontuaren ezizen bat sortu</a> behar duzu.
+    moved_msg: Zure kontuak orain %{acct} kontura birbideratzen du, eta zure jarraitzaileak hara migratuak izaten ari dira.
+    not_redirecting: Zure kontuak ez du orain beste konturen batera birbideratzen.
+    on_cooldown: Duela gutxi migratu duzu. Funtzio hau %{count} egun barru egongo da berriro eskuragarri.
+    past_migrations: Aurreko migrazioak
+    proceed_with_move: Mugitu jarraitzaileak
+    redirecting_to: 'Zure kontuak hona birbideratzen du: %{acct}.'
+    set_redirect: Ezarri birbideratzea
+    warning:
+      backreference_required: Kontu berria honi erreferentzia egiteko konfiguratu behar da
+      before: 'Jarraitu aurretik, irakurri adi ohar hauek:'
+      cooldown: Migratu eta gero egonaldi tarte bat egongo da eta bitartean ezin izango duzu berriro migratu
+      followers: Ekintza honek jarraitzaile guztiak eramango ditu uneko kontutik kontu berrira
+      only_redirect_html: Bestela, <a href="%{path}">zure profilean birbideratze soil bat jarri</a> dezakezu.
+      other_data: Ez da beste daturik migratuko automatikoki
+      redirect: Zure uneko kontuaren profila eguneratuko da birbideratze ohar batekin eta bilaketetatik kenduko da
   moderation:
     title: Moderazioa
   notification_mailer:
@@ -783,7 +892,7 @@ eu:
       too_few_options: elementu bat baino gehiago izan behar du
       too_many_options: ezin ditu %{max} elementu baino gehiago izan
   preferences:
-    other: Beste bat
+    other: Denetarik
     posting_defaults: Bidalketarako lehenetsitakoak
     public_timelines: Denbora-lerro publikoak
   relationships:
@@ -816,10 +925,6 @@ eu:
     reply:
       proceed: Ekin erantzuteari
       prompt: 'Toot honi erantzun nahi diozu:'
-  remote_unfollow:
-    error: Errorea
-    title: Izenburua
-    unfollowed: Jarraitzeari utzita
   scheduled_statuses:
     over_daily_limit: Egun horretarako programatutako toot kopuruaren muga gainditu duzu (%{limit})
     over_total_limit: Programatutako toot kopuruaren muga gainditu duzu (%{limit})
@@ -868,6 +973,7 @@ eu:
   settings:
     account: Kontua
     account_settings: Kontuaren ezarpenak
+    aliases: Kontuaren ezizenak
     appearance: Itxura
     authorized_apps: Baimendutako aplikazioak
     back: Itzuli Mastodon-era
@@ -908,6 +1014,9 @@ eu:
       private: Ezin dira publikoak ez diren toot-ak finkatu
       reblog: Bultzada bat ezin da finkatu
     poll:
+      total_people:
+        one: pertsona %{count}
+        other: "%{count} pertsona"
       total_votes:
         one: Boto %{count}
         other: "%{count} boto"
@@ -926,6 +1035,8 @@ eu:
     pinned: Finkatutako toot-a
     reblogged: "(r)en bultzada"
     sensitive_content: 'Kontuz: Eduki hunkigarria'
+  tags:
+    does_not_match_previous_name: ez dator aurreko izenarekin bat
   terms:
     body_html: |
       <h2>Pribatutasun politika</h2>
@@ -1044,6 +1155,7 @@ eu:
         silence: Zure kontua murriztua dagoen bitartean, jada zu jarraitzen zaituztenak besterik ez dituzte zure Toot-ak ikusiko zerbitzari honetan, eta agian zerrenda publikoetatik kenduko zaizu. Hala ere besteek oraindik zu jarraitu zaitzakete.
         suspend: Zure kontua kanporatua izan da, zure toot guztiak eta multimedia fitxategiak behin betiko ezabatu dira zerbitzari honetatik, eta zure jarraitzaileen zerbitzarietatik.
       review_server_policies: Berrikusi zerbitzariko politikak
+      statuses: 'Zehazki, honentzat:'
       subject:
         disable: Zure  %{acct} kontua izoztu da
         none: "%{acct} konturako abisua"
diff --git a/config/locales/fa.yml b/config/locales/fa.yml
index d37dbdeb49ecf34693635cac02e2f24e716d12d6..ebac705d804820c33b9f4e8312b92673d3d6458e 100644
--- a/config/locales/fa.yml
+++ b/config/locales/fa.yml
@@ -2,7 +2,7 @@
 fa:
   about:
     about_hashtag_html: این‌ها نوشته‌های عمومی هستند که برچسب (هشتگ) <strong>#%{hashtag}</strong> را دارند. اگر شما روی هر سروری حساب داشته باشید می‌توانید به این نوشته‌ها واکنش نشان دهید.
-    about_mastodon_html: ماستدون (Mastodon) یک شبکهٔ اجتماعی است که بر اساس پروتکل‌های آزاد وب و نرم‌افزارهای آزاد و کدباز ساخته شده است. این شبکه مانند ایمیل غیرمتمرکز است.
+    about_mastodon_html: 'شبکهٔ اجتماعی آینده: بدون تبلیغات، بدون شنود از طرف شرکت‌ها، طراحی اخلاق‌مدار، و معماری غیرمتمرکز! با ماستدون صاحب داده‌های خودتان باشید!'
     about_this: درباره
     active_count_after: فعال
     active_footnote: کاربران فعال در ماه گذشته
@@ -10,20 +10,19 @@ fa:
     api: رابط برنامه‌نویسی کاربردی
     apps: اپ‌های موبایل
     apps_platforms: ماستدون را در iOS، اندروید، و سایر سیستم‌ها داشته باشید
-    browse_directory: در فهرست گزیدهٔ کاربران این سرور چرخی بزنید و کاربران را بر اساس علاقه‌مندی‌هایشان پیدا کنید
+    browse_directory: کاربران این سرور را بر اساس علاقه‌مندی‌هایشان پیدا کنید
     browse_public_posts: فهرست لحظه‌ای نوشته‌های عمومی در ماستدون را ببینید
     contact: تماس
     contact_missing: تعیین نشده
     contact_unavailable: موجود نیست
     discover_users: یافتن کاربران
     documentation: مستندات
-    extended_description_html: |
-      <h3>جای خوبی برای قانون‌ها</h3>
-      <p>توضیحات تکمیلی نوشته نشده است.</p>
     federation_hint_html: با داشتن حساب روی %{instance} می‌توانید کاربران همهٔ سرورهای دیگر ماستدون (و سایر شبکه‌های سازگار با آن) را پی بگیرید.
-    generic_description: "%{domain} یک سرور روی شبکه است"
     get_apps: یک اپ موبایل را امتحان کنید
     hosted_on: ماستدون، میزبانی‌شده روی %{domain}
+    instance_actor_flash: |
+      این حساب یک بازیگر مجازی برای نمایندگی از این سرور است و متعلق به هیچ کاربری نیست.
+      این حساب برای ارتباط میان‌سروری به کار می‌رود و نباید مسدود شود، مگر این که شما بخواهید کل سرور را مسدود کنید، که در آن صورت باید از راه مسدودسازی دامین پیش بروید.
     learn_more: بیشتر بدانید
     privacy_policy: سیاست رازداری
     see_whats_happening: ببینید چه خبر است
@@ -35,6 +34,14 @@ fa:
     status_count_before: که در کنار هم
     tagline: با دوستان خود در ارتباط باشید و دوستان تازه پیدا کنید
     terms: شرایط کاربری
+    unavailable_content: محتوای ناموجود
+    unavailable_content_description:
+      domain: سرور
+      reason: 'دلیل:'
+      rejecting_media: تصاویر فرستاده شده از سمت این سرور پردازش نخواهد شد و هیچ تصویر کوچکی از آن‌ها در این‌جا نمایش نخواهد یافت، و آن‌ها را باید مستقیماً در آن سرور ببینید.
+      silenced: هیچ کدام از نوشته‌ها از طرف این سرور این‌جا نمایش نخواهند یافت مگر در فهرست پیگیری‌ها شما، اگر نویسنده‌اش را پی بگیرید.
+      suspended: شما نمی‌توانید هیچ کدام از کاربرهای این سرور را پی بگیرید، و هیچ داده‌ای از طرف این سرور پردازش یا ذخیره یا مبادله نخواهد شد.
+    unavailable_content_html: ماستدون در حالت کلی اجازه می‌دهد که شما همهٔ مطالب و کاربران در سرورهای دیگر را نیز ببینید و با آن‌ها برهم‌کنش داشته باشید. فهرست زیر ولی استثناهای این ارتباط است که به طور خاص روی این سرور اعمال شده‌اند.
     user_count_after:
       one: کاربر
       other: کاربر
@@ -42,6 +49,8 @@ fa:
     what_is_mastodon: ماستدون چیست؟
   accounts:
     choices_html: 'انتخاب‌های %{name}:'
+    endorsements_hint: شما می‌توانید از محیط وب ماستدون، کسانی را که پی می‌گیرید به دیگران هم پیشنهاد دهید تا این‌جا نشان داده شوند.
+    featured_tags_hint: شما می‌توانید برچسب‌های دلخواه خود را پیشنهاد دهید که این‌جا نمایش خواهند یافت.
     follow: پی بگیرید
     followers:
       one: پیگیر
@@ -53,6 +62,7 @@ fa:
     media: عکس و ویدیو
     moved_html: "%{name} حساب خود را به %{new_profile_link} منتقل کرده است:"
     network_hidden: این اطلاعات در دسترس نیست
+    never_active: هرگز
     nothing_here: این‌جا چیزی نیست!
     people_followed_by: کسانی که %{name} پی می‌گیرد
     people_who_follow: کسانی که %{name} را پی می‌گیرند
@@ -183,6 +193,7 @@ fa:
       username: نام کاربری
       warn: هشدار
       web: وب
+      whitelisted: فهرست مجاز
     action_logs:
       actions:
         assigned_to_self_report: "%{name} رسیدگی به گزارش %{target} را به عهده گرفت"
@@ -218,19 +229,24 @@ fa:
       deleted_status: "(بوق پاک‌شده)"
       title: سیاههٔ بازرسی
     custom_emojis:
+      assign_category: تعیین دسته
       by_domain: دامین
       copied_msg: نسخهٔ محلی شکلک با موفقیت ساخته شد
       copy: نسخه‌برداری
       copy_failed_msg: نشد که نسخهٔ محلی این شکلک ساخته شود
+      create_new_category: ساختن دستهٔ تازه
       created_msg: این شکلک با موفقیت ساخته شد!
       delete: پاک کردن
       destroyed_msg: این شکلک با موفقیت پاک شد!
       disable: غیرفعال‌سازی
+      disabled: غیرفعال
       disabled_msg: این شکلک با موفقیت غیرفعال شد
       emoji: Ø´Ú©Ù„Ú©
       enable: فعال‌سازی
+      enabled: فعال
       enabled_msg: این شکلک با موفقیت فعال شد
       image_hint: پروندهٔ PNG حداکثر 50KB
+      list: فهرست
       listed: فهرست‌شده
       new:
         title: افزودن شکلک سفارشی
@@ -238,11 +254,14 @@ fa:
       shortcode: کد کوتاه
       shortcode_hint: دست‌کم ۲ نویسه و تنها شامل حروف، اعداد و زیرخط
       title: شکلک‌های سفارشی
+      uncategorized: دسته‌بندی نشده
+      unlist: نافهرست
       unlisted: فهرست‌نشده
       update_failed_msg: این شکلک نتوانست به‌روز شود
       updated_msg: شکلک با موفقیت به‌روز شد!
       upload: بارگذاری
     dashboard:
+      authorized_fetch_mode: حالت دریافت مجازشده
       backlog: کارهای باقیمانده
       config: پیکربندی
       feature_deletions: حساب‌های حذف‌شده
@@ -250,10 +269,13 @@ fa:
       feature_profile_directory: فهرست گزیدهٔ کاربران
       feature_registrations: ثبت‌نام‌ها
       feature_relay: رله
+      feature_spam_check: ضدهرزنامه
       feature_timeline_preview: پیش‌نمایش نوشته‌ها
       features: ویژگی‌ها
       hidden_service: ارتباط میان‌سروری با سرویس‌های نهفته
       open_reports: گزارش‌های فعال
+      pending_tags: برچسب منتظر بازبینی
+      pending_users: کاربران منتظر بازبینی
       recent_users: کاربران تازه
       search: جستجوی متنی
       single_user_mode: حالت تک‌کاربره
@@ -261,15 +283,22 @@ fa:
       space: فضای مصرف‌شده
       title: ابزارهای مدیریت
       total_users: شمار کاربران
-      trends: هشتگ‌های پرکاربرد
+      trends: برچسب‌های پرکاربرد
       week_interactions: فعالیت‌ها در این هفته
       week_users_active: کاربران فعال هفتهٔ اخیر
       week_users_new: کاربران هفتهٔ اخیر
+      whitelist_mode: حالت فهرست مجاز
+    domain_allows:
+      add_new: دامین مجازشده
+      created_msg: این دامین با موفقیت مجاز شد
+      destroyed_msg: دامین از حالت مجاز خارج شد
+      undo: برداشتن از فهرست مجازها
     domain_blocks:
       add_new: افزودن مسدودسازی دامین تازه
       created_msg: مسدودکردن دامین در حال انجام است
       destroyed_msg: مسدودکردن دامین واگردانده شد
       domain: دامین
+      edit: ویرایش مسدودسازی دامین
       existing_domain_block_html: شما پیش‌تر محدودیت‌های سخت‌تری روی %{name} اعمال کرده‌اید، و باید نخست <a href="%{unblock_url}">مسدودسازی را لغو کنید</a>.
       new:
         create: مسدودسازی
@@ -280,6 +309,10 @@ fa:
           silence: بی‌صداکردن
           suspend: معلق‌کردن
         title: مسدودسازی دامین دیگر
+      private_comment: یادداشت خصوصی
+      private_comment_hint: یادداشتی دربارهٔ محدودیت روی این دامین برای سایر ناظمان.
+      public_comment: یادداشت عمومی
+      public_comment_hint: یادداشتی دربارهٔ محدودیت روی این دامین برای عموم، در صورتی که فهرست دامین‌های محدود شده منتشر شود.
       reject_media: نپذیرفتن پرونده‌های تصویری
       reject_media_hint: تصویرهای ذخیره‌شده در این‌جا را پاک می‌کند و جلوی دریافت تصویرها را در آینده می‌گیرد. بی‌تأثیر برای معلق‌شده‌ها
       reject_reports: نپذیرفتن گزارش‌ها
@@ -299,6 +332,7 @@ fa:
         title: واگردانی مسدودسازی دامنه برای %{domain}
         undo: واگردانی
       undo: واگردانی مسدودسازی دامین
+      view: دیدن مسدودسازی دامنه
     email_domain_blocks:
       add_new: افزودن تازه
       created_msg: مسدودسازی دامین ایمیل با موفقیت ساخته شد
@@ -322,6 +356,8 @@ fa:
         all: همه
         limited: محدود
         title: مدیریت
+      private_comment: یادداشت خصوصی
+      public_comment: یادداشت عمومی
       title: ارتباط میان‌سروری
       total_blocked_by_us: مسدودشده از طرف ما
       total_followed_by_them: ما را پی می‌گیرند
@@ -351,6 +387,7 @@ fa:
       pending: در انتظار پذیرش رله
       save_and_enable: ذخیره و فعال‌سازی
       setup: پیوستن به رله‌ها
+      signatures_not_enabled: وقتی حالت امن یا حالت فهرست سفید فعال باشد رله‌ها به درستی کار نخواهند کرد
       status: وضعیت
       title: رله‌ها
     report_notes:
@@ -399,6 +436,16 @@ fa:
       custom_css:
         desc_html: ظاهر ماستدون را با CSS-ای که در همهٔ صفحه‌ها جاسازی می‌شود تغییر دهید
         title: سبک CSS سفارشی
+      default_noindex:
+        desc_html: روی همهٔ کاربرانی که این تنظیم را خودشان تغییر نداده‌اند تأثیر می‌گذارد
+        title: درخواست پیش‌فرض از طرف کاربران برای ظاهر نشدن در نتایج موتورهای جستجوگر
+      domain_blocks:
+        all: برای همه
+        disabled: برای هیچ‌کدام
+        title: نمایش دامین‌های مسدودشده
+        users: برای کاربران محلی واردشده
+      domain_blocks_rationale:
+        title: دیدن دلیل
       hero:
         desc_html: در صفحهٔ آغازین نمایش می‌یابد. دست‌کم ۶۰۰×۱۰۰ پیکسل توصیه می‌شود. اگر تعیین نشود، با تصویر بندانگشتی سرور جایگزین خواهد شد
         title: تصویر سربرگ
@@ -449,6 +496,9 @@ fa:
         desc_html: می‌توانید سیاست رازداری، شرایط استفاده، یا سایر مسائل قانونی را به دلخواه خود بنویسید. تگ‌های HTML هم مجاز است
         title: شرایط استفادهٔ سفارشی
       site_title: نام سرور
+      spam_check_enabled:
+        desc_html: ماستدون می‌تواند حساب‌ها را به طور خودکار بی‌صدا کند یا گزارش دهد. این کار بر اساس سنجه‌هایی از قبیل شناسایی پیغام‌های ناخواستهٔ تکراری انجام می‌شود و ممکن است گاهی اشتباه باشد.
+        title: ضدهرزنامه
       thumbnail:
         desc_html: برای دیدن با OpenGraph و رابط برنامه‌نویسی. وضوح پیشنهادی ۱۲۰۰×۶۳۰ پیکسل
         title: تصویر کوچک سرور
@@ -456,12 +506,19 @@ fa:
         desc_html: نوشته‌های عمومی این سرور را در صفحهٔ آغازین نشان دهید
         title: پیش‌نمایش نوشته‌ها
       title: تنظیمات سایت
+      trendable_by_default:
+        desc_html: روی برچسب‌هایی که پیش از این ممنوع نشده‌اند تأثیر می‌گذارد
+        title: بگذارید که برچسب‌های پرطرفدار بدون بازبینی قبلی نمایش داده شوند
+      trends:
+        desc_html: برچسب‌های عمومی که پیش‌تر بازبینی شده‌اند و هم‌اینک پرطرفدارند
+        title: برچسب‌های پرطرفدار
     statuses:
       back_to_account: بازگشت به صفحهٔ حساب
       batch:
         delete: پاک‌کردن
         nsfw_off: علامت‌زدن به عنوان غیرحساس
         nsfw_on: علامت‌زدن به عنوان حساس
+      deleted: پاک‌شده
       failed_to_execute: اجرا نشد
       media:
         title: رسانه
@@ -469,21 +526,24 @@ fa:
       no_status_selected: هیچ بوقی تغییری نکرد زیرا هیچ‌کدام از آن‌ها انتخاب نشده بودند
       title: نوشته‌های حساب
       with_media: دارای عکس یا ویدیو
-    subscriptions:
-      callback_url: نشانی Callback
-      confirmed: تأییدشده
-      expires_in: مهلت انقضا
-      last_delivery: آخرین ارسال
-      title: WebSub
-      topic: موضوع
     tags:
-      accounts: حساب‌ها
-      hidden: پنهان‌شده
-      hide: در فهرست گزیدهٔ کاربران نشان نده
+      accounts_today: کاربرد یکتا در امروز
+      accounts_week: کاربرد یکتا در این هفته
+      breakdown: کاربردهای امروز به تفکیک منبع
+      context: زمینه
+      directory: در فهرست
+      in_directory: "%{count} در فهرست"
+      last_active: آخرین فعالیت
+      most_popular: محبوب‌ترین
+      most_recent: تازه‌ترین
       name: برچسب
+      review: وضعیت بازبینی
+      reviewed: بازبینی شده
       title: برچسب‌ها
-      unhide: نمایش در فهرست گزیدهٔ کاربران
-      visible: نمایان
+      trending_right_now: پرطرفدارهای کنونی
+      unique_uses_today: "%{count} امروز منتشر شده"
+      unreviewed: بازبینی نشده
+      updated_msg: تنظیمات برچسب‌ها با موفقیت به‌روز شد
     title: مدیریت سرور
     warning_presets:
       add_new: افزودن تازه
@@ -499,11 +559,21 @@ fa:
       body: کاربر %{reporter} کاربر %{target} را گزارش داد
       body_remote: کسی از %{domain} گزارش %{target} را فرستاده
       subject: گزارش تازه‌ای برای %{instance} (#%{id})
+    new_trending_tag:
+      body: 'برچسب #%{name} امروز پرطرفدار است، ولی تا حالا بازبینی نشده. تا وقتی که شما اجازه نداده‌اید، این برچسب به طور عمومی نمایش داده نخواهد شد. اگر فرم را به شکل فعلی ذخیره کنید، هیچ وقت چیزی دربارهٔ این برچسب نخواهید دید.'
+      subject: برچسب تازه‌ای در %{instance} نیازمند بررسی است (#%{name})
+  aliases:
+    add_new: ساختن نام مستعار
+    created_msg: نام مستعار تازه با موفقیت ساخته شد. الان می‌توانید انتقال از حساب قدیمی را آغاز کنید.
+    deleted_msg: نام مستعار با موفقیت حذف شد. انتقال از آن حساب به حساب فعلی دیگر ممکن نیست.
+    hint_html: اگر می‌خواهید از حساب دیگری به این حساب منتقل شوید، این‌جا می‌توانید یک نام مستعار بسازید که برای انتقال از حساب قدیمی به این حساب لازم است. این کار به تنهایی <strong>بی‌ضرر و قابل بازگشت</strong> است. <strong>فرایند انتقال حساب از حساب قدیمی آغاز خواهد شد</strong>.
+    remove: حذف ارتباط نام مستعار
   appearance:
     advanced_web_interface: رابط کاربری پیشرفته
     advanced_web_interface_hint: 'اگر می‌خواهید همهٔ فضای نمایشگر خود را به کار ببرید، می‌توانید به کمک رابط کاربری پیشرفته ستون‌های گوناگونی داشته باشید تا در یک نگاه همهٔ اطلاعاتی را که می‌خواهید ببینید: نوشته‌های دیگران، اعلان‌ها، فهرست نوشته‌های همه‌جا، و هر تعداد فهرست و برچسب که بخواهید.'
     animations_and_accessibility: پویانمایی‌های و دسترسی‌پذیری
     confirmation_dialogs: پیغام‌های تأیید
+    discovery: کاوش
     sensitive_content: محتوای حساس
   application_mailer:
     notification_preferences: تغییر ترجیحات ایمیل
@@ -524,9 +594,13 @@ fa:
     apply_for_account: درخواست دعوت‌نامه
     change_password: رمز
     checkbox_agreement_html: من <a href="%{rules_path}" target="_blank">قانون‌های این سرور</a> و <a href="%{terms_path}" target="_blank">شرایط کاربری</a> را می‌پذیرم
-    confirm_email: تأیید ایمیل
+    checkbox_agreement_without_rules_html: من با <a href="%{terms_path}" target="_blank">شرایط استفاده</a> موافقم
     delete_account: پاک‌کردن حساب
     delete_account_html: اگر می‌خواهید حساب خود را پاک کنید، از <a href="%{path}">این‌جا</a> پیش بروید. از شما درخواست تأیید خواهد شد.
+    description:
+      prefix_invited_by_user: "@%{name} شما را به عضویت در این سرور ماستدون دعوت کرده است!"
+      prefix_sign_up: همین امروز عضو ماستدون شوید!
+      suffix: با داشتن حساب می‌توانید دیگران را پی بگیرید، نوشته‌های تازه منتشر کنید، و با کاربران دیگر از هر سرور ماستدون دیگری و حتی سرورهای دیگر در ارتباط باشید!
     didnt_get_confirmation: راهنمایی برای تأیید را دریافت نکردید؟
     forgot_password: رمزتان را گم کرده‌اید؟
     invalid_reset_password_token: کد بازنشانی رمز نامعتبر یا منقضی شده است. لطفاً کد دیگری درخواست کنید.
@@ -544,6 +618,16 @@ fa:
     reset_password: بازنشانی رمز
     security: امنیت
     set_new_password: تعیین رمز تازه
+    setup:
+      email_below_hint_html: اگر نشانی ایمیل زیر نادرست است، می‌توانید آن را تغییر دهید و ایمیل تأیید دوباره‌ای دریافت کنید.
+      email_settings_hint_html: ایمیل تأیید به %{email} فرستاده شد. اگر این نشانی ایمیل درست نیست، می‌توانید از تنظیمات حساب آن را تغییر دهید.
+      title: راه اندازی
+    status:
+      account_status: وضعیت حساب
+      confirming: در حال انتظار برای کامل شدن تأیید ایمیل.
+      functional: حساب شما قابل استفاده است.
+      pending: درخواست شما منتظر تأیید مسئولان سایت است و این فرایند ممکن است کمی طول بکشد. اگر درخواست شما پذیرفته شود به شما ایمیلی فرستاده خواهد شد.
+      redirecting_to: حساب شما غیرفعال است زیرا هم‌اکنون به %{acct} منتقل شده است.
     trouble_logging_in: برای ورود مشکلی دارید؟
   authorize_follow:
     already_following: شما همین الان هم این حساب را پی‌می‌گیرید
@@ -556,6 +640,11 @@ fa:
       return: نمایهٔ این کاربر را نشان بده
       web: رفتن به وب
     title: پیگیری %{acct}
+  challenge:
+    confirm: ادامه
+    hint_html: "<strong>نکته:</strong> ما در یک ساعت آینده رمزتان را از شما نخواهیم پرسید."
+    invalid_password: رمز نامعتبر
+    prompt: برای ادامه رمزتان را تأیید کنید
   datetime:
     distance_in_words:
       about_x_hours: "%{count} ساعت"
@@ -571,26 +660,33 @@ fa:
       x_months: "%{count} ماه"
       x_seconds: "%{count} ثانیه"
   deletes:
-    bad_password_msg: هکر گرامی، رمزی که وارد کردید اشتباه است ؛)
+    challenge_not_passed: اطلاعاتی که وارد کردید اشتباه بود
     confirm_password: رمز فعلی خود را وارد کنید تا معلوم شود که خود شمایید
-    description_html: این کار همهٔ محتوای حساب شما را <strong>برای همیشه و به‌طور بازگشت‌ناپذیری</strong> پاک کرده و حساب را غیرفعال می‌کند. نام کاربری شما برای جلوگیری از جعل هویت احتمالی در آینده از دسترس خارج خواهد شد.
+    confirm_username: برای تأیید این فرایند نام کاربری خود را وارد کنید
     proceed: پاک‌کردن حساب
     success_msg: حساب شما با موفقیت پاک شد
-    warning_html: تنها پاک‌شدن محتوای حساب در این سرور خاص تضمین می‌شود. محتوایی که به گستردگی هم‌رسانی شده باشد ممکن است ردش همچنان باقی بماند. سرورهای آفلاین یا سرورهایی که دیگر مشترک شما نیستند پایگاه‌های دادهٔ خود را به‌روز نخواهند کرد.
-    warning_title: دسترس‌پذیری محتوای هم‌رسان‌شده
+    warning:
+      before: 'پیش از ادامه،‌ لطفاً نکته‌های زیر را به دقت بخوانید:'
+      caches: محتواهایی که سرورهای دیگر ذخیره کرده‌اند شاید همچنان باقی بمانند
+      data_removal: نوشته‌ها و داده‌های شما برای همیشه پاک خواهند شد
+      email_change_html: شما می‌توانید بدون پاک کردن حساب <a href="%{path}">نشانی ایمیل خود را تغییر دهید</a>
+      email_contact_html: اگر ایمیل همچنان نرسیده، برای درخواست کمک به <a href="mailto:%{email}">%{email}</a> پیغام دهید
+      email_reconfirmation_html: اگر ایمیل تأیید به دستتان نرسیده، می‌توانید <a href="%{path}">یک بار دیگر برایش درخواست بدهید</a>
+      irreversible: شما نخواهید توانست حساب خود را بازیابی یا فعال‌سازی کنید
+      more_details_html: برای اطلاعات بیشتر <a href="%{terms_path}">سیاست رازداری</a> را ببینید.
+      username_available: نام کاربری شما دوباره در دسترس خواهد بود
+      username_unavailable: نام کاربری شما برای دیگران غیرقابل دسترس خواهد ماند
   directories:
     directory: فهرست گزیدهٔ کاربران
-    enabled: شما هم‌اینک در فهرست گزیدهٔ کاربران نمایش می‌یابید.
-    enabled_but_waiting: شما می‌خواهید در فهرست گزیدهٔ کاربران این سرور باشید، ولی تعداد پیگیران شما هنوز به مقدار لازم (%{min_followers}) نرسیده است.
     explanation: کاربران این سرور را بر اساس علاقه‌مندی‌هایشان پیدا کنید
     explore_mastodon: گشت و گذار در %{title}
-    how_to_enable: شما هنوز در فهرست گزیدهٔ کاربران این سرور نشان داده نمی‌شوید. این‌جا می‌توانید انتخابش کنید. اگر در بخش معرفی خود در نمایه‌تان برچسب (هشتگ) داشته باشد، نام شما هم برای آن هشتگ‌ها فهرست می‌شود!
-    people:
-      one: "%{count} نفر"
-      other: "%{count} نفر"
+  domain_validator:
+    invalid_domain: نام دامین معتبر نیست
   errors:
+    '400': درخواستی که فرستادید نامعتبر یا اشتباه بود.
     '403': شما اجازهٔ دیدن این صفحه را ندارید.
     '404': صفحه‌ای که به دنبالش هستید این‌جا نیست.
+    '406': این صفحه در قالبی که درخواست کرده‌اید موجود نیست.
     '410': صفحه‌ای که به دنبالش بودید دیگر این‌جا وجود ندارد.
     '422':
       content: تأیید امنیتی انجام نشد. آیا مرورگر شما کوکی‌ها را مسدود می‌کند؟
@@ -599,6 +695,7 @@ fa:
     '500':
       content: شرمنده، یک چیزی از سمت ما اشتباه شده.
       title: این صفحه درست نیست
+    '503': این صفحه به خاطر مشکل موقت سرور در دسترس نیست.
     noscript_html: برای استفاده از نسخهٔ تحت وب ماستدون، لطفاً جاوااسکریپت را فعال کنید. یا به جایش می‌توانید <a href="%{apps_path}">یک اپ ماستدون</a> را به‌کار ببرید.
   existing_username_validator:
     not_found: کاربری در این سرور با این نام کاربری پیدا نشد
@@ -622,6 +719,7 @@ fa:
     add_new: افزودن تازه
     errors:
       limit: شما بیشترین تعداد مجاز برچسب‌ها را دارید
+    hint_html: "<strong>برچسب‌های برگزیده چیستند؟</strong> این برچسب‌ها (هشتگ‌ها) به طور واضحی روی نمایهٔ عمومی شما نمایش می‌یابند و دیگران می‌توانند نوشته‌های شما را تحت هر کدام از این برچسب‌ها مرور کنند. این یک روش بسیار خوب برای دسته‌بندی آثار خلاقانه یا پروژه‌های بلندمدت شماست."
   filters:
     contexts:
       home: خانه
@@ -642,10 +740,12 @@ fa:
     developers: برنامه‌نویسان
     more: بیشتر…
     resources: منابع
+    trending_now: پرطرفدار
   generic:
     all: همه
     changes_saved_msg: تغییرات با موفقیت ذخیره شدند!
     copy: رونوشت
+    no_batch_actions_available: هیچ کار گروهی‌ای در این صفحه موجود نیست
     order_by: مرتب‌سازی
     save_changes: ذخیرهٔ تغییرات
     validation_errors:
@@ -717,9 +817,34 @@ fa:
       too_many: نمی‌توان بیشتر از ۴ تصویر بارگذاری کرد
   migrations:
     acct: username@domain حساب تازه
-    currently_redirecting: 'نمایهٔ شما منتقل می‌شود به:'
-    proceed: ذخیره
-    updated_msg: تنظیمات نقل مکان حساب شما با موفقیت به‌روز شد!
+    cancel: لغو انتقال
+    cancel_explanation: با لغو انتقال، حساب شما دوباره فعال می‌شود، ولی این کار پیگیران شما را که به حساب دیگر منتقل شده‌اند برنمی‌گرداند.
+    cancelled_msg: انتقال حساب با موفقیت لغو شد.
+    errors:
+      already_moved: این همان حسابی است که به آن منتقل شده‌اید
+      missing_also_known_as: به حساب شما اشاره نمی‌کند
+      move_to_self: نمی‌تواند حساب فعلی شما باشد
+      not_found: چنین حسابی پیدا نشد
+      on_cooldown: شما باید صبر کنید
+    followers_count: شمار پیگیران در زمان انتقال
+    incoming_migrations: انتقال از یک حساب دیگر
+    incoming_migrations_html: برای انتقال از یک حساب دیگر به این حساب، شما باید نخست <a href="%{path}">یک نام مستعار بسازید</a>.
+    moved_msg: حساب شما هم‌اینک به %{acct} منتقل شده است و پیگیران شما در حال انتقال به آن‌جا هستند.
+    not_redirecting: حساب شما هم‌اینک به هیچ حساب دیگری منتقل نشده است یا اشاره نمی‌کند.
+    on_cooldown: شما به تازگی حساب خود را منتقل کرده‌اید. این ویژگی برای شما دوباره در %{count} روز فعال خواهد شد.
+    past_migrations: انتقال‌های پیشین
+    proceed_with_move: انتقال پیگیران
+    redirecting_to: حساب شما هم‌اینک به %{acct} منتقل شده است.
+    set_redirect: اشاره به حساب دیگر
+    warning:
+      backreference_required: حساب تازهٔ شما نخست باید تنظیم شود تا به این حساب اشاره کند
+      before: 'پیش از ادامه،‌ لطفاً نکته‌های زیر را به دقت بخوانید:'
+      cooldown: پس از انتقال حساب، یک دورهٔ انتظار وجود دارد که در این مدت نخواهید توانست دوباره حسابتان را منتقل کنید
+      disabled_account: حساب فعلی شما پس از این کار دیگر قابل استفاده نخواهد بود. شما فقط خواهید توانست داده‌های خود را بیرون ببرید یا حساب را دوباره فعال کنید.
+      followers: این کار همهٔ پیگیران شما را از حساب فعلی به حساب تازه منتقل خواهد کرد
+      only_redirect_html: شما همچنین می‌توانید حساب خود را <a href="%{path}">به یک حساب دیگر اشاره دهید</a>.
+      other_data: هیچ دادهٔ دیگری خودبه‌خود منتقل نخواهد شد
+      redirect: نمایهٔ حساب فعلی شما به حساب تازه اشاره خواهد کرد و خودش در نتیجهٔ جستجوها ظاهر نخواهد شد
   moderation:
     title: مدیریت کاربران
   notification_mailer:
@@ -816,10 +941,6 @@ fa:
     reply:
       proceed: به سمت پاسخ‌دادن
       prompt: 'شما می‌خواهید به این بوق پاسخ دهید:'
-  remote_unfollow:
-    error: خطا
-    title: عنوان
-    unfollowed: پایان پیگیری
   scheduled_statuses:
     over_daily_limit: شما از حد مجاز %{limit} بوق زمان‌بندی‌شده در آن روز فراتر رفته‌اید
     over_total_limit: شما از حد مجاز %{limit} بوق زمان‌بندی‌شده فراتر رفته‌اید
@@ -868,6 +989,7 @@ fa:
   settings:
     account: حساب
     account_settings: تنظیمات حساب
+    aliases: نام‌های مستعار
     appearance: نما
     authorized_apps: برنامه‌های مجاز
     back: بازگشت به ماستدون
@@ -885,6 +1007,8 @@ fa:
     profile: نمایه
     relationships: پیگیری‌ها و پیگیران
     two_factor_authentication: ورود دومرحله‌ای
+  spam_check:
+    spam_detected: این یک گزارش خودکار برای تشخیص هرزنامه است.
   statuses:
     attached:
       description: 'پیوست‌شده: %{attached}'
@@ -908,6 +1032,9 @@ fa:
       private: نوشته‌های غیرعمومی را نمی‌توان ثابت کرد
       reblog: بازبوق‌ها را نمی‌توان ثابت کرد
     poll:
+      total_people:
+        one: "%{count} نفر"
+        other: "%{count} نفر"
       total_votes:
         one: "%{count} رأی"
         other: "%{count} رأی"
@@ -926,6 +1053,8 @@ fa:
     pinned: نوشته‌های ثابت
     reblogged: بازبوقید
     sensitive_content: محتوای حساس
+  tags:
+    does_not_match_previous_name: با نام پیشین مطابق نیست
   terms:
     body_html: |
       <h2>سیاست رازداری</h2>
@@ -1043,7 +1172,9 @@ fa:
         disable: تا وقتی حساب شما متوقف باشد، داده‌های شما دست‌نخورده باقی می‌مانند، ولی تا وقتی که حسابتان باز نشده، نمی‌توانید هیچ کاری با آن بکنید.
         silence: تا وقتی حساب شما محدود باشد، تنها کسانی که از قبل پیگیر شما بودند نوشته‌های شما در این سرور را می‌بینند و شما در فهرست‌های عمومی دیده نمی‌شوید. ولی دیگران همچنان می‌توانند به دلخواه خودشان پیگیر شما شوند.
         suspend: حساب شما معلق شده است، و همهٔ نوشته‌ها و رسانه‌های تصویری شما به طور بازگشت‌ناپذیری پاک شده‌اند؛ چه از این سرور و چه از سرورهای دیگری که از آن‌ها پیگیر داشتید.
+      get_in_touch: با پاسخ به این ایمیل می‌توانید با دست‌اندرکاران %{instance} در تماس باشید.
       review_server_policies: مرور سیاست‌های این سرور
+      statuses: 'به طور خاص برای:'
       subject:
         disable: حساب %{acct} شما متوقف شده است
         none: هشدار برای %{acct}
@@ -1059,7 +1190,7 @@ fa:
       edit_profile_step: 'شما می‌توانید نمایهٔ خود را به دلخواه خود تغییر دهید: می‌توانید تصویر نمایه، تصویر پس‌زمینه، نام، و چیزهای دیگری را تعیین کنید. اگر بخواهید، می‌توانید حساب خود را خصوصی کنید تا فقط کسانی که شما اجازه می‌دهید بتوانند پیگیر حساب شما شوند.'
       explanation: نکته‌هایی که برای آغاز کار به شما کمک می‌کنند
       final_action: چیزی منتشر کنید
-      final_step: 'چیزی بنویسید! حتی اگر الان کسی پیگیر شما نباشد، دیگران نوشته‌های عمومی شما را می‌بینند، مثلاً در فهرست نوشته‌های محلی و در هشتگ‌ها. شاید بخواهید با هشتگ #آشنایی خودتان را معرفی کنید.'
+      final_step: 'چیزی بنویسید! حتی اگر الان کسی پیگیر شما نباشد، دیگران نوشته‌های عمومی شما را می‌بینند، مثلاً در فهرست نوشته‌های محلی و در برچسب (هشتگ)ها. شاید بخواهید با برچسب #معرفی خودتان را معرفی کنید.'
       full_handle: نام کاربری کامل شما
       full_handle_hint: این چیزی است که باید به دوستان خود بگویید تا بتوانند به شما پیغام بفرستند یا از سرورهای دیگر پیگیر شما شوند.
       review_preferences_action: تغییر ترجیحات
diff --git a/config/locales/fi.yml b/config/locales/fi.yml
index e0dc0f756e5912420bf5243aa4c761aaa13d27b2..500c2408f29a2df2c9ac34b4c0ec502cc80b5186 100644
--- a/config/locales/fi.yml
+++ b/config/locales/fi.yml
@@ -10,10 +10,6 @@ fi:
     contact_missing: Ei asetettu
     contact_unavailable: Ei saatavilla
     documentation: Dokumentaatio
-    extended_description_html: |
-      <h3>Hyvä paikka säännöille</h3>
-      <p>Pidempää kuvausta ei ole vielä laadittu.</p>
-    generic_description: "%{domain} on yksi verkostoon kuuluvista palvelimista"
     hosted_on: Mastodon palvelimella %{domain}
     learn_more: Lisätietoja
     privacy_policy: Tietosuojaseloste
@@ -330,12 +326,6 @@ fi:
       no_media: Ei mediaa
       title: Tilin tilat
       with_media: Sisältää mediaa
-    subscriptions:
-      callback_url: Paluu-URL
-      confirmed: Vahvistettu
-      expires_in: Vanhenee
-      last_delivery: Viimeisin toimitus
-      topic: Aihe
     title: Ylläpito
   admin_mailer:
     new_report:
@@ -358,7 +348,6 @@ fi:
     your_token: Pääsytunnus
   auth:
     change_password: Salasana
-    confirm_email: Vahvista sähköpostiosoite
     delete_account: Poista tili
     delete_account_html: Jos haluat poistaa tilisi, <a href="%{path}">paina tästä</a>. Poisto on vahvistettava.
     didnt_get_confirmation: Etkö saanut vahvistusohjeita?
@@ -400,16 +389,14 @@ fi:
       x_months: "%{count} kk"
       x_seconds: "%{count} s"
   deletes:
-    bad_password_msg: Hyvä yritys, hakkerit! Väärä salasana
     confirm_password: Tunnistaudu syöttämällä nykyinen salasanasi
-    description_html: Tämä poistaa <strong>pysyvästi ja peruuttamattomasti</strong> kaiken tilisi sisällön ja poistaa tilin käytöstä. Käyttäjänimesi pysyy varattuna, jotta identiteettiäsi ei myöhemmin varasteta.
     proceed: Poista tili
     success_msg: Tilin poisto onnistui
-    warning_html: Sisällön poistaminen taataan vain tämän instanssin osalta. Jos sisältöä on jaettu paljon, siitä todennäköisesti jää jälkiä. Palvelimet, joihin ei saada yhteyttä tai jotka ovat lopettaneet päivitystesi tilaamisen, eivät päivitä tietokantojaan.
-    warning_title: Sisällön saatavuustieto levitetty
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Sinulla ei ole lupaa nähdä tätä sivua.
     '404': Etsimääsi sivua ei ole olemassa.
+    '406': This page is not available in the requested format.
     '410': Etsimääsi sivua ei ole enää olemassa.
     '422':
       content: Turvallisuusvahvistus epäonnistui. Oletko estänyt evästeet?
@@ -418,6 +405,7 @@ fi:
     '500':
       content: Valitettavasti jokin meni pieleen meidän päässämme.
       title: Sivu ei ole oikein
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: Mastodon-selainsovelluksen käyttöön vaaditaan JavaScript. Voit vaihtoehtoisesti kokeilla jotakin omalle käyttöjärjestelmällesi tehtyä Mastodon<a href="%{apps_path}">sovellusta</a>.
   exports:
     archive_takeout:
@@ -476,9 +464,6 @@ fi:
       too_many: Tiedostoja voi liittää enintään 4
   migrations:
     acct: uuden tilin käyttäjätunnus@verkkotunnus
-    currently_redirecting: 'Profiiliisi on asetettu uudelleenohjaus:'
-    proceed: Tallenna
-    updated_msg: Tilinsiirtoasetusten päivitys onnistui!
   moderation:
     title: Moderointi
   notification_mailer:
@@ -536,8 +521,6 @@ fi:
     missing_resource: Vaadittavaa uudelleenohjaus-URL:ää tiliisi ei löytynyt
     proceed: Siirry seuraamaan
     prompt: 'Olet aikeissa seurata:'
-  remote_unfollow:
-    error: Virhe
   sessions:
     activity: Viimeisin toiminta
     browser: Selain
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 5c15ab6a4282870b9a29a07d68884187df335823..f44a0893f38854eba22d53372586cfbe75325b1a 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -2,7 +2,7 @@
 fr:
   about:
     about_hashtag_html: Figurent ci-dessous les pouets tagués avec <strong>#%{hashtag}</strong>. Vous pouvez interagir avec eux si vous avez un compte n’importe où dans le Fediverse.
-    about_mastodon_html: Mastodon est un réseau social utilisant des formats ouverts et des logiciels libres. Comme le courriel, il est décentralisé.
+    about_mastodon_html: Mastodon est un réseau social utilisant des protocoles Web ouverts et des logiciels libres. Tout comme le courriel, il est décentralisé.
     about_this: À propos
     active_count_after: actif·ve·s
     active_footnote: Utilisateur·rice·s actif·ve·s mensuels (MAU)
@@ -17,13 +17,12 @@ fr:
     contact_unavailable: Non disponible
     discover_users: Découvrez des utilisateur·rice·s
     documentation: Documentation
-    extended_description_html: |
-      <h3>Un bon endroit pour les règles</h3>
-      <p>La description étendue n’a pas été remplie.</p>
     federation_hint_html: Avec un compte sur %{instance}, vous pourrez suivre les gens sur n’importe quel serveur Mastodon et au-delà.
-    generic_description: "%{domain} est seulement un serveur du réseau"
     get_apps: Essayez une application mobile
     hosted_on: Serveur Mastodon hébergée par %{domain}
+    instance_actor_flash: |
+      Ce compte est un acteur virtuel utilisé pour représenter le serveur lui-même et non un utilisateur individuel.
+      Il est utilisé à des fins de fédération et ne doit pas être bloqué à moins que vous ne vouliez bloquer l’instance entière, dans ce cas vous devriez utiliser un bloqueur de domaine.
     learn_more: En savoir plus
     privacy_policy: Politique de vie privée
     see_whats_happening: Voir ce qui se passe
@@ -35,6 +34,14 @@ fr:
     status_count_before: Ayant publié
     tagline: Suivez vos ami·e·s et découvrez en de nouveaux·elles
     terms: Conditions d’utilisation
+    unavailable_content: Contenu non disponible
+    unavailable_content_description:
+      domain: Serveur
+      reason: 'Motif :'
+      rejecting_media: Les fichiers média de ce serveur ne seront pas traités et aucune miniature ne sera affichée, nécessitant un clic manuel vers l'autre serveur.
+      silenced: Les messages de ce serveur ne s'afficheront nulle part sauf votre flux personnel si vous suivez l'auteur.
+      suspended: Vous ne pourrez suivre personne de ce serveur, aucune donnée ne sera traitée ou stockée et aucune donnée ne sera échangée.
+    unavailable_content_html: Mastodon vous permet généralement de visualiser le contenu et d'interagir avec les utilisateurs de n'importe quel autre serveur dans le fédiverse. Voici les exceptions qui ont été faites sur ce serveur en particulier.
     user_count_after:
       one: utilisateur
       other: utilisateurs
@@ -42,6 +49,8 @@ fr:
     what_is_mastodon: Qu’est-ce que Mastodon ?
   accounts:
     choices_html: "%{name} recommande :"
+    endorsements_hint: Vous pouvez soutenir les personnes que vous suivez depuis l’interface web, et elles apparaîtront ici.
+    featured_tags_hint: Vous pouvez recommander des hashtags spécifiques qui seront affichés ici.
     follow: Suivre
     followers:
       one: Abonné·e
@@ -53,6 +62,7 @@ fr:
     media: Médias
     moved_html: "%{name} a changé de compte pour %{new_profile_link} :"
     network_hidden: Cette information n’est pas disponible
+    never_active: Jamais
     nothing_here: Rien à voir ici !
     people_followed_by: Personnes suivies par %{name}
     people_who_follow: Personnes qui suivent %{name}
@@ -183,6 +193,7 @@ fr:
       username: Nom d’utilisateur⋅ice
       warn: Avertissement
       web: Web
+      whitelisted: Sur liste blanche
     action_logs:
       actions:
         assigned_to_self_report: "%{name} s’est assigné·e le signalement de %{target}"
@@ -218,19 +229,24 @@ fr:
       deleted_status: "(statut supprimé)"
       title: Journal d’audit
     custom_emojis:
+      assign_category: Attribuer une catégorie
       by_domain: Domaine
       copied_msg: Copie locale de l’émoji créée avec succès !
       copy: Copier
       copy_failed_msg: Impossible de faire une copie locale de cet émoji
+      create_new_category: Créer une nouvelle catégorie
       created_msg: Émoji créé avec succès !
       delete: Supprimer
       destroyed_msg: Émoji supprimé avec succès !
       disable: Désactiver
+      disabled: Désactivé
       disabled_msg: Émoji désactivé avec succès !
       emoji: Émoji
       enable: Activer
+      enabled: Activé
       enabled_msg: Émoji activé avec succès
       image_hint: PNG de moins de 50 Ko
+      list: Liste
       listed: Listé
       new:
         title: Ajouter un nouvel émoji personnalisé
@@ -238,11 +254,14 @@ fr:
       shortcode: Raccourci
       shortcode_hint: Au moins deux caractères, seulement des caractères alphanumériques ou des tirets bas
       title: Émojis personnalisés
+      uncategorized: Non catégorisé
+      unlist: Délisté
       unlisted: Délisté
       update_failed_msg: N’a pas pu mettre à jour cet émoji
       updated_msg: Émoji mis à jour avec succès !
       upload: Téléverser
     dashboard:
+      authorized_fetch_mode: Mode de récupération autorisé
       backlog: tâches en attente
       config: Configuration
       feature_deletions: Suppressions de comptes
@@ -250,10 +269,13 @@ fr:
       feature_profile_directory: Annuaire des profils
       feature_registrations: Inscriptions
       feature_relay: Relais de fédération
+      feature_spam_check: Anti-spam
       feature_timeline_preview: Aperçu du fil public
       features: Fonctionnalités
       hidden_service: Fédération avec des services cachés
       open_reports: signalements non résolus
+      pending_tags: hashtags en attente d’approbation
+      pending_users: utilisateur·rice·s en attente d’approbation
       recent_users: Utilisateur·rice·s récent·e·s
       search: Recherche plein texte
       single_user_mode: Mode utilisateur·ice unique
@@ -265,11 +287,18 @@ fr:
       week_interactions: interactions cette semaine
       week_users_active: actif·ve·s cette semaine
       week_users_new: utilisateur·rice·s cette semaine
+      whitelist_mode: Mode liste blanche
+    domain_allows:
+      add_new: Mettre le domaine sur liste sur blanche
+      created_msg: Ce domaine a été ajouté à la liste blanche avec succès
+      destroyed_msg: Le domaine a été supprimé de la liste blanche
+      undo: Supprimer de la liste blanche
     domain_blocks:
       add_new: Ajouter un nouveau bloqueur de domaine
       created_msg: Le blocage de domaine est désormais activé
       destroyed_msg: Le blocage de domaine a été désactivé
       domain: Domaine
+      edit: Modifier le bloqueur de domaine
       existing_domain_block_html: Vous avez déjà imposé des limites plus strictes à %{name}, vous devez d’abord le <a href="%{unblock_url}">débloquer</a>.
       new:
         create: Créer le blocage
@@ -280,6 +309,10 @@ fr:
           silence: Masqué
           suspend: Suspendre
         title: Nouveau blocage de domaine
+      private_comment: Commentaire privé
+      private_comment_hint: Commenter sur cette limitation de domaine pour informer les modérateurs internes.
+      public_comment: Commentaire public
+      public_comment_hint: Commentaire sur cette limitation de domaine pour le grand public, si la visibilité publique de la liste des limitations de domaine est activée.
       reject_media: Fichiers média rejetés
       reject_media_hint: Supprime localement les fichiers média stockés et refuse d’en télécharger ultérieurement. Ne concerne pas les suspensions
       reject_reports: Rejeter les signalements
@@ -299,6 +332,7 @@ fr:
         title: Annuler le blocage de domaine pour %{domain}
         undo: Annuler
       undo: Annuler le bloqueur de domaine
+      view: Afficher les bloqueurs de domaines
     email_domain_blocks:
       add_new: Ajouter
       created_msg: Le blocage de domaine de courriel est désormais activé
@@ -322,6 +356,8 @@ fr:
         all: Tout
         limited: Limité
         title: Modération
+      private_comment: Commentaire privé
+      public_comment: Commentaire public
       title: Fédération
       total_blocked_by_us: Bloqués par nous
       total_followed_by_them: Suivi par eux
@@ -341,7 +377,7 @@ fr:
     relays:
       add_new: Ajouter un nouveau relais
       delete: Effacer
-      description_html: Un <strong>relai de fédération</strong> est un serveur intermédiaire qui échange de grandes quantités de pouets entre les serveurs qui publient dessus et ceux qui y sont abonnés. <strong>Il peut aider les petits et moyen serveurs à découvrir du contenu sur le fediverse</strong>, ce qui normalement nécessiterait que les membres locaux suivent des gens inscrits sur des serveurs distants.
+      description_html: Un <strong>relai de fédération</strong> est un serveur intermédiaire qui échange de grandes quantités de pouets publics entre les serveurs qui publient dessus et ceux qui y sont abonnés. <strong>Il peut aider les petits et moyen serveurs à découvrir du contenu sur le fediverse</strong>, ce qui normalement nécessiterait que les membres locaux suivent des gens inscrits sur des serveurs distants.
       disable: Désactiver
       disabled: Désactivé
       enable: Activé
@@ -351,6 +387,7 @@ fr:
       pending: En attente de l’approbation du relai
       save_and_enable: Sauvegarder et activer
       setup: Paramétrer une connexion de relais
+      signatures_not_enabled: Les relais ne fonctionneront pas correctement lorsque le mode sécurisé ou le mode liste blanche est activé
       status: Statut
       title: Relais
     report_notes:
@@ -399,6 +436,16 @@ fr:
       custom_css:
         desc_html: Modifier l’apparence avec une CSS chargée sur chaque page
         title: CSS personnalisé
+      default_noindex:
+        desc_html: Affecte tous les utilisateurs qui n'ont pas changé eux-mêmes ce paramètre
+        title: Opter pour le retrait de l'indexation des moteurs de recherche par défaut
+      domain_blocks:
+        all: À tout le monde
+        disabled: À personne
+        title: Afficher les bloqueurs de domaines
+        users: Pour les utilisateurs locaux connectés
+      domain_blocks_rationale:
+        title: Voir le raisonnement
       hero:
         desc_html: Affichée sur la page d’accueil. Au moins 600x100px recommandé. Lorsqu’elle n’est pas définie, se rabat sur la vignette du serveur
         title: Image d’en-tête
@@ -449,6 +496,9 @@ fr:
         desc_html: Affichée sur la page des conditions d’utilisation du site<br>Vous pouvez utiliser des balises HTML
         title: Politique de confidentialité
       site_title: Nom du serveur
+      spam_check_enabled:
+        desc_html: Mastodon peut auto-silencer et signaler des comptes qui envoient des messages répétés non sollicités. Il peut y avoir de faux positifs.
+        title: Automatisation anti-spam
       thumbnail:
         desc_html: Utilisée pour les prévisualisations via OpenGraph et l’API. 1200x630px recommandé
         title: Vignette du serveur
@@ -456,12 +506,19 @@ fr:
         desc_html: Afficher le fil public sur la page d’accueil
         title: Prévisualisation du fil global
       title: Paramètres du serveur
+      trendable_by_default:
+        desc_html: Affecte les hashtags qui n'ont pas été précédemment non autorisés
+        title: Autoriser les hashtags à apparaître dans les tendances sans examen préalable
+      trends:
+        desc_html: Afficher publiquement les hashtags approuvés qui sont populaires en ce moment
+        title: Hashtags populaires
     statuses:
       back_to_account: Retour à la page du compte
       batch:
         delete: Supprimer
         nsfw_off: Marquer comme non-sensible
         nsfw_on: Marquer comme sensible
+      deleted: Supprimé
       failed_to_execute: Erreur d’exécution
       media:
         title: Médias
@@ -469,21 +526,24 @@ fr:
       no_status_selected: Aucun statut n’a été modifié car aucun n’a été sélectionné
       title: État du compte
       with_media: avec médias
-    subscriptions:
-      callback_url: URL de rappel
-      confirmed: Confirmé
-      expires_in: Expire dans
-      last_delivery: Dernière livraison
-      title: WebSub
-      topic: Sujet
     tags:
-      accounts: Comptes
-      hidden: Masqué
-      hide: Masquer dans l’annuaire
-      name: Hashtag
+      accounts_today: Utilisations uniques aujourd'hui
+      accounts_week: Utilisation unique cette semaine
+      breakdown: Répartition de l’utilisation actuelle par source
+      context: Contexte 
+      directory: Dans le répertoire
+      in_directory: "%{count} dans le répertoire"
+      last_active: Dernière activité
+      most_popular: Plus populaire
+      most_recent: Plus récent
+      name: Mot-clé
+      review: État de traitement
+      reviewed: Traité
       title: Hashtags
-      unhide: Afficher dans l’annuaire
-      visible: Visible
+      trending_right_now: Populaire en ce moment
+      unique_uses_today: "%{count} posts aujourd'hui"
+      unreviewed: Non traité
+      updated_msg: Paramètres Hashtag mis à jour avec succès
     title: Administration
     warning_presets:
       add_new: Ajouter un nouveau
@@ -499,11 +559,21 @@ fr:
       body: "%{reporter} a signalé %{target}"
       body_remote: Quelqu’un de %{domain} a signalé %{target}
       subject: Nouveau signalement sur %{instance} (#%{id})
+    new_trending_tag:
+      body: 'Le hashtag #%{name} est populaire aujourd’hui, mais il n’a pas été approuvé. Il ne sera pas affiché publiquement à moins que vous l’autorisiez, ou sauvegardiez simplement ce formulaire tel quel pour ne plus jamais en entendre parler.'
+      subject: Nouveau hashtag en attente de traitement sur %{instance} (#%{name})
+  aliases:
+    add_new: Créer un alias
+    created_msg: Un nouvel alias a été créé avec succès. Vous pouvez maintenant lancer le déplacement depuis l'ancien compte.
+    deleted_msg: Suppression réussie de l'alias. Le déménagement de ce compte vers celui-ci ne sera plus possible.
+    hint_html: Si vous voulez déménager d’un autre compte vers celui-ci, vous pouvez créer ici un alias, qui est nécessaire avant de pouvoir migrer les abonné·e·s de l’ancien compte vers celui-ci. Cette action en soi est <strong>inoffensive et réversible</strong>. <strong>La migration du compte est initiée à partir de l’ancien compte</strong>.
+    remove: Détacher l'alias
   appearance:
     advanced_web_interface: Interface web avancée
     advanced_web_interface_hint: 'Si vous voulez utiliser toute la largeur de votre écran, l’interface web avancée vous permet de configurer plusieurs colonnes différentes pour voir autant d’informations que vous le souhaitez en même temps : Accueil, notifications, fil public fédéré, un nombre illimité de listes et hashtags.'
     animations_and_accessibility: Animations et accessibilité
     confirmation_dialogs: Dialogues de confirmation
+    discovery: Découverte
     sensitive_content: Contenu sensible
   application_mailer:
     notification_preferences: Modifier les préférences de courriel
@@ -524,15 +594,19 @@ fr:
     apply_for_account: Demander une invitation
     change_password: Mot de passe
     checkbox_agreement_html: J’accepte les <a href="%{rules_path}" target="_blank">règles du serveur</a> et les <a href="%{terms_path}" target="_blank">conditions de service</a>
-    confirm_email: Confirmer mon adresse mail
+    checkbox_agreement_without_rules_html: J’accepte les <a href="%{terms_path}" target="_blank">conditions d’utilisation</a>
     delete_account: Supprimer le compte
     delete_account_html: Si vous désirez supprimer votre compte, vous pouvez <a href="%{path}">cliquer ici</a>. Il vous sera demandé de confirmer cette action.
+    description:
+      prefix_invited_by_user: "@%{name} vous invite à rejoindre ce serveur Mastodon !"
+      prefix_sign_up: Inscrivez-vous aujourd’hui sur Mastodon !
+      suffix: Avec un compte, vous pourrez suivre des gens, publier des statuts et échanger des messages avec les utilisateurs de n'importe quel serveur Mastodon et bien plus !
     didnt_get_confirmation: Vous n’avez pas reçu les consignes de confirmation ?
     forgot_password: Mot de passe oublié ?
     invalid_reset_password_token: Le lien de réinitialisation du mot de passe est invalide ou a expiré. Merci de réessayer.
     login: Se connecter
     logout: Se déconnecter
-    migrate_account: Déplacer vers un compte différent
+    migrate_account: Déménager vers un compte différent
     migrate_account_html: Si vous voulez rediriger ce compte vers un autre, vous pouvez le <a href="%{path}">configurer ici</a>.
     or_log_in_with: Ou authentifiez-vous avec
     providers:
@@ -544,6 +618,16 @@ fr:
     reset_password: Réinitialiser le mot de passe
     security: Sécurité
     set_new_password: Définir le nouveau mot de passe
+    setup:
+      email_below_hint_html: Si l’adresse e-mail ci-dessous est incorrecte, vous pouvez la modifier ici et recevoir un nouveau courriel de confirmation.
+      email_settings_hint_html: L’e-mail de confirmation a été envoyé à %{email}. Si cette adresse e-mail n’est pas correcte, vous pouvez la modifier dans les paramètres du compte.
+      title: Configuration
+    status:
+      account_status: Statut du compte
+      confirming: En attente de la confirmation par e-mail à compléter.
+      functional: Votre compte est entièrement opérationnel.
+      pending: Votre demande est en attente d'examen par notre personnel. Cela peut prendre un certain temps. Vous recevrez un courriel si votre demande est approuvée.
+      redirecting_to: Votre compte est inactif car il est actuellement redirigé vers %{acct}.
     trouble_logging_in: Vous avez un problème pour vous connecter ?
   authorize_follow:
     already_following: Vous suivez déjà ce compte
@@ -556,6 +640,11 @@ fr:
       return: Afficher le profil de l’utilisateur⋅ice
       web: Retour à l’interface web
     title: Suivre %{acct}
+  challenge:
+    confirm: Continuer
+    hint_html: "<strong>Astuce :</strong> Nous ne vous demanderons plus votre mot de passe pour la prochaine heure."
+    invalid_password: Mot de passe invalide
+    prompt: Confirmez votre mot de passe pour continuer
   datetime:
     distance_in_words:
       about_x_hours: "%{count} h"
@@ -571,26 +660,33 @@ fr:
       x_months: "%{count} mois"
       x_seconds: "%{count} s"
   deletes:
-    bad_password_msg: Bien essayé ! Mot de passe incorrect
+    challenge_not_passed: Les renseignements que vous avez entrés n'étaient pas exacts
     confirm_password: Entrez votre mot de passe pour vérifier votre identité
-    description_html: Cela va supprimer votre compte et le désactiver de manière <strong>permanente et irréversible</strong>. Votre nom d’utilisateur⋅ice restera réservé afin d’éviter la confusion.
+    confirm_username: Entrez votre nom d'utilisateur pour confirmer la procédure
     proceed: Supprimer compte
     success_msg: Votre compte a été supprimé avec succès
-    warning_html: Seule la suppression du contenu depuis ce serveur est garantie. Le contenu qui a été partagé est susceptible de laisser des traces. Les serveurs hors-ligne ainsi que ceux n’étant plus abonnées à vos publications ne mettront pas leur base de données à jour.
-    warning_title: Disponibilité du contenu disséminé
+    warning:
+      before: 'Avant de procéder, veuillez lire attentivement ces notes :'
+      caches: Le contenu mis en cache par d'autres serveurs peut persister
+      data_removal: Vos messages et autres données seront définitivement supprimés
+      email_change_html: Vous pouvez <a href="%{path}">modifier votre adresse courriel</a> sans supprimer votre compte
+      email_contact_html: S'il n'arrive toujours pas, vous pouvez envoyer un courriel à <a href="mailto:%{email}">%{email}</a> pour de l'aide
+      email_reconfirmation_html: Si vous ne recevez pas le courriel de confirmation, vous pouvez le <a href="%{path}">demander à nouveau</a>
+      irreversible: Vous ne pourrez pas restaurer ou réactiver votre compte
+      more_details_html: Pour plus de détails, voir la <a href="%{terms_path}">politique de confidentialité</a>.
+      username_available: Votre nom d’utilisateur sera à nouveau disponible
+      username_unavailable: Votre nom d’utilisateur restera indisponible
   directories:
     directory: Annuaire des profils
-    enabled: Vous êtes actuellement listé dans l’annuaire.
-    enabled_but_waiting: Vous avez choisi d’être listé dans l’annuaire, mais vous n’avez pas encore le nombre minimum de suiveurs (%{min_followers}) pour y être inscrit.
     explanation: Découvrir des utilisateurs en se basant sur leurs centres d’intérêt
     explore_mastodon: Explorer %{title}
-    how_to_enable: Vous n’êtes pas encore inscrit dans l’annuaire. Vous pouvez vous inscrire ci-dessous. Utilisez des hashtags dans votre texte biographique pour être listé sous des hashtags spécifiques !
-    people:
-      one: "%{count} personne"
-      other: "%{count} personne"
+  domain_validator:
+    invalid_domain: n’est pas un nom de domaine valide
   errors:
+    '400': La demande que vous avez soumise est invalide ou mal formée.
     '403': Vous n’avez pas accès à cette page.
     '404': La page que vous recherchez n’existe pas.
+    '406': Cette page n'est pas disponible au format demandé.
     '410': La page que vous recherchez n’existe plus.
     '422':
       content: Vérification de sécurité échouée. Bloquez-vous les cookies ?
@@ -599,6 +695,7 @@ fr:
     '500':
       content: Nous sommes désolé·e·s, mais quelque chose s’est mal passé de notre côté.
       title: Cette page n’est pas correcte
+    '503': La page n'a pas pu être servie en raison d'une défaillance temporaire du serveur.
     noscript_html: Pour utiliser Mastodon, veuillez activer JavaScript. Sinon, essayez l’une des <a href="%{apps_path}">applications natives</a> pour Mastodon pour votre plate-forme.
   existing_username_validator:
     not_found: n’a pas trouvé d’utilisateur·rice local·e avec ce nom
@@ -621,7 +718,8 @@ fr:
   featured_tags:
     add_new: Ajouter un nouvel hashtag
     errors:
-      limit: Vous avez déjà mis en avant le nombre maximum de hashtags
+      limit: Vous avez déjà recommandé le nombre maximum de hashtags
+    hint_html: "<strong>Que sont les hashtags vedettes ?</strong> Ils sont affichés avec emphase sur votre flux d'actualités publique et permettent aux gens de parcourir vos messages publics spécifiquement sous ces hashtags. Ils sont un excellent outil pour garder trace des œuvres créatives ou des projets à long terme."
   filters:
     contexts:
       home: Accueil
@@ -642,10 +740,12 @@ fr:
     developers: Développeurs
     more: Davantage…
     resources: Ressources
+    trending_now: Tendance en ce moment
   generic:
     all: Tous
     changes_saved_msg: Les modifications ont été enregistrées avec succès !
     copy: Copier
+    no_batch_actions_available: Aucune action par lots disponible sur cette page
     order_by: Classer par
     save_changes: Enregistrer les modifications
     validation_errors:
@@ -717,9 +817,34 @@ fr:
       too_many: Impossible de joindre plus de 4 fichiers
   migrations:
     acct: profil@domaine du nouveau compte
-    currently_redirecting: 'Votre profil va être redirigé vers :'
-    proceed: Enregistrer
-    updated_msg: Les paramètres de votre migration de compte ont été mis à jour avec succès !
+    cancel: Annuler la redirection
+    cancel_explanation: Annuler la redirection réactivera votre compte courant, mais ne rapportera pas les abonné·e·s qui ont été déplacé·e·s sur ce compte.
+    cancelled_msg: Suppression de la redirection réussie.
+    errors:
+      already_moved: est le même compte que vous avez déjà déplacé vers
+      missing_also_known_as: ne référence pas rétroactivement ce compte
+      move_to_self: ne peut pas être un compte courant
+      not_found: n'a pas été trouvé
+      on_cooldown: Vous êtes en refroidissement
+    followers_count: Abonnés au moment du déplacement
+    incoming_migrations: Déplacement d'un compte différent
+    incoming_migrations_html: Pour passer d'un autre compte à celui-ci, vous devez d'abord <a href="%{path}">créer un alias de compte</a>.
+    moved_msg: Votre compte est maintenant redirigé vers %{acct} et vos abonnés sont déplacés.
+    not_redirecting: Votre compte n'est pas redirigé vers un autre compte actuellement.
+    on_cooldown: Vous avez récemment migré votre compte. Cette fonction sera à nouveau disponible dans %{count} jours.
+    past_migrations: Migrations passées
+    proceed_with_move: Migrer les abonné·e·s
+    redirecting_to: Votre compte est redirigé vers %{acct}.
+    set_redirect: Définir redirection
+    warning:
+      backreference_required: Le nouveau compte doit d'abord être configuré pour faire référence à celui-ci
+      before: 'Avant de procéder, veuillez lire attentivement ces notes :'
+      cooldown: Après le déménagement, il y a une période de gel pendant laquelle vous ne pourrez plus re-déménager
+      disabled_account: Votre compte actuel ne sera pas entièrement utilisable par la suite. Cependant, vous aurez accès à l'exportation de données et à la ré-activation.
+      followers: Cette action va déménager tou·te·s les abonné·e·s du compte actuel vers le nouveau compte
+      only_redirect_html: Alternativement, vous pouvez <a href="%{path}">seulement appliquer une redirection sur votre profil</a>.
+      other_data: Aucune autre donnée ne sera déplacée automatiquement
+      redirect: Le profil de votre compte actuel sera mis à jour avec un avis de redirection et sera exclu des recherches
   moderation:
     title: Modération
   notification_mailer:
@@ -816,10 +941,6 @@ fr:
     reply:
       proceed: Confirmer la réponse
       prompt: 'Vous souhaitez répondre à ce pouet :'
-  remote_unfollow:
-    error: Erreur
-    title: Titre
-    unfollowed: Non-suivi
   scheduled_statuses:
     over_daily_limit: Vous avez dépassé la limite de %{limit} pouets planifiés pour ce jour
     over_total_limit: Vous avez dépassé la limite de %{limit} pouets planifiés
@@ -829,7 +950,7 @@ fr:
     browser: Navigateur
     browsers:
       alipay: Alipay
-      blackberry: Blackberry
+      blackberry: BlackBerry
       chrome: Chrome
       edge: Microsoft Edge
       electron: Electron
@@ -843,7 +964,7 @@ fr:
       phantom_js: PhantomJS
       qq: QQ Browser
       safari: Safari
-      uc_browser: UCBrowser
+      uc_browser: UC Browser
       weibo: Weibo
     current_session: Session courante
     description: "%{browser} sur %{platform}"
@@ -852,8 +973,8 @@ fr:
     platforms:
       adobe_air: Adobe Air
       android: Android
-      blackberry: Blackberry
-      chrome_os: ChromeOS
+      blackberry: BlackBerry
+      chrome_os: Chrome OS
       firefox_os: Firefox OS
       ios: iOS
       linux: Linux
@@ -868,6 +989,7 @@ fr:
   settings:
     account: Compte
     account_settings: Paramètres du compte
+    aliases: Alias du compte
     appearance: Apparence
     authorized_apps: Applications autorisées
     back: Retour vers Mastodon
@@ -875,7 +997,7 @@ fr:
     development: Développement
     edit_profile: Modifier le profil
     export: Export de données
-    featured_tags: Hashtags mis en avant
+    featured_tags: Hashtags recommandés
     identity_proofs: Preuves d’identité
     import: Import de données
     import_and_export: Import et export
@@ -885,6 +1007,8 @@ fr:
     profile: Profil
     relationships: Abonnements et abonné·e·s
     two_factor_authentication: Identification à deux facteurs
+  spam_check:
+    spam_detected: Ceci est un rapport automatisé. Du spam a été détecté.
   statuses:
     attached:
       description: 'Attaché : %{attached}'
@@ -908,6 +1032,9 @@ fr:
       private: Les statuts non-publics ne peuvent pas être épinglés
       reblog: Un partage ne peut pas être épinglé
     poll:
+      total_people:
+        one: "%{count} personnes"
+        other: "%{count} personnes"
       total_votes:
         one: "%{count} vote"
         other: "%{count} votes"
@@ -926,6 +1053,8 @@ fr:
     pinned: Pouet épinglé
     reblogged: a partagé
     sensitive_content: Contenu sensible
+  tags:
+    does_not_match_previous_name: ne correspond pas au nom précédent
   terms:
     body_html: |
       <h2>Politique de confidentialité</h2>
@@ -1005,7 +1134,7 @@ fr:
 
       <p>Dans le cas où nous déciderions de changer notre politique de confidentialité, nous posterons les modifications sur cette page.</p>
 
-      <p>Ce document est publié sous lincence CC-BY-SA. Il a été mis à jours pour la dernière fois le 7 mars 2018.</p>
+      <p>Ce document est publié sous licence CC-BY-SA. Il a été mis à jour pour la dernière fois le 7 mars 2018.</p>
 
       <p>Originellement adapté de la <a href="https://github.com/discourse/discourse">politique de confidentialité de Discourse</a>.</p>
     title: "%{instance} Conditions d’utilisation et politique de confidentialité"
@@ -1043,7 +1172,9 @@ fr:
         disable: Lorsque votre compte est gelé, les données de votre compte demeurent intactes, mais vous ne pouvez effectuer aucune action jusqu’à ce qu’il soit débloqué.
         silence: Lorsque votre compte est limité, seulement les utilisateurs qui vous suivent déjà verront vos pouets sur ce serveur, et vous pourriez être exclu de plusieurs listes publiques. Néanmoins, d’autres utilisateurs peuvent vous suivre manuellement.
         suspend: Votre compte a été suspendu, et tous vos pouets et vos fichiers multimédia téléversés ont été supprimés irréversiblement de ce serveur, et des serveurs où vous aviez des abonné⋅e⋅s.
+      get_in_touch: Vous pouvez répondre à cette adresse pour entrer en contact avec l’équipe de %{instance}.
       review_server_policies: Passer en revue les politiques du serveur
+      statuses: 'Spécialement, pour :'
       subject:
         disable: Votre compte %{acct} a été gelé
         none: Avertissement pour %{acct}
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index 79ef993e2edab37d2bbf385f5c1860833a32151c..f325757520fc38133199d5da87b6b33bc3643c91 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -1,9 +1,9 @@
 ---
 gl:
   about:
-    about_hashtag_html: Estas son mensaxes públicas etiquetadas con <strong>#%{hashtag}</strong>. Pode interactuar con elas si ten unha conta nalgures do fediverso.
+    about_hashtag_html: Estas son mensaxes públicas etiquetadas con <strong>#%{hashtag}</strong>. Podes interactuar con elas se tes unha conta nalgures do fediverso.
     about_mastodon_html: Mastodon é unha rede social que se basea en protocolos web abertos e libres, software de código aberto. É descentralizada como o correo electrónico.
-    about_this: Sobre
+    about_this: Acerca de
     active_count_after: activo
     active_footnote: Usuarias Activas no Mes (UAM)
     administered_by: 'Administrada por:'
@@ -17,13 +17,12 @@ gl:
     contact_unavailable: N/A
     discover_users: Descubra usuarias
     documentation: Documentación
-    extended_description_html: |
-      <h3>Un bo lugar para regras</h3>
-      <p>A descrición extendida aínda non se proporcionou.</p>
     federation_hint_html: Con unha conta en %{instance} poderá seguir a outras persoas en calquera dos servidores Mastodon e incluso máis.
-    generic_description: "%{domain} é un servidor na rede"
     get_apps: Probe cunha app móbil
     hosted_on: Mastodon aloxado en %{domain}
+    instance_actor_flash: 'Esta conta é un actor virtual utilizado para representar ao servidor e non a unha usuaria individual. Utilízase para propósitos de federación e non debería estar bloqueada a menos que queira bloquear a toda a instancia, en tal caso debería utilizar o bloqueo do dominio.
+
+'
     learn_more: Coñeza máis
     privacy_policy: Política de intimidade
     see_whats_happening: Mire o que acontece
@@ -35,6 +34,14 @@ gl:
     status_count_before: Que publicaron
     tagline: Siga as amizades e faga outras novas
     terms: Termos do servizo
+    unavailable_content: Contido non dispoñible
+    unavailable_content_description:
+      domain: Servidor
+      reason: 'Razón:'
+      rejecting_media: Os ficheiros de medios de este servidor non se procesarán e non se mostrarán miniaturas, precisando solicitarse manualmente ao outro servidor.
+      silenced: As publicacións desde este servidor non se mostrarán en ningún lugar excepto no Inicio se segues ao autor.
+      suspended: Non poderás seguir a ninguén desde este servidor, e non se procesarán nin se gardarán datos que procedan del, e non se intercambiarán datos.
+    unavailable_content_html: Normalmente Mastodon permíteche ver contidos de outros servidores do fediverso e interactuar coas súas usuarias. Estas son as excepcións que se estableceron en este servidor particular.
     user_count_after:
       one: usuaria
       other: usuarias
@@ -42,6 +49,8 @@ gl:
     what_is_mastodon: Qué é Mastodon?
   accounts:
     choices_html: 'Eleccións de %{name}:'
+    endorsements_hint: Desde a interface web pode recomendar xente que segue, e aparecerán aquí.
+    featured_tags_hint: Pode destacar determinadas etiquetas que se mostrarán aquí.
     follow: Seguir
     followers:
       one: Seguidora
@@ -53,6 +62,7 @@ gl:
     media: Medios
     moved_html: "%{name} mudouse a %{new_profile_link}:"
     network_hidden: A información non está dispoñible
+    never_active: Nunca
     nothing_here: Nada por aquí!
     people_followed_by: Personas que segue %{name}
     people_who_follow: Personas que seguen a %{name}
@@ -183,6 +193,7 @@ gl:
       username: Nome de usuaria
       warn: Aviso
       web: Web
+      whitelisted: Lista branca
     action_logs:
       actions:
         assigned_to_self_report: "%{name} asignou o informe %{target} a ela misma"
@@ -218,19 +229,24 @@ gl:
       deleted_status: "(estado eliminado)"
       title: Rexistro de auditoría
     custom_emojis:
+      assign_category: Asignar categoría
       by_domain: Dominio
       copied_msg: Creouse con éxito unha copia local dos emoji
       copy: Copiar
       copy_failed_msg: Non se puido facer copia local de ese emoji
+      create_new_category: Crear nova categoría
       created_msg: Creou o emoji con satisfactoriamente!
       delete: Eliminar
       destroyed_msg: Emojo destruído satisfactoriamente!
       disable: Deshabilitar
+      disabled: Desactivado
       disabled_msg: Deshabilitouse correctamente ese emoji
       emoji: Emoji
       enable: Habilitar
+      enabled: Activado
       enabled_msg: Habilitouse correctamente ese emoji
       image_hint: PNG ate 50KB
+      list: A lista
       listed: Listado
       new:
         title: Engadir novo emoji persoalizado
@@ -238,11 +254,14 @@ gl:
       shortcode: Código corto
       shortcode_hint: Cando menos 2 caracteres, só caracteres alfanuméricos e subliñados
       title: Emojis persoalizados
+      uncategorized: Sen categorizar
+      unlist: Sacar da lista
       unlisted: Non listado
       update_failed_msg: Non se puido actualizar ese emoji
       updated_msg: Actualizouse correctamente o emoji!
       upload: Subir
     dashboard:
+      authorized_fetch_mode: Modo de obtención autorizado
       backlog: traballos respaldados
       config: Axustes
       feature_deletions: Borrado de contas
@@ -250,10 +269,13 @@ gl:
       feature_profile_directory: Directorio do perfil
       feature_registrations: Rexistros
       feature_relay: Repetidores de federación
+      feature_spam_check: Anti-spam
       feature_timeline_preview: Vista previa da TL
       features: Características
       hidden_service: Federación con servizos ocultos
       open_reports: informes abertos
+      pending_tags: etiquetas agardando revisión
+      pending_users: usuarias agardando revisión
       recent_users: Usuarias recentes
       search: Busca de texto completo
       single_user_mode: Modo de usuario individual
@@ -265,11 +287,18 @@ gl:
       week_interactions: interaccións en esta semana
       week_users_active: activas estas semana
       week_users_new: usuarias esta semana
+      whitelist_mode: Modo de lista branca
+    domain_allows:
+      add_new: Dominio en lista branca
+      created_msg: Engadeu o dominio a lista branca
+      destroyed_msg: Quitou o dominio da lista branca
+      undo: Eliminar da lista branca
     domain_blocks:
       add_new: Engadir novo bloqueo de dominio
       created_msg: Estase a procesar o bloqueo do dominio
       destroyed_msg: Desfixose a acción de bloqueo de dominio
       domain: Dominio
+      edit: Editar bloqueo de dominio
       existing_domain_block_html: Xa estableceu límites estrictos para %{name}, precisa <a href="%{unblock_url}">desbloqueala</a> primeiro.
       new:
         create: Crear bloque
@@ -280,6 +309,10 @@ gl:
           silence: Silenciar
           suspend: Suspender
         title: Novo bloqueo de dominio
+      private_comment: Comentario privado
+      private_comment_hint: Comentar entre moderadores para uso interno as limitacións de este dominio.
+      public_comment: Comentario público
+      public_comment_hint: Comentar sobre as limitacións de este dominio para o interese público, se o aviso da lista de dominios limitados está activado.
       reject_media: Rexeitar ficheiros de medios
       reject_media_hint: Eliminar ficheiros de medios almacenados localmente e rexeita descargalos no futuro. Irrelevante para as suspensións
       reject_reports: Rexeitar informes
@@ -299,6 +332,7 @@ gl:
         title: Desfacer o bloqueo de dominio para %{domain}
         undo: Desfacer
       undo: Desfacer bloqueo de dominio
+      view: Ver dominios bloqueados
     email_domain_blocks:
       add_new: Engadir novo
       created_msg: Engadeuse correctamente o dominio de email a lista negra
@@ -322,6 +356,8 @@ gl:
         all: Todo
         limited: Limitado
         title: Moderación
+      private_comment: Comentario privado
+      public_comment: Comentario público
       title: Federación
       total_blocked_by_us: Bloqueado por nós
       total_followed_by_them: Seguidas por eles
@@ -351,6 +387,7 @@ gl:
       pending: Agardando polo permiso do repetidor
       save_and_enable: Gardar e activar
       setup: Configurar a conexión ao repetidor
+      signatures_not_enabled: Os repetidores non funcionarán correctamente se o modo seguro ou lista branca están activados
       status: Estado
       title: Repetidores
     report_notes:
@@ -399,6 +436,16 @@ gl:
       custom_css:
         desc_html: Modificar o aspecto con CSS cargado en cada páxina
         title: CSS persoalizado
+      default_noindex:
+        desc_html: Aféctalle a todas as usuarias que non cambiaron os axustes elas mesmas
+        title: Por omisión exclúe as usuarias do indexado por servidores de busca
+      domain_blocks:
+        all: Para todas
+        disabled: Para ninguén
+        title: Mostrar dominios bloqueados
+        users: Para usuarias locais conectadas
+      domain_blocks_rationale:
+        title: Mostrar razón
       hero:
         desc_html: Mostrado na portada. Recoméndase 600x100px como mínimo. Se non se establece, mostrará a imaxe por omisión do servidor
         title: Imáxe Heróe
@@ -449,6 +496,9 @@ gl:
         desc_html: Pode escribir a súa propia política de intimidade, termos de servizo ou aclaracións legais. Pode utilizar etiquetas HTML
         title: Termos de servizo persoalizados
       site_title: Nome do servidor
+      spam_check_enabled:
+        desc_html: Mastodon pode silenciar e informar automáticamente sobre contas baseándose en medidas como detectar contas que envían mensaxes non solicitadas de xeito repetido. Podería haber falsos positivos.
+        title: Anti-spam
       thumbnail:
         desc_html: Utilizado para vistas previsas vía OpenGraph e API. Recoméndase 1200x630px
         title: Icona do servidor
@@ -456,12 +506,19 @@ gl:
         desc_html: Mostrar liña de tempo pública na páxina de inicio
         title: vista previa da liña temporal
       title: Axustes do sitio
+      trendable_by_default:
+        desc_html: Afecta a etiquetas que non foron rexeitadas previamente
+        title: Permite as etiquetas ser tendencia sen revisión previa
+      trends:
+        desc_html: Mostrar públicamente etiquetas previamente revisadas que actualmente son tendencia
+        title: Etiquetas Tendencia
     statuses:
       back_to_account: Voltar a páxina da conta
       batch:
         delete: Eliminar
         nsfw_off: Marcar como non sensible
         nsfw_on: Marcar como sensible
+      deleted: Eliminado
       failed_to_execute: Fallou a execución
       media:
         title: Medios
@@ -469,21 +526,24 @@ gl:
       no_status_selected: Non se cambiou ningún estado xa que ningún foi seleccionado
       title: Estados da conta
       with_media: con medios
-    subscriptions:
-      callback_url: URL de chamada
-      confirmed: Confirmado
-      expires_in: Caduca en
-      last_delivery: Última entrega
-      title: WebSub
-      topic: Asunto
     tags:
-      accounts: Contas
-      hidden: Ocultas
-      hide: Ocultar do directorio
+      accounts_today: Usos únicos hoxe
+      accounts_week: Usos únicos esta semana
+      breakdown: Consumo do uso diario por fonte
+      context: Contexto
+      directory: No directorio
+      in_directory: "%{count} no directorio"
+      last_active: Úlimo activo
+      most_popular: Máis popular
+      most_recent: Máis recente
       name: Etiqueta
+      review: Estado de revisión
+      reviewed: Revisado
       title: Etiquetas
-      unhide: Mostrar en directorio
-      visible: Visible
+      trending_right_now: Agora como Tendencia
+      unique_uses_today: "%{count} publicando hoxe"
+      unreviewed: Sen revisar
+      updated_msg: Actualizaronse os axustes das etiquetas
     title: Administración
     warning_presets:
       add_new: Engadir novo
@@ -499,11 +559,21 @@ gl:
       body: "%{reporter} informou sobre %{target}"
       body_remote: Alguén desde %{domain} informou sobre %{target}
       subject: Novo informe sobre %{instance} (#%{id})
+    new_trending_tag:
+      body: 'A etiqueta #%{name} é tendencia hoxe, pero non foi previamente revisada. Non se mostrará publicamente a menos que vostede o permita, ou garde o formulario para facer que non se lle consulte de novo.'
+      subject: Unha nova etiqueta que revisar en %{instance} (#%{name})
+  aliases:
+    add_new: Crear alcume
+    created_msg: Creou un novo alcume correctamente. Pode iniciar o movemento desde a conta antiga.
+    deleted_msg: Eliminou correctamente o alias. Xa non será posible mover desde esa conta a esta.
+    hint_html: Se quere mudarse desde outra conta a esta nova, aquí pode crear un alcume, que é requerido antes de poder proceder a mover os seguidores da conta antiga a esta nova. Esta acción por si mesma é <strong>inocua e reversible</strong>. <strong>A migración da conta iníciase desde a conta antiga</strong>.
+    remove: Desligar alcume
   appearance:
     advanced_web_interface: Interface web avanzada
     advanced_web_interface_hint: Se quere utilizar todo o ancho da súa pantalla, a interface web avanzada permítelle configurar diferentes columnas para ver tanta información como desexe. Inicio, notificacións, liña temporal federada, calquera número de listas e etiquetas.
     animations_and_accessibility: Animacións e accesibilidade
     confirmation_dialogs: Diálogos de confirmación
+    discovery: Descubrir
     sensitive_content: Contido sensible
   application_mailer:
     notification_preferences: Cambiar os axustes de correo-e
@@ -524,9 +594,13 @@ gl:
     apply_for_account: Solicite un convite
     change_password: Contrasinal
     checkbox_agreement_html: Acepto as <a href="%{rules_path}" target="_blank">regras do servidor</a> e os <a href="%{terms_path}" target="_blank">termos do servizo</a>
-    confirm_email: Confirmar correo-e
+    checkbox_agreement_without_rules_html: Acepto os <a href="%{terms_path}" target="_blank">termos do servizo</a>
     delete_account: Eliminar conta
     delete_account_html: Se desexa eliminar a súa conta, pode <a href="%{path}">facelo aquí</a>. Pediráselle confirmación.
+    description:
+      prefix_invited_by_user: "@%{name} convídate a que te unas a este servidor Mastodon!"
+      prefix_sign_up: Rexístrate agora en Mastodon!
+      suffix: Ao abrir unha conta, poderás seguir a xente, actualizacións das publicacións e intercambiar mensaxes coas usuarias de calquera servidor de Mastodon e moito máis!
     didnt_get_confirmation: Non recibeu as instruccións de confirmación?
     forgot_password: Esqueceu o contrasinal?
     invalid_reset_password_token: O testemuño para restablecer o contrasinal non é válido ou caducou. Por favor solicite un novo.
@@ -544,6 +618,16 @@ gl:
     reset_password: Restablecer contrasinal
     security: Seguridade
     set_new_password: Establecer novo contrasinal
+    setup:
+      email_below_hint_html: Se o enderezo inferior non é correcto, pode cambialo aquí e recibir un correo de confirmación.
+      email_settings_hint_html: Enviouse un correo de confirmación a %{email}. Se o enderezo non é correcto pode cambialo nos axustes da conta.
+      title: Axustes
+    status:
+      account_status: Estado da conta
+      confirming: Agardando a confirmación do correo enviado.
+      functional: A súa conta está totalmente operativa.
+      pending: A súa aplicación está pendente de revisión. Poderíanos levar algún tempo. Recibirá un correo se a aplicación está aprobada.
+      redirecting_to: A túa conta está inactiva porque está redirixida a %{acct}.
     trouble_logging_in: Problemas para conectar?
   authorize_follow:
     already_following: Xa está a seguir esta conta
@@ -556,6 +640,11 @@ gl:
       return: Mostrar o perfil da usuaria
       web: Ir a web
     title: Seguir %{acct}
+  challenge:
+    confirm: Continuar
+    hint_html: "<strong>Nota:</strong> Non che pediremos o contrasinal na seguinte hora."
+    invalid_password: Contrasinal incorrecto
+    prompt: Confirma o contrasinal para continuar
   datetime:
     distance_in_words:
       about_x_hours: "%{count}h"
@@ -571,26 +660,33 @@ gl:
       x_months: "%{count}mes"
       x_seconds: "%{count}s"
   deletes:
-    bad_password_msg: Bo intento, hackers! Contrasinal incorrecto
+    challenge_not_passed: A información introducida non é correcta
     confirm_password: Introduza o seu contrasinal para verificar a súa identidade
-    description_html: Esto eliminará de xeito <strong>permanente e irreversible</strong> o contido da súa conta e será desactivada. O seu nome de usuaria permanecerá reservado para evitar futuras confusións de identidades.
+    confirm_username: Introduce o nome de usuaria para confirmar o procedemento
     proceed: Eliminar conta
     success_msg: A súa conta eliminouse correctamente
-    warning_html: Só se garantiza a eliminación de contido de este servidor. O contido que foi compartido con outras instancias é probable que deixe rastros. O servidores fora de liña e servidores que se desuscribiron das súas actualizacións non actualizarán as súas bases de datos.
-    warning_title: Dispoñibilidade do contido espallado
+    warning:
+      before: 'Antes de seguir, por favor lé estas notas con atención:'
+      caches: O contido almacenado en outros servidores podería persistir
+      data_removal: As túas publicacións e outros datos serán permanentemente borrados
+      email_change_html: Podes <a href="%{path}">cambiar o enderezo de correo</a> sen eliminar a conta
+      email_contact_html: Se non o recibes, podes escribir a <a href="mailto:%{email}">%{email}</a> pedindo axuda
+      email_reconfirmation_html: Se non recibes o correo de confirmación, podes <a href="%{path}">solicitalo de novo</a>
+      irreversible: Non poderás restaurar ou reactivar a conta
+      more_details_html: Para máis detalles, mira a <a href="%{terms_path}">política de intimidade</a>.
+      username_available: O nome de usuario estará dispoñible novamente
+      username_unavailable: O nome de usuario non estará dispoñible
   directories:
     directory: Directorio de perfil
-    enabled: Vostede está actualmente na lista do directorio.
-    enabled_but_waiting: Vostede optou por ser incluída no directorio, mais por agora non ten o número mínimo de seguidoras (%{min_followers}) para aparecer.
     explanation: Descubra usuarias según o seu interese
     explore_mastodon: Explorar %{title}
-    how_to_enable: Actualmente non solicitou ser incluída no directorio, pode facelo abaixo. Utilice etiquetas no texto de biografía para ser incluída baixo etiquetas específicas!
-    people:
-      one: "%{count} persoa"
-      other: "%{count} persoas"
+  domain_validator:
+    invalid_domain: non é un nome de dominio válido
   errors:
+    '400': A solicitude que enviou non é válida ou ten formato incorrecto.
     '403': Non ten permiso para ver esta páxina.
     '404': A páxina que está a buscar non está aquí.
+    '406': Esta páxina non está dispoñible no formato solicitado.
     '410': A páxina que estaba a buscar xa non existe.
     '422':
       content: Fallou a verificación de seguridade. Está bloqueando as cookies?
@@ -599,6 +695,7 @@ gl:
     '500':
       content: Sentímolo, pero algo do noso lado falloou.
       title: Esta páxina non é correcta
+    '503': A páxina non se puido servir debido a un fallo temporal no servidor.
     noscript_html: Para utilizar a aplicación web de Mastodon debe habilitar JavaScript. De xeito alternativo, intente unha das <a href="%{apps_path}">apps nativas</a> para Mastodon da súa plataforma.
   existing_username_validator:
     not_found: non se atopou unha usuaria local con ese alcume
@@ -622,6 +719,7 @@ gl:
     add_new: Engadir novo
     errors:
       limit: Xa acadou o número máximo de etiquetas
+    hint_html: "<strong>¿Qué son as etiquetas destacadas?</strong> Móstranse destacadas no seu perfil público e permítenlle a outras persoas ver os seus toots públicos nos que as utilizou. Son unha ferramenta moi útil para facer seguimento de traballos creativos e proxectos a longo prazo."
   filters:
     contexts:
       home: Liña temporal inicial
@@ -642,10 +740,12 @@ gl:
     developers: Desenvolvedoras
     more: Máis…
     resources: Recursos
+    trending_now: Tendencia agora
   generic:
     all: Todo
     changes_saved_msg: Cambios gardados correctamente!!
     copy: Copiar
+    no_batch_actions_available: Non hai accións en pila dispoñibles nesta páxina
     order_by: Ordenar por
     save_changes: Gardar cambios
     validation_errors:
@@ -717,9 +817,34 @@ gl:
       too_many: Non pode anexar máis de 4 ficheiros
   migrations:
     acct: nomeusuaria@dominio da nova conta
-    currently_redirecting: 'O seu perfil está listo para redirixir a:'
-    proceed: Gardar
-    updated_msg: O axuste de migración da conta actualizouse correctamente!
+    cancel: Cancelar a redirección
+    cancel_explanation: Ao cancelar a redirección reactivarás a conta actual, pero non poderás traer de volta os seguidores que se moveron a esa conta.
+    cancelled_msg: Cancelouse a redirección.
+    errors:
+      already_moved: é a mesma conta a que xa te moveches
+      missing_also_known_as: non está referenciando hacia esta conta
+      move_to_self: non pode ser a conta actual
+      not_found: non se atopou
+      on_cooldown: Estas no período de calma
+    followers_count: Seguidoras no momento da migración
+    incoming_migrations: Movendo desde unha conta diferente
+    incoming_migrations_html: Para migrar doutra conta cara esta, primeiro debes <a href="%{path}">crear un alias da conta</a>.
+    moved_msg: A túa conta está redirixindo agora a %{acct} e os teus seguidores movéronse alí.
+    not_redirecting: Neste momento a túa conta non está redirixindo cara a ningunha outra.
+    on_cooldown: Migraches recentemente a conta. Esta función estará dispoñible de novo en %{count} días.
+    past_migrations: Migracións pasadas
+    proceed_with_move: Mover seguidoras
+    redirecting_to: A conta está redirixindo cara a %{acct}.
+    set_redirect: Establecer redirección
+    warning:
+      backreference_required: Tes que configurar primeiro a nova conta para referenciar hacia esta
+      before: 'Antes de seguir, por favor lé estas notas con atención:'
+      cooldown: Tras a migración existe un período de calma durante o cal non poderás voltar a migrar de novo
+      disabled_account: Tras o cambio a túa conta actual non será totalmente usable, pero terás acceso a exportar os datos e tamén a reactivación.
+      followers: Esta acción moverá todas as túas seguidoras desde a conta actual a nova conta
+      only_redirect_html: De xeito alternativo, podes <a href="%{path}">simplemente por unha redirección no perfil</a>.
+      other_data: Non se moverán outros datos de xeito automático
+      redirect: O perfil da túa conta actualizarase cun aviso de redirección e será excluído das buscas
   moderation:
     title: Moderación
   notification_mailer:
@@ -816,10 +941,6 @@ gl:
     reply:
       proceed: Respostar
       prompt: 'Vostede quere respostar a este toot:'
-  remote_unfollow:
-    error: Fallo
-    title: Título
-    unfollowed: Deixou de seguir
   scheduled_statuses:
     over_daily_limit: Excedeu o límite de %{limit} toots programados para ese día
     over_total_limit: Excedeu o límite de %{limit} toots programados
@@ -868,6 +989,7 @@ gl:
   settings:
     account: Conta
     account_settings: Axustes da conta
+    aliases: Alcumes da conta
     appearance: Aparencia
     authorized_apps: Apps autorizadas
     back: Voltar a Mastodon
@@ -884,7 +1006,7 @@ gl:
     preferences: Preferencias
     profile: Perfil
     relationships: Seguindo e seguidoras
-    two_factor_authentication: Validar Doble Factor
+    two_factor_authentication: Validar Dobre Factor
   statuses:
     attached:
       description: 'Axenado: %{attached}'
@@ -908,6 +1030,9 @@ gl:
       private: As mensaxes non-públicas non poden ser fixadas
       reblog: Non se poden fixar as mensaxes promovidas
     poll:
+      total_people:
+        one: "%{count} persoa"
+        other: "%{count} persoas"
       total_votes:
         one: "%{count} voto"
         other: "%{count} votos"
@@ -926,6 +1051,8 @@ gl:
     pinned: Mensaxe fixada
     reblogged: promovida
     sensitive_content: Contido sensible
+  tags:
+    does_not_match_previous_name: non concorda co nome anterior
   terms:
     body_html: |
       <h2>Intimidade</h2>
@@ -1019,11 +1146,11 @@ gl:
       month: "%b %Y"
   two_factor_authentication:
     code_hint: Introducir o código xerado polo seu aplicativo de autenticación para confirmar
-    description_html: Si habilita a <strong>autenticación de doble factor</strong>, a conexión pediralle estar en posesión do seu teléfono, que xerará testemuños para poder entrar.
+    description_html: Se activa a <strong>autenticación de dobre factor</strong>, a conexión pediralle estar en posesión do seu teléfono, que creará testemuños para poder entrar.
     disable: Deshabilitar
     enable: Habilitar
-    enabled: A autenticación de doble-factor está habilitada
-    enabled_success: Habilitouse con éxito a autenticación de doble-factor
+    enabled: A autenticación de dobre-factor está activada
+    enabled_success: Activouse con éxito a autenticación de dobre-factor
     generate_recovery_codes: Xerar códigos de recuperación
     instructions_html: "<strong>Escanee este código QR en Google Authenticator ou aplicativo TOTP similar no seu teléfono</strong>. Desde agora, este aplicativo xerará testemuños que vostede deberá introducir ao conectarse."
     lost_recovery_codes: Os códigos de recuperación permítenlle recuperar o acceso a súa conta si perde o teléfono. Si perde os códigos de recuperación, pode restauralos aquí. Os seus códigos de recuperación anteriores serán invalidados.
@@ -1043,7 +1170,9 @@ gl:
         disable: Cando a súa conta está conxelada, os datos permanecen intactos, pero non pode levar a fin accións ate que se desbloquea.
         silence: Mentras a conta está limitada, só a xente que actualmente a segue verá os seus toots en este servidor, e vostede podería estar excluída de varias listas públicas. Porén, outras persoas poderíana seguila de xeito manual.
         suspend: A súa conta foi suspendida, e todos os seus toots e medios subidos foron eliminados de este servidor de xeito irreversible, e dos servidores onde tivese seguidoras.
+      get_in_touch: Pode responder a este correo para contactar coa administración de %{instance}.
       review_server_policies: Revisar políticas do servidor
+      statuses: 'En concreto, para:'
       subject:
         disable: A súa conta %{acct} foi conxelada
         none: Aviso para %{acct}
@@ -1074,7 +1203,7 @@ gl:
   users:
     follow_limit_reached: Non pode seguir a máis de %{limit} persoas
     invalid_email: O enderezo de correo non é válido
-    invalid_otp_token: Código de doble-factor non válido
+    invalid_otp_token: O código do segundo factor non é válido
     otp_lost_help_html: Si perde o acceso a ambos, pode contactar con %{email}
     seamless_external_login: Está conectado a través de un servizo externo, polo que os axustes de contrasinal e correo-e non están dispoñibles.
     signed_in_as: 'Rexistrada como:'
diff --git a/config/locales/he.yml b/config/locales/he.yml
index 5e50f738df81679c1b18cf72ef4d8e771cc6f593..50db571dccd45726875bb077bd7b1897ad301866 100644
--- a/config/locales/he.yml
+++ b/config/locales/he.yml
@@ -9,10 +9,6 @@ he:
     contact_missing: ללא הגדרה
     contact_unavailable: לא רלוונטי/חסר
     documentation: תיעוד
-    extended_description_html: |
-      <h3>מקום טוב לכללים</h3>
-      <p>התיאור המורחב טרם הוגדר.</p>
-    generic_description: "%{domain} הוא שרת אחד בתוך הרשת"
     hosted_on: מסטודון שיושב בכתובת %{domain}
     learn_more: מידע נוסף
     source_code: קוד מקור
@@ -177,13 +173,6 @@ he:
         title: תיאור אתר מורחב
       site_title: כותרת האתר
       title: הגדרות אתר
-    subscriptions:
-      callback_url: קישורית Callback
-      confirmed: מאושר
-      expires_in: פג תוקף ב-
-      last_delivery: משלוח אחרון
-      title: מנוי WebSub
-      topic: נושא
     title: ניהול
   application_mailer:
     settings: 'שינוי הגדרות דוא"ל: %{link}'
@@ -219,14 +208,17 @@ he:
       x_months: "%{count} חודשים"
       x_seconds: "%{count} שניות"
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': חסרות לך הרשאות לצפיה בעמוד זה.
     '404': הדף המבוקש לא קיים.
+    '406': This page is not available in the requested format.
     '410': הדף המבוקש כבר לא קיים.
     '422':
       content: בדיקת אבטחה נכשלה. החסמת עוגיותיך מפנינו?
       title: בדיקת בטיחות נכשלה
     '429': הוחנק
     '500': 
+    '503': The page could not be served due to a temporary server failure.
   exports:
     blocks: רשימת חסימות
     follows: רשימת נעקבים
diff --git a/config/locales/hr.yml b/config/locales/hr.yml
index a4fe6205538722a87471e3240a28fc58bdce3afe..67d83525b3d80ef26994a78431f1c9e71a13c421 100644
--- a/config/locales/hr.yml
+++ b/config/locales/hr.yml
@@ -44,12 +44,15 @@ hr:
       x_months: "%{count}mj"
       x_seconds: "%{count}sek"
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': You don't have permission to view this page.
     '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
     '410': The page you were looking for doesn't exist here anymore.
     '422': 
     '429': Throttled
     '500': 
+    '503': The page could not be served due to a temporary server failure.
   exports:
     blocks: Blokirao si
     follows: Slijediš
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index d771b96832fd3df682c49e2aaf33eeb958a5e4e3..af7048ebb72ec5c93e0fd5d133fb15c3055dbdce 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -10,20 +10,19 @@ hu:
     api: API
     apps: Mobil appok
     apps_platforms: Használd a Mastodont iOS-ről, Androidról vagy más platformról
-    browse_directory: Böngészd a profil adatbázist és szűrj érdeklődési kör szerint
+    browse_directory: Böngészd a profilokat és szűrj érdeklődési körre
     browse_public_posts: Nézz bele a Mastodon élő adatfolyamába
     contact: Kapcsolat
     contact_missing: Nincs megadva
     contact_unavailable: N/A
     discover_users: Találj meg másokat
     documentation: Dokumentáció
-    extended_description_html: |
-      <h3>Ez itt a szabályzat helye</h3>
-      <p>Még nem állítottál be bővebb leírást.</p>
     federation_hint_html: Egy %{instance} fiókkal bármely más Mastodon szerveren vagy a föderációban lévő felhasználót követni tudsz.
-    generic_description: "%{domain} csak egy a számtalan szerver közül a föderációban"
     get_apps: Próbálj ki egy mobil appot
     hosted_on: "%{domain} Mastodon szerver"
+    instance_actor_flash: 'Ez a fiók egy virtuális szereplő, mely magát a szervert reprezentálja, nem egy felhasználót. Ez a föderáció támogatására készült, ezért nem szabad blokkolni, hacsak egy teljes szervert nem akarsz kitiltani, amire persze a domain blokkolása jobb megoldás.
+
+'
     learn_more: Tudj meg többet
     privacy_policy: Adatvédelmi szabályzat
     see_whats_happening: Nézd, mi történik
@@ -35,6 +34,14 @@ hu:
     status_count_before: eddig
     tagline: Kövess barátokat és találj újakat
     terms: Felhasználási feltételek
+    unavailable_content: A tartalom nem elérhető
+    unavailable_content_description:
+      domain: Szerver
+      reason: 'Indok:'
+      rejecting_media: A szerverről származó médiafájlok nem kerülnek feldolgozásra, és nem jelennek meg miniatűrök, amelyek kézi átkattintást igényelnek a másik szerverre.
+      silenced: A szerver hozzászólásai csak a saját hírvonalon jelennek meg, ha követik a szerzőt.
+      suspended: Nem fogsz tudni követni senkit ebből a szerverből, és nem kerül feldolgozásra vagy tárolásra a tőle származó adat, és nincs adatcsere.
+    unavailable_content_html: A Mastodon általában mindenféle tartalomcserét és interakciót lehetővé tesz bármelyik szerverrel a fediverzumban. Ezek azok a kivételek, melyek a mi szerverünkön érvényben vannak.
     user_count_after:
       one: felhasználónk
       other: felhasználónk
@@ -42,6 +49,8 @@ hu:
     what_is_mastodon: Mi a Mastodon?
   accounts:
     choices_html: "%{name} választásai:"
+    endorsements_hint: A webes felületen jóváhagyhatod a követett embereket, és itt jelennek meg.
+    featured_tags_hint: Szerepeltethetsz bizonyos hashtageket, melyek itt jelennek majd meg.
     follow: Követés
     followers:
       one: Követő
@@ -53,6 +62,7 @@ hu:
     media: Média
     moved_html: "%{name} ide költözött: %{new_profile_link}"
     network_hidden: Ez az információ nem elérhető
+    never_active: Soha
     nothing_here: Nincs itt semmi!
     people_followed_by: "%{name} követettjei"
     people_who_follow: "%{name} követői"
@@ -183,6 +193,7 @@ hu:
       username: Felhasználónév
       warn: Figyelmeztetés
       web: Web
+      whitelisted: Engedélyező-listán
     action_logs:
       actions:
         assigned_to_self_report: "%{name} a %{target} bejelentést magához rendelte"
@@ -218,19 +229,24 @@ hu:
       deleted_status: "(törölt tülk)"
       title: Audit napló
     custom_emojis:
+      assign_category: Kategóriák
       by_domain: Domain
       copied_msg: Sikeresen létrehoztuk az emoji helyi másolatát
       copy: Másolás
       copy_failed_msg: Emoji helyi másolatának létrehozása sikertelen
+      create_new_category: Új kategória létrehozása
       created_msg: Emoji létrehozva!
       delete: Törlés
       destroyed_msg: Emoji törlése sikeres!
       disable: Letiltás
+      disabled: Letiltva
       disabled_msg: Emoji letiltva
       emoji: Emoji
       enable: Engedélyezés
+      enabled: Engedélyezve
       enabled_msg: Emoji engedélyezve
       image_hint: PNG (maximális méret 50KB)
+      list: Listázás
       listed: Listázva
       new:
         title: Új egyedi emoji hozzáadása
@@ -238,11 +254,14 @@ hu:
       shortcode: Rövidítés
       shortcode_hint: Legalább két karakter, csak betűk, számok és alsóvonás
       title: Egyedi emojik
+      uncategorized: Nem kategorizált
+      unlist: Elrejtés a listáról
       unlisted: Nincs listázva
       update_failed_msg: Nem sikerült frissíteni az emojit
       updated_msg: Emoji sikeresen frissítve!
       upload: Feltöltés
     dashboard:
+      authorized_fetch_mode: Áttöltő üzemmód engedélyezve
       backlog: hátralévő feladatok
       config: Beállítások
       feature_deletions: Fióktörlések
@@ -250,10 +269,13 @@ hu:
       feature_profile_directory: Profil adatbázis
       feature_registrations: Regisztráció
       feature_relay: Föderációs relé
+      feature_spam_check: Anti-spam
       feature_timeline_preview: Idővonal betekintő
       features: Funkciók
       hidden_service: Föderáció rejtett szolgáltatásokkal
       open_reports: nyitott bejelentések
+      pending_tags: hashtag engedélyezésre vár
+      pending_users: felhasználó engedélyezésre vár
       recent_users: Legutóbbi felhasználók
       search: Keresés teljes szövegben
       single_user_mode: Egyfelhasználós mód
@@ -265,11 +287,18 @@ hu:
       week_interactions: interakció ezen a héten
       week_users_active: aktív ezen a héten
       week_users_new: felhasználó ezen a héten
+      whitelist_mode: Engedélyező-lista mód
+    domain_allows:
+      add_new: Domain engedélyezése
+      created_msg: A domain rákerült az engedélyező-listára
+      destroyed_msg: A domain lekerült az engedélyező-listáról
+      undo: Eltávolítás az engedélyező-listáról
     domain_blocks:
       add_new: Új tiltott domain hozzáadása
       created_msg: A domain-tiltás feldolgozása folyamatban
       destroyed_msg: A domain tiltása feloldva
       domain: Domain
+      edit: Domain tiltás szerkesztése
       existing_domain_block_html: A %{name} domainen már szorosabb korlátokat állítottál be, először <a href="%{unblock_url}">oldd fel a tiltást</a>.
       new:
         create: Tiltás létrehozása
@@ -280,6 +309,10 @@ hu:
           silence: Némítás
           suspend: Felfüggesztés
         title: Új domain tiltása
+      private_comment: Privát megjegyzés
+      private_comment_hint: Megjegyzés domain tiltásával kapcsolatban belső használatra, a többi moderátor részére.
+      public_comment: Nyilvános megjegyzés
+      public_comment_hint: Megjegyzés domain tiltásával kapcsolatban a nyilvánosság számára, ha a domainek tiltólistája egyébként látható.
       reject_media: Médiafájlok elutasítása
       reject_media_hint: Eltávolítja a helyben tárolt médiafájlokat és a továbbiakban letiltja az új médiafájlok letöltését. Felfüggesztett fiókok esetében irreleváns opció
       reject_reports: Bejelentések elutasítása
@@ -299,6 +332,7 @@ hu:
         title: "%{domain} domain tiltásának feloldása"
         undo: Visszavonás
       undo: Domain tiltásának visszavonása
+      view: Domain tiltásának megtekintése
     email_domain_blocks:
       add_new: Új hozzáadása
       created_msg: E-mail domain sikeresen hozzáadva a feketelistához
@@ -322,6 +356,8 @@ hu:
         all: Mind
         limited: Korlátozott
         title: Moderáció
+      private_comment: Privát megjegyzés
+      public_comment: Nyilvános megjegyzés
       title: Föderáció
       total_blocked_by_us: Általunk letiltott
       total_followed_by_them: Általuk követett
@@ -351,6 +387,7 @@ hu:
       pending: Várakozás a relé jóváhagyására
       save_and_enable: Mentés és engedélyezés
       setup: Relé kapcsolat felállítása
+      signatures_not_enabled: A relék nem fognak jól működni, amíg a biztonságos mód vagy az engedélyező listás mód aktív
       status: Állapot
       title: Relék
     report_notes:
@@ -399,6 +436,16 @@ hu:
       custom_css:
         desc_html: Változtasd meg a kinézetet ebben a CSS-ben, mely minden oldalon be fog töltődni
         title: Egyedi CSS
+      default_noindex:
+        desc_html: Olyan felhasználókat érinti, akik nem módosították ezt a beállítást
+        title: Alapértelmezésként ne indexeljék a keresők a felhasználóinkat
+      domain_blocks:
+        all: Mindenkinek
+        disabled: Senkinek
+        title: Domain tiltások megjelenitése
+        users: Bejelentkezett helyi felhasználóknak
+      domain_blocks_rationale:
+        title: Mutasd meg az indokolást
       hero:
         desc_html: A kezdőoldalon látszik. Legalább 600x100px méret javasolt. Ha nincs beállítva, a szerver bélyegképet használjuk
         title: Hősi kép
@@ -449,6 +496,9 @@ hu:
         desc_html: Megírhatod saját adatkezelési szabályzatodat, felhasználási feltételeidet vagy más hasonló jellegű dokumentumodat. HTML-tageket is használhatsz
         title: Egyedi felhasználási feltételek
       site_title: A szerver neve
+      spam_check_enabled:
+        desc_html: A Mastodon automatikusan elnémíthatja és bejelentheti azokat a fiókokat, akik rendszeresen kéretlen üzeneteket küldenek. Persze lehetnek tévedések is.
+        title: Automatikus anti-spam
       thumbnail:
         desc_html: OpenGraph-os és API-s előnézetekben használjuk. Ajánlott mérete 1200x630 pixel
         title: A szerver bélyegképe
@@ -456,12 +506,16 @@ hu:
         desc_html: Nyilvános idővonal megjelenítése a főoldalon
         title: Idővonal előnézete
       title: Webhely beállításai
+      trends:
+        desc_html: Előzetesen engedélyezett és most trendi hashtagek nyilvános mutatása
+        title: Trendi hashtagek
     statuses:
       back_to_account: Vissza a fiók oldalára
       batch:
         delete: Törlés
         nsfw_off: Szenzitív megjelölés törlése
         nsfw_on: Megjelölés szenzitív tartalomként
+      deleted: Törölve
       failed_to_execute: Végrehajtás sikertelen
       media:
         title: Média
@@ -469,21 +523,24 @@ hu:
       no_status_selected: Nem változtattunk meg semmit, mert semmi sem volt kiválasztva
       title: Felhasználó tülkjei
       with_media: Médiafájlokkal
-    subscriptions:
-      callback_url: Callback URL
-      confirmed: Megerősítve
-      expires_in: Elévül
-      last_delivery: Utolsó kézbesítés
-      title: WebSub
-      topic: Téma
     tags:
-      accounts: Fiókok
-      hidden: Rejtett
-      hide: Ne jelenjen meg a profilok adatbázisában
-      name: Hashtag
+      accounts_today: Egyedi használat a mai napon
+      accounts_week: Egyedi használat ezen a héten
+      breakdown: Mai használat bontása forrás szerint
+      context: Környezet
+      directory: Katalógusban
+      in_directory: "%{count} a katalógusban"
+      last_active: Utoljára aktív
+      most_popular: Legnépszerűbb
+      most_recent: Legutóbbi
+      name: Címke
+      review: Engedélyezés állapota
+      reviewed: Engedélyezett
       title: Hashtagek
-      unhide: Jelenjen meg a profilok adatbázisában
-      visible: Látható
+      trending_right_now: Most trendi
+      unique_uses_today: "%{count} mai tülkölés"
+      unreviewed: Még nem engedélyezett
+      updated_msg: A hashtag beállításokat sikeresen frissítettük
     title: Karbantartás
     warning_presets:
       add_new: Új hozzáadása
@@ -499,11 +556,21 @@ hu:
       body: "%{reporter} jelentette: %{target}"
       body_remote: Valaki a %{domain} domainről jelentette %{target}
       subject: 'Új jelentés az alábbi szerveren: %{instance} (#%{id})'
+    new_trending_tag:
+      body: 'A #%{name} hashtag trendi a mai napon, de még nem engedélyeztük eddig. Nem mutatjuk meg nyilvánosan, hacsak nem engedélyezed. Csak simán mentsd az űrlapot, ha soha többé nem akarsz erről a hashtagről hallani.'
+      subject: Új hashtag (#%{name}) engedélyezésre vár a %{instance} szerveren
+  aliases:
+    add_new: Alias készítése
+    created_msg: Elkészült az új aliasod. Most már elkezdheted a költöztetést a régi fiókból.
+    deleted_msg: Sikeresen eltávolítottad az aliast. A fiókról erre a fiókra való áttérés már nem lehetséges.
+    hint_html: Ha másik fiókról kívánsz átlépni erre a fiókra, itt létrehozhatsz egy aliast, amelyre szükség van, mielőtt folytathatod a követők áthelyezését a régi fiókból erre. Ez az áthelyezés önmagában <strong>ártalmatlan és visszafordítható</strong> folyamat. <strong>A fiók áttelepítését a régi fiókból indul el. </strong>
+    remove: Alias szétkapcsolása
   appearance:
     advanced_web_interface: Haladó webes felület
     advanced_web_interface_hint: 'Ha szeretnéd, a teljes képernyőszélességet felhasználhatod. A haladó webes felülettel különböző oszlopokat állíthatsz be, hogy egyszerre annyi infót láthass, amennyit csak akarsz: Saját idővonal, értesítések, föderációs idővonal, bármennyi lista vagy hashtag.'
     animations_and_accessibility: Animáció és akadálymentesítés
     confirmation_dialogs: Megerősítő párbeszédablakok
+    discovery: Felfedezés
     sensitive_content: Szenzitív tartalom
   application_mailer:
     notification_preferences: E-mail beállítások módosítása
@@ -524,9 +591,13 @@ hu:
     apply_for_account: Meghívó kérése
     change_password: Jelszó
     checkbox_agreement_html: Egyetértek a <a href="%{rules_path}" target="_blank">szerver szabályaival</a> és a <a href="%{terms_path}" target="_blank">felhasználási feltételekkel</a>
-    confirm_email: E-mail megerősítése
+    checkbox_agreement_without_rules_html: Egyetértek a <a href="%{terms_path}" target="_blank">felhasználási feltételekkel</a>
     delete_account: Felhasználói fiók törlése
     delete_account_html: Felhasználói fiókod törléséhez <a href="%{path}">kattints ide</a>. A rendszer újbóli megerősítést fog kérni.
+    description:
+      prefix_invited_by_user: "@%{name} meghív téged, hogy csatlakozz erre a Mastodon szerverre!"
+      prefix_sign_up: Regisztrláj még ma a Mastodonra!
+      suffix: Egy fiókkal követhetsz másokat, tülkölhetsz, eszmét cserélhetsz más Mastodon szerverek felhasználóival!
     didnt_get_confirmation: Nem kaptad meg a megerősítési lépéseket?
     forgot_password: Elfelejtetted a jelszavad?
     invalid_reset_password_token: A jelszó-visszaállítási kulcs nem megfelelő vagy lejárt. Kérlek generálj egy újat.
@@ -544,6 +615,16 @@ hu:
     reset_password: Jelszó visszaállítása
     security: Biztonság
     set_new_password: Új jelszó beállítása
+    setup:
+      email_below_hint_html: Ha az alábbi e-mail cím nem megfelelő, itt megváltoztathatod és kaphatsz egy új igazoló e-mailt.
+      email_settings_hint_html: A visszaigazoló e-mailt elküldtük ide %{email}. Ha az e-mail cím nem megfelelő, megváltoztathatod a fiókod beállításainál.
+      title: Beállítás
+    status:
+      account_status: Fiók állapota
+      confirming: Várakozás a visszaigazolásra.
+      functional: A fiókod teljesen működőképes.
+      pending: A jelentkezésed engedélyezésre vár. Ez eltarthat egy ideig. Kapsz egy e-mailt, ha az elbírálás megtörtént.
+      redirecting_to: A fiókod inaktív, mert jelenleg ide %{acct} van átirányítva.
     trouble_logging_in: Problémád van a bejelentkezéssel?
   authorize_follow:
     already_following: Már követed ezt a felhasználót
@@ -556,6 +637,11 @@ hu:
       return: Visszatérés a felhasználó profiloldalára
       web: Megtekintés a weben
     title: "%{acct} követése"
+  challenge:
+    confirm: Folytatás
+    hint_html: "<strong>Hasznos:</strong> Nem fogjuk megint a jelszavadat kérdezni a következő órában."
+    invalid_password: Érvénytelen jelszó
+    prompt: Add meg a jelszót a folytatáshoz
   datetime:
     distance_in_words:
       about_x_hours: "%{count}ó"
@@ -571,26 +657,33 @@ hu:
       x_months: "%{count}h"
       x_seconds: "%{count}mp"
   deletes:
-    bad_password_msg: Haha, hekker! Helytelen jelszó
+    challenge_not_passed: A beírt információ helytelen
     confirm_password: Személyazonosságod megerősítéséhez írd be a jelenlegi jelszavad
-    description_html: Ezzel <strong>véglegesen és visszafordíthatatlanul</strong> törlöd minden tartalmadat és deaktiválod a fiókodat. A felhasználónevedet megtartjuk, hogy megakadályozzuk a neveddel történő jövőbeni visszaélések lehetőségét.
+    confirm_username: Add meg a felhasználói nevedet a jóváhagyáshoz
     proceed: Felhasználói fiók törlése
     success_msg: Felhasználói fiókod sikeresen töröltük
-    warning_html: Csak azt tudjuk garantálni, hogy az általad létrehozott tartalmat erről a szerverről töröljük. Ha egyes tartalmaidat sokan megosztották, valószínűleg marad nyomuk a megosztások miatt. Nem fogjuk tudni frissíteni azon szerverek adatbázisát, amelyek nem kapcsolódnak a föderációhoz vagy amelyek leiratkoztak a tülkjeidről.
-    warning_title: Szórt tartalmak elérése
+    warning:
+      before: 'Mielőtt továbbmész, kérlek olvasd el ezt alaposan:'
+      caches: Más szerverek által cache-elt tartalmak még megmaradhatnak
+      data_removal: A tülkjeid és minden más adatod véglegesen törlődni fog
+      email_change_html: <a href="%{path}">Megváltoztathatod az email címed</a> a fiókod törlése nélkül
+      email_contact_html: Ha még mindig nem érkezik meg, emailezhetsz ide <a href="mailto:%{email}">%{email}</a> segítségért
+      email_reconfirmation_html: Ha nem kaptad meg a megerősítő emailt, <a href="%{path}">itt újrakérheted</a>
+      irreversible: Nem fogod tudni visszaállítani vagy újraaktiválni a fiókodat
+      more_details_html: A részletekért nézd meg az <a href="%{terms_path}">adatvédelmi szabályzatot</a>.
+      username_available: A fiókod ismét elérhetővé válik
+      username_unavailable: A fiókod elérhetetlen marad
   directories:
     directory: Profilok
-    enabled: Szerepelsz a profil adatbázisban.
-    enabled_but_waiting: Engedélyezted, hogy szerepelj a profil adatbázisban, de még nincs elegendő követőd (%{min_followers}) ehhez.
     explanation: Találj másokra érdeklődésük alapján
     explore_mastodon: "%{title} felfedezése"
-    how_to_enable: Nem engedélyezted a profil adatbázisban való megjelenésed. Engedélyezheted alább. Használj hashtageket az életrajzodban, hogy az ezekhez tartozó listákba bekerülj!
-    people:
-      one: "%{count} ember"
-      other: "%{count} ember"
+  domain_validator:
+    invalid_domain: nem egy valódi domain név
   errors:
+    '400': A küldött kérés érvénytelen vagy hibás volt.
     '403': Nincs jogosultságod az oldal megtekintéséhez.
     '404': Az általad keresett oldal nem található.
+    '406': Ez az oldal a kért formátumban nem áll rendelkezésre.
     '410': Az általad keresett oldal már nem létezik.
     '422':
       content: Megerősítés sikertelen. Nem tiltottad le esetleg a sütiket?
@@ -599,6 +692,7 @@ hu:
     '500':
       content: Sajnáljuk, valami hiba történt a mi oldalunkon.
       title: Az oldal nem megfelelő
+    '503': Az oldalt nem tudjuk megmutatni átmeneti szerverprobléma miatt.
     noscript_html: A Mastodon webalkalmazás használatához engedélyezned kell a JavaScriptet. A másik megoldás, hogy kipróbálsz egy platformodnak megfelelő <a href="%{apps_path}">alkalmazást</a>.
   existing_username_validator:
     not_found: ezzel a névvel nem találtunk helyi felhasználót
@@ -622,6 +716,7 @@ hu:
     add_new: Új hozzáadása
     errors:
       limit: Már kiemelted a maximálisan engedélyezett számú hashtaget
+    hint_html: "<strong>Mik a kiemelt hashtagek?</strong> Ezek állandóan megjelennek a nyilvános profilodon és lehetővé teszik, hogy mások kifejezetten az ezekhez tartozó tülkjeidet böngésszék. Jó eszköz ez kreatív munkák vagy hosszútávú projektek nyomonkövetésére."
   filters:
     contexts:
       home: Saját idővonal
@@ -642,10 +737,12 @@ hu:
     developers: Fejlesztőknek
     more: Többet…
     resources: Segédanyagok
+    trending_now: Most trendi
   generic:
     all: Mind
     changes_saved_msg: A változásokat elmentettük!
     copy: Másolás
+    no_batch_actions_available: Ezen az oldalon nem elérhetőek kötegelt műveletek
     order_by: Rendezés
     save_changes: Változások mentése
     validation_errors:
@@ -717,9 +814,34 @@ hu:
       too_many: Maximum négy fájlt csatolhatsz a tülkhöz
   migrations:
     acct: Az új fiók felhasznalonev@domain formátumban
-    currently_redirecting: 'A profilod az alábbi fiókra van átirányítva:'
-    proceed: Mentés
-    updated_msg: Fiókod átirányítási beállításait sikeresen mentettük!
+    cancel: Átirányítás törlése
+    cancel_explanation: Az átirányítás törlése reaktiválja a fiókodat, de nem fogja visszahozni azokat a követőidet, akik közben a másik fiókhoz kerültek át.
+    cancelled_msg: Az átirányítást sikeresen töröltük.
+    errors:
+      already_moved: ugyanaz a fiók, ahová már elköltöztél
+      missing_also_known_as: nem hivatkozza vissza ezt a fiókot
+      move_to_self: nem lehet az aktuális fiók
+      not_found: nem található
+      on_cooldown: Még tart a türelmi idő
+    followers_count: Követő a költözéskor
+    incoming_migrations: Más fiókból költöző
+    incoming_migrations_html: Ahhoz, hogy egy másik fiókból ebbe költözz, először <a href="%{path}">hozz létre egy fiók aliast</a>.
+    moved_msg: A fiókod mostantól ide %{acct} irányít át, a követőidet átköltöztetjük.
+    not_redirecting: A fiókod nincs átirányítva jelenleg sehová sem.
+    on_cooldown: Nemrég költöztetted a fiókod. Ez a funkció %{count} nap múlva lesz megint elérhető.
+    past_migrations: Eddigi költözések
+    proceed_with_move: Követők átköltöztetése
+    redirecting_to: A fiókod át van irányítva ide %{acct}.
+    set_redirect: Átirányítás beállítása
+    warning:
+      backreference_required: Az új fiókot először be kell úgy állítani, hogy ezt visszahivatkozza
+      before: 'Mielőtt továbbmész, olvasd el ezeket kérlek figyelmesen:'
+      cooldown: A költözés után van egy türelmi idő, mely alatt nem tudsz majd újra költözni
+      disabled_account: A jelenlegi fiókod nem lesz teljesen használható ezután. Viszont elérhető lesz majd az adatexport funkció, valamint a reaktiválás is.
+      followers: Ez a művelet az összes követődet a jelenlegi fiókról az újra fogja költöztetni
+      only_redirect_html: Az is lehetséges, hogy <a href="%{path}">csak átirányítást raksz a profilodra</a>.
+      other_data: Más adatot nem fogunk automatikusan mozgatni
+      redirect: A jelenlegi fiókod profiljára átirányításról szóló figyelmeztetést rakunk, valamint már nem fogjuk mutatni a keresésekben
   moderation:
     title: Moderáció
   notification_mailer:
@@ -816,10 +938,6 @@ hu:
     reply:
       proceed: Válaszadás
       prompt: 'Erre a tülkre szeretnél válaszolni:'
-  remote_unfollow:
-    error: Hiba
-    title: Cím
-    unfollowed: Nem követett
   scheduled_statuses:
     over_daily_limit: Túllépted az időzített tülkökre vonatkozó napi limitet (%{limit})
     over_total_limit: Túllépted az időzített tülkökre vonatkozó limitet (%{limit})
@@ -868,6 +986,7 @@ hu:
   settings:
     account: Fiók
     account_settings: Fiók beállítások
+    aliases: Fiók aliasok
     appearance: Megjelenés
     authorized_apps: Jóváhagyott alkalmazások
     back: Vissza a Mastodonhoz
@@ -908,6 +1027,9 @@ hu:
       private: Csak nyilvános tülköt tűzhetsz ki
       reblog: Megtolt tülköt nem tudsz kitűzni
     poll:
+      total_people:
+        one: "%{count} személy"
+        other: "%{count} személy"
       total_votes:
         one: "%{count} szavazat"
         other: "%{count} szavazat"
@@ -924,25 +1046,27 @@ hu:
       unlisted_long: Mindenki látja, de a nyilvános idővonalakon nem jelenik meg
   stream_entries:
     pinned: Kitűzött tülk
-    reblogged: megtolt
+    reblogged: megtolta
     sensitive_content: Szenzitív tartalom
+  tags:
+    does_not_match_previous_name: nem illeszkedik az előző névvel
   terms:
     body_html: |
       <h2>Adatvédelmi nyilatkozat</h2>
       <h3 id="collect">Milyen adatokat gyűjtünk?</h3>
 
       <ul>
-      <li><em>Alapvető fiókadatok</em>: Ha regisztrálsz ezen a szerveren, kérhetünk tőled felhasználói nevet, e-mail címet és jelszót is. Megadhatsz magadról egyéb profil információt, mint megjelenítendő név, bemutatkozás, feltölthetsz profilképet, háttérképet. A felhasználói neved, megjelenítendő neved, bemutatkozásod, profil képed és háttér képed mindig nyilvánosak mindenki számára.</li>
-      <li><em>Tülkök (posztok), követések, más nyilvános adatok</em>: Az általad követett emberek listája nyilvános. Ugyanez igaz a te követőidre is. Ha küldesz egy üzenetet, ennek az idejét eltároljuk azzal az alkalmazással együtt, melyből az üzenetet küldted. Az üzenetek tartalmazhatnak média csatolmányt, képeket, videókat. A nyilvános tülkök (posztok) bárki számára elérhetőek. Ha egy tülköt kiemelsz a profilodon, az is nyilvánossá válik. Amikor a tülkjeidet a követőidnek továbbítjuk, a poszt más szerverekre is kerülhet, melyeken így másolatok képződhetnek. Ha törölsz tülköket, ez is továbbítódik a követőid felé. A megtolás (reblog) és kedvencnek jelölés művelete is mindig nyilvános.</li>
-      <li><em>Közvetlen üzenetek és csak követőknek szánt tülkök</em>: Minden tülk a szerveren tárolódik. A csak követőknek szánt tülköket a követőidnek és az ezekben megemlítetteknek továbbítjuk, míg a közvetlen üzeneteket kizárólag az ebben megemlítettek kapják. Néhány esetben ez azt jelenti, hogy ezek más szerverekre is továbbítódnak, így ott másolatok keletkezhetnek. Jóhiszeműen feltételezzük, hogy más szerverek is hasonlóan járnak el, mikor ezeket az üzeneteket csak az arra jogosultaknak mutatják meg. Ugyanakkor ez nem feltétlenül igaz. Ezért érdemes megnézni azokat a szervereket, melyeken követőid vannak. Be tudod állítani, hogy minden követési kérelmet jóvá kelljen hagynod. <em>Tartsd észben, hogy a szerver üzemeltetői láthatják az üzeneteket</em>, illetve a fogadók képernyőképet, másolatot készíthetnek belőlük, vagy újraoszthatják őket.<em>Ne ossz meg veszélyes információt a Mastodon hálózaton!</em></li>
-      <li><em>IP címek és egyéb metaadatok</em>: Bejelentkezéskor letároljuk a használt böngésződet és IP címedet. Mindent rögzített munkamenet elérhető és visszavonható a beállítások között. A legutolsó IP címet maximum 12 hónapig tárolunk. Egyéb szerver logokat is megtarthatunk, melyek HTTP kérésenként is tárolhatják az IP címedet.</li>
+      <li><em>Alapvető fiókadatok</em>: Ha regisztrálsz ezen a szerveren, kérhetünk tőled felhasználói nevet, e-mail címet és jelszót is. Megadhatsz magadról egyéb profil információt, megjelenítendő nevet, bemutatkozást, feltölthetsz profilképet, háttérképet. A felhasználói neved, megjelenítendő neved, bemutatkozásod, profil képed és háttér képed mindig nyilvánosak mindenki számára.</li>
+      <li><em>Tülkök (posztok), követések, más nyilvános adatok</em>: Az általad követett emberek listája nyilvános. Ugyanez igaz a te követőidre is. Ha küldesz egy üzenetet, ennek az idejét eltároljuk azzal az alkalmazással együtt, melyből az üzenetet küldted. Az üzenetek tartalmazhatnak média csatolmányt, képeket, videókat. A nyilvános tülkök (posztok) bárki számára elérhetőek. Ha egy tülköt kiemelsz a profilodon, az is nyilvánossá válik. Amikor a tülkjeidet a követőidnek továbbítjuk, a poszt más szerverekre is átkerülhet, melyeken így másolatok képződhetnek. Ha törölsz tülköket, ez is továbbítódik a követőid felé. A megtolás (reblog) és kedvencnek jelölés művelete is mindig nyilvános.</li>
+      <li><em>Közvetlen üzenetek és csak követőknek szánt tülkök</em>: Minden tülk a szerveren tárolódik. A csak követőknek szánt tülköket a követőidnek és az ezekben megemlítetteknek továbbítjuk, míg a közvetlen üzeneteket kizárólag az ebben megemlítettek kapják. Néhány esetben ez azt jelenti, hogy ezek más szerverekre is továbbítódnak, így ott másolatok keletkezhetnek. Jóhiszeműen feltételezzük, hogy más szerverek is hasonlóan járnak el, mikor ezeket az üzeneteket csak az arra jogosultaknak mutatják meg. Ugyanakkor ez nem feltétlenül igaz. Érdemes ezért megvizsgálni azokat a szervereket, melyeken követőid vannak. Be tudod állítani, hogy minden követési kérelmet jóvá kelljen hagynod. <em>Tartsd észben, hogy a szerver üzemeltetői láthatják az üzeneteket</em>, illetve a fogadók képernyőképet, másolatot készíthetnek belőlük, vagy újraoszthatják őket. <em>Ne ossz meg veszélyes információt a Mastodon hálózaton!</em></li>
+      <li><em>IP címek és egyéb metaadatok</em>: Bejelentkezéskor letároljuk a használt böngésződet és IP címedet. Minden rögzített munkamenet elérhető és visszavonható a beállítások között. Az utoljára rögzített IP címet maximum 12 hónapig tároljuk. Egyéb szerver logokat is megtarthatunk, melyek HTTP kérésenként is tárolhatják az IP címedet.</li>
       </ul>
 
       <hr class="spacer" />
 
       <h3 id="use">Mire használjuk az adataidat?</h3>
 
-      <p>Bármely tőled begyűjtött adatot a következő célokra használhatjuk:</p>
+      <p>Bármely tőled begyűjtött adatot a következő célokra használhatjuk fel:</p>
 
       <ul>
       <li>Mastodon alapfunkcióinak biztosítása: Csak akkor léphetsz kapcsolatba másokkal, ha be vagy jelentkezve. Pl. követhetsz másokat a saját, személyre szabott idővonaladon.</li>
@@ -960,14 +1084,14 @@ hu:
 
       <h3 id="data-retention">Mik az adatmegőrzési szabályaink?</h3>
 
-      <p>Jóhiszeműen járunk el, hogy:</p>
+      <p>Mindent megteszünk, hogy:</p>
 
       <ul>
-      <li>A szerver logokat, melyek kérésenként tartalmazzák a felhasználó IP címét maximum 90 napig tartjuk meg.</li>
-      <li>A regisztrált felhasználók IP címeikkel összekötő adatokat maximum 12 hónapig tartjuk meg.</li>
+      <li>A szerver logokat, melyek kérésenként tartalmazzák a felhasználó IP címét maximum 90 napig tartsuk meg.</li>
+      <li>A regisztrált felhasználókat IP címeikkel összekötő adatokat maximum 12 hónapig tartsuk meg.</li>
       </ul>
 
-      <p>Kérhetsz archívot minden tárolt adatodról, tülkjeidről, média fájljaidról, profil- és háttér képedről.</p>
+      <p>Kérhetsz mentést minden tárolt adatodról, tülködről, média fájlodról, profil- és háttér képedről.</p>
 
       <p>Bármikor visszaállíthatatlanul le is törölheted a fiókodat.</p>
 
@@ -983,7 +1107,7 @@ hu:
 
       <h3 id="disclose">Átadunk bármilyen adatot harmadik személynek?</h3>
 
-      <p>Az azonosításodra alkalmazható adatokat nem adjuk el, nem kereskedünk vele, nem adjuk át külső szereplőnek. Ez nem foglalja magába azon harmadik személyeket, aki az üzemeltetésben, felhasználók kiszolgálásban és a tevékenységünkben segítenek, de csak addig, amíg ők is elfogadják, hogy ezeket az adatokat bizalmasan kezelik. Akkor is átadhatjuk ezeket az adatokat, ha erre hitünk szerint törvény kötelez minket, ha betartatjuk az oldalunk szabályzatát vagy megvédjük a saját vagy mások személyiségi jogait, tulajdonát, biztonságát.</p>
+      <p>Az azonosításodra alkalmazható adatokat nem adjuk el, nem kereskedünk vele, nem adjuk át külső szereplőnek. Ez nem foglalja magában azon harmadik személyeket, aki az üzemeltetésben, felhasználók kiszolgálásban és a tevékenységünkben segítenek, de csak addig, amíg ők is elfogadják, hogy ezeket az adatokat bizalmasan kezelik. Akkor is átadhatjuk ezeket az adatokat, ha erre hitünk szerint törvény kötelez minket, ha betartatjuk az oldalunk szabályzatát vagy megvédjük a saját vagy mások személyiségi jogait, tulajdonát, biztonságát.</p>
 
       <p>A nyilvános tartalmaidat más hálózatban lévő szerverek letölthetik. A nyilvános és csak követőknek szánt tülkjeid olyan szerverekre is elküldődnek, melyeken követőid vannak. A közvetlen üzenetek is átkerülnek a címzettek szervereire, ha ők más szerveren regisztráltak.</p>
 
@@ -993,9 +1117,9 @@ hu:
 
       <h3 id="children">Az oldal gyerekek általi használata</h3>
 
-      <p>Ha ez a szerver az EU-ban vagy EEA-ban van: Az oldalunk, szolgáltatásaink és termékeink mind 16 éven felülieket céloznak. Ha 16 évnél fiatalabb vagy, a GDPR (<a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">General Data Protection Regulation</a>) értelmében kérlek ne használd ezt az oldalt!</p>
+      <p>Ha ez a szerver az EU-ban vagy EEA-ban található: Az oldalunk, szolgáltatásaink és termékeink mind 16 éven felülieket céloznak. Ha 16 évnél fiatalabb vagy, a GDPR (<a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">General Data Protection Regulation</a>) értelmében kérlek ne használd ezt az oldalt!</p>
 
-      <p>Ha ez a szerver az USA-ban van: Az oldalunk, szolgáltatásaink és termékeink mind 13 éven felülieket céloznak. Ha 13 évnél fiatalabb vagy, a COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) értelmében kérlek ne használd ezt az oldalt!</p>
+      <p>Ha ez a szerver az USA-ban található: Az oldalunk, szolgáltatásaink és termékeink mind 13 éven felülieket céloznak. Ha 13 évnél fiatalabb vagy, a COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) értelmében kérlek ne használd ezt az oldalt!</p>
 
       <p>A jogi előírások különbözhetnek ettől a világ egyéb tájain.</p>
 
@@ -1043,7 +1167,9 @@ hu:
         disable: A fiókod befagyasztott állapotban megtartja minden adatát, de feloldásig nem csinálhatsz vele semmit.
         silence: A fiókod korlátozott állapotában csak a követőid láthatják a tülkjeidet, valamint nem kerülsz rá nyilvános idővonalakra. Ugyanakkor mások manuálisan még követhetnek.
         suspend: A fiókodat felfüggesztették, így minden tülköd és feltöltött fájlod menthetetlenül elveszett erről a szerverről és minden olyanról is, ahol voltak követőid.
+      get_in_touch: Válaszolhatsz erre az emailre, hogy kapcsolatba lépj a %{instance} csapatával.
       review_server_policies: Szerver szabályzat átnézése
+      statuses: 'Különösen hozzá:'
       subject:
         disable: A fiókodat %{acct} befagyasztották
         none: Figyelmeztetés a %{acct} fióknak
diff --git a/config/locales/hy.yml b/config/locales/hy.yml
index 86645a57464f0d8490abb7e5e436711065f3733a..203351893d9aa9ce0a6872dc30ac1146c3d69309 100644
--- a/config/locales/hy.yml
+++ b/config/locales/hy.yml
@@ -1,12 +1,26 @@
 ---
 hy:
+  about:
+    active_count_after: Õ¡Õ¯Õ¿Õ«Õ¾
+    see_whats_happening: Տես ինչ ա կատարվում
+    status_count_after:
+      one: Õ½Õ¿Õ¡Õ¿Õ¸Ö‚Õ½
+      other: Õ½Õ¿Õ¡Õ¿Õ¸Ö‚Õ½
+    what_is_mastodon: Ô»ÕžÕ¶Õ¹ Õ§ Õ„Õ¡Õ½Õ¿Õ¸Õ¤Õ¸Õ¶Õ¨
+  accounts:
+    media: Õ„Õ¥Õ¤Õ«Õ¡
+  auth:
+    login: Õ„Õ¿Õ¶Õ¥Õ¬
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': You don't have permission to view this page.
     '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
     '410': The page you were looking for doesn't exist here anymore.
     '422': 
     '429': Throttled
     '500': 
+    '503': The page could not be served due to a temporary server failure.
   invites:
     expires_in:
       '1800': 30 minutes
@@ -15,3 +29,7 @@ hy:
       '43200': 12 hours
       '604800': 1 week
       '86400': 1 day
+  sessions:
+    platforms:
+      android: Ô±Õ¶Õ¤Ö€Õ¸Õ«Õ¤
+      linux: Ô¼Õ«Õ¶Õ¸Ö‚Ö„Õ½
diff --git a/config/locales/id.yml b/config/locales/id.yml
index 43721b19b9f68255ff9c2a9fc8dab9a7cf111b3f..572769a5ae994fddcb5b7cad1dd97f96d65a8725 100644
--- a/config/locales/id.yml
+++ b/config/locales/id.yml
@@ -4,29 +4,51 @@ id:
     about_hashtag_html: Ini adalah toot public yang ditandai dengan <strong>#%{hashtag}</strong>. Anda bisa berinteraksi dengan mereka jika anda memiliki akun dimanapun di fediverse.
     about_mastodon_html: Mastodon adalah sebuah jejaring sosial <em>terbuka, open-source</em. Sebuah alternatif <em>desentralisasi</em> dari platform komersial, menjauhkan anda resiko dari sebuah perusahaan yang memonopoli komunikasi anda. Pilih server yang anda percayai &mdash; apapun yang anda pilih, anda tetap dapat berinteraksi dengan semua orang. Semua orang dapat menjalankan server Mastodon sendiri dan berpartisipasi dalam <em>jejaring sosial</em> dengan mudah.
     about_this: Tentang server ini
+    active_count_after: aktif
+    active_footnote: Pengguna Aktif Bulanan (PAB)
     administered_by: 'Dikelola oleh:'
+    api: API
     apps: Aplikasi hp
+    apps_platforms: Gunakan Mastodon dari iOS, Android, dan platform lain
+    browse_directory: Jelajahi direktori profil dan saring sesuai minat
+    browse_public_posts: Jelajahi siaran langsung pos publik di Mastodon
     contact: Kontak
     contact_missing: Belum diset
     contact_unavailable: Tidak Tersedia
+    discover_users: Temukan pengguna
     documentation: Dokumentasi
-    extended_description_html: |
-      <h3>Tempat yang baik untuk peraturan</h3>
-      <p>Deskripsi lainnya belum diset.</p>
-    generic_description: "%{domain} adalah satu server dalam jaringan"
+    federation_hint_html: Dengan akun di %{instance} Anda dapat mengikuti orang di server Mastodon mana pun dan di luarnya.
+    get_apps: Coba aplikasi mobile
     hosted_on: Mastodon dihosting di %{domain}
+    instance_actor_flash: 'Akun ini adalah aktor virtual yang dipakai untuk merepresentasikan server, bukan pengguna individu. Ini dipakai untuk tujuan federasi dan jangan diblokir kecuali Anda ingin memblokir seluruh instansi, yang seharusnya Anda pakai blokir domain.
+
+'
     learn_more: Pelajari selengkapnya
     privacy_policy: Kebijakan Privasi
+    see_whats_happening: Lihat apa yang sedang terjadi
+    server_stats: 'Statistik server:'
     source_code: Kode sumber
     status_count_after:
       other: status
     status_count_before: Yang telah menulis
+    tagline: Ikuti teman dan temukan yang baru
     terms: Kebijakan layanan
+    unavailable_content: Konten tak tersedia
+    unavailable_content_description:
+      domain: Server
+      reason: Alasan
+      rejecting_media: 'Berkas media dari server ini tak akan diproses dan disimpan, dan tak akan ada gambar kecil yang ditampilkan, perlu klik manual utk menuju berkas asli:'
+      silenced: 'Pos dari server ini akan disembunyikan dari linimasa publik dan percakapan, dan takkan ada notifikasi yang dibuat dari interaksi pengguna mereka, kecuali Anda mengikuti mereka:'
+      suspended: 'Takkan ada data yang diproses, disimpan, dan ditukarkan dari server ini, sehingga interaksi atau komunikasi dengan pengguna dari server ini tak mungkin dilakukan:'
+    unavailable_content_html: Mastodon umumnya mengizinkan Anda untuk melihat konten dan berinteraksi dengan pengguna dari server lain di fediverse. Ini adalah pengecualian yang dibuat untuk beberapa server.
     user_count_after:
       other: pengguna
     user_count_before: Tempat bernaung bagi
     what_is_mastodon: Apa itu Mastodon?
   accounts:
+    choices_html: 'Pilihan %{name}:'
+    endorsements_hint: Anda dapat mempromosikan orang yang Anda ikuti lewat antar muka web, dan mereka akan muncul di sini.
+    featured_tags_hint: Anda dapat mengunggulkan tagar tertentu yang akan ditampilkan di sini.
     follow: Ikuti
     followers:
       other: Pengikut
@@ -34,8 +56,10 @@ id:
     joined: Bergabung pada %{date}
     last_active: terakhir aktif
     link_verified_on: Kepemilikan tautan ini telah dicek pada %{date}
+    media: Media
     moved_html: "%{name} telah pindah ke %{new_profile_link}:"
     network_hidden: Informasi ini tidak tersedia
+    never_active: Tak pernah
     nothing_here: Tidak ada apapun disini!
     people_followed_by: Orang yang diikuti %{name}
     people_who_follow: Orang-orang yang mengikuti %{name}
@@ -47,7 +71,10 @@ id:
     posts_with_replies: Toot dan balasan
     reserved_username: Nama pengguna telah dipesan
     roles:
+      admin: Admin
+      bot: Bot
       moderator: Moderator
+    unavailable: Profil tidak tersedia
     unfollow: Berhenti mengikuti
   admin:
     account_actions:
@@ -59,7 +86,11 @@ id:
       delete: Hapus
       destroyed_msg: Catatan moderasi berhasil dihapus!
     accounts:
+      approve: Terima
+      approve_all: Terima semua
       are_you_sure: Anda yakin?
+      avatar: Avatar
+      by_domain: Domian
       change_email:
         changed_msg: Email akun ini berhasil diubah!
         current_email: Email saat ini
@@ -71,10 +102,12 @@ id:
       confirmed: Dikonfirmasi
       confirming: Mengkonfirmasi
       deleted: Terhapus
+      demote: Turunkan
       disable: Nonaktifkan
       disable_two_factor_authentication: Nonaktifkan 2FA
       disabled: Dinonaktifkan
       display_name: Nama
+      domain: Domain
       edit: Ubah
       email: E-mail
       email_status: Status Email
@@ -84,12 +117,15 @@ id:
       followers: Pengikut
       followers_url: URL pengikut
       follows: Mengikut
+      header: Tajuk
       inbox_url: URL Kotak masuk
       invited_by: Diundang oleh
+      ip: IP
       joined: Bergabung
       location:
         all: Semua
         local: Lokal
+        remote: Remot
         title: Lokasi
       login_status: Status login
       media_attachments: Lampiran media
@@ -97,15 +133,18 @@ id:
       moderation:
         active: Aktif
         all: Semua
+        pending: Tertunda
         silenced: Didiamkan
         suspended: Disuspen
         title: Moderasi
       moderation_notes: Catatan moderasi
       most_recent_activity: Aktivitas terbaru
       most_recent_ip: IP terbaru
+      no_account_selected: Tak ada akun yang diubah sebab tak ada yang dipilih
       no_limits_imposed: Tidak ada batasan
       not_subscribed: Tidak berlangganan
       outbox_url: URL Kotak keluar
+      pending: Tinjauan tertunda
       perform_full_suspension: Lakukan suspen penuh
       profile_url: URL profil
       promote: Promosikan
@@ -113,20 +152,26 @@ id:
       public: Publik
       push_subscription_expires: Langganan PuSH telah kadaluarsa
       redownload: Muat ulang profil
+      reject: Tolak
+      reject_all: Tolak semua
       remove_avatar: Hapus avatar
       remove_header: Hapus header
       resend_confirmation:
         already_confirmed: Pengguna ini sudah dikonfirmasi
         send: Kirim ulang email konfirmasi
         success: Email konfirmasi berhasil dikirim!
+      reset: Reset
       reset_password: Reset kata sandi
       resubscribe: Langganan ulang
       role: Hak akses
       roles:
+        admin: Administrator
+        moderator: Moderator
         staff: Staf
         user: Pengguna
       salmon_url: URL Salmon
       search: Cari
+      shared_inbox_url: URL kotak masuk bersama
       show:
         created_reports: Laporan yang dibuat oleh akun ini
         targeted_reports: Laporan yang dibuat tentang akun ini
@@ -135,6 +180,7 @@ id:
       statuses: Status
       subscribe: Langganan
       suspended: Disuspen
+      time_in_queue: Menunggu dalam antrean %{time}
       title: Akun
       unconfirmed_email: Email belum dikonfirmasi
       undo_silenced: Undo mendiamkan
@@ -142,20 +188,131 @@ id:
       unsubscribe: Berhenti langganan
       username: Nama pengguna
       warn: Beri Peringatan
+      web: Web
+      whitelisted: Masuk daftar putih
+    action_logs:
+      actions:
+        assigned_to_self_report: "%{name} menugaskan laporan %{target} kpd dirinya sendiri"
+        change_email_user: "%{name} mengubah alamat surel pengguna %{target}"
+        confirm_user: "%{name} mengonfirmasi alamat surel pengguna %{target}"
+        create_account_warning: "%{name} mengirim peringatan untuk %{target}"
+        create_custom_emoji: "%{name} mengunggah emoji baru %{target}"
+        create_domain_block: "%{name} memblokir domain %{target}"
+        create_email_domain_block: "%{name} memasukkan ke daftar hitam domain surel %{target}"
+        demote_user: "%{name} menurunkan pengguna %{target}"
+        destroy_custom_emoji: "%{name} menghapus emoji %{target}"
+        destroy_domain_block: "%{name} membuka blokir domain %{target}"
+        destroy_email_domain_block: "%{name} memasukkan ke daftar putih surel domain %{target}"
+        disable_2fa_user: "%{name} mematikan syarat dua faktor utk pengguna %{target}"
+        disable_custom_emoji: "%{name} mematikan emoji %{target}"
+        disable_user: "%{name} mematikan login untuk pengguna %{target}"
+        enable_custom_emoji: "%{name} mengaktifkan emoji %{target}"
+        enable_user: "%{name} mengaktifkan login untuk pengguna %{target}"
+        memorialize_account: "%{name} mengubah akun %{target} jadi halaman memorial"
+        promote_user: "%{name} mempromosikan pengguna %{target}"
+        remove_avatar_user: "%{name} menghapus avatar %{target}"
+        reopen_report: "%{name} membuka ulang laporan %{target}"
+        reset_password_user: "%{name} mereset kata sandi pengguna %{target}"
+        resolve_report: "%{name} menyelesaikan laporan %{target}"
+        silence_account: "%{name} membungkam akun %{target}"
+        suspend_account: "%{name} menangguhkan akun %{target}"
+        unassigned_report: "%{name} tidak menugaskan laporan %{target}"
+        unsilence_account: "%{name} menghapus bungkaman akun %{target}"
+        unsuspend_account: "%{name} menghapus penangguhan akun %{target}"
+        update_custom_emoji: "%{name} memperbarui emoji %{target}"
+      deleted_status: "(status dihapus)"
+      title: Log audit
+    custom_emojis:
+      assign_category: Beri kategori
+      by_domain: Domain
+      copied_msg: Pembuatan salinan lokal emoji berhasil
+      copy: Salin
+      copy_failed_msg: Tidak dapat membuat salinan lokal emoji
+      create_new_category: Buat kategori baru
+      created_msg: Emoji berhasil dibuat!
+      delete: Hapus
+      destroyed_msg: Emoji berhasil dihapus!
+      disable: Matikan
+      disabled: Dinonaktifkan
+      disabled_msg: Emoji berhasil dinonaktifkan
+      emoji: Emoji
+      enable: Aktifkan
+      enabled: Diaktifkan
+      enabled_msg: Emoji berhasil diaktifkan
+      image_hint: PNG hingga 50KB
+      list: Daftar
+      listed: Terdaftar
+      new:
+        title: Tambah emoji kustom baru
+      overwrite: Timpa
+      shortcode: Kode pendek
+      shortcode_hint: Sedikitnya 2 karakter, hanya karakter alfanumerik dan garis bawah
+      title: Emoji kustom
+      uncategorized: Tak terkategorikan
+      unlist: Tak terdaftar
+      unlisted: Tak terdaftar
+      update_failed_msg: Tak dapat memperbarui emoji
+      updated_msg: Emoji berhasil diperbarui!
+      upload: Unggah
+    dashboard:
+      authorized_fetch_mode: Mode aman
+      backlog: pekerjaan di-backlog
+      config: Konfigurasi
+      feature_deletions: Penghapusan akun
+      feature_invites: Tautan undangan
+      feature_profile_directory: Direktori profil
+      feature_registrations: Registrasi
+      feature_relay: Relai federasi
+      feature_spam_check: Anti-spam
+      feature_timeline_preview: Pratinjau linimasa
+      features: Fitur
+      hidden_service: Federasi dengan layanan tersembunyi
+      open_reports: buka laporan
+      pending_tags: tagar menunggu ditinjau
+      pending_users: pengguna menunggu ditinjau
+      recent_users: Pengguna terbaru
+      search: Pencarian teks lengkap
+      single_user_mode: Mode pengguna tunggal
+      software: Perangkat lunak
+      space: Penggunaan ruang
+      title: Dasbor
+      total_users: total pengguna
+      trends: Tren
+      week_interactions: interaksi minggu ini
+      week_users_active: aktif minggu ini
+      week_users_new: pengguna minggu ini
+      whitelist_mode: Mode daftar putih
+    domain_allows:
+      add_new: Daftar putihkan domain
+      created_msg: Domain berhasil masuk daftar putih
+      destroyed_msg: Domain dihapus dari daftar putih
+      undo: Hapus dari daftar putih
     domain_blocks:
       add_new: Tambah
       created_msg: Pemblokiran domain sedang diproses
       destroyed_msg: Pemblokiran domain telah dibatalkan
+      domain: Domain
+      edit: Edit blok domain
+      existing_domain_block_html: Anda telah menerapkan batasan yang lebih ketat pada %{name}, Anda harus <a href="%{unblock_url}">membuka blokirnya</a> lebih dulu.
       new:
         create: Buat pemblokiran
         hint: Pemblokiran domain tidak akan menghentikan pembuatan akun dalam database, tapi kami akan memberikan moderasi otomatis pada akun-akun tersebut.
         severity:
           desc_html: "<strong>Pendiaman</strong> akan membuat semua postingan tidak dapat dilihat oleh semua orang yang tidak mengikutinya. <strong>Suspen</strong> akan menghapus semua konten, media, dan profil dari akun yang bersangkutan."
+          noop: Tidak ada
           silence: Pendiaman
           suspend: Suspen
         title: Pemblokiran domain baru
+      private_comment: Komentar pribadi
+      public_comment: Komentar publik
       reject_media: Tolak berkas media
       reject_media_hint: Hapus file media yang tersimpan dan menolak semua unduhan nantinya. Tidak terpengaruh dengan suspen
+      reject_reports: Tolak laporan
+      reject_reports_hint: Abaikan semua laporan dari domain ini. Tidak relevan untuk penangguhan
+      rejecting_media: tolak berkas media
+      severity:
+        silence: dibungkam
+        suspend: ditangguhkan
       show:
         affected_accounts:
           other: "%{count} akun dalam database terpengaruh"
@@ -163,22 +320,111 @@ id:
           silence: Hapus pendiaman terhadap akun pada domain ini
           suspend: Hapus suspen terhadap akun pada domain ini
         title: Hapus pemblokiran domain %{domain}
+        undo: Urungkan
+      undo: Urungkan blokir domain
+      view: Lihat blokir domain
+    email_domain_blocks:
+      add_new: Tambah baru
+      created_msg: Berhasil menambahkan domain surel ke daftar hitam
+      delete: Hapus
+      destroyed_msg: Berhasil menghapus domain surel dari daftar hitam
+      domain: Domain
+      new:
+        create: Tambah domain
+        title: Entri daftar hitam surel baru
+      title: Daftar hitam surel
+    followers:
+      back_to_account: Kembali Ke Akun
+      title: Pengikut %{acct}
     instances:
+      by_domain: Domain
+      delivery_available: Pengiriman tersedia
+      moderation:
+        all: Semua
+        limited: Terbatas
+        title: Moderasi
+      private_comment: Komentar pribadi
+      public_comment: Komentar publik
       title: Server yang diketahui
+      total_blocked_by_us: Yang kita blokir
+      total_followed_by_them: Diikuti mereka
+      total_followed_by_us: Diikuti kita
+      total_storage: Lampiran media
+    invites:
+      deactivate_all: Nonaktifkan semua
+      filter:
+        all: Semua
+        available: Tersedia
+        expired: Kedaluwarsa
+        title: Saring
+      title: Undang
+    pending_accounts:
+      title: Akun tertunda (%{count})
+    relays:
+      add_new: Tambah relai baru
+      delete: Hapus
+      disable: Matikan
+      disabled: Dimatikan
+      enable: Aktifkan
+      enable_hint: Saat diaktifkan, server Anda akan melanggan semua toot publik dari relai ini, dan akan mengirim toot publik server ini ke sana.
+      enabled: Diaktifkan
+      inbox_url: URL Relai
+      pending: Menunggu persetujuan relai
+      save_and_enable: Simpan dan aktifkan
+      setup: Atur koneksi relai
+      signatures_not_enabled: Relai tak akan bekerja dengan benar saat mode aman atau mode daftar putih diaktifkan
+      status: Status
+      title: Relai
+    report_notes:
+      created_msg: Catatan laporan berhasil dibuat!
+      destroyed_msg: Catatan laporan berhasil dihapus!
     reports:
+      account:
+        note: catatan
+        report: lapor
+      action_taken_by: Aksi dilakukan oleh
+      are_you_sure: Apakah Anda yakin?
+      assign_to_self: Tugaskan kpd saya
+      assigned: Moderator tertugas
       comment:
         none: Tidak ada
+      created_at: Dilaporkan
       mark_as_resolved: Tandai telah diseleseikan
+      mark_as_unresolved: Tandai belum terselesaikan
+      notes:
+        create: Tambah catatan
+        create_and_resolve: Diselesaikan dengan catatan
+        create_and_unresolve: Dibuka kembali dengan catatan
+        delete: Hapus
+        placeholder: Jelaskan aksi yang telah dilakukan, atau pembaruan lain yang berhubungan...
+      reopen: Buka lagi laporan
       report: 'Laporkan #%{id}'
       reported_account: Akun yang dilaporkan
       reported_by: Dilaporkan oleh
       resolved: Terseleseikan
+      resolved_msg: Laporan berhasil diselesaikan!
+      status: Status
       title: Laporan
       unresolved: Belum Terseleseikan
+      updated_at: Diperbarui
     settings:
+      activity_api_enabled:
+        desc_html: Hitung status yang dipos scr lokal, pengguna aktif, dan registrasi baru dlm keranjang bulanan
+        title: Terbitkan statistik keseluruhan tentang aktivitas pengguna
+      bootstrap_timeline_accounts:
+        desc_html: Pisahkan nama pengguna dengan koma. Hanya akun lokal dan tak terkunci yang akan bekerja. Isi bawaan jika kosong adalah semua admin lokal.
+        title: Ikuti scr bawaan untuk pengguna baru
       contact_information:
         email: Masukkan alamat email
         username: Masukkan nama pengguna
+      custom_css:
+        desc_html: Ubah tampilan dengan CSS yang dimuat di setiap halaman
+        title: CSS Kustom
+      default_noindex:
+        title: Singkirkan pengguna dari pengindeksan mesin pencari scr bawaan
+      domain_blocks:
+        all: Kepada semua orang
+        title: Lihat blokir domain
       registrations:
         closed_message:
           desc_html: Ditampilkan pada halaman depan saat pendaftaran ditutup<br>Anda bisa menggunakan tag HTML
@@ -191,12 +437,51 @@ id:
         title: Deskripsi situs tambahan
       site_title: Judul Situs
       title: Pengaturan situs
-    subscriptions:
-      confirmed: Dikonfirmasi
-      expires_in: Kadaluarsa dalam
-      last_delivery: Terakhir dikirim
-      topic: Topik
+    statuses:
+      batch:
+        nsfw_off: Tandai sebagai tak sensitif
+        nsfw_on: Tandai sebagai sensitif
+      deleted: Dihapus
+      failed_to_execute: Gagal mengeksekusi
+      media:
+        title: Media
+      no_media: Tanpa media
+      no_status_selected: Tak ada status yang berubah karena tak ada yang dipilih
+      title: Status akun
+      with_media: Dengan media
+    tags:
+      accounts_today: Penggunaan unik hari ini
+      accounts_week: Penggunaan unik minggu ini
+      breakdown: Rinci penggunaan hari ini berdasar sumber
+      context: Konteks
+      directory: Di direktori
+      in_directory: "%{count} di direktori"
+      last_active: Terakhir aktif
+      most_popular: Paling populer
+      most_recent: Terkini
+      name: Tagar
+      review: Tinjau status
+      reviewed: Ditinjau
+      title: Tagar
+      trending_right_now: Sedang tren sekarang
+      unique_uses_today: "%{count} memposkan hari ini"
+      unreviewed: Tak tertinjau
+      updated_msg: Pembaruan pengaturan tagar berhasil
     title: Administrasi
+    warning_presets:
+      add_new: Tambah baru
+      delete: Hapus
+      edit: Sunting
+      edit_preset: Sunting preset peringatan
+      title: Kelola preset peringatan
+  admin_mailer:
+    new_pending_account:
+      body: Detail akun baru di bawah. Anda dapat menyetujui atau menolak lamaran ini.
+      subject: Akun baru muncul untuk ditinjau di %{instance} (%{username})
+    new_report:
+      body: "%{reporter} telah melaporkan %{target}"
+      body_remote: Seseorang dari %{domain} telah melaporkan %{target}
+      subject: Laporan baru untuk %{instance} (#%{id})
   application_mailer:
     settings: 'Ubah pilihan email: %{link}'
     view: 'Tampilan:'
@@ -231,14 +516,17 @@ id:
       x_months: "%{count}bln"
       x_seconds: "%{count}dtk"
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Anda tidak mempunyai izin untuk melihat halaman ini.
     '404': Halaman yang anda cari tidak ditemukan
+    '406': This page is not available in the requested format.
     '410': Halaman yang anda cari sudah tidak dapat ditemukan lagi.
     '422':
       content: Verifikasi keamanan gagal. Apa anda memblokir cookie?
       title: Verifikasi keamanan gagal
     '429': Throttled
     '500': 
+    '503': The page could not be served due to a temporary server failure.
   exports:
     blocks: Anda blokir
     follows: Anda ikuti
diff --git a/config/locales/io.yml b/config/locales/io.yml
index 559bf0f534dcb71eb6c69c9418fd0ff29d0d9c6a..2b3e5069173f29841aaba51144cc9c59d88f7bb2 100644
--- a/config/locales/io.yml
+++ b/config/locales/io.yml
@@ -73,12 +73,15 @@ io:
       half_a_minute: Jus
       less_than_x_seconds: Jus
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': You don't have permission to view this page.
     '404': La pagino quan tu serchas ne existas.
+    '406': This page is not available in the requested format.
     '410': La pagino quan tu serchas ne plus existas.
     '422': 
     '429': Throttled
     '500': 
+    '503': The page could not be served due to a temporary server failure.
   exports:
     blocks: Tu blokusas
     follows: Tu sequas
diff --git a/config/locales/it.yml b/config/locales/it.yml
index 6dfe212d1496abc4b94d73243d27b1364e764337..f35c717a6ded7c456f3c2af45e222ac093d709d0 100644
--- a/config/locales/it.yml
+++ b/config/locales/it.yml
@@ -8,7 +8,7 @@ it:
     active_footnote: Utenti Attivi Mensili (MAU)
     administered_by: 'Amministrato da:'
     api: API
-    apps: Applicazioni Mobile
+    apps: Applicazioni per dispositivi mobili
     apps_platforms: Usa Mastodon da iOS, Android e altre piattaforme
     browse_directory: Sfoglia la directory dei profili e filtra per interessi
     browse_public_posts: Sfoglia il flusso in tempo reale di post pubblici su Mastodon
@@ -17,13 +17,12 @@ it:
     contact_unavailable: N/D
     discover_users: Scopri utenti
     documentation: Documentazione
-    extended_description_html: |
-      <h3>Un buon posto per le regole</h3>
-      <p>La descrizione estesa non è ancora stata preparata.</p>
     federation_hint_html: Con un account su %{instance} sarai in grado di seguire persone su qualsiasi server Mastodon e oltre.
-    generic_description: "%{domain} è un server nella rete"
     get_apps: Prova un'app per smartphone
     hosted_on: Mastodon ospitato su %{domain}
+    instance_actor_flash: |
+      Questo account è un attore virtuale utilizzato per rappresentare il server stesso e non un particolare utente.
+      È utilizzato per scopi di federazione e non dovrebbe essere bloccato a meno che non si voglia bloccare l'intera istanza: in questo caso si dovrebbe utilizzare un blocco di dominio.
     learn_more: Scopri altro
     privacy_policy: Politica della privacy
     see_whats_happening: Guarda cosa succede
@@ -35,6 +34,10 @@ it:
     status_count_before: Che hanno pubblicato
     tagline: Segui amici e trovane di nuovi
     terms: Termini di Servizio
+    unavailable_content: Contenuto non disponibile
+    unavailable_content_description:
+      reason: 'Motivo:'
+      rejecting_media: I file multimediali di questo server non saranno elaborati e non verranno visualizzate miniature, che richiedono clic manuale sull'altro server.
     user_count_after:
       one: utente
       other: utenti
@@ -42,6 +45,7 @@ it:
     what_is_mastodon: Che cos'è Mastodon?
   accounts:
     choices_html: 'Suggerimenti da %{name}:'
+    featured_tags_hint: Puoi mettere in evidenza determinati hashtag che verranno visualizzati qui.
     follow: Segui
     followers:
       one: Seguace
@@ -51,8 +55,9 @@ it:
     last_active: ultima attività
     link_verified_on: La proprietà di questo link è stata controllata il %{date}
     media: Media
-    moved_html: "%{name} è stato spostato su %{new_profile_link}:"
+    moved_html: "%{name} si è spostato su %{new_profile_link}:"
     network_hidden: Questa informazione non e' disponibile
+    never_active: Mai
     nothing_here: Qui non c'è nulla!
     people_followed_by: Persone seguite da %{name}
     people_who_follow: Persone che seguono %{name}
@@ -63,7 +68,7 @@ it:
       other: Toot
     posts_tab_heading: Toot
     posts_with_replies: Toot e risposte
-    reserved_username: Il nome utente è gia stato preso
+    reserved_username: Questo nome utente è già stato preso
     roles:
       admin: Amministratore
       bot: Bot
@@ -87,7 +92,7 @@ it:
       by_domain: Dominio
       change_email:
         changed_msg: Account email cambiato con successo!
-        current_email: Email corrente
+        current_email: Email attuale
         label: Cambia email
         new_email: Nuova email
         submit: Cambia email
@@ -98,7 +103,7 @@ it:
       deleted: Cancellato
       demote: Declassa
       disable: Disabilita
-      disable_two_factor_authentication: Disabilita 2FA
+      disable_two_factor_authentication: Disabilita l'autenticazione a due fattori
       disabled: Disabilitato
       display_name: Nome visualizzato
       domain: Dominio
@@ -115,7 +120,7 @@ it:
       inbox_url: URL inbox
       invited_by: Invitato da
       ip: IP
-      joined: Unito
+      joined: Iscritto da
       location:
         all: Tutto
         local: Locale
@@ -133,7 +138,7 @@ it:
         title: Moderazione
       moderation_notes: Note di moderazione
       most_recent_activity: Attività più recenti
-      most_recent_ip: IP più recenti
+      most_recent_ip: IP più recente
       no_account_selected: Nessun account è stato modificato visto che non ne è stato selezionato nessuno
       no_limits_imposed: Nessun limite imposto
       not_subscribed: Non sottoscritto
@@ -183,6 +188,7 @@ it:
       username: Nome utente
       warn: Avverti
       web: Web
+      whitelisted: Nella whitelist
     action_logs:
       actions:
         assigned_to_self_report: "%{name} ha assegnato il rapporto %{target} a se stesso"
@@ -218,10 +224,12 @@ it:
       deleted_status: "(stato cancellato)"
       title: Registro di controllo
     custom_emojis:
+      assign_category: Assegna categoria
       by_domain: Dominio
       copied_msg: Creata con successo una copia locale dell'emoji
       copy: Copia
       copy_failed_msg: Impossibile creare una copia locale di questo emoji
+      create_new_category: Crea nuova categoria
       created_msg: Emoji creato con successo!
       delete: Elimina
       destroyed_msg: Emoji distrutto con successo!
@@ -229,6 +237,7 @@ it:
       disabled_msg: Questa emoji è stata disabilitata con successo
       emoji: Emoji
       enable: Abilita
+      enabled: Abilitato
       enabled_msg: Questa emoji è stata abilitata con successo
       image_hint: PNG fino a 50 KB
       listed: Elencato
@@ -238,11 +247,13 @@ it:
       shortcode: Scorciatoia
       shortcode_hint: Almeno due caratteri, solo caratteri alfanumerici e trattino basso
       title: Emoji personalizzate
+      uncategorized: Nessuna categoria
       unlisted: Non elencato
       update_failed_msg: Impossibile aggiornare questa emojii
       updated_msg: Emoji aggiornata con successo!
       upload: Carica
     dashboard:
+      authorized_fetch_mode: Modalità sicura
       backlog: lavori arretrati
       config: Configurazione
       feature_deletions: Cancellazioni di account
@@ -250,10 +261,13 @@ it:
       feature_profile_directory: Directory dei profili
       feature_registrations: Registrazioni
       feature_relay: Ripetitore di federazione
+      feature_spam_check: Anti-spam
       feature_timeline_preview: Anteprima timeline
       features: Funzionalità
       hidden_service: Federazione con servizi nascosti
       open_reports: apri report
+      pending_tags: hashtag in attesa di essere esaminati
+      pending_users: utenti in attesa di essere esaminati
       recent_users: Utenti Recenti
       search: Ricerca testo intero
       single_user_mode: Modalita utente singolo
@@ -265,11 +279,18 @@ it:
       week_interactions: interazioni per questa settimana
       week_users_active: attivi questa settimana
       week_users_new: utenti questa settimana
+      whitelist_mode: Modalità whitelist
+    domain_allows:
+      add_new: Dominio nella whitelist
+      created_msg: Il dominio è stato inserito nella whitelist
+      destroyed_msg: Il dominio è stato eliminato dalla whitelist
+      undo: Elimina dalla whitelist
     domain_blocks:
       add_new: Aggiungi nuovo
       created_msg: Il blocco del dominio sta venendo processato
       destroyed_msg: Il blocco del dominio è stato rimosso
       domain: Dominio
+      edit: Modifica blocco di dominio
       existing_domain_block_html: Hai già impostato limitazioni più stringenti su %{name}, dovresti <a href="%{unblock_url}">sbloccare</a> prima.
       new:
         create: Crea blocco
@@ -280,6 +301,10 @@ it:
           silence: Silenzia
           suspend: Sospendi
         title: Nuovo blocco dominio
+      private_comment: Commento privato
+      private_comment_hint: Commento su questa limitazione di dominio per uso interno da parte dei moderatori.
+      public_comment: Commento pubblico
+      public_comment_hint: Commento pubblico su questa limitazione di dominio, se l'elenco delle limitazioni di dominio è pubblico.
       reject_media: Rifiuta file media
       reject_media_hint: Rimuovi i file media salvati in locale e blocca i download futuri. Irrilevante per le sospensioni
       reject_reports: Respingi rapporti
@@ -299,6 +324,7 @@ it:
         title: Annulla il blocco del dominio per %{domain}
         undo: Annulla
       undo: Annulla
+      view: Visualizza blocco di dominio
     email_domain_blocks:
       add_new: Aggiungi nuovo
       created_msg: Dominio email aggiunto con successo alla lista nera
@@ -322,6 +348,8 @@ it:
         all: Tutto
         limited: Limitato
         title: Moderazione
+      private_comment: Commento privato
+      public_comment: Commento pubblico
       title: Istanze conosciute
       total_blocked_by_us: Bloccato da noi
       total_followed_by_them: Seguito da loro
@@ -399,6 +427,13 @@ it:
       custom_css:
         desc_html: Modifica l'aspetto con il CSS caricato in ogni pagina
         title: CSS personalizzato
+      domain_blocks:
+        all: A tutti
+        disabled: A nessuno
+        title: Mostra blocchi di dominio
+        users: Agli utenti locali connessi
+      domain_blocks_rationale:
+        title: Mostra motivazione
       hero:
         desc_html: Mostrata nella pagina iniziale. Almeno 600x100 px consigliati. Se non impostata, sarà usato il thumbnail del server
         title: Immagine dell'eroe
@@ -449,6 +484,9 @@ it:
         desc_html: Potete scrivere la vostra politica sulla privacy, condizioni del servizio o altre informazioni legali. Potete usare tag HTML
         title: Termini di servizio personalizzati
       site_title: Nome del server
+      spam_check_enabled:
+        desc_html: Mastodon può silenziare e segnalare automaticamente account che inviano ripetutamente messaggi non richiesti. Potrebbero esserci falsi positivi.
+        title: Automazione anti-spam
       thumbnail:
         desc_html: Usato per anteprime tramite OpenGraph e API. 1200x630px consigliati
         title: Thumbnail del server
@@ -456,12 +494,16 @@ it:
         desc_html: Mostra la timeline pubblica sulla pagina iniziale
         title: Anteprima timeline
       title: Impostazioni sito
+      trends:
+        desc_html: Visualizza pubblicamente gli hashtag precedentemente esaminati che sono attualmente in tendenza
+        title: Hashtag di tendenza
     statuses:
       back_to_account: Torna alla pagina dell'account
       batch:
         delete: Elimina
         nsfw_off: Segna come non sensibile
         nsfw_on: Segna come sensibile
+      deleted: Cancellato
       failed_to_execute: Impossibile eseguire
       media:
         title: Media
@@ -469,21 +511,22 @@ it:
       no_status_selected: Nessun status è stato modificato perché nessuno era stato selezionato
       title: Gli status dell'account
       with_media: con media
-    subscriptions:
-      callback_url: URL Callback
-      confirmed: Confermato
-      expires_in: Scade in
-      last_delivery: Ultima distribuzione
-      title: WebSub
-      topic: Argomento
     tags:
-      accounts: Account
-      hidden: Nascosto
-      hide: Nascondi dalla directory
-      name: Etichetta
+      accounts_today: Usi unici oggi
+      accounts_week: Usi unici questa settimana
+      breakdown: Suddivisione dell'utilizzo di oggi per fonte
+      context: Contesto
+      directory: Nella directory
+      in_directory: "%{count} nella directory"
+      last_active: Ultima attività
+      most_popular: Più popolari
+      most_recent: Più recenti
+      name: Hashtag
+      reviewed: Controllato
       title: Hashtag
-      unhide: Mostra nella directory
-      visible: Visibile
+      trending_right_now: Di tendenza ora
+      unreviewed: Non controllato
+      updated_msg: Impostazioni degli hashtag aggiornate
     title: Amministrazione
     warning_presets:
       add_new: Aggiungi nuovo
@@ -499,6 +542,11 @@ it:
       body: "%{reporter} ha segnalato %{target}"
       body_remote: Qualcuno da %{domain} ha segnalato %{target}
       subject: Nuova segnalazione per %{instance} (#%{id})
+    new_trending_tag:
+      body: 'L''hashtag #%{name} oggi è di tendenza, ma non è stato mai controllato. Non sarà visualizzato pubblicamente se non lo permetti; se salvi il form senza modifiche non lo vedrai mai più.'
+      subject: Nuovo hashtag pronto per essere controllato su %{instance} (%{name})
+  aliases:
+    add_new: Crea alias
   appearance:
     advanced_web_interface: Interfaccia web avanzata
     advanced_web_interface_hint: |-
@@ -526,7 +574,7 @@ it:
     apply_for_account: Richiedi un invito
     change_password: Password
     checkbox_agreement_html: Sono d'accordo con le <a href="%{rules_path}" target="_blank">regole del server</a> ed i <a href="%{terms_path}" target="_blank">termini di servizio</a>
-    confirm_email: Conferma email
+    checkbox_agreement_without_rules_html: Accetto i <a href="%{terms_path}" target="_blank">termini di servizio</a>
     delete_account: Elimina account
     delete_account_html: Se desideri cancellare il tuo account, puoi <a href="%{path}">farlo qui</a>. Ti sarà chiesta conferma.
     didnt_get_confirmation: Non hai ricevuto le istruzioni di conferma?
@@ -546,6 +594,14 @@ it:
     reset_password: Resetta la password
     security: Credenziali
     set_new_password: Imposta una nuova password
+    setup:
+      email_below_hint_html: Se l'indirizzo e-mail sottostante non è corretto, puoi cambiarlo qui e ricevere una nuova e-mail di conferma.
+      email_settings_hint_html: L'email di conferma è stata inviata a %{email}. Se l'indirizzo e-mail non è corretto, puoi modificarlo nelle impostazioni dell'account.
+    status:
+      account_status: Stato dell'account
+      confirming: In attesa che la conferma e-mail sia completata.
+      functional: Il tuo account è pienamente operativo.
+      pending: La tua richiesta è in attesa di esame da parte del nostro staff. Potrebbe richiedere un po' di tempo. Riceverai una e-mail se la richiesta è approvata.
     trouble_logging_in: Problemi di accesso?
   authorize_follow:
     already_following: Stai già seguendo questo account
@@ -558,6 +614,11 @@ it:
       return: Mostra il profilo dell'utente
       web: Vai al web
     title: Segui %{acct}
+  challenge:
+    confirm: Continua
+    hint_html: "<strong>Suggerimento:</strong> Non ti chiederemo di nuovo la tua password per la prossima ora."
+    invalid_password: Password non valida
+    prompt: Conferma la tua password per continuare
   datetime:
     distance_in_words:
       about_x_hours: "%{count} ore"
@@ -573,26 +634,24 @@ it:
       x_months: "%{count} mesi"
       x_seconds: "%{count} secondi"
   deletes:
-    bad_password_msg: Ci avete provato, hacker! Password errata
+    challenge_not_passed: Le informazioni che hai inserito non sono corrette
     confirm_password: Inserisci la tua password attuale per verificare la tua identità
-    description_html: Questa azione eliminerà <strong>in modo permanente e irreversibile</strong> tutto il contenuto del tuo account e lo disattiverà. Il tuo nome utente resterà riservato per prevenire che qualcuno in futuro assuma la tua identità.
+    confirm_username: Inserisci il tuo nome utente per confermare la procedura
     proceed: Cancella l'account
     success_msg: Il tuo account è stato cancellato
-    warning_html: È garantita la cancellazione del contenuto solo da questo server. I contenuti che sono stati ampiamente condivisi probabilmente lasceranno delle tracce. I server offline e quelli che non ricevono più i tuoi aggiornamenti non aggiorneranno i loro database.
-    warning_title: Disponibilità di contenuto diffuso
+    warning:
+      before: 'Prima di procedere, per favore leggi attentamente queste note:'
   directories:
     directory: Directory dei profili
-    enabled: Attualmente sei elencato nella directory.
-    enabled_but_waiting: Hai scelto di essere elencato nella directory, ma non hai ancora il numero minimo di seguaci (%{min_followers}) per comparire.
     explanation: Scopri utenti in base ai loro interessi
     explore_mastodon: Esplora %{title}
-    how_to_enable: Attualmente non hai scelto di comparire nella directory. Puoi farlo qui sotto. Se vuoi comparire sotto determinati hashtag, usali nel testo della tua biografia.
-    people:
-      one: "%{count} persona"
-      other: "%{count} persone"
+  domain_validator:
+    invalid_domain: non è un nome di dominio valido
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Non sei autorizzato a visualizzare questa pagina.
     '404': La pagina che stavi cercando non esiste.
+    '406': This page is not available in the requested format.
     '410': La pagina che stavi cercando qui non esiste più.
     '422':
       content: Verifica di sicurezza non riuscita. Stai bloccando i cookies?
@@ -601,6 +660,7 @@ it:
     '500':
       content: Siamo spiacenti, ma qualcosa non ha funzionato dal nostro lato.
       title: Questa pagina non è corretta
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: Per usare l'interfaccia web di Mastodon dovi abilitare JavaScript. In alternativa puoi provare una delle <a href="%{apps_path}">app native</a> per Mastodon per la tua piattaforma.
   existing_username_validator:
     not_found: impossibile trovare un utente locale con quel nome utente
@@ -624,6 +684,7 @@ it:
     add_new: Aggiungi nuovo
     errors:
       limit: Hai già messo in evidenza il numero massimo di hashtag
+    hint_html: "<strong>Cosa sono gli hashtag in evidenza?</strong> Sono visualizzati in evidenza sul tuo profilo pubblico e permettono alle persone di visualizzare i tuoi post pubblici marcati con questi hashtag. Sono un grande strumento per tenere traccia di opere creative o progetti a lungo termine."
   filters:
     contexts:
       home: Timeline home
@@ -644,6 +705,7 @@ it:
     developers: Sviluppatori
     more: Altro…
     resources: Risorse
+    trending_now: Di tendenza ora
   generic:
     all: Tutto
     changes_saved_msg: Modifiche effettuate con successo!
@@ -719,9 +781,6 @@ it:
       too_many: Impossibile allegare più di 4 file
   migrations:
     acct: utente@dominio del nuovo account
-    currently_redirecting: 'Il tuo profilo sarà ridirezionato a:'
-    proceed: Salva
-    updated_msg: L'impostazione per la migrazione dell'account è sta aggiornata!
   moderation:
     title: Moderazione
   notification_mailer:
@@ -818,10 +877,6 @@ it:
     reply:
       proceed: Continua per rispondere
       prompt: 'Vuoi rispondere a questo toot:'
-  remote_unfollow:
-    error: Errore
-    title: Titolo
-    unfollowed: Non più seguito
   scheduled_statuses:
     over_daily_limit: Hai superato il limite di %{limit} toot programmati per questo giorno
     over_total_limit: Hai superato il limite di %{limit} toot programmati
@@ -885,7 +940,7 @@ it:
     notifications: Notifiche
     preferences: Preferenze
     profile: Profilo
-    relationships: Follows and followers
+    relationships: Follows e followers
     two_factor_authentication: Autenticazione a due fattori
   statuses:
     attached:
@@ -928,6 +983,8 @@ it:
     pinned: Toot fissato in cima
     reblogged: condiviso
     sensitive_content: Materiale sensibile
+  tags:
+    does_not_match_previous_name: non corrisponde al nome precedente
   terms:
     title: "%{instance} Termini di servizio e politica della privacy"
   themes:
@@ -964,7 +1021,9 @@ it:
         disable: Mentre il tuo account è congelato, i tuoi dati dell'account rimangono intatti, ma non potrai eseguire nessuna azione fintanto che non viene sbloccato.
         silence: Mentre il tuo account è limitato, solo le persone che già ti seguono possono vedere i tuoi toot su questo server, e potresti essere escluso da vari elenchi pubblici. Comunque, altri possono manualmente seguirti.
         suspend: Il tuo account è stato sospeso, e tutti i tuoi toot ed i tuoi file media caricati sono stati irreversibilmente rimossi da questo server, e dai server dove avevi dei seguaci.
+      get_in_touch: Puoi rispondere a questa email per entrare in contatto con lo staff di %{instance}.
       review_server_policies: Rivedi regole del server
+      statuses: 'Nello specifico, per:'
       subject:
         disable: Il tuo account %{acct} è stato congelato
         none: Avviso per %{acct}
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index ca640d07d1f42546867ac77070a85a1dc56728ae..3ff226efe1017adac07fbb47d97b98fd537018e8 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -17,13 +17,12 @@ ja:
     contact_unavailable: N/A
     discover_users: ユーザーを見つける
     documentation: ドキュメント
-    extended_description_html: |
-      <h3>ルールを書くのに適した場所</h3>
-      <p>詳細説明が設定されていません。</p>
     federation_hint_html: "%{instance} のアカウントひとつでどんなMastodon互換サーバーのユーザーでもフォローできるでしょう。"
-    generic_description: "%{domain} は、Mastodon サーバーの一つです"
     get_apps: モバイルアプリを試す
     hosted_on: Mastodon hosted on %{domain}
+    instance_actor_flash: 'このアカウントはサーバーそのものを示す仮想的なもので、特定のユーザーを示すものではありません。これはサーバーの連合のために使用されます。サーバー全体をブロックするときは、このアカウントをブロックせずに、ドメインブロックを使用してください。
+
+'
     learn_more: もっと詳しく
     privacy_policy: プライバシーポリシー
     see_whats_happening: やりとりを見てみる
@@ -34,12 +33,22 @@ ja:
     status_count_before: トゥート数
     tagline: Follow friends and discover new ones
     terms: 利用規約
+    unavailable_content: 制限中のサーバー
+    unavailable_content_description:
+      domain: サーバー
+      reason: 制限理由
+      rejecting_media: 'これらのサーバーからのメディアファイルは処理されず、保存や変換もされません。サムネイルも表示されません。表示するにはクリックしてそのサーバーに直接アクセスする必要があります:'
+      silenced: 'これらのサーバーからの投稿は公開タイムラインと会話から隠されます。また該当するユーザーからの通知は相手をフォローしている場合を除き表示されません:'
+      suspended: 'これらのサーバーからのデータは処理されず、保存や変換もされません。該当するユーザーとの交流もできません:'
+    unavailable_content_html: 通常 Mastodon では連合先のどんなサーバーのユーザーとでもやりとりできます。ただし次のサーバーには例外が設定されています。
     user_count_after:
       other: 人
     user_count_before: ユーザー数
     what_is_mastodon: Mastodon とは?
   accounts:
     choices_html: "%{name} によるおすすめ:"
+    endorsements_hint: あなたがフォローしている中でおすすめしたい人をここで紹介できます。
+    featured_tags_hint: 特定のハッシュタグをここに表示できます。
     follow: フォロー
     followers:
       other: フォロワー
@@ -50,6 +59,7 @@ ja:
     media: メディア
     moved_html: "%{name} さんは引っ越しました %{new_profile_link}:"
     network_hidden: この情報は利用できません
+    never_active: 活動なし
     nothing_here: 何もありません!
     people_followed_by: "%{name} さんがフォロー中のアカウント"
     people_who_follow: "%{name} さんをフォロー中のアカウント"
@@ -179,6 +189,7 @@ ja:
       username: ユーザー名
       warn: 警告
       web: Web
+      whitelisted: ホワイトリストに登録済み
     action_logs:
       actions:
         assigned_to_self_report: "%{name} さんが通報 %{target} を自身の担当に割り当てました"
@@ -214,31 +225,39 @@ ja:
       deleted_status: "(削除済)"
       title: 操作履歴
     custom_emojis:
+      assign_category: カテゴリーを割り当て
       by_domain: ドメイン
       copied_msg: 絵文字のコピーをローカルに作成しました
       copy: コピー
       copy_failed_msg: 絵文字のコピーをローカルに作成できませんでした
+      create_new_category: カテゴリーを作成
       created_msg: 絵文字の追加に成功しました!
       delete: 削除
       destroyed_msg: 絵文字の削除に成功しました!
       disable: 無効化
+      disabled: 無効
       disabled_msg: 絵文字を無効化しました
       emoji: 絵文字
       enable: 有効化
+      enabled: 有効
       enabled_msg: 絵文字を有効化しました
       image_hint: 50KBまでのPNG画像を利用できます
-      listed: 収載
+      list: 表示
+      listed: 表示
       new:
         title: 新規カスタム絵文字の追加
       overwrite: 上書き
       shortcode: ショートコード
       shortcode_hint: 2文字以上の半角英数字とアンダーバーのみ利用できます
       title: カスタム絵文字
-      unlisted: 未収載
+      uncategorized: 未分類
+      unlist: 非表示
+      unlisted: 非表示
       update_failed_msg: 絵文字を更新できませんでした
       updated_msg: 絵文字の更新に成功しました!
       upload: アップロード
     dashboard:
+      authorized_fetch_mode: セキュアモード
       backlog: 未処理のジョブ
       config: 構成
       feature_deletions: アカウント削除
@@ -246,10 +265,13 @@ ja:
       feature_profile_directory: ディレクトリ
       feature_registrations: 新規登録
       feature_relay: 連合リレー
+      feature_spam_check: スパム対策
       feature_timeline_preview: タイムラインプレビュー
       features: 機能
       hidden_service: 秘匿サービスとの連合
       open_reports: 未解決の通報
+      pending_tags: 審査待ちのハッシュタグ
+      pending_users: 承認待ちの人数
       recent_users: 最近登録したユーザー
       search: 全文検索
       single_user_mode: シングルユーザーモード
@@ -261,11 +283,18 @@ ja:
       week_interactions: 今週交流のあった回数
       week_users_active: 今週活動した人数
       week_users_new: 今週登録した人数
+      whitelist_mode: ホワイトリストモード
+    domain_allows:
+      add_new: ホワイトリストに追加
+      created_msg: ドメインのホワイトリストへの登録が完了しました
+      destroyed_msg: ドメインをホワイトリストから削除しました
+      undo: ホワイトリストから削除
     domain_blocks:
       add_new: ドメインブロックを追加
       created_msg: ドメインブロック処理を完了しました
       destroyed_msg: ドメインブロックを外しました
       domain: ドメイン
+      edit: ドメインブロックを編集
       existing_domain_block_html: 既に%{name}に対して、より厳しい制限を課しています。先に<a href="%{unblock_url}">その制限を解除</a>する必要があります。
       new:
         create: ブロックを作成
@@ -276,6 +305,10 @@ ja:
           silence: サイレンス
           suspend: 停止
         title: 新規ドメインブロック
+      private_comment: コメント (非公開)
+      private_comment_hint: このコメントは同じサーバーのモデレーターも閲覧できます。
+      public_comment: コメント (公開)
+      public_comment_hint: ドメインブロックの公開を有効にしている場合、このコメントも公開されます。
       reject_media: メディアファイルを拒否
       reject_media_hint: ローカルに保存されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です
       reject_reports: 通報を拒否
@@ -294,6 +327,7 @@ ja:
         title: "%{domain}のドメインブロックを戻す"
         undo: 元に戻す
       undo: ドメインブロックを戻す
+      view: ドメインブロックを表示
     email_domain_blocks:
       add_new: 新規追加
       created_msg: ブラックリストに追加しました
@@ -316,6 +350,8 @@ ja:
         all: すべて
         limited: 制限あり
         title: モデレーション
+      private_comment: コメント (非公開)
+      public_comment: コメント (公開)
       title: 既知のサーバー
       total_blocked_by_us: ブロック合計
       total_followed_by_them: 被フォロー合計
@@ -345,6 +381,7 @@ ja:
       pending: リレーサーバーの承認待ちです
       save_and_enable: 保存して有効にする
       setup: リレー接続を設定する
+      signatures_not_enabled: セキュアモードまたはホワイトリストモードが有効の場合、リレーは正常に動作しません
       status: ステータス
       title: リレー
     report_notes:
@@ -393,6 +430,16 @@ ja:
       custom_css:
         desc_html: 全ページに適用されるCSSの編集
         title: カスタムCSS
+      default_noindex:
+        desc_html: この設定を変更していない全ユーザーに影響します
+        title: デフォルトで検索エンジンによるインデックスを拒否する
+      domain_blocks:
+        all: 誰にでも許可
+        disabled: 誰にも許可しない
+        title: ドメインブロックを表示
+        users: ログイン済みローカルユーザーのみ許可
+      domain_blocks_rationale:
+        title: コメントを表示
       hero:
         desc_html: フロントページに表示されます。サイズは600x100px以上推奨です。未設定の場合、標準のサムネイルが使用されます
         title: ヒーローイメージ
@@ -416,17 +463,17 @@ ja:
           desc_html: 誰でも自分のアカウントを削除できるようにします
           title: アカウント削除を受け付ける
         min_invite_role:
-          disabled: 誰も許可しない
+          disabled: 誰にも許可しない
           title: 招待の作成を許可
       registrations_mode:
         modes:
           approved: 登録には承認が必要
-          none: 誰も許可しない
+          none: 誰にも許可しない
           open: 誰でも登録可
         title: 新規登録
       show_known_fediverse_at_about_page:
-        desc_html: チェックを入れるとプレビュー欄に既知の連合先全てのトゥートを表示します。外すとローカルのトゥートだけ表示します。
-        title: タイムラインプレビューに連合タイムラインを表示する
+        desc_html: チェックを外すと、ランディングページからリンクされた公開タイムラインにローカルの公開トゥートのみ表示します。
+        title: 公開タイムラインに連合先のコンテンツも表示する
       show_staff_badge:
         desc_html: ユーザーページにスタッフのバッジを表示します
         title: スタッフバッジを表示する
@@ -443,19 +490,29 @@ ja:
         desc_html: 独自のプライバシーポリシーや利用規約、その他の法的根拠を記述できます。HTMLタグが使えます
         title: カスタム利用規約
       site_title: サーバーの名前
+      spam_check_enabled:
+        desc_html: 迷惑なメッセージを繰り返し送信するアカウントを自動で通報することができます。誤検知を含む可能性があります。
+        title: スパム対策を有効にする
       thumbnail:
         desc_html: OpenGraphとAPIによるプレビューに使用されます。サイズは1200×630px推奨です
         title: サーバーのサムネイル
       timeline_preview:
-        desc_html: ランディングページに公開タイムラインを表示します
-        title: タイムラインプレビュー
+        desc_html: ランディングページに公開タイムラインへのリンクを表示し、認証なしでの公開タイムラインへの API アクセスを許可します
+        title: 公開タイムラインへの未認証のアクセスを許可する
       title: サイト設定
+      trendable_by_default:
+        desc_html: 表示を拒否していないハッシュタグに影響します
+        title: 審査前のハッシュタグのトレンドへの表示を許可する
+      trends:
+        desc_html: 現在トレンドになっている承認済みのハッシュタグを公開します
+        title: トレンドタグを有効にする
     statuses:
       back_to_account: アカウントページに戻る
       batch:
         delete: 削除
         nsfw_off: 閲覧注意をはずす
         nsfw_on: 閲覧注意にする
+      deleted: 削除済み
       failed_to_execute: 実行に失敗しました
       media:
         title: メディア
@@ -463,21 +520,24 @@ ja:
       no_status_selected: 何も選択されていないため、変更されていません
       title: トゥート一覧
       with_media: メディアあり
-    subscriptions:
-      callback_url: コールバックURL
-      confirmed: 確認済み
-      expires_in: 期限
-      last_delivery: 最終配送
-      title: WebSub
-      topic: トピック
     tags:
-      accounts: アカウント
-      hidden: 非表示
-      hide: ディレクトリから隠す
+      accounts_today: 本日使用した人数
+      accounts_week: 今週使用した人数
+      breakdown: 直近のサーバー別使用状況
+      context: 表示先
+      directory: ディレクトリに使用
+      in_directory: "%{count} 人がディレクトリに使用"
+      last_active: 最近使われた順
+      most_popular: 使用頻度順
+      most_recent: 新着順
       name: ハッシュタグ
+      review: 審査状況
+      reviewed: 審査済み
       title: ハッシュタグ
-      unhide: ディレクトリに表示する
-      visible: 表示
+      trending_right_now: 現在のトレンド
+      unique_uses_today: 本日 %{count} 人がトゥートに使用
+      unreviewed: 未審査
+      updated_msg: ハッシュタグ設定が更新されました
     title: 管理
     warning_presets:
       add_new: 追加
@@ -493,11 +553,21 @@ ja:
       body: "%{reporter} が %{target} を通報しました"
       body_remote: "%{domain} の誰かが %{target} を通報しました"
       subject: "%{instance} の新しい通報 (#%{id})"
+    new_trending_tag:
+      body: 'ハッシュタグ #%{name} が本日のトレンドになっていますが、審査がまだ行われていないためトレンドタグには表示されていません。一度許可すれば次回からこの操作は不要です。'
+      subject: "%{instance} で新しいハッシュタグ (#%{name}) が審査待ちです"
+  aliases:
+    add_new: エイリアスを作成
+    created_msg: エイリアスを作成しました。これで以前のアカウントから引っ越しを開始できます。
+    deleted_msg: エイリアスを削除しました。指定されていたアカウントからは引っ越しできなくなります。
+    hint_html: 他のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。エイリアス自体は<strong>無害で、取り消す</strong>ことができます。<strong>引っ越しは以前のアカウント側から開始する必要があります</strong>。
+    remove: エイリアスを削除
   appearance:
     advanced_web_interface: 上級者向け UI
     advanced_web_interface_hint: ディスプレイを幅いっぱいまで活用したい場合、上級者向け UI をおすすめします。ホーム、通知、連合タイムライン、更にはリストやハッシュタグなど、様々な異なるカラムから望む限りの情報を一度に受け取れるような設定が可能になります。
     animations_and_accessibility: アニメーションとアクセシビリティー
     confirmation_dialogs: 確認ダイアログ
+    discovery: 見つける
     sensitive_content: 閲覧注意コンテンツ
   application_mailer:
     notification_preferences: メール設定の変更
@@ -518,9 +588,13 @@ ja:
     apply_for_account: 登録を申請する
     change_password: パスワード
     checkbox_agreement_html: <a href="%{rules_path}" target="_blank">サーバーのルール</a> と <a href="%{terms_path}" target="_blank">プライバシーポリシー</a> に同意します
-    confirm_email: メールアドレスの確認
+    checkbox_agreement_without_rules_html: <a href="%{terms_path}" target="_blank">利用規約</a> に同意します
     delete_account: アカウントの削除
     delete_account_html: アカウントを削除したい場合、<a href="%{path}">こちら</a> から手続きが行えます。削除する前に、確認画面があります。
+    description:
+      prefix_invited_by_user: "@%{name} があなたをこの Mastodon サーバーに招待しました"
+      prefix_sign_up: 今すぐ Mastodon を始めよう!
+      suffix: アカウントがあれば、どんな Mastodon 互換サーバーのユーザーでもフォローしたりメッセージをやり取りできるようになります!
     didnt_get_confirmation: 確認メールを受信できませんか?
     forgot_password: パスワードをお忘れですか?
     invalid_reset_password_token: パスワードリセットトークンが正しくないか期限切れです。もう一度リクエストしてください。
@@ -538,6 +612,16 @@ ja:
     reset_password: パスワードを再発行
     security: セキュリティ
     set_new_password: 新しいパスワード
+    setup:
+      email_below_hint_html: 下記のメールアドレスが間違っている場合、ここで変更することで新たに確認メールを受信できます。
+      email_settings_hint_html: 確認用のメールを %{email} に送信しました。メールアドレスが正しくない場合、以下より変更することができます。
+      title: セットアップ
+    status:
+      account_status: アカウントの状態
+      confirming: メールアドレスの確認が完了するのを待っています。
+      functional: アカウントは完全に機能しています。
+      pending: あなたの申請は現在サーバー管理者による審査待ちです。これにはしばらくかかります。申請が承認されるとメールが届きます。
+      redirecting_to: アカウントは %{acct} に引っ越し設定されているため非アクティブになっています。
     trouble_logging_in: ログインできませんか?
   authorize_follow:
     already_following: あなたは既にこのアカウントをフォローしています
@@ -550,6 +634,11 @@ ja:
       return: ユーザーのプロフィールを見る
       web: Web を開く
     title: "%{acct} をフォロー"
+  challenge:
+    confirm: 続ける
+    hint_html: 以後 1 時間はパスワードの再入力を求めません
+    invalid_password: パスワードが間違っています
+    prompt: 続行するにはパスワードを入力してください
   datetime:
     distance_in_words:
       about_x_hours: "%{count}時間"
@@ -565,25 +654,33 @@ ja:
       x_months: "%{count}月"
       x_seconds: "%{count}ç§’"
   deletes:
-    bad_password_msg: パスワードが違います
+    challenge_not_passed: 入力された情報は正しくありません
     confirm_password: 本人確認のため、現在のパスワードを入力してください
-    description_html: あなたのアカウントに含まれるコンテンツは全て削除され、アカウントは無効化されます。これは恒久的なもので、<strong>取り消すことはできません</strong>。なりすましを防ぐために、同じユーザー名で再度登録することはできなくなります。
+    confirm_username: 確認のためユーザー名を入力してください
     proceed: アカウントを削除する
     success_msg: アカウントは正常に削除されました
-    warning_html: 削除が保証されるのはこのサーバー上のコンテンツのみです。他のサーバー等、外部に広く共有されたコンテンツについては痕跡が残ることがあります。また、現在接続できないサーバーや、あなたの更新を受け取らなくなったサーバーに対しては、削除は反映されません。
-    warning_title: 共有されたコンテンツについて
+    warning:
+      before: '続行する前に、次の点を再度確認してください:'
+      caches: 他のサーバーにコンテンツのキャッシュがずっと残る場合があります
+      data_removal: あなたの投稿やその他のデータはこのサーバーから完全に削除されます
+      email_change_html: アカウントを削除しなくても<a href="%{path}">メールアドレスを変更</a>できます
+      email_contact_html: それでも届かない場合、<a href="mailto:%{email}">%{email}</a> までメールで問い合わせてください
+      email_reconfirmation_html: 確認のメールが届かない場合、<a href="%{path}">もう一度申請</a>できます。
+      irreversible: アカウントを元に戻したり復活させることはできません
+      more_details_html: 詳しくは<a href="%{terms_path}">プライバシーポリシー</a>をご覧ください。
+      username_available: あなたのユーザー名は再利用できるようになります
+      username_unavailable: あなたのユーザー名は引き続き利用できません
   directories:
     directory: ディレクトリ
-    enabled: あなたはディレクトリに掲載されています。
-    enabled_but_waiting: あなたはディレクトリへの掲載を選択しましたが、掲載に必要な最小フォロワー数 (%{min_followers} 人) を満たしていません。
     explanation: 関心を軸にユーザーを発見しよう
     explore_mastodon: "%{title}を探索"
-    how_to_enable: あなたはディレクトリへの掲載を選択していません。下記から選択できます。ハッシュタグカラムに掲載するにはプロフィール文にハッシュタグを使用してください。
-    people:
-      other: "%{count} 人"
+  domain_validator:
+    invalid_domain: は無効なドメイン名です
   errors:
+    '400': 送信されたリクエストは無効であるか、または不正なフォーマットです。
     '403': このページを表示する権限がありません。
     '404': お探しのページは見つかりませんでした。
+    '406': このページは要求された形式では利用できません。
     '410': お探しのページはもう存在しません。
     '422':
       content: セキュリティ認証に失敗しました。Cookieをブロックしていませんか?
@@ -592,6 +689,7 @@ ja:
     '500':
       content: もうしわけありませんが、なにかが間違っています。
       title: このページは正しくありません
+    '503': 一時的なサーバー障害のため利用することができません。
     noscript_html: Mastodonのウェブアプリケーションを利用する場合はJavaScriptを有効にしてください。またはあなたのプラットフォーム向けの<a href="%{apps_path}">Mastodonネイティブアプリ</a>を探すことができます。
   existing_username_validator:
     not_found: そのようなユーザー名はローカルに見つかりませんでした
@@ -615,6 +713,7 @@ ja:
     add_new: 追加
     errors:
       limit: 注目のハッシュタグの上限に達しました
+    hint_html: "<strong>注目のハッシュタグとは?</strong>プロフィールページに目立つ形で表示され、そのハッシュタグのついたあなたの公開投稿だけを抽出して閲覧できるようにします。クリエイティブな仕事や長期的なプロジェクトを追うのに優れた機能です。"
   filters:
     contexts:
       home: ホームタイムライン
@@ -635,10 +734,12 @@ ja:
     developers: 開発者向け
     more: さらに…
     resources: リソース
+    trending_now: トレンドタグ
   generic:
     all: すべて
     changes_saved_msg: 正常に変更されました!
     copy: コピー
+    no_batch_actions_available: このページに一括操作はありません
     order_by: 並び順
     save_changes: 変更を保存
     validation_errors:
@@ -689,7 +790,7 @@ ja:
       '604800': 1 週間
       '86400': 1 æ—¥
     expires_in_prompt: 無期限
-    generate: 作成
+    generate: 招待リンクを作成
     invited_by: '次の人に招待されました:'
     max_uses:
       other: "%{count}"
@@ -708,9 +809,34 @@ ja:
       too_many: 追加できるファイルは4つまでです
   migrations:
     acct: 引っ越し先の ユーザー名@ドメイン
-    currently_redirecting: 'あなたのプロフィールは引っ越し先が設定されています:'
-    proceed: 保存
-    updated_msg: アカウントの引っ越し設定を更新しました!
+    cancel: 引っ越しを取り消す
+    cancel_explanation: 引っ越しを取り消すと現在のアカウントが再度有効化されますが、引き継がれたフォロワーは戻されません。
+    cancelled_msg: 引っ越し設定を取り消しました。
+    errors:
+      already_moved: は既に引っ越したアカウントと同じです
+      missing_also_known_as: はこのアカウントとエイリアスの設定がされていません
+      move_to_self: 同じアカウントに引っ越すことはできません
+      not_found: 見つかりませんでした
+      on_cooldown: クールダウン期間中です
+    followers_count: 引き継がれるフォロワー
+    incoming_migrations: 別のアカウントから引っ越す
+    incoming_migrations_html: 別のアカウントからこのアカウントに引っ越すには、まず<a href="%{path}">アカウントエイリアスを作成</a>する必要があります。
+    moved_msg: アカウントは %{acct} に引っ越し設定されており、フォロワーは引っ越し先に引き継がれています。
+    not_redirecting: アカウントは現在引っ越し設定されていません。
+    on_cooldown: あなたは最近アカウントを引っ越しました。この機能は %{count} 日後に再度利用できるようになります。
+    past_migrations: 過去の引っ越し
+    proceed_with_move: フォロワーを引き継ぐ
+    redirecting_to: アカウントは %{acct} に引っ越し設定されています。
+    set_redirect: 引っ越しを設定
+    warning:
+      backreference_required: まずは引っ越し先のアカウントでこのアカウントに対しエイリアスを作成する必要があります
+      before: '続行する前に、次の点を再度確認してください:'
+      cooldown: 引っ越し後はクールダウン期間があります。その間再度引っ越すことはできません
+      disabled_account: 引っ越した後はデータのエクスポートと再有効化を除きほとんどの機能が利用できなくなります
+      followers: すべてのフォロワーを現在のアカウントから新しいアカウントに引き継ぎます
+      only_redirect_html: または、<a href="%{path}">フォロワーを残したまま引っ越す</a>こともできます。
+      other_data: その他のデータは自動的に引き継がれません
+      redirect: プロフィールに引っ越し済みの通知が表示され、検索結果から除外されます
   moderation:
     title: モデレーション
   notification_mailer:
@@ -805,10 +931,6 @@ ja:
     reply:
       proceed: 返信する
       prompt: '返信しようとしています:'
-  remote_unfollow:
-    error: エラー
-    title: タイトル
-    unfollowed: フォロー解除しました
   scheduled_statuses:
     over_daily_limit: その日予約できる投稿数 %{limit} を超えています
     over_total_limit: 予約できる投稿数 %{limit} を超えています
@@ -856,7 +978,8 @@ ja:
     title: セッション
   settings:
     account: アカウント
-    account_settings: セキュリティ
+    account_settings: アカウント設定
+    aliases: アカウントエイリアス
     appearance: 外観
     authorized_apps: 認証済みアプリ
     back: Mastodon に戻る
@@ -874,6 +997,8 @@ ja:
     profile: プロフィール
     relationships: フォロー・フォロワー
     two_factor_authentication: 二段階認証
+  spam_check:
+    spam_detected: これは自動的に作成された通報です。スパムが検出されています。
   statuses:
     attached:
       description: '添付: %{attached}'
@@ -894,6 +1019,8 @@ ja:
       private: 非公開のトゥートを固定することはできません
       reblog: ブーストを固定することはできません
     poll:
+      total_people:
+        other: "%{count} 人"
       total_votes:
         other: "%{count}票"
       vote: 投票
@@ -911,6 +1038,8 @@ ja:
     pinned: 固定されたトゥート
     reblogged: さんがブースト
     sensitive_content: 閲覧注意
+  tags:
+    does_not_match_previous_name: 以前の名前と一致しません
   terms:
     body_html: |
       <h2>プライバシーポリシー</h2>
@@ -1003,7 +1132,7 @@ ja:
       default: "%Y年%m月%d日 %H:%M"
       month: "%Yå¹´ %b"
   two_factor_authentication:
-    code_hint: 確認するには認証アプリで表示されたコードを入力してください
+    code_hint: 続行するには認証アプリで表示されたコードを入力してください
     description_html: "<strong>二段階認証</strong>を有効にするとログイン時、認証アプリからコードを入力する必要があります。"
     disable: 無効
     enable: 有効
@@ -1028,7 +1157,9 @@ ja:
         disable: アカウントが凍結されている間、データはそのまま残りますが、凍結が解除されるまでは何の操作もできません。
         silence: あなたのアカウントは制限されていますが、あなたをフォローしているユーザーのみ、このサーバー上の投稿を見ることができます。そしてあなたは様々な公開リストから除外されるかもしれません。ただし、他のユーザーは手動であなたをフォローすることができます。
         suspend: あなたのアカウントは停止されています。あなたの投稿とアップロードされたメディアファイルは、このサーバーとあなたのフォロワーが参加していたサーバーから完全に削除されました。
+      get_in_touch: このメールに返信することで %{instance} のスタッフと連絡を取ることができます。
       review_server_policies: サーバーのポリシーを確認
+      statuses: '特に次のトゥート:'
       subject:
         disable: あなたのアカウント %{acct} は凍結されています
         none: "%{acct} に対する警告"
diff --git a/config/locales/ka.yml b/config/locales/ka.yml
index 53057d8603312cd6fa368577bfe041c572c5c482..0ab1f2d69da1adc37222057e15bb3708c599f454 100644
--- a/config/locales/ka.yml
+++ b/config/locales/ka.yml
@@ -11,10 +11,6 @@ ka:
     contact_missing: არაა დაყენებული
     contact_unavailable: მიუწ.
     documentation: დოკუმენტაცია
-    extended_description_html: |
-      <h3>კარგი ადგილი წესებისთვის</h3>
-      <p>განვრცობილი აღწერილობა ჯერ არ შექმნილა.</p>
-    generic_description: "%{domain} ერთი სერვერია ქსელში"
     hosted_on: მასტოდონს მასპინძლობს %{domain}
     learn_more: გაიგე მეტი
     privacy_policy: კონფიდენციალურობის პოლიტიკა
@@ -368,13 +364,6 @@ ka:
       no_status_selected: სატუსები არ შეცვლილა, რადგან არცერთი არ მონიშნულა
       title: ანგარიშის სტატუსები
       with_media: მედიით
-    subscriptions:
-      callback_url: ქოლბექ ურლ
-      confirmed: დამოწმდა
-      expires_in: ვადა გასდის
-      last_delivery: ბოლო მიღება
-      title: ვებ-საბი
-      topic: სათაური
     title: ადმინისტრაცია
   admin_mailer:
     new_report:
@@ -397,7 +386,6 @@ ka:
     your_token: თქვენი წვდომის ტოკენი
   auth:
     change_password: პაროლი
-    confirm_email: ელ-ფოსტის დამოწმება
     delete_account: ანგარიშის გაუქმება
     delete_account_html: თუ გსურთ გააუქმოთ თქვენი ანგარიში, შეგიძლიათ <a href="%{path}">გააგრძელოთ აქ</a>. საჭირო იქნება დამოწმება.
     didnt_get_confirmation: არ მოგსვლიათ დამოწმების ინსტრუქციები?
@@ -442,16 +430,14 @@ ka:
       x_months: "%{count}თვე"
       x_seconds: "%{count}წმ"
   deletes:
-    bad_password_msg: კარგად სცადეთ, ჰაკერებო! არასწორი პაროლი
     confirm_password: იდენტობის დასამოწმებლად შეიყვანეთ მიმდინარე პაროლი
-    description_html: ეს <strong>სამუდამოდ, დაუბრუნებლად</strong> გააუქმებს კონტენტს თქვენი ანგარიშიდან და მოახდენს მის დეაქტივაციას. მომხმარებლის სახელი კი, სამომავლო იმპერსონაციების შესაჩერებლად, გახდება რეზერვირებული.
     proceed: ანგარიშის გაუქმება
     success_msg: თქვენი ანგარიში წარმატებით გაუქმდა
-    warning_html: მოცულობის გაუქმება გარანტირებულია მხოლოდ ამ ინსტანციაზე. კონტენტი რომელიც ფართო მასშტაბით გაზიარდა უფრო დატოვებს კვალს. ოფლაინ სერვერები და სერვერები, რომლებმაც შეწყვიტეს თქვენი განახლებების გამოწერა არ განაახლებენ მონაცემთა ბაზებს.
-    warning_title: წვდომა გავრცელებულ კონტენტზე
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': ამ გვერდის ხილვის უფლება არ გაქვთ.
     '404': გვერდი რომელსაც ეძებთ არ არსებობს.
+    '406': This page is not available in the requested format.
     '410': გვერდი რომელსაც ეძებდით აღარ არსებობს.
     '422':
       content: უსაფრთხოების ვერიფიკაცია ვერ მოხერხდა. ბლოკავთ ქუქის?
@@ -460,6 +446,7 @@ ka:
     '500':
       content: ბოდიში, ჩვენ მხარეს რაღაც არია.
       title: გვერდი არაა სწორი
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: მასტოდონ ვებ-აპლიკაციის გამოყენებისთვის, გთხოვთ ჩართოთ ჯავასკრიპტი. სხვა შემთხვევაში, მასტოდონის თქვენი პატფორმისთვის სცადეთ გამოიყენოთ ერთ-ერთი <a href="%{apps_path}">მშობლიური აპლიკაცია</a>.
   exports:
     archive_takeout:
@@ -540,9 +527,6 @@ ka:
       too_many: თან ვერ დაურთავთ 4 ფაილზე მეტს
   migrations:
     acct: username@domain ახალი ანგარიშის
-    currently_redirecting: 'თქვენი პროფილი გამართულია მოახდინოს გადამისამართება მისამართზე:'
-    proceed: შენახვა
-    updated_msg: თქვენი ანგარიშის მიგრაციის პარამეტრები წარმატეებით დამახსოვრდა!
   moderation:
     title: მოდერაცია
   notification_mailer:
@@ -601,10 +585,6 @@ ka:
     no_account_html: არ გაქვთ ანგარიში? შეგიძლიათ <a href='%{sign_up_path}' target='_blank'>დარეგისტრირდეთ აქ</a>
     proceed: გააგრძელეთ გასაყოლად
     prompt: 'თქვენ გაჰყვებით:'
-  remote_unfollow:
-    error: შეცდომა
-    title: სათაური
-    unfollowed: დადევნების შეწყვეტა
   sessions:
     activity: ბოლო აქტივობა
     browser: ბრაუზერი
diff --git a/config/locales/kk.yml b/config/locales/kk.yml
index c6212c378fd33925b54416da0fb61617cd34d726..49cc18a4cce8c043418c9248ad4e3f1ebdea64eb 100644
--- a/config/locales/kk.yml
+++ b/config/locales/kk.yml
@@ -10,10 +10,6 @@ kk:
     contact_missing: Бапталмаған
     contact_unavailable: Белгісіз
     documentation: Құжаттама
-    extended_description_html: |
-      <h3>Ережелерге арналған жақсы орын</h3>
-      <p>Әлі ештеңе жазылмапты</p>
-    generic_description: "%{domain} желідегі серверлердің бірі"
     hosted_on: Mastodon орнатылған %{domain} доменінде
     learn_more: Көбірек білу
     privacy_policy: Құпиялылық саясаты
@@ -436,21 +432,8 @@ kk:
       no_status_selected: Бірде-бір статус өзгерген жоқ, себебі ештеңе таңдалмады
       title: Аккаунт статустары
       with_media: Медиамен
-    subscriptions:
-      callback_url: Callbаck URL
-      confirmed: Confirmеd
-      expires_in: Expirеs in
-      last_delivery: Last dеlivery
-      title: WеbSub
-      topic: Tоpic
     tags:
-      accounts: Accоunts
-      hidden: Hiddеn
-      hide: Hidе from directory
-      name: Hаshtag
       title: Hashtаgs
-      unhide: Shоw in directory
-      visible: Visiblе
     title: Administrаtion
     warning_presets:
       add_new: Add nеw
@@ -479,7 +462,6 @@ kk:
     your_token: Your access tokеn
   auth:
     change_password: Құпиясөз
-    confirm_email: Еmаil құптау
     delete_account: Аккаунт өшіру
     delete_account_html: Аккаунтыңызды жойғыңыз келсе, <a href="%{path}">мына жерді</a> басыңыз. Сізден растау сұралатын болады.
     didnt_get_confirmation: Растау хаты келмеді ме?
@@ -524,26 +506,18 @@ kk:
       x_months: "%{count}ай"
       x_seconds: "%{count}сек"
   deletes:
-    bad_password_msg: Болмады ма, хакер бала? Құпиясөз қате
     confirm_password: Қазіргі құпиясөзіңізді жазыңыз
-    description_html: This will <strong>permanently, irreversibly</strong> remove content from your account аnd deactivate it. Your username will remain reserved to prevent future impersonations.
     proceed: Аккаунт өшіру
     success_msg: Аккаунтыңыз сәтті өшірілді
-    warning_html: Only deletion of content from this particular server is guaranteed. Content that has been widely sharеd is likely to leave traces. Offline servers and servers that have unsubscribed from your updates will not update their databases.
-    warning_title: Бөлінген мазмұнның қол жетімділігі
   directories:
     directory: Профильдер каталогы
-    enabled: Каталогтағы тізімге ендіңіз.
-    enabled_but_waiting: Каталогта көрінгіңіз келетінін түсінеміз, бірақ ол үшін кем дегенде (%{min_followers}) оқырманыңыз болуы қажет.
     explanation: Қолданушыларды қызығушылықтарына қарай реттеу
     explore_mastodon: "%{title} шарлау"
-    how_to_enable: Сіз қазіргі уақытта каталогқа қосылмағансыз. Төменде қосылуға болады. Арнайы био мәтініндегі хэштегтерді қолданыңыз!
-    people:
-      one: "%{count} адам"
-      other: "%{count} адам"
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Бұны көру үшін сізде рұқсат жоқ.
     '404': Сіз іздеген бет бұл жерде емес екен.
+    '406': This page is not available in the requested format.
     '410': Сіз іздеген бет қазір жоқ екен.
     '422':
       content: Қауіпсіздік растауы қате. кукилерді блоктағансыз ба?
@@ -552,6 +526,7 @@ kk:
     '500':
       content: Кешірерсіз, бірақ қазір бір қате пайда болып тұр.
       title: Бұл бет дұрыс емес екен
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: Mastodon веб қосымшасын қолдану үшін, JavaScript қосыңыз. Болмай жатса, <a href="%{apps_path}">мына қосымшаларды</a> қосып көріңіз, Mastodon қолдану үшін.
   exports:
     archive_takeout:
@@ -645,9 +620,6 @@ kk:
       too_many: 4 файлдан артық қосылмайды
   migrations:
     acct: жаңа аккаунт үшін username@domain
-    currently_redirecting: 'Профиліңіз көшіріледі:'
-    proceed: Сақтау
-    updated_msg: Аккаунт көшіруіңіз сәтті аяқталды!
   moderation:
     title: Модерация
   notification_mailer:
@@ -726,10 +698,6 @@ kk:
     reply:
       proceed: Жауап жазу
       prompt: 'Сіз мына жазбаға жауап жазасыз:'
-  remote_unfollow:
-    error: Қате
-    title: Тақырыбы
-    unfollowed: Жазылудан бас тартылды
   scheduled_statuses:
     over_daily_limit: Сіз бір күндік %{limit} жазба лимитін тауыстыңыз
     over_total_limit: Сіз %{limit} жазба лимитін тауыстыңыз
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index 3f14d5df64e70359bde3ca8ff560c9a8ed8aac11..bae3d69bcac35674e724d21cc36b85686bd8110f 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -1,7 +1,7 @@
 ---
 ko:
   about:
-    about_hashtag_html: "<strong>#%{hashtag}</strong> 라는 해시태그가 붙은 공개 툿 입니다. 같은 연합에 속한 임의의 인스턴스에 계정을 생성하면 당신도 대화에 참여할 수 있습니다."
+    about_hashtag_html: "<strong>#%{hashtag}</strong> 해시태그가 붙은 공개 툿 입니다. 같은 연합에 속한 임의의 인스턴스에 계정을 생성하면 당신도 대화에 참여할 수 있습니다."
     about_mastodon_html: 마스토돈은 <em>오픈 소스 기반의</em> 소셜 네트워크 서비스 입니다. 상용 플랫폼의 대체로서 <em>분산형 구조</em>를 채택해, 여러분의 대화가 한 회사에 독점되는 것을 방지합니다. 신뢰할 수 있는 인스턴스를 선택하세요 &mdash; 어떤 인스턴스를 고르더라도, 누구와도 대화할 수 있습니다. 누구나 자신만의 마스토돈 인스턴스를 만들 수 있으며, 아주 매끄럽게 <em>소셜 네트워크</em>에 참가할 수 있습니다.
     about_this: 이 인스턴스에 대해서
     active_count_after: 활성 사용자
@@ -17,13 +17,12 @@ ko:
     contact_unavailable: 없음
     discover_users: 유저 발견하기
     documentation: 문서
-    extended_description_html: |
-      <h3>룰을 작성하는 장소</h3>
-      <p>아직 설명이 작성되지 않았습니다.</p>
     federation_hint_html: "%{instance}에 계정을 만드는 것으로 모든 마스토돈 서버, 그리고 호환 되는 모든 서버의 사용자를 팔로우 할 수 있습니다."
-    generic_description: "%{domain} 은 네트워크에 있는 한 서버입니다"
     get_apps: 모바일 앱 사용해 보기
     hosted_on: "%{domain}에서 호스팅 되는 마스토돈"
+    instance_actor_flash: |
+      이 계정은 가상의 actor로서 개인 유저가 아닌 서버 자체를 나타냅니다.
+      이것은 페더레이션을 목적으로 사용 되며 인스턴스 전체를 차단하려 하지 않는 이상 차단하지 않아야 합니다, 그 경우에는 도메인 차단을 사용하세요.
     learn_more: 자세히
     privacy_policy: 개인정보 정책
     see_whats_happening: 무슨 일이 일어나는 지 보기
@@ -34,12 +33,22 @@ ko:
     status_count_before: 툿 수
     tagline: 친구들을 팔로우 하고 새로운 사람들도 만나기
     terms: 이용약관
+    unavailable_content: 이용 불가능한 컨텐츠
+    unavailable_content_description:
+      domain: 서버
+      reason: 이유
+      rejecting_media: 이 서버의 미디어 파일들은 처리되지 않고 썸네일또한 보이지 않게 됩니다. 수동으로 클릭하여 해당 서버로 가게 됩니다.
+      silenced: 이 서버의 게시물은 작성자를 팔로우 한 경우에만 홈 피드에 나타나며 이를 제외한 어디에도 나타나지 않습니다.
+      suspended: 이 서버의 아무도 팔로우 할 수 없으며, 어떤 데이터도 처리되거나 저장 되지 않고 데이터가 교환 되지도 않습니다.
+    unavailable_content_html: 마스토돈은 일반적으로 연합우주에 있는 어떤 서버의 유저와도 게시물을 보고 응답을 할 수 있도록 허용합니다. 다음 항목들은 특정한 서버에 대해 만들어 진 예외사항입니다.
     user_count_after:
       other: 명
     user_count_before: 사용자 수
     what_is_mastodon: 마스토돈이란?
   accounts:
     choices_html: "%{name}의 추천:"
+    endorsements_hint: 내가 팔로우 하고 있는 사람들을 여기에 추천 할 수 있습니다.
+    featured_tags_hint: 특정한 해시태그들을 여기에 표시되도록 할 수 있습니다.
     follow: 팔로우
     followers:
       other: 팔로워
@@ -50,6 +59,7 @@ ko:
     media: 미디어
     moved_html: "%{name}은 %{new_profile_link}으로 이동되었습니다:"
     network_hidden: 이 정보는 사용할 수 없습니다
+    never_active: 없음
     nothing_here: 아무 것도 없습니다!
     people_followed_by: "%{name} 님이 팔로우 중인 계정"
     people_who_follow: "%{name} 님을 팔로우 중인 계정"
@@ -179,6 +189,7 @@ ko:
       username: 아이디
       warn: 경고
       web: 웹
+      whitelisted: 허용 목록
     action_logs:
       actions:
         assigned_to_self_report: "%{name}이 리포트 %{target}을 자신에게 할당했습니다"
@@ -214,19 +225,24 @@ ko:
       deleted_status: "(삭제됨)"
       title: 감사 기록
     custom_emojis:
+      assign_category: 분류 지정
       by_domain: 도메인
       copied_msg: 성공적으로 에모지의 로컬 복사본을 생성했습니다
       copy: 복사
       copy_failed_msg: 에모지의 로컬 복사본을 만드는 데 실패하였습니다
+      create_new_category: 분류 생성
       created_msg: 에모지가 성공적으로 생성되었습니다!
       delete: 삭제
       destroyed_msg: 에모지가 성공적으로 삭제되었습니다!
       disable: 비활성화
+      disabled: 비활성
       disabled_msg: 성공적으로 비활성화하였습니다
       emoji: 에모지
       enable: 활성화
+      enabled: 활성됨
       enabled_msg: 성공적으로 활성화하였습니다
       image_hint: 50KB 이하의 PNG
+      list: 목록
       listed: 목록에 실림
       new:
         title: 새 커스텀 에모지 추가
@@ -234,11 +250,14 @@ ko:
       shortcode: 짧은 코드
       shortcode_hint: 최소 2글자, 영문자, 숫자, _만 사용 가능
       title: 커스텀 에모지
+      uncategorized: 분류되지 않음
+      unlist: 목록에서 제거
       unlisted: 목록에 없음
       update_failed_msg: 에모지를 업데이트 할 수 없습니다
       updated_msg: 에모지가 성공적으로 업데이트 되었습니다!
       upload: 업로드
     dashboard:
+      authorized_fetch_mode: 인증 된 페치 모드
       backlog: 미처리 된 작업
       config: 설정
       feature_deletions: 계정 삭제
@@ -246,10 +265,13 @@ ko:
       feature_profile_directory: 프로필 디렉토리
       feature_registrations: 가입
       feature_relay: 연합 릴레이
+      feature_spam_check: 안티 스팸
       feature_timeline_preview: 타임라인 미리보기
       features: 기능
       hidden_service: 히든 서비스와의 연합
       open_reports: 미해결 신고
+      pending_tags: 심사를 기다리는 해시태그
+      pending_users: 심사를 기다리는 유저
       recent_users: 최근 가입 한 유저
       search: 전문 검색
       single_user_mode: 싱글 유저 모드
@@ -261,11 +283,18 @@ ko:
       week_interactions: 이번 주의 상호작용
       week_users_active: 이번 주의 활성 사용자
       week_users_new: 이번 주의 신규 유저
+      whitelist_mode: 화이트리스트 모드
+    domain_allows:
+      add_new: 허용 된 도메인
+      created_msg: 도메인이 성공적으로 허용 목록에 추가되었습니다
+      destroyed_msg: 도메인이 허용 목록에서 제거되었습니다
+      undo: 허용 목록에서 제외
     domain_blocks:
       add_new: 도메인 차단 추가하기
       created_msg: 도메인 차단 처리를 완료했습니다
       destroyed_msg: 도메인 차단이 해제되었습니다
       domain: 도메인
+      edit: 도메인 차단 수정
       existing_domain_block_html: 이미 %{name}에 대한 더 강력한 제한이 걸려 있습니다, <a href="%{unblock_url}">차단 해제</a>를 먼저 해야 합니다.
       new:
         create: 차단 추가
@@ -278,6 +307,10 @@ ko:
           silence: 침묵
           suspend: ì •ì§€
         title: 새로운 도메인 차단
+      private_comment: 비공개 주석
+      private_comment_hint: 이 도메인 제한에 대한 주석은 모더레이터를 위해 내부적으로 사용 됩니다.
+      public_comment: 공개 주석
+      public_comment_hint: 이 도메인 제한에 대한 공개적인 주석, 도메인 제한 공개를 활성화 한 경우 보여집니다.
       reject_media: 미디어 파일 거부하기
       reject_media_hint: 로컬에 저장된 미디어 파일을 삭제하고, 이후로도 다운로드를 거부합니다. 정지와는 관계 없습니다
       reject_reports: ì‹ ê³  ê±°ë¶€
@@ -296,6 +329,7 @@ ko:
         title: "%{domain}의 도메인 차단을 해제"
         undo: 실행 취소
       undo: 도메인 차단 취소
+      view: 도메인 차단 보기
     email_domain_blocks:
       add_new: 새로 추가
       created_msg: 이메일 도메인 차단 규칙을 생성했습니다
@@ -318,6 +352,8 @@ ko:
         all: 모두
         limited: 제한됨
         title: 모더레이션
+      private_comment: 비공개 주석
+      public_comment: 공개 주석
       title: ì—°í•©
       total_blocked_by_us: 우리에게 차단 됨
       total_followed_by_them: 우리를 팔로우
@@ -347,6 +383,7 @@ ko:
       pending: 릴레이의 승인 대기중
       save_and_enable: 저장하고 활성화
       setup: 릴레이 연결 설정
+      signatures_not_enabled: 시큐어모드나 화이트리스트모드를 사용하고 있다면 릴레이는 제대로 동작하지 않을 것입니다
       status: 상태
       title: 릴레이
     report_notes:
@@ -395,6 +432,16 @@ ko:
       custom_css:
         desc_html: 모든 페이지에 적용할 CSS
         title: 커스텀 CSS
+      default_noindex:
+        desc_html: 이 설정을 바꾸지 않은 모든 유저들에게 적용 됩니다
+        title: 유저들이 기본적으로 검색엔진에 인덱싱 되지 않도록 합니다
+      domain_blocks:
+        all: 모두에게
+        disabled: 아무에게도 안 함
+        title: 도메인 차단 보여주기
+        users: 로그인 한 유저에게
+      domain_blocks_rationale:
+        title: 사유 보여주기
       hero:
         desc_html: 프론트페이지에 표시 됩니다. 최소 600x100픽셀을 권장합니다. 만약 설정되지 않았다면, 서버의 썸네일이 사용 됩니다
         title: 히어로 이미지
@@ -415,7 +462,7 @@ ko:
           desc_html: 신규 등록을 받지 않을 때 프론트 페이지에 표시됩니다. HTML 태그를 사용할 수 있습니다
           title: 신규 등록 정지 시 메시지
         deletion:
-          desc_html: 유저가 자신의 계정을 삭제할 수 있도록 설정합니다
+          desc_html: 유저가 자신의 계정을 삭제할 수 있도록 허용합니다
           title: 계정 삭제를 허가함
         min_invite_role:
           disabled: 아무도 못 하게
@@ -445,6 +492,9 @@ ko:
         desc_html: 당신은 독자적인 개인정보 취급 방침이나 이용약관, 그 외의 법적 근거를 작성할 수 있습니다. HTML태그를 사용할 수 있습니다
         title: 커스텀 서비스 이용 약관
       site_title: 서버 이름
+      spam_check_enabled:
+        desc_html: 마스토돈은 반복된 메시지 등의 측정값에 따라 자동으로 계정을 침묵, 신고할 수 있습니다. 위양성(False-positive)이 존재할 수 있습니다.
+        title: 안티 스팸
       thumbnail:
         desc_html: OpenGraph와 API의 미리보기로 사용 됩니다. 1200x630px을 권장합니다
         title: 서버 썸네일
@@ -452,12 +502,19 @@ ko:
         desc_html: 랜딩 페이지에 공개 타임라인을 표시합니다
         title: 타임라인 프리뷰
       title: 사이트 설정
+      trendable_by_default:
+        desc_html: 이전에 비허용 되지 않은 해시태그들에 영향을 미칩니다
+        title: 해시태그가 사전 리뷰 없이 트렌드에 올라갈 수 있도록 허용
+      trends:
+        desc_html: 리뷰를 거친 해시태그를 유행하는 해시태그에 공개적으로 보여줍니다
+        title: 유행하는 해시태그
     statuses:
       back_to_account: 계정으로 돌아가기
       batch:
         delete: 삭제
         nsfw_off: NSFW 끄기
         nsfw_on: NSFW 켜기
+      deleted: 삭제됨
       failed_to_execute: 실행을 실패하였습니다
       media:
         title: 미디어
@@ -465,21 +522,24 @@ ko:
       no_status_selected: 아무 것도 선택 되지 않아 아무 것도 바뀌지 않았습니다
       title: 계정 툿
       with_media: 미디어 있음
-    subscriptions:
-      callback_url: 콜백 URL
-      confirmed: 확인됨
-      expires_in: 기한
-      last_delivery: 최종 발송
-      title: WebSub
-      topic: 토픽
     tags:
-      accounts: 계정들
-      hidden: 숨겨짐
-      hide: 디렉토리에서 숨기기
+      accounts_today: 오늘의 순 사용자
+      accounts_week: 금주의 순 사용자
+      breakdown: 소스별 오늘의 사용량 분석
+      context: 문맥
+      directory: 디렉토리에 있음
+      in_directory: 디렉토리에 %{count}개 있음
+      last_active: 최근 활동
+      most_popular: 최고 인기
+      most_recent: 최신
       name: 해시태그
+      review: 심사 상태
+      reviewed: 심사 됨
       title: 해시태그
-      unhide: 디렉토리에 표시
-      visible: 보여짐
+      trending_right_now: 지금 유행 중
+      unique_uses_today: 오늘 %{count}명이 포스팅
+      unreviewed: 심사 되지 않음
+      updated_msg: 해시태그 설정이 성공적으로 갱신되었습니다
     title: 관리
     warning_presets:
       add_new: 새로 추가
@@ -495,11 +555,21 @@ ko:
       body: "%{reporter} 가 %{target} 를 신고했습니다"
       body_remote: "%{domain}의 누군가가 %{target}을 신고했습니다"
       subject: "%{instance} 에 새 신고 등록됨 (#%{id})"
+    new_trending_tag:
+      body: '오늘 #%{name} 해시태그가 유행하고 있습니다, 하지만 심사 된 적이 없습니다. 허용하지 않는 한 공개적으로 나타나지 않습니다. 또는 그냥 저장을 눌러 더이상 나타나지 않게 할 수 있습니다.'
+      subject: 새 해시태그가 %{instance}에서 심사 대기 중입니다(#%{name})
+  aliases:
+    add_new: 별칭 만들기
+    created_msg: 새 별칭이 성공적으로 만들어졌습니다. 이제 기존 계정에서 이주를 시작할 수 있습니다.
+    deleted_msg: 성공적으로 별칭을 삭제했습니다. 해당 계정에서 이 계정으로의 이주는 더이상 가능하지 않습니다.
+    hint_html: 다른 계정에서 이 계정으로 옮기길 원하는 경우, 여기에서 별칭을 만들 수 있습니다, 기존 계정의 팔로워를 이쪽으로 옮기고 싶은 경우 필요한 과정입니다. 이 행동 자체는 <strong>해롭지 않고 되돌리기가 가능합니다</strong>.<strong>계정 이주는 이전 계정에서 착수하게 됩니다</strong>
+    remove: 별칭 연결 끊기
   appearance:
     advanced_web_interface: 고급 웹 인터페이스
     advanced_web_interface_hint: '화면의 가로폭을 가득 채우고 싶다면, 고급 웹 인터페이스는 한 번에 여러 정보를 볼 수 있도록 여러 컬럼을 설정할 수 있도록 합니다: 홈, 알림, 연합타임라인, 리스트, 해시태그 등'
     animations_and_accessibility: 애니메이션과 접근성
     confirmation_dialogs: 확인 대화상자
+    discovery: 발견하기
     sensitive_content: 민감한 내용
   application_mailer:
     notification_preferences: 메일 설정 변경
@@ -520,9 +590,13 @@ ko:
     apply_for_account: 가입 요청하기
     change_password: 패스워드
     checkbox_agreement_html: <a href="%{rules_path}" target="_blank">서버 규칙</a>과 <a href="%{terms_path}" target="_blank">이용약관</a>에 동의합니다
-    confirm_email: 확인 메일 승인
+    checkbox_agreement_without_rules_html: <a href="%{terms_path}" target="_blank">이용 약관</a>에 동의합니다
     delete_account: 계정 삭제
     delete_account_html: 계정을 삭제하고 싶은 경우, <a href="%{path}">여기서</a> 삭제할 수 있습니다. 삭제 전 확인 화면이 표시됩니다.
+    description:
+      prefix_invited_by_user: "@%{name} 님이 당신을 이 마스토돈 서버로 초대했습니다!"
+      prefix_sign_up: 마스토돈에 가입하세요!
+      suffix: 계정 하나로 사람들을 팔로우 하고, 게시물을 작성하며 마스토돈을 포함한 다른 어떤 서버의 유저와도 메시지를 주고 받을 수 있습니다!
     didnt_get_confirmation: 확인 메일을 받지 못하셨습니까?
     forgot_password: 비밀번호를 잊어버리셨습니까?
     invalid_reset_password_token: 암호 리셋 토큰이 올바르지 못하거나 기간이 만료되었습니다. 다시 요청해주세요.
@@ -540,6 +614,16 @@ ko:
     reset_password: 암호 재설정
     security: 보안
     set_new_password: 새 암호
+    setup:
+      email_below_hint_html: 아래의 이메일 계정이 올바르지 않을 경우, 여기서 변경하고 새 확인 메일을 받을 수 있습니다.
+      email_settings_hint_html: 확인 메일이 %{email}로 보내졌습니다. 이메일 주소가 올바르지 않은 경우, 계정 설정에서 변경하세요.
+      title: 설정
+    status:
+      account_status: 계정 상태
+      confirming: 이메일 확인 과정이 완료되기를 기다리는 중.
+      functional: 계정이 완벽이 작동합니다.
+      pending: 당신의 가입 신청은 스태프의 검사를 위해 대기중입니다. 이것은 시간이 다소 소요됩니다. 가입 신청이 승인 될 경우 이메일을 받게 됩니다.
+      redirecting_to: 계정이 %{acct}로 리다이렉트 중이기 때문에 비활성 상태입니다.
     trouble_logging_in: 로그인 하는데 문제가 있나요?
   authorize_follow:
     already_following: 이미 이 계정을 팔로우 하고 있습니다
@@ -552,6 +636,11 @@ ko:
       return: 유저 프로필 보기
       web: 웹으로 가기
     title: "%{acct} 를 팔로우"
+  challenge:
+    confirm: 계속
+    hint_html: "<strong>팁:</strong> 한 시간 동안 다시 암호를 묻지 않을 것입니다."
+    invalid_password: 잘못된 암호
+    prompt: 계속하려면 암호 확인
   datetime:
     distance_in_words:
       about_x_hours: "%{count}시간"
@@ -567,25 +656,33 @@ ko:
       x_months: "%{count}ì›”"
       x_seconds: "%{count}ì´ˆ"
   deletes:
-    bad_password_msg: 비밀번호가 올바르지 않습니다
+    challenge_not_passed: 입력한 정보가 올바르지 않습니다
     confirm_password: 본인 확인을 위해 현재 사용 중인 암호를 입력해 주십시오
-    description_html: 계정에 업로드된 모든 컨텐츠가 삭제되며, 계정은 비활성화 됩니다. 이것은 영구적으로 이루어지는 것이므로 <strong>되돌릴 수 없습니다</strong>. 사칭 행위를 방지하기 위해 같은 아이디로 다시 등록하는 것은 불가능합니다.
+    confirm_username: 절차를 진행하려면 당신의 사용자명을 입력하세요
     proceed: 계정 삭제
     success_msg: 계정이 성공적으로 삭제되었습니다
-    warning_html: 삭제가 보장되는 것은 이 서버 상에서의 컨텐츠에 한합니다. 타 서버 등, 외부에 멀리 공유된 컨텐츠는 흔적이 남아 삭제되지 않는 경우도 있습니다. 그리고 현재 접속이 불가능한 서버나, 업데이트를 받지 않게 된 서버에 대해서는 삭제가 반영되지 않을 수도 있습니다.
-    warning_title: 공유된 컨텐츠에 대해서
+    warning:
+      before: '진행하기 전, 주의사항을 꼼꼼히 읽어보세요:'
+      caches: 다른 서버에 캐싱 된 정보들은 남아있을 수 있습니다
+      data_removal: 당신의 게시물과 다른 정보들은 영구적으로 삭제 됩니다
+      email_change_html: 계정을 지우지 않고도 <a href="%{path}">이메일 주소를 수정할 수 있습니다</a>
+      email_contact_html: 아직 도착하지 않았다면, <a href="mailto:%{email}">%{email}</a>에 메일을 보내 도움을 요청할 수 있습니다
+      email_reconfirmation_html: 아직 확인 메일이 도착하지 않은 경우, <a href="%{path}">다시 요청할 수 있습니다</a>
+      irreversible: 계정을 복구하거나 다시 사용할 수 없게 됩니다
+      more_details_html: 더 자세한 정보는, <a href="%{terms_path}">개인정보 정책</a>을 참고하세요.
+      username_available: 당신의 계정명은 다시 사용할 수 있게 됩니다
+      username_unavailable: 당신의 계정명은 앞으로 사용할 수 없습니다
   directories:
     directory: 프로필 디렉토리
-    enabled: 당신은 디렉터리에 표시 되고 있습니다.
-    enabled_but_waiting: 디렉터리에 표시 되기 위해서는 수동으로 참여해야 합니다, 하지만 디렉터리에 표시 되기 위한 최소 팔로워(%{min_followers})수에 미치지 못했습니다.
     explanation: 관심사에 대한 유저들을 발견합니다
     explore_mastodon: "%{title} 탐사하기"
-    how_to_enable: 아직 디렉터리에 참여하지 않았습니다. 아래에서 참여할 수 있습니다. 바이오 텍스트에 해시태그를 사용해 특정 해시태그 디렉터리에 표시 될 수 있습니다!
-    people:
-      other: "%{count}명"
+  domain_validator:
+    invalid_domain: 올바른 도메인 네임이 아닙니다
   errors:
+    '400': 제출한 요청이 올바르지 않습니다.
     '403': 이 페이지를 표시할 권한이 없습니다.
     '404': 당신이 찾으려는 페이지는 존재하지 않습니다.
+    '406': 이 페이지는 요청한 자료형으로 제공되지 않습니다.
     '410': 당신이 보려는 페이지는 더이상 여기에 존재하지 않습니다.
     '422':
       content: 보안 인증에 실패했습니다. 쿠키를 차단하고 있진 않습니까?
@@ -594,6 +691,7 @@ ko:
     '500':
       content: 죄송합니다, 뭔가 잘못 되었습니다.
       title: 이 페이지는 잘못되었습니다
+    '503': 이 페이지는 임시적인 서버 문제로 인해 제공 될 수 없습니다.
     noscript_html: 마스토돈을 사용하기 위해서는 자바스크립트를 켜 주십시오. 아니면 <a href="%{apps_path}">네이티브 앱</a> 중 하나를 사용할 수 있습니다.
   existing_username_validator:
     not_found: 해당 유저네임에 대한 로컬 유저를 찾을 수 없습니다
@@ -617,6 +715,7 @@ ko:
     add_new: 추가
     errors:
       limit: 이미 추천 해시태그의 개수가 최대입니다
+    hint_html: "<strong>추천 해시태그가 무엇이죠?</strong> 당신의 공개 프로필 페이지에 눈에 띄게 표현 되며 사람들이 그 해시태그를 포함한 당신의 글을 찾아 볼 수 있도록 합니다. 창작활동이나 긴 기간을 가지는 프로젝트를 쭉 따라가기에 좋은 도구입니다."
   filters:
     contexts:
       home: 홈 타임라인
@@ -637,10 +736,12 @@ ko:
     developers: 개발자
     more: 더 보기…
     resources: 리소스
+    trending_now: 지금 유행중
   generic:
     all: 모두
     changes_saved_msg: 정상적으로 변경되었습니다!
     copy: 복사
+    no_batch_actions_available: 이 페이지에서 수행할 수 있는 일괄작업이 없습니다
     order_by: 순서
     save_changes: 변경 사항을 저장
     validation_errors:
@@ -710,9 +811,34 @@ ko:
       too_many: 최대 4개까지 첨부할 수 있습니다
   migrations:
     acct: 새 계정의 username@domain
-    currently_redirecting: '당신의 프로파일은 여기로 리디렉션 됩니다:'
-    proceed: 저장
-    updated_msg: 계정 이동 설정이 저장되었습니다!
+    cancel: 리디렉션 취소
+    cancel_explanation: 리다이렉트를 취소하면 현재 계정이 다시 활성화 됩니다, 새 계정으로 이동한 팔로워를 되찾을 수는 없습니다.
+    cancelled_msg: 리다이렉트를 성공적으로 취소했습니다.
+    errors:
+      already_moved: 이미 이동한 계정과 동일합니다
+      missing_also_known_as: 이 계정을 역으로 참조하지 않고 있습니다
+      move_to_self: 현재 계정은 사용할 수 없습니다
+      not_found: 찾을 수 없습니다
+      on_cooldown: 쿨다운 기간입니다
+    followers_count: 이주 될 팔로워들
+    incoming_migrations: 다른 계정으로부터 옮기기
+    incoming_migrations_html: 다른 계정을 이 계정으로 이주하고 싶은 경우, 먼저 <a href="%{path}">계정 별칭을 만들어야 합니다</a>.
+    moved_msg: 당신의 계정은 %{acct}로 리다이렉트 되고 있으며 팔로워들은 이주 될 것입니다.
+    not_redirecting: 현재 이 계정은 어디로도 리다이렉트 되고 있지 않습니다.
+    on_cooldown: 당신은 최근에 이미 계정을 이동했습니다. 이 기능은 %{count}일 후에 다시 이용 가능합니다.
+    past_migrations: 과거 이주
+    proceed_with_move: 팔로워 이동
+    redirecting_to: 당신의 계정은 %{acct} 로 리다이렉트됩니다.
+    set_redirect: 리디렉션 설정
+    warning:
+      backreference_required: 새 계정은 이 계정으로 역참조를 하도록 설정되어 있어야 합니다
+      before: '진행하기 전, 주의사항을 꼼꼼히 읽어보세요:'
+      cooldown: 이주 뒤에는 새로운 이주를 하지 못하는 쿨다운 기간이 존재합니다
+      disabled_account: 이 계정은 완전한 사용이 불가능하게 됩니다. 하지만, 데이터 내보내기나 재활성화를 위해 접근할 수 있습니다.
+      followers: 이 행동은 현재 계정의 모든 팔로워를 새 계정으로 이동시킵니다
+      only_redirect_html: 대신, <a href="%{path}">프로필에 리디렉션만 표시</a>할 수 있습니다.
+      other_data: 다른 어떤 데이터도 자동적으로 옮겨지지 않을 것입니다
+      redirect: 현재 계정 프로필은 리다이렉트 알림과 함께 업데이트 되며 검색에서 제외 됩니다
   moderation:
     title: 모더레이션
   notification_mailer:
@@ -807,10 +933,6 @@ ko:
     reply:
       proceed: 답장 진행
       prompt: '이 툿에 답장을 하려 합니다:'
-  remote_unfollow:
-    error: 에러
-    title: 타이틀
-    unfollowed: 언팔로우됨
   scheduled_statuses:
     over_daily_limit: 그 날짜에 대한 %{limit}개의 예약 툿 제한을 초과합니다
     over_total_limit: 예약 툿 제한 %{limit}을 초과합니다
@@ -859,6 +981,7 @@ ko:
   settings:
     account: 계정
     account_settings: 계정 설정
+    aliases: 계정 별명
     appearance: 외관
     authorized_apps: 인증된 애플리케이션
     back: 돌아가기
@@ -896,6 +1019,8 @@ ko:
       private: 비공개 툿은 고정될 수 없습니다
       reblog: 부스트는 고정될 수 없습니다
     poll:
+      total_people:
+        other: "%{count}명"
       total_votes:
         other: "%{count}명 투표함"
       vote: 투표
@@ -913,6 +1038,8 @@ ko:
     pinned: 고정된 툿
     reblogged: 님이 부스트 했습니다
     sensitive_content: 민감한 컨텐츠
+  tags:
+    does_not_match_previous_name: 이전 이름과 맞지 않습니다
   terms:
     body_html: |
       <h2>개인정보 정책</h2>
@@ -1030,7 +1157,9 @@ ko:
         disable: 당신의 계정이 동결 된 동안 당신의 계정은 유지 됩니다. 하지만 잠금이 풀릴 때까지 당신은 아무 것도 할 수 없습니다.
         silence: 당신의 계정이 제한 된 동안엔 당신의 팔로워 이외엔 툿을 받아 볼 수 없고 공개 리스팅에서 제외 됩니다. 하지만 다른 사람들은 여전히 당신을 팔로우 가능합니다.
         suspend: 당신의 계정은 정지 되었으며, 모든 툿과 업로드 한 미디어가 서버에서 삭제 되어 되돌릴 수 없습니다.
+      get_in_touch: 이 메일에 대해 답장해서 %{instance}의 스태프와 연락 할 수 있습니다.
       review_server_policies: 서버 정책 검토하기
+      statuses: '구체적으로:'
       subject:
         disable: 당신의 계정 %{acct}가 동결 되었습니다
         none: "%{acct}에게의 경고"
diff --git a/config/locales/lt.yml b/config/locales/lt.yml
index 2cf0b7c42bfd9b1acc055b856cc5fa760701c771..7a09bee594b90dccdbdaed1f51d3522ca5281b05 100644
--- a/config/locales/lt.yml
+++ b/config/locales/lt.yml
@@ -9,10 +9,6 @@ lt:
     contact: Kontaktai
     contact_missing: Nenustatyta
     documentation: Dokumentacija
-    extended_description_html: |
-      <h3>TaisyklÄ—s</h3>
-      <p>Ilgas aprašymas dar nėra sudartyas</p>
-    generic_description: "%{domain} yra vienas serveris tinkle"
     hosted_on: Mastodon palaikomas naudojantis %{domain} talpinimu
     learn_more: Daugiau
     privacy_policy: Privatumo Politika
@@ -419,21 +415,8 @@ lt:
       no_status_selected: Jokie statusai nebuvo pakeisti, nes niekas nepasirinkta
       title: Paskyros statusai
       with_media: Su medija
-    subscriptions:
-      callback_url: AtgalinÄ— URL
-      confirmed: Patvirtinta
-      expires_in: Pasibaigia
-      last_delivery: Paskutinis pristatymas
-      title: WebSub protokolas
-      topic: Tema
     tags:
-      accounts: Paskyros
-      hidden: PaslÄ—pti
-      hide: Paslėpti iš direktorijos
-      name: Saitažodis(#)
       title: Saitažodžiai(#)
-      unhide: Rodyti direktorijoje
-      visible: Matomas
     title: Administracija
     warning_presets:
       add_new: PridÄ—ti naujÄ…
@@ -462,7 +445,6 @@ lt:
     your_token: Jūsų prieigos žetonas
   auth:
     change_password: Slaptažodis
-    confirm_email: Patvirtinti el paštą
     delete_account: Ištrinti paskyrą
     delete_account_html: Jeigu norite ištrinti savo paskyrą, galite eiti <a href="%{path}">čia</a>. Jūsų prašys patvirtinti pasirinkimą.
     didnt_get_confirmation: Negavote patvirtinimo instrukcijų?
@@ -504,23 +486,18 @@ lt:
       x_months: "%{count}mÄ—n"
       x_seconds: "%{count}sek"
   deletes:
-    bad_password_msg: Geras bandymas, programišiau! Neteisingas slaptažodis
     confirm_password: Kad patvirtintumėte savo tapatybę, įveskite dabartini slaptažodį
-    description_html: Tai <strong>be sugrąžinimo, visam laikui</strong> panaikins visa turini iš Jūsų paskyros ir deaktyvuos ją. Jūsų vartotojo vardas paliks rezervuotas, kad išvengtumėme tapatybės pavagimo ateityje.
     proceed: Ištrinti paskyrą
     success_msg: Jūsų paskyra sėkmingai ištrinta
-    warning_html: Tiktai panaikinimas turinio iš šio serverio garantuotas. Turinys, kuris buvo viešai prieinamas ir dalinamas kituose serveriuose paliks pėdsakus. Serveriai, kurie neseka jūsų, kurie nėra tinkle, nepakeis savo duomenų sistemos.
-    warning_title: Platinamo turinio prieinamumas
   directories:
     directory: Profilio direktorija
-    enabled: Jūs esate rodomas šioje direktorijoje.
-    enabled_but_waiting: Jūs pasirinkote būti įtrauktas į direktorija, bet jūs neturite minimalaus sekėjų skaičiaus (%{min_followers}), kad būtumėte rodomas.
     explanation: Raskite vartotojus, remiantis tuo, kuo jie domisi
     explore_mastodon: Naršyti %{title}
-    how_to_enable: Jūs nesate prisijungęs prie šios direktorijos. Galite prisijungti žemiau. Naudokite saitažodžius savo biografiniame tekste, kad būtumėte rastas naudojantis specifinius saitažodžius!
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Jūs neturie prieigos matyti šiam puslapiui.
     '404': Puslapis nerastas.
+    '406': This page is not available in the requested format.
     '410': Puslapis neegzistuoja.
     '422':
       content: Apsaugos patvirtinmas klaidingas. Ar jūs blokuojate sausainius?
@@ -529,6 +506,7 @@ lt:
     '500':
       content: Atsiprašome, tačiau mūsų pusėje įvyko klaida.
       title: Netinkamas puslapis
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: Kad naudotumėtės Mastodon web aplikacija, prašome įsijungti JavaScript. Alternatyviai, pabandykite viena iš <a href="%{apps_path}">vietinių aplikacijų</a> Mastodon savo platformai.
   exports:
     archive_takeout:
@@ -615,9 +593,6 @@ lt:
       too_many: Negalima pridėti daugiau nei 4 failų
   migrations:
     acct: slapyvardis@domenas naujam vartotojui
-    currently_redirecting: 'Jūsų profilis nustatytas nukreipimui į:'
-    proceed: Išsaugoti
-    updated_msg: Jūsų paskyros migracijos nustatymai sėkmingai pakeisti!
   moderation:
     title: Moderacija
   notification_mailer:
@@ -672,10 +647,6 @@ lt:
     reply:
       proceed: Atsakyti
       prompt: 'Jūs norite atsakyti šiam toot''ui:'
-  remote_unfollow:
-    error: Klaida
-    title: Pavadinimas
-    unfollowed: Nebesekama
   scheduled_statuses:
     over_daily_limit: Jūs pasieketė limitą (%{limit}) galimų toot'ų per dieną
     over_total_limit: Jūs pasieketė %{limit} limitą galimų toot'ų
diff --git a/config/locales/lv.yml b/config/locales/lv.yml
index 971450a89bae8bd48cf8ae69fec86bb9e5f4dc33..f6625dd41f385314f766b63aaaa7dba272d96740 100644
--- a/config/locales/lv.yml
+++ b/config/locales/lv.yml
@@ -1,12 +1,15 @@
 ---
 lv:
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': You don't have permission to view this page.
     '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
     '410': The page you were looking for doesn't exist here anymore.
     '422': 
     '429': Throttled
     '500': 
+    '503': The page could not be served due to a temporary server failure.
   invites:
     expires_in:
       '1800': 30 minutes
diff --git a/config/locales/mk.yml b/config/locales/mk.yml
new file mode 100644
index 0000000000000000000000000000000000000000..acb18fb5a00af60406909358b4962f8eff2990db
--- /dev/null
+++ b/config/locales/mk.yml
@@ -0,0 +1,20 @@
+---
+mk:
+  errors:
+    '400': The request you submitted was invalid or malformed.
+    '403': You don't have permission to view this page.
+    '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
+    '410': The page you were looking for doesn't exist here anymore.
+    '422': 
+    '429': Throttled
+    '500': 
+    '503': The page could not be served due to a temporary server failure.
+  invites:
+    expires_in:
+      '1800': 30 minutes
+      '21600': 6 hours
+      '3600': 1 hour
+      '43200': 12 hours
+      '604800': 1 week
+      '86400': 1 day
diff --git a/config/locales/ms.yml b/config/locales/ms.yml
index 3597ccd15fd46d85a60d4aac6099b0950f18a784..d824aed60c3ed70bb2ff9b55a474a5c1b4e67eeb 100644
--- a/config/locales/ms.yml
+++ b/config/locales/ms.yml
@@ -10,10 +10,6 @@ ms:
     contact_missing: Tidak ditetapkan
     contact_unavailable: Tidak tersedia
     documentation: Pendokumenan
-    extended_description_html: |
-      <h3>Tempat sesuai untuk peraturan</h3>
-      <p>Kenyataan penuh masih belum ditetapkan.</p>
-    generic_description: "%{domain} ialah salah sebuah pelayan dalam rangkaian Mastodon"
     hosted_on: Mastodon dihoskan di %{domain}
     learn_more: Ketahui lebih lanjut
     privacy_policy: Polisi privasi
@@ -297,12 +293,15 @@ ms:
         create_and_resolve: Selesaikan dengan nota
         placeholder: Terangkan tindakan apa yang telah diambil, atau sebarang kemas kini lain yang berkaitan...
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': You don't have permission to view this page.
     '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
     '410': The page you were looking for doesn't exist here anymore.
     '422': 
     '429': Throttled
     '500': 
+    '503': The page could not be served due to a temporary server failure.
   exports:
     archive_takeout:
       in_progress: Mengkompil arkib anda...
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 78be7872d8fb028be74bf90f56efc4eab0199269..2c9e42b33c371a96be0e460b92c7703f4c647641 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -17,13 +17,12 @@ nl:
     contact_unavailable: n.v.t
     discover_users: Gebruikers ontdekken
     documentation: Documentatie
-    extended_description_html: |
-      <h3>Een goede plek voor richtlijnen</h3>
-      <p>De uitgebreide omschrijving is nog niet ingevuld.</p>
     federation_hint_html: Met een account op %{instance} ben je in staat om mensen die zich op andere Mastodonservers (en op andere plekken) bevinden te volgen.
-    generic_description: "%{domain} is een server in het Mastodonnetwerk"
     get_apps: Mobiele apps
     hosted_on: Mastodon op %{domain}
+    instance_actor_flash: 'Dit account is een virtuel actor die wordt gebruikt om de server zelf te vertegenwoordigen en is geen individuele gebruiker. Het wordt voor federatiedoeleinden gebruikt en moet niet worden geblokkeerd, tenzij je de hele server wil blokkeren. In zo''n geval dien je echter een domeinblokkade te gebruiken.
+
+'
     learn_more: Meer leren
     privacy_policy: Privacybeleid
     see_whats_happening: Kijk wat er aan de hand is
@@ -35,6 +34,14 @@ nl:
     status_count_before: Zij schreven
     tagline: Vrienden volgen en nieuwe ontdekken
     terms: Gebruiksvoorwaarden
+    unavailable_content: Niet beschikbare inhoud
+    unavailable_content_description:
+      domain: Server
+      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.
+      silenced: Toots van deze server worden nergens weergegeven, behalve op jouw eigen starttijdlijn wanneer je het account volgt.
+      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.
+    unavailable_content_html: Met Mastodon kun je in het algemeen berichten bekijken van en communiceren met gebruikers van elke andere server in de fediverse. Dit zijn de uitzonderingen die door deze server zijn gemaakt en expliciet alleen hier gelden.
     user_count_after:
       one: gebruiker
       other: gebruikers
@@ -42,6 +49,8 @@ nl:
     what_is_mastodon: Wat is Mastodon?
   accounts:
     choices_html: 'Aanbevelingen van %{name}:'
+    endorsements_hint: Je kunt mensen die je volgt in de webomgeving aanbevelen, waarna ze dan hier zullen verschijnen.
+    featured_tags_hint: Je kunt specifieke hashtags uitlichten, waarna ze dan hier zullen verschijnen.
     follow: Volgen
     followers:
       one: Volger
@@ -53,6 +62,7 @@ nl:
     media: Media
     moved_html: "%{name} is verhuisd naar %{new_profile_link}:"
     network_hidden: Deze informatie is niet beschikbaar
+    never_active: Nooit
     nothing_here: Hier is niets!
     people_followed_by: Mensen die %{name} volgen
     people_who_follow: Mensen die %{name} volgen
@@ -183,6 +193,7 @@ nl:
       username: Gebruikersnaam
       warn: Waarschuwen
       web: Webapp
+      whitelisted: Op de witte lijst
     action_logs:
       actions:
         assigned_to_self_report: "%{name} heeft rapportage %{target} aan zichzelf toegewezen"
@@ -218,19 +229,24 @@ nl:
       deleted_status: "(verwijderde toot}"
       title: Auditlog
     custom_emojis:
+      assign_category: Categorie toewijzen
       by_domain: Domein
       copied_msg: Lokale kopie van emoji maken geslaagd
       copy: Kopiëren
       copy_failed_msg: Kan geen lokale kopie van deze emoji maken
+      create_new_category: Nieuwe categorie toevoegen
       created_msg: Aanmaken van emoji geslaagd!
       delete: Verwijderen
       destroyed_msg: Verwijderen van emoji geslaagd!
       disable: Uitschakelen
+      disabled: Uitgeschakeld
       disabled_msg: Uitschakelen van deze emoji geslaagd
       emoji: Emoji
       enable: Inschakelen
+      enabled: Ingeschakeld
       enabled_msg: Inschakelen van deze emoji geslaagd
       image_hint: PNG van max. 50KB
+      list: In lijst
       listed: Weergegeven
       new:
         title: Lokale emoji toevoegen
@@ -238,11 +254,14 @@ nl:
       shortcode: Verkorte code
       shortcode_hint: Tenminste 2 tekens (alleen alfanumeriek en underscores)
       title: Lokale emoji’s
+      uncategorized: Niet gecategoriseerd
+      unlist: Niet in lijst
       unlisted: Niet weergegeven
       update_failed_msg: Deze emoji kon niet worden bijgewerkt
       updated_msg: Bijwerken van emoji is geslaagd!
       upload: Uploaden
     dashboard:
+      authorized_fetch_mode: Veilige modus
       backlog: achterstallige taken
       config: Configuratie
       feature_deletions: Verwijderen van account
@@ -250,10 +269,13 @@ nl:
       feature_profile_directory: Gebruikersgids
       feature_registrations: Registraties
       feature_relay: Federatierelay
+      feature_spam_check: Anti-spam
       feature_timeline_preview: Voorvertoning van tijdlijn
       features: Functies
       hidden_service: Federatie met verborgen diensten
       open_reports: onopgeloste rapportages
+      pending_tags: hashtags die op een beoordeling wachten
+      pending_users: gebruikers die op een beoordeling wachten
       recent_users: Recente gebruikers
       search: In volledige tekst zoeken
       single_user_mode: Modus voor één gebruiker
@@ -265,11 +287,18 @@ nl:
       week_interactions: interacties deze week
       week_users_active: actieve gebruikers deze week
       week_users_new: nieuwe gebruikers deze week
+      whitelist_mode: Modus voor de witte lijst
+    domain_allows:
+      add_new: Domein voor de witte lijst
+      created_msg: Het domein is succesvol aan de witte lijst toegevoegd
+      destroyed_msg: Het domein is van de witte lijst verwijderd
+      undo: Van de witte lijst verwijderen
     domain_blocks:
       add_new: Nieuwe domeinblokkade toevoegen
       created_msg: Domeinblokkade wordt nu verwerkt
       destroyed_msg: Domeinblokkade is ongedaan gemaakt
       domain: Domein
+      edit: Domeinblokkade bewerken
       existing_domain_block_html: Jij hebt al strengere beperkingen opgelegd aan %{name}, je moet het domein eerst <a href="%{unblock_url}">deblokkeren</a>.
       new:
         create: Blokkade aanmaken
@@ -280,6 +309,10 @@ nl:
           silence: Negeren
           suspend: Opschorten
         title: Nieuwe domeinblokkade
+      private_comment: Privé-opmerking
+      private_comment_hint: Opmerking over deze domeinbeperking voor intern gebruik door de moderatoren.
+      public_comment: Openbare opmerking
+      public_comment_hint: Opmerking over deze domeinbeperking voor het grote publiek, voor wanneer de openbare lijst met domeinbeperkingen is ingeschakeld.
       reject_media: Mediabestanden weigeren
       reject_media_hint: Verwijderd lokaal opgeslagen mediabestanden en weigert deze in de toekomst te downloaden. Irrelevant voor opgeschorte domeinen
       reject_reports: Rapportages weigeren
@@ -299,6 +332,7 @@ nl:
         title: Domeinblokkade voor %{domain} ongedaan maken
         undo: Ongedaan maken
       undo: domeinblokkade ongedaan maken
+      view: Domeinblokkade bekijken
     email_domain_blocks:
       add_new: Nieuwe toevoegen
       created_msg: Blokkeren e-maildomein geslaagd
@@ -322,6 +356,8 @@ nl:
         all: Alles
         limited: Beperkt
         title: Moderatie
+      private_comment: Privé-opmerking
+      public_comment: Openbare opmerking
       title: Andere domeinen
       total_blocked_by_us: Door ons geblokkeerd
       total_followed_by_them: Door hun gevolgd
@@ -351,6 +387,7 @@ nl:
       pending: Aan het wachten op toestemming van de relayserver
       save_and_enable: Opslaan en inschakelen
       setup: Een verbinding met een relayserver maken
+      signatures_not_enabled: Federatierelays werken niet goed wanneer de veilige modus of de witte lijstmodus is ingeschakeld
       status: Status
       title: Relayservers
     report_notes:
@@ -399,6 +436,16 @@ nl:
       custom_css:
         desc_html: Het uiterlijk van deze server met CSS aanpassen
         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
+      domain_blocks:
+        all: Naar iedereen
+        disabled: Naar niemand
+        title: Domeinblokkades tonen
+        users: Naar ingelogde lokale gebruikers
+      domain_blocks_rationale:
+        title: Motivering tonen
       hero:
         desc_html: Wordt op de voorpagina getoond. Tenminste 600x100px aanbevolen. Wanneer dit niet is ingesteld wordt de thumbnail van de Mastodonserver getoond
         title: Hero-afbeelding
@@ -431,8 +478,8 @@ nl:
           open: Iedereen kan zich registreren
         title: Registratiemodus
       show_known_fediverse_at_about_page:
-        desc_html: Wanneer ingeschakeld wordt de globale tijdlijn op de voorpagina getoond en wanneer uitgeschakeld de lokale tijdljn.
-        title: De globale tijdlijn op de voorpagina tonen
+        desc_html: Wanneer ingeschakeld wordt de globale tijdlijn op de voorpagina getoond en wanneer uitgeschakeld de lokale tijdlijn
+        title: De globale tijdlijn op de openbare tijdlijnpagina tonen
       show_staff_badge:
         desc_html: Medewerkersbadge op profielpagina tonen
         title: Medewerkersbadge tonen
@@ -449,19 +496,26 @@ nl:
         desc_html: Je kan hier jouw eigen privacybeleid, gebruiksvoorwaarden en ander juridisch jargon kwijt. Je kan HTML gebruiken
         title: Aangepaste gebruiksvoorwaarden
       site_title: Naam Mastodonserver
+      spam_check_enabled:
+        desc_html: Mastodon kan accounts die herhaaldelijk ongevraagde berichten versturen automatisch negeren of rapporteren. Het is mogelijk dat er foutpositieven tussen zitten.
+        title: Automatische spambestrijding
       thumbnail:
         desc_html: Gebruikt als voorvertoning voor OpenGraph en de API. 1200x630px aanbevolen
         title: Thumbnail Mastodonserver
       timeline_preview:
-        desc_html: Toon een openbare tijdlijn op de voorpagina
-        title: Tijdlijn op de voorpagina tonen
+        desc_html: Toon een link naar de openbare tijdlijnpagina op de voorpagina en geef de API zonder in te loggen toegang tot de openbare tijdlijn
+        title: Toegang tot de openbare tijdlijn zonder in te loggen toestaan
       title: Server-instellingen
+      trends:
+        desc_html: Eerder beoordeelde hashtags die op dit moment trending zijn openbaar tonen
+        title: Trending hashtags
     statuses:
       back_to_account: Terug naar accountpagina
       batch:
         delete: Verwijderen
         nsfw_off: Als niet gevoelig markeren
         nsfw_on: Als gevoelig markeren
+      deleted: Verwijderd
       failed_to_execute: Uitvoeren mislukt
       media:
         title: Media
@@ -469,21 +523,24 @@ nl:
       no_status_selected: Er werden geen toots gewijzigd, omdat er geen enkele werd geselecteerd
       title: Toots van account
       with_media: Met media
-    subscriptions:
-      callback_url: Callback-URL
-      confirmed: Bevestigd
-      expires_in: Verloopt over
-      last_delivery: Laatste bezorging
-      title: WebSub
-      topic: Account
     tags:
-      accounts: Accounts
-      hidden: Verborgen
-      hide: Niet in gebruikersgids tonen
+      accounts_today: Aantal verschillende keren vandaag gebruikt
+      accounts_week: Aantal verschillende keren deze week gebruikt
+      breakdown: Uitsplitsing van het gebruik van vandaag naar bron
+      context: Context
+      directory: In de gebruikersgids
+      in_directory: "%{count} keer in de gebruikersgids"
+      last_active: Laatst actief
+      most_popular: Meest populair
+      most_recent: Meest recent
       name: Hashtag
+      review: Status beoordelen
+      reviewed: Beoordeeld
       title: Hashtags
-      unhide: In gebruikersgids tonen
-      visible: Zichtbaar
+      trending_right_now: Op dit moment trending
+      unique_uses_today: "%{count} keer vandaag gebruikt"
+      unreviewed: Niet beoordeeld
+      updated_msg: Instellingen hashtag succesvol bijgewerkt
     title: Beheer
     warning_presets:
       add_new: Nieuwe toevoegen
@@ -499,11 +556,21 @@ nl:
       body: "%{reporter} heeft %{target} gerapporteerd"
       body_remote: Iemand van %{domain} heeft %{target} gerapporteerd
       subject: Nieuwe rapportage op %{instance} (#%{id})
+    new_trending_tag:
+      body: 'De hashtag #%{name} is vandaag trending, maar is nog niet beoordeeld. Het wordt niet in het openbaar getoond alvorens je de hashtag goedkeurt. Je kunt ook het formulier zoals het nu is opslaan, waarna je er niks meer over zult horen.'
+      subject: Nieuwe hashtag te beoordelen op %{instance} (#%{name})
+  aliases:
+    add_new: Alias aanmaken
+    created_msg: Succesvol een nieuwe alias aangemaakt. Je kunt nu met de verhuizing vanaf het oude account beginnen.
+    deleted_msg: De alias is succesvol verwijderd. Verhuizen vanaf dat account naar dit account is niet meer mogelijk.
+    hint_html: Wanneer je vanaf een ander account naar dit account wilt verhuizen, kun je hier een alias aanmaken. Dit is nodig voordat je verder kunt gaan met het verhuizen van volgers van het oude naar dit nieuwe account. Deze actie is op zich <strong>ongevaarlijk en omkeerbaar</strong>. <strong>De accountmigratie wordt gestart vanaf het oude account</strong>.
+    remove: Alias ontkoppelen
   appearance:
     advanced_web_interface: Geavanceerde webomgeving
     advanced_web_interface_hint: 'Wanneer je van de hele schermbreedte gebruik wilt maken, stelt de geavanceerde webomgeving je in staat om meerdere verschillende kolommen te configureren. Hiermee kun je zoveel mogelijk informatie op hetzelfde moment bekijken, zoals: Start, meldingen, de globale tijdlijn, meerdere lijsten en hashtags.'
     animations_and_accessibility: Animaties en toegankelijkheid
     confirmation_dialogs: Bevestigingen
+    discovery: Ontdekken
     sensitive_content: Gevoelige inhoud
   application_mailer:
     notification_preferences: E-mailvoorkeuren wijzigen
@@ -524,9 +591,13 @@ nl:
     apply_for_account: Een uitnodiging aanvragen
     change_password: Wachtwoord
     checkbox_agreement_html: Ik ga akkoord met de <a href="%{rules_path}" target="_blank">regels van deze server</a> en de <a href="%{terms_path}" target="_blank">gebruiksvoorwaarden</a>
-    confirm_email: E-mail bevestigen
+    checkbox_agreement_without_rules_html: Ik ga akkoord met de <a href="%{terms_path}" target="_blank">gebruiksvoorwaarden</a>
     delete_account: Account verwijderen
     delete_account_html: Wanneer je jouw account graag wilt verwijderen, kun je dat <a href="%{path}">hier doen</a>. We vragen jou daar om een bevestiging.
+    description:
+      prefix_invited_by_user: "@%{name} nodigt je hierbij uit om een account aan te maken op deze Mastodonserver!"
+      prefix_sign_up: Registreer je vandaag nog op Mastodon!
+      suffix: Met een account ben je in staat om mensen te volgen, berichten te plaatsen en uit te wisselen met mensen die zich op andere Mastodonservers bevinden en meer!
     didnt_get_confirmation: Geen bevestigingsinstructies ontvangen?
     forgot_password: Wachtwoord vergeten?
     invalid_reset_password_token: De code om jouw wachtwoord opnieuw in te stellen is verlopen. Vraag een nieuwe aan.
@@ -544,6 +615,16 @@ nl:
     reset_password: Wachtwoord opnieuw instellen
     security: Beveiliging
     set_new_password: Nieuw wachtwoord instellen
+    setup:
+      email_below_hint_html: Wanneer onderstaand e-mailadres niet klopt, kun je dat hier veranderen. Je ontvangt dan hierna een bevestigingsmail.
+      email_settings_hint_html: De bevestigingsmail is verzonden naar %{email}. Wanneer dat e-mailadres niet klopt, kun je dat veranderen in je accountinstellingen.
+      title: Instellen
+    status:
+      account_status: Accountstatus
+      confirming: Aan het wachten totdat de e-mail is bevestigd.
+      functional: Jouw account is volledig operationeel.
+      pending: Jouw aanvraag moet nog worden beoordeeld door een van onze medewerkers. Dit kan misschien eventjes duren. Je ontvangt een e-mail wanneer jouw aanvraag is goedgekeurd.
+      redirecting_to: Jouw account is inactief omdat het momenteel wordt doorverwezen naar %{acct}.
     trouble_logging_in: Problemen met inloggen?
   authorize_follow:
     already_following: Je volgt dit account al
@@ -556,6 +637,11 @@ nl:
       return: Profiel van deze gebruiker tonen
       web: Ga naar de webapp
     title: Volg %{acct}
+  challenge:
+    confirm: Doorgaan
+    hint_html: "<strong>Tip:</strong> We vragen jou het komende uur niet meer naar jouw wachtwoord."
+    invalid_password: Ongeldig wachtwoord
+    prompt: Bevestig wachtwoord om door te gaan
   datetime:
     distance_in_words:
       about_x_hours: "%{count}u"
@@ -571,26 +657,33 @@ nl:
       x_months: "%{count}ma"
       x_seconds: "%{count}s"
   deletes:
-    bad_password_msg: Goed geprobeerd hackers! Ongeldig wachtwoord
+    challenge_not_passed: De informatie die u hebt ingevoerd is ongeldig
     confirm_password: Voer jouw huidige wachtwoord in om jouw identiteit te bevestigen
-    description_html: Hierdoor worden alle gegevens van jouw account <strong>permanent, onomkeerbaar</strong> verwijderd en wordt deze gedeactiveerd. Om toekomstige identiteitsdiefstal te voorkomen, kan op deze server jouw gebruikersnaam niet meer gebruikt worden.
+    confirm_username: Voer uw gebruikersnaam in om de procedure te bevestigen
     proceed: Account verwijderen
     success_msg: Jouw account is succesvol verwijderd
-    warning_html: We kunnen alleen garanderen dat jouw gegevens op deze server worden verwijderd. Berichten (toots), incl. media, die veel zijn gedeeld laten mogelijk sporen achter. Offline servers en servers die niet meer op jouw updates zijn geabonneerd zullen niet hun databases updaten.
-    warning_title: Verwijdering gegevens op andere servers
+    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
+      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>
+      irreversible: Je zult niet in staat zijn om jouw account te herstellen of te deactiveren
+      more_details_html: Zie het <a href="%{terms_path}">privacybeleid</a> voor meer informatie.
+      username_available: Jouw gebruikersnaam zal weer beschikbaar komen
+      username_unavailable: Jouw gebruikersnaam zal onbeschikbaar blijven
   directories:
     directory: Gebruikersgids
-    enabled: Je staat momenteel in de gebruikersgids vermeldt.
-    enabled_but_waiting: Je hebt er voor gekozen om in de gebruikersgids te worden vermeldt, maar je hebt daarvoor nog niet het minimaal aantal volgers (%{min_followers}).
     explanation: Ontdek gebruikers aan de hand van hun interesses
     explore_mastodon: "%{title} verkennen"
-    how_to_enable: Je geeft momenteel geen toestemming om in de gebruikersgids te worden vermeldt. Je kunt hieronder toestemming geven. Gebruik hashtags in de tekst van jouw bio, om onder bepaalde hashtags te worden vermeldt!
-    people:
-      one: "%{count} gebruikers"
-      other: "%{count} gebruikers"
+  domain_validator:
+    invalid_domain: is een ongeldige domeinnaam
   errors:
+    '400': De aanvraag die je hebt ingediend was ongeldig of foutief.
     '403': Jij hebt geen toestemming om deze pagina te bekijken.
     '404': De pagina waarnaar jij op zoek bent bestaat niet.
+    '406': Deze pagina is niet beschikbaar in het opgevraagde formaat.
     '410': De pagina waarnaar jij op zoek bent bestaat niet meer.
     '422':
       content: Veiligheidsverificatie mislukt. Blokkeer je toevallig cookies?
@@ -599,6 +692,7 @@ nl:
     '500':
       content: Het spijt ons, er is aan onze kant iets fout gegaan.
       title: Er is iets mis
+    '503': De pagina kon door een tijdelijke serverstoring niet worden geladen.
     noscript_html: Schakel JavaScript in om de webapp van Mastodon te kunnen gebruiken. Als alternatief kan je een <a href="%{apps_path}">Mastodon-app</a> zoeken voor jouw platform.
   existing_username_validator:
     not_found: Kon geen lokale gebruiker met die gebruikersnaam vinden
@@ -622,6 +716,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."
   filters:
     contexts:
       home: Starttijdlijn
@@ -642,10 +737,12 @@ nl:
     developers: Ontwikkelaars
     more: Meer…
     resources: Hulpmiddelen
+    trending_now: Trends
   generic:
     all: Alles
     changes_saved_msg: Wijzigingen succesvol opgeslagen!
     copy: Kopiëren
+    no_batch_actions_available: Geen batchacties op deze pagina beschikbaar
     order_by: Sorteer op
     save_changes: Wijzigingen opslaan
     validation_errors:
@@ -697,7 +794,7 @@ nl:
       '604800': 1 week
       '86400': 1 dag
     expires_in_prompt: Nooit
-    generate: Genereren
+    generate: Uitnodigingslink genereren
     invited_by: 'Jij bent uitgenodigd door:'
     max_uses:
       one: 1 keer
@@ -716,10 +813,35 @@ nl:
       images_and_video: Een video kan niet aan een toot met afbeeldingen worden gekoppeld
       too_many: Er kunnen niet meer dan 4 afbeeldingen toegevoegd worden
   migrations:
-    acct: gebruikersnaam@domein van het nieuwe account
-    currently_redirecting: 'Jouw profiel wordt nu doorverwezen naar:'
-    proceed: Opslaan
-    updated_msg: Jouw accountmigratie-instelling is succesvol bijgewerkt!
+    acct: Verhuisd naar
+    cancel: Doorverwijzing annuleren
+    cancel_explanation: Het annuleren van de doorverwijzing zal jouw huidige account opnieuw activeren, maar brengt geen volgers terug die naar het andere account zijn verhuisd.
+    cancelled_msg: De doorverwijzing is succesvol geannuleerd.
+    errors:
+      already_moved: is hetzelfde account waarnaar je al naar toe bent verhuisd
+      missing_also_known_as: verwijst niet terug naar dit account
+      move_to_self: kan niet het huidige account zijn
+      not_found: kon niet worden gevonden
+      on_cooldown: Jouw laatste migratie is nog te kort geleden
+    followers_count: Volgers op het moment van verhuizing
+    incoming_migrations: Verhuizen vanaf een ander account
+    incoming_migrations_html: Om vanaf een ander account naar dit account te verhuizen, dien je eerst <a href="%{path}">een accountalias aan te maken</a>.
+    moved_msg: Jouw account wordt nu naar %{acct} doorverwezen en jouw volgers worden verhuisd.
+    not_redirecting: Jouw account wordt momenteel niet naar een ander account doorverwezen.
+    on_cooldown: Je hebt recentelijk jouw account verhuisd. Deze mogelijkheid is weer beschikbaar over %{count} dagen.
+    past_migrations: Vorige migraties
+    proceed_with_move: Volgers verhuizen
+    redirecting_to: Jouw account wordt nu naar %{acct} doorverwezen.
+    set_redirect: Doorverwijzing instellen
+    warning:
+      backreference_required: Het nieuwe account moet eerst worden ingesteld om naar dit account te kunnen terugverwijzen
+      before: 'Lees eerst goed deze tekst, alvorens verder te gaan:'
+      cooldown: Na de verhuizing kun je tijdelijk niet opnieuw verhuizen
+      disabled_account: Jouw huidige account is hierna niet meer volledig bruikbaar. Je hebt echter wel toegang tot het exporteren van je gegevens en tot het opnieuw activeren van je account.
+      followers: Deze actie verhuisd alle volgers vanaf het huidige account naar het nieuwe account
+      only_redirect_html: Je kunt als alternatief ook <a href="%{path}">alleen de doorverwijzing op je profiel zetten</a>.
+      other_data: Geen andere gegevens worden automatisch verhuisd
+      redirect: Jouw huidige accountprofiel wordt bijgewerkt met een doorverwijzingsmelding en wordt uitgesloten van zoekresultaten
   moderation:
     title: Moderatie
   notification_mailer:
@@ -816,10 +938,6 @@ nl:
     reply:
       proceed: Doorgaan met reageren
       prompt: 'Je wilt op de volgende toot reageren:'
-  remote_unfollow:
-    error: Fout
-    title: Titel
-    unfollowed: Ontvolgd
   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
@@ -868,6 +986,7 @@ nl:
   settings:
     account: Account
     account_settings: Accountinstellingen
+    aliases: Accountaliassen
     appearance: Uiterlijk
     authorized_apps: Geautoriseerde apps
     back: Terug naar Mastodon
@@ -908,6 +1027,9 @@ nl:
       private: Alleen openbare toots kunnen worden vastgezet
       reblog: Een boost kan niet worden vastgezet
     poll:
+      total_people:
+        one: "%{count} persoon"
+        other: "%{count} personen"
       total_votes:
         one: "%{count} stem"
         other: "%{count} stemmen"
@@ -926,6 +1048,8 @@ nl:
     pinned: Vastgemaakte toot
     reblogged: boostte
     sensitive_content: Gevoelige inhoud
+  tags:
+    does_not_match_previous_name: komt niet overeen met de vorige naam
   terms:
     body_html: |
       <h2>Privacy Policy</h2>
@@ -1043,7 +1167,9 @@ nl:
         disable: Zolang jouw account is bevroren blijven jouw accountgegevens intact, maar kun je geen handelingen uitvoeren totdat het account is vrijgegeven.
         silence: Zolang jouw account wordt beperkt, kunnen alleen mensen die jou al volgen jouw toots op deze server zien. Tevens ben je niet zichtbaar in meldingen, gesprekken en op openbare tijdlijnen. Anderen kunnen je echter wel handmatig volgen.
         suspend: Jouw account is opgeschort. Jouw toots en geüploade media zijn onomkeerbaar van deze server verwijderd, en ook o.a. van de servers waar jij volgers had.
+      get_in_touch: Je kunt deze e-mail beantwoorden om in contact te komen met de medewerkers van %{instance}.
       review_server_policies: Serverbeleid bekijken
+      statuses: 'Met name voor:'
       subject:
         disable: Jouw account %{acct} is bevroren
         none: Waarschuwing voor %{acct}
diff --git a/config/locales/nn.yml b/config/locales/nn.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a1b61d6e7db4a8e6cc2c7c85eedf4c5a8315db75
--- /dev/null
+++ b/config/locales/nn.yml
@@ -0,0 +1,20 @@
+---
+nn:
+  errors:
+    '400': The request you submitted was invalid or malformed.
+    '403': You don't have permission to view this page.
+    '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
+    '410': The page you were looking for doesn't exist here anymore.
+    '422': 
+    '429': Throttled
+    '500': 
+    '503': The page could not be served due to a temporary server failure.
+  invites:
+    expires_in:
+      '1800': 30 minutes
+      '21600': 6 hours
+      '3600': 1 hour
+      '43200': 12 hours
+      '604800': 1 week
+      '86400': 1 day
diff --git a/config/locales/no.yml b/config/locales/no.yml
index d21dda6fba0f4b4d39131d49fa6054d67606ba22..fa40975a79856508897977d310cd22f734f7a958 100644
--- a/config/locales/no.yml
+++ b/config/locales/no.yml
@@ -7,10 +7,6 @@
     contact: Kontakt
     contact_missing: Ikke innstilt
     contact_unavailable: Ikke tilgjengelig
-    extended_description_html: |
-      <h3>En god plassering for regler</h3>
-      <p>En utvidet beskrivelse er ikke satt opp ennå.</p>
-    generic_description: "%{domain} er en tjener i nettverket"
     hosted_on: Mastodon driftet på %{domain}
     learn_more: Lær mer
     source_code: Kildekode
@@ -267,12 +263,6 @@
       no_media: Ingen media
       title: Kontostatuser
       with_media: Med media
-    subscriptions:
-      callback_url: Callback-URL
-      confirmed: Bekreftet
-      expires_in: Utløper om
-      last_delivery: Siste levering
-      topic: Emne
     title: Administrasjon
   admin_mailer:
     new_report:
@@ -332,16 +322,14 @@
       x_months: "%{count} mnd"
       x_seconds: "%{count} sek"
   deletes:
-    bad_password_msg: Godt forsøk, hacker! Feil passord
     confirm_password: Skriv inn ditt passord for å verifisere din identitet
-    description_html: Dette vil <strong>permanent, irreversibelt</strong> fjerne innhold fra din konto og deaktivere den. Ditt brukernavn vil forbli reservert for å forhindre fremtidige etterligninger.
     proceed: Slett konto
     success_msg: Din konto ble slettet
-    warning_html: Kun sletting av innhold fra denne instansen er garantert. Innhold som har blitt delt i stor utstrekning vil sannsynligvis legge igjen spor. Tjenere som er frakoblet og tjenere som ikke abbonerer på dine oppdatereringer vil ikke oppdatere sine databaser.
-    warning_title: Tilgjengelighet av spredt innhold
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Du har ikke tillatelse til å vise denne siden.
     '404': Siden du leter etter finnes ikke.
+    '406': This page is not available in the requested format.
     '410': Siden du leter etter finnes ikke lenger.
     '422':
       content: Sikkerhetsverifisering feilet. Blokkerer du informasjonskapsler?
@@ -350,6 +338,7 @@
     '500':
       content: Beklager men noe gikk galt ved vår ende.
       title: Denne siden er ikke korrekt
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: For å bruke Mastodon webapplikasjon må du aktivere JavaScript. Alternativt kan du forsøke en av de mange <a href="%{apps_path}">integrerte appene</a> for Mastodon til din plattform.
   exports:
     blocks: Du blokkerer
@@ -401,9 +390,6 @@
       too_many: Kan ikke legge ved mer enn 4 filer
   migrations:
     acct: brukernavn@domene til den nye kontoen
-    currently_redirecting: 'Din profil er omdirigert til:'
-    proceed: Lagre
-    updated_msg: Dine innstillinger for kontomigrering er oppdatert!
   moderation:
     title: Moderasjon
   notification_mailer:
diff --git a/config/locales/oc.yml b/config/locales/oc.yml
index 785caa4eccab9eccd276cc66d7aefa34fedeee7a..bd19401ed107cdd0bcb87c36adebb6eb1f56ee07 100644
--- a/config/locales/oc.yml
+++ b/config/locales/oc.yml
@@ -7,6 +7,7 @@ oc:
     active_count_after: actius
     active_footnote: Utilizaire actius per mes (UAM)
     administered_by: 'Administrat per :'
+    api: API
     apps: Aplicacions per mobil
     apps_platforms: Utilizatz Mastodon d‘iOS, Android o d’autras plataforma estant
     browse_directory: Navigatz per l’annuari de perfil e filtratz segon çò qu’aimatz
@@ -16,10 +17,7 @@ oc:
     contact_unavailable: Pas disponible
     discover_users: Descobrissètz de nòvas personas
     documentation: Documentacion
-    extended_description_html: |
-      <h3>Una bona plaça per las règlas</h3>
-      <p>La descripcion longa es pas estada causida pel moment.</p>
-    generic_description: "%{domain} es un dels servidors del malhum"
+    federation_hint_html: Amb un compte sus %{instance} poiretz sègre de personas de qualque siasque servidor Mastodon e encara mai.
     get_apps: Ensajatz una aplicacion mobil
     hosted_on: Mastodon albergat sus %{domain}
     learn_more: Ne saber mai
@@ -31,7 +29,11 @@ oc:
       one: estatut
       other: estatuts
     status_count_before: qu’an escrich
+    tagline: Seguètz d’amics e trobatz-ne de nòus
     terms: Condicions d’utilizacion
+    unavailable_content: Contengut pas disponible
+    unavailable_content_description:
+      reason: 'Motiu :'
     user_count_after:
       one: utilizaire
       other: utilizaires
@@ -50,6 +52,7 @@ oc:
     media: Mèdias
     moved_html: "%{name} a mudat a %{new_profile_link} :"
     network_hidden: Aquesta informacion es pas disponibla
+    never_active: Jamai
     nothing_here: I a pas res aquí !
     people_followed_by: Lo monde que %{name} sèc
     people_who_follow: Lo monde que sègon %{name}
@@ -62,8 +65,10 @@ oc:
     posts_with_replies: Tuts e responsas
     reserved_username: Aqueste nom d’utilizaire es reservat
     roles:
+      admin: Admin
       bot: Robòt
       moderator: Moderador
+    unavailable: Perfil indisponible
     unfollow: Quitar de sègre
   admin:
     account_actions:
@@ -76,7 +81,9 @@ oc:
       destroyed_msg: Nòta de moderacion ben suprimida !
     accounts:
       approve: Aprovar
+      approve_all: O validar tot
       are_you_sure: Sètz segur ?
+      avatar: Avatar
       by_domain: Domeni
       change_email:
         changed_msg: Adreça corrèctament cambiada !
@@ -107,6 +114,7 @@ oc:
       header: Bandièra
       inbox_url: URL de recepcion
       invited_by: Convidat per
+      ip: IP
       joined: Venguèt
       location:
         all: Totes
@@ -126,6 +134,7 @@ oc:
       moderation_notes: Nòtas de moderacion
       most_recent_activity: Activitat mai recenta
       most_recent_ip: IP mai recenta
+      no_account_selected: Cap de compte pas cambiat estant que cap èra pas seleccionat
       no_limits_imposed: Cap de limit impausat
       not_subscribed: Pas seguidor
       outbox_url: URL Outbox
@@ -134,8 +143,11 @@ oc:
       profile_url: URL del perfil
       promote: Promòure
       protocol: Protocòl
+      public: Public
       push_subscription_expires: Fin de l’abonament PuSH
       redownload: Actualizar lo perfil
+      reject: Regetar
+      reject_all: O regetar tot
       remove_avatar: Supriir l’avatar
       remove_header: Levar la bandièra
       resend_confirmation:
@@ -145,7 +157,9 @@ oc:
       reset: Reïnicializar
       reset_password: Reïnicializar lo senhal
       resubscribe: Se tornar abonar
+      role: Autorizacions
       roles:
+        admin: Administrator
         moderator: Moderador
         staff: Personnal
         user: Uitlizaire
@@ -160,6 +174,7 @@ oc:
       statuses: Estatuts
       subscribe: S’abonar
       suspended: Suspendut
+      time_in_queue: En espèra a la fila %{time}
       title: Comptes
       unconfirmed_email: Adreça pas confirmada
       undo_silenced: Levar lo silenci
@@ -167,6 +182,8 @@ oc:
       unsubscribe: Se desabonar
       username: Nom d’utilizaire
       warn: Avisar
+      web: Web
+      whitelisted: Mes en lista blanca
     action_logs:
       actions:
         assigned_to_self_report: "%{name} s’assignèt lo rapòrt %{target}"
@@ -202,18 +219,23 @@ oc:
       deleted_status: "(estatut suprimit)"
       title: Audit dels jornals
     custom_emojis:
+      assign_category: Atribuir una categoria
       by_domain: Domeni
       copied_msg: Còpia locala de l’emoji ben creada
       copy: Copiar
       copy_failed_msg: Fracàs de la còpia locala de l’emoji
+      create_new_category: Crear una nòva categoria
       created_msg: Emoji ben creat !
       delete: Suprimir
       destroyed_msg: Emoji ben suprimit !
       disable: Desactivar
       disabled_msg: Aqueste emoji es ben desactivat
+      emoji: Emoji
       enable: Activar
+      enabled: Activat
       enabled_msg: Aqueste emoji es ben activat
       image_hint: PNG cap a 50Ko
+      list: Listar
       listed: Listat
       new:
         title: Ajustar un nòu emoji personal
@@ -221,11 +243,14 @@ oc:
       shortcode: Acorchi
       shortcode_hint: Almens 2 caractèrs, solament alfanumerics e jonhent bas
       title: Emojis personals
+      uncategorized: Sens categoria
+      unlist: Listar pas
       unlisted: Pas listat
       update_failed_msg: Mesa a jorn de l’emoji fracasada
       updated_msg: Emoji ben mes a jorn !
       upload: Enviar
     dashboard:
+      authorized_fetch_mode: Mòde recuperacion autorizat
       backlog: Accions en retard
       config: Configuracion
       feature_deletions: Supressions de comptes
@@ -233,9 +258,13 @@ oc:
       feature_profile_directory: Annuari de perfils
       feature_registrations: Inscripcions
       feature_relay: Relai de federacion
+      feature_spam_check: Anti-spam
+      feature_timeline_preview: Apercebut del flux d’actualitats
       features: Foncionalitats
       hidden_service: Federacion amb servicis amagats
       open_reports: Senhalaments dobèrts
+      pending_tags: etiquetas en espèra de validacion
+      pending_users: utilizaires en espèra de validacion
       recent_users: Utilizaires recents
       search: Recèrca tèxte complèt
       single_user_mode: Mòde sol utilizaire
@@ -247,11 +276,19 @@ oc:
       week_interactions: interaccions aquesta setmana
       week_users_active: actius aquesta setmana
       week_users_new: utilizaires aquesta setmana
+      whitelist_mode: Mòde lista blanca
+    domain_allows:
+      add_new: Plaçar en lista blanca
+      created_msg: Lo domeni es corrèctament estat plaçat en lista blanca
+      destroyed_msg: Lo domeni es corrèctament estat levat de la lista blanca
+      undo: Levar de la lista blanca
     domain_blocks:
       add_new: Ajustar un novèl blocatge de domeni
       created_msg: Domeni blocat es a èsser tractat
       destroyed_msg: Lo blocatge del domeni es estat levat
       domain: Domeni
+      edit: Modificar lo blocatge de domeni
+      existing_domain_block_html: Impausèretz ja de limitas mai estrictas per %{name}, vos cal lo <a href="%{unblock_url}">desblocar</a>d’en primièr.
       new:
         create: Crear blocatge
         hint: Lo blocatge empacharà pas la creacion de compte dins la basa de donadas, mai aplicarà la moderacion sus aquestes comptes.
@@ -261,6 +298,9 @@ oc:
           silence: Silenci
           suspend: Suspendre
         title: Nòu blocatge domeni
+      private_comment: Comentari privat
+      private_comment_hint: Comentari tocant la limitacion d’aqueste domeni per un usatge intèrn pels moderators.
+      public_comment: Comentari public
       reject_media: Regetar los fichièrs mèdias
       reject_media_hint: Lèva los fichièrs gardats localament e regèta las demandas de telecargament dins lo futur. Servís pas a res per las suspensions
       reject_reports: Regetar los senhalaments
@@ -280,6 +320,7 @@ oc:
         title: Restablir lo blocatge de domeni de %{domain}
         undo: Restablir
       undo: Restablir
+      view: Veire lo blocatge de domeni
     email_domain_blocks:
       add_new: Ajustar
       created_msg: Blocatge del domeni de corrièl ben plaçat
@@ -303,6 +344,8 @@ oc:
         all: Totas
         limited: Limitat
         title: Moderacion
+      private_comment: Comentari privat
+      public_comment: Comentari public
       title: Federacion
       total_blocked_by_us: Avèm blocat
       total_followed_by_them: Sègon
@@ -317,6 +360,8 @@ oc:
         expired: Expirats
         title: Filtre
       title: Convits
+    pending_accounts:
+      title: Comptes en espèra (%{count})
     relays:
       add_new: Ajustar un nòu relai
       delete: Suprimir
@@ -330,6 +375,7 @@ oc:
       pending: En espèra d’aprovacion del relai
       save_and_enable: Salvar e activar
       setup: Configurar una connexion relai
+      signatures_not_enabled: Los relais foncionaràn pas coma cal se lo mòde segur o lista blanca es activat
       status: Estatut
       title: Relais
     report_notes:
@@ -378,6 +424,15 @@ oc:
       custom_css:
         desc_html: Modificar l’estil amb una fuèlha CSS cargada sus cada pagina
         title: CSS personalizada
+      default_noindex:
+        desc_html: Tòca totes los utilizaires qu’an pas cambiat lo paramètre
+      domain_blocks:
+        all: A tot lo monde
+        disabled: A degun
+        title: Mostrar los blocatges de domeni
+        users: Als utilizaires locals connectats
+      domain_blocks_rationale:
+        title: Mostrar lo rasonament
       hero:
         desc_html: Mostrat en primièra pagina. Almens 600x100px recomandat. S’es pas configurat l’imatge del servidor serà mostrat
         title: Imatge de l’eròi
@@ -405,7 +460,9 @@ oc:
           title: Autorizat amb invitacions
       registrations_mode:
         modes:
+          approved: Validacion necessària per s’inscriure
           none: Degun pòt pas se marcar
+          open: Tot lo monde se pòt marcar
         title: Mòdes d’inscripcion
       show_known_fediverse_at_about_page:
         desc_html: Un còp activat mostrarà los tuts de totes los fediverse dins l’apercebut. Autrament mostrarà pas que los tuts locals.
@@ -426,6 +483,8 @@ oc:
         desc_html: Afichada sus la pagina de las condicions d’utilizacion<br>Podètz utilizar de balisas HTML
         title: Politica de confidencialitat del site
       site_title: Títol del servidor
+      spam_check_enabled:
+        title: Anti-spam
       thumbnail:
         desc_html: Servís pels apercebuts via OpenGraph e las API. Talha de 1200x630px recomandada
         title: Miniatura del servidor
@@ -433,12 +492,15 @@ oc:
         desc_html: Mostrar lo flux public sus la pagina d’acuèlh
         title: Apercebut flux public
       title: Paramètres del site
+      trends:
+        title: Etiquetas tendéncia
     statuses:
       back_to_account: Tornar a la pagina Compte
       batch:
         delete: Suprimir
         nsfw_off: Marcar coma pas sensible
         nsfw_on: Marcar coma sensible
+      deleted: Suprimits
       failed_to_execute: Fracàs
       media:
         title: Mèdia
@@ -446,19 +508,23 @@ oc:
       no_status_selected: Cap d’estatut pas cambiat estant que cap èra pas seleccionat
       title: Estatuts del compte
       with_media: Amb mèdia
-    subscriptions:
-      callback_url: URL de rapèl
-      confirmed: Confirmat
-      expires_in: S’acaba dins
-      last_delivery: Darrièra distribucion
-      topic: Subjècte
     tags:
-      accounts: Comptes
-      hidden: Amagat
-      hide: Amagar dins l’annuari
+      accounts_today: Utilizacions unicas uèi
+      accounts_week: Utilizacions unicas aquesta setmana
+      context: Contèxt
+      directory: A l’annuari
+      in_directory: "%{count} a l’annuari"
+      last_active: Darrièra activitat
+      most_popular: Mai popularas
+      most_recent: Mai recentas
       name: Etiqueta
+      review: Repassar l’estatut
+      reviewed: Repassadas
       title: Etiquetas
-      unhide: Aparéisser dins l’annuari
+      trending_right_now: Actualament en tendéncia
+      unique_uses_today: "%{count} publicacions uèi"
+      unreviewed: Pas repassadas
+      updated_msg: Paramètres d’etiquetas corrèctament actualizats
     title: Administracion
     warning_presets:
       add_new: N’ajustar un nòu
@@ -467,12 +533,23 @@ oc:
       edit_preset: Modificar lo tèxt predefinit d’avertiment
       title: Gerir los tèxtes predefinits
   admin_mailer:
+    new_pending_account:
+      body: Los detalhs del nòu compte son çai-jos. Podètz validar o regetar aquesta demanda.
+      subject: Nòu compte per repassar sus %{instance} (%{username})
     new_report:
       body: "%{reporter} a senhalat %{target}"
       body_remote: Qualqu’un de %{domain} senhalèt %{target}
       subject: Novèl senhalament per %{instance} (#%{id})
+  appearance:
+    advanced_web_interface: Interfàcia web avançada
+    advanced_web_interface_hint: 'Se volètz utilizar la nautor complèta de l’ecran, l’interfàcia web avançada vos permet de configurar diferentas colomnas per mostrar tan d’informacions que volètz : Acuèlh, notificacions, flux d’actualitat, e d’autras listas e etiquetas.'
+    animations_and_accessibility: Animacion e accessibilitat
+    confirmation_dialogs: Fenèstras de confirmacion
+    discovery: Descobèrta
+    sensitive_content: Contengut sensible
   application_mailer:
     notification_preferences: Cambiar las preferéncias de corrièl
+    salutation: "%{name},"
     settings: 'Cambiar las preferéncias de corrièl : %{link}'
     view: 'Veire :'
     view_profile: Veire lo perfil
@@ -489,9 +566,12 @@ oc:
     apply_for_account: Demandar una invitacion
     change_password: Senhal
     checkbox_agreement_html: Accepti las <a href="%{rules_path}" target="_blank">règlas del servidor</a> e <a href="%{terms_path}" target="_blank">los tèrmes del servici</a>
-    confirm_email: Confirmar lo corrièl
+    checkbox_agreement_without_rules_html: Soi d’acòrdi amb las <a href="%{terms_path}" target="_blank">condicions d’utilizacion</a>
     delete_account: Suprimir lo compte
     delete_account_html: Se volètz suprimir vòstre compte, podètz <a href="%{path}">o far aquí</a>. Vos demandarem que confirmetz.
+    description:
+      prefix_invited_by_user: "@%{name} vos convida a rejónher aqueste servidor Mastodon !"
+      prefix_sign_up: Marcatz-vos a Mostodon uèi !
     didnt_get_confirmation: Avètz pas recebut las instruccions de confirmacion ?
     forgot_password: Senhal oblidat ?
     invalid_reset_password_token: Lo geton de reïnicializacion es invalid o acabat. Tornatz demandar un geton se vos plai.
@@ -500,12 +580,20 @@ oc:
     migrate_account: Mudar endacòm mai
     migrate_account_html: Se volètz mandar los visitors d’aqueste compte a un autre, podètz<a href="%{path}"> o configurar aquí</a>.
     or_log_in_with: O autentificatz-vos amb
+    providers:
+      cas: CAS
+      saml: SAML
     register: Se marcar
     registration_closed: "%{instance} accepta pas de nòus membres"
     resend_confirmation: Tornar mandar las instruccions de confirmacion
     reset_password: Reïnicializar lo senhal
     security: Seguretat
     set_new_password: Picar un nòu senhal
+    setup:
+      title: Configuracion
+    status:
+      account_status: Estat del compte
+      functional: Vòstre compte es complètament foncional.
     trouble_logging_in: Problèmas de connexion ?
   authorize_follow:
     already_following: Seguètz ja aqueste compte
@@ -518,6 +606,11 @@ oc:
       return: Veire lo perfil a la persona
       web: Tornar a l’interfàcia Web
     title: Sègre %{acct}
+  challenge:
+    confirm: Contunhar
+    hint_html: "<strong>Asutúcia :</strong> vos demandarem pas vòstre senhal de nòu d’aquí unas oras."
+    invalid_password: Senhal invalid
+    prompt: Confirmatz lo senhal per dire de contunhar
   datetime:
     distance_in_words:
       about_x_hours: "%{count} h"
@@ -531,27 +624,31 @@ oc:
       x_days: "%{count} jorns"
       x_minutes: "%{count} min"
       x_months: "%{count} meses"
+      x_seconds: "%{count}s"
   deletes:
-    bad_password_msg: Ben ensajat pirata ! Senhal incorrècte
+    challenge_not_passed: Las informacions qu’avètz fornidas son pas corrèctas
     confirm_password: Picatz vòstre senhal actual per verificar vòstra identitat
-    description_html: Aquò suprimirà <strong>definitivament e sens possibilitat de retorn</strong> lo contengut de vòstre compte e lo desactivarà. Lo nom d’utilizaire serà gardat per evitar una futura impostura.
+    confirm_username: Picatz vòstre nom d’utilizaire per confirmar la procedura
     proceed: Suprimir lo compte
     success_msg: Compte ben suprimit
-    warning_html: La supression del contengut d’aqueste servidor es sola assegurada. Lo contengut fòrça partejat daissarà probablament de traças. Los servidors fòra-linha e los que vos sègon pas mai auràn pas la mesa a jorn de lor basa de donada.
-    warning_title: Disponibilitat del contengut difusat
+    warning:
+      caches: Lo contengut en cache suls autres servidors pòt demorar
+      email_change_html: Podètz <a href="%{path}">cambiar vòstra adreça electronia</a>sens suprimir vòstre compte
+      irreversible: Poiretz pas restaurar o reactivar vòstre compte
+      more_details_html: Per mai d’informacion, vejatz la <a href="%{terms_path}">politica de confidencialitat</a>.
+      username_available: Vòstre nom d’utilizaire serà disponible de nòu
+      username_unavailable: Vòstre nom d’utilizaire demorarà pas disponible
   directories:
     directory: Annuari de perfils
-    enabled: Sètz actualament listat dins l'annuari.
-    enabled_but_waiting: Avètz causit d'èsser listat dins l'annuari mas avètz pas encara lo nombre minimum de seguidors (%{min_followers}) per i èsser listat.
     explanation: Trobar d’utilizaires segon lor interèsses
     explore_mastodon: Explorar %{title}
-    how_to_enable: Sètz pas actualament listat dins l’annuari. Podètz cambiar aquò çai-jos. Utilizatz d'etiquetas dins vòstre tèxt de bio per èsser listat amb d’etiquetas especificas !
-    people:
-      one: "%{count} persona"
-      other: "%{count} personas"
+  domain_validator:
+    invalid_domain: es pas un nom de domeni valid
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Avètz pas l’autorizacion de veire aquesta pagina.
     '404': La pagina que cercatz existís pas aquí.
+    '406': La pagina es pas disponibla dins lo format demandat.
     '410': La pagina que cercatz existís pas mai aquí.
     '422':
       content: Verificacion de seguretat fracassada. Blocatz los cookies ?
@@ -560,16 +657,21 @@ oc:
     '500':
       content: Un quicomet a pas foncionat coma caliá.
       title: Aquesta pagina es pas corrècta
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: Per utilizar l’aplicacion web de Mastodon, mercés d’activar JavaScript. O podètz utilizar <a href="%{apps_path}">una aplicacion</a> per vòstra plataforma coma alernativa.
+  existing_username_validator:
+    not_found: impossible de trobar un utilizaire local amb aqueste nom d’utilizaire
+    not_found_multiple: impossible de trobar %{usernames}
   exports:
     archive_takeout:
       date: Data
       download: Telecargar vòstre archiu
-      hint_html: Podètz demandar un archiu de vòstres <strong>tuts e mèdias enviats</strong>. Las donadas exportadas seràn al format ActivityPub, ligible pels logicials compatibles. Podètz demandar un archiu cada 7 jorns.
-      in_progress: Complilacion de vòstre archiu...
+      hint_html: Podètz demandar un archiu de vòstres <strong>tuts e mèdias enviats</strong>. Las donadas exportadas seràn al format ActivityPub, legible pels logicials compatibles. Podètz demandar un archiu cada 7 jorns.
+      in_progress: Compilacion de vòstre archiu...
       request: Demandar vòstre archiu
       size: Talha
     blocks: Personas que blocatz
+    csv: CSV
     domain_blocks: Blocatge de domenis
     follows: Personas que seguètz
     lists: Listas
@@ -599,20 +701,37 @@ oc:
     developers: Desvolopaires
     more: Mai…
     resources: Ressorsas
+    trending_now: Tendéncia del moment
   generic:
     all: Tot
     changes_saved_msg: Cambiaments ben realizats !
     copy: Copiar
+    no_batch_actions_available: Cap d’accion de massa pas disponibla sus aquesta pagina
+    order_by: Triar per
     save_changes: Salvar los cambiaments
     validation_errors:
       one: I a quicòm que truca ! Mercés de corregir l’error çai-jos
       other: I a quicòm que truca ! Mercés de corregir las %{count} errors çai-jos
+  html_validator:
+    invalid_markup: 'conten un balisatge HTML invalid : %{error}'
   identity_proofs:
+    active: Actiu
     authorize: Ã’c, autorizar
     authorize_connection_prompt: Autorizar aquesta connexion criptografica ?
+    errors:
+      failed: La connexion criptografica a fracassat. Ensajatz tornamai de %{provider} estant.
+      keybase:
+        invalid_token: Los getons Keybase son de hashes de signaturas e devon èsser de caractèrs 66 hex
+        verification_failed: Keybase reconeis pas aqueste geton coma signatura de l’utilizaire Keybase %{kb_username}. Ensajatz tornamai de Keybase estant.
+      wrong_user: Creacion impossibla de la pròva per %{proving} en estant connectat coma %{current}. Connectatz-vos coma %{proving} e ensajatz tornamai.
+    explanation_html: Aquí podètz connectar d’un biais criptografic vòstras identitats, coma un perfil Keybase. Aquò permet al monde de vos enviar de messatges chifrats e fisar al contengut que lor enviatz.
     i_am_html: Soi %{username} a %{service}.
     identity: Identitat
+    inactive: Inactiu
+    publicize_checkbox: 'E enviatz lo tut seguent :'
+    publicize_toot: 'Es provat ! Soi %{username} de %{service} : %{url}'
     status: Estatut de verificacion
+    view_proof: Veire la pròva
   imports:
     modes:
       merge: Fondre
@@ -659,9 +778,20 @@ oc:
       too_many: Se pòt pas ajustar mai de 4 fichièrs
   migrations:
     acct: nomutilizaire@domeni del nòu compte
-    currently_redirecting: 'Vòstre perfil es parametrat per mandar a :'
-    proceed: Enregistrar
-    updated_msg: Vòstre paramètre de migracion es ben estat mes a jorn !
+    cancel: Anullar la redireccion
+    cancelled_msg: Redireccion corrèctament anullada.
+    errors:
+      move_to_self: pòt pas èsser lo compte actual
+      not_found: impossible de trobar
+    incoming_migrations: Mudar d’un compte diferent
+    moved_msg: Vòstre compte manda ara a %{acct} e vòstres seguidors son desplaçats.
+    not_redirecting: Vòstre compte manda pas enlòc pel moment.
+    past_migrations: Migracions passadas
+    proceed_with_move: Desplaçar los seguidors
+    redirecting_to: Vòstre compte manda a %{acct}.
+    warning:
+      before: 'Abans de contunhar, volgatz legir aquestas nòtas amb atencion :'
+      other_data: Cap d’autra donada serà desplaçada automaticament
   moderation:
     title: Moderacion
   notification_mailer:
@@ -698,11 +828,22 @@ oc:
       body: "%{name} a tornat partejar vòstre estatut :"
       subject: "%{name} a tornat partejar vòstre estatut"
       title: Novèl partatge
+  number:
+    human:
+      decimal_units:
+        format: "%n%u"
+        units:
+          billion: B
+          million: M
+          quadrillion: Q
+          thousand: K
+          trillion: T
   pagination:
     newer: Mai recents
     next: Seguent
     older: Mai ancians
     prev: Precedent
+    truncate: "&hellip;"
   polls:
     errors:
       already_voted: Avètz ja votat per aqueste sondatge
@@ -715,13 +856,20 @@ oc:
       too_many_options: pòt pas contenir mai de %{max} opcions
   preferences:
     other: Autre
+    posting_defaults: Valors per defaut de las publicacions
+    public_timelines: Fluxes d’actualitats publics
   relationships:
     activity: Activitat del compte
     dormant: Inactiu
+    last_active: Darrièra activitat
+    most_recent: Mai recenta
     moved: Mudat
     mutual: Mutuala
     primary: Pirmària
     relationship: Relacion
+    remove_selected_domains: Levar totes los seguidors dels domenis seleccionats
+    remove_selected_followers: Levar los seguidors seleccionats
+    remove_selected_follows: Quitar de sègre las personas seleccionadas
     status: Estat del compte
   remote_follow:
     acct: Picatz vòstre utilizaire@domeni que que volètz utilizar per sègre aqueste utilizaire
@@ -740,9 +888,6 @@ oc:
     reply:
       proceed: Contunhar per respondre
       prompt: 'Volètz respondre a aqueste tut :'
-  remote_unfollow:
-    title: Títol
-    unfollowed: Pas mai seguit
   scheduled_statuses:
     over_daily_limit: Avètz passat la limita de %{limit}  tuts programats per aquel jorn
     over_total_limit: Avètz passat la limita de %{limit}  tuts programats
@@ -751,15 +896,47 @@ oc:
     activity: Darrièra activitat
     browser: Navigator
     browsers:
+      alipay: Alipay
+      blackberry: Blackberry
+      chrome: Chrome
+      edge: Microsoft Edge
+      electron: Electron
+      firefox: Firefox
       generic: Navigator desconegut
+      ie: Internet Explorer
+      micro_messenger: MicroMessenger
+      nokia: Nokia S40 Ovi Browser
+      opera: Opera
+      otter: Otter
+      phantom_js: PhantomJS
+      qq: QQ Browser
+      safari: Safari
+      uc_browser: UCBrowser
+      weibo: Weibo
     current_session: Session en cors
     description: "%{browser} sus %{platform}"
     explanation: Aquí los navigators connectats a vòstre compte Mastodon.
+    ip: IP
     platforms:
+      adobe_air: Adobe Air
+      android: Android
+      blackberry: Blackberry
+      chrome_os: ChromeOS
+      firefox_os: Firefox OS
+      ios: iOS
+      linux: Linux
+      mac: Mac
       other: plataforma desconeguda
+      windows: Windows
+      windows_mobile: Windows Mobile
+      windows_phone: Windows Phone
     revoke: Revocar
     revoke_success: Session ben revocada
+    title: Sessions
   settings:
+    account: Compte
+    account_settings: Paramètres de compte
+    appearance: Aparéncia
     authorized_apps: Aplicacions autorizadas
     back: Tornar a Mastodon
     delete: Supression de compte
@@ -767,10 +944,13 @@ oc:
     edit_profile: Modificar lo perfil
     export: Exportar de donadas
     featured_tags: Etiquetas en avant
+    identity_proofs: Pròvas d’identitat
     import: Importar de donadas
+    import_and_export: Import e export
     migrate: Migracion de compte
     notifications: Notificacions
     preferences: Preferéncias
+    profile: Perfil
     relationships: Abonaments e seguidors
     two_factor_authentication: Autentificacion en dos temps
   statuses:
@@ -806,6 +986,7 @@ oc:
     visibilities:
       private: Seguidors solament
       private_long: Mostrar pas qu’als seguidors
+      public: Public
       public_long: Tot lo monde pòt veire
       unlisted: Pas listat
       unlisted_long: Tot lo monde pòt veire mai serà pas visible sul flux public
@@ -813,6 +994,8 @@ oc:
     pinned: Tut penjat
     reblogged: a partejat
     sensitive_content: Contengut sensible
+  tags:
+    does_not_match_previous_name: correspond pas al nom precedent
   terms:
     body_html: |
       <h2>Politica de confidencialitat</h2>
@@ -905,6 +1088,7 @@ oc:
   time:
     formats:
       default: Lo %d %b de %Y a %Ho%M
+      month: "%b de %Y"
   two_factor_authentication:
     code_hint: Picatz lo còdi generat per vòstra aplicacion d’autentificacion per confirmar
     description_html: S’activatz <strong> l’autentificacion two-factor</strong>, vos caldrà vòstre mobil per vos connectar perque generarà un geton per vos daissar dintrar.
@@ -931,7 +1115,9 @@ oc:
         disable: Quand vòstre compte es gelat, las donadas d’aqueste demòran senceras, mas podètz pas realizar cap d’accion fins que siá desblocat.
         silence: Del temps que vòstre compte es limitat, solament lo monde que vos sègon veiràn vòstres tuts sus aqueste servidor, e poiriatz èsser tirat de mantunas listas publicas. Pasmens, d’autres vos pòdon sègre manualament.
         suspend: Vòstre compte es suspendut e totes vòstres tuts e fichièrs enviats son estats suprimits sens retorn possible d’aqueste servidor e los de vòstres seguidors.
+      get_in_touch: Podètz respondre a aqueste corrièl per contactar la còla de %{instance}.
       review_server_policies: Repassar las politicas del servidor
+      statuses: 'Especificament per :'
       subject:
         disable: Vòstre compte %{acct} es gelat
         none: Avertiment per %{acct}
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index 78db4c6723440066a59675099917c0318409a41c..3c24a18caca7d0acdb01982ee46e4bd256600f07 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -17,11 +17,7 @@ pl:
     contact_unavailable: Nie dotyczy
     discover_users: Odkrywaj użytkowników
     documentation: Dokumentacja
-    extended_description_html: |
-      <h3>Dobre miejsce na zasady użytkowania</h3>
-      <p>Nie ustawiono jeszcze szczegółowego opisu</p>
     federation_hint_html: Z kontem na %{instance}, możesz śledzić użytkowników każdego serwera Mastodona i nie tylko.
-    generic_description: "%{domain} jest jednym z serwerów sieci"
     get_apps: Spróbuj aplikacji mobilnej
     hosted_on: Mastodon uruchomiony na %{domain}
     learn_more: Dowiedz się więcej
@@ -52,7 +48,7 @@ pl:
       many: śledzących
       one: śledzący
       other: ÅšledzÄ…cych
-    following: Åšledzonych
+    following: śledzonych
     joined: Dołączył(a) %{date}
     last_active: ostatnio aktywny(-a)
     link_verified_on: Własność tego odnośnika została sprawdzona %{date}
@@ -481,21 +477,8 @@ pl:
       no_status_selected: Żaden wpis nie został zmieniony, bo żaden nie został wybrany
       title: Wpisy konta
       with_media: Z zawartością multimedialną
-    subscriptions:
-      callback_url: URL zwrotny
-      confirmed: Potwierdzone
-      expires_in: Wygasa
-      last_delivery: Ostatnio doręczono
-      title: WebSub
-      topic: Temat
     tags:
-      accounts: Konta
-      hidden: Ukryte
-      hide: Ukryj w katalogu
-      name: Hasztag
       title: Hashtagi
-      unhide: Pokazuj w katalogu
-      visible: Widoczne
     title: Administracja
     warning_presets:
       add_new: Dodaj nowy
@@ -536,7 +519,6 @@ pl:
     apply_for_account: PoproÅ› o zaproszenie
     change_password: Hasło
     checkbox_agreement_html: Zgadzam się z <a href="%{rules_path}" target="_blank">regułami serwera</a> i <a href="%{terms_path}" target="_blank">zasadami korzystania z usługi</a>
-    confirm_email: Potwierdź adres e-mail
     delete_account: Usunięcie konta
     delete_account_html: Jeżeli chcesz usunąć konto, <a href="%{path}">przejdź tutaj</a>. Otrzymasz prośbę o potwierdzenie.
     didnt_get_confirmation: Nie otrzymałeś(-aś) instrukcji weryfikacji?
@@ -583,28 +565,18 @@ pl:
       x_months: "%{count} miesięcy"
       x_seconds: "%{count}s"
   deletes:
-    bad_password_msg: Niezła próba, hakerze! Wprowadzono nieprawidłowe hasło
     confirm_password: Wprowadź aktualne hasło, aby potwierdzić tożsamość
-    description_html: Ta opcja usunie <strong>bezpowrotnie i nieodwracalnie</strong> całą zawartość konta i zdezaktywuje je. Twoja nazwa użytkownika pozostanie zarezerwowana, aby zapobiec nadużyciom.
     proceed: Usuń konto
     success_msg: Twoje konto zostało pomyślnie usunięte
-    warning_html: Możemy usunąć zawartość jedynie w obrębie tego serwera. Zawartość udostępniona publicznie pozostawia trwałe ślady. Serwery niepodłączone do sieci bądź nieśledzące Twoich aktualizacji mogą zachować Twoje dane.
-    warning_title: Dostępność usuniętej zawartości
   directories:
     directory: Katalog profilów
-    enabled: JesteÅ› obecnie zapisany(-a) do katalogu
-    enabled_but_waiting: Jesteś zapisany(-a) do katalogu, ale jeszcze nie śledzi Cię wystarczająca liczba osób (%{min_followers}), aby się tam pojawić.
     explanation: Poznaj profile na podstawie zainteresowań
     explore_mastodon: Odkrywaj %{title}
-    how_to_enable: Nie jesteś obecnie zapisany(-a) do katalogu. Poniżej możesz zapisać się. Użyj hashtagów w swoim opisie, aby zostać wyświetlonym pod określonymi hashtagami!
-    people:
-      few: "%{count} osoby"
-      many: "%{count} osób"
-      one: "%{count} osoba"
-      other: "%{count} osób"
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Nie masz uprawnień, aby wyświetlić tę stronę.
     '404': Strona, którą próbujesz odwiedzić, nie istnieje.
+    '406': This page is not available in the requested format.
     '410': Strona, którą próbujesz odwiedzić, przestała istnieć.
     '422':
       content: Sprawdzanie bezpieczeństwa nie powiodło się. Czy blokujesz pliki cookie?
@@ -613,6 +585,7 @@ pl:
     '500':
       content: Przepraszamy, coś poszło nie tak, po naszej stronie.
       title: Ta strona jest nieprawidłowa
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: Aby korzystać z aplikacji Mastodon, włącz JavaScript. Możesz też skorzystać z jednej z <a href="%{apps_path}">natywnych aplikacji</a> obsługującej Twoje urządzenie.
   existing_username_validator:
     not_found: nie znaleziono lokalnego użytkownika o tej nazwie
@@ -735,9 +708,6 @@ pl:
       too_many: Nie możesz załączyć więcej niż 4 plików
   migrations:
     acct: nazwa@domena nowego konta
-    currently_redirecting: 'Obecnie Twoje konto przekierowuje do:'
-    proceed: Zapisz
-    updated_msg: Pomyślnie zaktualizowano ustawienia migracji Twojego konta!
   moderation:
     title: Moderacja
   notification_mailer:
@@ -838,10 +808,6 @@ pl:
     reply:
       proceed: Przejdź do dodawania odpowiedzi
       prompt: 'Chcesz odpowiedzieć na ten wpis:'
-  remote_unfollow:
-    error: Błąd
-    title: Tytuł
-    unfollowed: Przestałeś(-aś) śledzić
   scheduled_statuses:
     over_daily_limit: Przekroczyłeś(-aś) limit %{limit} zaplanowanych wpisów na ten dzień
     over_total_limit: Przekroczyłeś(-aś) limit %{limit} zaplanowanych wpisów
@@ -1023,9 +989,9 @@ pl:
 
       <h3 id="children">Korzystanie ze strony przez dzieci</h3>
 
-      <p>Jeżeli serwer znajduje się w UE lub w EOG: Ta strona, produkty i usługi są przeznaczone dla osób, które ukończyły 16 lat. Jeżeli nie ukończyłeś(-aś) 16 roku życia, zgodnie z wymogami COPPA (<a href="https://pl.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Prawo o Ochronie Prywatności Dzieci w Internecie</a>), nie używaj tej strony.</p>
+      <p>Jeżeli serwer znajduje się w UE lub w EOG: Ta strona, produkty i usługi są przeznaczone dla osób, które ukończyły 16 lat. Jeżeli nie ukończyłeś(-aś) 16 roku życia, zgodnie z wymogami RODO (<a href="https://pl.wikipedia.org/wiki/Ogólne_rozporządzenie_o_ochronie_danych">Ogólne rozporządzenie o ochronie danych</a>), nie używaj tej strony.</p>
 
-      <p>Jeżeli serwer znajduje się w USA: Ta strona, produkty i usługi są przeznaczone dla osób, które ukończyły 13 lat. Jeżeli nie ukończyłeś(-aś) 13 roku życia, zgodnie z wymogami RODO (<a href="https://pl.wikipedia.org/wiki/Ogólne_rozporządzenie_o_ochronie_danych">Ogólne rozporządzenie o ochronie danych</a>), nie używaj tej strony.</p>
+      <p>Jeżeli serwer znajduje się w USA: Ta strona, produkty i usługi są przeznaczone dla osób, które ukończyły 13 lat. Jeżeli nie ukończyłeś(-aś) 13 roku życia, zgodnie z wymogami COPPA (<a href="https://pl.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Prawo o Ochronie Prywatności Dzieci w Internecie</a>), nie używaj tej strony.</p>
 
       <p>Wymogi mogą być inne, jeżeli serwer znajduje się w innym kraju.</p>
 
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 2f339b1edc0eec7a1e9224fccf932238007f34bd..c21d98063c7e46a1a920e50cd5699b8fb8a682ec 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -7,6 +7,7 @@ pt-BR:
     active_count_after: Ativo
     active_footnote: Usuários ativos mensais (UAM)
     administered_by: 'Administrado por:'
+    api: API
     apps: Apps
     apps_platforms: Use o Mastodon a partir de iOS, Android e outras plataformas
     browse_directory: Navegue pelo diretório de perfis e filtre por interesses
@@ -16,11 +17,7 @@ pt-BR:
     contact_unavailable: Não disponível
     discover_users: Descubra usuários
     documentation: Documentação
-    extended_description_html: |
-      <h3>Um bom lugar para regras</h3>
-      <p>A descrição da instância ainda não foi feita.</p>
     federation_hint_html: Com uma conta em %{instance} você vai poder seguir pessoas em qualquer servidor Mastodon ou outros do fediverso.
-    generic_description: "%{domain} é um servidor na rede"
     get_apps: Experimente um aplicativo
     hosted_on: Mastodon hospedado em %{domain}
     learn_more: Saiba mais
@@ -31,6 +28,11 @@ pt-BR:
     status_count_before: Autores de
     tagline: Siga amigos e encontre novos
     terms: Termos de serviço
+    unavailable_content: Conteúdo indisponível
+    unavailable_content_description:
+      reason: 'Motivo:'
+      suspended: Você não será capaz de seguir ninguém deste servidor, e nenhum dado dele será processado ou armazenado, e nenhum dado trocado.
+    unavailable_content_html: Mastodon geralmente permite que você veja o conteúdo e interaja com usuários de qualquer outro servidor no fediverso. Estas são as exceções deste servidor específico.
     user_count_after:
       one: usuário
       other: usuários
@@ -38,6 +40,7 @@ pt-BR:
     what_is_mastodon: O que é Mastodon?
   accounts:
     choices_html: 'Escolhas de %{name}:'
+    featured_tags_hint: Você pode destacar hashtags específicas que serão exibidas aqui.
     follow: Seguir
     followers:
       one: Seguidor
@@ -49,11 +52,16 @@ pt-BR:
     media: Mídia
     moved_html: "%{name} se mudou para %{new_profile_link}:"
     network_hidden: Esta informação não está disponível
+    never_active: Nunca
     nothing_here: Não há nada aqui!
     people_followed_by: Pessoas que %{name} segue
     people_who_follow: Pessoas que seguem %{name}
     pin_errors:
       following: Você tem que estar seguindo a pessoa que você quer sugerir
+    posts:
+      one: Toot
+      other: Toots
+    posts_tab_heading: Toots
     posts_with_replies: Toots e respostas
     reserved_username: Este usuário está reservado
     roles:
@@ -75,6 +83,7 @@ pt-BR:
       approve: Aprovar
       approve_all: Aprovar tudo
       are_you_sure: Você tem certeza?
+      avatar: Imagem de Perfil
       by_domain: Domínio
       change_email:
         changed_msg: E-mail da conta modificado com sucesso!
@@ -105,9 +114,11 @@ pt-BR:
       header: Cabeçalho
       inbox_url: URL da caixa de entrada
       invited_by: Convidado por
+      ip: IP
       joined: Se cadastrou
       location:
         all: Todos
+        local: Local
         remote: Remoto
         title: Localização
       login_status: Situação de login
@@ -163,6 +174,7 @@ pt-BR:
       statuses: Postagens
       subscribe: Inscrever-se
       suspended: Suspenso
+      time_in_queue: Esperando na fila por %{time}
       title: Contas
       unconfirmed_email: E-mail não confirmado
       undo_silenced: Retirar silenciamento
@@ -170,6 +182,7 @@ pt-BR:
       unsubscribe: Desinscrever-se
       username: Nome de usuário
       warn: Notificar
+      web: Web
     action_logs:
       actions:
         assigned_to_self_report: "%{name} designou a denúncia %{target} para si"
@@ -205,15 +218,18 @@ pt-BR:
       deleted_status: "(status deletado)"
       title: Auditar relatório
     custom_emojis:
+      assign_category: Designar Categoria
       by_domain: Domínio
       copied_msg: Cópia local do emoji criada com sucesso
       copy: Copiar
       copy_failed_msg: Não foi possível criar uma cópia local deste emoji
+      create_new_category: Criar nova categoria
       created_msg: Emoji criado com sucesso!
       delete: Excluir
       destroyed_msg: Emoji deletado com sucesso!
       disable: Desabilitar
       disabled_msg: Emoji desabilitado com sucesso
+      emoji: Emoji
       enable: Habilitar
       enabled_msg: Emoji habilitado com sucesso
       image_hint: PNG de até 50KB
@@ -224,6 +240,7 @@ pt-BR:
       shortcode: Atalho
       shortcode_hint: Pelo menos 2 caracteres, apenas caracteres alfanuméricos e underscores
       title: Emojis customizados
+      uncategorized: Não categorizado
       unlisted: Não listado
       update_failed_msg: Não foi possível atualizar esse emoji
       updated_msg: Emoji atualizado com sucesso!
@@ -236,13 +253,17 @@ pt-BR:
       feature_profile_directory: Diretório de perfis
       feature_registrations: Cadastros
       feature_relay: Repetidor da federação
+      feature_spam_check: Anti-spam
       feature_timeline_preview: pré-visualização da timeline
       features: Funcionalidades
       hidden_service: Federação com serviços onion
       open_reports: Denúncias em aberto
+      pending_tags: hashtags aguardando revisão
+      pending_users: usuários aguardando revisão
       recent_users: Usuários recentes
       search: Pesquisa em texto
       single_user_mode: Modo de usuário único
+      software: Software
       space: Uso de espaço em disco
       title: Painel de controle
       total_users: usuários no total
@@ -255,6 +276,8 @@ pt-BR:
       created_msg: Bloqueio de domínio está sendo processado
       destroyed_msg: Bloqueio de domínio desfeito
       domain: Domínio
+      edit: Editar bloqueio de domínio
+      existing_domain_block_html: Você já impôs limites mais restritivos a %{name}, é necessário primeiro <a href="%{unblock_url}">desbloqueá-lo</a>.
       new:
         create: Criar bloqueio
         hint: O bloqueio de domínio não prevenirá a criação de entradas de contas na base de dados, mas vai reatroativa e automaticamente aplicar métodos específicos de moderação nestas contas.
@@ -264,6 +287,8 @@ pt-BR:
           silence: Silêncio
           suspend: Suspensão
         title: Novo bloqueio de domínio
+      private_comment: Comentário privado
+      public_comment: Comentário público
       reject_media: Rejeitar arquivos de mídia
       reject_media_hint: Remove arquivos de mídia armazenados localmente e recusa quaisquer outros no futuro. Irrelevante para suspensões
       reject_reports: Rejeitar denúncias
@@ -283,6 +308,7 @@ pt-BR:
         title: Retirar bloqueio de domínio de %{domain}
         undo: Retirar
       undo: Retirar bloqueio de domínio
+      view: Ver domínios bloqueados
     email_domain_blocks:
       add_new: Adicionar novo
       created_msg: Bloqueio de domínio de e-mail criado com sucesso
@@ -306,6 +332,8 @@ pt-BR:
         all: Todas
         limited: Limitado
         title: Moderação
+      private_comment: Comentário privado
+      public_comment: Comentário público
       title: Federação
       total_blocked_by_us: Bloqueado por nós
       total_followed_by_them: Seguidos por eles
@@ -381,6 +409,15 @@ pt-BR:
       custom_css:
         desc_html: Modificar o visual com CSS que é carregado em todas as páginas
         title: CSS customizado
+      default_noindex:
+        desc_html: Afeta qualquer usuário que não tenha modificado esta configuração manualmente
+      domain_blocks:
+        all: Para todo mundo
+        disabled: Para ninguém
+        title: Mostrar domínios bloqueados
+        users: Para usuários locais logados
+      domain_blocks_rationale:
+        title: Visualizar justificativa
       hero:
         desc_html: Aparece na página inicial. Ao menos 600x100px é recomendado. Se não estiver definido, o thumbnail da instância é usado no lugar
         title: Imagem de capa
@@ -431,6 +468,8 @@ pt-BR:
         desc_html: Você pode escrever a sua própria política de privacidade, termos de serviço, entre outras coisas. Você pode usar tags HTML
         title: Termos de serviço customizados
       site_title: Nome da instância
+      spam_check_enabled:
+        title: Automação anti-spam
       thumbnail:
         desc_html: Usada para prévias via OpenGraph e API. Recomenda-se 1200x630px
         title: Miniatura da instância
@@ -438,12 +477,15 @@ pt-BR:
         desc_html: Exibir a timeline pública na página inicial
         title: Prévia da timeline
       title: Configurações do site
+      trends:
+        title: Hashtags em alta
     statuses:
       back_to_account: Voltar para página da conta
       batch:
         delete: Deletar
         nsfw_off: Marcar como não-sensível
         nsfw_on: Marcar como sensível
+      deleted: Excluídos
       failed_to_execute: Falha em executar
       media:
         title: Mídia
@@ -451,18 +493,16 @@ pt-BR:
       no_status_selected: Nenhum status foi modificado porque nenhum estava selecionado
       title: Postagens da conta
       with_media: Com mídia
-    subscriptions:
-      callback_url: URL de Callback
-      confirmed: Confirmado
-      expires_in: Expira em
-      last_delivery: Última entrega
-      topic: Tópico
     tags:
-      accounts: Contas
-      hidden: Escondido
-      hide: Esconder do diretório
-      unhide: Mostrar no diretório
-      visible: Visível
+      context: Contexto
+      directory: No diretório
+      most_popular: Mais populares
+      most_recent: Mais recentes
+      name: Hashtag
+      title: Hashtags
+      trending_right_now: Em alta no momento
+      unreviewed: Não revisadas
+      updated_msg: Configurações de hashtag atualizadas com sucesso
     title: Administração
     warning_presets:
       add_new: Adicionar um novo
@@ -478,8 +518,14 @@ pt-BR:
       body: "%{reporter} denunciou %{target}"
       body_remote: Alguém da instância %{domain} reportou %{target}
       subject: Nova denúncia sobre %{instance} (#%{id})
+  appearance:
+    animations_and_accessibility: Animações e acessibilidade
+    confirmation_dialogs: Popups de confirmação
+    discovery: Descobrir
+    sensitive_content: Conteúdo sensível
   application_mailer:
     notification_preferences: Mudar preferências de e-mail
+    salutation: "%{name},"
     settings: 'Mudar e-mail de preferência: %{link}'
     view: 'Visualizar:'
     view_profile: Ver perfil
@@ -496,9 +542,13 @@ pt-BR:
     apply_for_account: Pedir um convite
     change_password: Senha
     checkbox_agreement_html: Eu concordo com <a href="%{rules_path}" target="_blank">as regras do servidor</a> e com <a href="%{terms_path}" target="_blank">os termos de serviço</a>
-    confirm_email: Confirmar e-mail
+    checkbox_agreement_without_rules_html: Concordo com os <a href="%{terms_path}" target="_blank">termos do serviço </a>
     delete_account: Excluir conta
     delete_account_html: Se você deseja excluir a sua conta, você pode <a href="%{path}">prosseguir para cá</a>. Uma confirmação será requisitada.
+    description:
+      prefix_invited_by_user: "@%{name} convidou você para entrar neste servidor do Mastodon!"
+      prefix_sign_up: Cadastre-se no Mastodon hoje!
+      suffix: Com uma conta, você poderá seguir pessoas, postar atualizações, trocar mensagens com usuários de qualquer servidor Mastodon e muito mais!
     didnt_get_confirmation: Não recebeu instruções de confirmação?
     forgot_password: Esqueceu a sua senha?
     invalid_reset_password_token: Token de modificação de senha é inválido ou expirou. Por favor, requisite um novo.
@@ -507,6 +557,9 @@ pt-BR:
     migrate_account: Mudar para uma conta diferente
     migrate_account_html: Se você quer redirecionar essa conta para uma outra você pode <a href="%{path}">configurar isso aqui</a>.
     or_log_in_with: Ou faça login com
+    providers:
+      cas: CAS
+      saml: SAML
     register: Cadastrar-se
     registration_closed: "%{instance} não está aceitando novos membros"
     resend_confirmation: Reenviar instruções de confirmação
@@ -525,8 +578,12 @@ pt-BR:
       return: Exibir o perfil do usuário
       web: Voltar para a página inicial
     title: Seguir %{acct}
+  challenge:
+    invalid_password: Senha inválida
+    prompt: Confirme sua senha para continuar
   datetime:
     distance_in_words:
+      about_x_hours: "%{count}h"
       about_x_months: "%{count} meses"
       about_x_years: "%{count} anos"
       almost_x_years: "%{count} anos"
@@ -539,26 +596,25 @@ pt-BR:
       x_months: "%{count} meses"
       x_seconds: "%{count} segundos"
   deletes:
-    bad_password_msg: Boa tentativa, hackers! Senha incorreta
+    challenge_not_passed: As informações que você inseriu não estão corretas
     confirm_password: Insira a sua senha atual para verificar a sua identidade
-    description_html: Isto vai <strong>permanente e irreversivelmente</strong> remover conteúdo de sua conta e desativá-la. O seu nome de usuário permanecerá reservado para previnir futuros roubos de identidade.
+    confirm_username: Digite seu nome de usuário para confirmar o procedimento
     proceed: Excluir conta
     success_msg: A sua conta foi excluída com sucesso
-    warning_html: Apenas a exclusão de conteúdo desta instância em particular é garantida. Conteúdo que tenha sido largamente compartilhado muito provavelmente deixará traços. Servidores offline e servidores que se desinscreveram de suas atualizações não irão atualizar as suas bases de dados.
-    warning_title: Disponibilidade de conteúdo disseminado
+    warning:
+      before: 'Antes de prosseguir, por favor leia com cuidado:'
+      data_removal: Suas postagens e outros dados serão removidos permanentemente
+      username_available: Seu nome de usuário ficará disponível novamente
+      username_unavailable: Seu nome de usuário permanecerá indisponível
   directories:
     directory: Diretório de perfis
-    enabled: Você está na lista do diretório.
-    enabled_but_waiting: Você escolheu ser listado no diretório, mas você ainda não tem o mínimo de seguidores (%{min_followers}) para ser listado.
     explanation: Descobrir usuários baseado em seus interesses
     explore_mastodon: Explorar %{title}
-    how_to_enable: Você não se inscreveu no diretório. Você pode se inscrever abaixo. Use hashtags no texto da sua bio para ser listado em hashtags específicas!
-    people:
-      one: "%{count} pessoa"
-      other: "%{count} pessoas"
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Você não tem permissão para ver esta página.
     '404': A página pela qual você está procurando não existe.
+    '406': This page is not available in the requested format.
     '410': A página pela qual você está procurando não existe mais.
     '422':
       content: A verificação de segurança falhou. Você desativou o uso de cookies?
@@ -567,6 +623,7 @@ pt-BR:
     '500':
       content: Desculpe, algo deu errado.
       title: Esta página não está certa
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: Para usar o aplicativo web do Mastodon, por favor ative o JavaScript. Ou, se quiser, experimente um dos <a href="%{apps_path}">apps nativos</a> para o Mastodon em sua plataforma.
   existing_username_validator:
     not_found: não foi possível encontrar um usuário local com esse nome de usuário
@@ -580,6 +637,7 @@ pt-BR:
       request: Solicitar o seu arquivo
       size: Tamanho
     blocks: Você bloqueou
+    csv: CSV
     domain_blocks: Bloqueios de domínio
     follows: Você segue
     lists: Listas
@@ -684,9 +742,15 @@ pt-BR:
       too_many: Não é possível anexar mais de 4 imagens
   migrations:
     acct: username@domain da nova conta
-    currently_redirecting: 'Seu perfil está configurado para redirecionar para:'
-    proceed: Salvar
-    updated_msg: As configurações de migração da sua conta foram atualizadas com sucesso!
+    incoming_migrations: Migrando de outra conta
+    moved_msg: Agora sua conta está redirecionando para %{acct} e seus seguidores estão sendo movidos.
+    not_redirecting: Sua conta não está redirecionando para nenhuma outra conta atualmente.
+    on_cooldown: Você migrou recentemente sua conta. Esta função ficará disponível novamente em %{count} dias.
+    past_migrations: Migrações passadas
+    proceed_with_move: Migrar seguidores
+    redirecting_to: Sua conta está redirecionando para %{acct}.
+    warning:
+      before: 'Antes de prosseguir, por favor leia com cuidado:'
   moderation:
     title: Moderação
   notification_mailer:
@@ -770,10 +834,6 @@ pt-BR:
     reply:
       proceed: Proceder para responder
       prompt: 'Você quer responder à esse toot:'
-  remote_unfollow:
-    error: Erro
-    title: Título
-    unfollowed: Deixou de seguir
   scheduled_statuses:
     over_daily_limit: Você excedeu o limite de %{limit} toots planejados para esse dia
     over_total_limit: Você excedeu o limite de %{limit} toots planejados
@@ -782,13 +842,40 @@ pt-BR:
     activity: Última atividade
     browser: Navegador
     browsers:
+      alipay: Alipay
+      blackberry: BlackBerry
+      chrome: Chrome
+      edge: Microsoft Edge
+      electron: Electron
+      firefox: Firefox
       generic: Navegador desconhecido
+      ie: Internet Explorer
+      micro_messenger: MicroMessenger
       nokia: Navegador Nokia S40 Ovi
+      opera: Opera
+      otter: Otter
+      phantom_js: PhantomJS
+      qq: QQ Browser
+      safari: Safari
+      uc_browser: UCBrowser
+      weibo: Weibo
     current_session: Sessão atual
     description: "%{browser} em %{platform}"
     explanation: Estes são os navegadores que estão conectados com a sua conta do Mastodon.
+    ip: IP
     platforms:
+      adobe_air: Adobe Air
+      android: Android
+      blackberry: BlackBerry
+      chrome_os: ChromeOS
+      firefox_os: Firefox OS
+      ios: iOS
+      linux: Linux
+      mac: Mac
       other: Plataforma desconhecida
+      windows: Windows
+      windows_mobile: Windows Mobile
+      windows_phone: Windows Phone
     revoke: Revogar
     revoke_success: Sessão revogada com sucesso
     title: Sessões
@@ -842,6 +929,7 @@ pt-BR:
       vote: Votar
     show_more: Mostrar mais
     sign_in_to_participate: Entre para participar dessa conversa
+    title: '%{name}: "%{quote}"'
     visibilities:
       private: Apenas seguidores
       private_long: Mostrar apenas para seguidores
@@ -853,6 +941,8 @@ pt-BR:
     pinned: Toot fixado
     reblogged: compartilhou
     sensitive_content: Conteúdo sensível
+  tags:
+    does_not_match_previous_name: não corresponde ao nome anterior
   terms:
     body_html: |
       <h2>Política de privacidade</h2>
@@ -969,6 +1059,7 @@ pt-BR:
         disable: Enquanto sua conta está congelada, seus dados estão intactos, mas você não pode realizar nenhuma ação até que sua conta seja desbloqueada.
         silence: Enquanto sua conta está limitada, somente pessoas que já estão seguindo você poderão ver seus toots nesse servidor, e você pode ser excluído de diversas listagens públicas. No entanto, outros ainda podem seguir você manualmente.
         suspend: Sua conta está suspensa e todos os seus toots e mídias foram irreversivelmente removidas desse servidor e de servidores onde você tinha seguidores.
+      get_in_touch: Você pode responder a este e-mail para entrar em contato com a equipe de %{instance}.
       review_server_policies: Revisar as políticas do servidor
       subject:
         disable: Sua conta %{acct} foi congelada
diff --git a/config/locales/pt.yml b/config/locales/pt-PT.yml
similarity index 95%
rename from config/locales/pt.yml
rename to config/locales/pt-PT.yml
index 9cd92f6bd8fc72f69540fc99e3e928c97142cd76..fe23160a2cd980ffd489b981878cf906f0d50adb 100644
--- a/config/locales/pt.yml
+++ b/config/locales/pt-PT.yml
@@ -1,5 +1,5 @@
 ---
-pt:
+pt-PT:
   about:
     about_hashtag_html: Estes são toots públicos marcados com <strong>#%{hashtag}</strong>. Podes interagir com eles se tiveres uma conta Mastodon.
     about_mastodon_html: Mastodon é uma rede social baseada em protocolos abertos da web e software livre e gratuito. É descentralizado como e-mail.
@@ -10,10 +10,6 @@ pt:
     contact_missing: Não configurado
     contact_unavailable: n.d.
     documentation: Documentação
-    extended_description_html: |
-      <h3>Um bom lugar para regras</h3>
-      <p>A descrição estendida ainda não foi configurada.</p>
-    generic_description: "%{domain} é um servidor na rede"
     hosted_on: Mastodon em %{domain}
     learn_more: Saber mais
     privacy_policy: Política de privacidade
@@ -431,18 +427,6 @@ pt:
       no_status_selected: Nenhum estado foi alterado porque nenhum foi selecionado
       title: Estado das contas
       with_media: Com media
-    subscriptions:
-      callback_url: URL de Callback
-      confirmed: Confirmado
-      expires_in: Expira em
-      last_delivery: Última entrega
-      topic: Tópico
-    tags:
-      accounts: Contas
-      hidden: Escondidas
-      hide: Esconder no diretório
-      unhide: Mostrar no diretório
-      visible: Visível
     title: Administração
     warning_presets:
       add_new: Adicionar novo
@@ -471,7 +455,6 @@ pt:
     your_token: O teu token de acesso
   auth:
     change_password: Palavra-passe
-    confirm_email: Confirmar e-mail
     delete_account: Eliminar conta
     delete_account_html: Se desejas eliminar a conta, podes <a href="%{path}">continua aqui</a>. Uma confirmação será pedida.
     didnt_get_confirmation: Não recebeu o email de confirmação?
@@ -512,28 +495,18 @@ pt:
       x_months: "%{count} meses"
       x_seconds: "%{count} segundos"
   deletes:
-    bad_password_msg: Boa tentativa, hackers! Palavra-passe incorreta
     confirm_password: Introduz a palavra-passe atual para verificar a tua identidade
-    description_html: Isto vai <strong>permanente e irreversivelmente</strong> remover conteúdo da tua conta e desativá-la. O teu nome de utilizador permanecerá reservado para prevenir futuros roubos de identidade.
     proceed: Eliminar conta
     success_msg: A tua conta foi eliminada com sucesso
-    warning_html: |-
-      Apenas a eliminação de conteúdo desta instância é garantido.
-      Conteúdo que tenha sido partilhado com outras instâncias muito provavelmente deixará pegadas. Servidores offline e servidores que se desinscreveram das tuas atualizações não  atualizarão as suas bases de dados.
-    warning_title: Disponibilidade de conteúdo disseminado
   directories:
     directory: Dirétorio de perfil
-    enabled: Neste momento tu estás listado no dirétorio.
-    enabled_but_waiting: Tu escolheste ser listado no diretório, mas ainda não tens o número mínimo de seguidores (%{min_followers}) para integrares esta lista.
     explanation: Descobre utilizadores com base nos seus interesses
     explore_mastodon: Explorar %{title}
-    how_to_enable: Tu ainda não integras este directório. Podes fazer isso abaixo. Usa hashtags na tua biografia para seres listado em hashtags específicas!
-    people:
-      one: "%{count} pessoa"
-      other: "%{count} pessoas"
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Não tens a permissão necessária para ver esta página.
     '404': A página que estás a procurar não existe.
+    '406': This page is not available in the requested format.
     '410': A página que estás a procurar não existe mais.
     '422':
       content: "A verificação de segurança falhou. \nDesativaste o uso de cookies?"
@@ -542,6 +515,7 @@ pt:
     '500':
       content: Desculpe, mas algo correu mal.
       title: Esta página não está correta
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: Para usar o aplicativo web do Mastodon, por favor ativa o JavaScript. Alternativamente, experimenta um dos <a href="%{apps_path}">apps nativos</a> para o Mastodon na sua plataforma.
   exports:
     archive_takeout:
@@ -634,9 +608,6 @@ pt:
       too_many: Não é possível anexar mais de 4 arquivos
   migrations:
     acct: username@domain da nova conta
-    currently_redirecting: 'O teu perfil está configurado para redirecionar para:'
-    proceed: Salvar
-    updated_msg: As configurações de migração da tua conta foram atualizadas com sucesso!
   moderation:
     title: Moderação
   notification_mailer:
@@ -707,10 +678,6 @@ pt:
     reply:
       proceed: Prosseguir com resposta
       prompt: 'Queres responder a esta publicação:'
-  remote_unfollow:
-    error: Erro
-    title: Título
-    unfollowed: Não seguido
   scheduled_statuses:
     over_daily_limit: Excedeste o limite de %{limit} publicações agendadas para esse dia
     over_total_limit: Tu excedeste o limite de %{limit} publicações agendadas
diff --git a/config/locales/ro.yml b/config/locales/ro.yml
index 6e6c6f403ec985c1a3485018f82564adb683d6b8..7deab602139c2b16289ad21c472521fb29d158d9 100644
--- a/config/locales/ro.yml
+++ b/config/locales/ro.yml
@@ -4,7 +4,6 @@ ro:
     hosted_on: Mastodon găzduit de %{domain}
   auth:
     change_password: Parolă
-    confirm_email: Confirmă email
     delete_account: Șterge contul
     delete_account_html: Dacă vrei să ștergi acest cont <a href="%{path}">poți începe aici</a>. Va trebui să confirmi această acțiune.
     didnt_get_confirmation: Nu ai primit instrucțiunile de confirmare?
@@ -44,22 +43,17 @@ ro:
       x_days: "%{count}z"
       x_months: "%{count}l"
   deletes:
-    bad_password_msg: Bună încercare, hackere! Parolă incorectă
     confirm_password: Introdu parola curentă pentru a-ți verifica identitatea
-    description_html: Această acțiune este <strong>permanentă și ireversibilă,</strong> elimină conținutul și dezactivează contul tău. Acest username va rămâne permanent rezervat pentru a evita furtul de identitate.
     proceed: Șterge contul
     success_msg: Contul tău a fost șterg. Nu mai poate fi recuperat :D
-    warning_html: Doar ștergerea conținutului de pe acest server este garantată. Conținutul tău care a fost redistribuit în alte instațe e posibil să lase urme. Serverele deconecate sau care nu mai sunt abonate la actualizările contului tău nu își vor mai actualiza baza de date.
   directories:
     explanation: Descoperă oameni și companii în funcție de interesele lor
     explore_mastodon: Explorează %{title}
-    people:
-      few: "%{count} persoană"
-      one: "%{count} persoană"
-      other: "%{count} oameni"
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Nu ai permisiunea să vizitezi această pagină.
     '404': Pagina pe care o cauți nu există.
+    '406': This page is not available in the requested format.
     '410': Pagina pe care o cauți nu mai există.
     '422':
       content: Verificarea securității a eșuat. Ai blocat cookiurile?
@@ -68,6 +62,7 @@ ro:
     '500':
       content: Ne pare rău, dar ceva a funcționat greșit. Încercați din nou!?
       title: Această pagină nu este corectă
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: Pentru a utiliza o aplicație web Mastodon, te rog activează JavaScript. Alternativ, încearcă una din <a href="%{apps_path}">aplicațiile native</a> Mastodon pentru platforma ta.
   exports:
     archive_takeout:
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 7e336be984e7590919fc80a3c2f9fde8f61b807c..df4a040488c7556c993113fae9d5fc24b535e91f 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -17,11 +17,7 @@ ru:
     contact_unavailable: неизв.
     discover_users: Находите пользователей
     documentation: Документация
-    extended_description_html: |
-      <h3>Хорошее место для правил</h3>
-      <p>Расширенное описание еще не настроено.</p>
     federation_hint_html: С учётной записью на %{instance} вы сможете подписываться на людей с любого сервера Mastodon и не только.
-    generic_description: "%{domain} - один из серверов сети"
     get_apps: Попробуйте мобильное приложение
     hosted_on: Mastodon размещен на %{domain}
     learn_more: Узнать больше
@@ -37,6 +33,11 @@ ru:
     status_count_before: Опубликовано
     tagline: Подписывайтесь на друзей и заводите новые знакомства
     terms: Условия использования
+    unavailable_content: Содержимое недоступно
+    unavailable_content_description:
+      domain: Сервер
+      reason: Причина
+      rejecting_media: 'Медиафайлы с этих серверов не будут обработаны или сохранены. Их миниатюры не будут отображаться и вам придётся вручную нажимать на исходный файл:'
     user_count_after:
       few: пользователя
       many: пользователей
@@ -59,6 +60,7 @@ ru:
     media: Медиа
     moved_html: "%{name} переехал(а) на %{new_profile_link}:"
     network_hidden: Эта информация недоступна
+    never_active: Никогда
     nothing_here: Здесь ничего нет!
     people_followed_by: Люди, на которых подписан(а) %{name}
     people_who_follow: Подписчики %{name}
@@ -119,7 +121,7 @@ ru:
       followers: Подписчики
       followers_url: URL подписчиков
       follows: Подписки
-      header: Заголовок
+      header: Шапка
       inbox_url: URL входящих
       invited_by: Приглашение выдал(а)
       ip: IP
@@ -171,6 +173,7 @@ ru:
         moderator: Модератор
         staff: Персонал
         user: Пользователь
+      salmon_url: Страница Salmon
       search: Поиск
       shared_inbox_url: URL общих входящих
       show:
@@ -181,6 +184,7 @@ ru:
       statuses: Статусы
       subscribe: Подписаться
       suspended: Заморожен
+      time_in_queue: Ожидание в очереди %{time}
       title: Аккаунты
       unconfirmed_email: Неподтверждённый e-mail
       undo_silenced: Снять глушение
@@ -189,6 +193,7 @@ ru:
       username: Имя пользователя
       warn: Предупредить
       web: Веб
+      whitelisted: В белом списке
     action_logs:
       actions:
         assigned_to_self_report: "%{name} назначил(а) жалобу %{target} на себя"
@@ -224,19 +229,24 @@ ru:
       deleted_status: "(удалённый статус)"
       title: Журнал событий
     custom_emojis:
+      assign_category: Задать категорию
       by_domain: Домен
       copied_msg: Локальная копия эмодзи успешно создана
       copy: Копировать
       copy_failed_msg: Не удалось создать локальную копию эмодзи
+      create_new_category: Создать новую категорию
       created_msg: Эмодзи успешно создано!
       delete: Удалить
       destroyed_msg: Эмодзи успешно удалено!
       disable: Отключить
+      disabled: Отключено
       disabled_msg: Эмодзи успешно отключено
       emoji: Эмодзи
       enable: Включить
+      enabled: Включено
       enabled_msg: Эмодзи успешно включено
       image_hint: PNG до 50KB
+      list: Список
       listed: В списке
       new:
         title: Добавить новый эмодзи
@@ -244,11 +254,13 @@ ru:
       shortcode: Шорткод
       shortcode_hint: Как минимум 2 символа, только алфавитно-цифровые символы и подчеркивания
       title: Собственные эмодзи
+      uncategorized: Вне категорий
       unlisted: Не в списке
       update_failed_msg: Невозможно обновить этот эмодзи
       updated_msg: Эмодзи обновлён!
       upload: Загрузить
     dashboard:
+      authorized_fetch_mode: Защищённый режим
       backlog: задачи
       config: Конфигурация
       feature_deletions: Удаление аккаунтов
@@ -256,10 +268,13 @@ ru:
       feature_profile_directory: Каталог профилей
       feature_registrations: Регистрация
       feature_relay: Ретрансляторы
+      feature_spam_check: Анти-спам
       feature_timeline_preview: Предпросмотр ленты
       features: Возможности
       hidden_service: Федерация со скрытыми сервисами
       open_reports: открытых жалоб
+      pending_tags: хэштеги, ожидающие проверки
+      pending_users: пользователи, ожидающие проверки
       recent_users: Недавние пользователи
       search: Полнотекстовый поиск
       single_user_mode: Однопользовательский режим
@@ -267,15 +282,22 @@ ru:
       space: Использовано места
       title: Панель управления
       total_users: всего пользователей
-      trends: Тренды
+      trends: Актуальное
       week_interactions: взаимодействий на этой неделе
       week_users_active: активно на этой неделе
       week_users_new: пользователей на этой неделе
+      whitelist_mode: Белый список
+    domain_allows:
+      add_new: Внести в белый список
+      created_msg: Домен добавлен в белый список
+      destroyed_msg: Домен убран из белого списка
+      undo: Убрать из белого списка
     domain_blocks:
       add_new: Заблокировать домен
       created_msg: Блокировка домена обрабатывается
       destroyed_msg: Блокировка домена снята
       domain: Домен
+      edit: Редактировать блокировку
       new:
         create: Создать блокировку
         hint: Блокировка домена не предотвратит создание новых аккаунтов в базе данных, но ретроактивно и автоматически применит указанные методы модерации для этих аккаунтов.
@@ -285,6 +307,8 @@ ru:
           silence: Глушение
           suspend: Блокировка
         title: Новая доменная блокировка
+      private_comment: Приватный комментарий
+      public_comment: Публичный комментарий
       reject_media: Запретить медиаконтент
       reject_media_hint: Удаляет локально хранимый медиаконтент и запрещает его загрузку в будущем. Не имеет значения в случае блокировки.
       reject_reports: Отклонять жалобы
@@ -331,9 +355,11 @@ ru:
         all: Все
         limited: Ограниченные
         title: Модерация
+      private_comment: Приватный комментарий
+      public_comment: Публичный комментарий
       title: Известные узлы
       total_blocked_by_us: Заблокировано нами
-      total_followed_by_them: Заблокировано ими
+      total_followed_by_them: Их подписчиков
       total_followed_by_us: Наших подписчиков
       total_reported: Жалобы на них
       total_storage: Медиафайлы
@@ -408,6 +434,13 @@ ru:
       custom_css:
         desc_html: Измените внешний вид с CSS, загружаемым на каждой странице
         title: Особый CSS
+      default_noindex:
+        title: Исключить пользователей из индексации поисковиками по умолчанию
+      domain_blocks:
+        all: Всем
+        disabled: Никому
+        title: Доменные блокировки
+        users: Залогиненным локальным пользователям
       hero:
         desc_html: Отображается на главной странице. Рекомендуется разрешение не менее 600х100px. Если не установлено, используется изображение узла
         title: Баннер узла
@@ -458,6 +491,8 @@ ru:
         desc_html: Вы можете добавить сюда собственную политику конфиденциальности, пользовательское соглашение и другие документы. Можно использовать теги HTML
         title: Условия использования
       site_title: Название сайта
+      spam_check_enabled:
+        title: Анти-спам
       thumbnail:
         desc_html: Используется для предпросмотра с помощью OpenGraph и API. Рекомендуется разрешение 1200x630px
         title: Картинка узла
@@ -465,12 +500,15 @@ ru:
         desc_html: Показывать публичную ленту на приветственной странице
         title: Предпросмотр ленты
       title: Настройки сайта
+      trends:
+        title: Популярные хэштеги
     statuses:
       back_to_account: Назад к странице аккаунта
       batch:
         delete: Удалить
         nsfw_off: Выключить NSFW
         nsfw_on: Включить NSFW
+      deleted: Удалено
       failed_to_execute: Не удалось выполнить
       media:
         title: Медиаконтент
@@ -478,21 +516,13 @@ ru:
       no_status_selected: Не выбран ни один статус, ничего не изменено
       title: Статусы аккаунта
       with_media: С медиаконтентом
-    subscriptions:
-      callback_url: Callback URL
-      confirmed: Подтверждено
-      expires_in: Истекает через
-      last_delivery: Последняя доставка
-      title: WebSub
-      topic: Тема
     tags:
-      accounts: Аккаунты
-      hidden: Скрыты
-      hide: Скрыть из каталога
-      name: Хэштег
+      accounts_today: Уникальных использований за сегодня
+      accounts_week: Уникальных использований за эту неделю
+      context: Контекст
+      directory: В каталоге
+      in_directory: "%{count} в каталоге"
       title: Хэштеги
-      unhide: Показывать в каталоге
-      visible: Видны
     title: Администрирование
     warning_presets:
       add_new: Добавить
@@ -510,6 +540,8 @@ ru:
       subject: Новая жалоба, узел %{instance} (#%{id})
   appearance:
     advanced_web_interface: Многоколоночный интерфейс
+    confirmation_dialogs: Окна подтверждений
+    discovery: Обзор
     sensitive_content: Чувствительное содержимое
   application_mailer:
     notification_preferences: Изменить настройки e-mail
@@ -530,7 +562,6 @@ ru:
     apply_for_account: Запросить приглашение
     change_password: Пароль
     checkbox_agreement_html: Я соглашаюсь с <a href="%{rules_path}" target="_blank">правилами сервера</a> и <a href="%{terms_path}" target="_blank">Условиями использования</a>
-    confirm_email: Подтвердите email
     delete_account: Удалить аккаунт
     delete_account_html: Если Вы хотите удалить свой аккаунт, вы можете <a href="%{path}">перейти сюда</a>. У Вас будет запрошено подтверждение.
     didnt_get_confirmation: Не получили инструкцию для подтверждения?
@@ -539,7 +570,7 @@ ru:
     login: Войти
     logout: Выйти
     migrate_account: Перенести аккаунт
-    migrate_account_html: Если Вы хотите перенести этот аккаунт на другой, вы можете <a href="%{path}">сделать это здесь</a>.
+    migrate_account_html: Если вы хотите перенаправить подписчиков на другой аккаунт, это можно <a href="%{path}">настроить здесь</a>.
     or_log_in_with: Или войти с помощью
     providers:
       cas: CAS
@@ -550,6 +581,9 @@ ru:
     reset_password: Сбросить пароль
     security: Безопасность
     set_new_password: Задать новый пароль
+    status:
+      functional: Ваш аккаунт в полном порядке.
+      redirecting_to: Ваш аккаунт признан неактивным, потому что он перенаправляет на %{acct}.
     trouble_logging_in: Не удаётся войти?
   authorize_follow:
     already_following: Вы уже подписаны на этот аккаунт
@@ -562,6 +596,9 @@ ru:
       return: Вернуться к профилю пользователя
       web: Перейти к WWW
     title: Подписаться на %{acct}
+  challenge:
+    invalid_password: Неверный пароль
+    prompt: Введите пароль для продолжения
   datetime:
     distance_in_words:
       about_x_hours: "%{count}ч"
@@ -577,28 +614,30 @@ ru:
       x_months: "%{count}мес"
       x_seconds: "%{count}сек"
   deletes:
-    bad_password_msg: Не вышло, хакеры! Неверный пароль
+    challenge_not_passed: Введённая вами информация некорректна
     confirm_password: Введите текущий пароль для подтверждения Вашей личности
-    description_html: Это действие <strong>перманентно и необратимо</strong> удалит контент Вашего аккаунта и деактивирует его. Ваше имя пользователя будет зарезервировано для предотвращения имперсонации в будущем.
+    confirm_username: Введите свой юзернейм для подтверждения
     proceed: Удалить аккаунт
     success_msg: Ваш аккаунт был успешно удален
-    warning_html: Гарантируется удаление контента только на этом узле. Широко распространившийся контент, скорее всего, оставит следы. Сервера, отключенные от сети или отписавшиеся от Ваших обновлений, не обновят свои базы данных.
-    warning_title: О доступности распространившегося контента
+    warning:
+      before: 'Внимательно прочитайте следующую информацию перед началом:'
+      caches: Содержимое, которое было закэшировано другими серверами, может сохраниться
+      data_removal: Ваши записи и прочие данные будут безвозвратно удалены
+      email_change_html: <a href="%{path}">Поменять свой e-mail</a> можно не удаляя аккаунт
+      irreversible: Вы не сможете восстановить или повторно активировать свой аккаунт
+      username_available: Ваш юзернейм снова станет доступным
+      username_unavailable: Ваш юзернейм останется недоступным
   directories:
     directory: Каталог профилей
-    enabled: В настоящий момент вы указаны в каталоге.
-    enabled_but_waiting: Вы согласились находиться в каталоге, но у вас ещё нет необходимого количества подписчиков (%{min_followers}), чтобы оказаться в каталоге.
     explanation: Находите пользователей по интересам
     explore_mastodon: Изучайте %{title}
-    how_to_enable: Вы ещё не находитесь в каталоге. Можете добавиться ниже. Используйте хэштеги в разделе "о себе", чтобы вас находили по этим хэштегам!
-    people:
-      few: "%{count} человека"
-      many: "%{count} человек"
-      one: "%{count} человек"
-      other: "%{count} человек"
+  domain_validator:
+    invalid_domain: не является корректным доменным именем
   errors:
+    '400': Ваш запрос был недействительным или неправильным.
     '403': У Вас нет доступа к просмотру этой страницы.
     '404': Страница, которую Вы искали, не существует.
+    '406': This page is not available in the requested format.
     '410': Страница, которую Вы искали, больше не существует.
     '422':
       content: Проверка безопасности не удалась. Возможно, Вы блокируете cookies?
@@ -607,6 +646,7 @@ ru:
     '500':
       content: Приносим извинения, но на нашей стороне что-то пошло не так.
       title: Страница неверна
+    '503': Страница не может быть отображена из-за временного сбоя на сервере.
     noscript_html: Для работы с Mastodon, пожалуйста, включите JavaScript. Кроме того, вы можете использовать одно из <a href="%{apps_path}">приложений</a> Mastodon для Вашей платформы.
   existing_username_validator:
     not_found: не удалось найти локального пользователя с таким именем
@@ -650,6 +690,7 @@ ru:
     developers: Разработчикам
     more: Ещё…
     resources: Ссылки
+    trending_now: Актуально сейчас
   generic:
     all: Все
     changes_saved_msg: Изменения успешно сохранены!
@@ -714,8 +755,8 @@ ru:
       many: "%{count} исп."
       one: 1 исп
       other: "%{count} исп"
-    max_uses_prompt: Без лимита
-    prompt: Генерируйте и делитесь ссылками с другими, чтобы предоставить им доступ к этому узлу
+    max_uses_prompt: Без ограничения
+    prompt: Создавайте и делитесь ссылками с другими, чтобы предоставить им доступом к этому узлу
     table:
       expires_at: Истекает
       uses: Исп.
@@ -729,9 +770,27 @@ ru:
       too_many: Нельзя добавить более 4 файлов
   migrations:
     acct: имя@домен нового аккаунта
-    currently_redirecting: 'Ваш профиль будет перенаправлен на:'
-    proceed: Сохранить
-    updated_msg: Настройки миграции вашего аккаунта обновлены!
+    cancel: Отменить переезд
+    cancel_explanation: Отмена перенаправления повторно активирует аккаунт, но не вернёт обратно подписчиков, которые были перемещены на тот аккаунт.
+    cancelled_msg: Переезд был успешно отменён.
+    errors:
+      on_cooldown: Вы пока не можете переезжать
+    followers_count: Подписчиков на момент переезда
+    incoming_migrations: Переезд с другого аккаунта
+    moved_msg: Ваш аккаунт теперь перенаправляет на %{acct} и подписчики перемещаются туда.
+    not_redirecting: Ваш аккаунт пока что не перенаправляет на любой другой аккаунт.
+    on_cooldown: Вы уже недавно переносили свой аккаунт. Эта возможность будет снова доступна через %{count} дн.
+    past_migrations: Прошлые переезды
+    proceed_with_move: Перенести подписчиков
+    redirecting_to: Ваша учётная запись перенаправлена на %{acct}.
+    set_redirect: Настроить перенаправление
+    warning:
+      before: 'Прежде чем продолжить, внимательно прочитайте следующую информацию:'
+      cooldown: После переезда наступает период, в течение которого вы не сможете ещё раз переехать
+      disabled_account: Ваш текущий аккаунт впоследствии нельзя будет больше использовать. При этом, у вас будет доступ к экспорту данных, а также к повторной активации аккаунта.
+      followers: Это действие перенесёт всех ваших подписчиков с текущего аккаунта на новый
+      other_data: Никакие другие данные не будут автоматически перенесены
+      redirect: Профиль этого аккаунта будет обновлён с заметкой о перенаправлении, а также исключён из поиска
   moderation:
     title: Модерация
   notification_mailer:
@@ -800,6 +859,7 @@ ru:
       too_many_options: может содержать не больше %{max} вариантов
   preferences:
     other: Другое
+    posting_defaults: Настройки отправки по умолчанию
     public_timelines: Публичные ленты
   relationships:
     activity: Активность аккаунта
@@ -831,10 +891,6 @@ ru:
     reply:
       proceed: Ответить
       prompt: 'Вы собираетесь ответить на этот статус:'
-  remote_unfollow:
-    error: Ошибка
-    title: Заголовок
-    unfollowed: Отписаны
   scheduled_statuses:
     over_daily_limit: Вы превысили лимит в %{limit} запланированных постов на указанный день
     over_total_limit: Вы превысили лимит на %{limit} запланированных постов
@@ -883,6 +939,7 @@ ru:
   settings:
     account: Учётная запись
     account_settings: Настройки учётной записи
+    aliases: Псевдонимы аккаунта
     appearance: Внешний вид
     authorized_apps: Авторизованные приложения
     back: Назад в Mastodon
@@ -1063,6 +1120,7 @@ ru:
         silence: Пока ваш аккаунт ограничен, ваши посты на этом сервере увидят только ваши действующие подписчики, а ваш аккаунт может быть исключён из различных каталогов. Впрочем, остальные могут подписаться на вас вручную.
         suspend: Ваш аккаунт заблокирован и все ваши посты и загруженные медиафайлы безвозвратно удалены с этого сервера и других серверов, где у вас были подписчики.
       review_server_policies: Посмотреть правила сервера
+      statuses: 'В частности, для:'
       subject:
         disable: Ваш аккаунт %{acct} заморожен
         none: "%{acct}, вам вынесено предупреждение"
@@ -1075,7 +1133,7 @@ ru:
         suspend: Аккаунт заблокирован
     welcome:
       edit_profile_action: Настроить профиль
-      edit_profile_step: Вы можете настроить свой профиль, загрузив аватар, обложку, сменив имя и много чего ещё. Если вы хотите фильтровать подписчиков до того, как они смогут на вас подписаться, вы можете закрыть свой аккаунт.
+      edit_profile_step: Настройте свой профиль, загрузив аватарку, шапку, изменив отображаемое имя и ещё много чего. Если вы хотите вручную рассматривать и подтверждать подписчиков, можно закрыть свой аккаунт.
       explanation: Несколько советов для новичков
       final_action: Начать постить
       final_step: 'Начните постить! Ваши публичные посты могут видеть другие, например, в локальной ленте или по хэштегам, даже если у вас нет подписчиков. Вы также можете поздороваться с остальными и представиться, используя хэштег #приветствие.'
diff --git a/config/locales/simple_form.ar.yml b/config/locales/simple_form.ar.yml
index b948a5c5067037e5643af1ccd615f132a7398bae..d1b85d921a1f81e9ce039f79ecc337e002858a22 100644
--- a/config/locales/simple_form.ar.yml
+++ b/config/locales/simple_form.ar.yml
@@ -14,8 +14,9 @@ ar:
         bot: يُعلِم أنّ هذا الحساب لا يمثل شخصًا
         context: واحد أو أكثر من السياقات التي يجب أن ينطبق عليها عامل التصفية
         digest: تُرسَل إليك بعد مُضيّ مدة مِن خمول نشاطك و فقط إذا ما تلقيت رسائل شخصية مباشِرة أثناء فترة غيابك مِن الشبكة
+        discoverable: سِجل الملفات التعريفية للمستخدمين هو طريقة أخرى لبلوغ جمهور أوسع
         email: سوف تتلقى رسالة إلكترونية للتأكيد
-        fields: يُمكنك عرض 4 عناصر على شكل جدول في ملفك الشخصي
+        fields: يُمكنك عرض 4 عناصر على شكل جدول في صفحتك التعريفية
         header: ملف PNG أو GIF أو JPG. حجمه على أقصى تصدير %{size}. سيتم تصغيره إلى %{dimensions}px
         inbox_url: نسخ العنوان الذي تريد استخدامه مِن صفحة الاستقبال للمُرحَّل
         irreversible: التبويقات التي تم تصفيتها ستختفي لا محالة حتى و إن تمت إزالة عامِل التصفية لاحقًا
@@ -25,14 +26,19 @@ ar:
         phrase: سوف يتم العثور عليه مهما كان نوع النص أو حتى و إن كان داخل الويب فيه تحذير عن المحتوى
         scopes: ما هي المجالات المسموح بها في التطبيق ؟ إن قمت باختيار أعلى المجالات فيمكنك الاستغناء عن الخَيار اليدوي.
         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_hide_network: الحسابات التي تُتابعها و التي تُتابِعك على حد سواء لن تُعرَض على صفحتك التعريفية
+        setting_noindex: ذلك يؤثر على صفحتك التعريفية وصفحات المنشورات
+        setting_show_application: يتم عرض اسم التطبيق الذي تستخدمه عند التبويق في العرض المفصّل لتبوبيقاتك
+        setting_use_pending_items: إخفاء تحديثات الخط وراء نقرة بدلًا مِن التمرير التلقائي للتدفق
         username: اسم المستخدم الخاص بك سوف يكون فريدا مِن نوعه على %{domain}
       featured_tag:
-        name: 'رُبَّما تريد/ي استخدام أحد هؤلاء:'
+        name: 'رُبَّما تريد·ين استخدام واحد مِن هذه:'
+      form_challenge:
+        current_password: إنك بصدد الدخول إلى منطقة آمنة
       imports:
         data: ملف CSV تم تصديره مِن خادوم ماستدون آخر
       invite_request:
@@ -46,6 +52,10 @@ ar:
         fields:
           name: التسمية
           value: المحتوى
+      account_alias:
+        acct: مُعرّف الحساب القديم
+      account_migration:
+        acct: مُعرّف الحساب الجديد
       account_warning_preset:
         text: نموذج نصي
       admin_account_action:
@@ -72,7 +82,7 @@ ar:
         display_name: الاسم المعروض
         email: عنوان البريد الإلكتروني
         expires_in: تنتهي مدة صلاحيته بعد
-        fields: البيانات الوصفية للصفحة الشخصية
+        fields: البيانات الوصفية للصفحة التعريفية
         header: الرأسية
         inbox_url: عنوان رابط صندوق المُرَحِّل
         irreversible: إسقاط بدلا من إخفائها
@@ -103,7 +113,9 @@ ar:
         setting_show_application: اكشف اسم التطبيقات المستخدمة لنشر التبويقات
         setting_system_font_ui: استخدم الخطوط الافتراضية للنظام
         setting_theme: سمة الموقع
+        setting_trends: اعرض ما يُتداوَل اليوم
         setting_unfollow_modal: إظهار مربع حوار للتأكيد قبل إلغاء متابعة أي حساب
+        setting_use_pending_items: الوضع البطيء
         severity: القوّة
         type: صيغة الاستيراد
         username: اسم المستخدم
@@ -115,6 +127,8 @@ ar:
         must_be_follower: حظر الإخطارات القادمة من حسابات لا تتبعك
         must_be_following: حظر الإخطارات القادمة من الحسابات التي لا تتابعها
         must_be_following_dm: حظر الرسائل المباشرة القادمة من طرف أشخاص لا تتبعهم
+      invite:
+        comment: التعليق
       invite_request:
         text: لماذا ترغب في الانضمام؟
       notification_emails:
@@ -123,8 +137,15 @@ ar:
         follow: ابعث بريداً إلكترونيًا عندما يتبعك أحد
         follow_request: ابعث بريدا إلكترونيا عندما يقوم أحدهم بإرسال طلب بالمتابعة
         mention: ابعث بريداً إلكترونيًا عندما يُشير إليك أو يذكُرك أحدهم
+        pending_account: ابعث رسالة إلكترونية إن كان هناك حساب جديد بحاجة إلى مراجعة
         reblog: ابعث بريداً إلكترونيًا عندما يقوم أحدهم بترقية منشورك
         report: إرسال رسالة إلكترونية عند تلقّي إبلاغ جديد
+        trending_tag: ابعث رسالة إلكترونية إن كان هناك وسم متداوَل بحاجة إلى مراجعة
+      tag:
+        listable: اسمح لهذا الوسم بالظهور في البحث وفي دليل الصفحات التعريفية
+        name: الوسم
+        trendable: السماح لهذه الكلمة المفتاحية بالظهور تحت المتداوَلة
+        usable: اسمح للتبويقات باستخدام هذا الوسم
     'no': لا
     recommended: موصى بها
     required:
diff --git a/config/locales/simple_form.br.yml b/config/locales/simple_form.br.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c7677c850c634b75b037ffd563bb1e1a6e36a5cf
--- /dev/null
+++ b/config/locales/simple_form.br.yml
@@ -0,0 +1 @@
+br:
diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml
index d8713e4caee9fceaad504a0bbe5b895c6a563732..517c7e3b0a74db8cdea9042510e06e924ed2d074 100644
--- a/config/locales/simple_form.ca.yml
+++ b/config/locales/simple_form.ca.yml
@@ -2,9 +2,14 @@
 ca:
   simple_form:
     hints:
+      account_alias:
+        acct: Especifica l'usuari@domini del compte des d'on et vols moure
+      account_migration:
+        acct: Especifica l'usuari@domini del compte al que et vols moure
       account_warning_preset:
         text: Pots utilitzar totes les sintaxi com ara URL, etiquetes i mencions
       admin_account_action:
+        include_statuses: L'usuari veurà quin tuts ha causat l'acció de moderació o avís
         send_email_notification: L'usuari rebrà una explicació del que ha passat amb el seu compte
         text_html: Opcional. Pots utilitzar tota la sintaxi. Pots <a href="%{path}">afegir configuracions predefinides d'avís</a> per a estalviar temps
         type_html: Tria què fer amb <strong>%{acct}</strong>
@@ -14,8 +19,10 @@ ca:
         avatar: PNG, GIF o JPG. Màxim %{size}. S'escalarà a %{dimensions}px
         bot: Aquest compte realitza principalment accions automatitzades i pot no estar controlat per cap persona
         context: Un o diversos contextos on s'ha d'aplicar el filtre
+        current_password: Per motius de seguretat si us plau entra la contrasenya del compte actual
+        current_username: Per a confirmar, si us plau entra el nom d'usuari del compte actual
         digest: Només s'envia després d'un llarg període d'inactivitat amb un resum de les mencions que has rebut en la teva absència
-        discoverable_html: El <a href="%{path}" target="_blank">directori</a> permet trobar usuaris en funció dels interessos i activitat. Requereix almenys %{min_followers} seguidors
+        discoverable: El directori de perfils és una altra manera per quin el teu compte pot assolir una audiència més àmplia
         email: Se t'enviarà un correu electrònic de confirmació
         fields: Pots tenir fins a 4 elements que es mostren com a taula al teu perfil
         header: PNG, GIF o JPG. Màxim %{size}. S'escalarà a %{dimensions}px
@@ -34,16 +41,24 @@ ca:
         setting_hide_network: Qui tu segueixes i els que et segueixen a tu no es mostraran en el teu perfil
         setting_noindex: Afecta el teu perfil públic i les pàgines d'estat
         setting_show_application: L'aplicació que fas servir per a publicar es mostrarà a la vista detallada dels teus toots
+        setting_use_blurhash: Els degradats es basen en els colors de les imatges ocultes, però enfosqueixen els detalls
+        setting_use_pending_items: Amaga les actualitzacions de la línia de temps després d'un clic en comptes de desplaçar-se automàticament
         username: El teu nom d'usuari serà únic a %{domain}
         whole_word: Quan la paraula clau o la frase sigui només alfanumèrica, s'aplicarà si coincideix amb la paraula sencera
+      domain_allow:
+        domain: Aquest domini podrà obtenir dades d’aquest servidor i les dades entrants d’aquests seran processades i emmagatzemades
       featured_tag:
         name: 'És possible que vulguis utilitzar un d''aquests:'
+      form_challenge:
+        current_password: Estàs entrant en una àrea segura
       imports:
         data: Fitxer CSV exportat des d'un altre servidor de Mastodon
       invite_request:
         text: Això ens ajudarà a revisar la teva petició
       sessions:
         otp: 'Introdueix el codi de dos factors generat per el teu telèfon o utilitza un dels teus codis de recuperació:'
+      tag:
+        name: Només pots canviar la caixa de les lletres, per exemple, per fer-la més llegible
       user:
         chosen_languages: Quan estigui marcat, només es mostraran els toots de les llengües seleccionades en les línies de temps públiques
     labels:
@@ -51,9 +66,14 @@ ca:
         fields:
           name: Etiqueta
           value: Contingut
+      account_alias:
+        acct: Nom del compte vell
+      account_migration:
+        acct: Nom del nou compte
       account_warning_preset:
         text: Text predefinit
       admin_account_action:
+        include_statuses: Inclou tuts reportats en el correu electrònic
         send_email_notification: Notifica l'usuari per correu electrònic
         text: Avís personalitzat
         type: Acció
@@ -108,7 +128,10 @@ ca:
         setting_show_application: Desvela l'aplicació utilitzada per enviar toots
         setting_system_font_ui: Utilitza el tipus de lletra predeterminat del sistema
         setting_theme: Tema del lloc
+        setting_trends: Mostra les tendències d'avui
         setting_unfollow_modal: Mostra el diàleg de confirmació abans de deixar de seguir a algú
+        setting_use_blurhash: Mostra degradats de colors per als Mèdia amagats
+        setting_use_pending_items: Mode lent
         severity: Severitat
         type: Importa el tipus
         username: Nom d'usuari
@@ -120,6 +143,8 @@ ca:
         must_be_follower: Blocar les notificacions de persones que no et segueixen
         must_be_following: Bloca les notificacions de persones que no segueixes
         must_be_following_dm: Bloca els missatges directes de persones que no segueixes
+      invite:
+        comment: Comenta
       invite_request:
         text: Per què vols unir-te?
       notification_emails:
@@ -131,6 +156,12 @@ ca:
         pending_account: Envia un correu electrònic quan es necessiti revisar un compte nou
         reblog: Envia un correu electrònic si algú comparteix el teu estat
         report: Envia un correu electrònic quan s'enviï un nou informe
+        trending_tag: Envia un correu quan una etiqueta sense revisar està en tendència
+      tag:
+        listable: Permet que aquesta etiqueta aparegui en les cerques i en el directori de perfils
+        name: Etiqueta
+        trendable: Permet que aquesta etiqueta aparegui en les tendències
+        usable: Permet als tuts emprar aquesta etiqueta
     'no': 'No'
     recommended: Recomanat
     required:
diff --git a/config/locales/simple_form.co.yml b/config/locales/simple_form.co.yml
index 1f5dba43fb5f55316a8d5e0e2d514cafb9a8213a..245bcea08a32b8141b3c428272814e3e47440e81 100644
--- a/config/locales/simple_form.co.yml
+++ b/config/locales/simple_form.co.yml
@@ -2,9 +2,14 @@
 co:
   simple_form:
     hints:
+      account_alias:
+        acct: Entrate u cugnome@duminiu di u contu attuale
+      account_migration:
+        acct: Entrate u cugnome@duminiu di u contu induve vulete traslucà
       account_warning_preset:
         text: Pudete utilizà a sintassa di i statuti, per esempiu l'URL, hashtag, minzione
       admin_account_action:
+        include_statuses: L'utilizatore viderà i statuti rispunsevuli di l'azzione o l'avertimentu di muderazione
         send_email_notification: L'utilizatore hà da riceve una spiegazione di cio chì hè accadutu à u so contu
         text_html: In uzzione. Pudete utilizà a sintassa di i statuti. Pudete ancu <a href="%{path}">aghjustà preselezzione d'avertimentu</a> per piglià tempu
         type_html: Sceglie chì fà cù <strong>%{acct}</strong>
@@ -14,8 +19,10 @@ co:
         avatar: Furmatu PNG, GIF o JPG. %{size} o menu. Sarà ridottu à %{dimensions}px
         bot: Stu contu hè autumatizatu è ùn hè forse micca survegliatu
         context: Cuntestu·i induve u filtru deve esse applicatu
+        current_password: Per ragione di sicurità, entrate a chjave d'accessu di stu contu
+        current_username: Per cunfirmà, entrate u cugnome di questu contu
         digest: Solu mandatu dopu à una longa perioda d’inattività, è solu s’elli ci sò novi missaghji diretti
-        discoverable_html: L'<a href="%{path}" target="_blank">annuariu</a> permette à a ghjente di truvà conti à partesi d'interessi è d'attività. Ci vole à avè almenu %{min_followers} abbunati
+        discoverable: L'annuariu di i prufili hè un'altra manera per u vostru contu di tuccà un'audienza più larga
         email: Avete da riceve un'e-mail di cunfirmazione
         fields: Pudete avè fin’à 4 elementi mustrati cum’un tavulone nant’à u vostru prufile
         header: Furmatu PNG, GIF o JPG. %{size} o menu. Sarà ridottu à %{dimensions}px
@@ -34,16 +41,24 @@ co:
         setting_hide_network: I vostri abbunati è abbunamenti ùn saranu micca mustrati nant’à u vostru prufile
         setting_noindex: Tocca à u vostru prufile pubblicu è i vostri statuti
         setting_show_application: L'applicazione chì voi utilizate per mandà statuti sarà affissata indè a vista ditagliata di quelli
+        setting_use_blurhash: I digradati blurhash sò basati nant'à i culori di u ritrattu piattatu ma senza i ditagli
+        setting_use_pending_items: Clicchi per messe à ghjornu i statuti invece di fà sfilà a linea autumaticamente
         username: U vostru cugnome sarà unicu nant'à %{domain}
         whole_word: Quandu a parolla o a frasa sana hè alfanumerica, sarà applicata solu s'ella currisponde à a parolla sana
+      domain_allow:
+        domain: Stu duminiu puderà ricuperà i dati di stu servore è i dati ch'affaccanu da quallà saranu trattati è cunservati
       featured_tag:
         name: 'Pudete vulè utilizà unu di quelli:'
+      form_challenge:
+        current_password: Entrate in in una zona sicurizata
       imports:
         data: Un fugliale CSV da un’altru servore di Mastodon
       invite_request:
         text: Quessu ci aiutarà à valutà a vostra dumanda
       sessions:
         otp: 'Entrate u codice d’identificazione à dui fattori nant’à u vostru telefuninu, o unu di i vostri codici di ricuperazione:'
+      tag:
+        name: Pudete solu cambià a cassa di i caratteri, per esempiu per u rende più lighjevule
       user:
         chosen_languages: Soli i statuti scritti in e lingue selezziunate saranu mustrati indè e linee pubbliche
     labels:
@@ -51,9 +66,14 @@ co:
         fields:
           name: Marcu
           value: Cuntinutu
+      account_alias:
+        acct: Cugnome di l'anzianu contu
+      account_migration:
+        acct: Cugnome di u novu contu
       account_warning_preset:
         text: Testu preselezziunatu
       admin_account_action:
+        include_statuses: Inchjude i statuti palisati indè l'e-mail
         send_email_notification: Nutificà l'utilizatore cù un'e-mail
         text: Avertimentu persunalizatu
         type: Azzione
@@ -108,7 +128,10 @@ co:
         setting_show_application: Indicà u nome di l'applicazione utilizata per mandà statuti
         setting_system_font_ui: Pulizza di caratteri di u sistemu
         setting_theme: Tema di u situ
+        setting_trends: Vede e tendenze per oghji
         setting_unfollow_modal: Mustrà una cunfirmazione per siguità qualch’unu
+        setting_use_blurhash: Vede digradati di culori per i media piattati
+        setting_use_pending_items: Modu lentu
         severity: Severità
         type: Tippu d’impurtazione
         username: Cugnome
@@ -120,6 +143,8 @@ co:
         must_be_follower: Piattà e nutificazione di quelli·e ch’ùn vi seguitanu
         must_be_following: Piattà e nutificazione di quelli·e ch’ùn seguitate
         must_be_following_dm: Bluccà e missaghji diretti di quelli·e ch’ùn seguitate
+      invite:
+        comment: Cummentariu
       invite_request:
         text: Perchè vulete ghjunghje?
       notification_emails:
@@ -131,6 +156,12 @@ co:
         pending_account: Mandà un'e-mail quandu un novu contu hà bisognu d'esse valutatu
         reblog: Mandà un’e-mail quandu qualch’unu sparte i mo statuti
         report: Mandà un'e-mail quandu c'hè un novu signalamentu
+        trending_tag: Mandà un'e-mail quandu un hashtag micca verificatu hè in e tendenze
+      tag:
+        listable: Auturizà stu hashtag à esse vistu nant'à l'annuariu di i prufili
+        name: Hashtag
+        trendable: Auturizà stu hashtag à esse vistu in e tendenze
+        usable: Auturizà i statuti à utilizà stu hashtag
     'no': Nò
     recommended: Ricumandati
     required:
diff --git a/config/locales/simple_form.cs.yml b/config/locales/simple_form.cs.yml
index 3bf74e9718232a79bb7d23c9879b7e7b06fba09f..047d59c6983bd352ec107da1766ade5458ccad38 100644
--- a/config/locales/simple_form.cs.yml
+++ b/config/locales/simple_form.cs.yml
@@ -2,9 +2,14 @@
 cs:
   simple_form:
     hints:
+      account_alias:
+        acct: Zadejte přezdívku@doménu účtu, ze kterého se chcete přesunout
+      account_migration:
+        acct: Zadejte přezdívku@doménu účtu, na který se chcete přesunout
       account_warning_preset:
         text: Můžete používat syntaxi tootů, jako například URL, hashtagy a zmínky
       admin_account_action:
+        include_statuses: Uživatel uvidí, které tooty způsobily moderátorskou akci nebo varování
         send_email_notification: Uživatel obdrží vysvětlení toho, co se stalo s jeho účtem
         text_html: Volitelné. Můžete používat syntaxi tootů. Pro ušetření času si můžete <a href="%{path}">přidat předlohy pro varování</a>
         type_html: Vyberte, co chcete udělat s účtem <strong>%{acct}</strong>
@@ -14,8 +19,10 @@ cs:
         avatar: PNG, GIF či JPG. Maximálně %{size}. Bude zmenšen na %{dimensions} px
         bot: Tento účet provádí hlavně automatizované akce a nemusí být spravován
         context: Jeden či více kontextů, ve kterých má být filtr uplatněn
+        current_password: Z bezpečnostních důvodů prosím zadejte heslo aktuálního účtu
+        current_username: Prosím potvrďte 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žel/a osobní zprávy
-        discoverable_html: <a href="%{path}" target="_blank">Adresář</a> dovoluje lidem najít účty podle zájmů a aktivity. Vyžaduje alespoň %{min_followers} sledujících
+        discoverable: Adresář profilů je další způsob, díky kterému se může váš účet dostat k širšímu publiku
         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
@@ -34,16 +41,24 @@ cs:
         setting_hide_network: Koho sledujete a kdo sleduje vás nebude zobrazeno na vašem profilu
         setting_noindex: Ovlivňuje váš veřejný profil a stránky tootů
         setting_show_application: Aplikace, kterou používáte k psaní tootů, bude zobrazena v detailním zobrazení vašich tootů
+        setting_use_blurhash: Gradienty jsou založeny na barvách skryté grafiky, ale zakrývají jakékoliv detaily
+        setting_use_pending_items: Skrýt aktualizace časové osy a načíst je kliknutím namísto automatického rolování proudu
         username: Vaše uživatelské jméno bude na %{domain} unikátní
         whole_word: Je-li klíčové slovo či fráze pouze alfanumerická, bude aplikována pouze, pokud se shoduje s celým slovem
+      domain_allow:
+        domain: Tato doména bude moci stahovat data z tohoto serveru a příchozí data z ní budou zpracována a uložena
       featured_tag:
         name: 'Nejspíš budete chtít použít jeden z těchto:'
+      form_challenge:
+        current_password: Vstupujete do zabezpečeného prostoru
       imports:
         data: Soubor CSV exportovaný z jiného serveru Mastodon
       invite_request:
         text: To nám pomůže posoudit váš požadavek
       sessions:
         otp: 'Napište dvoufázový kód vygenerovaný vaší mobilní aplikací, nebo použijte jeden z vašich záložních kódů:'
+      tag:
+        name: Můžete měnit pouze velikost písmen, například kvůli lepší čitelnosti
       user:
         chosen_languages: Je-li tohle zaškrtnuto, budou ve veřejných časových osách zobrazeny pouze tooty ve zvolených jazycích
     labels:
@@ -51,9 +66,14 @@ cs:
         fields:
           name: Označení
           value: Obsah
+      account_alias:
+        acct: Adresa starého účtu
+      account_migration:
+        acct: Adresa nového účtu
       account_warning_preset:
         text: Text předlohy
       admin_account_action:
+        include_statuses: Zahrnout v e-mailu nahlášené tooty
         send_email_notification: Informovat uživatele e-mailem
         text: Vlastní varování
         type: Akce
@@ -108,7 +128,10 @@ cs:
         setting_show_application: Zobrazit aplikaci používanou k psaní tootů
         setting_system_font_ui: Použít výchozí písmo systému
         setting_theme: Motiv stránky
+        setting_trends: Zobrazit dnešní trendy
         setting_unfollow_modal: Zobrazovat před zrušením sledování potvrzovací okno
+        setting_use_blurhash: Zobrazit pro skrytá média barevné gradienty
+        setting_use_pending_items: Pomalý režim
         severity: Přísnost
         type: Typ importu
         username: Uživatelské jméno
@@ -120,6 +143,8 @@ cs:
         must_be_follower: Blokovat oznámení od lidí, kteří vás nesledují
         must_be_following: Blokovat oznámení od lidí, které nesledujete
         must_be_following_dm: Blokovat přímé zprávy od lidí, které nesledujete
+      invite:
+        comment: Komentář
       invite_request:
         text: Proč se chcete připojit?
       notification_emails:
@@ -131,6 +156,12 @@ cs:
         pending_account: Posílat e-maily, když je třeba posoudit nový účet
         reblog: Posílat e-maily, když někdo boostne váš toot
         report: Posílat e-maily, je-li odesláno nové nahlášení
+        trending_tag: Posílat e-maily, když se neschválený hashtag stane populárním
+      tag:
+        listable: Dovolit tomuto hashtagu objevovat se v hledáních a v adresáři profilů
+        name: Hashtag
+        trendable: Dovolit tomuto hashtagu objevovat se v trendech
+        usable: Dovolit tootům používat tento hashtag
     'no': Ne
     recommended: Doporučeno
     required:
diff --git a/config/locales/simple_form.cy.yml b/config/locales/simple_form.cy.yml
index 023506f24fc1060be95add1ea5cd2d26c491a3d7..9b06bf473a0175925389e37194fdd304fd3b0039 100644
--- a/config/locales/simple_form.cy.yml
+++ b/config/locales/simple_form.cy.yml
@@ -2,9 +2,14 @@
 cy:
   simple_form:
     hints:
+      account_alias:
+        acct: Rhowch enwdefnyddiwr@parth y cyfrif rydych chi am symud ohono
+      account_migration:
+        acct: Rhowch enwdefnyddiwr@parth y cyfrif rydych chi am symud iddo
       account_warning_preset:
         text: Gallwch defnyddio cystrawen tŵt, fel URLs, hashnodau a sôniadau
       admin_account_action:
+        include_statuses: Bydd y defnyddiwr yn gweld pa tŵtiau sydd wedi achosi'r weithred gymedroli neu'r rhybudd
         send_email_notification: Bydd y defnyddiwr yn derbyn esboniad o beth digwyddodd gyda'i cyfrif
         text_html: Yn ddewisol. Gallwch defnyddio cystrawen tŵt. Gallwch <a href="%{path}">ychwanegu rhagosodiadau rhybydd</a> i arbed amser
         type_html: Dewis beth i wneud gyda <strong>%{acct}</strong>
@@ -14,8 +19,10 @@ cy:
         avatar: PNG, GIF neu JPG. %{size} ar y mwyaf. Caiff ei israddio i %{dimensions}px
         bot: Mae'r cyfrif hwn yn perfformio gweithredoedd awtomatig yn bennaf ac mae'n bosib nad yw'n cael ei fonitro
         context: Un neu fwy cyd-destun lle dylai'r hidlydd weithio
+        current_password: At ddibenion diogelwch, nodwch gyfrinair y cyfrif cyfredol
+        current_username: I gadarnhau, nodwch enw defnyddiwr y cyfrif cyfredol
         digest: Ond yn cael eu hanfon ar ôl cyfnod hir o anweithgarwch ac ond os ydych wedi derbyn unrhyw negeseuon personol yn eich absenoldeb
-        discoverable_html: Mae'r <a href="%{path}" target="_blank">cyfeiriadur</a> yn gadael i bobl ddarganfod cyfrifau wedi'i seilio ar ddiddordebau a gweithgareddau.. Mae hyn angen o leiaf %{min_followers} o ddilynwyr
+        discoverable: Mae'r cyfeirlyfr proffil yn ffordd arall y gall eich cyfrif gyrraedd cynulleidfa ehangach
         email: Byddwch yn derbyn e-bost i gadarnhau
         fields: Mae modd i chi arddangos hyd at 4 eitem fel tabl ar eich proffil
         header: PNG, GIF neu JPG. %{size} ar y mwyaf. Ceith ei israddio i %{dimensions}px
@@ -34,16 +41,24 @@ cy:
         setting_hide_network: Ni fydd y rheini yr ydych yn eu dilyn a phwy sy'n eich dilyn chi yn cael ei ddangos ar eich proffil
         setting_noindex: Mae hyn yn effeithio ar eich proffil cyhoeddus a'ch tudalennau statws
         setting_show_application: Bydd y offer frydych yn defnyddio i dŵtio yn cael ei arddangos yn golwg manwl eich tŵtiau
+        setting_use_blurhash: Mae graddiannau wedi'u seilio ar liwiau'r delweddau cudd ond maent yn cuddio unrhyw fanylion
+        setting_use_pending_items: Cuddio diweddariadau llinell amser y tu ôl i glic yn lle sgrolio yn awtomatig
         username: Bydd eich enw defnyddiwr yn unigryw ar %{domain}
         whole_word: Os yw'r allweddair neu'r ymadrodd yn alffaniwmerig yn unig, mi fydd ond yn cael ei osod os yw'n cyfateb a'r gair cyfan
+      domain_allow:
+        domain: Bydd y parth hwn yn gallu nôl data o'r gweinydd hwn a bydd data sy'n dod i mewn ohono yn cael ei brosesu a'i storio
       featured_tag:
         name: 'Efallai hoffech defnyddio un o''r rhain:'
+      form_challenge:
+        current_password: Rydych chi'n mynd i mewn i ardal sicr
       imports:
         data: Allforiwyd dogfen CSV o achos Mastodon arall
       invite_request:
         text: Bydd hyn yn helpu ni adolygu eich cais
       sessions:
         otp: 'Mewnbynnwch y cod dau gam a gynhyrchwyd gan eich ap ffôn neu defnyddiwch un o''ch codau adfer:'
+      tag:
+        name: Dim ond er mwyn ei gwneud yn fwy darllenadwy y gallwch chi newid y llythrennau, er enghraifft
       user:
         chosen_languages: Wedi eu dewis, dim ond tŵtiau yn yr ieithoedd hyn bydd yn cael eu harddangos mewn ffrydiau cyhoeddus
     labels:
@@ -51,9 +66,14 @@ cy:
         fields:
           name: Label
           value: Cynnwys
+      account_alias:
+        acct: Enw'r hen gyfrif
+      account_migration:
+        acct: Enw'r cyfrif newydd
       account_warning_preset:
         text: Testun rhagosodedig
       admin_account_action:
+        include_statuses: Cynhwyswch tŵtiau yr adroddwyd amdanynt yn yr e-bost
         send_email_notification: Hysbysu'r defnyddiwr trwy e-bost
         text: Rhybudd wedi'i addasu
         type: Gweithredu
@@ -108,7 +128,10 @@ cy:
         setting_show_application: Datguddio'r offer defnyddwyd i anfon tŵtiau
         setting_system_font_ui: Defnyddio ffont rhagosodedig y system
         setting_theme: Thema'r wefan
+        setting_trends: Dangos tueddiadau o heddiw ymlaen
         setting_unfollow_modal: Dangos deialog cadarnhau cyn dad-ddilyn rhywun
+        setting_use_blurhash: Dangoswch raddiannau lliwgar ar gyfer cyfryngau cudd
+        setting_use_pending_items: Modd araf
         severity: Difrifoldeb
         type: Modd mewnforio
         username: Enw defnyddiwr
@@ -120,6 +143,8 @@ cy:
         must_be_follower: Blocio hysbysiadau o bobl nad ydynt yn eich dilyn
         must_be_following: Blocio hysbysiadau o bobl nad ydych yn eu dilyn
         must_be_following_dm: Blocio negeseuon uniongyrchol o bobl nad ydych yn eu dilyn
+      invite:
+        comment: Sylw
       invite_request:
         text: Pam hoffech ymuno?
       notification_emails:
@@ -131,6 +156,12 @@ cy:
         pending_account: Anfon ebost pan mae cyfrif newydd angen adolygiad
         reblog: Anfon e-bost pan mae rhywun yn bŵstio eich statws
         report: Anfon e-bost pan y cyflwynir adroddiad newydd
+        trending_tag: Anfonwch e-bost pan fydd hashnod heb ei adolygu yn tueddu
+      tag:
+        listable: Gadewch i'r hashnod hwn ymddangos mewn chwiliadau ac ar y cyfeiriadur proffil
+        name: Hashnod
+        trendable: Gadewch i'r hashnod hwn ymddangos o dan dueddiadau
+        usable: Caniatáu i tŵtiau ddefnyddio'r hashnod hwn
     'no': Na
     recommended: Argymhellwyd
     required:
diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml
index 324afece652e63461ee24727bf4c4cef8571ab8a..48a0a68834227506aaf6bc812f2fa726ab227a2e 100644
--- a/config/locales/simple_form.da.yml
+++ b/config/locales/simple_form.da.yml
@@ -26,6 +26,8 @@ da:
         setting_noindex: PÃ¥virker din offentlige profil og status sider
         username: Dit brugernavn vil være unikt på %{domain}
         whole_word: Når nøgle ordet eller udtrykket kun er alfanumerisk, vil det kun blive brugt hvis det passer hele ordet
+      featured_tag:
+        name: 'Du kunne måske tænke dig at bruge en af følgende:'
       imports:
         data: CSV fil eksporteret fra en anden Mastodon server
       sessions:
@@ -83,6 +85,7 @@ da:
         setting_system_font_ui: Brug systemets standard font
         setting_theme: Tema for side
         setting_unfollow_modal: Vis bekræftelses dialog før du stopper med at følge nogen
+        setting_use_pending_items: Langsom tilstand
         severity: Omfang
         type: Importtype
         username: Brugernavn
@@ -92,6 +95,8 @@ da:
         must_be_follower: Bloker notifikationer fra folk der ikke følger dig
         must_be_following: Bloker notifikationer fra folk du ikke følger
         must_be_following_dm: Bloker direkte beskeder fra folk du ikke følger
+      invite_request:
+        text: Hvorfor ønsker du at tilmelde dig?
       notification_emails:
         digest: Send sammendrag via emails
         favourite: Send email når nogen favoriserer din status
@@ -100,7 +105,11 @@ da:
         mention: Send e-mail når nogen nævner dig
         reblog: Send email når nogen fremhæver din status
         report: Send email når en ny anmeldelse bliver indsendt
+      tag:
+        name: Hashtag
     'no': Nej
+    recommended: Anbefalet
     required:
+      mark: "*"
       text: påkrævet
     'yes': Ja
diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml
index 61e0f9740d2168f18a228b9039d938766917a9ad..1b7667c84c5d02625b5e9434cc80a84eacba6cb6 100644
--- a/config/locales/simple_form.de.yml
+++ b/config/locales/simple_form.de.yml
@@ -2,9 +2,14 @@
 de:
   simple_form:
     hints:
+      account_alias:
+        acct: Gib den benutzernamen@domain des Kontos an, von dem du umziehen möchtest
+      account_migration:
+        acct: Gib den benutzernamen@domain des Kontos an, zu dem du umziehen möchtest
       account_warning_preset:
         text: Du kannst Beitragssyntax benutzen, wie z.B. URLs, Hashtags und Erwähnungen
       admin_account_action:
+        include_statuses: Der Benutzer wird sehen, welche Beiträge diese Maßnahme verursacht haben
         send_email_notification: Benutzer_in wird Bescheid gegeben, was mit dem Konto geschehen ist
         text_html: Optional. Du kannst Beitragssyntax nutzen. Du kannst <a href="%{path}">Warnungsvorlagen</a> benutzen um Zeit zu sparen
         type_html: Wähle aus, was du mit <strong>%{acct}</strong> machen möchtest
@@ -14,8 +19,10 @@ de:
         avatar: PNG, GIF oder JPG. Maximal %{size}. Wird auf %{dimensions} px herunterskaliert
         bot: Dieses Konto führt lediglich automatisierte Aktionen durch und wird möglicherweise nicht überwacht
         context: Ein oder mehrere Kontexte, wo der Filter aktiv werden soll
+        current_password: Aus Sicherheitsgründen gib bitte das Passwort des aktuellen Kontos ein
+        current_username: Um das zu bestätigen, gib den Benutzernamen des aktuellen Kontos ein
         digest: Wenn du eine lange Zeit inaktiv bist, wird dir eine Zusammenfassung von Erwähnungen zugeschickt, die du in deiner Abwesenheit empfangen hast
-        discoverable_html: Das <a href="%{path}" target="_blank">Verzeichnis</a> erlaubt es dein Profil durch deine Hashtags und deine Aktivitäten zu entdecken. Voraussetzung ist allerdings mindestens %{min_followers} Folger_innen
+        discoverable: Das Profilverzeichnis ist eine andere Möglichkeit, mit der dein Konto ein größeres Publikum erreichen kann
         email: Du wirst eine Bestätigungs-E-Mail erhalten
         fields: Du kannst bis zu 4 Elemente auf deinem Profil anzeigen lassen, die als Tabelle dargestellt werden
         header: PNG, GIF oder JPG. Maximal %{size}. Wird auf %{dimensions} px herunterskaliert
@@ -34,16 +41,24 @@ de:
         setting_hide_network: Wem du folgst und wer dir folgt, wird in deinem Profil nicht angezeigt
         setting_noindex: Betrifft dein öffentliches Profil und deine Beiträge
         setting_show_application: Die Anwendung die du nutzst wird in der detaillierten Ansicht deiner Beiträge angezeigt
-        username: Dein Profilname wird auf %{domain} einzigartig sein
+        setting_use_blurhash: Die Farbverläufe basieren auf den Farben der versteckten Medien, aber verstecken irgendwelche Details
+        setting_use_pending_items: Neue Beiträge hinter einem Klick verstecken anstatt automatisch zu scrollen
+        username: Dein Benutzername wird auf %{domain} einzigartig sein
         whole_word: Wenn das Schlagwort nur aus Buchstaben und Zahlen besteht, wird es nur angewendet, wenn es dem ganzen Wort entspricht
+      domain_allow:
+        domain: Diese Domain kann Daten von diesem Server abrufen und eingehende Daten werden verarbeitet und gespeichert
       featured_tag:
         name: 'Du möchtest vielleicht einen von diesen benutzen:'
+      form_challenge:
+        current_password: Du betrittst einen sicheren Bereich
       imports:
         data: CSV-Datei, die aus einem anderen Mastodon-Server exportiert wurde
       invite_request:
         text: Dies wird uns helfen deine Anmeldungsanfrage besser zu verarbeiten
       sessions:
         otp: 'Gib die Zwei-Faktor-Authentifizierung von deinem Telefon ein oder benutze einen deiner Wiederherstellungscodes:'
+      tag:
+        name: Du kannst zum Beispiel nur die Groß- und Kleinschreibung der Buchstaben ändern, um es lesbarer zu machen
       user:
         chosen_languages: Wenn aktiviert, werden nur Beiträge in den ausgewählten Sprachen auf den öffentlichen Zeitleisten angezeigt
     labels:
@@ -51,9 +66,14 @@ de:
         fields:
           name: Bezeichnung
           value: Inhalt
+      account_alias:
+        acct: Adresse des alten Kontos
+      account_migration:
+        acct: Adresse des neuen Kontos
       account_warning_preset:
         text: Vorlagentext
       admin_account_action:
+        include_statuses: Meldungen der E-Mail beifügen
         send_email_notification: Benachrichtige den Nutzer per E-Mail
         text: Eigene Warnung
         type: Aktion
@@ -64,7 +84,7 @@ de:
           suspend: Deaktivieren und Benutzerdaten unwiderruflich löschen
         warning_preset_id: Benutze eine Warnungsvorlage
       defaults:
-        autofollow: Eingeladene Nutzer_innen sollen dir automatisch folgen
+        autofollow: Eingeladene Nutzer sollen dir automatisch folgen
         avatar: Profilbild
         bot: Dieses Profil ist ein Bot
         chosen_languages: Sprachen filtern
@@ -108,7 +128,10 @@ de:
         setting_show_application: Anwendung preisgeben, die benutzt wurde um Beiträge zu versenden
         setting_system_font_ui: Standardschriftart des Systems verwenden
         setting_theme: Theme
+        setting_trends: Heutige Trends anzeigen
         setting_unfollow_modal: Bestätigungsdialog anzeigen, bevor jemandem entfolgt wird
+        setting_use_blurhash: Farbverlauf für versteckte Medien anzeigen
+        setting_use_pending_items: Langsamer Modus
         severity: Schweregrad
         type: Art des Imports
         username: Profilname
@@ -120,6 +143,8 @@ de:
         must_be_follower: Benachrichtigungen von Profilen blockieren, die mir nicht folgen
         must_be_following: Benachrichtigungen von Profilen blockieren, denen ich nicht folge
         must_be_following_dm: Private Nachrichten von Profilen, denen ich nicht folge, blockieren
+      invite:
+        comment: Kommentar
       invite_request:
         text: Warum möchtest du beitreten?
       notification_emails:
@@ -131,6 +156,12 @@ de:
         pending_account: E-Mail senden, wenn ein neues Benutzerkonto zur Überprüfung aussteht
         reblog: E-Mail senden, wenn jemand meinen Beitrag teilt
         report: E-Mail senden, wenn ein neuer Bericht vorliegt
+        trending_tag: E-Mail senden, wenn ein ausstehender Hashtag angesagt ist
+      tag:
+        listable: Erlaube diesem Hashtag im Profilverzeichnis zu erscheinen
+        name: Hashtag
+        trendable: Erlaube es diesen Hashtag in den Trends erscheinen zu lassen
+        usable: Beiträge erlauben, diesen Hashtag zu verwenden
     'no': Nein
     recommended: Empfohlen
     required:
diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml
index 67f3b64aa1b6f1be7289bd072e768566a3348f16..cbce97171ae0e8884c64bd1c46852c1ffa312530 100644
--- a/config/locales/simple_form.el.yml
+++ b/config/locales/simple_form.el.yml
@@ -2,9 +2,14 @@
 el:
   simple_form:
     hints:
+      account_alias:
+        acct: Ορίστε το username@domain του λογαριασμού από τον οποίο θέλετε να μετακινηθείτε
+      account_migration:
+        acct: Ορίστε το username@domain του λογαριασμού στον οποίο θέλετε να μετακινηθείτε
       account_warning_preset:
         text: Μπορεις να χρησιμοποιήσεις συντακτικό ενός τουτ όπως διευθύνσεις URL, ταμπέλες και αναφορές
       admin_account_action:
+        include_statuses: Ο χρήστης θα δει ποια τουτ προκάλεσαν την προειδοποίηση ή την ενέργεια των διαχειριστών
         send_email_notification: Ο χρήστης θα λάβει μια εξήγηση του τι συνέβη με τον λογαριασμό του
         text_html: Προαιρετικό. Μπορείς να χρησιμοποιήσεις συντακτικό ενός τουτ. Μπορείς να <a href="%{path}">ορίσεις προκαθορισμένες προειδοποιήσεις</a> για να γλυτώσεις χρόνο
         type_html: Διάλεξε τι θα κανείς με τον  <strong>%{acct}</strong>
@@ -14,8 +19,10 @@ el:
         avatar: PNG, GIF ή JPG. Έως %{size}. Θα περιοριστεί σε διάσταση %{dimensions}px
         bot: Ο λογαριασμός αυτός εκτελεί κυρίως αυτοματοποιημένες ενέργειες και ίσως να μην παρακολουθείται
         context: Ένα ή περισσότερα πλαίσια στα οποία μπορεί να εφαρμόζεται αυτό το φίλτρο
+        current_password: Για λόγους ασφαλείας παρακαλώ γράψε τον κωδικό του τρέχοντος λογαριασμού
+        current_username: Για επιβεβαίωση, παρακαλώ γράψε το όνομα χρήστη του τρέχοντος λογαριασμού
         digest: Αποστέλλεται μόνο μετά από μακρά περίοδο αδράνειας και μόνο αν έχεις λάβει προσωπικά μηνύματα κατά την απουσία σου
-        discoverable_html: "Ο <a href=\"%{path}\" target=\"_blank\">κατάλογος</a> \nσου επιτρέπει να βρεις λογαριασμούς βάσει ενδιαφερόντων και δραστηριότητας. Απαιτεί τουλάχιστον %{min_followers} ακόλουθους"
+        discoverable: Ο κατάλογος λογαριασμών είναι ένας ακόμα τρόπος ώστε ο λογαριασμός σου να βρει μεγαλύτερο κοινό
         email: Θα σου σταλεί email επιβεβαίωσης
         fields: Μπορείς να έχεις έως 4 σημειώσεις σε μορφή πίνακα στο προφίλ σου
         header: PNG, GIF ή JPG. Έως %{size}. Θα περιοριστεί σε διάσταση %{dimensions}px
@@ -34,16 +41,24 @@ el:
         setting_hide_network: Δε θα εμφανίζεται στο προφίλ σου ποιους ακολουθείς και ποιοι σε ακολουθούν
         setting_noindex: Επηρεάζει το δημόσιο προφίλ και τις δημοσιεύσεις σου
         setting_show_application: Η εφαρμογή που χρησιμοποιείς για να στέλνεις τα τουτ σου θα εμφανίζεται στις αναλυτικές λεπτομέρειες τους
+        setting_use_blurhash: Οι χρωματισμοί βασίζονται στα χρώματα του κρυμμένου πολυμέσου αλλά θολώνουν τις λεπτομέρειες
+        setting_use_pending_items: Εμφάνιση ενημερώσεων ροής μετά από κλικ αντί για αυτόματη κύλισή τους
         username: Το όνομα χρήστη σου θα είναι μοναδικό στο %{domain}
         whole_word: Όταν η λέξη ή η φράση κλειδί είναι μόνο αλφαριθμητική, θα εφαρμοστεί μόνο αν ταιριάζει με ολόκληρη τη λέξη
+      domain_allow:
+        domain: Ο τομέας αυτός θα επιτρέπεται να ανακτά δεδομένα από αυτό τον διακομιστή και τα εισερχόμενα δεδομένα θα επεξεργάζονται και θα αποθηκεύονται
       featured_tag:
         name: 'Ίσως να θες να χρησιμοποιήσεις μια από αυτές:'
+      form_challenge:
+        current_password: Μπαίνεις σε ασφαλή περιοχή
       imports:
         data: Αρχείο CSV που έχει εξαχθεί από διαφορετικό κόμβο Mastodon
       invite_request:
         text: Αυτό θα μας βοηθήσει να επιθεωρήσουμε την αίτησή σου
       sessions:
         otp: 'Βάλε τον κωδικό δυο παραγόντων (2FA) από την εφαρμογή του τηλεφώνου σου ή χρησιμοποίησε κάποιον από τους κωδικούς ανάκτησης σου:'
+      tag:
+        name: Μπορείς να αλλάξεις μόνο το πλαίσιο των χαρακτήρων, για παράδειγμα για να γίνει περισσότερο ευανάγνωστο
       user:
         chosen_languages: Όταν ενεργοποιηθεί, στη δημόσια ροή θα εμφανίζονται τουτ μόνο από τις επιλεγμένες γλώσσες
     labels:
@@ -51,9 +66,14 @@ el:
         fields:
           name: Ταμπέλα
           value: Περιεχόμενο
+      account_alias:
+        acct: Διακριτικό του παλιού λογαριασμού
+      account_migration:
+        acct: Διακριτικό του νέου λογαριασμού
       account_warning_preset:
         text: Προκαθορισμένο κείμενο
       admin_account_action:
+        include_statuses: Συμπερίληψη των καταγγελλομένων τουτ στο email
         send_email_notification: Ενημέρωση χρήστη μέσω email
         text: Προσαρμοσμένη προειδοποίηση
         type: Ενέργεια
@@ -108,7 +128,10 @@ el:
         setting_show_application: Αποκάλυψη εφαρμογής που χρησιμοποιήθηκε για την αποστολή των τουτ
         setting_system_font_ui: Χρησιμοποίησε την προεπιλεγμένη γραμματοσειρά του συστήματος
         setting_theme: Θέμα ιστότοπου
+        setting_trends: Εμφάνιση σημερινών τάσεων
         setting_unfollow_modal: Εμφάνιση ερώτησης επιβεβαίωσης πριν διακόψεις την παρακολούθηση κάποιου
+        setting_use_blurhash: Εμφάνιση χρωματισμών για τα κρυμμένα πολυμέσα
+        setting_use_pending_items: Αργή λειτουργία
         severity: Αυστηρότητα
         type: Τύπος εισαγωγής
         username: Όνομα χρηστη
@@ -120,17 +143,25 @@ el:
         must_be_follower: Μπλόκαρε τις ειδοποιήσεις από όσους δεν ακολουθείς
         must_be_following: Μπλόκαρε τις ειδοποιήσεις που προέρχονται από άτομα που δεν τα ακολουθείς
         must_be_following_dm: Μπλόκαρε τα προσωπικά μηνύματα από όσους δεν ακολουθείς
+      invite:
+        comment: Σχόλια
       invite_request:
         text: Γιατί θέλεις να συμμετάσχεις;
       notification_emails:
         digest: Στέλνε συνοπτικά email
-        favourite: Στελνε email όταν κάποιος σημειώνει ως αγαπημένη τη δημοσίευσή σου
-        follow: Στελνε email όταν κάποιος σε ακολουθεί
-        follow_request: Στέλνε email όταν κάποιος ζητάει να σε ακολουθήσει
-        mention: Στέλνε email όταν κάποιος σε αναφέρει
+        favourite: Αποστολή email όταν κάποιος σημειώνει ως αγαπημένη τη δημοσίευσή σου
+        follow: Αποστολή email όταν κάποιος σε ακολουθεί
+        follow_request: Αποστολή email όταν κάποιος ζητάει να σε ακολουθήσει
+        mention: Αποστολή email όταν κάποιος σε αναφέρει
         pending_account: Αποστολή email όταν υπάρχει νέος λογαριασμός για επιθεώρηση
-        reblog: Στέλνε email όταν κάποιος προωθεί τη δημοσίευση σου
+        reblog: Αποστολή email όταν κάποιος προωθεί τη δημοσίευση σου
         report: Αποστολή email όταν υποβάλλεται νέα καταγγελία
+        trending_tag: Αποστολή email όταν μια μη-εγκεκριμένη ταμπέλα γίνεται δημοφιλής
+      tag:
+        listable: Εμφάνιση αυτής της ταμπέλας στο δημόσιο κατάλογο
+        name: Ταμπέλα
+        trendable: Εμφάνιση της ταμπέλας στις τάσεις
+        usable: Χρήση της ταμπέλας σε τουτ
     'no': Όχι
     recommended: Προτείνεται
     required:
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index 93235ad1f8f0ab3d238d5dfa53343afe8e42a7d6..2a08510ff02292a323ff1484ecdeb6c3417f926c 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -2,9 +2,14 @@
 en:
   simple_form:
     hints:
+      account_alias:
+        acct: Specify the username@domain of the account you want to move from
+      account_migration:
+        acct: Specify the username@domain of the account you want to move to
       account_warning_preset:
         text: You can use toot syntax, such as URLs, hashtags and mentions
       admin_account_action:
+        include_statuses: The user will see which toots have caused the moderation action or warning
         send_email_notification: The user will receive an explanation of what happened with their account
         text_html: Optional. You can use toot syntax. You can <a href="%{path}">add warning presets</a> to save time
         type_html: Choose what to do with <strong>%{acct}</strong>
@@ -14,8 +19,10 @@ en:
         avatar: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px
         bot: This account mainly performs automated actions and might not be monitored
         context: One or multiple contexts where the filter should apply
+        current_password: For security purposes please enter the password of the current account
+        current_username: To confirm, please enter the username of the current account
         digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence
-        discoverable_html: The <a href="%{path}" target="_blank">directory</a> lets people find accounts based on interests and activity. Requires at least %{min_followers} followers
+        discoverable: The profile directory is another way by which your account can reach a wider audience
         email: You will be sent a confirmation e-mail
         fields: You can have up to 4 items displayed as a table on your profile
         header: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px
@@ -35,16 +42,24 @@ en:
         setting_hide_network: Who you follow and who follows you will not be shown on your profile
         setting_noindex: Affects your public profile and status pages
         setting_show_application: The application you use to toot will be displayed in the detailed view of your toots
+        setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details
+        setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed
         username: Your username will be unique on %{domain}
         whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word
+      domain_allow:
+        domain: This domain will be able to fetch data from this server and incoming data from it will be processed and stored
       featured_tag:
         name: 'You might want to use one of these:'
+      form_challenge:
+        current_password: You are entering a secure area
       imports:
         data: CSV file exported from another Mastodon server
       invite_request:
         text: This will help us review your application
       sessions:
         otp: 'Enter the two-factor code generated by your phone app or use one of your recovery codes:'
+      tag:
+        name: You can only change the casing of the letters, for example, to make it more readable
       user:
         chosen_languages: When checked, only toots in selected languages will be displayed in public timelines
     labels:
@@ -52,9 +67,14 @@ en:
         fields:
           name: Label
           value: Content
+      account_alias:
+        acct: Handle of the old account
+      account_migration:
+        acct: Handle of the new account
       account_warning_preset:
         text: Preset text
       admin_account_action:
+        include_statuses: Include reported toots in the e-mail
         send_email_notification: Notify the user per e-mail
         text: Custom warning
         type: Action
@@ -110,7 +130,10 @@ en:
         setting_show_application: Disclose application used to send toots
         setting_system_font_ui: Use system's default font
         setting_theme: Site theme
+        setting_trends: Show today's trends
         setting_unfollow_modal: Show confirmation dialog before unfollowing someone
+        setting_use_blurhash: Show colorful gradients for hidden media
+        setting_use_pending_items: Slow mode
         severity: Severity
         type: Import type
         username: Username
@@ -122,6 +145,8 @@ en:
         must_be_follower: Block notifications from non-followers
         must_be_following: Block notifications from people you don't follow
         must_be_following_dm: Block direct messages from people you don't follow
+      invite:
+        comment: Comment
       invite_request:
         text: Why do you want to join?
       notification_emails:
@@ -133,6 +158,12 @@ en:
         pending_account: Send e-mail when a new account needs review
         reblog: Send e-mail when someone boosts your status
         report: Send e-mail when a new report is submitted
+        trending_tag: Send e-mail when an unreviewed hashtag is trending
+      tag:
+        listable: Allow this hashtag to appear in searches and on the profile directory
+        name: Hashtag
+        trendable: Allow this hashtag to appear under trends
+        usable: Allow toots to use this hashtag
     'no': 'No'
     recommended: Recommended
     required:
diff --git a/config/locales/simple_form.eo.yml b/config/locales/simple_form.eo.yml
index 1b63b27a894b809581f200242336a6e41070ec9c..8c2a087c5a3a2c608e34c2e8e3524b98ee0babc6 100644
--- a/config/locales/simple_form.eo.yml
+++ b/config/locales/simple_form.eo.yml
@@ -15,7 +15,6 @@ eo:
         bot: Tiu konto ĉefe faras aŭtomatajn agojn, kaj povas esti ne kontrolata
         context: Unu ol pluraj kuntekstoj kie la filtrilo devus agi
         digest: Sendita nur post longa tempo de neaktiveco, kaj nur se vi ricevis personan mesaĝon en via foresto
-        discoverable_html: La <a href="%{path}" target="_blank">profilujo</a> permesas al homoj trovi kontojn laÅ­ interesoj kaj aktiveco. Postulas almenaÅ­ %{min_followers} sekvantojn
         email: Vi ricevos konfirman retmesaĝon
         fields: Vi povas havi ĝis 4 tabelajn elementojn en via profilo
         header: Formato PNG, GIF aÅ­ JPG. Äœis %{size}. Estos malgrandigita al %{dimensions}px
@@ -108,7 +107,9 @@ eo:
         setting_show_application: Publikigi la aplikaĵon uzatan por sendi mesaĝojn
         setting_system_font_ui: Uzi la dekomencan tiparon de la sistemo
         setting_theme: Reteja etoso
+        setting_trends: Montri hodiaŭajn furoraĵojn
         setting_unfollow_modal: Montri fenestron por konfirmi antaŭ ol ĉesi sekvi iun
+        setting_use_pending_items: Malrapida reĝimo
         severity: Graveco
         type: Importa tipo
         username: Uzantnomo
@@ -120,6 +121,8 @@ eo:
         must_be_follower: Bloki sciigojn de nesekvantoj
         must_be_following: Bloki sciigojn de homoj, kiujn vi ne sekvas
         must_be_following_dm: Bloki rektajn mesaĝojn de homoj, kiujn vi ne sekvas
+      invite:
+        comment: Komento
       invite_request:
         text: Kial vi volas aliĝi?
       notification_emails:
@@ -131,6 +134,11 @@ eo:
         pending_account: Sendi retmesaĝon kiam nova konto bezonas kontrolon
         reblog: Sendi retmesaĝon kiam iu diskonigas vian mesaĝon
         report: Sendi retmesaĝon kiam nova signalo estas sendita
+        trending_tag: Sendi retpoŝtmesaĝon kiam nekontrolita kradvorto furoras
+      tag:
+        name: Kradvorto
+        trendable: Permesi al ĉi tiu kradvorto aperi en furoraĵoj
+        usable: Permesi tootojn uzi ĉiun tiun haketon
     'no': Ne
     recommended: Rekomendita
     required:
diff --git a/config/locales/simple_form.es-AR.yml b/config/locales/simple_form.es-AR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..515d5c1ed90e1b9725f1a1648a82ee512d0575ea
--- /dev/null
+++ b/config/locales/simple_form.es-AR.yml
@@ -0,0 +1 @@
+es-AR:
diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml
index 7b871e8ba0bf6c4475c93ae544c33941f30d402e..2fb33dbc3ca775de7ab65c8ad5c335700c61ba3d 100644
--- a/config/locales/simple_form.es.yml
+++ b/config/locales/simple_form.es.yml
@@ -2,16 +2,27 @@
 es:
   simple_form:
     hints:
+      account_alias:
+        acct: Especifique el nombre de usuario@dominio de la cuenta desde la cual se desea migrar
+      account_migration:
+        acct: Especifique el nombre de usuario@dominio de la cuenta a la cual se desea migrar
       account_warning_preset:
         text: Puede usar sintaxis de toots, como URLs, hashtags y menciones
       admin_account_action:
+        include_statuses: El usuario verá qué toots han causado la acción de moderación o advertencia
         send_email_notification: El usuario recibirá una explicación de lo que sucedió con respecto a su cuenta
+        text_html: Opcional. Puede usar sintaxis de toots. Puede añadir <a href="%{path}">configuraciones predefinidas de advertencia</a> para ahorrar tiempo
+        type_html: Elige qué hacer con <strong>%{acct}</strong>
+        warning_preset_id: Opcional. Aún puede añadir texto personalizado al final de la configuración predefinida
       defaults:
         autofollow: Los usuarios que se registren mediante la invitación te seguirán automáticamente
         avatar: PNG, GIF o JPG. Máximo %{size}. Será escalado a %{dimensions}px
         bot: Esta cuenta ejecuta principalmente acciones automatizadas y podría no ser monitorizada
         context: Uno o múltiples contextos en los que debe aplicarse el filtro
+        current_password: Por razones de seguridad por favor ingrese la contraseña de la cuenta actual
+        current_username: Para confirmar, por favor ingrese el nombre de usuario de la cuenta actual
         digest: Solo enviado tras un largo periodo de inactividad y solo si has recibido mensajes personales durante tu ausencia
+        discoverable: El directorio del perfil es otra forma en la que su cuenta puede llegar a un público más amplio
         email: Se le enviará un correo de confirmación
         fields: Puedes tener hasta 4 elementos mostrándose como una tabla en tu perfil
         header: PNG, GIF o JPG. Máximo %{size}. Será escalado a %{dimensions}px
@@ -22,16 +33,32 @@ es:
         password: Utilice al menos 8 caracteres
         phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de un toot
         scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionas el alcance de nivel mas alto, no necesitas seleccionar las individuales.
+        setting_aggregate_reblogs: No mostrar nuevos retoots para los toots que han sido recientemente retooteados (sólo afecta a los retoots recibidos recientemente)
+        setting_default_sensitive: El contenido multimedia sensible está oculto por defecto y puede ser mostrado con un click
+        setting_display_media_default: Ocultar contenido multimedia marcado como sensible
+        setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia
+        setting_display_media_show_all: Mostrar siempre contenido multimedia marcado como sensible
         setting_hide_network: A quién sigues y quién te sigue no será mostrado en tu perfil
         setting_noindex: Afecta a tu perfil público y páginas de estado
         setting_show_application: La aplicación que utiliza usted para publicar toots se mostrará en la vista detallada de sus toots
+        setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles
+        setting_use_pending_items: Ocultar nuevos estados detrás de un clic en lugar de desplazar automáticamente el feed
+        username: Tu nombre de usuario será único en %{domain}
         whole_word: Cuando la palabra clave o frase es solo alfanumérica, solo será aplicado si concuerda con toda la palabra
+      domain_allow:
+        domain: Este dominio podrá obtener datos de este servidor y los datos entrantes serán procesados y archivados
+      featured_tag:
+        name: 'Puede que quieras usar uno de estos:'
+      form_challenge:
+        current_password: Estás entrando en un área segura
       imports:
         data: Archivo CSV exportado desde otra instancia de Mastodon
       invite_request:
         text: Esto nos ayudará a revisar su aplicación
       sessions:
         otp: 'Introduce el código de autenticación de dos factores geberado por tu aplicación de teléfono o usa uno de tus códigos de recuperación:'
+      tag:
+        name: Sólo se puede cambiar el cajón de las letras, por ejemplo, para que sea más legible
       user:
         chosen_languages: Cuando se marca, solo se mostrarán los toots en los idiomas seleccionados en los timelines públicos
     labels:
@@ -39,15 +66,26 @@ es:
         fields:
           name: Etiqueta
           value: Contenido
+      account_alias:
+        acct: Maneja la cuenta antigua
+      account_migration:
+        acct: Maneja la cuenta nueva
+      account_warning_preset:
+        text: Texto predefinido
       admin_account_action:
+        include_statuses: Incluir en el correo electrónico a los toots denunciados
         send_email_notification: Notificar al usuario por correo electrónico
         text: Aviso personalizado
         type: Acción
         types:
           disable: Deshabilitar
+          none: No hacer nada
           silence: Silenciar
+          suspend: Suspender y eliminar de forma irreversible la información de la cuenta
+        warning_preset_id: Usar un aviso predeterminado
       defaults:
         autofollow: Invitar a seguir tu cuenta
+        avatar: Avatar
         bot: Esta es una cuenta bot
         chosen_languages: Filtrar idiomas
         confirm_new_password: Confirmar nueva contraseña
@@ -55,6 +93,7 @@ es:
         context: Filtrar contextos
         current_password: Contraseña actual
         data: Información
+        discoverable: Listar esta cuenta en el directorio
         display_name: Nombre para mostrar
         email: Dirección de correo electrónico
         expires_in: Expirar tras
@@ -70,28 +109,42 @@ es:
         otp_attempt: Código de dos factores
         password: Contraseña
         phrase: Palabra clave o frase
+        setting_advanced_layout: Habilitar interfaz web avanzada
+        setting_aggregate_reblogs: Agrupar retoots en las líneas de tiempo
         setting_auto_play_gif: Reproducir automáticamente los GIFs animados
         setting_boost_modal: Mostrar ventana de confirmación antes de un Retoot
         setting_default_language: Idioma de publicación
         setting_default_privacy: Privacidad de publicaciones
         setting_default_sensitive: Marcar siempre imágenes como sensibles
         setting_delete_modal: Mostrar diálogo de confirmación antes de borrar un toot
+        setting_display_media: Visualización multimedia
+        setting_display_media_default: Por defecto
+        setting_display_media_hide_all: Ocultar todo
+        setting_display_media_show_all: Mostrar todo
+        setting_expand_spoilers: Siempre expandir los toots marcados con advertencias de contenido
         setting_hide_network: Ocultar tu red
         setting_noindex: Excluirse del indexado de motores de búsqueda
         setting_reduce_motion: Reducir el movimiento de las animaciones
         setting_show_application: Mostrar aplicación usada para publicar toots
         setting_system_font_ui: Utilizar la tipografía por defecto del sistema
         setting_theme: Tema del sitio
+        setting_trends: Mostrar las tendencias de hoy
         setting_unfollow_modal: Mostrar diálogo de confirmación antes de dejar de seguir a alguien
+        setting_use_blurhash: Mostrar gradientes coloridos para contenido multimedia oculto
+        setting_use_pending_items: Modo lento
         severity: Severidad
         type: Importar tipo
         username: Nombre de usuario
         username_or_email: Usuario o Email
         whole_word: Toda la palabra
+      featured_tag:
+        name: Etiqueta
       interactions:
         must_be_follower: Bloquear notificaciones de personas que no te siguen
         must_be_following: Bloquear notificaciones de personas que no sigues
         must_be_following_dm: Bloquear mensajes directos de la gente que no sigues
+      invite:
+        comment: Comentar
       invite_request:
         text: "¿Por qué quiere unirse usted?"
       notification_emails:
@@ -103,8 +156,15 @@ es:
         pending_account: Enviar correo electrónico cuando una nueva cuenta necesita revisión
         reblog: Enviar correo electrónico cuando alguien comparta su publicación
         report: Enviar un correo cuando se envía un nuevo informe
+        trending_tag: Enviar correo electrónico cuando una etiqueta no revisada está de tendencia
+      tag:
+        listable: Permitir que esta etiqueta aparezca en las búsquedas y en el directorio del perfil
+        name: Etiqueta
+        trendable: Permitir que esta etiqueta aparezca bajo tendencias
+        usable: Permitir a los toots usar esta etiqueta
     'no': 'No'
     recommended: Recomendado
     required:
+      mark: "*"
       text: necesario
     'yes': Sí
diff --git a/config/locales/simple_form.et.yml b/config/locales/simple_form.et.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c52818a3541ee56164894edd37d7315dc6a92462
--- /dev/null
+++ b/config/locales/simple_form.et.yml
@@ -0,0 +1,152 @@
+---
+et:
+  simple_form:
+    hints:
+      account_warning_preset:
+        text: Te saate kasutada tuututuse süntaksi, näiteks URLe, silte ja mainimisi
+      admin_account_action:
+        send_email_notification: Konto omanik saab selgituse selle kohta, mis juhtus nende kontoga
+        text_html: Valikuline. Te saate kasutada tuututuse süntaksi. Te saate <a href="%{path}">lisada hoiatuste eelseadistusi</a> aega säästmiseks
+        type_html: Vali, mida teha kasutajaga <strong>%{acct}</strong>
+        warning_preset_id: Valikuline. Te saate ikka lisada mis tahes teksti eelseadistuse lõppu
+      defaults:
+        autofollow: Inimesed, kes loovad konto selle kutse läbi, automaatselt jälgivad Teid
+        avatar: PNG, GIF või JPG. Kõige rohkem %{size}. Vähendatakse %{dimensions} pikslini
+        bot: See konto teeb enamjaolt automatiseeritud tegevusi ja ei pruugi olla järelvalve all
+        context: Üks või mitu konteksti, mille vastu see filter peaks rakenduma
+        digest: Saadetakse ainult pärast pikka perioodi tegevusetust ja ainult siis, kui Teile on saadetud privaatseid sõnumeid
+        email: Teile saadetakse kinnituskiri e-posti teel
+        fields: Te saate oma profiilil tabelina kuvada kuni 4 asja
+        header: PNG, GIF või JPG. Kõige rohkem %{size}. Vähendatakse %{dimensions} pikslini
+        inbox_url: Kopeerige soovitud relee avalehe URL
+        irreversible: Filtreeritud tuututused kaovad taastamatult, isegi kui filter on hiljem eemaldatud
+        locale: Kasutajaliidese, e-kirjade ja push-teadete keel
+        locked: Nõuab käsitsi jälgijate kinnitamist
+        password: Sisestage vähemalt 8 tähemärki
+        phrase: Kattub olenemata tuututuse teksti suurtähtedest või sisuhoiatusest
+        scopes: Milliseid API-sid see rakendus tohib kasutada. Kui Te valite kõrgeima taseme, ei pea Te valima individuaalseid.
+        setting_aggregate_reblogs: Ära näita uusi upitusi tuututustele, mis on just hiljuti upitatud (ainult kehtib uutele upitusele)
+        setting_default_sensitive: Tundlik meedia on vaikimisi peidetud ning seda saab avada sellele klikkides
+        setting_display_media_default: Peida tundlikuks märgitud meedia
+        setting_display_media_hide_all: Alati peida kõik meedia
+        setting_display_media_show_all: Alati näita tundlikuks märgistatud meedia
+        setting_hide_network: Keda Te jälgite ja kes jägib Teid ei kuvata Teie profiilil
+        setting_noindex: Mõjutab Teie avalikku profiili ja staatuse lehekülgi
+        setting_show_application: Rakendus, mida kasutate tuututamiseks, kuvatakse tuututuste üksikasjade vaates
+        setting_use_blurhash: Värvid põhinevad peidetud visuaalidel, kuid hägustavad igasuguseid detaile
+        setting_use_pending_items: Peida ajajoone uuendused kliki taga selle asemel, et automaatselt kerida voogu
+        username: Teie kasutajanimi on %{domain}-il unikaalne
+        whole_word: Kui võtmesõna või fraas on ainult tähtnumbriline, rakendub see ainult siis, kui see kattub terve sõnaga
+      domain_allow:
+        domain: See domeen saab tõmmata andmeid sellelt serverilt ning sissetulevad andmed sellelt domeenilt töödeldakse ning salvestatakse
+      featured_tag:
+        name: 'Äkki soovite kasutada mõnda neist:'
+      imports:
+        data: CSV fail eksporditi teisest Mastodoni serverist
+      invite_request:
+        text: See aitab meil üle vaadata Teie taotlust
+      sessions:
+        otp: 'Sisesta kahesammulise autentimise kood loodud Teie mobiilirakenduse poolt või sisesta mõni taastuskood:'
+      tag:
+        name: Te saate ainult muuta tähtede tõstetust, näiteks selleks, et muuta seda rohkem loetavaks
+      user:
+        chosen_languages: Kui valitud, ainult valitud keeltes tuututused näidatakse avalikes ajajoontes
+    labels:
+      account:
+        fields:
+          name: Nimetus
+          value: Sisu
+      account_warning_preset:
+        text: Eelseadistatud tekst
+      admin_account_action:
+        send_email_notification: Teavita kasutajat e-posti teel
+        text: Hoiatus
+        type: Tegevus
+        types:
+          disable: Keela
+          none: Ära tee midagi
+          silence: Vaigista
+          suspend: Peata ja taastamatult kustuta konto andmed
+        warning_preset_id: Kasuta hoiatuse eelseadistust
+      defaults:
+        autofollow: Kutsu oma kontot jälgima
+        avatar: Profiilipilt
+        bot: See konto on robot
+        chosen_languages: Filtreeri keeli
+        confirm_new_password: Kinnita uus salasõna
+        confirm_password: Kinnita salasõna
+        context: Filtreeri kontekste
+        current_password: Kehtiv salasõna
+        data: Andmed
+        discoverable: Lisage see konto kataloogi
+        display_name: Kuvanimi
+        email: E-posti aadress
+        expires_in: Aegu pärast
+        fields: Profiili metaandmed
+        header: Päis
+        inbox_url: Relee sisendkausta URL
+        irreversible: Kustuta selle asemel, et peita
+        locale: Kasutajaliidese keel
+        locked: Lukusta konto
+        max_uses: Maksimum kasutajate arv
+        new_password: Uus salasõna
+        note: Elulugu
+        otp_attempt: Kahesammulise autentimise kood
+        password: Salasõna
+        phrase: Võtmesõna või fraas
+        setting_advanced_layout: Aktiveeri arenenud veebiliides
+        setting_aggregate_reblogs: Grupita upitused ajajoontes
+        setting_auto_play_gif: Mängi GIF-e automaatselt
+        setting_boost_modal: Näita kinnitusdialoogi enne upitamist
+        setting_default_language: Postituse keel
+        setting_default_privacy: Postituse privaatsus
+        setting_default_sensitive: Alati märgista meedia tundlikuks
+        setting_delete_modal: Näita kinnitusdialoogi enne tuututuse kustutamist
+        setting_display_media: Meedia kuvarežiim
+        setting_display_media_default: Vaikimisi
+        setting_display_media_hide_all: Peida kõik
+        setting_display_media_show_all: Kuva kõik
+        setting_expand_spoilers: Alati laienda sisuhoiatustega tuututused
+        setting_hide_network: Peida oma võrk
+        setting_noindex: Keeldu otsingumootorite indekseerimistest
+        setting_reduce_motion: Vähenda liikumist animatsioonides
+        setting_show_application: Avalikusta tuututuste saatmisel kasutatud rakendused
+        setting_system_font_ui: Kasuta süsteemi vaikefonti
+        setting_theme: Saidi teema
+        setting_trends: Näita tänaseid trende
+        setting_unfollow_modal: Näita kinnitusdialoogi enne jälgimise eemaldamist
+        setting_use_blurhash: Näita peidetud meedia asemel värvilist pilti
+        setting_use_pending_items: Aeglane režiim
+        severity: Tõsidus
+        type: Impordi tüüp
+        username: Kasutajanimi
+        username_or_email: Kasutajanimi või e-post
+        whole_word: Terve sõna
+      featured_tag:
+        name: Silt
+      interactions:
+        must_be_follower: Keela teavitused mittejälgijatelt
+        must_be_following: Keela teavitused kasutajatelt, keda sa ei jälgi
+        must_be_following_dm: Keela privaatsõnumid kasutajatelt, keda sa ei jälgi
+      invite_request:
+        text: Miks Te soovite liituda?
+      notification_emails:
+        digest: Saada ülevaatlike e-kirju
+        favourite: Saada e-kiri, kui keegi lisab teie staatuse lemmikuks
+        follow: Saada e-kiri, kui keegi alustab Teie jälgimist
+        follow_request: Saada e-kiri, kui keegi soovib Teid jälgida
+        mention: Saada e-kiri, kui keegi mainib Teid
+        pending_account: Saada e-kiri, kui uus konto vajab ülevaatlust
+        reblog: Saada e-kiri, kui keegi upitab Teie staatust
+        report: Saada e-kiri, kui esitatud on uus teavitus
+        trending_tag: Saada e-kiri, kui ülevaatamata silt trendib
+      tag:
+        listable: Luba sellel sildil ilmuda profiilide kataloogis
+        trendable: Luba sellel sildil trendida
+        usable: Luba tuututustel seda silti kasutada
+    'no': Ei
+    recommended: Soovituslik
+    required:
+      mark: "*"
+      text: kohustuslik
+    'yes': Jah
diff --git a/config/locales/simple_form.eu.yml b/config/locales/simple_form.eu.yml
index acd5fd6d98b41cda86f79ffec92e88dbac1ced70..471577d79af65ad38e983d85f9ed988425f7acaf 100644
--- a/config/locales/simple_form.eu.yml
+++ b/config/locales/simple_form.eu.yml
@@ -2,6 +2,8 @@
 eu:
   simple_form:
     hints:
+      account_alias:
+        acct: Zehaztu migrazioaren jatorri den kontuaren erabiltzailea@domeinua
       account_warning_preset:
         text: Toot sintaxia erabili dezakezu, URLak, traolak eta aipamenak
       admin_account_action:
@@ -14,8 +16,9 @@ eu:
         avatar: PNG, GIF edo JPG. Gehienez %{size}. %{dimensions}px neurrira eskalatuko da
         bot: Kontu honek nagusiki automatizatutako ekintzak burutzen ditu eta agian ez du inork monitorizatzen
         context: Iragazkia aplikatzeko testuinguru bat edo batzuk
-        digest: Soilik jarduerarik gabeko epe luze bat eta gero, eta soilik ez zeudela mezu pertsonalen bat jaso baduzu
-        discoverable_html: <a href="%{path}" target="_blank">Direktorioa</a>k Jendea interesen eta jardueraren arabera aurkitzea ahalbidetzen du. Gutxienez %{min_followers} jarraitzaile behar dira bertan agertzeko
+        current_password: Segurtasunagatik sartu uneko kontuaren pasahitza
+        current_username: Berresteko sartu uneko kontuaren erabiltzaile-izena
+        digest: Jarduerarik gabeko epe luze bat eta gero mezu pertsonalen bat jaso baduzu, besterik ez
         email: Baieztapen e-mail bat bidaliko zaizu
         fields: 4 elementu bistaratu ditzakezu taula batean zure profilean
         header: PNG, GIF edo JPG. Gehienez %{size}. %{dimensions}px eskalara txikituko da
@@ -26,7 +29,7 @@ eu:
         password: Erabili 8 karaktere gutxienez
         phrase: Bat egingo du Maiuskula/minuskula kontuan hartu gabe eta edukiaren abisua kontuan hartu gabe
         scopes: Zeintzuk API atzitu ditzakeen aplikazioak. Goi mailako arloa aukeratzen baduzu, ez dituzu azpikoak aukeratu behar.
-        setting_aggregate_reblogs: Ez erakutsi buktzada berriak berriki bultzada jaso duten tootentzat (berriki jasotako bultzadei eragiten die besterik ez)
+        setting_aggregate_reblogs: Ez erakutsi bultzada berriak berriki bultzada jaso duten toot-entzat (berriki jasotako bultzadei eragiten die bakarrik)
         setting_default_sensitive: Multimedia hunkigarria lehenetsita ezkutatzen da, eta sakatuz ikusi daiteke
         setting_display_media_default: Ezkutatu hunkigarri gisa markatutako multimedia
         setting_display_media_hide_all: Ezkutatu multimedia guztia beti
@@ -34,10 +37,13 @@ eu:
         setting_hide_network: Nor jarraitzen duzun eta nork jarraitzen zaituen ez da bistaratuko zure profilean
         setting_noindex: Zure profil publiko eta Toot-en orrietan eragina du
         setting_show_application: Tootak bidaltzeko erabiltzen duzun aplikazioa zure tooten ikuspegi xehetsuan bistaratuko da
+        setting_use_blurhash: Gradienteak ezkutatutakoaren koloreetan oinarritzen dira, baina xehetasunak ezkutatzen dituzte
         username: Zure erabiltzaile-izena bakana izango da %{domain} domeinuan
         whole_word: Hitz eta esaldi gakoa alfanumerikoa denean, hitz osoarekin bat datorrenean besterik ez da aplikatuko
       featured_tag:
         name: 'Hauetakoren bat erabili zenezake:'
+      form_challenge:
+        current_password: Zonalde seguruan sartzen ari zara
       imports:
         data: Beste Mastodon zerbitzari batetik esportatutako CSV fitxategia
       invite_request:
@@ -51,9 +57,14 @@ eu:
         fields:
           name: Etiketa
           value: Edukia
+      account_alias:
+        acct: Kontu zaharraren helbidea
+      account_migration:
+        acct: Kontu berriaren helbidea
       account_warning_preset:
         text: Aurrez ezarritako testua
       admin_account_action:
+        include_statuses: Txertatu salatutako toot-ak e-mailean
         send_email_notification: Jakinarazi erabiltzaileari e-mail bidez
         text: Abisu pertsonalizatua
         type: Ekintza
@@ -108,7 +119,10 @@ eu:
         setting_show_application: Utzi agerian tootak bidaltzeko erabilitako aplikazioa
         setting_system_font_ui: Erabili sistemako tipografia lehenetsia
         setting_theme: Gunearen gaia
+        setting_trends: Erakutsi gaurko joerak
         setting_unfollow_modal: Erakutsi baieztapen elkarrizketa-koadroa inor jarraitzeari utzi aurretik
+        setting_use_blurhash: Erakutsi gradiente koloretsuak ezkutatutako multimediaren ordez
+        setting_use_pending_items: Modu geldoa
         severity: Larritasuna
         type: Inportazio mota
         username: Erabiltzaile-izena
@@ -118,8 +132,10 @@ eu:
         name: Traola
       interactions:
         must_be_follower: Blokeatu jarraitzaile ez direnen jakinarazpenak
-        must_be_following: Blokeatu zuk jarraitzen ez dituzunen jakinarazpenak
-        must_be_following_dm: Blokeatu zuk jarraitzen ez dituzunen mezu zuzenak
+        must_be_following: Blokeatu zuk jarraitzen ez dituzu horien jakinarazpenak
+        must_be_following_dm: Blokeatu zuk jarraitzen ez dituzun horien mezu zuzenak
+      invite:
+        comment: Iruzkina
       invite_request:
         text: Zergatik elkartu nahi duzu?
       notification_emails:
@@ -131,6 +147,12 @@ eu:
         pending_account: Bidali e-mail bat kontu bat berrikusi behar denean
         reblog: Bidali e-mail bat norbaitek zure mezuari bultzada ematen badio
         report: Bidali e-maila txosten berri bat aurkezten denean
+        trending_tag: Bidali e-mail bat errebisatu gabeko traola bat joeran dagoenean
+      tag:
+        listable: Baimendu traola hau bilaketetan agertzea eta profilen direktorioan
+        name: Traola
+        trendable: Baimendu traola hau joeretan agertzea
+        usable: Baimendu toot-ek traola hau erabiltzea
     'no': Ez
     recommended: Aholkatua
     required:
diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml
index 7d2dbd8a94bc9785b5b1b64badf0c5cb6eb9abec..70ad0c66ab20879bc86a5ba1484acd833c0d9023 100644
--- a/config/locales/simple_form.fa.yml
+++ b/config/locales/simple_form.fa.yml
@@ -2,9 +2,14 @@
 fa:
   simple_form:
     hints:
+      account_alias:
+        acct: نشانی username@domain را برای حسابی که می‌خواهید از آن منتقل شوید بنویسید
+      account_migration:
+        acct: نشانی username@domain را برای حسابی که می‌خواهید به آن منتقل شوید بنویسید
       account_warning_preset:
         text: می‌توانید مانند بوق‌های معمولی کاربران دیگر را نام ببرید یا پیوند و برچسب بگذارید
       admin_account_action:
+        include_statuses: این کاربر خواهد دید که کدام بوق او موجب اقدام مدیریتی یا هشدار شده است
         send_email_notification: توضیحی که کاربر می‌بینید که برای حسابش چه رخ داده است
         text_html: اختیاری. می‌توانید مثل بوق‌های معمولی بنویسید. می‌توانید برای صرفه‌جویی در زمان <a href="%{path}">هشدارهای ازپیش‌آماده بیفزایید</a>
         type_html: با حساب <strong>%{acct}</strong> می‌خواهید چه کار کنید؟‌
@@ -14,8 +19,10 @@ fa:
         avatar: یکی از قالب‌های PNG یا  GIF یا JPG. بیشترین اندازه %{size}. تصویر به اندازهٔ %{dimensions} پیکسل تبدیل خواهد شد
         bot: این حساب بیشتر به طور خودکار فعالیت می‌کند و نظارت پیوسته‌ای روی آن وجود ندارد
         context: یک یا چند زمینه که فیلتر باید در آن‌ها اعمال شود
+        current_password: به دلایل امنیتی لطفاً رمز این حساب را وارد کنید
+        current_username: برای تأیید، لطفاً نام کاربری حساب فعلی را وارد کنید
         digest: تنها وقتی فرستاده می‌شود که مدتی طولانی فعالیتی نداشته باشید و در این مدت برای شما پیغام خصوصی‌ای نوشته شده باشد
-        discoverable_html: با <a href="%{path}" target="_blank">فهرست گزیدهٔ کاربران</a> مردم می‌توانند حساب‌های این سرور را بر اساس علاقه‌مندی‌ها و فعالیت‌شان پیدا کنند. هر حساب دست‌کم باید %{min_followers} پیگیر داشته باشد
+        discoverable: فهرست گزیدهٔ کاربران این سرور راه دیگری است که با آن حساب شما می‌تواند مخاطبان بیشتری پیدا کند
         email: به شما ایمیل تأییدی فرستاده خواهد شد
         fields: شما می‌توانید تا چهار مورد را در یک جدول در نمایهٔ خود نمایش دهید
         header: یکی از قالب‌های PNG یا  GIF یا JPG. بیشترین اندازه %{size}. تصویر به اندازهٔ %{dimensions} پیکسل تبدیل خواهد شد
@@ -34,16 +41,24 @@ fa:
         setting_hide_network: فهرست پیگیران شما و فهرست کسانی که شما پی می‌گیرید روی نمایهٔ شما دیده نخواهد شد
         setting_noindex: روی نمایهٔ عمومی و صفحهٔ نوشته‌های شما تأثیر می‌گذارد
         setting_show_application: برنامه‌ای که به کمک آن بوق می‌زنید، در جزئیات بوق شما نمایش خواهد یافت
+        setting_use_blurhash: سایه‌ها بر اساس رنگ‌های به‌کاررفته در تصویر پنهان‌شده ساخته می‌شوند ولی جزئیات تصویر در آن‌ها آشکار نیست
+        setting_use_pending_items: به جای پیش‌رفتن خودکار در فهرست، به‌روزرسانی فهرست نوشته‌ها را پشت یک کلیک پنهان کن
         username: نام کاربری شما روی %{domain} یکتا خواهد بود
         whole_word: اگر کلیدواژه فقط دارای حروف و اعداد باشد، تنها وقتی پیدا می‌شود که با کل یک واژه در متن منطبق باشد، نه با بخشی از یک واژه
+      domain_allow:
+        domain: این دامین خواهد توانست داده‌ها از این سرور را دریافت کند و داده‌های از این دامین در این‌جا پردازش و ذخیره خواهند شد
       featured_tag:
         name: 'شاید بخواهید چنین چیزهایی را به کار ببرید:'
+      form_challenge:
+        current_password: شما در حال ورود به یک منطقهٔ‌ حفاظت‌شده هستید
       imports:
         data: پروندهٔ CSV که از سرور ماستدون دیگری برون‌سپاری شده
       invite_request:
         text: این برای بررسی درخواست شما به ما کمک خواهد کرد
       sessions:
         otp: 'کد تأیید دومرحله‌ای که اپ روی تلفن شما ساخته را وارد کنید یا یکی از کدهای بازیابی را به کار ببرید:'
+      tag:
+        name: شما تنها می‌توانید بزرگی و کوچکی حروف را تغییر دهید تا مثلاً آن را خواناتر کنید
       user:
         chosen_languages: اگر انتخاب کنید، تنها نوشته‌هایی که به زبان‌های برگزیدهٔ شما نوشته شده‌اند در فهرست نوشته‌های عمومی نشان داده می‌شوند
     labels:
@@ -51,9 +66,14 @@ fa:
         fields:
           name: برچسب
           value: محتوا
+      account_alias:
+        acct: نشانی حساب قدیمی
+      account_migration:
+        acct: نشانی حساب تازه
       account_warning_preset:
         text: متن از پیش آماده‌شده
       admin_account_action:
+        include_statuses: بوق‌های گزارش‌شده را در ایمیل بگنجان
         send_email_notification: اطلاع‌رسانی به کاربر از راه ایمیل
         text: هشدار موردی
         type: نوع کنش
@@ -108,7 +128,10 @@ fa:
         setting_show_application: برنامه‌ای که به کار می‌برید آشکار شود
         setting_system_font_ui: به‌کاربردن قلم پیش‌فرض سیستم
         setting_theme: تم سایت
+        setting_trends: نشان‌دادن موضوعات پرطرفدار روز
         setting_unfollow_modal: نمایش پیغام تأیید پیش از لغو پیگیری دیگران
+        setting_use_blurhash: به جای تصویرهای پنهان‌شده، سایه‌های رنگی نشان بده
+        setting_use_pending_items: حالت آهسته
         severity: شدت
         type: نوع درون‌ریزی
         username: نام کاربری (لاتین)
@@ -120,6 +143,8 @@ fa:
         must_be_follower: مسدودکردن اعلان‌های همه به جز پیگیران
         must_be_following: مسدودکردن اعلان‌های کسانی که شما پی نمی‌گیرید
         must_be_following_dm: مسدودکردن پیغام‌های خصوصی کسانی که شما پی نمی‌گیرید
+      invite:
+        comment: توضیح
       invite_request:
         text: چرا می‌خواهید عضو شوید؟
       notification_emails:
@@ -131,6 +156,12 @@ fa:
         pending_account: وقتی حساب تازه‌ای نیاز به بازبینی داشت ایمیل بفرست
         reblog: وقتی کسی نوشتهٔ شما را بازبوقید ایمیل بفرست
         report: وقتی گزارش تازه‌ای فرستاده شد ایمیل بفرست
+        trending_tag: وقتی یک برچسب بازبینی‌نشده پرطرفدار شد ایمیل بفرست
+      tag:
+        listable: بگذارید که این برچسب در جستجوها و در فهرست گزیدهٔ کاربران نمایش داده شود
+        name: برچسب
+        trendable: بگذارید که این برچسب در موضوعات پرطرفدار دیده شود
+        usable: بگذارید که این برچسب در بوق‌ها به کار بروند
     'no': خیر
     recommended: توصیه می‌شود
     required:
diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml
index 6b3aa9bfbaca910c6bbc3b951e2823aa0e679ac1..cff0db7191001733aef7258ac2aa451e22fed76e 100644
--- a/config/locales/simple_form.fr.yml
+++ b/config/locales/simple_form.fr.yml
@@ -2,9 +2,14 @@
 fr:
   simple_form:
     hints:
+      account_alias:
+        acct: Spécifiez le nom d’utilisateur@domaine du compte que vous souhaitez migrer
+      account_migration:
+        acct: Spécifiez le nom d’utilisateur@domaine du compte vers lequel vous souhaitez déménager
       account_warning_preset:
         text: Vous pouvez utiliser la syntaxe des pouets, comme les URLs, les hashtags et les mentions
       admin_account_action:
+        include_statuses: L’utilisateur·rice verra quels sont les pouets qui ont provoqué l’action de modération ou l’avertissement
         send_email_notification: L’utilisateur recevra une explication de ce qu’il s’est passé avec son compte
         text_html: Optionnel. Vous pouvez utilisez la syntaxe des pouets. Vous pouvez <a href="%{path}">ajouter des présélections d’attention</a> pour économiser du temps
         type_html: Choisir que faire avec <strong>%{acct}</strong>
@@ -14,8 +19,10 @@ fr:
         avatar: Au format PNG, GIF ou JPG. %{size} maximum. Sera réduit à %{dimensions}px
         bot: Ce compte exécute principalement des actions automatisées et pourrait ne pas être surveillé
         context: Un ou plusieurs contextes où le filtre devrait s’appliquer
+        current_password: Pour des raisons de sécurité, veuillez saisir le mot de passe du compte courant
+        current_username: Pour confirmer, veuillez saisir le nom d'utilisateur du compte courant
         digest: Uniquement envoyé après une longue période d’inactivité et uniquement si vous avez reçu des messages personnels pendant votre absence
-        discoverable_html: L’<a href="%{path}" target="_blank">annuaire</a> permet aux gens de trouver des comptes en se basant sur les intérêts et les activités. Nécessite au moins %{min_followers} abonnés
+        discoverable: L’annuaire des profils est une autre façon pour votre compte d’atteindre une plus grand audience
         email: Vous recevrez un courriel de confirmation
         fields: Vous pouvez avoir jusqu’à 4 éléments affichés en tant que tableau sur votre profil
         header: Au format PNG, GIF ou JPG. %{size} maximum. Sera réduit à %{dimensions}px
@@ -34,16 +41,24 @@ fr:
         setting_hide_network: Ceux que vous suivez et ceux qui vous suivent ne seront pas affichés sur votre profil
         setting_noindex: Affecte votre profil public ainsi que vos statuts
         setting_show_application: Le nom de l’application que vous utilisez afin d’envoyer des pouets sera affiché dans la vue détaillée de ceux-ci
+        setting_use_blurhash: Les dégradés sont basés sur les couleurs des visuels cachés mais ne divulgent pas les détails
+        setting_use_pending_items: Masquer les mises à jour chronologiques derrière un clic au lieu de les montrer automatiquement en faisant défiler le flux d'actualités
         username: Votre nom d’utilisateur sera unique sur %{domain}
         whole_word: Lorsque le mot-clef ou la phrase-clef est uniquement alphanumérique, ça sera uniquement appliqué s’il correspond au mot entier
+      domain_allow:
+        domain: Ce domaine pourra récupérer des données de ce serveur et les données entrantes seront traitées et stockées
       featured_tag:
         name: 'Vous pourriez vouloir utiliser l’un d’entre eux :'
+      form_challenge:
+        current_password: Vous entrez une zone sécurisée
       imports:
         data: Un fichier CSV généré par un autre serveur de Mastodon
       invite_request:
         text: Cela nous aidera à considérer votre demande
       sessions:
         otp: 'Entrez le code d’authentification à deux facteurs généré par l’application de votre téléphone ou utilisez un de vos codes de récupération :'
+      tag:
+        name: Vous ne pouvez modifier que la casse des lettres, par exemple, pour le rendre plus lisible
       user:
         chosen_languages: Lorsque coché, seuls les pouets dans les langues sélectionnées seront affichés sur les fils publics
     labels:
@@ -51,9 +66,14 @@ fr:
         fields:
           name: Étiquette
           value: Contenu
+      account_alias:
+        acct: L’identifiant de l’ancien compte
+      account_migration:
+        acct: L’identifiant du nouveau compte
       account_warning_preset:
         text: Texte de présélection
       admin_account_action:
+        include_statuses: Inclure les pouets signalés dans le courriel
         send_email_notification: Notifier l’utilisateur par courriel
         text: Attention personnalisée
         type: Action
@@ -108,7 +128,10 @@ fr:
         setting_show_application: Dévoiler le nom de l’application utilisée pour envoyer des pouets
         setting_system_font_ui: Utiliser la police par défaut du système
         setting_theme: Thème du site
+        setting_trends: Afficher les tendances du jour
         setting_unfollow_modal: Afficher une fenêtre de confirmation avant de vous désabonner d’un compte
+        setting_use_blurhash: Afficher les dégradés colorés pour les médias cachés
+        setting_use_pending_items: Mode lent
         severity: Sévérité
         type: Type d’import
         username: Identifiant
@@ -120,6 +143,8 @@ fr:
         must_be_follower: Masquer les notifications des personnes qui ne vous suivent pas
         must_be_following: Masquer les notifications des personnes que vous ne suivez pas
         must_be_following_dm: Bloquer les messages directs des personnes que vous ne suivez pas
+      invite:
+        comment: Commentaire
       invite_request:
         text: Pourquoi voulez-vous vous inscrire ?
       notification_emails:
@@ -131,6 +156,12 @@ fr:
         pending_account: Envoyer un courriel lorsqu’un nouveau compte est en attente d’approbation
         reblog: Envoyer un courriel lorsque quelqu’un partage mes statuts
         report: Envoyer un courriel lorsqu’un nouveau rapport est soumis
+        trending_tag: Envoyer un e-mail quand un hashtag non contrôlé est en tendance actuelle
+      tag:
+        listable: Autoriser ce hashtag à apparaître dans les recherches et dans l’annuaire des profils
+        name: Hashtag
+        trendable: Autoriser ce hashtag à apparaître sous les tendances du jour
+        usable: Autoriser les toots à utiliser ce hashtag
     'no': Non
     recommended: Recommandé
     required:
diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml
index 22389051fbdd82c6c3353c8cb0b50be619c1d60c..61308bf48b7dc70f0ae0cdf81672e718f0f9fd39 100644
--- a/config/locales/simple_form.gl.yml
+++ b/config/locales/simple_form.gl.yml
@@ -2,9 +2,14 @@
 gl:
   simple_form:
     hints:
+      account_alias:
+        acct: Indica o usuaria@servidor da conta desde a cal queres migrar
+      account_migration:
+        acct: Indica o usuaria@servidor da conta a cal queres migrar
       account_warning_preset:
-        text: Vostede pode utilizar dar formato ao toot, como URLs, etiquetas e mencións
+        text: Pódeslle dar formato ao toot, como URLs, etiquetas e mencións
       admin_account_action:
+        include_statuses: A usuaria verá que toots causaron a acción da moderación ou aviso
         send_email_notification: A usuaria recibirá unha explicación sobre o que lle aconteceu a súa conta
         text_html: Optativo. Pode utilizar formato no toot. Pode <a href="%{path}">engadir avisos preestablecidos</a> para aforrar tempo
         type_html: Escolla que facer con <strong>%{acct}</strong>
@@ -14,8 +19,10 @@ gl:
         avatar: PNG, GIF ou JPG.  Máximo %{size}. Será reducida a %{dimensions}px
         bot: Esta conta realiza principalmente accións automatizadas e podería non estar monitorizada
         context: Un ou varios contextos onde se debería aplicar o filtro
+        current_password: Por razóns de seguridade, introduce o contrasinal da conta actual
+        current_username: Para confirmar, introduce o nome de usuaria da conta actual
         digest: Enviar só tras un longo período de inactividade e só si recibeu algunha mensaxe persoal na súa ausencia
-        discoverable_html: O <a href="%{path}" target="_blank">directorio</a> permite atopar contas en función de intereses e actividade. Require ter ao menos %{min_followers} seguidoras
+        discoverable: O directorio de perfil é outro xeito para que a túa conta alcance unha maior audiencia
         email: Enviaráselle un correo-e de confirmación
         fields: Pode ter ate 4 elementos no seu perfil mostrados como unha táboa
         header: PNG, GIF ou JPG. Máximo %{size}. Será reducida a %{dimensions}px
@@ -34,16 +41,24 @@ gl:
         setting_hide_network: Non se mostrará no seu perfil quen a segue e quen a está a seguir
         setting_noindex: Afecta ao seu perfil público e páxinas de estado
         setting_show_application: A aplicación que está a utilizar para enviar toots mostrarase na vista detallada do toot
+        setting_use_blurhash: Os gradientes toman as cores da imaxe oculta pero esborranchando todos os detalles
+        setting_use_pending_items: Ocultar as actualizacións da liña temporal tras un click no lugar de desprazar automáticamente os comentarios
         username: O seu nome de usuaria será único en %{domain}
         whole_word: Se a chave ou frase de paso é só alfanumérica, só se aplicará se concorda a palabra completa
+      domain_allow:
+        domain: Este dominio estará en disposición de obter datos desde este servidor e datos de entrada a el poderán ser procesados e gardados
       featured_tag:
         name: 'Podería utilizar algunha de estas:'
+      form_challenge:
+        current_password: Estás entrando nun área segura
       imports:
         data: Ficheiro CSV exportado desde outro servidor Mastodon
       invite_request:
         text: Esto axudaranos a revisar a súa aplicación
       sessions:
-        otp: 'Introduza o código de doble-factor xerado no aplicativo do seu móbil ou utilice un dos seus códigos de recuperación:'
+        otp: 'Introduza o código de dobre-factor creado pola aplicación do seu móbil ou utilice un dos seus códigos de recuperación:'
+      tag:
+        name: Só pode cambiar maiús/minúsculas, por exemplo, mellorar a lexibilidade
       user:
         chosen_languages: Se ten marca, só os toots nos idiomas seleccionados serán mostrados en liñas temporais públicas
     labels:
@@ -51,9 +66,14 @@ gl:
         fields:
           name: Etiqueta
           value: Contido
+      account_alias:
+        acct: Xestina a conta antiga
+      account_migration:
+        acct: Xestiona a nova conta
       account_warning_preset:
         text: Texto preestablecido
       admin_account_action:
+        include_statuses: Incluír toots reportados no correo
         send_email_notification: Notificar a usuaria por correo-e
         text: Aviso personalizado
         type: Acción
@@ -86,7 +106,7 @@ gl:
         max_uses: Número máximo de usos
         new_password: Novo contrasinal
         note: Sobre vostede
-        otp_attempt: Código de Doble-Factor
+        otp_attempt: Código de Dobre-Factor
         password: Contrasinal
         phrase: Palabra chave ou frase
         setting_advanced_layout: Activar interface web avanzada
@@ -108,7 +128,10 @@ gl:
         setting_show_application: Mostrar a aplicación utilizada para tootear
         setting_system_font_ui: Utilizar a tipografía por defecto do sistema
         setting_theme: Decorado da instancia
+        setting_trends: Mostrar as tendencias de hoxe
         setting_unfollow_modal: Solicitar confirmación antes de deixar de seguir alguén
+        setting_use_blurhash: Mostrar gradientes coloridos para medios ocultos
+        setting_use_pending_items: Modo lento
         severity: Severidade
         type: Tipo de importación
         username: Nome de usuaria
@@ -120,6 +143,8 @@ gl:
         must_be_follower: Bloquear as notificacións de non-seguidoras
         must_be_following: Bloquea as notificacións de personas que non segue
         must_be_following_dm: Bloquea as mensaxes directas de personas que non segue
+      invite:
+        comment: Comentar
       invite_request:
         text: Por que quere unirse?
       notification_emails:
@@ -131,6 +156,12 @@ gl:
         pending_account: Enviar correo-e cando unha nova conta precisa revisión
         reblog: Enviar un correo cando alguén promociona a súa mensaxe
         report: Enviar un correo cando se envíe un novo informe
+        trending_tag: Enviar correo cando unha etiqueta non revisada é tendencia
+      tag:
+        listable: Permitir a esta etiqueta aparecer no directorio de perfil
+        name: Etiqueta
+        trendable: Permitir a esta etiqueta aparecer baixo Tendencias
+        usable: Permitir que os toots utilicen esta etiqueta
     'no': Non
     recommended: Recomendado
     required:
diff --git a/config/locales/simple_form.hu.yml b/config/locales/simple_form.hu.yml
index 7e5e4e79d51baee9a356caa77fc3c3450c7e3d14..de1830f5d57703ff0b99726cf6d6ca396ed579b9 100644
--- a/config/locales/simple_form.hu.yml
+++ b/config/locales/simple_form.hu.yml
@@ -2,9 +2,14 @@
 hu:
   simple_form:
     hints:
+      account_alias:
+        acct: Adjad meg annak a fióknak a felhasználóneve@domainjét, ahonnan át szeretnéd mozgatni
+      account_migration:
+        acct: Add meg a fióknév@domain fiókot, melybe költözni szeretnél
       account_warning_preset:
         text: Használhatod a tülkökben szokásos szintaxist, URL-eket, hashtageket, megemlítéseket
       admin_account_action:
+        include_statuses: A felhasználó látni fogja, melyik tülk okozta a moderációt vagy figyelmeztetést
         send_email_notification: A felhasználó magyarázatot kap arra, mi történt a fiókjával
         text_html: Opcionális. A tülk szintaxis használható. Egyszerűsítés végett létre is hozhatsz <a href="%{path}">figyelmeztetéseket</a>
         type_html: Megmondhatod, mi legyen vele <strong>%{acct}</strong>
@@ -14,8 +19,10 @@ hu:
         avatar: PNG, GIF vagy JPG. Maximum %{size}. Átméretezzük %{dimensions} pixelre
         bot: Ez a fiók automatikus műveleteket végez és valószínűleg nem figyeljük
         context: Kontextusok, ahol a szűrőnek működnie kell
+        current_password: Biztonsági okok miatt kérlek, írd be a jelenlegi fiók jelszavát
+        current_username: A jóváhagyáshoz írd be a jelenlegi fiók felhasználói nevét
         digest: Csak hosszú távollét esetén küldődik és csak ha személyes üzenetet kaptál távollétedben
-        discoverable_html: A <a href="%{path}" target="_blank">profilok</a> adatbázisa lehetővé teszi, hogy embereket közös érdeklődés vagy tevékenység alapján találj meg. Legalább %{min_followers} követőre van szükség az adatbázisba való bekerüléshez
+        discoverable: A profil adatbázis egy újabb mód, ahogyan a fiókod szélesebb tömegeket érhet el
         email: Kapsz egy megerősítő e-mailt
         fields: A profilodon legfeljebb 4 bejegyzés szerepelhet táblázatos formában
         header: PNG, GIF vagy JPG. Maximum %{size}. Átméretezzük %{dimensions} pixelre
@@ -34,16 +41,24 @@ hu:
         setting_hide_network: Nem látszik majd a profilodon, kik követnek és te kiket követsz
         setting_noindex: A nyilvános profilodra és a tülkjeidre vonatkozik
         setting_show_application: A tülkök részletes nézetében látszani fog, milyen alkalmazást használtál a tülköléshez
+        setting_use_blurhash: A kihomályosítás az eredeti képből történik, de minden részletet elrejt
+        setting_use_pending_items: Idővonal frissítése csak kattintásra automatikus görgetés helyett
         username: A felhasználói neved egyedi lesz a %{domain} domainen
         whole_word: Ha a kulcsszó alfanumerikus, csak akkor minősül majd találatnak, ha teljes szóra illeszkedik
+      domain_allow:
+        domain: Ez a domain adatot kérhet le a szerverünkről és az ettől érkező adatokat feldolgozzuk és mentjük
       featured_tag:
         name: 'Ezeket esetleg használhatod:'
+      form_challenge:
+        current_password: Beléptél egy biztonsági térben
       imports:
         data: Egy másik Mastodon szerverről exportált CSV fájl
       invite_request:
         text: Ez segít nekünk átnézni a jelentkezésedet
       sessions:
         otp: 'Add meg a telefonodon generált kétlépcsős azonosító kódodat vagy használd az egyik tartalék bejelentkező kódot:'
+      tag:
+        name: Csak a kis/nagybetűséget változtathatod meg, pl. hogy olvashatóbb legyen
       user:
         chosen_languages: Ha aktív, csak a kiválasztott nyelvű tülkök jelennek majd meg a nyilvános idővonalon
     labels:
@@ -51,9 +66,14 @@ hu:
         fields:
           name: Címke
           value: Tartalom
+      account_alias:
+        acct: Régi fiók kezelése
+      account_migration:
+        acct: Új fiók kezelése
       account_warning_preset:
         text: Figyelmeztető szöveg
       admin_account_action:
+        include_statuses: Helyezd az e-mailbe a jelentett tülköket
         send_email_notification: Figyelmeztessük a felhasználót e-mailben
         text: Egyedi figyelmeztetés
         type: Művelet
@@ -108,7 +128,10 @@ hu:
         setting_show_application: A tülkölésre használt alkalmazás feltüntetése
         setting_system_font_ui: Rendszer betűtípusának használata
         setting_theme: Megjelenítési sablon
+        setting_trends: Mai trend mutatása
         setting_unfollow_modal: Megerősítés kérése mielőtt abbahagyod valaki követését
+        setting_use_blurhash: Rejtett média helyett homály mutatása
+        setting_use_pending_items: Lassú mód
         severity: Súlyosság
         type: Importálás típusa
         username: Felhasználónév
@@ -120,6 +143,8 @@ hu:
         must_be_follower: Nem követőidtől érkező értesítések tiltása
         must_be_following: Nem követettjeidtől érkező értesítések tiltása
         must_be_following_dm: Nem követettjeidtől érkező üzenetek tiltása
+      invite:
+        comment: Hozzászólás
       invite_request:
         text: Miért akarsz csatlakozni?
       notification_emails:
@@ -131,6 +156,12 @@ hu:
         pending_account: E-mail küldése, ha új fiókot kell engedélyezni
         reblog: E-mail küldése, amikor valaki megtolja a tülködet
         report: E-mail küldése, ha új bejelentés érkezett
+        trending_tag: E-mail küldése, ha egy még nem látott hashtag trendi lett
+      tag:
+        listable: A hashtag megjelenhet a profiladatbázisban
+        name: Címke
+        trendable: A hashtag megjelenhet a trendek között
+        usable: Tülkök használhatják ezt a hashtaget
     'no': Nem
     recommended: Ajánlott
     required:
diff --git a/config/locales/simple_form.id.yml b/config/locales/simple_form.id.yml
index ba9fbb4e88180c16ef4253801127cb0c5b1ab9f2..54d10f21a66842ec1eb5e0d4d9135af8b014d902 100644
--- a/config/locales/simple_form.id.yml
+++ b/config/locales/simple_form.id.yml
@@ -2,42 +2,167 @@
 id:
   simple_form:
     hints:
+      account_alias:
+        acct: Tentukan namapengguna@domain akun yang ingin Anda pindah
+      account_migration:
+        acct: Tentukan namapengguna@domain akun yang ingin Anda pindah
+      account_warning_preset:
+        text: Anda dapat memakai sintaks toot, seperti URL, tagar, dan sebutan
+      admin_account_action:
+        include_statuses: Pengguna dapat melihat toot mana yang menyebabkan aksi atau peringatan moderasi
+        send_email_notification: Pengguna akan menerima penjelasan tentang apa yang terjadi pada akun mereka
+        text_html: Opsional. Anda dapat memakai sintaks toot. Anda dapat <a href="%{path}">menambahkan preset peringatan</a> untuk hemat waktu
+        type_html: Pilih apa yang perlu dilakukan dengan <strong>%{acct}</strong>
+        warning_preset_id: Opsional. Anda tetap dapat menambahkan teks kustom pada akhir preset
       defaults:
+        autofollow: Orang yang ingin mendaftar lewat undangan, otomatis mengikuti Anda
         avatar: PNG, GIF atau JPG. Maksimal %{size}. Ukuran dikecilkan menjadi %{dimensions}px
+        bot: Akun ini melakukan aksi otomatis dan mungkin tak termonitor
+        context: Satu atau lebih konteks tempat saringan perlu diterapkan
+        current_password: Untuk tujuan keamanan mohon masukkan kata sandi akun ini
+        current_username: Untuk konfirmasi, mohon masukkan nama pengguna akun ini
+        digest: Hanya kirim setelah lama tidak aktif dan hanya jika Anda menerima pesan personal atas absensi Anda
+        discoverable: Direktori profil adalah cara lain agar akun Anda menyentuh audiens yang lebih luas
+        email: Anda akan dikirimi surel konfirmasi
+        fields: Anda bisa memiliki hingga 4 item utk ditampilkan sebagai tabel di profil Anda
         header: PNG, GIF atau JPG. Maksimal %{size}. Ukuran dikecilkan menjadi %{dimensions}px
+        inbox_url: Salin URL dari halaman depan relai yang ingin Anda pakai
+        irreversible: Toot tersaring akan hilang permanen bahkan jika saringan dihapus kemudian
+        locale: Bahasa antar muka pengguna, surel, dan notifikasi dorong
         locked: Anda harus menerima permintaan pengikut secara manual dan setting privasi postingan akan diubah khusus untuk pengikut
+        password: Gunakan minimal 8 karakter
+        phrase: Akan dicocokkan terlepas dari luaran dalam teks atau peringatan konten dari toot
+        scopes: API mana yang diizinkan untuk diakses aplikasi. Jika Anda memilih cakupan level-atas, Anda tak perlu memilih yang individual.
+        setting_aggregate_reblogs: Jangan tampilkan boost baru untuk toot yang baru saja di-boost (hanya memengaruhi boost yang baru diterima)
+        setting_default_sensitive: Media sensitif disembunyikan secara bawaan dan akan ditampilkan dengan klik
+        setting_display_media_default: Sembunyikan media yang ditandai sebagai sensitif
+        setting_display_media_hide_all: Selalu sembunyikan semua media
+        setting_display_media_show_all: Selalu tampilkan media sensitif
+        setting_hide_network: Siapa yang Anda ikuti dan yang mengikuti Anda tak akan ditampilkan dalam profil
+        setting_noindex: Pengaruhi profil publik dan halaman status Anda
+        setting_show_application: Aplikasi yang Anda pakai untuk men-toot akan ditampilkan di tampilan detail toot
+        setting_use_blurhash: Gradien didasarkan pada warna visual yang tersembunyi tetapi mengaburkan setiap detail
+        setting_use_pending_items: Sembunyikan pembaruan linimasa di balik klik alih-alih bergulir secara otomatis
+        username: Nama pengguna Anda unik di %{domain}
+        whole_word: Ketika kata kunci/frasa hanya alfanumerik, maka itu hanya akan diterapkan jika cocok dengan semua kata
+      domain_allow:
+        domain: Domain ini dapat mengambil data dari server ini dan data yang diterima akan diproses dan disimpan
+      featured_tag:
+        name: 'Anda mungkin ingin pakai salah satu dari ini:'
+      form_challenge:
+        current_password: Anda memasuki area aman
       imports:
         data: File CSV yang diexpor dari server Mastodon lain
+      invite_request:
+        text: Ini akan membantu kami meninjau aplikasi Anda
       sessions:
         otp: Masukkan kode dua-faktor dari handphone atau gunakan kode pemulihan anda.
+      user:
+        chosen_languages: Ketika dicentang, hanya toot dalam bahasa yang dipilih yang akan ditampilkan di linimasa publik
     labels:
+      account:
+        fields:
+          name: Label
+          value: Konten
+      account_alias:
+        acct: Menangani akun lama
+      account_migration:
+        acct: Menangani akun baru
+      account_warning_preset:
+        text: Teks preset
+      admin_account_action:
+        include_statuses: Sertakan toot terlapor pada surel
+        send_email_notification: Beritahu pengguna per surel
+        text: Peringatan kustom
+        type: Aksi
+        types:
+          disable: Matikan
+          none: Biarkan
+          silence: Diamkan
+          suspend: Tangguhkan dan hapus data akun scr permanen
+        warning_preset_id: Gunakan preset peringatan
       defaults:
+        autofollow: Undang untuk mengikuti Anda
+        avatar: Avatar
+        bot: Ini akun bot
+        chosen_languages: Saring bahasa
         confirm_new_password: Konfirmasi kata sandi baru
         confirm_password: Konfirmasi kata sandi
+        context: Saring konteks
         current_password: Kata sandi sekarang
+        data: Data
+        discoverable: Daftarkan akun ini di direktori
         display_name: Nama yang ditampilkan
         email: Alamat e-mail
+        expires_in: Kedaluwarsa setelah
+        fields: Metadata profil
+        header: Tajuk
+        inbox_url: URL kotak relai
+        irreversible: Hapus alih-alih sembunyikan
         locale: Bahasa
         locked: Buat akun menjadi pribadi
+        max_uses: Jumlah maksimal penggunaan
         new_password: Password baru
+        note: Bio
         otp_attempt: Kode dua-faktor
         password: Kata sandi
+        phrase: Kata kunci atau frasa
+        setting_advanced_layout: Aktifkan antar muka web mahir
+        setting_aggregate_reblogs: Boost grup di linimasa
+        setting_auto_play_gif: Mainkan otomatis animasi GIF
         setting_boost_modal: Tampilkan dialog konfirmasi dialog sebelum boost
+        setting_default_language: Bahasa posting
         setting_default_privacy: Privasi postingan
+        setting_default_sensitive: Selalu tandai media sebagai sensitif
+        setting_delete_modal: Tampilkan dialog konfirmasi sebelum hapus toot
+        setting_display_media: Tampilan media
+        setting_display_media_default: Bawaan
+        setting_display_media_hide_all: Sembunyikan semua
+        setting_display_media_show_all: Tunjukkan semua
+        setting_expand_spoilers: Selalu bentangkan toot yang bertanda peringatan konten
+        setting_hide_network: Sembunyikan jaringan Anda
+        setting_noindex: Opt-out dari pengindeksan mesin pencari
+        setting_reduce_motion: Kurangi gerakan animasi
+        setting_show_application: Singkap aplikasi yang dipakai utk kirim toot
+        setting_system_font_ui: Gunakan fon bawaan sistem
+        setting_theme: Tema situs
+        setting_trends: Tampilkan tren hari ini
+        setting_unfollow_modal: Tampilkan konfirmasi dialog sebelum berhenti mengikuti seseorang
+        setting_use_blurhash: Tampilkan gradien penuh warna utk media tersembunyi
+        setting_use_pending_items: Mode pelan
         severity: Keparahan
         type: Tipe impor
         username: Nama pengguna
+        username_or_email: Nama pengguna atau Surel
+        whole_word: Seluruh kata
+      featured_tag:
+        name: Tagar
       interactions:
         must_be_follower: Blokir notifikasi dari non-pengikut
         must_be_following: Blokir notifikasi dari orang yang tidak anda ikuti
+        must_be_following_dm: Blokir pesan langsung dari orang yang tak Anda ikuti
+      invite:
+        comment: Komentar
+      invite_request:
+        text: Mengapa Anda ingin gabung?
       notification_emails:
         digest: Kirim email berisi rangkuman
         favourite: Kirim email saat seseorang menyukai status anda
         follow: Kirim email saat seseorang mengikuti anda
         follow_request: Kirim email saat seseorang meminta untuk mengikuti anda
         mention: Kirim email saat seseorang menyebut anda
+        pending_account: Kirim surel ketika akun baru perlu ditinjau
         reblog: Kirim email saat seseorang mem-boost status anda
+        report: Kirim surel ketika laporan baru dikirim
+        trending_tag: Kirim surel ketika tagar tak tertinjau jadi tren
+      tag:
+        listable: Izinkan tagar ini muncul di penelusuran dan di direktori profil
+        name: Tagar
+        trendable: Izinkan tagar ini muncul di bawah tren
+        usable: Izinkan toot memakai tagar ini
     'no': Tidak
+    recommended: Direkomendasikan
     required:
+      mark: "*"
       text: wajib
     'yes': Ya
diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml
index 377a552935ab6f5c670b3d35a47ea71f2c2ae952..21c86da9e11c379088b451dcc15498fc5e910411 100644
--- a/config/locales/simple_form.it.yml
+++ b/config/locales/simple_form.it.yml
@@ -5,6 +5,7 @@ it:
       account_warning_preset:
         text: Puoi usare la sintassi dei toot, come URL, hashtag e menzioni
       admin_account_action:
+        include_statuses: L'utente vedrà quali toot hanno causato l'azione di moderazione o l'avviso
         send_email_notification: L'utente riceverà una spiegazione di ciò che è successo con suo account
         text_html: Opzionale. Puoi usare la sintassi dei toot. Puoi <a href="%{path}">aggiungere avvisi preimpostati</a> per risparmiare tempo
         type_html: Decidi cosa fare con <strong>%{acct}</strong>
@@ -15,7 +16,6 @@ it:
         bot: Questo account esegue principalmente operazioni automatiche e potrebbe non essere tenuto sotto controllo da una persona
         context: Uno o più contesti nei quali il filtro dovrebbe essere applicato
         digest: Inviata solo dopo un lungo periodo di inattività e solo se hai ricevuto qualche messaggio personale in tua assenza
-        discoverable_html: La <a href="%{path}" target="_blank">directory</a> permette alle persone di trovare account in base a determinati interessi o attività. Richiede almeno %{min_followers} seguaci
         email: Ti manderemo una email di conferma
         fields: Puoi avere fino a 4 voci visualizzate come una tabella sul tuo profilo
         header: PNG, GIF o JPG. Al massimo %{size}. Verranno scalate a %{dimensions}px
@@ -34,8 +34,12 @@ it:
         setting_hide_network: Chi segui e chi segue te non saranno mostrati sul tuo profilo
         setting_noindex: Ha effetto sul tuo profilo pubblico e sulle pagine degli status
         setting_show_application: L'applicazione che usi per pubblicare i toot sarà mostrata nella vista di dettaglio dei tuoi toot
+        setting_use_blurhash: I gradienti sono basati sui colori delle immagini nascoste ma offuscano tutti i dettagli
+        setting_use_pending_items: Fare clic per mostrare i nuovi messaggi invece di aggiornare la timeline automaticamente
         username: Il tuo nome utente sarà unico su %{domain}
         whole_word: Quando la parola chiave o la frase è solo alfanumerica, si applica solo se corrisponde alla parola intera
+      domain_allow:
+        domain: Questo dominio potrà recuperare i dati da questo server e i dati in arrivo da esso verranno elaborati e memorizzati
       featured_tag:
         name: 'Eccone alcuni che potresti usare:'
       imports:
@@ -44,6 +48,8 @@ it:
         text: Questo ci aiuterà ad esaminare la tua richiesta
       sessions:
         otp: 'Inserisci il codice a due fattori generato dall''app del tuo telefono o usa uno dei codici di recupero:'
+      tag:
+        name: Puoi cambiare solo il minuscolo/maiuscolo delle lettere, ad esempio, per renderlo più leggibile
       user:
         chosen_languages: Quando una o più lingue sono contrassegnate, nelle timeline pubbliche vengono mostrati solo i toot nelle lingue selezionate
     labels:
@@ -54,6 +60,7 @@ it:
       account_warning_preset:
         text: Testo preimpostato
       admin_account_action:
+        include_statuses: Includi i toots segnalati nell'email
         send_email_notification: Informa l'utente via email
         text: Avviso personalizzato
         type: Azione
@@ -108,7 +115,10 @@ it:
         setting_show_application: Rendi pubblica l'applicazione usata per inviare i toot
         setting_system_font_ui: Usa il carattere predefinito del sistema
         setting_theme: Tema sito
+        setting_trends: Mostra tendenze di oggi
         setting_unfollow_modal: Chiedi conferma prima di smettere di seguire qualcuno
+        setting_use_blurhash: Mostra i gradienti colorati per i media nascosti
+        setting_use_pending_items: Modalità lenta
         severity: Severità
         type: Tipo importazione
         username: Nome utente
@@ -121,7 +131,7 @@ it:
         must_be_following: Blocca notifiche dalle persone che non segui
         must_be_following_dm: Blocca i messaggi diretti dalle persone che non segui
       invite_request:
-        text: Perchè vuoi unirti?
+        text: Perché vuoi iscriverti?
       notification_emails:
         digest: Invia email riassuntive
         favourite: Invia email quando segna come preferito al tuo stato
@@ -131,6 +141,12 @@ it:
         pending_account: Invia e-mail quando un nuovo account richiede l'approvazione
         reblog: Invia email quando qualcuno da un boost al tuo stato
         report: Manda una mail quando viene inviato un nuovo rapporto
+        trending_tag: Invia e-mail quando un hashtag non controllato è in tendenza
+      tag:
+        listable: Permetti a questo hashtag di apparire nella directory dei profili
+        name: Hashtag
+        trendable: Permetti a questo hashtag di apparire nelle tendenze
+        usable: Permetti ai toot di utilizzare questo hashtag
     'no': 'No'
     recommended: Consigliato
     required:
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
index ccc6c5ca1c73b2c767c14cc81ae29e5b219c6eff..b936c8e9a905da9e31f08014712a64f683466f28 100644
--- a/config/locales/simple_form.ja.yml
+++ b/config/locales/simple_form.ja.yml
@@ -2,9 +2,14 @@
 ja:
   simple_form:
     hints:
+      account_alias:
+        acct: 引っ越し元のユーザー名@ドメインを指定してください
+      account_migration:
+        acct: 引っ越し先のユーザー名@ドメインを指定してください
       account_warning_preset:
         text: URL、ハッシュタグ、メンションなど、投稿に用いる構文が使用できます
       admin_account_action:
+        include_statuses: ユーザーは取られた制限や警告の原因となったトゥートを確認できるようになります
         send_email_notification: ユーザーは自分のアカウントに何が起こったのか説明を受け取ります
         text_html: オプションです。投稿に用いる構文を使うことができます。簡略化のため<a href="%{path}">プリセット警告文を追加</a>することができます
         type_html: "<strong>%{acct}</strong>さんに対し、何を行うか選択してください"
@@ -14,8 +19,10 @@ ja:
         avatar: "%{size}までのPNG、GIF、JPGが利用可能です。%{dimensions}pxまで縮小されます"
         bot: このアカウントは主に自動で動作し、人が見ていない可能性があります
         context: フィルターを適用する対象 (複数選択可)
+        current_password: 現在のアカウントのパスワードを入力してください
+        current_username: 確認のため、現在のアカウントのユーザー名を入力してください
         digest: 長期間使用していない場合と不在時に返信を受けた場合のみ送信されます
-        discoverable_html: <a href="%{path}" target="_blank">ディレクトリ</a> は興味や活動をもとにアカウントを見つけることを可能にします。 掲載には %{min_followers} 人以上のフォロワーが必要です
+        discoverable: ディレクトリはあなたのアカウントをより多くの人に見つけてもらうためのひとつの手段です
         email: 確認のメールが送信されます
         fields: プロフィールに表として4つまでの項目を表示することができます
         header: "%{size}までのPNG、GIF、JPGが利用可能です。 %{dimensions}pxまで縮小されます"
@@ -34,16 +41,24 @@ ja:
         setting_hide_network: フォローとフォロワーの情報がプロフィールページで見られないようにします
         setting_noindex: 公開プロフィールおよび各投稿ページに影響します
         setting_show_application: トゥートするのに使用したアプリがトゥートの詳細ビューに表示されるようになります
+        setting_use_blurhash: ぼかしはメディアの色を元に生成されますが、細部は見えにくくなっています
+        setting_use_pending_items: 新着があってもタイムラインを自動的にスクロールしないようにします
         username: あなたのユーザー名は %{domain} の中で重複していない必要があります
         whole_word: キーワードまたはフレーズが英数字のみの場合、単語全体と一致する場合のみ適用されるようになります
+      domain_allow:
+        domain: 登録するとこのサーバーからデータを受信したり、このドメインから受信するデータを処理して保存できるようになります
       featured_tag:
         name: 'これらを使うといいかもしれません:'
+      form_challenge:
+        current_password: セキュリティ上重要なエリアにアクセスしています
       imports:
         data: 他の Mastodon サーバーからエクスポートしたCSVファイルを選択して下さい
       invite_request:
         text: このサーバーは現在承認制です。申請を承認する際に役立つメッセージを添えてください
       sessions:
         otp: '携帯電話のアプリで生成された二段階認証コードを入力するか、リカバリーコードを使用してください:'
+      tag:
+        name: 視認性向上などのためにアルファベット大文字小文字の変更のみ行うことができます
       user:
         chosen_languages: 選択すると、選択した言語のトゥートのみが公開タイムラインに表示されるようになります
     labels:
@@ -51,9 +66,14 @@ ja:
         fields:
           name: ラベル
           value: 内容
+      account_alias:
+        acct: 引っ越し元のユーザー ID
+      account_migration:
+        acct: 引っ越し先のユーザー ID
       account_warning_preset:
         text: プリセット警告文
       admin_account_action:
+        include_statuses: 通報されたトゥートをメールに含める
         send_email_notification: メールでユーザーに通知
         text: カスタム警告文
         type: アクション
@@ -108,7 +128,10 @@ ja:
         setting_show_application: 送信したアプリを開示する
         setting_system_font_ui: システムのデフォルトフォントを使う
         setting_theme: サイトテーマ
+        setting_trends: 本日のトレンドタグを表示する
         setting_unfollow_modal: フォローを解除する前に確認ダイアログを表示する
+        setting_use_blurhash: 非表示のメディアを色付きのぼかしで表示する
+        setting_use_pending_items: 手動更新モード
         severity: 重大性
         type: インポートする項目
         username: ユーザー名
@@ -120,6 +143,8 @@ ja:
         must_be_follower: フォロワー以外からの通知をブロック
         must_be_following: フォローしていないユーザーからの通知をブロック
         must_be_following_dm: フォローしていないユーザーからのダイレクトメッセージをブロック
+      invite:
+        comment: コメント
       invite_request:
         text: 意気込みをお聞かせください
       notification_emails:
@@ -131,6 +156,12 @@ ja:
         pending_account: 新しいアカウントの承認が必要な時にメールで通知する
         reblog: トゥートがブーストされた時にメールで通知する
         report: 通報を受けた時にメールで通知する
+        trending_tag: 未審査のハッシュタグが人気の時にメールで通知する
+      tag:
+        listable: 検索とディレクトリへの使用を許可する
+        name: ハッシュタグ
+        trendable: トレンドへの表示を許可する
+        usable: トゥートへの使用を許可する
     'no': いいえ
     recommended: おすすめ
     required:
diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml
index 8147cde5dbb8abd80fdd93ccb20cd9bd406b1972..28af44cbb3b8b1aa76678cdf3e988e691a497ac3 100644
--- a/config/locales/simple_form.ko.yml
+++ b/config/locales/simple_form.ko.yml
@@ -2,23 +2,30 @@
 ko:
   simple_form:
     hints:
+      account_alias:
+        acct: 이동하고자 하는 계정의 사용자이름@도메인을 설정하세요
+      account_migration:
+        acct: 이동하고자 하는 목적지 계정의 사용자이름@도메인을 설정하세요
       account_warning_preset:
         text: URL, 해시태그, 멘션과 같은 툿 문법을 사용할 수 있습니다
       admin_account_action:
+        include_statuses: 사용자는 어떤 툿에 대해 경고나 조치가 취해졌는지 볼 수 있게 됩니다
         send_email_notification: 유저는 어떤 일이 일어났는 지에 대한 설명을 받게 됩니다
         text_html: 선택사항. 툿 문법을 사용할 수 있습니다. <a href="%{path}">경고 틀을 추가</a>하여 시간을 절약할 수 있습니다
         type_html: "<strong>%{acct}</strong>에 대해 취할 행동 선택"
         warning_preset_id: 선택사항. 틀의 마지막에 임의의 텍스트를 추가 할 수 있습니다
       defaults:
         autofollow: 이 초대를 통해 가입하는 사람은 당신을 자동으로 팔로우 하게 됩니다
-        avatar: PNG, GIF 혹은 JPG. 최대 %{size}. %{dimensions}px로 다운스케일 될 것임
+        avatar: PNG, GIF 혹은 JPG. 최대 %{size}. %{dimensions}px로 축소 됨
         bot: 사람들에게 계정이 사람이 아님을 알립니다
         context: 필터를 적용 할 한 개 이상의 컨텍스트
+        current_password: 보안을 위해 현재 계정의 암호를 입력해주세요
+        current_username: 확인을 위해, 현재 계정의 사용자명을 입력해주세요
         digest: 오랫동안 활동하지 않았을 때 받은 멘션들에 대한 요약 받기
-        discoverable_html: <a href="%{path}" target="_blank">디렉토리</a> 는 사람들의 관심사와 활동에 관련 된 계정들을 찾을 수 있게 해 줍니다. 최소 %{min_followers}명의 팔로어가 필요합니다
+        discoverable: 프로필 디렉터리는 내 계정이 더 많은 관심을 갖게 할 수 있는 다른 방법입니다
         email: 당신은 확인 메일을 받게 됩니다
         fields: 당신의 프로파일에 최대 4개까지 표 형식으로 나타낼 수 있습니다
-        header: PNG, GIF 혹은 JPG. 최대 %{size}. %{dimensions}px로 다운스케일 됨
+        header: PNG, GIF 혹은 JPG. 최대 %{size}. %{dimensions}px로 축소 됨
         inbox_url: 사용 할 릴레이 서버의 프론트페이지에서 URL을 복사합니다
         irreversible: 필터링 된 툿은 나중에 필터가 사라지더라도 돌아오지 않게 됩니다
         locale: 유저 인터페이스, 이메일, 푸시 알림 언어
@@ -34,16 +41,24 @@ ko:
         setting_hide_network: 나를 팔로우 하는 사람들과 내가 팔로우 하는 사람들이 내 프로필에 표시되지 않게 합니다
         setting_noindex: 공개 프로필 및 각 툿페이지에 영향을 미칩니다
         setting_show_application: 당신이 툿을 작성하는데에 사용한 앱이 툿의 상세정보에 표시 됩니다
+        setting_use_blurhash: 그라디언트는 숨겨진 내용의 색상을 기반으로 하지만 상세 내용은 보이지 않게 합니다
+        setting_use_pending_items: 타임라인의 새 게시물을 자동으로 보여 주는 대신, 클릭해서 나타내도록 합니다
         username: 당신의 유저네임은 %{domain} 안에서 유일해야 합니다
         whole_word: 키워드가 영문과 숫자로만 이루어 진 경우, 단어 전체에 매칭 되었을 때에만 작동하게 합니다
+      domain_allow:
+        domain: 이 도메인은 이 서버에서 데이터를 가져갈 수 있고 이 도메인에서 보내진 데이터는 처리되고 저장 됩니다
       featured_tag:
         name: '이것들을 사용하면 좋을 것 같습니다:'
+      form_challenge:
+        current_password: 당신은 보안 구역에 진입하고 있습니다
       imports:
         data: 다른 마스토돈 서버에서 추출된 CSV 파일
       invite_request:
         text: 이 정보는 우리가 심사를 하는 데에 참고할 수 있습니다
       sessions:
         otp: '휴대전화에서 생성 된 2단계 인증 코드를 입력하거나, 복구 코드 중 하나를 사용하세요:'
+      tag:
+        name: 읽기 쉽게하기 위한 글자의 대소문자만 변경할 수 있습니다.
       user:
         chosen_languages: 체크하면, 선택 된 언어들만 공개 타임라인에 보여집니다
     labels:
@@ -51,9 +66,14 @@ ko:
         fields:
           name: 라벨
           value: ë‚´ìš©
+      account_alias:
+        acct: 기존 계정의 핸들
+      account_migration:
+        acct: 새 계정의 핸들
       account_warning_preset:
         text: 프리셋 텍스트
       admin_account_action:
+        include_statuses: 신고된 툿을 이메일에 포함
         send_email_notification: 이메일로 유저에게 알리기
         text: 커스텀 경고
         type: 조치
@@ -108,7 +128,10 @@ ko:
         setting_show_application: 툿 작성에 사용한 앱을 공개
         setting_system_font_ui: 시스템의 초기 설정 폰트를 사용
         setting_theme: 사이트 테마
+        setting_trends: 오늘의 유행 보이기
         setting_unfollow_modal: 언팔로우 전 언팔로우 확인 표시
+        setting_use_blurhash: 숨겨진 미디어에 대해 그라디언트 표시
+        setting_use_pending_items: 느린 모드
         severity: 심각도
         type: 불러오기 종류
         username: 유저 이름
@@ -120,6 +143,8 @@ ko:
         must_be_follower: 나를 팔로우 하지 않는 사람에게서 온 알림을 차단
         must_be_following: 내가 팔로우 하지 않는 사람에게서 온 알림을 차단
         must_be_following_dm: 내가 팔로우 하지 않은 사람에게서 오는 다이렉트메시지를 차단
+      invite:
+        comment: 주석
       invite_request:
         text: 가입하려는 이유가 무엇인가요?
       notification_emails:
@@ -131,8 +156,15 @@ ko:
         pending_account: 새 계정이 심사가 필요할 때 이메일 보내기
         reblog: 누군가 내 툿을 부스트 했을 때 이메일 보내기
         report: 새 신고 등록시 이메일로 알리기
+        trending_tag: 리뷰 되지 않은 해시태그가 유행할 때 이메일 보내기
+      tag:
+        listable: 이 해시태그가 프로필 디렉토리에 보여지도록 허용
+        name: 해시태그
+        trendable: 이 해시태그가 유행에 보여지도록 허용
+        usable: 이 해시태그를 툿에 사용 가능하도록 허용
     'no': 아니오
     recommended: 추천함
     required:
+      mark: "*"
       text: 필수 항목
     'yes': 네
diff --git a/config/locales/simple_form.mk.yml b/config/locales/simple_form.mk.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8b9144a988941222eba3d4c61c19a62a8b057db4
--- /dev/null
+++ b/config/locales/simple_form.mk.yml
@@ -0,0 +1 @@
+mk:
diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml
index 58d29ce12c036755ccba4da4fcbfa580796898f3..77445b0cbca8c0c66d697a105136aec3f3afe0ee 100644
--- a/config/locales/simple_form.nl.yml
+++ b/config/locales/simple_form.nl.yml
@@ -2,9 +2,14 @@
 nl:
   simple_form:
     hints:
+      account_alias:
+        acct: Vul de gebruikersnaam@domein van het account in, die je wilt verhuizen
+      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
       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}">voorinstellingen van waarschuwingen toevoegen</a>
         type_html: Kies wat er met <strong>%{acct}</strong> moet gebeuren
@@ -14,8 +19,10 @@ nl:
         avatar: PNG, GIF of JPG. Maximaal %{size}. Wordt teruggeschaald naar %{dimensions}px
         bot: Dit is een geautomatiseerd account en wordt mogelijk niet gemonitord
         context: Een of meerdere locaties waar de filter actief moet zijn
+        current_password: Voer voor veiligheidsredenen het wachtwoord van je huidige account in
+        current_username: Voer ter bevestiging de gebruikersnaam van je huidige account in
         digest: Wordt alleen na een lange periode van inactiviteit verzonden en alleen wanneer je tijdens jouw afwezigheid persoonlijke berichten hebt ontvangen
-        discoverable_html: In de <a href="%{path}" target="_blank">gebruikersgids</a> kunnen mensen andere accounts vinden aan de hand van interesses en activiteit. Dit vereist tenminste %{min_followers} volgers
+        discoverable: De gebruikersgids is een andere manier waarmee jouw account een groter publiek kan bereiken
         email: Je krijgt een bevestigingsmail
         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
@@ -34,16 +41,24 @@ nl:
         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_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}
         whole_word: Wanneer het trefwoord of zinsdeel alfanumeriek is, wordt het alleen gefilterd wanneer het hele woord overeenkomt
+      domain_allow:
+        domain: Dit domein is in staat om gegevens van deze server op te halen, en binnenkomende gegevens worden verwerkt en opgeslagen
       featured_tag:
         name: 'Je wilt misschien een van deze gebruiken:'
+      form_challenge:
+        current_password: Je betreedt een veilige omgeving
       imports:
         data: CSV-bestand dat op een andere Mastodonserver werd geëxporteerd
       invite_request:
         text: Dit helpt ons om jouw aanvraag te beoordelen
       sessions:
         otp: 'Voer de tweestaps-aanmeldcode vanaf jouw mobiele telefoon in of gebruik een van jouw herstelcodes:'
+      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
     labels:
@@ -51,9 +66,14 @@ nl:
         fields:
           name: Label
           value: Inhoud
+      account_alias:
+        acct: Mastodonadres van het oude account
+      account_migration:
+        acct: Mastodonadres van het nieuwe account
       account_warning_preset:
         text: Tekst van voorinstelling
       admin_account_action:
+        include_statuses: Gerapporteerde toots aan de e-mail toevoegen
         send_email_notification: Meld dit per e-mail aan de gebruiker
         text: Aangepaste waarschuwing
         type: Actie
@@ -108,7 +128,10 @@ nl:
         setting_show_application: Toepassing onthullen die je voor het verzenden van toots gebruikt
         setting_system_font_ui: Standaardlettertype van jouw systeem gebruiken
         setting_theme: Thema website
+        setting_trends: Trends van vandaag tonen
         setting_unfollow_modal: Vraag voor het ontvolgen van iemand een bevestiging
+        setting_use_blurhash: Wazige kleurovergangen voor verborgen media tonen
+        setting_use_pending_items: Langzame modus
         severity: Zwaarte
         type: Importtype
         username: Gebruikersnaam
@@ -120,6 +143,8 @@ nl:
         must_be_follower: Meldingen van mensen die jou niet volgen blokkeren
         must_be_following: Meldingen van mensen die jij niet volgt blokkeren
         must_be_following_dm: Directe berichten van mensen die jij niet volgt blokkeren
+      invite:
+        comment: Opmerking
       invite_request:
         text: Waarom wil jij je aanmelden?
       notification_emails:
@@ -131,6 +156,12 @@ nl:
         pending_account: Een e-mail verzenden wanneer een nieuw account moet worden beoordeeld
         reblog: Een e-mail versturen wanneer iemand jouw toot heeft geboost
         report: Verstuur een e-mail wanneer een nieuw rapportage is ingediend
+        trending_tag: Een e-mail versturen wanneer een nog niet beoordeelde hashtag trending is
+      tag:
+        listable: Toestaan dat deze hashtag in zoekopdrachten en in de gebruikersgids 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
     'no': Nee
     recommended: Aanbevolen
     required:
diff --git a/config/locales/simple_form.nn.yml b/config/locales/simple_form.nn.yml
new file mode 100644
index 0000000000000000000000000000000000000000..777f4e600f65daa6a550f2cf0f04551a49cf4fcd
--- /dev/null
+++ b/config/locales/simple_form.nn.yml
@@ -0,0 +1 @@
+nn:
diff --git a/config/locales/simple_form.oc.yml b/config/locales/simple_form.oc.yml
index e0bfcfef9c57864c842fd4b3389c20035e978174..59651d9de3565e7e2c2aabd12b74bb50cb832ecd 100644
--- a/config/locales/simple_form.oc.yml
+++ b/config/locales/simple_form.oc.yml
@@ -2,9 +2,14 @@
 oc:
   simple_form:
     hints:
+      account_alias:
+        acct: Donatz l’utilizaire@domeni del compte que volètz desplaçar
+      account_migration:
+        acct: Donatz l’utilizaire@domeni del compte ont volètz anar
       account_warning_preset:
         text: Podètz utilizar la sintaxi dels tuts, coma las URL, las etiquetas e las mencions
       admin_account_action:
+        include_statuses: L’utilizaire veiràs quals tuts a provocat l’accion de moderacion o avertiment
         send_email_notification: L’utilizaire recebrà una explicacion de çò qu’arribèt a son compte
         text_html: Opcional. Podètz utilizar la sintaxi dels tuts. Podètz <a href="%{path}">ajustar un avertiment personalizat</a> per estalviar de temps
         type_html: Causir de qué far amb <strong>%{acct}</strong>
@@ -14,8 +19,10 @@ oc:
         avatar: PNG, GIF o JPG. Maximum %{size}. Serà retalhat en %{dimensions}px
         bot: Avisar lo monde qu’aqueste compte es pas d’una persona
         context: Un o mai de contèxtes ont lo filtre deuriá s’aplicar
+        current_password: Per de rasons de seguretat volgatz picar lo senhal del compte actual
+        current_username: Per confirmar, volgatz picar lo nom d’utilizaire del compte actual
         digest: Solament enviat aprèp un long moment d’inactivitat e solament s’avètz recebut de messatges personals pendent vòstra abséncia
-        discoverable_html: L’<a href="%{path}" target="_blank">annuari</a> permet al monde de trobar de comptes segon lor interèsses e activitats. Requerís almens %{min_followers} seguidors
+        discoverable: L’annuari de perfiles es un biais mai per que vòstre compte aja una audiéncia mai granda
         email: Vos mandarem un corrièl de confirmacion
         fields: Podètz far veire cap a 4 elements sus vòstre perfil
         header: PNG, GIF o JPG. Maximum %{size}. Serà retalhada en %{dimensions}px
@@ -27,22 +34,31 @@ oc:
         phrase: Serà pres en compte que siá en majuscula o minuscula o dins un avertiment de contengut sensible
         scopes: A quinas APIs poiràn accedir las aplicacions. Se seleccionatz un encastre de naut nivèl, fa pas mestièr de seleccionar los nivèls mai basses.
         setting_aggregate_reblogs: Mostrar pas los nòus partatges que son estats partejats recentament (afecta pas que los nòus partatges recebuts)
+        setting_default_sensitive: Los mèdias sensibles son resconduts per defaut e se revelhan amb un clic
         setting_display_media_default: Rescondre los mèdias marcats coma sensibles
         setting_display_media_hide_all: Totjorn rescondre los mèdias
         setting_display_media_show_all: Totjorn mostrar los mèdias marcats coma sensibles
         setting_hide_network: Vòstre perfil mostrarà pas los que vos sègon e lo monde que seguètz
         setting_noindex: Aquò es destinat a vòstre perfil public e vòstra pagina d’estatuts
         setting_show_application: Lo nom de l’aplicacion qu’utilizatz per publicar serà mostrat dins la vista detalhada de vòstres tuts
+        setting_use_blurhash: Los degradats venon de las colors de l’imatge rescondut en enfoscar los detalhs
+        setting_use_pending_items: Rescondre las actualizacions del flux d’actualitat aprèp un clic allòc de desfilar lo flux automaticament
         username: Vòstre nom d’utilizaire serà unic sus %{domain}
         whole_word: Quand lo mot-clau o frasa es solament alfranumeric, serà pas qu’aplicat se correspond al mot complèt
+      domain_allow:
+        domain: Aqueste domeni poirà recuperar las donadas d’aqueste servidor estant e las donadas venent d’aqueste domeni seràn tractadas e gardadas
       featured_tag:
         name: 'Benlèu que volètz utilizar una d’aquestas causas :'
+      form_challenge:
+        current_password: Dintratz dins una zòna segura
       imports:
         data: Fichièr CSV exportat d’un autre servidor Mastodon
       invite_request:
         text: Aquò nos ajudarà per validar vòstra demanda
       sessions:
         otp: 'Picatz lo còdi d’autentificacion en dos temps (Two factor code) de vòstra aplicacion mobil o utilizatz un de vòstres còdis de recuperacion :'
+      tag:
+        name: Podètz pas que cambiar la talha de las letras, per exemple, per que sián de melhor legir
       user:
         chosen_languages: Quand seleccionadas, solament los tuts dins las lengas triadas seràn mostrats dins vòstre flux d’actualitat
     labels:
@@ -50,9 +66,14 @@ oc:
         fields:
           name: Nom
           value: Contengut
+      account_alias:
+        acct: Identificant de l’ancian compte
+      account_migration:
+        acct: Identificant del nòu compte
       account_warning_preset:
         text: Tèxt predefinit
       admin_account_action:
+        include_statuses: Inclure los tuts senhalats dins lo corrièl
         send_email_notification: Avisar l’utilizaire per corrièl
         text: Avertiment personalizat
         type: Accions
@@ -64,6 +85,7 @@ oc:
         warning_preset_id: Utilizar un avertiment predefinit
       defaults:
         autofollow: Convidar a sègre vòstre compte
+        avatar: Avatar
         bot: Aquò es lo compte a un robòt
         chosen_languages: Filtrar las lengas
         confirm_new_password: Confirmacion del nòu senhal
@@ -83,9 +105,11 @@ oc:
         locked: Far venir lo compte privat
         max_uses: Limit d’utilizacions
         new_password: Nòu senhal
+        note: Bio
         otp_attempt: Còdi Two-factor
         password: Senhal
         phrase: Senhal o frasa
+        setting_advanced_layout: Activar l’interfàcia web avançada
         setting_aggregate_reblogs: Agropar los partatges dins lo flux d’actualitat
         setting_auto_play_gif: Lectura automatica dels GIFS animats
         setting_boost_modal: Mostrar una fenèstra de confirmacion abans de partejar un estatut
@@ -104,7 +128,10 @@ oc:
         setting_show_application: Revelar lo nom de l’aplicacion utilizada per enviar de tuts
         setting_system_font_ui: Utilizar la polissa del sistèma
         setting_theme: Tèma del site
+        setting_trends: Veire las tendéncias d’uèi
         setting_unfollow_modal: Mostrar una confirmacion abans de quitar de sègre qualqu’un
+        setting_use_blurhash: Mostrar los degradats colorats pels mèdias resconduts
+        setting_use_pending_items: Mòde lent
         severity: Severitat
         type: Tipe d’impòrt
         username: Nom d’utilizaire
@@ -116,6 +143,8 @@ oc:
         must_be_follower: Blocar las notificacions del mond que vos sègon pas
         must_be_following: Blocar las notificacions del mond que seguètz pas
         must_be_following_dm: Blocar los messatges del monde que seguètz pas
+      invite:
+        comment: Comentari
       invite_request:
         text: Perqué volètz vos marcar ?
       notification_emails:
@@ -127,7 +156,15 @@ oc:
         pending_account: Enviar un corrièl quand cal validar un compte novèl
         reblog: Enviar un corrièl quand qualqu’un tòrna partejar vòstre estatut
         report: Enviar un corrièl pels nòus senhalaments
+        trending_tag: Enviar un corrièl quand una etiqueta pas repassada es en tendéncia
+      tag:
+        listable: Permetre a aquesta etiqueta d’aparéisser a las recèrcas e a l’annuari de perfils
+        name: Etiqueta
+        trendable: Permetre a aquesta etiqueta d’aparéisser a las tendéncias
+        usable: Permetre als tuts d’utilizar aquesta etiqueta
     'no': Non
+    recommended: Recomandat
     required:
+      mark: "*"
       text: requesit
     'yes': Ã’c
diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml
index 2f9bf5329f17d78d06b644109e1d0fa3cbfb8e39..6958179851fe5ddb8604f41f21b4d7aedd892db0 100644
--- a/config/locales/simple_form.pl.yml
+++ b/config/locales/simple_form.pl.yml
@@ -15,7 +15,6 @@ pl:
         bot: To konto wykonuje głównie zautomatyzowane działania i może nie być monitorowane
         context: Jedno lub wiele miejsc, w których filtr zostanie zastosowany
         digest: Wysyłane tylko po długiej nieaktywności, jeżeli w tym czasie otrzymaleś jakąś wiadomość bezpośrednią
-        discoverable_html: <a href="%{path}" target="_blank">Katalog</a> pozwala znaleźć konta na podstawie zainteresowań i aktywności. Profil musi śledzić przynajmniej %{min_followers} osób
         email: Otrzymasz e-mail potwierdzajÄ…cy
         fields: Możesz ustawić maksymalnie 4 niestandardowe pola wyświetlane jako tabela na Twoim profilu
         header: PNG, GIF lub JPG. Maksymalnie %{size}. Zostanie zmniejszony do %{dimensions}px
@@ -34,6 +33,7 @@ pl:
         setting_hide_network: Informacje o tym, kto Cię śledzi i kogo śledzisz nie będą widoczne
         setting_noindex: Wpływa na widoczność strony profilu i Twoich wpisów
         setting_show_application: W informacjach o wpisie będzie widoczna informacja o aplikacji, z której został wysłany
+        setting_use_blurhash: Gradienty są oparte na kolorach ukrywanej zawartości, ale uniewidaczniają wszystkie szczegóły
         username: Twoja nazwa użytkownika będzie niepowtarzalna na %{domain}
         whole_word: Jeśli słowo lub fraza składa się jedynie z liter lub cyfr, filtr będzie zastosowany tylko do pełnych wystąpień
       featured_tag:
@@ -109,6 +109,7 @@ pl:
         setting_system_font_ui: Używaj domyślnej czcionki systemu
         setting_theme: Motyw strony
         setting_unfollow_modal: Pytaj o potwierdzenie przed cofnięciem śledzenia
+        setting_use_blurhash: Pokazuj kolorowe gradienty dla ukrytej zawartości multimedialnej
         severity: Priorytet
         type: Importowane dane
         username: Nazwa użytkownika
diff --git a/config/locales/simple_form.pt-BR.yml b/config/locales/simple_form.pt-BR.yml
index df7af796cdf4ea8830d725c3ce122d253dfa5d33..72f179d5238af5affcbc180a482355af9c46b882 100644
--- a/config/locales/simple_form.pt-BR.yml
+++ b/config/locales/simple_form.pt-BR.yml
@@ -2,9 +2,14 @@
 pt-BR:
   simple_form:
     hints:
+      account_alias:
+        acct: Especifique o usuário@domínio da conta da qual você deseja mover
+      account_migration:
+        acct: Especifique o usuário@domínio da conta para o qual você deseja mover
       account_warning_preset:
         text: Você pode usar a sintaxe de um toot, como URLs, hashtags e menções
       admin_account_action:
+        include_statuses: O usuário verá quais toots causaram o aviso ou ação da moderação
         send_email_notification: O usuário vai receber uma explicação do que aconteceu com a sua conta
         text_html: Opcional. Você pode usar a sintaxe de toots. Você pode <a href="%{path}">adicionar avisos pré-definidos</a> para ganhar tempo
         type_html: Escolha o que fazer com <strong>%{acct}</strong>
@@ -14,8 +19,9 @@ pt-BR:
         avatar: PNG, GIF or JPG. Arquivos de até %{size}. Eles serão diminuídos para %{dimensions}px
         bot: Essa conta executa principalmente ações automatizadas e pode não ser monitorada
         context: Um ou mais contextos onde o filtro deve ser aplicado
+        current_password: Para fins de segurança digite a senha da conta atual
+        current_username: Para confirmar, por favor, digite o usuário da conta atual
         digest: Enviado após um longo período de inatividade com um resumo das menções que você recebeu em sua ausência
-        discoverable_html: O <a href="%{path}" target="_blank">diretório</a> permite encontrar contas baseado em seus interesses e atividades. Requer pelo menos %{min_followers} seguidores
         email: Você receberá um email de confirmação
         fields: Você pode ter até 4 itens exibidos em forma de tabela no seu perfil
         header: PNG, GIF or JPG. Arquivos de até %{size}. Eles serão diminuídos para %{dimensions}px
@@ -27,16 +33,21 @@ pt-BR:
         phrase: Vai coincidir, independente de maiúsculas ou minúsculas, no texto ou no aviso de conteúdo de um toot
         scopes: Quais APIs a aplicação vai ter permissão de acessar. Se você selecionar um escopo de alto nível, você não precisa selecionar individualmente os outros.
         setting_aggregate_reblogs: Não mostrar novos compartilhamentos para toots que foram compartilhados recentemente (afeta somente novos compartilhamentos recebidos)
+        setting_default_sensitive: Mídia sensível está oculta por padrão e pode ser revelada com um clique
         setting_display_media_default: Esconder mídia marcada como sensível
         setting_display_media_hide_all: Sempre esconder todas as mídias
         setting_display_media_show_all: Sempre mostrar mídia marcada como sensível
         setting_hide_network: Quem você segue e quem segue você não serão exibidos no seu perfil
         setting_noindex: Afeta seu perfil público e as páginas de suas postagens
         setting_show_application: A aplicação que você usar para enviar seus toots vai aparecer na visão detalhada dos seus toots
+        setting_use_blurhash: Os gradientes são baseados nas cores das imagens escondidas, mas ofuscam quaisquer detalhes
+        setting_use_pending_items: Ocultar atualizações de linha de tempo atrás de um clique ao invés de rolar automaticamente o feed
         username: Seu nome de usuário será único em %{domain}
         whole_word: Quando a palavra ou frase é inteiramente alfanumérica, ela será aplicada somente se corresponder a palavra inteira
       featured_tag:
         name: 'Você pode querer usar um destes:'
+      form_challenge:
+        current_password: Você está entrando em uma área segura
       imports:
         data: Arquivo CSV exportado de outra instância do Mastodon
       invite_request:
@@ -53,6 +64,7 @@ pt-BR:
       account_warning_preset:
         text: Texto pré-definido
       admin_account_action:
+        include_statuses: Incluir toots reportados no e-mail
         send_email_notification: Notificar o usuário por e-mail
         text: Aviso customizado
         type: Ação
@@ -64,6 +76,7 @@ pt-BR:
         warning_preset_id: Usar um aviso pré-definido
       defaults:
         autofollow: Convite para seguir a sua conta
+        avatar: Imagem de Perfil
         bot: Esta é uma conta-robô
         chosen_languages: Filtros de idioma
         confirm_new_password: Confirmar nova senha
@@ -83,6 +96,7 @@ pt-BR:
         locked: Trancar conta
         max_uses: Número máximo de usos
         new_password: Nova senha
+        note: Biografia
         otp_attempt: Código de autenticação em dois passos
         password: Senha
         phrase: Palavra-chave ou frase
@@ -111,10 +125,14 @@ pt-BR:
         username: Nome de usuário
         username_or_email: Nome de usuário ou e-mail
         whole_word: Palavra inteira
+      featured_tag:
+        name: Hashtag
       interactions:
         must_be_follower: Bloquear notificações de não-seguidores
         must_be_following: Bloquear notificações de pessoas que você não segue
         must_be_following_dm: Bloquear mensagens diretas de pessoas que você não segue
+      invite:
+        comment: Comentário
       invite_request:
         text: Por que você quer se cadastrar?
       notification_emails:
@@ -126,7 +144,15 @@ pt-BR:
         pending_account: Mandar um -mail quando uma nova conta precisar ser revisada
         reblog: Mandar um e-mail quando alguém compartilhar suas postagens
         report: Mandar um e-mail quando uma nova denúncia é submetida
+        trending_tag: Enviar e-mail quando uma hashtag não revisada estiver em alta
+      tag:
+        listable: Permitir que esta hashtag apareça em pesquisas e no diretório de perfis
+        name: Hashtag
+        trendable: Permitir que esta hashtag apareça em alta
+        usable: Permitir que toots usem esta hastag
     'no': Não
+    recommended: Recomendado
     required:
+      mark: "*"
       text: obrigatório
     'yes': Sim
diff --git a/config/locales/simple_form.pt.yml b/config/locales/simple_form.pt-PT.yml
similarity index 94%
rename from config/locales/simple_form.pt.yml
rename to config/locales/simple_form.pt-PT.yml
index bf638188984afe3e3b01e0590473a49aa6212d86..57acde4d9631dabb3c8304cd17352f1768cfa899 100644
--- a/config/locales/simple_form.pt.yml
+++ b/config/locales/simple_form.pt-PT.yml
@@ -1,10 +1,15 @@
 ---
-pt:
+pt-PT:
   simple_form:
     hints:
+      account_alias:
+        acct: Especifique o nome de usuário@domínio da conta de onde você deseja mover
+      account_migration:
+        acct: Especifique o nome de usuário@domínio da conta para onde você deseja mover
       account_warning_preset:
         text: Tu podes usar sintaxe de escrita, como URLs, hashtags e referências
       admin_account_action:
+        include_statuses: O usuário verá quais ferramentas causaram a ação de moderação ou aviso
         send_email_notification: O utilizador receberá uma explicação sobre o que aconteceu com a sua conta
         text_html: Opcional. Tu podes usar sintaxe de escrita. Tu podes <a href="%{path}"> adicionar predefinições de aviso</a> para poupar tempo
         type_html: Escolhe o que fazer com <strong>%{acct}</strong>
@@ -14,8 +19,8 @@ pt:
         avatar: PNG, GIF or JPG. Arquivos até %{size}. Vão ser reduzidos para %{dimensions}px
         bot: Esta conta executa essencialmente acções automáticas e pode não poder ser monitorizada
         context: Um ou múltiplos contextos nos quais o filtro deve ser aplicado
+        current_password: Para fins de segurança, por favor, digite a senha da conta atual
         digest: Enviado após um longo período de inatividade e apenas se foste mencionado na tua ausência
-        discoverable_html: O <a href="%{path}" target="_blank">directory</a> permite encontrar contas de pessoas com base nos seus interesses e actividades. Exige, pelo menos %{min_followers} seguidores
         email: Será enviado um e-mail de confirmação
         fields: Podes ter até 4 itens expostos, em forma de tabela, no teu perfil
         header: PNG, GIF or JPG. Arquivos até %{size}. Vão ser reduzidos para %{dimensions}px
diff --git a/config/locales/simple_form.ro.yml b/config/locales/simple_form.ro.yml
index 4df2fe16100d1f84b9b3df9bf27a5d41954a1bb7..ac4c344f192ae0490507479cc477a1afb6a76aad 100644
--- a/config/locales/simple_form.ro.yml
+++ b/config/locales/simple_form.ro.yml
@@ -15,7 +15,6 @@ ro:
         bot: Acest cont performează în cea mai mare parte acțiuni automate și nu poate fi monitorizat
         context: Contextele în care filtrul trebuie aplicat
         digest: Este trimis doar după o lungă perioadă de inactivitate și numai dacă primești mesaje personale în perioada de absență
-        discoverable_html: <a href="%{path}" target="_blank">Directorul</a> permite utilizatorilor să găsească conturi după interese și activități. Necesită minim %{min_followers} urmăritori
         email: Vei primi un e-mail de confirmare
         fields: Poti afișa pană la maxim 4 adrese sub formă de tabel pe pofilul tău
         header: PNG, GIF sau JPG. Cel mult %{size}. Vor fi redimensionate la %{dimensions}px
diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml
index 26a73c3c6d9925306cc07d39722e9d965b9ccb56..ab5eb855e27acae53600503a0d5ed0c4eaa2acca 100644
--- a/config/locales/simple_form.ru.yml
+++ b/config/locales/simple_form.ru.yml
@@ -10,19 +10,18 @@ ru:
         type_html: Выберите, что делать с аккаунтом <strong>%{acct}</strong>
         warning_preset_id: Необязательно. Вы можете добавить собственный текст в конце шаблона
       defaults:
-        autofollow: Люди, пришедшие по этому приглашению автоматически будут подписаны на Вас
-        avatar: PNG, GIF или JPG. Максимально %{size}. Будет уменьшено до %{dimensions}px
+        autofollow: Люди, пришедшие по этому приглашению, автоматически будут подписаны на вас
+        avatar: Поддерживается PNG, GIF и JPG. Максимальный размер — %{size}. Будет уменьшен до %{dimensions}px
         bot: Этот аккаунт обычно выполяет автоматизированные действия и может не просматриваться владельцем
         context: Один или несколько контекстов, к которым должны быть применены фильтры
         digest: Отсылается лишь после длительной неактивности, если вы в это время получали личные сообщения
-        discoverable_html: <a href="%{path}" target="_blank">Каталог</a> позволяет пользователям искать людей по интересам и активности. Необходимо наличие не менее %{min_followers} подписчиков
         email: Вам будет отправлено электронное письмо с подтверждением
         fields: В профиле можно отобразить до 4 пунктов как таблицу
-        header: PNG, GIF или JPG. Максимально %{size}. Будет уменьшено до %{dimensions}px
+        header: Поддерживается PNG, GIF и JPG. Максимальный размер — %{size}. Будет уменьшена до %{dimensions}px
         inbox_url: Копировать URL с главной страницы ретранслятора, который вы хотите использовать
         irreversible: Отфильтрованные статусы будут утеряны навсегда, даже если в будущем фильтр будет убран
         locale: Язык интерфейса, e-mail писем и push-уведомлений
-        locked: Потребует от вас ручного подтверждения подписчиков, изменит приватность постов по умолчанию на "только для подписчиков"
+        locked: Подписчиков нужно будет подтверждать самостоятельно
         password: Укажите не менее 8 символов
         phrase: Будет сопоставлено независимо от присутствия в тексте или предупреждения о содержании статуса
         scopes: Какие API приложению будет позволено использовать. Если вы выберете самый верхний, нижестоящие будут выбраны автоматически.
@@ -34,8 +33,12 @@ ru:
         setting_hide_network: Те, на кого вы подписаны и кто подписан на Вас, не будут отображены в вашем профиле
         setting_noindex: Относится к вашему публичному профилю и страницам статусов
         setting_show_application: В окне просмотра вашего статуса будет видно, с какого приложения он был отправлен
+        setting_use_blurhash: Градиенты основаны на цветах скрытых медиа, но скрывают любые детали
+        setting_use_pending_items: Показывать обновления в ленте только после клика вместо автоматической прокрутки
         username: Ваш юзернейм будет уникальным на %{domain}
         whole_word: Если слово или фраза состоит только из букв и цифр, сопоставление произойдёт только по полному совпадению
+      domain_allow:
+        domain: Этот домен сможет получать данные с этого сервера и его входящие данные будут обрабатываться и сохранены
       featured_tag:
         name: 'Возможно, вы захотите выбрать из них:'
       imports:
@@ -44,6 +47,8 @@ ru:
         text: Это поможет нам рассмотреть вашу заявку
       sessions:
         otp: 'Введите код двухфакторной аутентификации, сгенерированный в мобильном приложении, или используйте один из ваших кодов восстановления:'
+      tag:
+        name: Вы можете изменить только регистр букв чтобы, например, сделать тег более читаемым
       user:
         chosen_languages: Если выбрано, то в публичных лентах будут показаны только посты на выбранных языках
     labels:
@@ -64,7 +69,7 @@ ru:
           suspend: Заблокировать и безвозвратно удалить все данные аккаунта
         warning_preset_id: Использовать шаблон
       defaults:
-        autofollow: Пригласите подписаться на Ваш аккаунт
+        autofollow: С подпиской на ваш аккаунт
         avatar: Аватар
         bot: Это аккаунт бота
         chosen_languages: Фильтр языков
@@ -74,7 +79,7 @@ ru:
         current_password: Текущий пароль
         data: Данные
         discoverable: Показывать этот аккаунт в каталоге
-        display_name: Показываемое имя
+        display_name: Отображаемое имя
         email: Адрес e-mail
         expires_in: Истекает через
         fields: Метаданные профиля
@@ -83,20 +88,20 @@ ru:
         irreversible: Удалять, а не скрывать
         locale: Язык интерфейса
         locked: Сделать аккаунт закрытым
-        max_uses: Макс. число использований
+        max_uses: Максимальное число использований
         new_password: Новый пароль
-        note: О Вас
+        note: О себе
         otp_attempt: Двухфакторный код
         password: Пароль
         phrase: Слово или фраза
         setting_advanced_layout: Включить многоколоночный интерфейс
         setting_aggregate_reblogs: Группировать продвижения в лентах
         setting_auto_play_gif: Автоматически проигрывать анимированные GIF
-        setting_boost_modal: Показывать диалог подтверждения перед продвижением
+        setting_boost_modal: Всегда спрашивать перед продвижением
         setting_default_language: Язык отправляемых статусов
         setting_default_privacy: Видимость постов
         setting_default_sensitive: Всегда отмечать медиаконтент как чувствительный
-        setting_delete_modal: Показывать диалог подтверждения перед удалением
+        setting_delete_modal: Всегда спрашивать перед удалении поста
         setting_display_media: Отображение медиафайлов
         setting_display_media_default: По умолчанию
         setting_display_media_hide_all: Скрывать все
@@ -108,7 +113,10 @@ ru:
         setting_show_application: Раскрывать приложение, с которого отправляются статусы
         setting_system_font_ui: Использовать шрифт системы по умолчанию
         setting_theme: Тема сайта
-        setting_unfollow_modal: Показывать диалог подтверждения перед тем, как отписаться от аккаунта
+        setting_trends: Показывать сегодняшние тренды
+        setting_unfollow_modal: Всегда спрашивать перед отпиской от аккаунта
+        setting_use_blurhash: Показать цветные градиенты для скрытых медиа
+        setting_use_pending_items: Медленный режим
         severity: Строгость
         type: Тип импорта
         username: Имя пользователя
@@ -131,6 +139,11 @@ ru:
         pending_account: Отправлять e-mail при наличии новых заявок на присоединение
         reblog: Уведомлять по e-mail, когда кто-то продвинул ваш статус
         report: Уведомлять по e-mail при создании жалобы
+        trending_tag: Отправлять e-mail при непроверенных хэштегах в трендах
+      tag:
+        listable: Разрешить показ хэштега в поиске или в каталоге профилей
+        trendable: Разрешить показ хэштега в трендах
+        usable: Разрешить использовать этот хэштег в постах
     'no': Нет
     recommended: Рекомендуется
     required:
diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml
index 8470f793912267482982ee814e00735efe419693..b908c0196ce5ab3740cc0a9da327ee99d04a744c 100644
--- a/config/locales/simple_form.sk.yml
+++ b/config/locales/simple_form.sk.yml
@@ -5,6 +5,7 @@ sk:
       account_warning_preset:
         text: Môžeš používať rovnakú syntaxiu ako v rámci príspevkov, čiže URL, haštagy, a spomenutia
       admin_account_action:
+        include_statuses: Užívateľ uvidí, ktoré príspevky majú za následok moderačný zásah, alebo upozornenie
         send_email_notification: Užívateľ dostane vysvetlenie ohľadom toho, čo sa stalo s ich účtom
         text_html: Voliteľné. Môžeš používať rovnakú syntaxiu ako v príspevkoch. Môžeš pridať <a href="%{path}">varovné predlohy</a> a ušetriť tak čas
         type_html: Vyber si, čo urobiť s účtom <strong>%{acct}</strong>
@@ -15,7 +16,6 @@ sk:
         bot: Tento účet vykonáva hlavne automatizované akcie, a je pravdepodobne nespravovaný
         context: Jedno, alebo viac kritérií, v ktorých má byť filtrovanie uplatnené
         digest: Odoslané iba v prípade dlhodobej neprítomnosti, a len ak si obdržal/a nejaké osobné správy kým si bol/a preč
-        discoverable_html: Táto <a href="%{path}" target="_blank">databáza</a> umožňuje ľudom nájsť profily podľa záujmu a aktívnosti. Vyžaduje aby mali aspoň %{min_followers} sledovateľov
         email: Bude ti odoslaný potvrdzujúci email
         fields: Až štyri položky môžeš mať na svojom profile zobrazené vo forme tabuľky
         header: PNG, GIF, alebo JPG. Maximálne %{size}. Bude zmenšený na %{dimensions}px
@@ -34,8 +34,11 @@ sk:
         setting_hide_network: Koho následuješ, a kto následuje teba, nebude zobrazené na tvojom profile
         setting_noindex: Ovplyvňuje verejný profil a stránky s príspevkami
         setting_show_application: Aplikácia, ktorú používaš na písanie príspevkov, bude zobrazená v podrobnom náhľade jednotlivých tvojích príspevkov
+        setting_use_pending_items: Skry aktualizovanie časovej osi tak, aby bola načitávaná iba po kliknutí, namiesto samostatného posúvania
         username: Tvoja prezývka bude unikátna pre server %{domain}
         whole_word: Ak je kľúčové slovo, alebo fráza poskladaná iba s písmen a čísel, bude použité iba ak sa zhoduje s celým výrazom
+      domain_allow:
+        domain: Táto doména bude schopná získavať dáta z tohto servera, a prichádzajúce dáta ním budú spracovávané a uložené
       featured_tag:
         name: 'Možno by si chcel/a použiť niektoré z týchto:'
       imports:
@@ -54,6 +57,7 @@ sk:
       account_warning_preset:
         text: Text predlohy
       admin_account_action:
+        include_statuses: Zahrnúť nahlásené príspevky v emaile
         send_email_notification: Oznam užívateľovi cez email
         text: Špecifické varovanie
         type: Úkon
@@ -101,14 +105,17 @@ sk:
         setting_display_media_default: Å tandard
         setting_display_media_hide_all: Ukry všetky
         setting_display_media_show_all: Ukáž všetky
-        setting_expand_spoilers: Stále rozbaľ príspevky označené varovaním o obsahu
+        setting_expand_spoilers: Stále rozbaľ príspevky označené varovaním o chúlostivom obsahu
         setting_hide_network: Ukry svoju sieť kontaktov
         setting_noindex: Nezaraďuj príspevky do indexu pre vyhľadávče
         setting_reduce_motion: Mierni pohyb pri animáciách
         setting_show_application: Zverejni akú aplikáciu používaš na posielanie príspevkov
         setting_system_font_ui: Použi základné systémové písmo
         setting_theme: Vzhľad webu
+        setting_trends: Ukáž dnešné trendy
         setting_unfollow_modal: Vyžaduj potvrdenie pred skončením sledovania iného užívateľa
+        setting_use_blurhash: Ukáž farebné prechody pre skryté médiá
+        setting_use_pending_items: Pomalý režim
         severity: Závažnosť
         type: Typ importu
         username: Prezývka
@@ -131,8 +138,15 @@ sk:
         pending_account: Zaslať email, ak treba prehodnotiť nový účet
         reblog: Zaslať email, ak niekto re-tootne tvoj príspevok
         report: Zaslať email, ak niekto podá nové nahlásenie
+        trending_tag: Pošli email, ak sa neoverený haštag stane populárnym
+      tag:
+        listable: Povoľ zobrazovanie tohto haštagu v zozname profilov
+        name: Haštag
+        trendable: Povoľ zobrazovanie tohto haštagu medzi trendujúcimi
+        usable: Povoľ používanie tohto haštagu v príspevkoch
     'no': Nie
     recommended: Odporúčané
     required:
+      mark: "*"
       text: povinné
     'yes': Áno
diff --git a/config/locales/simple_form.sl.yml b/config/locales/simple_form.sl.yml
index 2e0495551f4a0063b6659c5f0c6d4929671931df..771edf383794fa14ec4a9301a6a581509218a35e 100644
--- a/config/locales/simple_form.sl.yml
+++ b/config/locales/simple_form.sl.yml
@@ -2,6 +2,13 @@
 sl:
   simple_form:
     hints:
+      account_warning_preset:
+        text: Lahko uporabite skladnjo tuta, kot so URL-ji, ključniki in omembe
+      admin_account_action:
+        send_email_notification: Uporabnik bo prejel razlago, kaj se je zgodilo z njihovim računom
+        text_html: Neobvezno. Lahko uporabite skladnjo tuta. <a href="%{path}">Prednastavite opozorila</a>, da prihranite čas
+        type_html: Izberite, kaj boste storili z <strong>%{acct}</strong>
+        warning_preset_id: Neobvezno. Å e vedno lahko dodate besedilo po meri na konec prednastavitve
       defaults:
         autofollow: Osebe, ki se prijavijo prek povabila, vas bodo samodejno sledile
         avatar: PNG, GIF ali JPG. Največ %{size}. Zmanjšana bo na %{dimensions}px
@@ -18,15 +25,26 @@ sl:
         password: Uporabite najmanj 8 znakov
         phrase: Se bo ujemal, ne glede na začetnice v tekstu ali opozorilo o vsebini troba
         scopes: Do katerih API-jev bo imel program dostop. Če izberete obseg najvišje ravni, vam ni treba izbrati posameznih.
+        setting_aggregate_reblogs: Ne prikažite novih spodbud za tute, ki so bili nedavno spodbujeni (vpliva samo na novo prejete spodbude)
+        setting_default_sensitive: Občutljivi mediji so privzeto skriti in jih je mogoče razkriti s klikom
         setting_display_media_default: Skrij medij, ki je označen kot občutljiv
         setting_display_media_hide_all: Vedno skrij vse medije
         setting_display_media_show_all: Vedno pokaži medij, ki je označen kot občutljiv
         setting_hide_network: Kogar spremljate in kdo vas spremlja ne bo prikazano na vašem profilu
         setting_noindex: Vpliva na vaš javni profil in na strani s stanjem
+        setting_show_application: Aplikacija, ki jo uporabljate za tutanje, bo prikazana v podrobnem pogledu vaših tutov
+        setting_use_blurhash: Gradienti temeljijo na barvah skrite vizualne slike, vendar zakrivajo vse podrobnosti
+        setting_use_pending_items: Skrij posodobitev časovnice za klikom namesto samodejnega posodabljanja
         username: Vaše uporabniško ime bo edinstveno na %{domain}
         whole_word: Ko je ključna beseda ali fraza samo alfanumerična, se bo uporabljala le, če se bo ujemala s celotno besedo
+      domain_allow:
+        domain: Ta domena bo lahko prejela podatke s tega strežnika, dohodni podatki z nje pa bodo obdelani in shranjeni
+      featured_tag:
+        name: 'Morda boste želeli uporabiti eno od teh:'
       imports:
         data: Izvožena CSV datoteka iz drugega Mastodon vozlišča
+      invite_request:
+        text: To nam bo pomagalo pregledati vašo prijavo
       sessions:
         otp: 'Vnesite dvomestno kodo, ki je ustvarjena z aplikacijo na telefonu, ali uporabite eno od vaših obnovitvenih kod:'
       user:
@@ -36,6 +54,18 @@ sl:
         fields:
           name: Oznaka
           value: Vsebina
+      account_warning_preset:
+        text: Prednastavljeno besedilo
+      admin_account_action:
+        send_email_notification: Obvesti uporabnika po e-pošti
+        text: Opozorilo po meri
+        type: Dejanje
+        types:
+          disable: Onemogoči
+          none: Ne naredi ničesar
+          silence: Utišaj
+          suspend: Suspendiraj in nepovratno izbriši podatke računa
+        warning_preset_id: Uporabi prednastavljeno opozorilo
       defaults:
         autofollow: Povabite, da sledi vašemu računu
         avatar: Podoba
@@ -46,6 +76,7 @@ sl:
         context: Filtriraj vsebino
         current_password: Trenutno geslo
         data: Podatki
+        discoverable: Dodaj ta račun v imenik
         display_name: Prikazno ime
         email: E-poštni naslov
         expires_in: Preteče po
@@ -57,9 +88,12 @@ sl:
         locked: Zaklenjen račun
         max_uses: Največje število uporabnikov
         new_password: Novo geslo
+        note: Bio
         otp_attempt: Dvofaktorska koda
         password: Geslo
         phrase: Ključna beseda ali fraza
+        setting_advanced_layout: Omogoči napredni spletni vmesnik
+        setting_aggregate_reblogs: Skupinske spodbude na časovnicah
         setting_auto_play_gif: Samodejno predvajanje animiranih GIF-ov
         setting_boost_modal: Pred sunkom pokaži potrditveno okno
         setting_default_language: Jezik objavljanja
@@ -74,27 +108,37 @@ sl:
         setting_hide_network: Skrij svoje omrežje
         setting_noindex: Odsotnost indeksiranja iskalnikov
         setting_reduce_motion: Zmanjšanje premikanja v animacijah
+        setting_show_application: Razkrij aplikacijo za pošiljanje tutov
         setting_system_font_ui: Uporabi privzeto pisavo sistema
         setting_theme: Tema strani
         setting_unfollow_modal: Pokaži potrditveno okno, preden nekoga prenehamo slediti
+        setting_use_blurhash: Pokaži barvite gradiente za skrite medije
+        setting_use_pending_items: Počasen način
         severity: Strogost
         type: Vrsta uvoza
         username: Uporabniško ime
         username_or_email: Uporabniško ime ali E-pošta
         whole_word: Celotna beseda
+      featured_tag:
+        name: Ključnik
       interactions:
         must_be_follower: Blokiraj obvestila nesledilcev
         must_be_following: Blokiraj obvestila oseb, ki jim ne sledite
         must_be_following_dm: Blokiraj neposredna sporočila oseb, ki jim ne sledite
+      invite_request:
+        text: Zakaj se želite pridružiti?
       notification_emails:
         digest: Pošlji izvlečke e-pošt
         favourite: Pošlji e-pošto, ko nekdo doda vaše stanje med priljubljene
         follow: Pošlji e-pošto, ko vas nekdo sledi
         follow_request: Pošlji e-pošto, ko vam nekdo želi slediti
         mention: Pošlji e-pošto, ko vas nekdo omeni
+        pending_account: Pošlji e-pošto, ko je potreben pregled novega računa
         reblog: Pošlji e-pošto, ko nekdo sune vaše stanje
         report: Pošlji e-pošto, ko je oddana nova prijava
     'no': Ne
+    recommended: Priporočeno
     required:
+      mark: "*"
       text: zahtevano
     'yes': Da
diff --git a/config/locales/simple_form.sq.yml b/config/locales/simple_form.sq.yml
index 04ef12c9a79347bf1ba130fa9a5300ec5af8c3d1..b365bdda86e3046baa5d29e7c4423366fe26d21c 100644
--- a/config/locales/simple_form.sq.yml
+++ b/config/locales/simple_form.sq.yml
@@ -15,7 +15,6 @@ sq:
         bot: Kjo llogari kryesisht bën veprime të automatizuara dhe mund të mos mbikëqyret dot
         context: Një ose disa kontekste kur duhet të zbatohet filtri
         digest: I dërguar vetëm pas një periudhe të gjatë pasiviteti dhe vetëm nëse keni marrë ndonjë mesazh personal gjatë mungesës suaj
-        discoverable_html: <a href="%{path}" target="_blank">Drejtoria</a> u lejon njerëzve të gjejnë llogari bazuar në interesat dhe veprimtarinë. Lyp të paktën %{min_followers} ndjekës
         email: Do t’ju dërgohet një email ripohimi
         fields: Te profili juaj mund të keni deri në 4 objekte të shfaqur si tabelë
         header: PNG, GIF ose JPG. E shumta %{size}. Do të ripërmasohet në %{dimensions}px
diff --git a/config/locales/simple_form.sr.yml b/config/locales/simple_form.sr.yml
index a097be5dd3e2c0f227e21c01e2693fad15054de5..6901b842c1318e3a2c8bfc11f63a578db13828d8 100644
--- a/config/locales/simple_form.sr.yml
+++ b/config/locales/simple_form.sr.yml
@@ -15,7 +15,6 @@ sr:
         bot: Овај налог углавном врши аутоматизоване радње и можда се не надгледа
         context: Један или више контекста у којима треба да се примени филтер
         digest: Послато после дужег периода неактивности са прегледом свих битних ствари које сте добили док сте били одсутни
-        discoverable_html: <a href="%{path}" target="_blank">Директоријум</a> омогућава људима да пронађу налоге засноване на интересима и активности.  Захтева бар %{min_followers} пратиоца
         email: Биће вам послата е-пошта са потврдом
         fields: Можете имати до 4 ставке приказане као табела на вашем профилу
         header: PNG, GIF или JPG. Највише %{size}. Биће смањена на %{dimensions}px
diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml
index 171714ab081562b5ab25f73fc0c3c02322bfbeb6..56bdcf7681e09ba35e1555ce7d31f8b9b6cf017c 100644
--- a/config/locales/simple_form.sv.yml
+++ b/config/locales/simple_form.sv.yml
@@ -2,11 +2,18 @@
 sv:
   simple_form:
     hints:
+      account_alias:
+        acct: Ange användarnamn@domän för kontot du flyttar från
+      account_migration:
+        acct: Ange användarnamn@domän för kontot du flyttar till
       account_warning_preset:
         text: Du kan använda inläggssyntax som webbadresser, hashtaggar och omnämnanden
       admin_account_action:
+        include_statuses: Användaren ser de toots som orsakat moderering eller varning
         send_email_notification: Användaren kommer att få en förklaring av vad som hände med sitt konto
+        text_html: Extra. Du kan använda toot syntax. Du kan <a href="%{path}">lägga till förvalda varningar</a> för att spara tid
         type_html: Välj vad du vill göra med <strong>%{acct}</strong>
+        warning_preset_id: Extra. Du kan lägga till valfri text i slutet av förinställningen
       defaults:
         autofollow: Användarkonton som skapas genom din inbjudan kommer automatiskt följa dig
         avatar: PNG, GIF eller JPG. Högst %{size}. Kommer att skalas ner till %{dimensions}px
diff --git a/config/locales/simple_form.th.yml b/config/locales/simple_form.th.yml
index 9d6b75ed0d7cf3cde54e8125b5cafa657263f0bb..500ff7f766674f966fefaa20e5eb24b56dc5c998 100644
--- a/config/locales/simple_form.th.yml
+++ b/config/locales/simple_form.th.yml
@@ -15,7 +15,6 @@ th:
         bot: บัญชีนี้ทำการกระทำอัตโนมัติเป็นหลักและอาจไม่ได้รับการสังเกตการณ์
         context: บริบทจำนวนหนึ่งหรือมากกว่าที่ตัวกรองควรใช้
         digest: ส่งเฉพาะหลังจากไม่มีการใช้งานเป็นเวลานานและในกรณีที่คุณได้รับข้อความส่วนบุคคลใด ๆ เมื่อคุณไม่อยู่เท่านั้น
-        discoverable_html: <a href="%{path}" target="_blank">ไดเรกทอรี</a> ช่วยให้ผู้คนค้นหาบัญชีตามความสนใจและกิจกรรม ต้องการอย่างน้อย %{min_followers} ผู้ติดตาม
         email: คุณจะได้รับอีเมลยืนยัน
         fields: คุณสามารถมีได้มากถึง 4 รายการแสดงเป็นตารางในโปรไฟล์ของคุณ
         header: PNG, GIF หรือ JPG สูงสุด %{size} จะถูกย่อขนาดเป็น %{dimensions}px
@@ -34,10 +33,14 @@ th:
         setting_hide_network: จะไม่แสดงผู้ที่คุณติดตามและผู้ที่ติดตามคุณในโปรไฟล์ของคุณ
         setting_noindex: มีผลต่อโปรไฟล์สาธารณะและหน้าสถานะของคุณ
         setting_show_application: จะแสดงแอปพลิเคชันที่คุณใช้เพื่อโพสต์ในมุมมองโดยละเอียดของโพสต์ของคุณ
+        setting_use_blurhash: การไล่ระดับสีอิงตามสีของภาพที่ซ่อนอยู่แต่ทำให้รายละเอียดใด ๆ คลุมเครือ
+        setting_use_pending_items: ซ่อนการอัปเดตเส้นเวลาไว้หลังการคลิกแทนที่จะเลื่อนฟีดโดยอัตโนมัติ
         username: ชื่อผู้ใช้ของคุณจะไม่ซ้ำกันบน %{domain}
         whole_word: เมื่อคำสำคัญหรือวลีมีแค่ตัวอักษรและตัวเลข จะถูกใช้หากตรงกันทั้งคำเท่านั้น
       featured_tag:
         name: 'คุณอาจต้องการใช้หนึ่งในนี้:'
+      form_challenge:
+        current_password: คุณกำลังเข้าสู่พื้นที่ปลอดภัย
       imports:
         data: ไฟล์ CSV ที่ส่งออกจากเซิร์ฟเวอร์ Mastodon อื่น
       invite_request:
@@ -108,7 +111,10 @@ th:
         setting_show_application: เปิดเผยแอปพลิเคชันที่ใช้ในการส่งโพสต์
         setting_system_font_ui: ใช้แบบอักษรเริ่มต้นของระบบ
         setting_theme: ชุดรูปแบบไซต์
+        setting_trends: แสดงแนวโน้มของวันนี้
         setting_unfollow_modal: แสดงกล่องโต้ตอบการยืนยันก่อนเลิกติดตามใครสักคน
+        setting_use_blurhash: แสดงการไล่ระดับสีที่มีสีสันสำหรับสื่อที่ซ่อนอยู่
+        setting_use_pending_items: โหมดช้า
         severity: ความรุนแรง
         type: ชนิดการนำเข้า
         username: ชื่อผู้ใช้
@@ -120,6 +126,8 @@ th:
         must_be_follower: ปิดกั้นการแจ้งเตือนจากที่ไม่ใช่ผู้ติดตาม
         must_be_following: ปิดกั้นการแจ้งเตือนจากผู้คนที่คุณไม่ได้ติดตาม
         must_be_following_dm: ปิดกั้นข้อความโดยตรงจากผู้คนที่คุณไม่ได้ติดตาม
+      invite:
+        comment: ความคิดเห็น
       invite_request:
         text: ทำไมคุณจึงต้องการเข้าร่วม?
       notification_emails:
@@ -131,6 +139,9 @@ th:
         pending_account: ส่งอีเมลเมื่อบัญชีใหม่ต้องการการตรวจทาน
         reblog: ส่งอีเมลเมื่อใครสักคนดันสถานะของคุณ
         report: ส่งอีเมลเมื่อมีการส่งรายงานใหม่
+      tag:
+        name: แฮชแท็ก
+        usable: อนุญาตให้โพสต์ใช้แฮชแท็กนี้
     'no': ไม่
     recommended: แนะนำ
     required:
diff --git a/config/locales/simple_form.tr.yml b/config/locales/simple_form.tr.yml
index 68b4c24c974fc753ca23db3900c645adcced9daa..663fce5e0a58555c8913df32fd88adc92aaa415b 100644
--- a/config/locales/simple_form.tr.yml
+++ b/config/locales/simple_form.tr.yml
@@ -2,51 +2,169 @@
 tr:
   simple_form:
     hints:
+      account_alias:
+        acct: Taşımak istediğiniz hesabın kullanıcı-adı@alan-adını belirtin
+      account_migration:
+        acct: Taşınmak istediğiniz hesabın kullanıcı-adı@alan-adını belirtin
+      account_warning_preset:
+        text: URL'ler, etiketler ve bahsetmeler gibi gönderi sözdizimleri kullanabilirsiniz
       admin_account_action:
+        include_statuses: Kullanıcı hangi gönderilerin denetleme eylemi ya da uyarısına neden olduğunu görecektir
         send_email_notification: Kullanıcı, hesabına ne olduğu hakkında bir bildirim alacak
+        text_html: İsteğe bağlı. Gönderi sözdizimleri kullanabilirsiniz. Zamandan kazanmak için <a href="%{path}">uyarı ön-ayarları ekleyebilirsiniz</a>
+        type_html: "<strong>%{acct}</strong> ile ne yapılacağını seçin"
         warning_preset_id: İsteğe bağlı. Hazır ayarın sonuna hala özel metin ekleyebilirsiniz
       defaults:
         autofollow: Davetiyeyle kaydolan kiÅŸiler sizi otomatik olarak takip eder
         avatar: En fazla %{size} olacak şekilde PNG, GIF veya JPG formatında yükleyiniz. %{dimensions}px büyüklüğüne indirgenecektir
+        bot: Bu hesap temelde otomatik eylemler gerçekleştirir ve izlenmeyebilir
+        context: Filtrenin geçerli olması gereken bir veya daha fazla içerik
+        current_password: Güvenlik nedeniyle lütfen şu anki hesabın parolasını girin
+        current_username: Onaylamak için lütfen şu anki hesabın kullanıcı adını girin
+        digest: Yalnızca uzun süre kullanılmadığında ve yalnızca yokluğunda kişisel mesajlar aldıysanız gönderilir
+        discoverable: Profil dizini, hesabınızın daha geniş bir kitleye ulaşmasının başka bir yoludur
+        email: Onay e-postası gönderilecek
+        fields: Profilinizde tablo olarak görüntülenen en fazla 4 ögeye sahip olabilirsiniz
         header: En fazla %{size} olacak şekilde PNG, GIF veya JPG formatında yükleyiniz. %{dimensions}px büyüklüğüne indirgenecektir.
+        inbox_url: Kullanmak istediğiniz aktarıcının ön sayfasından URL'yi kopyalayın
+        irreversible: Filtre uygulanmış gönderiler, filtre daha sonra çıkartılsa bile geri dönüşümsüz biçimde kaybolur
+        locale: Kullanıcı arayüzünün dili, e-postalar ve push bildirimleri
         locked: Takipçilerinizi manuel olarak kabul etmenizi ve gönderilerinizi varsayılan olarak sadece takipçilerinizin göreceği şekilde paylaşmanızı sağlar.
+        password: En az 8 karakter kullanın
+        phrase: Metnin büyük/küçük harf durumundan veya gönderinin içerik uyarısından bağımsız olarak eşleştirilecek
+        scopes: Uygulamanın erişmesine izin verilen API'ler. Üst seviye bir kapsam seçtiyseniz, bireysel kapsam seçmenize gerek yoktur.
+        setting_aggregate_reblogs: Yakın zamanda yinelenmiş gönderiler için yeni yinelemeler gösterme (yalnızca yeni alınan yinelemeleri etkiler)
+        setting_default_sensitive: Hassas medya varsayılan olarak gizlenir ve bir tıklama ile görüntülenebilir
+        setting_display_media_default: Hassas olarak işaretlenmiş medyayı gizle
+        setting_display_media_hide_all: Tüm medyayı gizle
+        setting_display_media_show_all: Hassas olarak işaretlenmiş medyayı göster
+        setting_hide_network: Takip edilenler ve takipçiler profilinizde gösterilmeyecek
+        setting_noindex: Herkese açık profilinizi ve durum sayfalarınızı etkiler
+        setting_show_application: Gönderi için kullandığınız uygulama, gönderilerinizin detaylı görünümünde gösterilecektir
+        setting_use_blurhash: Gradyenler gizli görsellerin renklerine dayanır, ancak detayları gizler
+        setting_use_pending_items: Zaman çizelgesi güncellemelerini, akışı otomatik olarak kaydırmak yerine bir tıklamanın arkasına gizleyin
+        username: Kullanıcı adınız %{domain} alanında benzersiz olacak
+        whole_word: Anahtar kelime veya kelime öbeği yalnızca alfasayısal olduğunda, yalnızca tüm sözcükle eşleşirse uygulanır
+      domain_allow:
+        domain: Bu alan adı, bu sunucudan veri alabilecek ve ondan gelen veri işlenecek ve saklanacaktır
+      featured_tag:
+        name: 'Bunlardan birini kullanmak isteyebilirsiniz:'
+      form_challenge:
+        current_password: Güvenli bir bölgeye giriyorsunuz
       imports:
         data: Diğer Mastodon sunucusundan dışarı aktardığınız CSV dosyası
+      invite_request:
+        text: Bu, başvurunuzu gözden geçirmemize yardımcı olacaktır
       sessions:
         otp: Telefonunuzdaki two-factor kodunuzu giriniz veya kurtarma kodlarınızdan birini giriniz.
+      tag:
+        name: Harflerin, örneğin daha okunabilir yapmak için, sadece büyük/küçük harf durumlarını değiştirebilirsiniz
+      user:
+        chosen_languages: İşaretlendiğinde, yalnızca seçilen dillerdeki karakterler genel zaman çizelgelerinde görüntülenir
     labels:
+      account:
+        fields:
+          name: Etiket
+          value: İçerik
+      account_alias:
+        acct: Eski hesabın tanıtıcısı
+      account_migration:
+        acct: Yeni hesabın tanıtıcısı
+      account_warning_preset:
+        text: Ön-ayar metni
+      admin_account_action:
+        include_statuses: Birdirilen gönderileri e-postaya dahil et
+        send_email_notification: E-postayla kullanıcıyı bilgilendir
+        text: Özel uyarı
+        type: Eylem
+        types:
+          disable: Devre dışı
+          none: Hiç birşey
+          silence: Sessiz
+          suspend: Hesap verilerini askıya alın ve geri alınamaz şekilde silin
+        warning_preset_id: Bir uyarı ön-ayarı kullan
       defaults:
+        autofollow: Hesabınızı takip etmeye davet edin
         avatar: Profil resmi
+        bot: Bu bir bot hesabı
+        chosen_languages: Dilleri filtrele
         confirm_new_password: Yeni parolanız (tekrar)
         confirm_password: Parolanız (tekrar)
+        context: İçeriği filtrele
         current_password: Mevcut parolanız
         data: Dosya
+        discoverable: Bu hesabı dizinde listele
         display_name: Görünen adınız
         email: E-posta adresiniz
+        expires_in: BitiÅŸ tarihi
+        fields: Profil Metaverisi
         header: Kapak resmi
+        inbox_url: Aktarıcı gelen kutusunun URL'si
+        irreversible: Gizlemek yerine bırak
         locale: Dil
         locked: Hesabımı kilitle
+        max_uses: Maksimum kullanım sayısı
         new_password: Yeni parolanız
         note: KiÅŸisel bilgiler
         otp_attempt: İki-faktörlü kod
         password: Parolanız
+        phrase: Anahtar kelime veya kelime öbeği
+        setting_advanced_layout: Gelişmiş web arayüzünü etkinleştir
+        setting_aggregate_reblogs: Zaman çizelgesindeki grup yinelemeleri
         setting_auto_play_gif: GIF'leri otomatik oynatt
         setting_boost_modal: Boost etmeden önce onay diyaloğu göster
+        setting_default_language: Gönderi dili
         setting_default_privacy: Gönderi gizliliği
+        setting_default_sensitive: Her zaman hassas medya olarak iÅŸaretle
+        setting_delete_modal: Bir gönderiyi silmeden önce onay iletişim kutusunu göster
+        setting_display_media: Medya görünümü
+        setting_display_media_default: Varsayılan
+        setting_display_media_hide_all: Tümünü gizle
+        setting_display_media_show_all: Tümünü göster
+        setting_expand_spoilers: İçerik uyarılarıyla işaretli gönderileri her zaman genişlet
+        setting_hide_network: Ağını gizle
+        setting_noindex: Arama motoru endekslemesini iptal et
+        setting_reduce_motion: Animasyonlarda hareketi azalt
+        setting_show_application: İçerik göndermek için kullanılan uygulamayı belirt
+        setting_system_font_ui: Sistemin varsayılan yazı tipini kullan
+        setting_theme: Site teması
+        setting_trends: Bugünün trendlerini göster
+        setting_unfollow_modal: Birini takip etmeden önce onay iletişim kutusunu göster
+        setting_use_blurhash: Gizli ortamlar için renkli gradyen göster
+        setting_use_pending_items: YavaÅŸ mod
         severity: Zorluk
         type: Dosya türü
         username: Kullanıcı adınız
+        username_or_email: Kullanıcı adı ya da email
+        whole_word: Tüm dünya
+      featured_tag:
+        name: Hashtag
       interactions:
         must_be_follower: Takipçim olmayan kişilerden gelen bildirimleri engelle
         must_be_following: Takip etmediÄŸim kiÅŸilerden gelen bildirimleri engelle
+        must_be_following_dm: Takip etmediğiniz kişilerin doğrudan ileti göndermesini engelle
+      invite:
+        comment: Yorum
+      invite_request:
+        text: Neden katılmak istiyorsun?
       notification_emails:
         digest: Özet e-postaları gönder
         favourite: Biri durumumu favorilerine eklediginde bana e-posta gönder
         follow: Biri beni takip ettiğinde bana e-posta gönder
         follow_request: Biri bana takip isteği gönderdiğinde, bana e-posta gönder
         mention: Biri benden bahsettiğinde, bana e-posta gönder
+        pending_account: Yeni bir hesap incelemesi gerektiğinde e-posta gönder
         reblog: Biri durumumu paylaştığında, bana e-posta gönder
+        report: Yeni bir rapor gönderildiğinde e-posta gönder
+        trending_tag: İncelenmemiş bir hashtag trend olduğunda e-posta gönder
+      tag:
+        listable: Bu etiketin aramalarda ve profil dizininde görünmesine izin ver
+        name: Hashtag
+        trendable: Bu etiketin trendlerin altında görünmesine izin ver
+        usable: Gönderilerin bu etiketi kullanmasına izin ver
     'no': Hayır
+    recommended: Önerilen
     required:
+      mark: "*"
       text: gerekli
     'yes': Evet
diff --git a/config/locales/simple_form.uk.yml b/config/locales/simple_form.uk.yml
index 35b20d8a98a6444d42222afad648f998806f0d50..a6ae14f6deb3ef39356f7785007714c53d7bfa48 100644
--- a/config/locales/simple_form.uk.yml
+++ b/config/locales/simple_form.uk.yml
@@ -2,48 +2,152 @@
 uk:
   simple_form:
     hints:
+      account_warning_preset:
+        text: Ви можете використовувати синтаксис дмухів, наприклад URLи, хештеґи та згадки
+      admin_account_action:
+        include_statuses: Користувач побачить, які дмухи призвели до адміністративних дій або попереджень
+        send_email_notification: Користувач отримає роз'яснення, що сталося з його обліковим записом
+        text_html: Необов'язково. Ви можете використовувати синтакс дмухів. Ви можете <a href="%{path}">додати шаблони попереджень</a>, щоб заощадити час
+        type_html: Оберіть, що робити з <strong>%{acct}</strong>
+        warning_preset_id: Необов'язково. Ви можете ще додати будь-який текст до кінця шаблону
       defaults:
+        autofollow: Люди, що зареєструвалися за вашим запрошенням, автоматично підпишуться на вас
         avatar: PNG, GIF, або JPG. Максимум - %{size}. Буде зменшено до %{dimensions}px
         bot: Цей аккаунт в основному виконує автоматичні дії та може не відстежуватіся
+        digest: Буде послано тільки після довгого періоду неактивності, та тільки якщо ви отримаєте персональне повідомлення у цей період
+        discoverable: Ще один шлях, за яким про вас можуть дізнатися користувачі — каталог профілів
+        email: Вам надійде електронний лист з підтвердженням
+        fields: До 4 елементів може бути відображено як таблиця у вашому профілі
         header: PNG, GIF, або JPG. Максимум - %{size}. Буде зменшено до %{dimensions}px
+        inbox_url: Скопіюйте інтернет-адресу з титульної сторінки ретранслятора
+        irreversible: Відсіяні дмухи зникнуть назавжди, навіть якщо фільтр потім буде знято
+        locale: Мова інтерфейсу, електронних листів та push-сповіщень
         locked: Буде вимагати від Вас самостійного підтверждення підписників, змінить приватність постів за замовчуванням на "тільки для підписників"
+        password: Не менше 8 символів
+        phrase: Шукає без врахування регістру у тексті дмуха або у його попередженні про вміст
+        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_use_blurhash: Градієнти, що базуються на кольорах прихованих медіа, але роблять нерозрізненними будь-які деталі
+        setting_use_pending_items: Не додавати нові повідомлення до стрічок миттєво. Показувати їх тільки після додаткового клацання.
+        username: Ваше ім'я користувача буде унікальним у %{domain}
+        whole_word: Якщо пошукове слово або фраза містить тільки літери та цифри, воно має співпадати цілком
+      domain_allow:
+        domain: Цей домен зможе отримувати дані з цього серверу. Вхідні дані будуть оброблені та збережені
+      featured_tag:
+        name: 'Можливо, ви захочете використовувати один з цих:'
       imports:
-        data: Файл CSV, экспортированный с другого узла Mastodon
+        data: Файл CSV, експортований з іншого сервера Mastodon
+      invite_request:
+        text: Це допоможе нам розглянути вашу заяву
       sessions:
         otp: Введите код двухфакторной аутентификации или используйте один из Ваших кодов восстановления.
+      tag:
+        name: Тут ви можете лише змінювати регістр літер, щоб підвищити читабельність
+      user:
+        chosen_languages: У глобальних стрічках будуть відображатися дмухи тільки обраними мовами
     labels:
+      account:
+        fields:
+          name: Позначка
+          value: Вміст
+      account_warning_preset:
+        text: Текст шаблону
+      admin_account_action:
+        include_statuses: Додати дмухи, на які ви скаржитесь, до електронного листа
+        send_email_notification: Сповістити користувача електронною поштою
+        text: Користувацьке попередження
+        type: Дія
+        types:
+          disable: Вимкнути
+          none: Нічого не робити
+          silence: Глушення
+          suspend: Призупинити та незворотньо видалити дані облікового запису
+        warning_preset_id: Використати шаблон попередження
       defaults:
+        autofollow: Запросити слідкувати за вашим обліковим записом
         avatar: Аватар
+        bot: Це обліковий запис бота
+        chosen_languages: Фільтрувати мови
         confirm_new_password: Підтвердіть новий пароль
         confirm_password: Підтвердіть пароль
+        context: Фільтр контексту
         current_password: Поточний пароль
         data: Дані
+        discoverable: Оприлюднити обліковий запис у каталозі
         display_name: Ім'я
         email: Email адреса
+        expires_in: Закінчується після
+        fields: Метадані профіля
         header: Заголовок
+        inbox_url: URL поштової скриньки ретранслятора
+        irreversible: Видалити назавжди, а не просто сховати
         locale: Мова
         locked: Зробити акаунт приватним
+        max_uses: Максимальна кількість використань
         new_password: Новий пароль
         note: Про Вас
         otp_attempt: Двофакторний код
         password: Пароль
+        phrase: Ключове слово або фраза
+        setting_advanced_layout: Увімкнути розширений web-інтерфейс
         setting_auto_play_gif: Автоматично відтворювати анімовані GIF
-        setting_boost_modal: Показывать диалог подтверждения перед продвижением
-        setting_default_privacy: Видимость постов
-        severity: Строгость
-        type: Тип импорта
-        username: Имя пользователя
+        setting_boost_modal: Відображати діалог підтвердження під час передмухування
+        setting_default_language: Мова дмухів
+        setting_default_privacy: Видимість постів
+        setting_default_sensitive: Позначити медіа як дражливе
+        setting_delete_modal: Показувати діалог підтвердження під час видалення дмуху
+        setting_display_media: Відображення медіа
+        setting_display_media_default: За промовчанням
+        setting_display_media_hide_all: Сховати всі
+        setting_display_media_show_all: Показати всі
+        setting_expand_spoilers: Завжди розгортати дмухи з попередженнями про вміст
+        setting_hide_network: Сховати вашу мережу
+        setting_noindex: Відмовитися від індексування пошуковими системами
+        setting_reduce_motion: Менше руху в анімаціях
+        setting_show_application: Відображати застосунки, використані для дмухання
+        setting_system_font_ui: Використовувати типовий системний шрифт
+        setting_theme: Тема сайту
+        setting_trends: Показати сьогоднішні тренди
+        setting_unfollow_modal: Відображати діалог підтвердження під час відписки від когось
+        setting_use_blurhash: Відображати барвисті градієнти замість прихованих медіа
+        setting_use_pending_items: Повільний режим
+        severity: Серйозність
+        type: Тип імпорту
+        username: Ім'я користувача
+        username_or_email: Ім'я користувача або електронна пошта
+        whole_word: Ціле слово
+      featured_tag:
+        name: Хештеґ
       interactions:
         must_be_follower: Блокувати сповіщення від непідписаних людей
         must_be_following: Блокувати сповіщення від людей, на яких ви не підписані
+        must_be_following_dm: Заблокувати прямі сповіщення від людей, на яких ви не підписані
+      invite_request:
+        text: Чому ви хочете приєднатися?
       notification_emails:
-        digest: Присылать дайджест по e-mail
+        digest: Надсилати дайджест електронною поштою
         favourite: Надсилати листа, коли комусь подобається Ваш статус
         follow: Надсилати листа, коли хтось підписується на Вас
         follow_request: Надсилати листа, коли хтось запитує дозволу на підписку
         mention: Надсилати листа, коли хтось згадує Вас
+        pending_account: Надсилати електронного листа, коли новий обліковий запис потребує розгляду
         reblog: Надсилати листа, коли хтось передмухує Ваш статус
+        report: Надсилати електронного листа, коли з'являється нова скарга
+        trending_tag: Надсилати електронного листа, коли нерозглянутий хештеґ стає популярним
+      tag:
+        listable: Дозволити появу цього хештеґа у каталозі профілів
+        name: Хештеґ
+        trendable: Дозволити появу цього хештеґа у списку популярних хештеґів
+        usable: Дозволити дмухам використовувати цей хештеґ
     'no': Ні
+    recommended: Рекомендовано
     required:
+      mark: "*"
       text: обов'язкове
     'yes': Так
diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml
index b781deb32b154664df3213152e04e72b24b0474a..9bff15d1d7b9abe062565b43bb809f19bba74afa 100644
--- a/config/locales/simple_form.zh-CN.yml
+++ b/config/locales/simple_form.zh-CN.yml
@@ -15,7 +15,6 @@ zh-CN:
         bot: 来自这个帐户的绝大多数操作都是自动进行的,并且可能无人监控
         context: 过滤器的应用场景
         digest: 仅在你长时间未登录,且收到了私信时发送
-        discoverable_html: <a href="%{path}" target="_blank">目录</a> 让大家能根据兴趣和活动寻找用户。需要至少 %{min_followers} 位关注者
         email: 我们会向你发送一封确认邮件
         fields: 这将会在个人资料页上以表格的形式展示,最多 4 个项目
         header: 文件大小限制 %{size},只支持 PNG、GIF 或 JPG 格式。图片分辨率将会压缩至 %{dimensions}px
@@ -34,6 +33,7 @@ zh-CN:
         setting_hide_network: 你关注的人和关注你的人将不会在你的个人资料页上展示
         setting_noindex: 此设置会影响到你的公开个人资料以及嘟文页面
         setting_show_application: 你用来发表嘟文的应用程序将会在你嘟文的详细内容中显示
+        setting_use_blurhash: 渐变是基于模糊后的隐藏内容生成的
         username: 你的用户名在 %{domain} 上是独特的
         whole_word: 如果关键词只包含字母和数字,就只会在整个词被匹配时才会套用
       featured_tag:
@@ -73,7 +73,7 @@ zh-CN:
         context: 过滤器场景
         current_password: 当前密码
         data: 数据文件
-        discoverable: 在本站用户资料目录中列出此账户
+        discoverable: 在本站用户目录中收录此账户
         display_name: 昵称
         email: 电子邮件地址
         expires_in: 失效时间
@@ -109,6 +109,7 @@ zh-CN:
         setting_system_font_ui: 使用系统默认字体
         setting_theme: 站点主题
         setting_unfollow_modal: 在取消关注前询问我
+        setting_use_blurhash: 将隐藏媒体显示为彩色渐变
         severity: 级别
         type: 导入数据类型
         username: 用户名
@@ -131,6 +132,8 @@ zh-CN:
         pending_account: 在有账户需要审核时,发送电子邮件提醒我
         reblog: 当有用户转嘟了我的嘟文时,发送电子邮件提醒我
         report: 在提交新举报时,发送电子邮件提醒我
+      tag:
+        listable: 允许这个话题标签在用户目录中显示
     'no': 否
     recommended: 推荐
     required:
diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml
index 4da117b61a2ba3baad683cd98dbe452cc404b208..fbbbff6ca2de7db468956b2ef9e0dcc43a484819 100644
--- a/config/locales/simple_form.zh-TW.yml
+++ b/config/locales/simple_form.zh-TW.yml
@@ -2,6 +2,10 @@
 zh-TW:
   simple_form:
     hints:
+      account_alias:
+        acct: 指定欲移動之帳戶的 使用者名稱@站台
+      account_migration:
+        acct: 指定欲移動至之帳戶的 使用者名稱@站台
       account_warning_preset:
         text: 您可使用嘟文語法,例如網址、「#」標籤和提及功能
       admin_account_action:
@@ -15,7 +19,6 @@ zh-TW:
         bot: 此帳戶主要執行自動操作且可能未被監控
         context: 應該套用過濾器的一項或多項內容
         digest: 僅在你長時間未登入且在未登入期間收到私訊時傳送
-        discoverable_html: <a href="%{path}" target="_blank">目錄</a> 讓使用者們能基於興趣與活動尋找帳戶。需要至少 %{min_followers} 位關注者
         email: 您將收到一封確認電子郵件
         fields: 您可在個人資料上有至多 4 個以表格形式顯示的項目
         header: 支援 PNG, GIF 或 JPG 圖片,檔案最大為 %{size},會按比例縮小成 %{dimensions} 像素
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index 75a43e322f6e345064c711626f886afc12738f9a..4975035fe2a7753ae98f6f87e69458a59f4723b8 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -11,21 +11,20 @@ sk:
     apps: Aplikácie
     apps_platforms: Uživaj Mastodon z iOSu, Androidu a iných platforiem
     browse_directory: Prehľadávaj databázu profilov, filtruj podľa záujmov
-    browse_public_posts: Prebádaj naživo prúd verejných príspevkov na Mastodone
+    browse_public_posts: Sleduj naživo prúd verejných príspevkov na Mastodone
     contact: Kontakt
     contact_missing: Nezadaný
     contact_unavailable: Neuvedený/á
     discover_users: Objavuj užívateľov
     documentation: Dokumentácia
-    extended_description_html: |
-      <h3>Pravidlá</h3>
-      <p>Žiadne zatiaľ uvedené nie sú</p>
-    federation_hint_html: S účtom na %{instance} budeš môcť následovať ľúdí na hociakom Mastodon serveri, ale aj inde.
-    generic_description: "%{domain} je jeden server v sieti"
+    federation_hint_html: S účtom na %{instance} budeš môcť následovať ľúdí na hociakom Mastodon serveri, ale aj na iných serveroch.
     get_apps: Vyskúšaj aplikácie
     hosted_on: Mastodon hostovaný na %{domain}
+    instance_actor_flash: |
+      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
-    privacy_policy: Ustanovenia o súkromí
+    privacy_policy: Zásady súkromia
     see_whats_happening: Pozoruj, čo sa deje
     server_stats: 'Serverové štatistiky:'
     source_code: Zdrojový kód
@@ -36,16 +35,22 @@ sk:
       other: príspevky
     status_count_before: Ktorí napísali
     tagline: Následuj kamarátov, a objavuj nových
-    terms: Podmienky užívania
+    terms: Podmienky užitia
+    unavailable_content: Nedostupný obsah
+    unavailable_content_description:
+      domain: Server
+      reason: 'Dôvod:'
     user_count_after:
       few: užívateľov
-      many: užívateľov
+      many: užívatelia
       one: užívateľ
-      other: uživatelia
+      other: užívateľov
     user_count_before: Domov pre
     what_is_mastodon: ÄŒo je Mastodon?
   accounts:
     choices_html: "%{name}vé voľby:"
+    endorsements_hint: Môžeš ukázať sledovaných užívateľov, s ktorými si spriaznený/á cez webové rozhranie, a tí tu budú zobrazení.
+    featured_tags_hint: Môžeš zvýrazniť určité haštagy, ktoré tu budú zobrazené.
     follow: Následuj
     followers:
       few: Sledovateľov
@@ -59,6 +64,7 @@ sk:
     media: Médiá
     moved_html: "%{name} účet bol presunutý na %{new_profile_link}:"
     network_hidden: Táto informácia nieje k dispozícii
+    never_active: Nikdy
     nothing_here: Nič tu nie je!
     people_followed_by: Ľudia, ktorých %{name} sleduje
     people_who_follow: Ľudia sledujúci %{name}
@@ -191,11 +197,12 @@ sk:
       username: Prezývka
       warn: Varuj
       web: Web
+      whitelisted: Na bielej listine
     action_logs:
       actions:
-        assigned_to_self_report: "%{name}pridelil/a hlásenie užívateľa %{target}sebe"
+        assigned_to_self_report: "%{name} pridelil/a hlásenie užívateľa %{target} sebe"
         change_email_user: "%{name} zmenil/a emailovú adresu užívateľa %{target}"
-        confirm_user: "%{name} potvrdil e-mailovú adresu používateľa %{target}"
+        confirm_user: "%{name} potvrdil emailovú adresu používateľa %{target}"
         create_account_warning: "%{name} poslal/a varovanie užívateľovi %{target}"
         create_custom_emoji: "%{name} nahral nový emoji %{target}"
         create_domain_block: "%{name} zablokoval doménu %{target}"
@@ -210,7 +217,7 @@ sk:
         disable_user: "%{name} zakázal prihlásenie pre používateľa %{target}"
         enable_custom_emoji: "%{name} povolil emoji %{target}"
         enable_user: "%{name} povolil prihlásenie pre používateľa %{target}"
-        memorialize_account: '%{name} zmenil účet %{target} na stránku "Navždy budeme spomínať"'
+        memorialize_account: "%{name} zmenil účet %{target} na pamätnú stránku"
         promote_user: "%{name} povýšil/a používateľa %{target}"
         remove_avatar_user: "%{name} odstránil/a %{target}ov avatár"
         reopen_report: "%{name} znovu otvoril/a hlásenie užívateľa %{target}"
@@ -226,19 +233,24 @@ sk:
       deleted_status: "(zmazaný príspevok)"
       title: Kontrólny záznam
     custom_emojis:
+      assign_category: Priraď kategóriu
       by_domain: Doména
       copied_msg: Miestna kópia emoji bola úspešne vytvorená
       copy: Kopíruj
       copy_failed_msg: Nebolo možné vytvoriť miestnu kópiu tohto emoji
+      create_new_category: Vytvor novú kategóriu
       created_msg: Emoji úspešne vytvorené!
       delete: Zmaž
       destroyed_msg: Emoji úspešne zničené!
       disable: Zakáž
+      disabled: Vypnuté
       disabled_msg: Emoji bolo úspešne zakázané
       emoji: Emotikony
       enable: Povoľ
+      enabled: Povolené
       enabled_msg: Emoji bolo úspešne povolené
       image_hint: PNG do 50KB
+      list: Zoznam
       listed: V zozname
       new:
         title: Pridaj nové, vlastné emoji
@@ -246,11 +258,14 @@ sk:
       shortcode: Skratka
       shortcode_hint: Aspoň 2 znaky, povolené sú alfanumerické, alebo podčiarkovník
       title: Vlastné emoji
+      uncategorized: Nezaradené
+      unlist: Nezaraď
       unlisted: Nie je na zozname
       update_failed_msg: Nebolo možné aktualizovať toto emoji
       updated_msg: Emoji bolo úspešne aktualizované!
       upload: Nahraj
     dashboard:
+      authorized_fetch_mode: Autorizovaný režim
       backlog: odložené aktivity
       config: Nastavenia
       feature_deletions: Vymazanie účtov
@@ -258,10 +273,13 @@ sk:
       feature_profile_directory: Katalóg profilov
       feature_registrations: Registrácie
       feature_relay: Federovací mostík
+      feature_spam_check: Proti spamu
       feature_timeline_preview: Náhľad časovej osi
       features: Vymoženosti
       hidden_service: Federácia so skrytými službami
       open_reports: otvorené hlásenia
+      pending_tags: haštagy čakajúce na posúdenie
+      pending_users: užívatelia čakajúci na posúdenie
       recent_users: Nedávni užívatelia
       search: Celofrázové vyhľadávanie
       single_user_mode: Jednouživateľské rozhranie
@@ -273,11 +291,18 @@ sk:
       week_interactions: Tohto-týždňové interakcie
       week_users_active: aktívni tento týždeň
       week_users_new: užívateľov počas tohto týždňa
+      whitelist_mode: Režim povolených
+    domain_allows:
+      add_new: Povolená doména
+      created_msg: Doména bola úspešne povolená
+      destroyed_msg: Doména bola odstránená zo zoznamu povolených
+      undo: Odober zo zoznamu povolených
     domain_blocks:
       add_new: Blokuj novú doménu
       created_msg: Doména je v štádiu blokovania
       destroyed_msg: Blokovanie domény bolo zrušené
       domain: Doména
+      edit: Uprav blokovanie domény
       existing_domain_block_html: Pre účet %{name} si už nahodil/a přísnejšie obmedzenie, najskôr ho teda musíš <a href="%{unblock_url}">odblokovať</a>.
       new:
         create: Vytvor blokovanie domény
@@ -288,6 +313,8 @@ sk:
           silence: Stíš
           suspend: Vylúč
         title: Nové blokovanie domény
+      private_comment: Súkromný komentár
+      public_comment: Verejný komentár
       reject_media: Odmietaj súbory s obrázkami, alebo videami
       reject_media_hint: Vymaže miestne uložené súbory médií a odmietne ich sťahovanie v budúcnosti. Nepodstatné pri vylúčení
       reject_reports: Zamietni hlásenia
@@ -309,6 +336,7 @@ sk:
         title: Zruš blokovanie domény %{domain}
         undo: Vráť späť
       undo: Odvolaj blokovanie domény
+      view: Ukáž blokovanie domén
     email_domain_blocks:
       add_new: Pridaj nový
       created_msg: Emailová doména bola úspešne pridaná do zoznamu zakázaných
@@ -334,6 +362,8 @@ sk:
         all: Všetky
         limited: Obmedzené
         title: Moderácia
+      private_comment: Súkromný komentár
+      public_comment: Verejný komentár
       title: Federácia
       total_blocked_by_us: Nami blokované
       total_followed_by_them: Nimi sledované
@@ -411,6 +441,16 @@ sk:
       custom_css:
         desc_html: Uprav vzhľad pomocou CSS, ktoré je načítané na každej stránke
         title: Vlastné CSS
+      default_noindex:
+        desc_html: Ovplyvňuje všetkých užívateľov, ktorí si toto nasavenie nezmenili sami
+        title: Vyraď užívateľov z indexovania vyhľadávačmi, ako východzie nastavenie
+      domain_blocks:
+        all: Všetkým
+        disabled: Nikomu
+        title: Ukáž blokované domény
+        users: Prihláseným, miestnym užívateľom
+      domain_blocks_rationale:
+        title: Ukáž zdôvodnenie
       hero:
         desc_html: Zobrazuje sa na hlavnej stránke. Doporučené je rozlišenie aspoň 600x100px. Pokiaľ nič nieje dodané, bude nastavený základný orázok serveru.
         title: Obrázok hrdinu
@@ -461,6 +501,9 @@ sk:
         desc_html: Môžeš si napísať svoje vlastné pravidla o súkromí, prevádzke, alebo aj iné legality. Môžeš tu používať HTML kód
         title: Vlastné pravidlá prevádzky
       site_title: Názov servera
+      spam_check_enabled:
+        desc_html: Mastodon môže sám stíšiť, a nahlásiť účty v závislosti od rozpoznania parametrov ako napríklad opakované rozosielanie nevyžiadanej komunikácie. Môže dôjsť aj k nesprávnej identifikácii.
+        title: Proti spamu
       thumbnail:
         desc_html: Používané pre náhľady cez OpenGraph a API. Doporučuje sa rozlišenie 1200x630px
         title: Miniatúra servera
@@ -468,12 +511,16 @@ sk:
         desc_html: Zobraziť verejnú nástenku na hlavnej stránke
         title: Náhľad nástenky
       title: Nastavenia stránky
+      trends:
+        desc_html: Verejne zobraz už schválené haštagy, ktoré práve trendujú
+        title: Populárne haštagy
     statuses:
       back_to_account: Späť na účet
       batch:
         delete: Vymaž
         nsfw_off: Označ ako nechúlostivé
         nsfw_on: Označ ako chúlostivé
+      deleted: Vymazané
       failed_to_execute: Nepodarilo sa vykonať
       media:
         title: Médiá
@@ -481,21 +528,24 @@ sk:
       no_status_selected: Žiadne príspevky neboli zmenené, keďže si žiadne nemal/a zvolené
       title: Príspevky na účte
       with_media: S médiami
-    subscriptions:
-      callback_url: Zdrojová adresa URL
-      confirmed: Potvrdené
-      expires_in: Vyprší do
-      last_delivery: Posledné doručenie
-      title: WebSub
-      topic: Téma
     tags:
-      accounts: Účty
-      hidden: Skryté
-      hide: Ukri od databázy
+      accounts_today: Jedinečných užívateľov za dnešok
+      accounts_week: Jedinečných užívateľov tento týždeň
+      breakdown: Rozpis dnešného využitia podľa zdroja
+      context: Súvis
+      directory: V zozname
+      in_directory: "%{count} v zozname"
+      last_active: Naposledy aktívny
+      most_popular: Najpopulárnejšie
+      most_recent: Najnovšie
       name: Haštag
+      review: Prehodnoť stav
+      reviewed: Zhodnotené
       title: Haštagy
-      unhide: Ukáž v databázi
-      visible: Viditeľné
+      trending_right_now: Práve populárne
+      unique_uses_today: "%{count} dnes prispievajú"
+      unreviewed: Neposúdené
+      updated_msg: Nastavenia haštagov boli úspešne aktualizované
     title: Spravovanie
     warning_presets:
       add_new: Pridaj nové
@@ -511,13 +561,21 @@ sk:
       body: "%{reporter} nahlásil/a %{target}"
       body_remote: Niekto z %{domain} nahlásil/a %{target}
       subject: Nové hlásenie pre %{instance} (#%{id})
+    new_trending_tag:
+      subject: Nový haštag očakáva preverenie na %{instance} (#%{name})
+  aliases:
+    add_new: Vytvor alias
+    remove: Odpoj alias
   appearance:
     advanced_web_interface: Pokročilé webové rozhranie
+    advanced_web_interface_hint: 'Ak chceš využiť celkovú šírku tvojej obrazovky, pokročilé webové rozhranie ti umožňuje nastaviť mnoho rôznych stĺpcov, aby si videl/a toľko informácií naraz, koľko chceš: Domov, oboznámenia, federovanú časovú os, a ľubovolný počet zoznamov, či haštagov.'
     animations_and_accessibility: Animácie a prístupnosť
     confirmation_dialogs: Potvrdzovacie dialógy
+    discovery: Nájdenie
     sensitive_content: Chúlostivý obsah
   application_mailer:
     notification_preferences: Zmeň emailové voľby
+    salutation: "%{name},"
     settings: 'Zmeň emailové voľby: %{link}'
     view: 'Zobraziť:'
     view_profile: Zobraz profil
@@ -534,9 +592,13 @@ sk:
     apply_for_account: Vyžiadaj si pozvánku
     change_password: Heslo
     checkbox_agreement_html: Súhlasím s <a href="%{rules_path}" target="_blank">pravidlami servera</a>, aj s <a href="%{terms_path}" target="_blank">prevoznými podmienkami</a>
-    confirm_email: Potvrď email
+    checkbox_agreement_without_rules_html: Súhlasím s <a href="%{terms_path}" target="_blank">podmienkami užívania</a>
     delete_account: Vymaž účet
     delete_account_html: Pokiaľ chceš svoj účet odtiaľto vymazať, môžeš tak <a href="%{path}">urobiť tu</a>. Budeš požiadaný/á o potvrdenie tohto kroku.
+    description:
+      prefix_invited_by_user: "@%{name} ťa pozýva na tento Mastodon server!"
+      prefix_sign_up: Zaregistruj sa na Mastodone už dnes!
+      suffix: S pomocou účtu budeš môcť následovať ľudí, posielať príspevky, a vymienať si správy s užívateľmi na hociakom Mastodon serveri, ale aj na iných serveroch!
     didnt_get_confirmation: Neobdržal/a si kroky na potvrdenie?
     forgot_password: Zabudnuté heslo?
     invalid_reset_password_token: Token na obnovu hesla vypršal. Prosím vypítaj si nový.
@@ -551,6 +613,16 @@ sk:
     reset_password: Obnov heslo
     security: Zabezpečenie
     set_new_password: Nastav nové heslo
+    setup:
+      email_below_hint_html: Ak je nižšie uvedená emailová adresa nesprávna, môžeš ju zmeniť a dostať nový potvrdzovací email.
+      email_settings_hint_html: Potvrdzovací email bol odoslaný na %{email}. Ak táto emailová adresa nieje správna, môžeš si ju zmeniť v nastaveniach účtu.
+      title: Nastavenie
+    status:
+      account_status: Stav účtu
+      confirming: Čaká sa na dokončenie potvrdenia emailom.
+      functional: Tvoj účet je plne funkčný.
+      pending: Tvoja žiadosť čaká na schvílenie od nášho týmu. Môže to chviľu potrvať. Ak bude tvoja žiadosť schválená, dostaneš o tom email.
+      redirecting_to: Tvoj účet je neaktívny, lebo v súčasnosti presmerováva na %{acct}.
     trouble_logging_in: Problém s prihlásením?
   authorize_follow:
     already_following: Tento účet už následuješ
@@ -563,6 +635,10 @@ sk:
       return: Ukáž užívateľov profil
       web: Prejdi do siete
     title: Následuj %{acct}
+  challenge:
+    confirm: Pokračuj
+    invalid_password: Nesprávne heslo
+    prompt: Pre pokračovanie potvrď svoje heslo
   datetime:
     distance_in_words:
       about_x_hours: "%{count}hod"
@@ -578,28 +654,28 @@ sk:
       x_months: "%{count}mesiace"
       x_seconds: "%{count}sek"
   deletes:
-    bad_password_msg: Dobrý pokus, hakeri! Nesprávne heslo
     confirm_password: Napíšte svoje terajšie heslo pre overenie vašej identity
-    description_html: Týmto <strong> natrvalo, nenavrátiteľne </strong> vymažeš obsah tvojho účtu, a deaktivuješ ho. Tvoja prezývka ale ostane rezervovaná ako prevencia pred budúcimi impersonáciami.
+    confirm_username: Zadaj svoju prezývku, na potvrdenie úkonu
     proceed: Vymaž účet
     success_msg: Tvoj účet bol úspešne vymazaný
-    warning_html: Iba vymazanie obsahu z tohto konkrétneho serveru je zaručené. Obsah, ktorý bol zdieľaný široko-ďaleko pravdepodobne zanechá nejaké stopy. Servery ktoré sú offline a tie ktoré ignorujú tvoje zmeny teda nezaktualizujú svoje databázy.
-    warning_title: Dostupnosť rozšírovaného obsahu
+    warning:
+      before: 'Než budeš pokračovať, prosím pozorne si prečítaj tieto poznámky:'
+      caches: Obsah, ktorý bol predčítaný inými servermi môže zanechať pozostatky
+      data_removal: Tvoje príspevky a iné dáta budú natrvalo odstránené
+      more_details_html: Pre viac podrobností, pozri <a href="%{terms_path}">zásady súkromia</a>.
+      username_available: Tvoje užívateľské meno bude znova dostupné
+      username_unavailable: Tvoja prezývka ostane neprístupná
   directories:
     directory: Katalóg profilov
-    enabled: Momentálne si uvedený/á na zozname profilov.
-    enabled_but_waiting: Vyjadril/a si záujem o uvedenie na zozname profilov, lenže ešte nemáš minimálny vyžadovaný počet následovateľov (%{min_followers}), aby si tam bol/a uveden/á.
     explanation: Pátraj po užívateľoch podľa ich záujmov
     explore_mastodon: Prebádaj %{title}
-    how_to_enable: Momentálne niesi zaradený/á do verejnej profilovej databázy. Prihlásiť sa môžeš nižšie. Použi haštagy vo svojom biografickom popise na profile, ak chceš byť uvedený/á aj pod konkrétnými haštagmi!
-    people:
-      few: "%{count} ľudí"
-      many: "%{count} ľudí"
-      one: "%{count} človek"
-      other: "%{count} ľudia"
+  domain_validator:
+    invalid_domain: nieje správny tvar domény
   errors:
+    '400': Požiadavka, ktorú si odoslal/a, bola buď nesprávna, alebo znehodnotená.
     '403': Nemáš povolenie pre zobrazenie tejto stránky.
     '404': Stránka ktorú hľadáš nieje tu.
+    '406': Táto stránka nie je dostupná v požadovanom formáte.
     '410': Stránka ktorú si tu hľadal/a sa tu už viac nenachádza.
     '422':
       content: Bezpečtnostné overenie zlyhalo. Blokuješ cookies?
@@ -608,6 +684,7 @@ sk:
     '500':
       content: Ospravedlňujem sa. Niečo sa pokazilo na našom konci.
       title: Táto stránka nieje v poriadku
+    '503': Táto stránka nemôže byť načítaná, kvôli dočasnému výpadku servera.
     noscript_html: Aby bolo možné používať Mastodon web aplikáciu, povoľ prosím JavaScript. Alebo skús jednu z <a href="%{apps_path}"> aplikácii </a> dostupných pre vašu platformu.
   existing_username_validator:
     not_found: nepodarilo sa nájsť miestného užívateľa s takouto prezývkou
@@ -651,18 +728,34 @@ sk:
     developers: Vývojári
     more: Viac…
     resources: Podklady
+    trending_now: Teraz populárne
   generic:
     all: Všetko
     changes_saved_msg: Zmeny boli úspešne uložené!
     copy: Kopíruj
+    no_batch_actions_available: Na tejto stránke niesú k dispozícii žiadne hromadné akcie
     order_by: Zoraď podľa
     save_changes: Ulož zmeny
+    validation_errors:
+      few: Niečo ešte nieje celkom v poriadku! Prosím skontroluj %{count} chýb uvedených nižšie
+      many: Niečo ešte nieje celkom v poriadku! Prosím skontroluj %{count} chýb uvedených nižšie
+      one: Niečo ešte nieje celkom v poriadku! Prosím skontroluj chybu uvedenú nižšie
+      other: Niečo ešte nieje celkom v poriadku! Prosím skontroluj %{count} chyby uvedené nižšie
+  html_validator:
+    invalid_markup: 'obsahuje neplatný HTML kód: %{error}'
   identity_proofs:
     active: Aktívne
     authorize: Áno, povoľ
     authorize_connection_prompt: Povoliť toto kryptografické prepojenie?
     errors:
       failed: Kryptografické prepojenie sa nepodarilo. Prosím skús to znova z %{provider}.
+      keybase:
+        invalid_token: Keybase tokeny sú hašovaniami podpisov a musia mať 66 znakov
+        verification_failed: Keybase nerozpoznáva tento token ako podpis od Keybase užívateľa menom %{kb_username}. Prosím skús to znova cez Keybase.
+      wrong_user: Nemožno vytvoriť overenie pre %{proving}, pokiaľ si prihlásený/á ako %{current}. Prihlás sa za %{proving} a skús to znova.
+    explanation_html: |-
+      Tu si môžeš kryptograficky prepojiť svoje iné identity, ako napríklad tvoj profil na Keybase.
+      Umožňuje to ostatním ľudom posielať ti enkryptované správy a veriť obsahu ktorý im pošleš ty.
     i_am_html: Na %{service} som %{username}.
     identity: Identita
     inactive: Neaktívne
@@ -718,9 +811,24 @@ sk:
       too_many: Nemôžeš priložiť viac ako 4 súbory
   migrations:
     acct: prezývka@doména nového účtu
-    currently_redirecting: 'Tvoj profil má nastavené presmerovanie na:'
-    proceed: Uložiť
-    updated_msg: Tvoje nastavenia pre presmerovanie účtu boli úspešne aktualizované!
+    cancel: Zruš presmerovanie
+    cancelled_msg: Presmerovanie úspešne zrušené.
+    errors:
+      missing_also_known_as: neodkazuje spätne na tento účet
+      move_to_self: nemôže to byť tvoj súčasný účet
+      not_found: nebolo možné nájsť
+      on_cooldown: Si v spánkovom stave
+    followers_count: Následovatelia v čase presunu
+    incoming_migrations: Presúvam sa z iného účtu
+    not_redirecting: Tvoj účet v súčasnosti nepresmerováva na žiaden iný účet.
+    past_migrations: Predošlé presuny
+    proceed_with_move: Presuň sledovateľov
+    redirecting_to: Tvoj účet presmerováva na %{acct}.
+    set_redirect: Nastav presmerovanie
+    warning:
+      backreference_required: Nový účet musí byť najskôr nastavený tak, aby spätne odkazoval na tento
+      before: 'Než budeš pokračovať, prosím pozorne si prečítaj tieto poznámky:'
+      other_data: Žiadne iné dáta nebudú presunuté automaticky
   moderation:
     title: Moderovanie
   notification_mailer:
@@ -728,6 +836,16 @@ sk:
       action: Zobraziť všetky notifikácie
       body: Tu nájdete krátky súhrn správ ktoré ste zmeškali od svojej poslednj návštevi od %{since}
       mention: "%{name} ťa spomenul/a v:"
+      new_followers_summary:
+        few: A ešte, kým si bol/a preč, si získal/a %{count} nových následovateľov! Hurá!
+        many: A ešte, kým si bol/a preč, si získal/a %{count} nových následovateľov! Hurá!
+        one: A ešte, kým si bol/a preč, si získal/a jedného nového následovateľa! Hurá!
+        other: A ešte, kým si bol/a preč, si získal/a %{count} nových následovateľov! Hurá!
+      subject:
+        few: "%{count} nových oboznámení od tvojej poslednej návštevy \U0001F418"
+        many: "%{count} nových oboznámení od tvojej poslednej návštevy \U0001F418"
+        one: "Jedno nové oboznámenie od tvojej poslednej návštevy \U0001F418"
+        other: "%{count} nové oboznámenia od tvojej poslednej návštevy \U0001F418"
       title: Zatiaľ čo si bol/a preč…
     favourite:
       body: 'Tvoj príspevok bol uložený medzi obľúbené užívateľa %{name}:'
@@ -768,6 +886,7 @@ sk:
       too_many_options: nemôže zahŕňať viac ako %{max} položiek
   preferences:
     other: Ostatné
+    posting_defaults: Východiskové nastavenia príspevkov
     public_timelines: Verejné časové osi
   relationships:
     activity: Aktivita účtu
@@ -778,6 +897,7 @@ sk:
     mutual: Spoločné
     primary: Hlavné
     relationship: Vzťah
+    remove_selected_domains: Vymaž všetkých následovateľov z vybraných domén
     remove_selected_followers: Odstráň vybraných následovatrľov
     remove_selected_follows: Prestaň sledovať vybraných užívateľov
     status: Stav účtu
@@ -798,10 +918,6 @@ sk:
     reply:
       proceed: Pokračuj odpovedaním
       prompt: 'Chceš odpovedať na tento príspevok:'
-  remote_unfollow:
-    error: Chyba
-    title: Názov
-    unfollowed: Už nesleduješ
   scheduled_statuses:
     over_daily_limit: Prekročil/a si denný limit %{limit} predplánovaných príspevkov
     over_total_limit: Prekročil/a si limit %{limit} predplánovaných príspevkov
@@ -856,17 +972,37 @@ sk:
   statuses:
     attached:
       description: 'Priložené: %{attached}'
+      image:
+        few: "%{count} obrázkov"
+        many: "%{count} obrázkov"
+        one: "%{count} obrázok"
+        other: "%{count} obrázky"
+      video:
+        few: "%{count} videí"
+        many: "%{count} videí"
+        one: "%{count} video"
+        other: "%{count} videá"
     boosted_from_html: Vyzdvihnuté od %{acct_link}
     content_warning: 'Varovanie o obsahu: %{warning}'
+    disallowed_hashtags:
+      few: 'obsah nepovolených haštagov: %{tags}'
+      many: 'obsah nepovolených haštagov: %{tags}'
+      one: 'obsahoval nepovolený haštag: %{tags}'
+      other: 'obsahoval nepovolené haštagy: %{tags}'
     language_detection: Zisti automaticky
     open_in_web: Otvor v okne na webe
     over_character_limit: limit %{max} znakov bol presiahnutý
     pin_errors:
       limit: Už si si pripol ten najvyšší možný počet hlášok
       ownership: Nieje možné pripnúť hlášku od niekoho iného
-      private: Neverejné príspevky nemôžu byť pripnuté
+      private: Neverejný príspevok nemôže byť pripnutý
       reblog: Vyzdvihnutie sa nedá pripnúť
     poll:
+      total_votes:
+        few: "%{count} hlasov"
+        many: "%{count} hlasov"
+        one: "%{count} hlas"
+        other: "%{count} hlasy"
       vote: Hlasuj
     show_more: Ukáž viac
     sign_in_to_participate: Prihlás sa pre zapojenie do diskusie
@@ -882,6 +1018,8 @@ sk:
     pinned: Pripnutý príspevok
     reblogged: vyzdvihnutý
     sensitive_content: Senzitívny obsah
+  tags:
+    does_not_match_previous_name: nezhoduje sa s predošlým názvom
   terms:
     body_html: |
       <h2>Podmienky súkromia</h2>
@@ -944,6 +1082,7 @@ sk:
         silence: Kým máš účet obmedzený, tvoje príspevky na tomto serveri uvidia iba tí ľudia, ktorí ťa už následujú, a môžeš byť vylúčený/á z rôznych verejných záznamov. Ostatní ťa však stále budú môcť následovať manuálne.
         suspend: Tvoj účet bol vylúčený, a všetky tvoje príspevky a nahraté médiálné súbory boli nenávratne zmazané z tohto serveru, a zo serverov na ktorých si mal následovateľov.
       review_server_policies: Prehodnoť pravidlá servera
+      statuses: 'Konkrétne kvôli:'
       subject:
         disable: Tvoj účet %{acct} bol zamrazený
         none: Varovanie pre %{acct}
@@ -956,7 +1095,7 @@ sk:
         suspend: Tvoj účet bol vylúčený
     welcome:
       edit_profile_action: Nastav profil
-      edit_profile_step: Profil si môžeš prispôsobiť nahratím portrétu a hlavičky, môžeš upraviť svoje meno a viac. Pokiaľ chceš preverovať nových následovateľov predtým než ťa budú môcť sledovať, môžeš uzamknúť svoj účet.
+      edit_profile_step: Profil si môžeš prispôsobiť nahratím portrétu a záhlavia, môžeš upraviť svoje meno a viac. Pokiaľ chceš preverovať nových následovateľov predtým než ťa budú môcť sledovať, môžeš uzamknúť svoj účet.
       explanation: Tu nájdeš nejaké tipy do začiatku
       final_action: Začni prispievať
       final_step: 'Začni písať! Aj bez následovateľov budú tvoje verejné príspevky videné ostatnými, napríklad na miestnej osi a pod haštagmi. Ak chceš, môžeš sa ostatným predstaviť pod haštagom #introductions.'
diff --git a/config/locales/sl.yml b/config/locales/sl.yml
index 85e167ca9d8a2fc3a6c63a1d97dfe63488c4af3b..22b58e7b629fd7066b3f5e9fcb8f5355482e7d07 100644
--- a/config/locales/sl.yml
+++ b/config/locales/sl.yml
@@ -1,22 +1,32 @@
 ---
 sl:
   about:
-    about_hashtag_html: To so javni tuti, označeni z <strong>#%{hashtag}</strong>. Z njimi se lahko povežete, če imate račun kjerkoli v fediversu.
+    about_hashtag_html: To so javni tuti, označeni z <strong>#%{hashtag}</strong>. Z njimi se lahko povežete, če imate račun kjerkoli v fediverse-u.
     about_mastodon_html: Mastodon je socialno omrežje, ki temelji na odprtih spletnih protokolih in prosti ter odprtokodni programski opremi. Je decentraliziran, kot e-pošta.
     about_this: O Mastodonu
+    active_count_after: dejaven
+    active_footnote: Aktivni mesečni uporabniki (AMU)
     administered_by: 'Upravlja:'
+    api: API
     apps: Mobilne aplikacije
+    apps_platforms: Uporabljajte Mastodon iz iOS, Android ali iz drugih platform
+    browse_directory: Brskajte po imeniku profilov in filtriranje po interesih
+    browse_public_posts: Brskajte javnih objav v živo na Mastodonu
     contact: Kontakt
     contact_missing: Ni nastavljeno
     contact_unavailable: Ni na voljo
+    discover_users: Odkrijte uporabnike
     documentation: Dokumentacija
-    extended_description_html: |
-      <h3>Dober prostor za pravila</h3>
-      <p>Razširjen opis še ni bil nastavljen.</p>
-    generic_description: "%{domain} je en strežnik v omrežju"
+    federation_hint_html: Z računom na %{instance} boste lahko spremljali ljudi na kateremkoli Mastodon strežniku.
+    get_apps: Poskusite mobilno aplikacijo
     hosted_on: Mastodon gostuje na %{domain}
-    learn_more: Spoznaj več
-    privacy_policy: Politika zasebnosti
+    instance_actor_flash: |
+      Ta račun je navidezni igralec, ki predstavlja strežnik in ne posameznega uporabnika.
+      Uporablja se za namene federacije in se ne blokira, če ne želite blokirati celotne instance. V tem primeru blokirajte domeno.
+    learn_more: Nauči se več
+    privacy_policy: Pravilnik o zasebnosti
+    see_whats_happening: Poglejte, kaj se dogaja
+    server_stats: 'Statistika strežnika:'
     source_code: Izvorna koda
     status_count_after:
       few: stanja
@@ -24,12 +34,13 @@ sl:
       other: stanj
       two: stanja
     status_count_before: Ki so avtorji
+    tagline: Sledite prijateljem in odkrijte nove
     terms: Pogoji storitve
     user_count_after:
       few: uporabniki
       one: uporabnik
       other: uporabnikov
-      two: uporabniki
+      two: uporabnika
     user_count_before: Dom za
     what_is_mastodon: Kaj je Mastodon?
   accounts:
@@ -38,56 +49,61 @@ sl:
     followers:
       few: Sledilci
       one: Sledilec
-      other: Sledilci
-      two: Sledilci
+      other: Sledilcev
+      two: Sledilca
     following: Sledim
     joined: Se je pridružil na %{date}
-    last_active: zadnji aktivni
+    last_active: zadnja dejavnost
     link_verified_on: Lastništvo te povezave je bilo preverjeno na %{date}
-    media: Medij
+    media: Mediji
     moved_html: "%{name} se je prestavil na %{new_profile_link}:"
-    network_hidden: Te informacije niso na voljo
-    nothing_here: Nič ni tukaj!
+    network_hidden: Ta informacija ni na voljo
+    nothing_here: Tukaj ni ničesar!
     people_followed_by: Ljudje, ki jim sledi %{name}
     people_who_follow: Ljudje, ki sledijo %{name}
     pin_errors:
       following: Verjetno že sledite osebi, ki jo želite potrditi
     posts:
-      few: Trob
-      one: Trob
-      other: Trob
-      two: Trob
-    posts_tab_heading: Trobi
+      few: Tuti
+      one: Tut
+      other: Tutov
+      two: Tuta
+    posts_tab_heading: Tuti
     posts_with_replies: Tuti in odgovori
     reserved_username: Uporabniško ime je zasedeno
     roles:
       admin: Skrbnik
       bot: Robot
+      moderator: Mod
+    unavailable: Profil ni na voljo
     unfollow: Prenehaj slediti
   admin:
     account_actions:
       action: Izvedi dejanje
-      title: Izvedi moderirano dejanje %{acct}
+      title: Izvedi moderirano dejanje za %{acct}
     account_moderation_notes:
       create: Pusti opombo
-      created_msg: Uspešno ustvarjena opomba moderiranja!
+      created_msg: Moderirana opomba je uspešno ustvarjena!
       delete: Izbriši
       destroyed_msg: Moderirana opomba je uspešno uničena!
     accounts:
-      are_you_sure: Ali si prepričan?
+      approve: Odobri
+      approve_all: Odobri vse
+      are_you_sure: Ali ste prepričani?
+      avatar: Podoba
       by_domain: Domena
       change_email:
         changed_msg: E-pošta računa je uspešno spremenjena!
-        current_email: Trenutna E-pošta
-        label: Spremeni E-pošto
-        new_email: Nova E-pošta
-        submit: Spremeni E-pošto
-        title: Spremeni E-pošto za %{username}
+        current_email: Trenutna e-pošta
+        label: Spremeni e-pošto
+        new_email: Nova e-pošta
+        submit: Spremeni e-pošto
+        title: Spremeni e-pošto za %{username}
       confirm: Potrdi
       confirmed: Potrjeno
       confirming: Potrjujem
       deleted: Izbrisano
-      demote: Ponižaj
+      demote: Degradiraj
       disable: Onemogoči
       disable_two_factor_authentication: Onemogoči 2FA
       disabled: Onemogočeno
@@ -95,44 +111,50 @@ sl:
       domain: Domena
       edit: Uredi
       email: E-pošta
-      email_status: Stanje E-pošte
+      email_status: Stanje e-pošte
       enable: Omogoči
       enabled: Omogočeno
-      feed_url: URL vir
+      feed_url: URL vira
       followers: Sledilci
-      followers_url: URL sledilci
+      followers_url: URL sledilcev
       follows: Sledi
       header: Glava
-      inbox_url: URl v mapi "Prejeto"
+      inbox_url: URL mape "Prejeto"
       invited_by: Povabljen od
+      ip: IP
       joined: Pridružil
       location:
         all: Vse
-        local: Lokalno
+        local: Lokalni
         remote: Oddaljeni
         title: Lokacija
       login_status: Stanje prijave
-      media_attachments: Medijske priloge
+      media_attachments: Predstavnostne priloge
       memorialize: Spremenite v spomin
       moderation:
         active: Dejaven
         all: Vse
+        pending: Na čakanju
         silenced: Utišan
         suspended: Suspendiran
         title: Moderiranje
       moderation_notes: Opombe moderiranja
-      most_recent_activity: Zadnja aktivnost
+      most_recent_activity: Zadnja dejavnost
       most_recent_ip: Zadnji IP
+      no_account_selected: Noben račun ni bil spremenjen, ker ni bil izbran noben
       no_limits_imposed: Brez omejitev
-      not_subscribed: Ni naročeno
-      outbox_url: URl za pošiljanje
-      perform_full_suspension: Začasno ustavi
+      not_subscribed: Ni naročen
+      outbox_url: URL za pošiljanje
+      pending: ÄŒakanje na pregled
+      perform_full_suspension: Suspendiraj
       profile_url: URL profila
-      promote: Spodbujanje
+      promote: Promoviraj
       protocol: Protokol
       public: Javen
       push_subscription_expires: Naročnina PuSH preteče
       redownload: Osveži profil
+      reject: Zavrni
+      reject_all: Zavrni vse
       remove_avatar: Odstrani podobo
       remove_header: Odstrani glavo
       resend_confirmation:
@@ -145,9 +167,11 @@ sl:
       role: Dovoljenja
       roles:
         admin: Skrbnik
+        moderator: Moderator
         staff: Osebje
         user: Uporabnik
-      search: Poišči
+      salmon_url: URL lososa
+      search: Iskanje
       shared_inbox_url: URL mape "Prejeto v skupni rabi"
       show:
         created_reports: Narejene prijave
@@ -157,6 +181,7 @@ sl:
       statuses: Stanja
       subscribe: Naroči
       suspended: Suspendiran
+      time_in_queue: ÄŒakanje v vrsti %{time}
       title: Računi
       unconfirmed_email: Nepotrjena e-pošta
       undo_silenced: Razveljavi utišanje
@@ -165,33 +190,34 @@ sl:
       username: Uporabniško ime
       warn: Opozori
       web: Splet
+      whitelisted: Na belem seznamu
     action_logs:
       actions:
         assigned_to_self_report: "%{name} je prijavil %{target} sebi"
         change_email_user: "%{name} je spremenil naslov e-pošte uporabnika %{target}"
         confirm_user: "%{name} je potrdil naslov e-pošte uporabnika %{target}"
         create_account_warning: "%{name} je poslal opozorilo %{target}"
-        create_custom_emoji: "%{name} je poslal nove emotikone %{target}"
+        create_custom_emoji: "%{name} je posodobil emotikone %{target}"
         create_domain_block: "%{name} je blokiral domeno %{target}"
         create_email_domain_block: "%{name} je dal na črni seznam e-pošto domene %{target}"
         demote_user: "%{name} je degradiral uporabnika %{target}"
-        destroy_custom_emoji: "%{name} je uničil emotikon %{target}"
+        destroy_custom_emoji: "%{name} je uničil emotikone %{target}"
         destroy_domain_block: "%{name} je odblokiral domeno %{target}"
         destroy_email_domain_block: "%{name} je dal na beli seznam e-pošto domene %{target}"
         destroy_status: "%{name} je odstranil stanje od %{target}"
         disable_2fa_user: "%{name} je onemogočil dvofaktorsko zahtevo za uporabnika %{target}"
-        disable_custom_emoji: "%{name} je onemogočil emotikon %{target}"
+        disable_custom_emoji: "%{name} je onemogočil emotikone %{target}"
         disable_user: "%{name} je onemogočil prijavo za uporabnika %{target}"
-        enable_custom_emoji: "%{name} je omogočil emotikon %{target}"
+        enable_custom_emoji: "%{name} je omogočil emotikone %{target}"
         enable_user: "%{name} je omogočil prijavo za uporabnika %{target}"
         memorialize_account: "%{name} je spremenil račun od %{target} v stran spominov"
-        promote_user: "%{name} je spodbudil uporabnika %{target}"
+        promote_user: "%{name} je promoviral uporabnika %{target}"
         remove_avatar_user: "%{name} je odstranil podobo od %{target}"
         reopen_report: "%{name} je ponovno odprl prijavo %{target}"
         reset_password_user: "%{name} je ponastavil geslo od uporabnika %{target}"
         resolve_report: "%{name} je razrešil prijavo %{target}"
         silence_account: "%{name} je utišal račun od %{target}"
-        suspend_account: "%{name} je začasno ustavil račun od %{target}"
+        suspend_account: "%{name} je suspendiral račun od %{target}"
         unassigned_report: "%{name} je nedodeljeno prijavil %{target}"
         unsilence_account: "%{name} je preklical utišanje računa od %{target}"
         unsuspend_account: "%{name} je aktiviral račun od %{target}"
@@ -201,9 +227,9 @@ sl:
       title: Dnevnik revizije
     custom_emojis:
       by_domain: Domena
-      copied_msg: Lokalna kopija emotikona je bila uspešno ustvarjena
+      copied_msg: Lokalna kopija emotikonov je bila uspešno ustvarjena
       copy: Kopiraj
-      copy_failed_msg: Lokalne kopije emotikona ni bilo mogoče ustvariti
+      copy_failed_msg: Lokalne kopije emotikonov ni bilo mogoče ustvariti
       created_msg: Emotikon je uspešno ustvarjen!
       delete: Izbriši
       destroyed_msg: Emotikon je uspešno uničen!
@@ -225,13 +251,15 @@ sl:
       updated_msg: Emotikon je uspešno posodobljen!
       upload: Pošlji
     dashboard:
-      backlog: Zaostala opravila
+      backlog: zaostala opravila
       config: Nastavitve
       feature_deletions: Brisanje računov
-      feature_invites: Poveza povabil
-      feature_profile_directory: Mapa profila
+      feature_invites: Povezave povabil
+      feature_profile_directory: Imenik profilov
       feature_registrations: Registracije
       feature_relay: Rele federacije
+      feature_spam_check: Anti-spam
+      feature_timeline_preview: Predogled časovnice
       features: Zmožnosti
       hidden_service: Federacija s skritimi storitvami
       open_reports: odprte prijave
@@ -241,22 +269,28 @@ sl:
       software: Programska oprema
       space: Uporaba prostora
       title: Nadzorna plošča
-      total_users: Skupaj uporabnikov
+      total_users: skupaj uporabnikov
       trends: Trendi
       week_interactions: interakcije ta teden
       week_users_active: aktivni ta teden
       week_users_new: uporabniki ta teden
+    domain_allows:
+      add_new: Dodaj domeno na beli seznam
+      created_msg: Domena je bila uspešno dodana na beli seznam
+      destroyed_msg: Domena je bila odstranjena iz belega seznama
+      undo: Odstrani iz belega seznama
     domain_blocks:
       add_new: Dodaj nov domenski blok
       created_msg: Domenski blok se sedaj obdeluje
       destroyed_msg: Domenski blok je bil razveljavljen
       domain: Domena
+      existing_domain_block_html: Uvedli ste strožje omejitve za %{name}, sedaj ga morate najprej <a href="%{unblock_url}">odblokirati</a>.
       new:
         create: Ustvari blok
         hint: Domenski blok ne bo preprečil ustvarjanja vnosov računov v zbirko podatkov, ampak bo retroaktivno in samodejno uporabil posebne metode moderiranja na teh računih.
         severity:
           desc_html: "<strong>Utišaj</strong> bo vse objave računa naredil nevidne vsem, ki jih ne sledijo. <strong>Suspendiraj</strong> bo odstranil vso vsebino, medije in podatke profila računa. Uporabi <strong>nič</strong>, če želite le zavrniti predstavnostne datoteke."
-          noop: Nič
+          noop: Brez
           silence: Utišaj
           suspend: Suspendiraj
         title: Nov domenski blok
@@ -271,13 +305,13 @@ sl:
         suspend: suspendirani
       show:
         affected_accounts:
-          few: "%{count} računov v bazi podatkov so prizadeti"
+          few: "%{count} računi v bazi podatkov so prizadeti"
           one: En račun v bazi podatkov je prizadet
-          other: "%{count} računov v bazi podatkov so prizadeti"
-          two: "%{count} računov v bazi podatkov so prizadeti"
+          other: "%{count} računov v bazi podatkov je prizadetih"
+          two: "%{count} računa v bazi podatkov so prizadeta"
         retroactive:
           silence: Prekliči utišanje za vse obstoječe račune iz te domene
-          suspend: Odsuspendiraj vse obstoječe račune iz te domene
+          suspend: Aktiviraj vse obstoječe račune iz te domene
         title: Razveljavi domenski blok za %{domain}
         undo: Razveljavi
       undo: Razveljavi domenski blok
@@ -290,17 +324,18 @@ sl:
       new:
         create: Dodaj domeno
         title: Nov vnos e-pošte na črni seznam
-      title: Črni seznam e-pošte
+      title: Črni seznam e-pošt
     followers:
       back_to_account: Nazaj na račun
       title: Sledilci od %{acct}
     instances:
+      by_domain: Domena
       delivery_available: Na voljo je dostava
       known_accounts:
-        few: "%{count} znanih računov"
+        few: "%{count} znani računi"
         one: "%{count} znan račun"
         other: "%{count} znanih računov"
-        two: "%{count} znanih računov"
+        two: "%{count} znana računa"
       moderation:
         all: Vse
         limited: Omejeno
@@ -317,15 +352,18 @@ sl:
         all: Vse
         available: Razpoložljivo
         expired: Potekel
+        title: Filter
       title: Povabila
+    pending_accounts:
+      title: "(%{count}) računov na čakanju"
     relays:
       add_new: Dodaj nov rele
       delete: Izbriši
-      description_html: "<strong>Rele federacije</strong> je posredniški strežnik, ki si izmenjuje velike količine javnih trobov med strežniki, ki so se naročili in objavili na njem. <strong>Majhnim in srednjim strežnikom lahko pomaga pri odkrivanju vsebine iz sistema fediverse</strong>, kar bi sicer zahtevalo, da lokalni uporabniki ročno sledijo druge osebe na oddaljenih strežnikih."
+      description_html: "<strong>Rele federacije</strong> je posredniški strežnik, ki si izmenjuje velike količine javnih tutov med strežniki, ki so se naročili in objavili na njem. <strong>Majhnim in srednjim strežnikom lahko pomaga pri odkrivanju vsebine iz sistema fediverse</strong>, kar bi sicer zahtevalo, da lokalni uporabniki ročno sledijo druge osebe na oddaljenih strežnikih."
       disable: Onemogoči
       disabled: Onemogočeno
       enable: Omogoči
-      enable_hint: Ko je omogočen, se bo vaš strežnik naročil na vse javne trobe iz tega releja in začel pošiljati javne trobe tega strežnika.
+      enable_hint: Ko je omogočen, se bo vaš strežnik naročil na vse javne tute iz tega releja in začel pošiljati javne tute tega strežnika.
       enabled: Omogočeno
       inbox_url: URL releja
       pending: ÄŒakanje na odobritev releja
@@ -345,7 +383,7 @@ sl:
       assign_to_self: Dodeli meni
       assigned: Dodeljen moderator
       comment:
-        none: Nič
+        none: Brez
       created_at: Prijavljeno
       mark_as_resolved: Označi kot rešeno
       mark_as_unresolved: Označi kot nerešeno
@@ -359,19 +397,19 @@ sl:
       report: 'Prijavi #%{id}'
       reported_account: Prijavljeni račun
       reported_by: Prijavljen od
-      resolved: Razrešeno
+      resolved: Razrešeni
       resolved_msg: Prijava je uspešno razrešena!
       status: Stanje
       title: Prijave
-      unassign: Odstopi
-      unresolved: Nerešeno
-      updated_at: Posodobljen
+      unassign: Odstopljeni
+      unresolved: Nerešeni
+      updated_at: Posodobljeni
     settings:
       activity_api_enabled:
         desc_html: Å tevilke lokalno objavljenih stanj, aktivnih uporabnikov in novih registracij na tedenskih seznamih
         title: Objavi združeno statistiko o dejavnosti uporabnikov
       bootstrap_timeline_accounts:
-        desc_html: Več uporabniških imen ločite z vejico. Deluje samo na lokalnih in odklenjenih računih. Privzeto, ko je prazno, pri vseh lokalnih skrbnikih.
+        desc_html: Več uporabniških imen ločite z vejico. Deluje samo na lokalnih in odklenjenih računih. Privzeto, ko je prazno, je pri vseh lokalnih skrbnikih.
         title: Privzeta sledenja za nove uporabnike
       contact_information:
         email: Poslovna e-pošta
@@ -379,24 +417,685 @@ sl:
       custom_css:
         desc_html: Spremeni videz z naloženim CSS na vsaki strani
         title: CSS po meri
+      hero:
+        desc_html: Prikazano na sprednji strani. Priporoča se vsaj 600x100px. Ko ni nastavljen, se vrne na sličico strežnika
+        title: Slika junaka
+      mascot:
+        desc_html: Prikazano na več straneh. Priporočena je najmanj 293 × 205 px. Ko ni nastavljen, se vrne na privzeto maskoto
+        title: Slika maskote
+      peers_api_enabled:
+        desc_html: Domene, na katere je ta strežnik naletel na fediverse-u
+        title: Objavi seznam odkritih strežnikov
+      preview_sensitive_media:
+        desc_html: Predogledi povezav na drugih spletiščih bodo prikazali sličico, tudi če je medij označen kot občutljiv
+        title: Prikaži občutljive medije v predogledih OpenGraph
+      profile_directory:
+        desc_html: Dovoli uporabnikom, da jih lahko odkrijejo
+        title: Omogoči imenik profilov
+      registrations:
+        closed_message:
+          desc_html: Prikazano na prvi strani, ko so registracije zaprte. Lahko uporabite oznake HTML
+          title: Sporočilo o zaprti registraciji
+        deletion:
+          desc_html: Dovoli vsakomur, da izbriše svoj račun
+          title: Odpri brisanje računa
+        min_invite_role:
+          disabled: Nihče
+          title: Dovoli vabila od
+      registrations_mode:
+        modes:
+          approved: Potrebna je odobritev za prijavo
+          none: Nihče se ne more prijaviti
+          open: Vsakdo se lahko prijavi
+        title: Način registracije
+      show_known_fediverse_at_about_page:
+        desc_html: Ko preklopite, bo prikazal tute vseh znanih fediverse-ov v predogledu. V nasprotnem primeru bodo prikazani samo lokalni tuti.
+        title: Pokaži znane fediverse-e v predogledu časovnice
+      show_staff_badge:
+        desc_html: Prikaži značko osebja na uporabniški strani
+        title: Prikaži značko osebja
+      site_description:
+        desc_html: Uvodni odstavek na API-ju. Opišite, zakaj je ta Mastodon strežnik poseben in karkoli pomembnega. Lahko uporabite HTML oznake, zlasti <code>&lt;a&gt;</code> in <code>&lt;em&gt;</code>.
+        title: Opis strežnika
+      site_description_extended:
+        desc_html: Dober kraj za vaš kodeks ravnanja, pravila, smernice in druge stvari, ki ločujejo vaš strežnik. Lahko uporabite oznake HTML
+        title: Razširjene informacije po meri
+      site_short_description:
+        desc_html: Prikazano v stranski vrstici in metaoznakah. V enem odstavku opišite, kaj je Mastodon in kaj naredi ta strežnik poseben.
+        title: Kratek opis strežnika
+      site_terms:
+        desc_html: Lahko napišete svojo pravilnik o zasebnosti, pogoje storitve ali druge pravne dokumente. Lahko uporabite oznake HTML
+        title: Pogoji storitve po meri
+      site_title: Ime strežnika
+      spam_check_enabled:
+        desc_html: Mastodon lahko samodejno utiša in samodejno prijavi račune, ki temeljijo na ukrepih, kot je odkrivanje računov, ki pošiljajo ponavljajoča se neželena sporočila. Lahko pride do zmot.
+        title: Anti-spam
+      thumbnail:
+        desc_html: Uporablja se za predogled prek OpenGrapha in API-ja. Priporočamo 1200x630px
+        title: Sličica strežnika
+      timeline_preview:
+        desc_html: Prikaži javno časovnico na ciljni strani
+        title: Predogled časovnice
+      title: Nastavitve strani
+    statuses:
+      back_to_account: Nazaj na stran računa
+      batch:
+        delete: Izbriši
+        nsfw_off: Označi, da ni občutljivo
+        nsfw_on: Označi, kot občutljivo
+      failed_to_execute: Ni bilo mogoče izvesti
+      media:
+        title: Mediji
+      no_media: Ni medijev
+      no_status_selected: Nobeno stanje ni bilo spremenjeno, ker ni bilo izbrano nobeno
+      title: Stanja računa
+      with_media: Z mediji
+    tags:
+      title: Ključniki
+    title: Upravljanje
+    warning_presets:
+      add_new: Dodaj novo
+      delete: Izbriši
+      edit: Uredi
+      edit_preset: Uredi prednastavitev opozoril
+      title: Upravljaj prednastavitev opozoril
+  admin_mailer:
+    new_pending_account:
+      body: Podrobnosti o novem računu so navedene spodaj. To aplikacijo lahko odobrite ali zavrnete.
+      subject: Nov račun za pregled na %{instance} (%{username})
+    new_report:
+      body: "%{reporter} je prijavil %{target}"
+      body_remote: Nekdo iz %{domain} je prijavil %{target}
+      subject: Nove prijave za %{instance} (#%{id})
+  appearance:
+    advanced_web_interface: Napredni spletni vmesnik
+    advanced_web_interface_hint: 'Če želite uporabiti celotno širino zaslona, vam napredni spletni vmesnik omogoča, da si nastavite več različnih stolpcev in da si hkrati ogledate toliko informacij, kot želite: domačo stran, obvestila, združeno časovnico, poljubno število seznamov in ključnikov.'
+    animations_and_accessibility: Animacije in dostopnost
+    confirmation_dialogs: Potrditvena okna
+    sensitive_content: Občutljiva vsebina
+  application_mailer:
+    notification_preferences: Spremenite e-poštne nastavitve
+    salutation: "%{name},"
+    settings: 'Spremenite e-poštne nastavitve: %{link}'
+    view: 'Pogled:'
+    view_profile: Ogled profila
+    view_status: Ogled stanja
+  applications:
+    created: Aplikacija je bila uspešno ustvarjena
+    destroyed: Aplikacija je bila uspešno izbrisana
+    invalid_url: Navedeni URL je neveljaven
+    regenerate_token: Obnovite dostopni žeton
+    token_regenerated: Dostopni žeton je bil uspešno regeneriran
+    warning: Bodite zelo previdni s temi podatki. Nikoli jih ne delite z nikomer!
+    your_token: Vaš dostopni žeton
+  auth:
+    apply_for_account: Zahtevajte povabilo
+    change_password: Geslo
+    checkbox_agreement_html: Strinjam se s <a href="%{rules_path}" target="_blank">pravili strežnika</a> in <a href="%{terms_path}" target="_blank">pogoji storitve</a>
+    checkbox_agreement_without_rules_html: Strinjam se s <a href="%{terms_path}" target="_blank">pogoji storitve</a>
+    delete_account: Izbriši račun
+    delete_account_html: Če želite izbrisati svoj račun, lahko nadaljujete <a href="%{path}">tukaj</a>. Prosili vas bomo za potrditev.
+    didnt_get_confirmation: Niste prejeli navodil za potrditev?
+    forgot_password: Ste pozabili svoje geslo?
+    invalid_reset_password_token: Žeton za ponastavitev gesla je neveljaven ali je potekel. Zahtevajte novo.
+    login: Prijava
+    logout: Odjava
+    migrate_account: Premakni se na drug račun
+    migrate_account_html: Če želite ta račun preusmeriti na drugega, ga lahko <a href="%{path}">nastavite tukaj</a>.
+    or_log_in_with: Ali se prijavite z
+    providers:
+      cas: CAS
+      saml: SAML
+    register: Vpis
+    registration_closed: "%{instance} ne sprejema novih članov"
+    resend_confirmation: Ponovno pošlji navodila za potrditev
+    reset_password: Ponastavi geslo
+    security: Varnost
+    set_new_password: Nastavi novo geslo
+    setup:
+      email_below_hint_html: Če spodnji e-poštni naslov ni pravilen, ga lahko spremenite tukaj in prejmete novo potrditveno e-pošto.
+      email_settings_hint_html: Potrditvena e-pošta je bila poslana na %{email}. Če ta e-poštni naslov ni pravilen, ga lahko spremenite v nastavitvah računa.
+      title: Nastavitev
+    status:
+      account_status: Stanje računa
+      confirming: Čakanje na potrditev e-pošte.
+      pending: Naše osebje preverja vašo prijavo. To lahko traja nekaj časa. Če bo vaša prijava odobrena, boste prejeli e-pošto.
+    trouble_logging_in: Težave pri prijavi?
+  authorize_follow:
+    already_following: Temu računu že sledite
+    error: Na žalost je prišlo do napake pri iskanju oddaljenega računa
+    follow: Sledi
+    follow_request: 'Prošnjo za sledenje se poslali:'
+    following: 'Uspeh! Zdaj sledite:'
+    post_follow:
+      close: Lahko pa tudi zaprete to okno.
+      return: Prikaži uporabnikov profil
+      web: Pojdi na splet
+    title: Sledi %{acct}
+  datetime:
+    distance_in_words:
+      about_x_hours: "%{count}h"
+      about_x_months: "%{count}mo"
+      about_x_years: "%{count}y"
+      almost_x_years: "%{count}y"
+      half_a_minute: Pravkar
+      less_than_x_minutes: "%{count}m"
+      less_than_x_seconds: Pravkar
+      over_x_years: "%{count}y"
+      x_days: "%{count}d"
+      x_minutes: "%{count}m"
+      x_months: "%{count}mo"
+      x_seconds: "%{count}s"
+  deletes:
+    confirm_password: Vnesite svoje trenutno geslo, da potrdite svojo identiteto
+    proceed: Izbriši račun
+    success_msg: Vaš račun je bil uspešno izbrisan
+  directories:
+    directory: Imenik profilov
+    explanation: Odkrijte uporabnike glede na njihove interese
+    explore_mastodon: Razišči %{title}
   errors:
-    '403': You don't have permission to view this page.
-    '404': The page you are looking for isn't here.
-    '410': The page you were looking for doesn't exist here anymore.
-    '422': 
-    '429': Throttled
-    '500': 
+    '400': The request you submitted was invalid or malformed.
+    '403': Nimate dovoljenja za ogled te strani.
+    '404': Iskana stran ne obstaja.
+    '406': This page is not available in the requested format.
+    '410': Iskana stran ne obstaja več.
+    '422':
+      content: Varnostno preverjanje ni uspelo. Ali blokirate piškotke?
+      title: Varnostno preverjanje je spodletelo
+    '429': Omejeno
+    '500':
+      content: Žal nam je, toda na našem koncu je prišlo do napake.
+      title: Ta stran ni pravilna
+    '503': The page could not be served due to a temporary server failure.
+    noscript_html: Če želite uporabljati spletno aplikacijo Mastodon, omogočite JavaScript. Druga možnost je, da za svojo platformo poskusite eno od <a href="%{apps_path}">lastnih aplikacij</a> za Mastodon.
+  existing_username_validator:
+    not_found: s tem uporabniškim imenom ni bilo mogoče najti lokalnega uporabnika
+    not_found_multiple: ni bilo mogoče najti %{usernames}
+  exports:
+    archive_takeout:
+      date: Datum
+      download: Prenesi svoj arhiv
+      hint_html: Zahtevate lahko arhiv vaših <strong>tutov in naloženih medijev</strong>. Izvoženi podatki bodo v formatu ActivityPub, ki ga bo mogoče brati s katerokoli skladno programsko opremo. Arhiv lahko zahtevate vsakih 7 dni.
+      in_progress: Prevajanje arhiva...
+      request: Zahtevajte svoj arhiv
+      size: Velikost
+    blocks: Blokirate
+    csv: CSV
+    domain_blocks: Bloki domene
+    follows: Sledite
+    lists: Seznami
+    mutes: Utišate
+    storage: Shranjeni mediji
+  featured_tags:
+    add_new: Dodaj novo
+    errors:
+      limit: Ste že dodali največje število ključnikov
+  filters:
+    contexts:
+      home: Domača časovnica
+      notifications: Obvestila
+      public: Javne časovnice
+      thread: Pogovori
+    edit:
+      title: Uredite filter
+    errors:
+      invalid_context: Ne vsebuje nobenega ali vsebuje neveljaven kontekst
+      invalid_irreversible: Nepovratno filtriranje deluje le v kontekstu doma ali obvestil
+    index:
+      delete: Izbriši
+      title: Filtri
+    new:
+      title: Dodaj nov filter
+  footer:
+    developers: Razvijalci
+    more: Več…
+    resources: Viri
+  generic:
+    all: Vse
+    changes_saved_msg: Spremembe so uspešno shranjene!
+    copy: Kopiraj
+    order_by: Razvrsti po
+    save_changes: Shrani spremembe
+    validation_errors:
+      few: Nekaj še ni čisto v redu! Spodaj si oglejte %{count} napake
+      one: Nekaj še ni čisto v redu! Spodaj si oglejte napako
+      other: Nekaj še ni čisto v redu! Spodaj si oglejte %{count} napak
+      two: Nekaj še ni čisto v redu! Spodaj si oglejte %{count} napaki
+  html_validator:
+    invalid_markup: 'vsebuje neveljavno oznako HTML: %{error}'
+  identity_proofs:
+    active: Dejaven
+    authorize: Da, odobri
+    authorize_connection_prompt: Odobrite to kriptografsko povezavo?
+    errors:
+      failed: Kriptografska povezava ni uspela. Poskusite znova od %{provider}.
+      keybase:
+        invalid_token: Žetoni Keybase so algoritem podpisov in morajo biti sestavljeni iz 66 heksadecimalnih znakov
+        verification_failed: Keybase ne prepozna tega žetona kot podpis uporabnika %{kb_username}. Poskusite znova s Keybase-om.
+      wrong_user: Dokler se prijavite kot %{current}, ni mogoče ustvariti dokazila za %{proving}. Prijavite se kot %{proving} in poskusite znova.
+    explanation_html: Tukaj lahko kriptografsko povežete druge identitete, na primer profil Keybase. To omogoča drugim, da vam pošljejo šifrirana sporočila in zaupate vsebino, ki ste jo poslali.
+    i_am_html: Jaz sem %{username} na %{service}.
+    identity: Identiteta
+    inactive: Neaktiven
+    publicize_checkbox: 'In to tutnite:'
+    publicize_toot: 'Dokazano je! Jaz sem %{username} na %{service}: %{url}'
+    status: Stanje preverjanja
+    view_proof: Oglejte si dokaz
+  imports:
+    modes:
+      merge: Združi
+      merge_long: Ohrani obstoječe zapise in dodaj nove
+      overwrite: Prepiši
+      overwrite_long: Zamenjaj trenutne zapise z novimi
+    preface: Podatke, ki ste jih izvozili iz drugega strežnika, lahko uvozite. Na primer seznam oseb, ki jih spremljate ali blokirate.
+    success: Vaši podatki so bili uspešno naloženi in bodo zdaj pravočasno obdelani
+    types:
+      blocking: Seznam blokiranih
+      domain_blocking: Seznam blokiranih domen
+      following: Seznam uporabnikov, katerim sledite
+      muting: Seznam utišanih
+    upload: Pošlji
+  in_memoriam_html: V spomin.
   invites:
+    delete: Onemogoči
+    expired: Poteklo
     expires_in:
-      '1800': 30 minutes
-      '21600': 6 hours
-      '3600': 1 hour
-      '43200': 12 hours
-      '604800': 1 week
-      '86400': 1 day
+      '1800': 30 minut
+      '21600': 6 ur
+      '3600': 1 ura
+      '43200': 12 ur
+      '604800': 1 teden
+      '86400': 1 dan
+    expires_in_prompt: Nikoli
+    generate: Ustvari
+    invited_by: 'Povabil/a vas je:'
+    max_uses:
+      few: "%{count} uporabe"
+      one: 1 uporaba
+      other: "%{count} uporab"
+      two: "%{count} uporabi"
+    max_uses_prompt: Brez omejitve
+    prompt: Ustvarite in delite povezave z drugimi, da omogočite dostop do tega strežnika
+    table:
+      expires_at: Poteče
+      uses: Uporabe
+    title: Povabite ljudi
+  lists:
+    errors:
+      limit: Dosegli ste največje število seznamov
+  media_attachments:
+    validations:
+      images_and_video: Videoposnetka ni mogoče priložiti stanju, ki že vsebuje slike
+      too_many: Ni možno priložiti več kot 4 datoteke
+  migrations:
+    acct: username@domain novega računa
+  moderation:
+    title: Moderiranje
+  notification_mailer:
+    digest:
+      action: Prikaži vsa obvestila
+      body: Tukaj je kratek povzetek sporočil, ki ste jih zamudili od vašega zadnjega obiska v %{since}
+      mention: "%{name} vas je omenil/a v:"
+      new_followers_summary:
+        few: Prav tako ste pridobili %{count} nove sledilce, ko ste bili odsotni! Juhu!
+        one: Prav tako ste pridobili enega novega sledilca, ko ste bili odsotni! Juhu!
+        other: Prav tako ste pridobili %{count} novih sledilcev, ko ste bili odsotni! Juhu!
+        two: Prav tako ste pridobili %{count} nova sledilca, ko ste bili odsotni! Juhu!
+      subject:
+        few: "%{count} nova obvestila od vašega zadnjega obiska \U0001F418"
+        one: "1 novo obvestilo od vašega zadnjega obiska \U0001F418"
+        other: "%{count} novih obvestil od vašega zadnjega obiska \U0001F418"
+        two: "%{count} novi obvestili od vašega zadnjega obiska \U0001F418"
+      title: V vaši odsotnosti...
+    favourite:
+      body: "%{name} je vzljubil/a vaše stanje:"
+      subject: "%{name} je vzljubil/a vaše stanje"
+      title: Novo priljubljeno
+    follow:
+      body: "%{name} vam sedaj sledi!"
+      subject: "%{name} vam sedaj sledi"
+      title: Novi sledilec
+    follow_request:
+      action: Upravljajte s prošnjami za sledenje
+      body: "%{name} vas je prosil/a za sledenje"
+      subject: 'Čakajoči sledilec/ka: %{name}'
+      title: Nova prošnja za sledenje
+    mention:
+      action: Odgovori
+      body: "%{name} vas je omenil/a v:"
+      subject: "%{name} vas je omenil/a"
+      title: Nova omemba
+    reblog:
+      body: "%{name} je spodbudil/a vaše stanje:"
+      subject: "%{name} je spodbudil/a vaše stanje"
+      title: Nova spodbuda
+  number:
+    human:
+      decimal_units:
+        format: "%n%u"
+        units:
+          billion: B
+          million: M
+          quadrillion: Q
+          thousand: K
+          trillion: T
+  pagination:
+    newer: Novejše
+    next: Naprej
+    older: Starejše
+    prev: Nazaj
+    truncate: "&hellip;"
+  polls:
+    errors:
+      already_voted: Na tej anketi ste že glasovali
+      duplicate_options: vsebuje podvojene elemente
+      duration_too_long: je predaleč v prihodnosti
+      duration_too_short: je prezgodaj
+      expired: Glasovanje se je že zaključilo
+      over_character_limit: ne more biti daljše od %{max} znakov
+      too_few_options: mora imeti več kot en element
+      too_many_options: ne more vsebovati več kot %{max} elementov
+  preferences:
+    other: Ostalo
+    posting_defaults: Privzete nastavitev objavljanja
+    public_timelines: Javne časovnice
+  relationships:
+    activity: Dejavnost računa
+    dormant: Skrit
+    last_active: Zadnja dejavnost
+    most_recent: Najnovejša
+    moved: Prestavljeno
+    mutual: Vzajemna
+    primary: Primarna
+    relationship: Razmerje
+    remove_selected_domains: Odstrani vse sledilce iz izbranih domen
+    remove_selected_followers: Odstrani izbrane sledilce
+    remove_selected_follows: Prenehaj slediti izbranim uporabnikom
+    status: Stanje računa
+  remote_follow:
+    acct: Vnesite uporabniško_ime@domena, iz katerega želite delovati
+    missing_resource: Za vaš račun ni bilo mogoče najti zahtevanega URL-ja za preusmeritev
+    no_account_html: Še nimate računa? Tukaj se lahko <a href='%{sign_up_path}' target='_blank'>prijavite</a>
+    proceed: Nadaljujte
+    prompt: 'Sledili boste:'
+    reason_html: "<strong>Zakaj je ta korak potreben?</strong> <code>%{instance}</code> morda ni strežnik, kjer ste registrirani, zato vas moramo najprej preusmeriti na domači strežnik."
+  remote_interaction:
+    favourite:
+      proceed: Nadaljuj s priljubljenim
+      prompt: 'Ali želite vzljubiti ta tut:'
+    reblog:
+      proceed: Nadaljuj s spodbudo
+      prompt: 'Ali želite spodbuditi ta tut:'
+    reply:
+      proceed: Nadaljuj z odgovorom
+      prompt: 'Ali želite odgovoriti na ta tut:'
+  scheduled_statuses:
+    over_daily_limit: Za ta dan ste presegli omejitev %{limit} načrtovanih tutov
+    over_total_limit: Presegli ste omejitev %{limit} načrtovanih tutov
+    too_soon: Načrtovani datum mora biti v prihodnosti
+  sessions:
+    activity: Zadnja dejavnost
+    browser: Brskalnik
+    browsers:
+      alipay: Alipay
+      blackberry: BlackBerry
+      chrome: Chrome
+      edge: Microsoft Edge
+      electron: Electron
+      firefox: Firefox
+      generic: Neznan brskalnik
+      ie: Internet Explorer
+      micro_messenger: MicroMessenger
+      nokia: Nokia S40 Ovi Browser
+      opera: Opera
+      otter: Otter
+      phantom_js: PhantomJS
+      qq: QQ Browser
+      safari: Safari
+      uc_browser: UC Browser
+      weibo: Weibo
+    current_session: Trenutna seja
+    description: "%{browser} na %{platform}"
+    explanation: To so spletni brskalniki, ki so trenutno prijavljeni v vaš Mastodon račun.
+    ip: IP
+    platforms:
+      adobe_air: Adobe Air
+      android: Android
+      blackberry: BlackBerry
+      chrome_os: ChromeOS
+      firefox_os: Firefox OS
+      ios: iOS
+      linux: Linux
+      mac: Mac
+      other: neznana platforma
+      windows: Windows
+      windows_mobile: Windows Mobile
+      windows_phone: Windows Phone
+    revoke: Prekliči
+    revoke_success: Seja je bila uspešno preklicana
+    title: Seje
+  settings:
+    account: Račun
+    account_settings: Nastavitve računa
+    appearance: Videz
+    authorized_apps: Pooblaščene aplikacije
+    back: Nazaj na Mastodon
+    delete: Brisanje računa
+    development: Razvoj
+    edit_profile: Uredi profil
+    export: Izvoz podatkov
+    featured_tags: Vključeni ključniki
+    identity_proofs: Dokazi o identiteti
+    import: Uvozi
+    import_and_export: Uvoz in izvoz
+    migrate: Selitev računa
+    notifications: Obvestila
+    preferences: Nastavitve
+    profile: Profil
+    relationships: Sledenja in sledilci
+    two_factor_authentication: Dvofaktorsko overjanje
   statuses:
+    attached:
+      description: 'Priloženo: %{attached}'
+      image:
+        few: "%{count} slike"
+        one: "%{count} slika"
+        other: "%{count} slik"
+        two: "%{count} sliki"
+      video:
+        few: "%{count} video posnetki"
+        one: "%{count} video posnetek"
+        other: "%{count} video posnetkov"
+        two: "%{count} video posnetka"
+    boosted_from_html: Spodbujeno iz %{acct_link}
+    content_warning: 'Opozorilo o vsebini: %{warning}'
+    disallowed_hashtags:
+      few: 'vsebuje nedovoljene ključnike: %{tags}'
+      one: 'vsebuje nedovoljeni ključnik: %{tags}'
+      other: 'vsebuje nedovoljenih ključnikov: %{tags}'
+      two: 'vsebuje nedovoljena ključnika: %{tags}'
+    language_detection: Samodejno zaznaj jezik
+    open_in_web: Odpri na spletu
+    over_character_limit: omejitev %{max} znakov je presežena
     pin_errors:
+      limit: Pripeli ste največje število tutov
       ownership: Trob nekoga drugega ne more biti pripet
       private: Nejavnega troba ni mogoče pripeti
+      reblog: Spodbuda ne more biti pripeta
+    poll:
+      total_votes:
+        few: "%{count} glasovi"
+        one: "%{count} glas"
+        other: "%{count} glasov"
+        two: "%{count} glasova"
+      vote: Glasuj
+    show_more: Pokaži več
+    sign_in_to_participate: Prijavite se, če želite sodelovati v pogovoru
+    title: '%{name}: "%{quote}"'
+    visibilities:
+      private: Samo sledilci
+      private_long: Prikaži samo sledilcem
+      public: Javno
+      public_long: Vsi lahko vidijo
+      unlisted: Ni prikazano
+      unlisted_long: Vsi lahko vidijo, vendar ni objavljeno na javnih časovnicah
   stream_entries:
     pinned: Pripet trob
+    reblogged: spodbujen
+    sensitive_content: Občutljiva vsebina
+  terms:
+    body_html: |
+      <h2>Pravilnik o zasebnosti</h2>
+      <h3 id="collect">Katere informacije zbiramo?</h3>
+
+      <ul>
+      <li><em>Osnovni podatki o računu</em>: Če se registrirate na tem strežniku, boste morda morali vnesti uporabniško ime, e-poštni naslov in geslo. Vnesete lahko tudi dodatne informacije o profilu, na primer prikazno ime in biografijo, ter naložite sliko profila in sliko glave. Uporabniško ime, prikazno ime, biografija, slika profila in slika glave so vedno javno dostopni.</li>
+      <li><em>Objave, sledenja in druge javne informacije</em>: Seznam oseb, ki jim sledite, je javno dostopen, enako velja za vaše sledilce. Ko pošljete sporočilo, sta datum in čas shranjena, kot tudi aplikacija, iz katere ste poslali sporočilo. Sporočila lahko vsebujejo medijske priloge, kot so slike in video posnetki. Javne in neprikazane objave so javno dostopne. Ko v profilu vključite objavo, je to tudi javno dostopna informacija. Vaše objave, ki so dostavljene vašim sledilcem, so včasih dostavljeni na različne strežnike, kjer se kopije objav tudi shranijo. Ko izbrišete objave, se to prav tako dostavi vašim sledilcem. Spodbujanje in vzljubitev drugih objav sta veno javni.</li>
+      <li><em>Neposredne objave in objave samo za sledilce</em>: Vse objave so shranjene in obdelane na strežniku. Objave samo za sledilce se dostavijo vašim sledilcem in uporabnikom, ki so v njih omenjeni. Neposredne objave se posredujejo samo uporabnikom, ki so v njih omenjeni. V nekaterih primerih so dostavljeni na različne strežnike, kopije pa se shranijo tam. V dobri veri si prizadevamo omejiti dostop do teh objav samo pooblaščenim osebam, vendar drugi strežniki to morda ne bodo storili. Zato je pomembno, da pregledate strežnike, na katerih so sledilci. V nastavitvah lahko preklapljate med možnostmi za odobritev in zavrnitev novih sledilcev. <em> Ne pozabite, da lahko operaterji strežnika in kateri koli prejemni strežnik takšna sporočila pregledajo </em> in da jih lahko prejemniki posnamejo, kopirajo ali drugače ponovno delijo. <em> Ne pošiljajte nevarnih informacij skozi Mastodon. </em> </li>
+      <li><em>IP-ji in drugi metapodatki</em>: Ko se prijavite, zabeležimo naslov IP, s katerega se prijavljate, in ime aplikacije brskalnika. V nastavitvah so za pregled in preklic na voljo vse prijavljene seje. Zadnji uporabljeni IP naslov je shranjen do 12 mesecev. Prav tako lahko obdržimo dnevnike strežnikov, ki vsebujejo IP naslov vsake zahteve na naš strežnik. </li>
+      </ul>
+
+      <hr class="spacer" />
+
+      <h3 id="use">Za kaj uporabljamo vaše podatke?</h3>
+
+      <p>Vse informacije, ki jih zbiramo od vas, so lahko uporabljene na naslednje načine:</p>
+
+      <ul>
+      <li>Za zagotavljanje osrednje funkcionalnosti Mastodona. Komunicirate lahko z vsebino drugih oseb in objavljate lastno vsebino, ko ste prijavljeni. Na primer, lahko spremljate druge osebe in si ogledate njihove kombinirane objave v svoji prilagojeni domači časovnici.</li>
+      <li>Za pomoč pri moderiranju skupnosti, na primer primerjavo vašega naslova IP naslova z drugimi znanimi, za izobčitev izmikanja ali drugih kršitev.</li>
+      <li>E-poštni naslov, ki ga navedete, se lahko uporabi za pošiljanje informacij, obvestil o drugih osebah, ki komunicirajo z vašo vsebino ali pošiljanju sporočil, ter za odzivanje na poizvedbe in/ali druge zahteve ali vprašanja.</li>
+      </ul>
+
+      <hr class="spacer" />
+
+      <h3 id="protect">Kako zaščitimo vaše podatke?</h3>
+
+      <p>Za ohranitev varnosti vaših osebnih podatkov izvajamo različne varnostne ukrepe, ko vnašate, pošiljate ali dostopate do vaših osebnih podatkov. Med drugim je seja brskalnika, pa tudi promet med vašimi aplikacijami in API-jem zaščitena s SSL-jem, geslo pa je zgoščeno z uporabo močnega enosmernega algoritma. Če želite omogočiti varen dostop do računa, lahko omogočite dvofaktorsko preverjanje pristnosti.</p>
+
+      <hr class="spacer" />
+
+      <h3 id="data-retention">Kakšna je naša politika hrambe podatkov?</h3>
+
+      <p>Prizadevali si bomo za:</p>
+
+      <ul>
+      <li>Shranjevanje dnevnike strežnikov, ki vsebujejo naslov IP vseh zahtev za ta strežnik, če so hranjeni, največ 90 dni.</li>
+      <li>Obdržiitev naslovov IP, povezane z registriranimi uporabniki, ne več kot 12 mesecev.</li>
+      </ul>
+
+      <p>Lahko zahtevate in prenesete arhiv vaše vsebine, vključno z objavami, predstavnostnimi prilogami, sliko profila in sliko glave.</p>
+
+      <p>Račun lahko kadar koli nepovratno izbrišete.</p>
+
+      <hr class="spacer"/>
+
+      <h3 id="cookies">Ali uporabljamo piškotke?</h3>
+
+      <p>Da. Piškotki so majhne datoteke, ki jih spletno mesto ali njegov ponudnik storitev prenese na trdi disk vašega računalnika prek spletnega brskalnika (če dovolite). Ti piškotki omogočajo, da spletno mesto prepozna vaš brskalnik in ga, če imate registriran račun, povežete z vašim registriranim računom.</p>
+
+      <p>Piškotke uporabljamo za razumevanje in shranjevanje vaših nastavitev za prihodnje obiske.</p>
+
+      <hr class="spacer" />
+
+      <h3 id="disclose">Ali razkrivamo informacije zunanjim strankam?</h3>
+
+      <p>Vaših osebnih podatkov ne prodajamo, preprodajamo ali kako drugače posredujemo zunanjim osebam. To ne vključuje zaupanja vrednih tretjih oseb, ki nam pomagajo pri upravljanju naše spletne strani, vodenju našega poslovanja ali storitev, če se te strani strinjajo, da bodo te informacije zaupne. Vaše podatke lahko tudi objavimo, če menimo, da je objava ustrezna in v skladu z zakonom, uveljavlja pravilnike o spletnih mestih ali ščiti naše ali druge pravice, lastnino ali varnost.</p>
+
+      <p>Vaše javne vsebine lahko prenesejo drugi strežniki v omrežju. Vaše objave in objave samo za sledilce so dostavljene na strežnike, na katerih prebivajo vaši sledilci, in neposredna sporočila so dostavljena na strežnike prejemnikov, če so ti sledilci ali prejemniki na drugem strežniku.</p>
+
+      <p>Ko odobrite aplikacijo za uporabo vašega računa, lahko glede na obseg dovoljenj, ki jih odobravate, dostopa do vaših javnih podatkov o profilu, seznama osebam, ki jim sledite, vaših sledilcev, seznamov, vseh vaših objav in priljubljenih. Aplikacije ne morejo nikoli dostopati do vašega e-poštnega naslova ali gesla.</p>
+
+      <hr class="spacer" />
+
+      <h3 id="children">Uporaba strani s strani otrok</h3>
+
+      <p>Če je ta strežnik v EU ali EEA: Naše spletno mesto, izdelki in storitve so namenjeni ljudem, ki so stari vsaj 16 let. Če ste mlajši od 16 let, po zahtevah GDPR (<a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">General Data Protection Regulation</a>) ne uporabljajte tega spletnega mesta.</p>
+
+      <p>Če je ta strežnik v ZDA Naše spletno mesto, izdelki in storitve so namenjeni ljudem, ki so stari vsaj 13 let. Če ste mlajši od 13 let, po zahtevah COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) ne uporabljajte tega spletnega mesta.</p>
+
+      <p>Če je ta strežnik v drugi jurisdikciji, so lahko zakonske zahteve drugačne.</p>
+
+      <hr class="spacer" />
+
+      <h3 id="changes">Spremembe našega pravilnika o zasebnosti</h3>
+
+      <p>Če se odločimo za spremembo našega pravilnika o zasebnosti, bomo te spremembe objavili na tej strani.</p>
+
+      <p>Ta dokument je CC-BY-SA. Zadnja posodobitev je bila 7. marca 2018.</p>
+
+      <p>Prvotno je bila prilagojena v skladu s <a href="https://github.com/discourse/discourse">pravilnikom o zasebnosti diskurza</a>.</p>
+    title: "%{instance} Pogoji storitve in pravilnik o zasebnosti"
+  themes:
+    contrast: Mastodon (Visok kontrast)
+    default: Mastodon (Temna)
+    mastodon-light: Mastodon (Svetla)
+  time:
+    formats:
+      default: "%b %d, %Y, %H:%M"
+      month: "%b %Y"
+  two_factor_authentication:
+    code_hint: Za potrditev vnesite kodo, ki jo je ustvarila aplikacija za preverjanje pristnosti
+    description_html: Če omogočite <strong>dvofaktorsko preverjanje pristnosti</strong>, boste za prijavo morali imeti svoj telefon, s katerim boste ustvarili žetone za vstop.
+    disable: Onemogoči
+    enable: Omogoči
+    enabled: Dvofaktorsko preverjanje pristnosti je omogočeno
+    enabled_success: Dvofaktorsko preverjanje pristnosti je uspešno omogočeno
+    generate_recovery_codes: Ustvari kode za obnovitev
+    instructions_html: "<strong>Skenirajte QR kodo z Google Authenticator ali s podobno aplikacijo TOTP</strong>. Od zdaj naprej bo ta aplikacija ustvarjala žetone, ki jih boste morali vnesti ob prijavi."
+    lost_recovery_codes: Obnovitvene kode vam omogočajo, da ponovno pridobite dostop do svojega računa, če izgubite telefon. Če ste izgubili obnovitvene kode, jih lahko obnovite tukaj. Vaše stare obnovitvene kode bodo neveljavne.
+    manual_instructions: 'Če ne morete skenirati QR kode in jo morate vnesti ročno, je tu skrivnost v tekstovni obliki:'
+    recovery_codes: Varnostna kopija obnovitvenih kod
+    recovery_codes_regenerated: Obnovitvene kode so bile uspešno regenerirane
+    recovery_instructions_html: Če kdaj izgubite dostop do telefona, lahko uporabite eno od spodnjih obnovitvenih kod, da ponovno pridobite dostop do svojega računa. <strong>Shranite obnovitvene kode</strong>. Lahko jih natisnete in shranite z drugimi pomembnimi dokumenti.
+    setup: Nastavi
+    wrong_code: Vnesena koda je bila neveljavna! Ali sta čas strežnika in čas naprave pravilna?
+  user_mailer:
+    backup_ready:
+      explanation: Zahtevali ste popolno varnostno kopijo računa Mastodon. Zdaj je pripravljen za prenos!
+      subject: Vaš arhiv je pripravljen za prenos
+      title: Prevzem arhiva
+    warning:
+      explanation:
+        disable: Medtem ko je vaš račun zamrznjen, podatki vašega računa ostanejo nedotaknjeni, vendar ne morete izvajati nobenih dejanj, dokler ga ne odklenete.
+        silence: Medtem ko je vaš račun omejen, bodo na tem strežniku videli vaše tute samo osebe, ki vam že sledijo in morda boste izključeni iz različnih javnih seznamov. Vendar vam lahko še vedno ročno sledijo drugi.
+        suspend: Vaš račun je bil začasno ukinjen, vsi vaši tuti in naložene predstavnostne datoteke so bili nepovratno odstranjeni s tega strežnika in strežnikov, kjer ste imeli sledilce.
+      review_server_policies: Preglejte pravilnike strežnika
+      subject:
+        disable: Vaš račun %{acct} je zamrznjen
+        none: Opozorila za %{acct}
+        silence: Vaš račun %{acct} je omejen
+        suspend: Vaš račun %{acct} je suspendiran
+      title:
+        disable: Račun je zamrznjen
+        none: Opozorilo
+        silence: Račun je omejen
+        suspend: Račun je suspendiran
+    welcome:
+      edit_profile_action: Nastavitve profila
+      edit_profile_step: Profil lahko prilagodite tako, da naložite podobo, glavo, spremenite prikazno ime in drugo. Če želite pregledati nove sledilce, preden jim dovolite sledenje, lahko zaklenete svoj račun.
+      explanation: Tu je nekaj nasvetov za začetek
+      final_action: Začnite objavljati
+      final_step: 'Začnite objavljati! Tudi brez sledilcev bodo vaša javna sporočila videli drugi, na primer na lokalni časovnici in v ključnikih. Morda se želite predstaviti s ključnikom #introductions.'
+      full_handle: Vaša polna ročica
+      full_handle_hint: To bi povedali svojim prijateljem, da vam lahko pošljejo sporočila ali vam sledijo iz drugega strežnika.
+      review_preferences_action: Spremenite nastavitve
+      review_preferences_step: Poskrbite, da določite svoje nastavitve, na primer, katera e-poštna sporočila želite prejemati ali katere privzete ravni zasebnosti bodo imele vaše objave. Če nimate potovalne slabosti, lahko omogočite samodejno predvajanje GIF-ov.
+      subject: Dobrodošli na Mastodon
+      tip_federated_timeline: Združena časovnica je pogled na mrežo Mastodona. Vključuje pa samo ljudi, na katere so naročeni vaši sosedje, zato ni popolna.
+      tip_following: Privzeto sledite skrbnikom strežnika. Če želite najti več zanimivih ljudi, preverite lokalne in združene časovnice.
+      tip_local_timeline: Lokalna časovnica je strežniški pogled ljudi na %{instance}. To so vaši neposredni sosedje!
+      tip_mobile_webapp: Če vam mobilni brskalnik ponuja, da dodate Mastodon na domači zaslon, lahko prejmete potisna obvestila. Deluje kot lastna aplikacija na več načinov!
+      tips: Nasveti
+      title: Dobrodošli, %{name}!
+  users:
+    follow_limit_reached: Ne morete spremljati več kot %{limit} ljudi
+    invalid_email: E-poštni naslov je napačen
+    invalid_otp_token: Neveljavna dvofaktorska koda
+    otp_lost_help_html: ÄŒe ste izgubili dostop do obeh, stopite v stik z %{email}
+    seamless_external_login: Prijavljeni ste prek zunanje storitve, tako da nastavitve gesla in e-pošte niso na voljo.
+    signed_in_as: 'Vpisani kot:'
+  verification:
+    explanation_html: '<strong>V metapodatkih svojega profila se lahko potrdite kot lastnik povezav</strong>. Za to mora povezano spletno mesto vsebovati povezavo do vašega Mastodon profila. Povezava <strong>mora</strong> imeti atribut <code>el="me"</code>. Vsebina besedila povezave ni pomembna. Tukaj je primer:'
+    verification: Potrditev
diff --git a/config/locales/sq.yml b/config/locales/sq.yml
index 6cab033321008a32681f8dcb10d0b8dc67bda20b..ac811b0b0e73f8cf531c1272e8441c9330a72566 100644
--- a/config/locales/sq.yml
+++ b/config/locales/sq.yml
@@ -9,10 +9,6 @@ sq:
     contact: Kontakt
     contact_missing: I parregulluar
     documentation: Dokumentim
-    extended_description_html: |
-      <h3>Një vend i mirë për rregulla</h3>
-      <p>Përshkrimi i zgjeruar s’është sajuar ende.</p>
-    generic_description: "%{domain} është një shërbyes te rrjeti"
     hosted_on: Mastodon i strehuar në %{domain}
     learn_more: Mësoni më tepër
     privacy_policy: Rregulla privatësie
@@ -423,19 +419,8 @@ sq:
       no_status_selected: S’u ndryshua ndonjë gjendje, ngaqë s’u përzgjodh ndonjë e tillë
       title: Gjendje llogarish
       with_media: Me media
-    subscriptions:
-      callback_url: URL Callback-u
-      confirmed: U ripohua
-      expires_in: Skadon më
-      last_delivery: Dorëzimi e fundit
-      topic: Temë
     tags:
-      accounts: Llogari
-      hidden: Fshehur
-      hide: Fshihe prej drejtorie
       title: Hashtage
-      unhide: Shfaqe në drejtori
-      visible: E dukshme
     title: Administrim
     warning_presets:
       add_new: Shtoni të ri
@@ -464,7 +449,6 @@ sq:
     your_token: Token-i juaj për hyrje
   auth:
     change_password: Fjalëkalim
-    confirm_email: Ripohoni email-in
     delete_account: Fshije llogarinë
     delete_account_html: Nëse dëshironi të fshihni llogarinë tuaj, mund <a href="%{path}">ta bëni që këtu</a>. Do t’ju kërkohet ta ripohoni.
     didnt_get_confirmation: S’morët udhëzime ripohimi?
@@ -502,23 +486,18 @@ sq:
       over_x_years: "%{count}v"
       x_months: "%{count}mj"
   deletes:
-    bad_password_msg: Provë e bukur, trimosha! Fjalëkalim i pasaktë
     confirm_password: Jepni fjalëkalimin tuaj të tanishëm që të verifikohet identiteti juaj
-    description_html: Kjo të heqë <strong>në mënyrë të përhershme, të pakthyeshme</strong> lëndë nga llogaria juaj dhe do ta çaktivizojë atë. Emri juaj i përdoruesit do të mbetet i rezervuar për të shmangur sozi të ardhme.
     proceed: Fshini llogarinë
     success_msg: Llogaria juaj u fshi me sukses
-    warning_html: Garantohet vetëm fshirja e lëndës prej këtij shërbyesi të veçantë. Lënda që është ndarë gjerësisht me të tjerët ka gjasa të lërë gjurmë. Shërbyesit <em>offline</em> dhe shërbyesit që janë shpajtuar prej përditësimeve tuaja, s’do t’i përditësojnë bazat e tyre të të dhënave.
-    warning_title: Mund të ketë lëndë të përhapur
   directories:
     directory: Drejtori profilesh
-    enabled: Gjendeni te lista e drejtorisë.
-    enabled_but_waiting: Keni zgjedhur të jeni pjesë e drejtorisë, por ende s’keni numrin minimum të ndjekësve (%{min_followers}) për përfshirje në të.
     explanation: Zbuloni përdorues bazuar në interesat e tyre
     explore_mastodon: Eksploroni %{title}
-    how_to_enable: S’keni zgjedhur të jeni i pranishëm te drejtoria. Mund ta bëni më poshtë. Përdorni te teksti i jetëshkrimit tuaj hashtagë, për t’u përfshirë nën hashtagë specifikë!
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': S’keni leje të shihni këtë faqe.
     '404': Faqja që po kërkonit, s’gjendet këtu.
+    '406': This page is not available in the requested format.
     '410': Faqja që po kërkonit, s’gjendet më këtu.
     '422':
       content: Verifikimi i sigurisë dështoi. Mos i bllokoni gjë cookie-t?
@@ -527,6 +506,7 @@ sq:
     '500':
       content: Na ndjeni, diçka shkoi ters në anën tonë.
       title: Kjo faqe s’është e saktë
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: Që të përdorni aplikacionin web Mastodon, ju lutemi, aktivizoni JavaScript-in. Ndryshe, provoni për Mastodon-in një nga <a href="%{apps_path}">aplikacionet e brendshëm</a> të platformës tuaj.
   exports:
     archive_takeout:
@@ -618,9 +598,6 @@ sq:
       too_many: S’mund të bashkëngjiten më shumë se 4 kartela
   migrations:
     acct: emërpërdoruesi@përkatësi e llogarisë së re
-    currently_redirecting: 'Profili juaj është caktuar të ridrejtojë te:'
-    proceed: Ruaje
-    updated_msg: Rregullimi juaj për migrim llogarish u përditësua me sukses!
   moderation:
     title: Moderim
   notification_mailer:
@@ -686,10 +663,6 @@ sq:
     reply:
       proceed: Ripohoni përgjigjen
       prompt: 'Doni t’i përgjigjeni këtij mesazhi:'
-  remote_unfollow:
-    error: Gabim
-    title: Titull
-    unfollowed: U hoq ndjekja
   scheduled_statuses:
     over_daily_limit: Keni tejkaluar kufirin e %{limit} mesazheve të planifikuara për atë ditë
     over_total_limit: Keni tejkaluar kufirin prej %{limit} mesazhesh të planifikuara
diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml
index 3310716e0f0ac4e640144944db4efbeb7204c30b..2bddaf3115d329d3ba45c7d9b4d6070888253401 100644
--- a/config/locales/sr-Latn.yml
+++ b/config/locales/sr-Latn.yml
@@ -6,10 +6,6 @@ sr-Latn:
     about_this: O instanci
     contact: Kontakt
     contact_missing: Nije postavljeno
-    extended_description_html: |
-      <h3>Dobro mesto za pravila</h3>
-      <p>Prošireni opis koji još nije postavljen.</p>
-    generic_description: "%{domain} je server na mreži"
     hosted_on: Mastodont hostovan na %{domain}
     learn_more: Saznajte više
     source_code: Izvorni kod
@@ -263,10 +259,6 @@ sr-Latn:
       no_media: Bez multimedije
       title: Statusi naloga
       with_media: Sa multimedijom
-    subscriptions:
-      confirmed: Potvrđeno
-      expires_in: Ističe za
-      last_delivery: Poslednja dostava
     title: Administracija
   admin_mailer:
     new_report:
@@ -318,22 +310,21 @@ sr-Latn:
       over_x_years: "%{count}god"
       x_months: "%{count}mesec"
   deletes:
-    bad_password_msg: Dobar pokušaj, hakeri! Neispravna lozinka
     confirm_password: Unesite trenutnu lozinku da bismo proverili Vaš identitet
-    description_html: Ovo će <strong>trajno, bespovratno</strong> ukloniti sadržaj sa Vašef naloga i deaktivirati ga. Vaše korisničko ime će ostati rezervisano da se spreči da se neko ne predstavlja kao Vi sutra.
     proceed: Obriši nalog
     success_msg: Vaš nalog je uspešno obrisan
-    warning_html: Garantovano je samo brisanje sadržaja sa ove instance. Sadržaj koji je deljen dalje će verovatno da ostavi neke tragove. Nedostupni i ugašeni serveri, kao i serveri koji su odjavljeni od primanja statusa od Vas, neće ažurirati svoje baze.
-    warning_title: Dostupnost rasejanog sadržaja
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Nemate dozvola da vidite ovu stranu.
     '404': Strana koju ste tražili ne postoji.
+    '406': This page is not available in the requested format.
     '410': Strana koju ste tražili više ne postoji.
     '422': 
     '429': Uspored
     '500':
       content: Izvinjavamo se, nešto je pošlo po zlu sa ove strane.
       title: Strana nije ispravna
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: Da biste koristili Mastodont veb aplikaciju, omogućite JavaScript. U suprotnom, probajte neku od <a href="%{apps_path}">originalnih aplikacija</a> za Mastodont za Vašu platformu.
   exports:
     blocks: Blokirali ste
@@ -386,9 +377,6 @@ sr-Latn:
       too_many: Ne može se prikačiti više od 4 fajla
   migrations:
     acct: korisnik@domen novog naloga
-    currently_redirecting: 'Profil Vam je podešen da preusmerava na :'
-    proceed: Sačuvaj
-    updated_msg: Prebacivanje postavki Vašeg naloga uspešno izmenjeno!
   moderation:
     title: Moderacija
   notification_mailer:
diff --git a/config/locales/sr.yml b/config/locales/sr.yml
index 1555fb235def46aa834865b8c250be565df82343..404310fe91a2dfd65c8657ce4e051c5f711bb2d5 100644
--- a/config/locales/sr.yml
+++ b/config/locales/sr.yml
@@ -9,10 +9,6 @@ sr:
     contact: Контакт
     contact_missing: Није постављено
     documentation: Документација
-    extended_description_html: |
-      <h3>Добро место за правила</h3>
-      <p>Проширени опис који још није постављен.</p>
-    generic_description: "%{domain} је сервер на мрежи"
     hosted_on: Мастодонт хостован на %{domain}
     learn_more: Сазнајте више
     privacy_policy: Полиса приватности
@@ -440,18 +436,8 @@ sr:
       no_status_selected: Ниједан статус није промењен јер ниједан није изабран
       title: Статуси налога
       with_media: Са мултимедијом
-    subscriptions:
-      confirmed: Потврђено
-      expires_in: Истиче за
-      last_delivery: Последња достава
     tags:
-      accounts: Налози
-      hidden: Скривено
-      hide: Сакриј од фасцикле
-      name: Тараба
       title: Тараба
-      unhide: Прикажи у фасцикли
-      visible: Видљиво
     title: Администрација
     warning_presets:
       add_new: Додај нови
@@ -480,7 +466,6 @@ sr:
     your_token: Ваш приступни токен
   auth:
     change_password: Лозинка
-    confirm_email: Потврдите адресу е-поште
     delete_account: Обриши налог
     delete_account_html: Ако желите да обришете Ваш налог, можете <a href="%{path}">наставити овде</a>. Бићете упитани да потврдите.
     didnt_get_confirmation: Нисте добили поруку са упутствима за потврду налога?
@@ -521,25 +506,18 @@ sr:
       x_days: "%{count}д"
       x_months: "%{count}месец"
   deletes:
-    bad_password_msg: Добар покушај, хакери! Неисправна лозинка
     confirm_password: Унесите тренутну лозинку да бисмо проверили Ваш идентитет
-    description_html: Ово ће <strong>трајно, бесповратно</strong> уклонити садржај са Вашеф налога и деактивирати га. Ваше корисничко име ће остати резервисано да се спречи да се неко не представља као Ви сутра.
     proceed: Обриши налог
     success_msg: Ваш налог је успешно обрисан
-    warning_html: Гарантовано је само брисање садржаја са ове инстанце. Садржај који је дељен даље ће вероватно да остави неке трагове. Недоступни и угашени сервери, као и сервери који су одјављени од примања статуса од Вас, неће ажурирати своје базе.
-    warning_title: Доступност расејаног садржаја
   directories:
     directory: Профил фасцикле
-    enabled: Ви сте тренутно видљиви у фасцикли.
     explanation: Откријте кориснике на основу њихових интереса
     explore_mastodon: Истражи %{title}
-    people:
-      few: "%{count} људе"
-      one: "%{count} особа/е"
-      other: "%{count} људи"
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Немате дозвола да видите ову страну.
     '404': Страна коју сте тражили не постоји.
+    '406': This page is not available in the requested format.
     '410': Страна коју сте тражили више не постоји.
     '422':
       content: Безбедоносна провера није успела. Да не блокирате колачиће?
@@ -548,6 +526,7 @@ sr:
     '500':
       content: Извињавамо се, нешто је пошло по злу са ове стране.
       title: Страна није исправна
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: Да бисте користили Мастодонт веб апликацију, омогућите JavaScript. У супротном, пробајте неку од <a href="%{apps_path}">оригиналних апликација</a> за Мастодонт за Вашу платформу.
   exports:
     archive_takeout:
@@ -631,9 +610,6 @@ sr:
       too_many: Не може се прикачити више од 4 фајла
   migrations:
     acct: корисник@домен новог налога
-    currently_redirecting: 'Профил Вам је подешен да преусмерава на :'
-    proceed: Сачувај
-    updated_msg: Пребацивање поставки Вашег налога успешно измењено!
   moderation:
     title: Модерација
   notification_mailer:
@@ -693,10 +669,6 @@ sr:
     reply:
       proceed: Наставите да бисте одговорили
       prompt: 'Желите да одговорите на ову трубу:'
-  remote_unfollow:
-    error: Грешка
-    title: Наслов
-    unfollowed: Отпраћени
   scheduled_statuses:
     over_daily_limit: Прекорачили сте границу од %{limit} планираних труба за тај дан
     over_total_limit: Прекорачили сте границу од %{limit} планираних труба
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index d3d0cb888d0d1177234d5f793463841e78c7838e..c980b7d05bee903b871d2c3d38f8d5a32075ee1b 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -8,10 +8,6 @@ sv:
     api: API
     contact: Kontakt
     contact_missing: Inte inställd
-    extended_description_html: |
-      <h3>En bra plats för regler</h3>
-      <p>Den utökade beskrivningen har inte konfigurerats ännu.</p>
-    generic_description: "%{domain} är en server i nätverket"
     hosted_on: Mastodon värd på %{domain}
     learn_more: Lär dig mer
     source_code: Källkod
@@ -22,25 +18,38 @@ sv:
   accounts:
     follow: Följa
     following: Följer
+    joined: Gick med %{date}
+    last_active: senast aktiv
     media: Media
     moved_html: "%{name} har flyttat till %{new_profile_link}:"
     network_hidden: Denna information är inte tillgänglig
+    never_active: Aldrig
     nothing_here: Det finns inget här!
     people_followed_by: Personer som %{name} följer
     people_who_follow: Personer som följer %{name}
+    posts_tab_heading: Toots
     posts_with_replies: Toots med svar
     reserved_username: Användarnamnet är reserverat
     roles:
+      admin: Administratör
+      bot: Robot
       moderator: Moderator
+    unavailable: Profilen är inte tillgänglig
     unfollow: Sluta följa
   admin:
+    account_actions:
+      action: Utför åtgärd
+      title: Utför aktivitet för moderering på %{acct}
     account_moderation_notes:
       create: Lämna kommentar
       created_msg: Modereringsnotering skapad utan problem!
       delete: Ta bort
       destroyed_msg: Modereringsnotering borttagen utan problem!
     accounts:
+      approve: Godkänn
+      approve_all: Godkänn alla
       are_you_sure: Är du säker?
+      avatar: Profilbild
       by_domain: Domän
       change_email:
         changed_msg: E-postadressen har ändrats!
@@ -52,6 +61,7 @@ sv:
       confirm: Bekräfta
       confirmed: Bekräftad
       confirming: Bekräftande
+      deleted: Raderad
       demote: Degradera
       disable: inaktivera
       disable_two_factor_authentication: Inaktivera 2FA
@@ -313,12 +323,6 @@ sv:
       no_media: Ingen media
       title: Kontostatus
       with_media: med media
-    subscriptions:
-      callback_url: Ã…teranrop URL
-      confirmed: Bekräftad
-      expires_in: Utgår om
-      last_delivery: Sista leverans
-      topic: Ämne
   admin_mailer:
     new_report:
       body: "%{reporter} har rapporterat %{target}"
@@ -340,7 +344,6 @@ sv:
     your_token: Din access token
   auth:
     change_password: Lösenord
-    confirm_email: Bekräfta e-postadress
     delete_account: Ta bort konto
     delete_account_html: Om du vill radera ditt konto kan du <a href="%{path}">fortsätta här</a>. Du kommer att bli ombedd att bekräfta.
     didnt_get_confirmation: Fick inte instruktioner om bekräftelse?
@@ -384,16 +387,14 @@ sv:
       x_months: "%{count}mån"
       x_seconds: "%{count}sek"
   deletes:
-    bad_password_msg: Bra försök, hackare! Fel lösenord
     confirm_password: Ange ditt lösenord för att verifiera din identitet
-    description_html: Detta vill <strong>permanent, irreversibelt</strong> ta bort innehåll från ditt konto och avaktivera det. Ditt användarnamn kommer att förbli reserverat för att förhindra framtida efterföljare.
     proceed: Ta bort konto
     success_msg: Ditt konto har tagits bort
-    warning_html: Endast borttagning av innehåll från denna speciella instans garanteras. Innehåll som har delats i stor utsträckning kommer sannolikt att lämna spår. Offline-servrar och servrar som har avstängt från dina uppdateringar uppdaterar inte sina databaser.
-    warning_title: Spridet innehåll och tillgänglighet
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': Du har inte behörighet att visa den här sidan.
     '404': Sidan du letade efter existerar inte.
+    '406': This page is not available in the requested format.
     '410': Sidan du letade efter existerar inte längre.
     '422':
       content: Säkerhetsverifiering misslyckades Blockerar du cookies?
@@ -402,6 +403,7 @@ sv:
     '500':
       content: Vi är ledsna, men något gick fel från vårat håll.
       title: Den här sidan är inte korrekt
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: För att använda Mastodon webbapplikationen, vänligen aktivera JavaScript. Alternativt kan du prova en av <a href="%{apps_path}">inhemska appar</a> för Mastodon för din plattform.
   exports:
     archive_takeout:
@@ -462,9 +464,6 @@ sv:
       too_many: Det går inte att bifoga mer än 4 filer
   migrations:
     acct: användarnamn@domän av det nya kontot
-    currently_redirecting: 'Din profil är satt att omdirigeras till:'
-    proceed: Spara
-    updated_msg: Dina kontoflyttsinställning har uppdaterats!
   moderation:
     title: Moderera
   notification_mailer:
@@ -513,10 +512,6 @@ sv:
     missing_resource: Det gick inte att hitta den begärda omdirigeringsadressen för ditt konto
     proceed: Fortsätt för att följa
     prompt: 'Du kommer att följa:'
-  remote_unfollow:
-    error: Fel
-    title: Titel
-    unfollowed: Slutade följa
   sessions:
     activity: Senaste aktivitet
     browser: Webbläsare
@@ -560,6 +555,8 @@ sv:
     notifications: Meddelanden
     preferences: Inställningar
     two_factor_authentication: Tvåstegsautentisering
+  spam_check:
+    spam_detected: Det här är en automatisk rapport. Spam har upptäckts.
   statuses:
     attached:
       description: 'Bifogad: %{attached}'
diff --git a/config/locales/ta.yml b/config/locales/ta.yml
index eef06fa7caabb2c2129df768eef29391c507c56a..75bb81fad0256d157ab3f91ca8724ca12a15edc3 100644
--- a/config/locales/ta.yml
+++ b/config/locales/ta.yml
@@ -1,12 +1,15 @@
 ---
 ta:
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': You don't have permission to view this page.
     '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
     '410': The page you were looking for doesn't exist here anymore.
     '422': 
     '429': Throttled
     '500': 
+    '503': The page could not be served due to a temporary server failure.
   invites:
     expires_in:
       '1800': 30 minutes
diff --git a/config/locales/te.yml b/config/locales/te.yml
index d4a2f507d78e4041b2eb2cc8a215155cae6a5067..526b03b4974fa02bd0ad153bcc8953a63d588c31 100644
--- a/config/locales/te.yml
+++ b/config/locales/te.yml
@@ -10,10 +10,6 @@ te:
     contact_missing: ఇంకా సెట్ చేయలేదు
     contact_unavailable: వర్తించదు
     documentation: పత్రీకరణ
-    extended_description_html: |
-      <h3>నియమాలకు ఒక మంచి ప్రదేశం</h3>
-      <p>మరింత విశదీకరణ ఇంకా సెట్ చేయబడలేదు.</p>
-    generic_description: "%{domain} అనేది నెట్వర్కులోని ఒక సర్వరు"
     hosted_on: మాస్టొడాన్ %{domain} లో హోస్టు చేయబడింది
     learn_more: మరింత తెలుసుకోండి
     privacy_policy: గోప్యత విధానము
@@ -113,12 +109,15 @@ te:
       most_recent_activity: ఇటీవల యాక్టివిటీ
       most_recent_ip: ఇటీవలి IP
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': You don't have permission to view this page.
     '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
     '410': The page you were looking for doesn't exist here anymore.
     '422': 
     '429': Throttled
     '500': 
+    '503': The page could not be served due to a temporary server failure.
   invites:
     expires_in:
       '1800': 30 minutes
diff --git a/config/locales/th.yml b/config/locales/th.yml
index 7a16bc2f3288ffdd34761126e02634bb56883bfd..5a56bcd35121e3ab012f23e3d6de1300982239aa 100644
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -1,6 +1,7 @@
 ---
 th:
   about:
+    about_hashtag_html: มีการแท็กโพสต์สาธารณะเหล่านี้ด้วย <strong>#%{hashtag}</strong> คุณสามารถโต้ตอบกับโพสต์หากคุณมีบัญชีที่ใดก็ตามในเฟดิเวิร์ส
     about_mastodon_html: Mastodon เป็นเครือข่ายสังคมที่ทำงานบนโปรโตคอลเว็บแบบเปิดและซอฟต์แวร์เสรีที่เปิดต้นฉบับ กระจายศูนย์เหมือนอีเมล
     about_this: เกี่ยวกับ
     active_count_after: ที่ใช้งาน
@@ -16,11 +17,9 @@ th:
     contact_unavailable: ไม่มี
     discover_users: ค้นพบผู้ใช้
     documentation: เอกสารประกอบ
-    extended_description_html: |
-      <h3>สถานที่ที่ดีสำหรับกฎ</h3>
-      <p>ยังไม่ได้ตั้งคำอธิบายแบบขยาย</p>
-    generic_description: "%{domain} เป็นเซิร์ฟเวอร์หนึ่งในเครือข่าย"
+    federation_hint_html: ด้วยบัญชีที่ %{instance} คุณจะสามารถติดตามผู้คนในเซิร์ฟเวอร์ Mastodon และอื่น ๆ
     get_apps: ลองแอปสำหรับมือถือ
+    hosted_on: Mastodon ที่โฮสต์ที่ %{domain}
     learn_more: เรียนรู้เพิ่มเติม
     privacy_policy: นโยบายความเป็นส่วนตัว
     see_whats_happening: ดูสิ่งที่กำลังเกิดขึ้น
@@ -29,7 +28,11 @@ th:
     status_count_after:
       other: สถานะ
     status_count_before: ผู้สร้าง
+    tagline: ติดตามเพื่อน ๆ และค้นพบเพื่อนใหม่ ๆ
     terms: เงื่อนไขการให้บริการ
+    unavailable_content_description:
+      domain: เซิร์ฟเวอร์
+      reason: 'เหตุผล:'
     user_count_after:
       other: ผู้ใช้
     user_count_before: บ้านของ
@@ -45,6 +48,7 @@ th:
     media: สื่อ
     moved_html: "%{name} ได้ย้ายไปยัง %{new_profile_link}:"
     network_hidden: ไม่มีข้อมูลนี้
+    never_active: ไม่เลย
     nothing_here: ไม่มีสิ่งใดที่นี่!
     people_followed_by: ผู้คนที่ %{name} ติดตาม
     people_who_follow: ผู้คนที่ติดตาม %{name}
@@ -156,18 +160,33 @@ th:
       warn: เตือน
       web: เว็บ
     action_logs:
+      actions:
+        change_email_user: "%{name} ได้เปลี่ยนที่อยู่อีเมลของผู้ใช้ %{target}"
+        confirm_user: "%{name} ได้ยืนยันที่อยู่อีเมลของผู้ใช้ %{target}"
+        create_account_warning: "%{name} ได้ส่งคำเตือนไปยัง %{target}"
+        create_domain_block: "%{name} ได้ปิดกั้นโดเมน %{target}"
+        destroy_custom_emoji: "%{name} ได้ทำลายอีโมจิ %{target}"
+        destroy_domain_block: "%{name} ได้เลิกปิดกั้นโดเมน %{target}"
+        destroy_status: "%{name} ได้เอาสถานะโดย %{target} ออก"
+        disable_custom_emoji: "%{name} ได้ปิดใช้งานอีโมจิ %{target}"
+        enable_custom_emoji: "%{name} ได้เปิดใช้งานอีโมจิ %{target}"
+        update_custom_emoji: "%{name} ได้อัปเดตอีโมจิ %{target}"
+        update_status: "%{name} ได้อัปเดตสถานะโดย %{target}"
       deleted_status: "(สถานะที่ลบแล้ว)"
       title: รายการบันทึกการตรวจสอบ
     custom_emojis:
       by_domain: โดเมน
       copy: คัดลอก
+      create_new_category: สร้างหมวดหมู่ใหม่
       created_msg: สร้างอีโมจิสำเร็จ!
       delete: ลบ
       destroyed_msg: ทำลายอีโมโจสำเร็จ!
       disable: ปิดใช้งาน
+      disabled: ปิดใช้งานอยู่
       disabled_msg: ปิดใช้งานอีโมจินั้นสำเร็จ
       emoji: อีโมจิ
       enable: เปิดใช้งาน
+      enabled: เปิดใช้งานอยู่
       enabled_msg: เปิดใช้งานอีโมจินั้นสำเร็จ
       image_hint: PNG สูงสุด 50KB
       new:
@@ -204,6 +223,7 @@ th:
       created_msg: กำลังประมวลผลการปิดกั้นโดเมน
       destroyed_msg: เลิกทำการปิดกั้นโดเมนแล้ว
       domain: โดเมน
+      edit: แก้ไขการปิดกั้นโดเมน
       new:
         create: สร้างการปิดกั้น
         hint: การปิดกั้นโดเมนจะไม่ป้องกันการสร้างรายการบัญชีในฐานข้อมูล แต่จะใช้วิธีการควบคุมเฉพาะกับบัญชีเหล่านั้นย้อนหลังและโดยอัตโนมัติ
@@ -213,6 +233,8 @@ th:
           silence: เงียบ
           suspend: ระงับ
         title: การปิดกั้นโดเมนใหม่
+      private_comment: ความคิดเห็นส่วนตัว
+      public_comment: ความคิดเห็นสาธารณะ
       reject_media: ปฏิเสธไฟล์สื่อ
       reject_media_hint: เอาไฟล์สื่อที่จัดเก็บไว้ในเว็บออกและปฏิเสธที่จะดาวน์โหลดไฟล์ใด ๆ ในอนาคต ไม่เกี่ยวข้องกับการระงับ
       reject_reports: ปฏิเสธรายงาน
@@ -228,6 +250,7 @@ th:
         title: เลิกทำการปิดกั้นโดเมนสำหรับ %{domain}
         undo: เลิกทำ
       undo: เลิกทำการปิดกั้นโดเมน
+      view: ดูการปิดกั้นโดเมน
     email_domain_blocks:
       add_new: เพิ่มใหม่
       delete: ลบ
@@ -243,6 +266,8 @@ th:
         all: ทั้งหมด
         limited: จำกัดอยู่
         title: การควบคุม
+      private_comment: ความคิดเห็นส่วนตัว
+      public_comment: ความคิดเห็นสาธารณะ
       title: การติดต่อกับภายนอก
       total_storage: ไฟล์แนบสื่อ
     invites:
@@ -287,11 +312,15 @@ th:
       unresolved: ยังไม่ได้แก้ปัญหา
       updated_at: อัปเดตเมื่อ
     settings:
+      bootstrap_timeline_accounts:
+        title: การติดตามเริ่มต้นสำหรับผู้ใช้ใหม่
       contact_information:
         email: อีเมลธุรกิจ
         username: ชื่อผู้ใช้ในการติดต่อ
       custom_css:
         title: CSS ที่กำหนดเอง
+      domain_blocks:
+        title: แสดงการปิดกั้นโดเมน
       profile_directory:
         title: เปิดใช้งานไดเรกทอรีโปรไฟล์
       registrations:
@@ -320,36 +349,33 @@ th:
         title: ข้อมูลแบบขยายที่กำหนดเอง
       site_short_description:
         title: คำอธิบายเซิร์ฟเวอร์แบบสั้น
+      site_terms:
+        title: เงื่อนไขการให้บริการที่กำหนดเอง
       site_title: ชื่อเซิร์ฟเวอร์
       timeline_preview:
         desc_html: แสดงเส้นเวลาสาธารณะในหน้าเริ่มต้น
         title: ตัวอย่างเส้นเวลา
       title: การตั้งค่าไซต์
+      trends:
+        title: แฮชแท็กที่กำลังนิยม
     statuses:
       back_to_account: กลับไปที่หน้าบัญชี
       batch:
         delete: ลบ
         nsfw_off: ทำเครื่องหมายว่าไม่ละเอียดอ่อน
         nsfw_on: ทำเครื่องหมายว่าละเอียดอ่อน
+      deleted: ลบแล้ว
       media:
         title: สื่อ
       no_media: ไม่มีสื่อ
       title: สถานะบัญชี
-    subscriptions:
-      callback_url: URL เรียกกลับ
-      confirmed: ยืนยันแล้ว
-      expires_in: หมดอายุภายใน
-      last_delivery: ส่งล่าสุด
-      title: WebSub
-      topic: หัวข้อ
     tags:
-      accounts: บัญชี
-      hidden: ซ่อนอยู่
-      hide: ซ่อนจากไดเรกทอรี
+      context: บริบท
+      directory: ในไดเรกทอรี
+      in_directory: "%{count} ในไดเรกทอรี"
       name: แฮชแท็ก
       title: แฮชแท็ก
-      unhide: แสดงในไดเรกทอรี
-      visible: มองเห็น
+      trending_right_now: กำลังนิยม
     title: การดูแล
     warning_presets:
       add_new: เพิ่มใหม่
@@ -360,6 +386,8 @@ th:
   appearance:
     advanced_web_interface: ส่วนติดต่อเว็บขั้นสูง
     animations_and_accessibility: ภาพเคลื่อนไหวและการช่วยการเข้าถึง
+    confirmation_dialogs: กล่องโต้ตอบการยืนยัน
+    discovery: ค้นพบ
     sensitive_content: เนื้อหาที่ละเอียดอ่อน
   application_mailer:
     notification_preferences: เปลี่ยนการกำหนดลักษณะอีเมล
@@ -368,11 +396,14 @@ th:
     view_profile: ดูโปรไฟล์
     view_status: ดูสถานะ
   applications:
+    created: สร้างแอปพลิเคชันสำเร็จ
+    destroyed: ลบแอปพลิเคชันสำเร็จ
     invalid_url: URL ที่ระบุไม่ถูกต้อง
   auth:
     apply_for_account: ขอคำเชิญ
     change_password: รหัสผ่าน
-    confirm_email: ยืนยันอีเมล
+    checkbox_agreement_html: ฉันยอมรับ <a href="%{rules_path}" target="_blank">กฎของเซิร์ฟเวอร์</a> และ <a href="%{terms_path}" target="_blank">เงื่อนไขการให้บริการ</a>
+    checkbox_agreement_without_rules_html: ฉันยอมรับ <a href="%{terms_path}" target="_blank">เงื่อนไขการให้บริการ</a>
     delete_account: ลบบัญชี
     forgot_password: ลืมรหัสผ่านของคุณ?
     login: เข้าสู่ระบบ
@@ -387,6 +418,8 @@ th:
     reset_password: ตั้งรหัสผ่านใหม่
     security: ความปลอดภัย
     set_new_password: ตั้งรหัสผ่านใหม่
+    status:
+      account_status: สถานะบัญชี
     trouble_logging_in: มีปัญหาในการเข้าสู่ระบบ?
   authorize_follow:
     already_following: คุณกำลังติดตามบัญชีนี้อยู่แล้ว
@@ -397,6 +430,10 @@ th:
       return: แสดงโปรไฟล์ของผู้ใช้
       web: ไปยังเว็บ
     title: ติดตาม %{acct}
+  challenge:
+    confirm: ดำเนินการต่อ
+    invalid_password: รหัสผ่านไม่ถูกต้อง
+    prompt: ยืนยันรหัสผ่านเพื่อดำเนินการต่อ
   datetime:
     distance_in_words:
       about_x_hours: "%{count} ชั่วโมง"
@@ -419,8 +456,10 @@ th:
     explanation: ค้นพบผู้ใช้ตามความสนใจของเขา
     explore_mastodon: สำรวจ %{title}
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': คุณไม่มีสิทธิอนุญาตเพื่อดูหน้านี้
     '404': หน้าที่คุณกำลังมองหาไม่ได้อยู่ที่นี่
+    '406': This page is not available in the requested format.
     '410': หน้าที่คุณกำลังมองหาไม่มีอยู่ที่นี่อีกต่อไป
     '422':
       content: การตรวจสอบความปลอดภัยล้มเหลว คุณกำลังปิดกั้นคุกกี้หรือไม่?
@@ -428,6 +467,9 @@ th:
     '429': Throttled
     '500':
       title: หน้านี้ไม่ถูกต้อง
+    '503': The page could not be served due to a temporary server failure.
+  existing_username_validator:
+    not_found_multiple: ไม่พบ %{usernames}
   exports:
     archive_takeout:
       date: วันที่
@@ -460,6 +502,7 @@ th:
     developers: นักพัฒนา
     more: เพิ่มเติม…
     resources: ทรัพยากร
+    trending_now: กำลังนิยม
   generic:
     all: ทั้งหมด
     changes_saved_msg: บันทึกการเปลี่ยนแปลงสำเร็จ!
@@ -488,9 +531,12 @@ th:
       '86400': 1 วัน
     expires_in_prompt: ไม่เลย
     generate: สร้าง
+    max_uses:
+      other: "%{count} การใช้"
     max_uses_prompt: ไม่มีขีดจำกัด
     table:
       expires_at: หมดอายุเมื่อ
+      uses: การใช้
     title: เชิญผู้คน
   media_attachments:
     validations:
@@ -498,7 +544,10 @@ th:
       too_many: ไม่สามารถแนบมากกว่า 4 ไฟล์
   migrations:
     acct: username@domain ของบัญชีใหม่
-    proceed: บันทึก
+    cancel: ยกเลิกการเปลี่ยนเส้นทาง
+    proceed_with_move: ย้ายผู้ติดตาม
+    redirecting_to: บัญชีของคุณกำลังเปลี่ยนเส้นทางไปยัง %{acct}
+    set_redirect: ตั้งการเปลี่ยนเส้นทาง
   moderation:
     title: การควบคุม
   notification_mailer:
@@ -536,7 +585,11 @@ th:
     older: เก่ากว่า
     prev: ก่อนหน้า
     truncate: "&hellip;"
+  polls:
+    errors:
+      duplicate_options: มีรายการที่ซ้ำกัน
   preferences:
+    other: อื่น ๆ
     posting_defaults: ค่าเริ่มต้นการโพสต์
     public_timelines: เส้นเวลาสาธารณะ
   relationships:
@@ -562,10 +615,6 @@ th:
     reply:
       proceed: ดำเนินการต่อเพื่อตอบกลับ
       prompt: 'คุณต้องการตอบกลับโพสต์นี้:'
-  remote_unfollow:
-    error: ข้อผิดพลาด
-    title: ชื่อเรื่อง
-    unfollowed: เลิกติดตามแล้ว
   sessions:
     activity: กิจกรรมล่าสุด
     browser: เบราว์เซอร์
@@ -613,7 +662,8 @@ th:
     development: การพัฒนา
     edit_profile: แก้ไขโปรไฟล์
     export: การส่งออกข้อมูล
-    import: นำเข้า
+    featured_tags: แฮชแท็กที่แนะนำ
+    import: การนำเข้า
     import_and_export: การนำเข้าและการส่งออก
     notifications: การแจ้งเตือน
     preferences: การกำหนดลักษณะ
@@ -628,12 +678,14 @@ th:
       video:
         other: "%{count} วิดีโอ"
     content_warning: 'คำเตือนเนื้อหา: %{warning}'
+    language_detection: ตรวจหาภาษาโดยอัตโนมัติ
     open_in_web: เปิดในเว็บ
     pin_errors:
       reblog: ไม่สามารถปักหมุดการดัน
     poll:
       total_votes:
         other: "%{count} การลงคะแนน"
+      vote: ลงคะแนน
     show_more: แสดงเพิ่มเติม
     sign_in_to_participate: ลงชื่อเข้าเพื่อเข้าร่วมการสนทนา
     title: '%{name}: "%{quote}"'
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index 3113e7a08f8cae0fa540d36afce2fec20bafb039..7e53e7f2c75aa8222598ad1632af3fa4c490b16d 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -4,25 +4,44 @@ tr:
     about_hashtag_html: Bunlar <strong>#%{hashtag}X</strong> ile etiketlenen genel paylaşımlar. Açık alanda herhangi bir yerde bir hesabınız varsa, onlarla etkileşime geçebilirsiniz.
     about_mastodon_html: Mastodon <em>ücretsiz ve açık kaynaklı</em> bir sosyal ağdır. <em>Merkezileştirilmemiş</em> yapısı sayesinde diğer ticari sosyal platformların aksine iletişimininizin tek bir firmada tutulmasının/yönetilmesinin önüne geçer. Güvendiğiniz bir sunucuyu seçerek oradaki kişilerle etkileşimde bulunabilirsiniz. Herkes kendi Mastodon sunucusunu kurabilir ve sorunsuz bir şekilde Mastodon <em>sosyal ağına</em> dahil edebilir.
     about_this: Bu sunucu hakkında
+    active_count_after: etkin
+    active_footnote: Aylık Aktif Kullanıcılar (AAK)
     administered_by: 'Tarafından yönetildi:'
+    api: API
     apps: Mobil uygulamalar
+    apps_platforms: İos, Android ve diğer platformlardaki Mastodon'u kullanın
+    browse_directory: Bir profil dizinine göz atın ve ilgi alanlarına göre filtreleyin
+    browse_public_posts: Mastodon'daki herkese açık yayınlara göz atın
     contact: İletişim
     contact_missing: Ayarlanmadı
     contact_unavailable: Yok
+    discover_users: Kullanıcıları keşfet
     documentation: Belgeler
-    extended_description_html: |
-      <h3>Kural için iyi bir yer</h3>
-      <p>Genişletilmiş açıklama henüz ayarlanmamış.</p>
-    generic_description: "%{domain} aÄŸdaki bir sunucudur"
+    federation_hint_html: "%{instance} hesabınızla, herhangi bir Mastodon sunucusundaki ve haricindeki kişileri takip edebilirsiniz."
+    get_apps: Bir mobil uygulamayı deneyin
     hosted_on: Mastodon %{domain} üzerinde barındırılıyor
+    instance_actor_flash: |
+      Bu hesap, herhangi bir kullanıcıyı değil sunucunun kendisini temsil etmek için kullanılan sanal bir aktördür.
+      Federasyon amaçlı kullanılır ve tüm yansıyı engellemek istemediğiniz sürece engellenmemelidir; bu durumda bir etki alanı bloğu kullanmanız gerekir.
     learn_more: Daha fazla bilgi edinin
     privacy_policy: Gizlilik politikası
+    see_whats_happening: Neler olduğunu görün
+    server_stats: 'Sunucu istatistikleri:'
     source_code: Kaynak kodu
     status_count_after:
       one: durum
       other: durum
     status_count_before: Åžu ana kadar
+    tagline: Arkadaşlarını takip et ve yenilerini keşfet
     terms: Kullanım şartları
+    unavailable_content: Mevcut olmayan içerik
+    unavailable_content_description:
+      domain: Sunucu
+      reason: Sebep
+      rejecting_media: 'Bu sunuculardaki medya dosyaları işlenmeyecek ya da saklanmayacak, ve hiçbir küçük resim gösterilmeyecektir, dolayısıyla orjinal dosyaya manuel tıklama gerekecektir:'
+      silenced: 'Bu sunuculardan gelen gönderiler genel zaman çizelgelerinde ve konuşmalarda gizlenecek ve siz onları takip etmediğiniz sürece, kullanıcıların etkileşimlerinden hiçbir bildirim alınmayacaktır:'
+      suspended: 'Bu sunuculardaki hiçbir veri işlenmeyecek, saklanmayacak veya değiş tokuş edilmeyecektir, dolayısıyla bu sunuculardaki kullanıcılarla herhangi bir etkileşim ya da iletişim imkansız olacaktır:'
+    unavailable_content_html: Mastodon, genel olarak fediverse'teki herhangi bir sunucudan içerik görüntülemenize ve kullanıcılarıyla etkileşim kurmanıza izin verir. Bunlar, bu sunucuda yapılmış olan istisnalardır.
     user_count_after:
       one: kullanıcı
       other: kullanıcı
@@ -30,38 +49,51 @@ tr:
     what_is_mastodon: Mastodon nedir?
   accounts:
     choices_html: "%{name} seçimleri:"
+    endorsements_hint: Takip ettiğiniz kişileri web arayüzünden onaylayabilirsiniz, burada görünecekler.
+    featured_tags_hint: Burada görüntülenecek belirli etiketlere sahip olabilirsiniz.
     follow: Takip et
     followers:
       one: Takipçi
       other: Takipçi
     following: Takip ediliyor
     joined: "%{date} tarihinde katıldı"
+    last_active: son aktivite
     link_verified_on: Bu bağlantının mülkiyeti %{date} tarihinde kontrol edildi
     media: Medya
     moved_html: "%{name}, %{new_profile_link} adresine taşındı:"
     network_hidden: Bu bilgi mevcut deÄŸil
+    never_active: Asla
     nothing_here: Burada henüz hiçbir gönderi yok!
     people_followed_by: Kullanıcı %{name}'in takip ettikleri
     people_who_follow: Kullanıcı %{name}'i takip edenler
     pin_errors:
       following: Onaylamak istediğiniz kişiyi zaten takip ediyor olmalısınız
+    posts:
+      one: Gönderi
+      other: Tootlar
     posts_tab_heading: Tootlar
     posts_with_replies: Tootlar ve yanıtlar
     reserved_username: Kullanıcı adı saklıdır
     roles:
       admin: Yönetici
+      bot: Bot
       moderator: Denetleyici
+    unavailable: Profil kullanılamıyor
     unfollow: Takibi bırak
   admin:
     account_actions:
       action: Eylemi gerçekleştir
+      title: "%{acct} üzerinde denetleme eylemi gerçekleştir"
     account_moderation_notes:
       create: Not bırakın
       created_msg: Denetim notu başarıyla oluşturuldu!
       delete: Sil
       destroyed_msg: Denetim notu başarıyla yok edildi!
     accounts:
+      approve: Onayla
+      approve_all: Tümünü onayla
       are_you_sure: Emin misiniz?
+      avatar: Avatar
       by_domain: Sunucu
       change_email:
         changed_msg: Hesap e-postası başarıyla değiştirildi!
@@ -74,6 +106,7 @@ tr:
       confirmed: Onaylandı
       confirming: Onaylama
       deleted: Silinen
+      demote: Düşür
       disable: Devre dışı
       disable_two_factor_authentication: 2AD kapat
       disabled: Kapalı
@@ -91,6 +124,7 @@ tr:
       header: Üstbilgi
       inbox_url: Gelen kutusu bağlantısı
       invited_by: Tarafından davet edildi
+      ip: IP
       joined: Katıldı
       location:
         all: Hepsi
@@ -101,14 +135,20 @@ tr:
       media_attachments: Medya ekleri
       memorialize: Bir hatıraya dön
       moderation:
+        active: Etkin
         all: Hepsi
+        pending: Bekliyor
         silenced: Susturulanlar
         suspended: Uzaklaştırılanlar
         title: Yönetim
       moderation_notes: Denetleme notları
       most_recent_activity: Son aktivite
       most_recent_ip: Son IP
+      no_account_selected: Hiçbiri seçilmediğinden hiçbir hesap değiştirilmedi
+      no_limits_imposed: Sınır koymaz
       not_subscribed: Abone edilmedi
+      outbox_url: Giden Kutusu URL'si
+      pending: Bekleyen yorum
       perform_full_suspension: Askıya al
       profile_url: Profil linki
       promote: Yükselt
@@ -116,6 +156,8 @@ tr:
       public: Herkese açık
       push_subscription_expires: PuSH aboneliÄŸi dolumu
       redownload: Profili yenile
+      reject: Reddet
+      reject_all: Tümünü reddet
       remove_avatar: Avatarı kaldır
       remove_header: Üstbilgiyi kaldır
       resend_confirmation:
@@ -142,6 +184,7 @@ tr:
       statuses: Durumlar
       subscribe: Abone ol
       suspended: Askıya alındı
+      time_in_queue: "%{time} kuyruÄŸunda bekliyor"
       title: Hesaplar
       unconfirmed_email: Onaylanmamış e-posta
       undo_silenced: Susturmayı geri al
@@ -149,25 +192,136 @@ tr:
       unsubscribe: Abonelikten çık
       username: Kullanıcı adı
       warn: Uyar
+      web: Web
+      whitelisted: Beyaz listede
     action_logs:
       actions:
+        assigned_to_self_report: "%{name} kendilerine %{target} adlı raporu verdi"
+        change_email_user: "%{name}, %{target} kullanıcısının e-posta adresini değiştirdi"
         confirm_user: "%{name} %{target} kullanıcısının e-posta adresini onayladı"
+        create_account_warning: "%{name} %{target} 'a bir uyarı gönderdi"
         create_custom_emoji: "%{name} yeni ifade yükledi %{target}"
+        create_domain_block: "%{target} alanı, %{name} tarafından engellendi"
+        create_email_domain_block: "%{target} e-posta alanı, %{name} tarafından kara listeye alınmış"
+        demote_user: "%{name} %{target} kullanıcısını düşürdü"
+        destroy_custom_emoji: "%{target} emoji, %{name} tarafından kaldırıldı"
+        destroy_domain_block: "%{target} alan adının engeli %{name} tarafından kaldırıldı"
+        destroy_email_domain_block: "%{target} e-posta sunucusu, %{name} tarafından beyaz listeye alındı"
+        destroy_status: "%{name}, %{target} kullanıcısının durumunu kaldırdı"
         disable_2fa_user: "%{name}, %{target} kullanıcısı için iki adım gereksinimini kapattı"
+        disable_custom_emoji: "%{target} emoji, %{name} tarafından devre dışı bırakıldı"
+        disable_user: "%{name} %{target} kullanıcısı için oturum açmayı devre dışı bıraktı"
+        enable_custom_emoji: "%{name} %{target} için emojiyi etkinleştirdi"
+        enable_user: "%{name} %{target} için oturum açmayı etkinleştirdi"
+        memorialize_account: "%{name} %{target}'in hesabını bir hatıra sayfasına dönüştürdü"
+        promote_user: "%{name} %{target} kullanıcısını yükseltti"
+        remove_avatar_user: "%{name} %{target}'in avatarını kaldırdı"
+        reopen_report: "%{name} %{target} şikayetini yeniden açtı"
+        reset_password_user: "%{name} %{target} kullanıcısının parolasını resetledi"
+        resolve_report: "%{name} %{target} şikayetini çözdü"
+        silence_account: "%{name} %{target}'in hesabını susturdu"
+        suspend_account: "%{name} %{target}'in hesabını uzaklaştırdı"
+        unassigned_report: "%{name} %{target} şikayetinin atamasını geri aldı"
+        unsilence_account: "%{name} %{target}'in hesabının susturmasını kaldırdı"
+        unsuspend_account: "%{name} %{target}'in hesabının uzaklaştırmasını kaldırdı"
+        update_custom_emoji: "%{name} %{target} emojiyi güncelledi"
+        update_status: "%{name}, %{target} kullanıcısının durumunu güncelledi"
+      deleted_status: "(silinmiÅŸ durum)"
+      title: Denetim günlüğü
+    custom_emojis:
+      assign_category: Kategori ata
+      by_domain: Alan adı
+      copied_msg: Emojinin yerel kopyası başarıyla oluşturuldu
+      copy: Kopyala
+      copy_failed_msg: Bu emojinin yerel bir kopyası oluşturulamadı
+      create_new_category: Yeni kategori ekle
+      created_msg: Emoji başarıyla oluşturuldu!
+      delete: Sil
+      destroyed_msg: Emojo başarıyla yok edildi!
+      disable: Devre dışı bırak
+      disabled: Devre dışı
+      disabled_msg: Bu emoji başarıyla devre dışı bırakıldı
+      emoji: İfadeler
+      enable: EtkinleÅŸtir
+      enabled: Etkin
+      enabled_msg: Bu emojiyi başarıyla etkinleştirdi
+      image_hint: 50 KB'a kadar PNG
+      list: Liste
+      listed: Listelenen
+      new:
+        title: Yeni özel emoji ekle
+      overwrite: Üzerine yaz
+      shortcode: Kısa kod
+      shortcode_hint: En az 2 karakter, sadece alfanümerik karakterler ve alt çizgiler
+      title: Özel emojiler
+      uncategorized: KategorilenmemiÅŸ
+      unlist: Liste dışı
+      unlisted: ListelenmemiÅŸ
+      update_failed_msg: Bu emoji güncellenemedi
+      updated_msg: Emoji başarıyla güncellendi!
+      upload: Yükle
+    dashboard:
+      authorized_fetch_mode: Güvenli mod
+      backlog: bekleyen iÅŸler
+      config: Yapılandırma
+      feature_deletions: Hesap silme
+      feature_invites: Davet linkleri
+      feature_profile_directory: Profil dizini
+      feature_registrations: Kayıtlar
+      feature_relay: Federasyon aktarıcısı
+      feature_spam_check: Anti-spam
+      feature_timeline_preview: Zaman çizelgesi önizlemesi
+      features: Özellikler
+      hidden_service: Gizli servislere sahip federasyon
+      open_reports: raporları aç
+      pending_tags: inceleme için bekleyen hashtag'ler
+      pending_users: inceleme için bekleyen kullanıcılar
+      recent_users: Son kullanıcılar
+      search: Tam metin araması
+      single_user_mode: Tek kullanıcı modu
+      software: Yazılım
+      space: Alan kullanımı
+      title: Kontrol Paneli
+      total_users: toplam kullanıcı
+      trends: Trendler
+      week_interactions: bu haftaki etkileÅŸimler
+      week_users_active: bu hafta aktif
+      week_users_new: bu hafta kullanıcılar
+      whitelist_mode: Beyaz liste modu
+    domain_allows:
+      add_new: Alan adını beyaz listeye al
+      created_msg: Alan adı başarıyla beyaz listeye alındı
+      destroyed_msg: Alan adı beyaz listeden kaldırıldı
+      undo: Beyaz listeden çıkar
     domain_blocks:
       add_new: Yeni ekle
       created_msg: Domain bloÄŸu ÅŸu an iÅŸleniyor
       destroyed_msg: Domain bloÄŸu silindi
+      domain: Alan adı
+      edit: Etki alanı bloğunu düzenle
+      existing_domain_block_html: '%{name}''e zaten daha katı sınırlar uyguladınız, önce <a href="%{unblock_url}">engellemesini kaldırmanız</a> gerekiyor.'
       new:
         create: Yeni blok oluÅŸtur
         hint: Domain bloğu, veri tabanında hesap kayıtlarının oluşturulmasını engellemez, fakat o hesapların üzerine otomatik olarak belirli yönetim metodlarını olarak uygular.
         severity:
           desc_html: "<strong>Susturma</strong>, uygulanan hesabın gönderilerini, o hesabı takip etmeyen diğer herkese gizler. <strong>Uzaklaştırma</strong> hesabın bütün içeriğini, ortam dosyalarını ve profil verisini siler."
+          noop: Yok
           silence: Sustur
           suspend: Uzaklaştır
         title: Yeni domain bloÄŸu
+      private_comment: Özel yorum
+      private_comment_hint: Denetleyiciler tarafından dahili kullanım için bu alan adı sınırlaması hakkında yorum.
+      public_comment: Genel yorum
+      public_comment_hint: Eğer alan adı sınırlamaları listesinin tanıtılması etkinleştirilmişse, genel kullanım için bu alan adı sınırlaması hakkında yorum.
       reject_media: Ortam dosyalarını reddetme
       reject_media_hint: Yerel olarak depolanmış ortam dosyalarını ve gelecekte indirilecek olanları reddeder. Uzaklaştırma için uygun değildir
+      reject_reports: Raporları reddet
+      reject_reports_hint: Bu alan adından gelen tüm şikayetleri yok sayın. Uzaklaştırmalar için mevzu dışıdır
+      rejecting_media: ortam dosyalarını reddet
+      rejecting_reports: ÅŸikayetleri reddet
+      severity:
+        silence: susturulmuÅŸ
+        suspend: uzaklaştırılmış
       show:
         affected_accounts:
           one: Veritabanındaki bir hesap etkilendi
@@ -178,69 +332,319 @@ tr:
         title: "%{domain} domain'i için yapılan işlemi geri al"
         undo: Geri al
       undo: Geri al
+      view: Alan adı bloğunu görüntüle
+    email_domain_blocks:
+      add_new: Yeni ekle
+      created_msg: E-posta alan adı kara listeye başarıyla eklendi
+      delete: Sil
+      destroyed_msg: E-posta alan adı kara listeden başarıyla silindi
+      domain: Alan adı
+      new:
+        create: Alan adı ekle
+        title: Yeni e-posta kara liste giriÅŸi
+      title: E-posta kara listesi
+    followers:
+      back_to_account: Hesaba Geri Dön
+      title: "%{acct} Takipçileri"
     instances:
+      by_domain: Alan adı
+      delivery_available: Teslimat mevcut
+      known_accounts:
+        one: "%{count} bilinen hesap"
+        other: "%{count} bilinen hesap"
+      moderation:
+        all: Tümü
+        limited: Sınırlı
+        title: Denetim
+      private_comment: Özel yorum
+      public_comment: Genel yorum
       title: Bilinen Sunucular
+      total_blocked_by_us: Tarafımızca engellenen
+      total_followed_by_them: Onlar tarafından takip edilen
+      total_followed_by_us: Tarafımızca takip edilen
+      total_reported: Onlar hakkında şikayetler
+      total_storage: Medya ekleri
+    invites:
+      deactivate_all: Tümünü devre dışı bırak
+      filter:
+        all: Tümü
+        available: Mevcut
+        expired: Süresi dolmuş
+        title: Filtre
+      title: Davetler
+    pending_accounts:
+      title: Bekleyen hesaplar (%{count})
+    relays:
+      add_new: Yeni aktarıcı ekle
+      delete: Sil
+      description_html: "<strong>Federasyon aktarıcısı</strong>, kendisine abone olan ve yayın yapan sunucular arasında büyük miktarlarda herkese açık gönderilerin değiş tokuşunu yapan aracı bir sunucudur. <strong>Küçük ve orta boyutlu sunucuların fediverse'ten içerik keşfetmesine yardımcı olurlar</strong>, aksi takdirde yerel kullanıcıların uzak sunuculardaki diğer kişileri manuel olarak takip etmeleri gerekecektir."
+      disable: Devre dışı
+      disabled: Devre dışı
+      enable: Etkin
+      enable_hint: Etkinleştirildiğinde, sunucunuz bu aktarıcıdan gelecek tüm herkese açık gönderilere abone olacak, ve kendisinin herkese açık gönderilerini bu aktarıcıya göndermeye başlayacaktır.
+      enabled: Etkin
+      inbox_url: Aktarıcı URL'si
+      pending: Aktarıcının onaylaması için bekleniyor
+      save_and_enable: Kaydet ve etkinleÅŸtir
+      setup: Bir aktarıcı bağlantısı kur
+      signatures_not_enabled: Güvenli mod ya da beyaz liste modu etkin iken aktarıcılar düzgün çalışmayacaktır
+      status: Durum
+      title: Aktarıcılar
+    report_notes:
+      created_msg: Şikayet notu başarıyla oluşturuldu!
+      destroyed_msg: Şikayet notu başarıyla silindi!
     reports:
+      account:
+        note: not
+        report: ÅŸikayet
+      action_taken_by: tarafından gerçekleştirilen eylem
+      are_you_sure: Emin misiniz?
+      assign_to_self: Bana ata
+      assigned: Denetleyici atandı
       comment:
         none: Yok
+      created_at: Åžikayet edildi
       mark_as_resolved: Giderildi olarak iÅŸaretle
+      mark_as_unresolved: Çözümlenmemiş olarak işaretle
+      notes:
+        create: Not Ekle
+        create_and_resolve: Not ile çözümle
+        create_and_unresolve: Not ile tekrar aç
+        delete: Sil
+        placeholder: Hangi işlemlerin yapıldığını, ya da diğer ilgili güncellemeleri açıklayın...
+      reopen: Şikayeti tekrar aç
       report: 'Åžikayet #%{id}'
       reported_account: Åžikayet edilen hesap
       reported_by: Åžikayet eden
       resolved: Giderildi
+      resolved_msg: Şikayet başarıyla çözümlendi!
       status: Durum
       title: Åžikayetler
+      unassign: Atamayı geri al
       unresolved: Giderilmedi
+      updated_at: Güncellendi
     settings:
+      activity_api_enabled:
+        desc_html: Yerel olarak yayınlanan durumların, aktif kullanıcıların, ve haftalık kovalardaki yeni kayıtların sayısı
+        title: Kullanıcı etkinliği hakkında toplu istatistikler yayınlayın
+      bootstrap_timeline_accounts:
+        desc_html: Birden fazla kullanıcı adını virgülle ayırın. Yalnızca yerel ve kilitlenmemiş hesaplar geçerlidir. Boş olduğunda varsayılan tüm yerel yöneticilerdir.
+        title: Yeni kullanıcılar için varsayılan takipler
       contact_information:
         email: Herkese açık e-posta adresiniz
         username: Bir kullanıcı adı giriniz
+      custom_css:
+        desc_html: Görünümü her sayfada yüklenecek CSS ile değiştirin
+        title: Özel CSS
+      default_noindex:
+        desc_html: Bu ayarı kendileri değiştirmeyen tüm kullanıcıları etkiler
+        title: Varsayılan olarak kullanıcıları arama motoru indekslemesinin dışında tut
+      domain_blocks:
+        all: Herkes için
+        disabled: Hiç kimseye
+        title: Alan adı bloklarını göster
+        users: Oturum açan yerel kullanıcılara
+      domain_blocks_rationale:
+        title: Gerekçeyi göster
+      hero:
+        desc_html: Önsayfada görüntülenir. En az 600x100px önerilir. Ayarlanmadığında, sunucu küçük resmi kullanılır
+        title: Kahraman görseli
+      mascot:
+        desc_html: Birden fazla sayfada görüntülenir. En az 293x205px önerilir. Ayarlanmadığında, varsayılan maskot kullanılır
+        title: Maskot görseli
+      peers_api_enabled:
+        desc_html: Bu sunucunun fediverse'te karşılaştığı alan adları
+        title: Keşfedilen sunucuların listesini yayınla
+      preview_sensitive_media:
+        desc_html: Medya duyarlı olarak işaretlenmiş olsa bile, diğer web sitelerindeki bağlantı ön izlemeleri küçük resim gösterecektir
+        title: OpenGraph ön izlemelerinde hassas medyayı göster
+      profile_directory:
+        desc_html: Kullanıcıların keşfedilebilir olmasına izin ver
+        title: Profil dizinini etkinleÅŸtir
       registrations:
         closed_message:
           desc_html: Kayıt alımları kapatıldığında ana sayfada görüntülenecek mesajdır. <br> HTML etiketleri kullanabilirsiniz
           title: Kayıt alımları kapatılma mesajı
+        deletion:
+          desc_html: Herkese hesabını silme izni ver
+          title: Hesap silmeyi aç
+        min_invite_role:
+          disabled: Hiç kimse
+          title: tarafından yapılan davetlere izin ver
+      registrations_mode:
+        modes:
+          approved: Kayıt için onay gerekli
+          none: Hiç kimse kayıt olamaz
+          open: Herkes kaydolabilir
+        title: Kayıt modu
+      show_known_fediverse_at_about_page:
+        desc_html: Değiştirildiğinde, bilinen bütün fediverse'lerden gönderileri ön izlemede gösterir. Diğer türlü sadece yerel gönderileri gösterecektir.
+        title: Zaman çizelgesi ön izlemesinde bilinen fediverse'i göster
+      show_staff_badge:
+        desc_html: Kullanıcının sayfasında bir personel rozeti göster
+        title: Personel rozeti göster
       site_description:
         desc_html: Ana sayfada paragraf olarak görüntülenecek bilgidir.<br>Özellikle <code>&lt;a&gt;</code> ve <code>&lt;em&gt;</code> olmak suretiyle HTML etiketlerini kullanabilirsiniz.
         title: Site açıklaması
       site_description_extended:
         desc_html: Harici bilgi sayfasında gösterilir.<br>HTML etiketleri girebilirsiniz
         title: Sunucu hakkında detaylı bilgi
+      site_short_description:
+        desc_html: Kenar çubuğunda ve meta etiketlerinde görüntülenir. Mastodon'un ne olduğunu ve bu sunucuyu özel kılan şeyleri tek bir paragrafta açıklayın.
+        title: Kısa sunucu açıklaması
+      site_terms:
+        desc_html: Kendi gizlilik politikanızı, hizmet şartlarınızı ya da diğer hukuki metinlerinizi yazabilirsiniz. HTML etiketleri kullanabilirsiniz
+        title: Özel hizmet şartları
       site_title: Site başlığı
+      spam_check_enabled:
+        desc_html: Mastodon, tekrar eden istenmeyen mesajlar gönderen hesapları otomatik olarak susturabilir ve şikayet edebilir. Yanlışlar olabilir.
+        title: Anti-spam otomasyonu
+      thumbnail:
+        desc_html: OpenGraph ve API ile ön izlemeler için kullanılır. 1200x630px tavsiye edilir
+        title: Sunucu küçük resmi
+      timeline_preview:
+        desc_html: Açılış sayfasında genel zaman çizelgesini görüntüle
+        title: Zaman çizelgesi önizlemesi
       title: Site Ayarları
-    subscriptions:
-      callback_url: Callback linki
-      confirmed: Onaylandı
-      expires_in: BitiÅŸ Tarihi
-      last_delivery: Son gönderim
-      topic: Konu
+      trendable_by_default:
+        desc_html: Daha önce izin verilmeyen etiketleri etkiler
+        title: Ön inceleme yapmadan etiketlerin trend olmasına izin ver
+      trends:
+        desc_html: Şu anda trend olan ve daha önce incelenen etiketleri herkese açık olarak göster
+        title: Trend etiketler
+    statuses:
+      back_to_account: Hesap sayfasına geri dön
+      batch:
+        delete: Sil
+        nsfw_off: Hassas deÄŸil olarak iÅŸaretle
+        nsfw_on: Hassas olarak iÅŸaretle
+      deleted: Silindi
+      failed_to_execute: Çalıştırılamadı
+      media:
+        title: Medya
+      no_media: Medya yok
+      no_status_selected: Hiçbiri seçilmediğinden hiçbir durum değiştirilmedi
+      title: Hesap durumları
+      with_media: Medya ile
     tags:
-      accounts: Hesaplar
-      name: Etiketler
+      accounts_today: Bugünkü eşsiz kullanımlar
+      accounts_week: Bu haftaki eşsiz kullanımlar
+      breakdown: Bugünkü kullanımın kaynağa göre dağılımı
+      context: İçerik
+      directory: Dizinde
+      in_directory: Dizinde %{count}
+      last_active: Son aktiflik
+      most_popular: En popüler
+      most_recent: En yeni
+      name: Etiket
+      review: Durumu gözden geçir
+      reviewed: Gözden geçirildi
       title: Etiketler
+      trending_right_now: Åžu anda trend
+      unique_uses_today: bugün %{count} gönderi
+      unreviewed: Gözden geçirilmedi
+      updated_msg: Etiket ayarları başarıyla güncellendi
     title: Yönetim
     warning_presets:
       add_new: Yeni ekle
       delete: Sil
       edit: Düzenle
+      edit_preset: Uyarı ön-ayarını düzenle
+      title: Uyarı ön-ayarlarını yönet
+  admin_mailer:
+    new_pending_account:
+      body: Yeni hesabın detayları aşağıdadır. Bu başvuruyu onaylayabilir ya da reddedebilirsiniz.
+      subject: "%{instance} üzerinde gözden geçirmek için yeni hesap (%{username})"
+    new_report:
+      body: "%{reporter} %{target}'i ÅŸikayet etti"
+      body_remote: "%{domain}'den birisi %{target}'i ÅŸikayet etti"
+      subject: "%{instance} için yeni şikayet (#%{id})"
+    new_trending_tag:
+      body: "#%{name} etiketi bugün trend, ancak daha önce incelenmedi. Siz izin vermediğiniz sürece herkese açık olarak gösterilmeyecek, ya da bir daha asla hakkında bir şey duymamak için olduğu şekliyle formu kaydedin."
+      subject: "%{instance} üzerinde gözden geçirmek için yeni etiket (#%{name})"
+  aliases:
+    add_new: Takma ad oluÅŸtur
+    created_msg: Yeni takma ad başarıyla oluşturuldu. Artık eski hesaptan taşınmayı başlatabilirsiniz.
+    deleted_msg: Yeni takma ad başarıyla kaldırıldı. O hesaptan bu hesaba taşınmak artık mümkün değil.
+    hint_html: Başka bir hesaptan bu hesaba taşınmak istiyorsanız, takipçileri eski hesaptan bu hesaba taşımadan önce gerekli olan takma adı burada oluşturabilirsiniz. Bu eylem kendi başına <strong>zararsızdır ve geri döndürülebilir</strong>. <strong>Hesap taşıma işlemi eski hesaptan başlatılır</strong>.
+    remove: Takma adların bağlantısını kaldır
+  appearance:
+    advanced_web_interface: Gelişmiş web arayüzü
+    advanced_web_interface_hint: 'Tüm ekran genişliğinizden yararlanmak istiyorsanız, gelişmiş web arayüzü istediğiniz kadar bilgi görecek kadar çok sayıda farklı sütunu yapılandırmanıza olanak tanır: Anasayfa, bildirimler, birleşik zaman çizelgesi, istediğiniz sayıda liste ve etiket.'
+    animations_and_accessibility: Animasyonlar ve eriÅŸilebilirlik
+    confirmation_dialogs: Onay iletişim kutuları
+    discovery: KeÅŸfet
+    sensitive_content: Hassas içerik
   application_mailer:
+    notification_preferences: E-posta tercihlerini deÄŸiÅŸtir
+    salutation: "%{name},"
     settings: 'E-mail tercihlerini deÄŸiÅŸtir: %{link}'
     view: 'Görüntüle:'
+    view_profile: Profili Görüntüle
+    view_status: Durumu görüntüle
   applications:
+    created: Uygulama başarıyla oluşturuldu
+    destroyed: Uygulama başarıyla silindi
     invalid_url: Verilen URL geçerli değil
+    regenerate_token: EriÅŸim belirtecini yeniden oluÅŸtur
+    token_regenerated: Erişim belirteci başarıyla oluşturuldu
+    warning: Bu verilere çok dikkat edin. Asla kimseyle paylaşmayın!
+    your_token: EriÅŸim belirteciniz
   auth:
+    apply_for_account: Davet et
+    change_password: Parola
+    checkbox_agreement_html: <a href="%{rules_path}" target="_blank">sunucu kuralları</a> ve<a href="%{terms_path}" target="_blank">hizmet şartlarını</a> kabul ediyorum
+    checkbox_agreement_without_rules_html: <a href="%{terms_path}" target="_blank">Hizmet şartlarını</a> kabul ediyorum
+    delete_account: Hesabı sil
+    delete_account_html: Hesabınızı silmek isterseniz, <a href="%{path}">buradan devam edebilirsiniz</a>. Sizden onay istenecektir.
+    description:
+      prefix_invited_by_user: "@%{name} sizi Mastodon'un bu sunucusuna katılmaya davet ediyor!"
+      prefix_sign_up: Bugün Mastodon'a kaydolun!
+      suffix: Bir hesapla, kişileri takip edebilir, güncellemeler gönderebilir, herhangi bir Mastodon sunucusundan kullanıcılarla mesaj alışverişinde bulunabilir ve daha birçok şey yapabilirsin!
     didnt_get_confirmation: Hesap doğrulama mailini almadınız mı?
     forgot_password: Parolanızı unuttunuz mu?
+    invalid_reset_password_token: Parola sıfırlama belirteci geçersiz veya süresi dolmuş. Lütfen yeni bir tane talep edin.
     login: GiriÅŸ yap
     logout: Çıkış
+    migrate_account: Farklı bir hesaba taşının
+    migrate_account_html: Bu hesabı başka bir hesaba yönlendirmek istiyorsanız, <a href="%{path}">buradan yapılandırabilirsiniz</a>.
+    or_log_in_with: Veya giriş yapın
+    providers:
+      cas: CAS
+      saml: SAML
     register: Üye ol
+    registration_closed: "%{instance} yeni üyeler kabul etmemektedir"
     resend_confirmation: Doğrulama mailini tekrar gönder
     reset_password: Parolayı değiştir
     security: Kimlik bilgileri
     set_new_password: Yeni parola oluÅŸtur
+    setup:
+      email_below_hint_html: Eğer aşağıdaki e-posta adresi yanlışsa, onu burada değiştirebilir ve yeni bir doğrulama e-postası alabilirsiniz.
+      email_settings_hint_html: Onaylama e-postası %{email} adresine gönderildi. Eğer bu e-posta adresi doğru değilse, hesap ayarlarından değiştirebilirsiniz.
+      title: Kurulum
+    status:
+      account_status: Hesap durumu
+      confirming: E-posta doğrulamasının tamamlanması bekleniyor.
+      functional: Hesabınız tamamen kullanıma hazır.
+      pending: Başvurunuz personelimiz tarafından gözden geçirilmeyi beklemektedir. Bu biraz zaman alabilir. Başvurunuz onaylanırsa bir e-posta alacaksınız.
+      redirecting_to: Hesabınız aktif değil çünkü şu anda %{acct} adresine yönlendirilmektedir.
+    trouble_logging_in: Oturum açarken sorun mu yaşıyorsunuz?
   authorize_follow:
+    already_following: Bu hesabı zaten takip ediyorsunuz
     error: Uzak hesap aranırken bir hata oluştu
     follow: Takip et
+    follow_request: 'Şuna takip isteği gönderdiniz:'
+    following: 'Başarılı! Artık şunu takip ediyorsunuz:'
+    post_follow:
+      close: Ya da, sadece bu pencereyi kapatabilirsiniz.
+      return: Kullanıcının profilini göster
+      web: Web'e git
     title: "%{acct}'i takip et"
+  challenge:
+    confirm: Devam et
+    hint_html: "<strong>İpucu:</strong> Önümüzdeki saat boyunca sana parolanı sormayacağız."
+    invalid_password: Geçersiz parola
+    prompt: Devam etmek parolayı doğrulayın
   datetime:
     distance_in_words:
       about_x_hours: "%{count}sa"
@@ -255,48 +659,197 @@ tr:
       x_minutes: "%{count}dk"
       x_months: "%{count}ay"
       x_seconds: "%{count}sn"
+  deletes:
+    challenge_not_passed: GirdiÄŸiniz bilgi doÄŸru deÄŸildi
+    confirm_password: Kimliğinizi doğrulamak için mevcut parolanızı girin
+    confirm_username: Prosedürü doğrulamak için kullanıcı adınızı girin
+    proceed: Hesabı sil
+    success_msg: Hesabınız başarıyla silindi
+    warning:
+      before: 'Devam etmeden önce, lütfen bu notları dikkatlice okuyun:'
+      caches: Diğer sunucular tarafından ön belleğe alınan içerik kalabilir
+      data_removal: Gönderileriniz ve diğer verileriniz kalıcı olarak silinecektir
+      email_change_html: Hesabınızı silmeden <a href="%{path}">e-posta adresinizi değiştirebilirsiniz</a>
+      email_contact_html: Eğer hala ulaşmazsa, yardım için <a href="mailto:%{email}">%{email}</a> adresine e-posta gönderebilirsiniz
+      email_reconfirmation_html: Eğer doğrulama e-postası almıyorsanız, <a href="%{path}">tekrar talep edebilirsiniz</a>
+      irreversible: Hesabınızı geri yükleyemeyecek ya da yeniden etkinleştiremeyeceksiniz
+      more_details_html: Daha fazla ayrıntı için, <a href="%{terms_path}">gizlilik politikası</a>na göz atın.
+      username_available: Kullanıcı adınız tekrar kullanılabilir olacaktır
+      username_unavailable: Kullanıcı adınız kullanılamaz kalacaktır
+  directories:
+    directory: Profil dizini
+    explanation: Kullanıcıları ilgi alanlarına göre keşfedin
+    explore_mastodon: "%{title} keÅŸfet"
+  domain_validator:
+    invalid_domain: geçerli bir alan adı değil
   errors:
+    '400': Gönderdiğiniz istek geçersiz veya hatalı biçimlendirilmiş.
     '403': Bu sayfayı görmek için izniniz yok.
     '404': Aradığınız sayfa bulunamadı.
+    '406': Bu sayfa istenen formatta mevcut deÄŸil.
     '410': Aradığınız sayfa artık yok.
     '422':
       content: Güvenlik doğrulaması başarısız oldu. Site cookie'lerini engellemiş olabilirsiniz.
       title: Güvenlik doğrulamasu başarısız
-    '429': Throttled
-    '500': 
+    '429': Kısıtlandı
+    '500':
+      content: Üzgünüz, ancak bir şey ters gitti.
+      title: Bu sayfa doÄŸru deÄŸil
+    '503': Geçici sunucu hatası nedeniyle sayfa görüntülenemedi.
+    noscript_html: Mastodon web uygulamasını kullanmak için lütfen JavaScript'i etkinleştirin. Alternatif olarak, platformunuz için Mastodon <a href="%{apps_path}">yerel uygulamalardan</a> birini deneyin.
+  existing_username_validator:
+    not_found: bu kullanıcı adına sahip yerel bir kullanıcı bulunamadı
+    not_found_multiple: "%{usernames} bulunamadı"
   exports:
+    archive_takeout:
+      date: Tarih
+      download: ArÅŸivinizi indirin
+      hint_html: "<strong>Gönderileriniz ve yüklediğiniz ortamların</strong> bir arşivini talep edebilirsiniz. Dışa aktarılan veriler, herhangi bir uyumlu yazılım tarafından okunabilen ActivityPub formatında olacaktır. Her 7 günde bir arşiv talep edebilirsiniz."
+      in_progress: ArÅŸivinizi derliyoruz...
+      request: ArÅŸiv isteÄŸi
+      size: Boyut
     blocks: Blokladıklarınız
+    csv: CSV
+    domain_blocks: Alan adı blokları
     follows: Takip ettikleriniz
+    lists: Listeler
     mutes: Susturduklarınız
     storage: Ortam deposu
+  featured_tags:
+    add_new: Yeni ekle
+    errors:
+      limit: Zaten azami hashtag miktarı belirlediniz
+    hint_html: "<strong>Öne çıkan etiketler nelerdir?</strong> Genel profilinizde belirgin bir şekilde görüntülenirler ve kişilerin genel yayınlarınıza özellikle bu etiketler altında göz atmalarına izin verir. Yaratıcı çalışmaları veya uzun vadeli projeleri takip etmek için harika bir araçtır."
+  filters:
+    contexts:
+      home: Ana zaman çizelgesi
+      notifications: Bildirimler
+      public: Genel zaman çizelgesi
+      thread: Sohbetler
+    edit:
+      title: Filtreyi düzenle
+    errors:
+      invalid_context: Sıfır ya da geçersiz içerik sağlandı
+      invalid_irreversible: Geri dönüşümsüz filtreleme sadece anasayfa ya da bildirim bağlamında çalışır
+    index:
+      delete: Sil
+      title: Filtreler
+    new:
+      title: Yeni filtre ekle
+  footer:
+    developers: GeliÅŸtiriciler
+    more: Daha Fazla…
+    resources: Kaynaklar
+    trending_now: Trendler
   generic:
+    all: Tümü
     changes_saved_msg: Değişiklikler başarıyla kaydedildi!
+    copy: Kopyala
+    no_batch_actions_available: Bu sayfada toplu iÅŸlem yok
+    order_by: Sıralama ölçütü
     save_changes: DeÄŸiÅŸiklikleri kaydet
     validation_errors:
       one: Bir şeyler ters gitti! Lütfen aşağıdaki hatayı gözden geçiriniz
       other: Bir şeyler ters gitti! Lütfen aşağıdaki %{count} hatayı gözden geçiriniz
+  html_validator:
+    invalid_markup: 'geçersiz HTML markup içermektedir: %{error}'
+  identity_proofs:
+    active: Aktif
+    authorize: Evet, yetkilendir
+    authorize_connection_prompt: Bu kriptolu bağlantıyı yetkilendir?
+    errors:
+      failed: Kriptolu bağlantı başarısız oldu. Lütfen %{provider} üzerinden tekrar deneyin.
+      keybase:
+        invalid_token: Keybase belirteçleri imza hash'leridir ve 66 hex karakter içermelidir
+        verification_failed: Keybase, bu belirteci, %{kb_username} Keybase kullanıcısının imzası olarak tanımıyor. Lütfen Keybase'den tekrar deneyin.
+      wrong_user: "%{current} olarak giriş yaparken %{proving} için bir kanıt oluşturulamıyor. %{proving} olarak giriş yapın ve tekrar deneyin."
+    explanation_html: Burada, Keybase profili gibi diğer kimliklerinizi şifreli olarak bağlayabilirsiniz. Bu, diğer kişilerin size şifreli mesajlar göndermesini ve gönderdiğiniz içeriğe güvenmelerini sağlar.
+    i_am_html: Ben %{service} üzerinde %{username}.
+    identity: Kimlik
+    inactive: Pasif
+    publicize_checkbox: 'Ve bunu paylaÅŸ:'
+    publicize_toot: 'İspatlandı! Ben %{service} üzerinde %{username}: %{url}'
+    status: DoÄŸrulama durumu
+    view_proof: Kanıt görüntüle
   imports:
+    modes:
+      merge: BirleÅŸtir
+      merge_long: Mevcut kayıtları sakla ve yenileri ekle
+      overwrite: Üzerine yaz
+      overwrite_long: Mevcut kayıtları yenileriyle değiştir
     preface: Diğer sunucudan alarak oluşturduğunuz dosyalar sayesinde, bu sunucudaki hesabınıza takipçilerinizi aktarabilir veya istemediğiniz kişileri otomatik olarak engelleyebilirsiniz.
     success: Verileriniz başarıyla yüklendi ve zaman içinde işlenecek
     types:
       blocking: Engellenenler listesi
+      domain_blocking: Alan adı engelleme listesi
       following: Takip edilenler listesi
       muting: Susturulanlar listesi
     upload: Yükle
+  in_memoriam_html: Hatırada.
   invites:
+    delete: Devre dışı bırak
+    expired: Süresi dolmuş
     expires_in:
-      '1800': 30 minutes
-      '21600': 6 hours
-      '3600': 1 hour
-      '43200': 12 hours
-      '604800': 1 week
-      '86400': 1 day
+      '1800': 30 dakika
+      '21600': 6 saat
+      '3600': 1 saat
+      '43200': 12 saat
+      '604800': 1 hafta
+      '86400': 1 gün
+    expires_in_prompt: Asla
+    generate: OluÅŸtur
+    invited_by: 'Tarafından davet edildi:'
+    max_uses:
+      one: 1 kullanım
+      other: "%{count} kullanım"
+    max_uses_prompt: Limit yok
+    prompt: Bu sunucuya erişim vermek için bağlantılar oluşturun ve başkalarıyla paylaşın
+    table:
+      expires_at: BitiÅŸ tarihi
+      uses: Kullanım
+    title: İnsanları davet et
+  lists:
+    errors:
+      limit: Maksimum liste miktarına ulaştınız
   media_attachments:
     validations:
       images_and_video: Halihazırda görsel içeren bir gönderiye video ekleyemezsiniz
       too_many: 4'ten fazla dosya ekleyemezsiniz
+  migrations:
+    acct: Taşındı
+    cancel: Yönlendirmeyi iptal et
+    cancel_explanation: Yönlendirmeyi iptal etmek, mevcut hesabınızı yeniden etkinleştirecek, ancak söz konusu hesaba taşınan takipçileri geri getirmeyecektir.
+    cancelled_msg: Yönlendirme başarıyla iptal edildi.
+    errors:
+      already_moved: zaten taşındığınızla aynı hesap
+      missing_also_known_as: bu hesaba geri referans yapmamaktadır
+      move_to_self: mevcut hesap olamaz
+      not_found: bulunamadı
+      on_cooldown: Soğuma aşamasındasınız
+    followers_count: Taşınma zamanındaki takipçiler
+    incoming_migrations: Farklı bir hesaptan taşınma
+    incoming_migrations_html: Başka bir hesaptan bu hesaba taşınmak için önce bir <a href="%{path}">hesap takma adı oluşturmanız</a> gerekmektedir.
+    moved_msg: Hesabınız şimdi %{acct} adresine yönlendiriliyor ve takipçileriniz taşınıyor.
+    not_redirecting: Hesabınız şu anda başka bir hesaba yönlendirilmemektedir.
+    on_cooldown: Son zamanlarda hesabınızı taşıdınız. Bu işlev %{count} gün içinde tekrar kullanılabilir olacaktır.
+    past_migrations: Geçmiş taşınmalar
+    proceed_with_move: Takipçileri taşı
+    redirecting_to: Hesabınız %{acct} hesabına yönlendirilmektedir.
+    set_redirect: Yönlendirme ayarla
+    warning:
+      backreference_required: Öncelikle, yeni hesap bu hesabı geri referans edecek şekilde yapılandırılmalıdır
+      before: 'Devam etmeden önce, lütfen bu notları dikkatlice okuyun:'
+      cooldown: Taşındıktan sonra, yeniden taşınma imkanınızın olmayacağı bir soğuma periyodu vardır
+      disabled_account: Sonrasında, mevcut hesabınız tamamen kullanılabilir olmayacaktır. Ancak, yeniden etkinleştirme işleminin yanı sıra veri dışa aktarma erişimine sahip olacaksınız.
+      followers: Bu eylem tüm takipçileri şu anki hesaptan yeni hesaba taşıyacaktır
+      only_redirect_html: Alternatif olarak, <a href="%{path}">sadece profilinize bir yönlendirme koyabilirsiniz</a>.
+      other_data: Başka bir veri otomatik olarak taşınmayacaktır
+      redirect: Mevcut hesabınızın profili bir yönlendirme bildirimi ile güncellenecek ve aramaların dışında tutulacaktır
+  moderation:
+    title: Yönetim
   notification_mailer:
     digest:
+      action: Tüm bildirimleri görüntüle
       body: Son ziyaretiniz olan %{since}'den beri'da kaçırdığınız şeylerin özeti
       mention: "%{name} senden bahsetti:"
       new_followers_summary:
@@ -305,41 +858,190 @@ tr:
       subject:
         one: "Son ziyaretinizden beri 1 yeni bildiriminiz var \U0001F418"
         other: "Son ziyaretinizden beri %{count} yeni bildiriminiz var \U0001F418"
+      title: Senin yokluÄŸunda...
     favourite:
       body: "%{name} durumunuzu favorilere ekledi:"
       subject: "%{name} favorilere ekledi"
+      title: Yeni favori
     follow:
       body: "%{name} sizi takip etmeye başladı!"
       subject: "%{name} sizi takip etmeye başladı"
+      title: Yeni takipçi
     follow_request:
+      action: Takip isteklerini yönet
       body: "%{name} size takip isteği gönderdi"
       subject: 'Takip isteÄŸi: %{name}'
+      title: Yeni takip isteÄŸi
     mention:
+      action: Yanıt
       body: "%{name} sizden bahsetti:"
       subject: "%{name} sizden bahsetti"
+      title: Yeni bahsetme
     reblog:
       body: "%{name} durumunuzu boost etti:"
       subject: "%{name} durumunuzu boost etti"
+      title: Yeni gönderi
+  number:
+    human:
+      decimal_units:
+        format: "%n%u"
+        units:
+          billion: B
+          million: M
+          quadrillion: Q
+          thousand: K
+          trillion: T
   pagination:
+    newer: Daha yeni
     next: Sonraki
+    older: Daha Eski
     prev: Önceki
+    truncate: "&hellip;"
+  polls:
+    errors:
+      already_voted: Bu ankete daha önce oy verdiniz
+      duplicate_options: kopya ögeler içeriyor
+      duration_too_long: ileriye doğru çok geç
+      duration_too_short: çok erken
+      expired: Anket çoktan sona erdi
+      over_character_limit: her biri %{max} karakterden daha uzun olamaz
+      too_few_options: birden fazla öğeye sahip olmalı
+      too_many_options: "%{max} öğeden fazla öğe içeremez"
+  preferences:
+    other: DiÄŸer
+    posting_defaults: Gönderi varsayılanları
+    public_timelines: Genel zaman çizelgeleri
+  relationships:
+    activity: Hesap etkinliÄŸi
+    dormant: Atıl
+    last_active: Son aktivite
+    most_recent: En son
+    moved: Taşındı
+    mutual: Ortak
+    primary: Birincil
+    relationship: İlişki
+    remove_selected_domains: Seçili alan adlarından tüm takipçileri kaldır
+    remove_selected_followers: Seçili takipçileri kaldır
+    remove_selected_follows: Seçili kullanıcıları takip etmeyi bırak
+    status: Hesap durumu
   remote_follow:
     acct: Takip edeceÄŸiniz kiÅŸiyi kullaniciadi@sunuculinki ÅŸeklinde giriniz
     missing_resource: Hesabınız için yönlendirme linki bulunamadı
+    no_account_html: Hesabınız yok mu? <a href='%{sign_up_path}' target='_blank'>Buradan kaydolabilirsiniz</a>
     proceed: Takip onayı
     prompt: Bu kullanıcıyı takip etmek istediğinize emin misiniz?
+    reason_html: "<strong>Bu adım neden gerekli?</strong><code>%{instance}</code> kayıtlı olduğunuz sunucu olmayabilir, bu yüzden önce sizi kendi sunucunuza yönlendirmemiz gerekmektedir."
+  remote_interaction:
+    favourite:
+      proceed: Favorilere eklemek için ilerle
+      prompt: 'Bu gönderiyi favorilerinize eklemek istiyorsunuz:'
+    reblog:
+      proceed: Yinelemek için ilerle
+      prompt: 'Bu gönderiyi yinelemek istiyorsunuz:'
+    reply:
+      proceed: Cevap vermek için ilerle
+      prompt: 'Bu gönderiye cevap vermek istiyorsunuz:'
+  scheduled_statuses:
+    over_daily_limit: O gün için %{limit} zamanlanmış gönderi sınırını aştınız
+    over_total_limit: "%{limit} zamanlanmış gönderi sınırını aştınız"
+    too_soon: Programlanan tarih bugünden ileri bir tarihte olmalıdır
+  sessions:
+    activity: Son aktivite
+    browser: Tarayıcı
+    browsers:
+      alipay: Alipay
+      blackberry: BlackBerry
+      chrome: Chrome
+      edge: Microsoft Edge
+      electron: Electron
+      firefox: Firefox
+      generic: Bilinmeyen tarayıcı
+      ie: Internet Explorer
+      micro_messenger: MicroMessenger
+      nokia: Nokia S40 Ovi Browser
+      opera: Opera
+      otter: Otter
+      phantom_js: PhantomJS
+      qq: QQ Browser
+      safari: Safari
+      uc_browser: UC Browser
+      weibo: Weibo
+    current_session: Åžu anki oturum
+    description: "%{platform} üzerinde %{browser}"
+    explanation: Bunlar şu anda Mastodon hesabınızda oturum açan web tarayıcılarıdır.
+    ip: IP
+    platforms:
+      adobe_air: Adobe Air
+      android: Android
+      blackberry: Blackberry
+      chrome_os: ChromeOS
+      firefox_os: Firefox OS
+      ios: iOS
+      linux: GNU/Linux
+      mac: Mac
+      other: bilinmeyen platform
+      windows: Windows
+      windows_mobile: Windows Mobil
+      windows_phone: Windows Phone
+    revoke: İptal
+    revoke_success: Oturum başarıyla iptal edildi
+    title: Oturumlar
   settings:
+    account: Hesap
+    account_settings: Hesap ayarları
+    aliases: Hesap takma adları
+    appearance: Görünüm
     authorized_apps: Yetkilendirilen uygulamalar
     back: Mastodon'a geri dön
+    delete: Hesap silme
+    development: GeliÅŸtirme
     edit_profile: Profili düzenle
     export: Dışa aktar
+    featured_tags: Öne çıkan hashtag'ler
+    identity_proofs: Kimlik belgesi
     import: İçe aktar
+    import_and_export: İçe al ve dışarı aktar
+    migrate: Hesap taşıma
+    notifications: Bildirim
     preferences: Tercihler
+    profile: Profil
+    relationships: Takip edilenler ve takipçiler
     two_factor_authentication: İki-faktörlü doğrulama
+  spam_check:
+    spam_detected: Bu otomatik bir ÅŸikayettir. Spam tespit edildi.
   statuses:
+    attached:
+      description: 'Ekli: %{attached}'
+      image:
+        one: "%{count} görsel"
+        other: "%{count} görsel"
+      video:
+        one: "%{count} video"
+        other: "%{count} video"
+    boosted_from_html: "%{acct_link} den yinelendi"
+    content_warning: 'İçerik uyarısı: %{warning}'
+    disallowed_hashtags:
+      one: 'izin verilmeyen bir etiket içeriyordu: %{tags}'
+      other: 'izin verilmeyen hashtag''leri içeriyordu: %{tags}'
+    language_detection: Dili otomatik olarak algıla
     open_in_web: Web sayfasında aç
     over_character_limit: "%{max} karakter limiti aşıldı"
+    pin_errors:
+      limit: Hali hazırda maksimum sayıda gönderiyi sabitlediniz
+      ownership: Başkasının gönderisi sabitlenemez
+      private: Halka açık olmayan gönderi sabitlenemez
+      reblog: Bir yineleme sabitlenemez
+    poll:
+      total_people:
+        one: "%{count} kiÅŸi"
+        other: "%{count} kiÅŸi"
+      total_votes:
+        one: "%{count} oy"
+        other: "%{count} oy"
+      vote: Oy
     show_more: Daha fazla
+    sign_in_to_participate: Sohbete katılmak için oturum açın
+    title: '%{name}: "%{quote}"'
     visibilities:
       private: Sadece takipçiler
       private_long: Sadece takipçilerime gönder
@@ -348,22 +1050,57 @@ tr:
       unlisted: ListelenmemiÅŸ
       unlisted_long: Herkes görebilir fakat herkese açık zaman tünellerinde listelenmez
   stream_entries:
+    pinned: Sabitlenmiş gönderi
     reblogged: boost edildi
     sensitive_content: Hassas içerik
+  tags:
+    does_not_match_previous_name: önceki adla eşleşmiyor
+  terms:
+    title: "%{instance} Hizmet Şartları ve Gizlilik Politikası"
+  themes:
+    contrast: Mastodon (Yüksek karşıtlık)
+    default: Mastodon (Karanlık)
+    mastodon-light: Mastodon (Açık)
+  time:
+    formats:
+      default: "%b %d, %Y, %H:%M"
+      month: "%b %Y"
   two_factor_authentication:
     code_hint: Onaylamak için kimlik doğrulama uygulamanızın oluşturduğu kodu giriniz
     description_html: Eğer <strong>iki-faktörlü kimlik doğrulamayı</strong> aktif ederseniz, giriş yaparken sizin için giriş kodu üreten telefonunuza ihtiyaç duyacaksınız.
     disable: Devre dışı bırak
     enable: AktifleÅŸtir
+    enabled: İki adımlı kimlik doğrulama etkin
     enabled_success: İki-faktörlü kimlik doğrulama başarıyla aktif edildi
     generate_recovery_codes: Kurtarma Kodlarını Oluştur
     instructions_html: <strong>Bu QR kodunu, telefonunuzdaki <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2">Google Authenticator</a> veya benzer bir TOTP uygulamasıyla taratınız</strong>. Bundan sonra giriş yaparken uygulamanın ürettiği kodu kullanarak giriş yapacaksınız.
     lost_recovery_codes: Kurtarma kodları telefonunuzu kaybettiğiniz durumlarda hesabınıza erişim yapabilmenize olanak tanır. Eğer kurtarma kodlarınızı kaybettiyseniz burada tekrar oluşturabilirsiniz. Eski kurtarma kodlarınız geçersiz hale gelecektir.
     manual_instructions: 'Eğer QR kodunu taratamıyorsanız ve elle giriş yapmanız gerekiyorsa buradaki gizli düz metni girebilirsiniz:'
+    recovery_codes: Kurtarma kodlarını yedekle
     recovery_codes_regenerated: Kurtarma kodları başarıyla oluşturuldu
     recovery_instructions_html: 'Eğer telefonunuza erişiminizi kaybederseniz, aşağıdaki kurtarma kodlarından birini kullanarak hesabınıza giriş yapabilirsiniz. Kurtarma kodlarınızı güvenli halde tutunuz. Örneğin: kodların çıktısını alıp diğer önemli belgeleriniz ile birlikte saklayabilirsiniz.'
     setup: Kuruluma baÅŸla
     wrong_code: Girdiğiniz kod geçersiz! Telefonunuzun saati geri/ileri kalmış olabilir.
+  user_mailer:
+    backup_ready:
+      explanation: Mastodon hesabınızın tam bir yedeğini istediniz. Şimdi indirmeye hazır!
+      subject: Arşiviniz indirilmeye hazır
+      title: ArÅŸiv paketlemesi
+    warning:
+      explanation:
+        disable: Hesabınız donmuşken, hesap verileriniz bozulmadan kalır, ancak kilidi açılıncaya kadar herhangi bir işlem gerçekleştiremezsiniz.
+        silence: Hesabınız sınırlı iken, yalnızca sizi takip eden kişiler bu sunucuda gönderilerinizi görecek ve çeşitli halka açık listelerin dışında tutulabilirsiniz. Ancak, diğerleri hala sizi manuel olarak takip edebilir.
+        suspend: Hesabınız askıya alındı ve tüm gönderileriniz ve yüklediğiniz medya dosyalarınız bu sunucudan ve takipçilerinizin bulunduğu sunuculardan geri alınamaz şekilde kaldırıldı.
+      get_in_touch: "%{instance} çalışanlarıyla iletişim kurmak için bu e-postayı yanıtlayabilirsiniz."
+      review_server_policies: Sunucu politikalarını inceleyin
+      subject:
+        disable: "%{acct} hesabınız donduruldu"
+        none: "%{acct} için uyarı"
+        silence: "%{acct} hesabınız sınırlandırıldı"
+        suspend: "%{acct} hesabınız askıya alındı"
+      title:
+        disable: Hesap donduruldu
+        none: Uyarı
   users:
     invalid_email: E-posta adresiniz geçersiz
     invalid_otp_token: İki-faktörlü kodunuz geçersiz
diff --git a/config/locales/uk.yml b/config/locales/uk.yml
index e027b6baee48a24f7fbd8f3d99e72e62389f6e99..1d3459a0f7729efd2f3222b756b8dc096be890ce 100644
--- a/config/locales/uk.yml
+++ b/config/locales/uk.yml
@@ -1,63 +1,113 @@
 ---
 uk:
   about:
-    about_hashtag_html: Немає публічних постів з хештегом<strong>#%{hashtag}</strong>. Ви можете  You can interact with them if you have an account anywhere in the fediverse.
-    about_mastodon_html: Mastodon - це <em>вільна</em> соціальна мережа з <em>відкритим вихідним кодом</em>. Вона є <em>децентралізованою</em> альтернативою комерційним платформам, що дозволяє уникнути ризиків монополізації вашого спілкування однією компанією. Виберіть сервер, якому ви довіряєте &mdash; що б ви не вибрали, Ви зможете спілкуватись з усіма іншими. Будь-який користувач може запустити власну інстанцію Mastodon та без проблем брати участь в <em>соціальній мережі</em>.
+    about_hashtag_html: Це публічні дмухи, позначені символом <strong>#%{hashtag}</strong>. Ви можете взаємодіяти з ними, якщо маєте обліковий запис будь-де у федіверсі.
+    about_mastodon_html: Mastodon — це соціальна мережа, заснована на відкритих веб-протоколах та вільному програмному забезпеченні з відкритим кодом. Вона є децентралізованою на кшталт електронної пошти.
     about_this: Про цю інстанцію
+    active_count_after: активних
+    active_footnote: Активні користувачі місяця (MAU)
     administered_by: 'Адміністратор:'
+    api: API
+    apps: Мобільні додатки
+    apps_platforms: Користуйтесь Mastodon на iOS, Android та інших платформах
+    browse_directory: Переглядайте каталог профілів та фільтруйте за інтересами
+    browse_public_posts: Переглядайте потік публічних постів на Mastodon
     contact: Зв'язатися
     contact_missing: Не зазначено
     contact_unavailable: Недоступно
+    discover_users: Знайдіть цікавих користувачів
     documentation: Документація
-    extended_description_html: |
-      <h3>Гарне місце для правил</h3>
-      <p>Детальний опис ще не налаштований.</p>
-    generic_description: "%{domain} є одним сервером у мережі"
+    federation_hint_html: З обліковим записом на %{instance} ви зможете слідкувати за людьми на будь-якому сервері Mastodon та поза ним.
+    get_apps: Спробуйте мобільний додаток
     hosted_on: Mastodon розміщено на %{domain}
+    instance_actor_flash: 'Цей обліковий запис є віртуальною особою, яка використовується для представлення самого сервера, а не певного користувача. Він використовується для потреб федерації і не повинен бути заблокований, якщо тільки ви не хочете заблокувати весь сервер, у цьому випадку ви повинні скористатися блокуванням домену.
+
+'
     learn_more: Дізнатися більше
     privacy_policy: Політика приватності
+    see_whats_happening: Погляньте, що відбувається
+    server_stats: 'Статистика серверу:'
     source_code: Вихідний код
+    status_count_after:
+      few: статуса
+      many: статусів
+      one: статус
+      other: статуси
     status_count_before: Опубліковано
+    tagline: Слідкуйте за друзями та знаходьте нових
     terms: Правила використання
+    unavailable_content: Недоступний вміст
+    unavailable_content_description:
+      domain: Сервер
+      reason: Причина
+    user_count_after:
+      few: користувача
+      many: користувачів
+      one: користувач
+      other: користувачі
     user_count_before: Тут живе
     what_is_mastodon: Що таке Mastodon?
   accounts:
+    choices_html: 'Вподобання %{name}:'
     follow: Підписатися
+    followers:
+      few: Підписника
+      many: Підписників
+      one: Підписник
+      other: Підписників
     following: Підписаний(-а)
     joined: Приєднався %{date}
+    last_active: остання активність
+    link_verified_on: Права власності на це посилання були перевірені %{date}
     media: Медіа
-    moved_html: "%{name} переїхав на %{new_profile_link}:"
+    moved_html: "%{name} переїхав до %{new_profile_link}:"
     network_hidden: Ця інформація недоступна
+    never_active: Ніколи
     nothing_here: Тут нічого немає!
     people_followed_by: Люди, на яких підписаний(-а) %{name}
     people_who_follow: Підписники %{name}
-    posts_with_replies: Пости і відповіді
+    pin_errors:
+      following: Ви повинні бути підписаним на людину, яку бажаєте схвалити
+    posts:
+      few: Дмуха
+      many: Дмухів
+      one: Дмух
+      other: Дмухів
+    posts_tab_heading: Дмухи
+    posts_with_replies: Дмухи та відповіді
     reserved_username: Це ім'я користувача зарезервоване
     roles:
       admin: Адміністратор
       bot: Бот
       moderator: Мод
+    unavailable: Профіль недоступний
     unfollow: Відписатися
   admin:
+    account_actions:
+      action: Виконати дію
+      title: Здійснити модераційну дію над %{acct}
     account_moderation_notes:
-      create: Залишити примітки
-      created_msg: Примітку модератора успішно створено!
+      create: Залишити нотатку
+      created_msg: Нотатку модератора успішно створено!
       delete: Видалити
-      destroyed_msg: Примітку модератора успішно видалено!
+      destroyed_msg: Нотатку модератора успішно видалено!
     accounts:
+      approve: Схвалити
+      approve_all: Схвалити всіх
       are_you_sure: Ви впевнені?
       avatar: Аватар
       by_domain: Домен
       change_email:
-        changed_msg: Поштова адреса аккаунту успішно змінена!
-        current_email: Поточна поштова адреса
-        label: Змінити поштову адресу
-        new_email: Новий e-mail
-        submit: Змінити поштову адресу
-        title: Змінити поштову адресу для %{username}
+        changed_msg: Адресу електронної пошти облікового запису успішно змінено!
+        current_email: Поточна адреса електронної пошти
+        label: Змінити адресу електронної пошти
+        new_email: Нова адреса електронної пошти
+        submit: Змінити адресу електронної пошти
+        title: Змінити адресу електронної пошти для %{username}
       confirm: Зберегти
       confirmed: Збережено
       confirming: Зберігається
+      deleted: Видалено
       demote: Усунути
       disable: Вимкнути
       disable_two_factor_authentication: Вимкнути двофакторну авторизацію
@@ -65,14 +115,19 @@ uk:
       display_name: Відображуване ім'я
       domain: Домен
       edit: Змінити
-      email_status: Статус e-mail
+      email: Електронна пошта
+      email_status: Статус електронної пошти
       enable: Увімкнути
       enabled: Увімкнено
-      feed_url: URL фіду
+      feed_url: URL-адреса каналу
       followers: Підписники
       followers_url: URL підписників
       follows: Підписки
-      inbox_url: Вхідний URL
+      header: Заголовок
+      inbox_url: URL вхідних повідомлень
+      invited_by: 'Запросив:'
+      ip: IP
+      joined: Приєднався
       location:
         all: Усі
         local: Локальні
@@ -82,29 +137,37 @@ uk:
       media_attachments: Мультимедійні вкладення
       memorialize: Зробити пам'ятником
       moderation:
+        active: Активний
         all: Усі
+        pending: Очікують
         silenced: Заглушені
-        suspended: Заблоковані
+        suspended: Призупинені
         title: Модерація
-      moderation_notes: Примітки модераторів
+      moderation_notes: Нотатки модераторів
       most_recent_activity: Остання активність
       most_recent_ip: Останній IP
+      no_account_selected: Жоден обліковий запис не було змінено, оскільки жоден не було вибрано
+      no_limits_imposed: Жодних обмежень не накладено
       not_subscribed: Не підписані
-      outbox_url: Вихідний URL
-      perform_full_suspension: Повне блокування
+      outbox_url: URL вихідних повідомлень
+      pending: Відгук в очікуванні
+      perform_full_suspension: Призупинити
       profile_url: URL профілю
       promote: Просунути
       protocol: Протокол
       public: Публічний
       push_subscription_expires: Підписка PuSH спливає
       redownload: Оновити аватар
+      reject: Відхилити
+      reject_all: Відхилити усе
       remove_avatar: Видалити аватар
+      remove_header: Видалити заголовок
       resend_confirmation:
         already_confirmed: Цей користувач уже підтверджений
-        send: Надіслати підтвердження ще раз
-        success: Повідомлення з підтвердженням успішно надіслано!
+        send: Надіслати електронний лист-підтвердження ще раз
+        success: Електронний лист-підтвердження успішно надіслано!
       reset: Скинути
-      reset_password: Зкинути пароль
+      reset_password: Скинути пароль
       resubscribe: Перепідписатися
       role: Дозволи
       roles:
@@ -112,30 +175,38 @@ uk:
         moderator: Модератор
         staff: Персонал
         user: Користувач
+      salmon_url: Salmon URL
       search: Пошук
       shared_inbox_url: URL спільного вхідного кошика
       show:
-        created_reports: Скарги створені цим аккаунтом
+        created_reports: Скарги, створені цим аккаунтом
         targeted_reports: Скарги щодо цього аккаунту
       silence: Глушення
+      silenced: Заглушені
       statuses: Статуси
       subscribe: Підписатися
-      title: Акаунти
-      unconfirmed_email: Непідтверджений e-mail
+      suspended: Призупинені
+      time_in_queue: Очікує в черзі %{time}
+      title: Облікові записи
+      unconfirmed_email: Непідтверджена адреса електронної пошти
       undo_silenced: Зняти глушення
-      undo_suspension: Зняти блокування
+      undo_suspension: Зняти призупинення
       unsubscribe: Відписатися
       username: Ім'я користувача
-      web: WWW
+      warn: Попередження
+      web: Веб
+      whitelisted: У білому списку
     action_logs:
       actions:
         assigned_to_self_report: "%{name} призначив(-ла) скаргу %{target} на себе"
         change_email_user: "%{name} змінив(-ла) поштову адресу користувача %{target}"
         confirm_user: "%{name} підтвердив(-ла) статус поштової адреси користувача %{target}"
+        create_account_warning: "%{name} надіслав попередження до %{target}"
         create_custom_emoji: "%{name} вивантажив(-ла) нове емодзі %{target}"
         create_domain_block: "%{name} заблокував(-ла) домен %{target}"
         create_email_domain_block: "%{name} додав(-ла) поштовий домен %{target} до чорного списку"
         demote_user: "%{name} понизив(-ла) %{target}"
+        destroy_custom_emoji: "%{name} знищив(-ла) емодзі %{target}"
         destroy_domain_block: "%{name} розблокував(-ла) домен %{target}"
         destroy_email_domain_block: "%{name} додав(-ла) поштовий домен %{target} до білого списку"
         destroy_status: "%{name} видалив(-ла) статус користувача %{target}"
@@ -144,19 +215,20 @@ uk:
         disable_user: "%{name} заборонив(-ла) авторизацію користувачу %{target}"
         enable_custom_emoji: "%{name} увімкнув(-ла) емодзі %{target}"
         enable_user: "%{name} увімкнув(-ла) авторизацію користувачу %{target}"
-        memorialize_account: "%{name} перетворив(-ла) сторінку %{target} у пам'ятник"
+        memorialize_account: "%{name} перетворив(-ла) обліковий запис %{target} на сторінку пам'яті"
         promote_user: "%{name} підвищив(-ла) користувача %{target}"
         remove_avatar_user: "%{name} прибрав(-ла) аватар користувача %{target}"
         reopen_report: "%{name} перевідкрив(-ла) скаргу %{target}"
         reset_password_user: "%{name} скинув(-ла) пароль користувача %{target}"
         resolve_report: "%{name} розв'язав(-ла) скаргу %{target}"
-        silence_account: "%{name} заглушив(-ла) аккаунт %{target}"
-        suspend_account: "%{name} заблокував аккаунт користувача %{target}"
+        silence_account: "%{name} заглушив(-ла) обліковий запис %{target}"
+        suspend_account: "%{name} заморозив обліковий запис користувача %{target}"
         unassigned_report: "%{name} зняв(-ла) призначення скарги %{target}"
-        unsilence_account: "%{name} розглушив(-ла) аккаунт %{target}"
-        unsuspend_account: "%{name} розблокував аккаунт користувача %{target}"
+        unsilence_account: "%{name} розглушив(-ла) обліковий запис %{target}"
+        unsuspend_account: "%{name} розморозив обліковий запис користувача %{target}"
         update_custom_emoji: "%{name} оновив(-ла) емодзі %{target}"
         update_status: "%{name} змінив(-ла) статус користуача %{target}"
+      deleted_status: "(видалений статус)"
       title: Журнал подій
     custom_emojis:
       by_domain: Домен
@@ -167,69 +239,100 @@ uk:
       delete: Видалити
       destroyed_msg: Емодзі усіпішно видалене!
       disable: Вимкнути
+      disabled: Вимкнено
       disabled_msg: Емодзі успішно вимкнено
       emoji: Емодзі
       enable: Увімкнути
+      enabled: Увімкнено
       enabled_msg: Емодзі успішно увімкнене
       image_hint: PNG розміром до 50 КБ
+      list: Список
       listed: У списку
       new:
         title: Додати новий емодзі
       overwrite: Переписати
       shortcode: Шорткод
-      shortcode_hint: Мінімум два символи, тільки цифрові й латинські символи або нижні підкреслення
+      shortcode_hint: Мінімум два символи, тільки цифрові й латинські символи або підкреслення
       title: Особливі емодзі
+      uncategorized: Без категорії
       unlisted: Не у списку
       update_failed_msg: Не вийшло оновити емозді
       updated_msg: Емодзі успішно оновлене!
       upload: Вивантажити
     dashboard:
+      backlog: відкладені завдання
       config: Налаштування
-      feature_deletions: Видалення аккаунтів
+      feature_deletions: Видалення облікових записів
       feature_invites: Посилання-запрошення
+      feature_profile_directory: Каталог профілів
       feature_registrations: Реєстрації
+      feature_relay: Ретранслятор дмухів між серверами
+      feature_spam_check: Анти-спам
+      feature_timeline_preview: Передпоказ стрічки
       features: Можливості
       hidden_service: Федерація з прихованими сервісами
       open_reports: відкриті скарги
+      pending_tags: хештеги, що очікують на перегляд
+      pending_users: користувачі, що очікують на перегляд
       recent_users: Останні користувачі
       search: Повнотекстовий пошук
       single_user_mode: Режим одного користувача
       software: Програмне забезпечення
       space: Використання дискового простору
-      title: Дашборд
+      title: Приборна панель
       total_users: користувачів загалом
       trends: Тренди
       week_interactions: дій за цей тиждень
       week_users_active: активно протягом тижня
       week_users_new: користувачів цього тижня
+      whitelist_mode: Режим білого списку
+    domain_allows:
+      add_new: Додати домен до білого списку
+      created_msg: Домен було успішно додано до білого списку
+      destroyed_msg: Домен було видалено з білого списку
+      undo: Видалити з білого списку
     domain_blocks:
       add_new: Додати нове
-      created_msg: Блокування домену оброблюється
+      created_msg: Блокування домену обробляється
       destroyed_msg: Блокування домену знято
       domain: Домен
+      edit: Редагувати блокування доменів
+      existing_domain_block_html: Ви вже наклали більш суворі обмеження на %{name}, вам треба спочатку <a href="%{unblock_url}">розблокувати його</a>.
       new:
         create: Створити блокування
-        hint: Блокування домену не завадить створенню нових акаунтів у базі даних, але ретроактивно та автоматично застосує вказані методи модерації для цих акаунтів.
+        hint: Блокування домену не завадить створенню нових облікових записів у базі даних, але ретроактивно та автоматично застосує до них конкретні методи модерації.
         severity:
-          desc_html: "<strong>Глушення</strong> зробить статуси акаунту невидимими для всіх, окрім їхніх підписників. <strong>Блокування</strong> видалить увесь контент акаунту, включаючи мультимедійні вкладення та дані профілю."
+          desc_html: "<strong>Глушення</strong> зробить пости облікового запису невидимими для всіх, окрім його підписників. <strong>Заморожування</strong> видалить увесь контент, медіа та дані профілю облікового запису. Якщо ви хочете лише заборонити медіафайли, оберіть <strong>Нічого</strong>."
           noop: Нічого
           silence: Глушення
           suspend: Блокування
         title: Нове блокування домену
+      private_comment: Приватний коментар
+      private_comment_hint: Прокоментуйте обмеження для цього домену, а модератори прочитають.
+      public_comment: Публічний коментар
+      public_comment_hint: Прокоментуйте обмеження для цього домену, це зможуть прочитати всі, якщо така опція ввімкнена.
       reject_media: Заборонити медіаконтент
       reject_media_hint: Видаляє медіаконтент, збережений локально, і забороняє його завантаження у майбутньому. Не має значення у випадку блокування
+      reject_reports: Відхилити скарги
+      reject_reports_hint: Ігнорувати всі скарги з цього домену. Не має значення у випадку блокування
+      rejecting_media: відхилення медіафайлів
+      rejecting_reports: відхилення скарг
+      severity:
+        silence: заглушені
+        suspend: призупинені
       show:
         affected_accounts:
-          few: Впливає на %{count} акаунти у базі даних
-          many: Впливає на %{count} акаунтів у базі даних
-          one: Впливає на один акаунт у базі даних
-          other: Впливає на %{count} акаунтів у базі даних
+          few: Впливає на %{count} облікових записи у базі даних
+          many: Впливає на %{count} облікових записів у базі даних
+          one: Впливає на один обліковий запис у базі даних
+          other: Впливає на %{count} облікових записів у базі даних
         retroactive:
-          silence: Зняти глушення з усіх існуючих акаунтів цього домену
-          suspend: Зняти блокування з усіх існуючих акаунтів цього домену
+          silence: Зняти глушення з усіх існуючих заглушених облікових записів цього домену
+          suspend: Розморозити існуючі заморожені облікові записи з цього домену
         title: Зняти блокування з домена %{domain}
         undo: Відмінити
       undo: Відмінити
+      view: Переглянути заблоковані домени
     email_domain_blocks:
       add_new: Додати
       created_msg: Успішно додано поштовий домен до чорного списку
@@ -238,25 +341,63 @@ uk:
       domain: Домен
       new:
         create: Додати домен
-        title: Нове доменне блокування домену email
+        title: Нове блокування поштового домену
       title: Чорний список поштових доменів
+    followers:
+      back_to_account: Повернутися до Облікового запису
+      title: Підписники %{acct}
     instances:
+      by_domain: Домен
+      delivery_available: Доставлення доступне
+      known_accounts:
+        few: "%{count} відомих облікових записів"
+        many: "%{count} відомих облікових записів"
+        one: "%{count} відомий обліковий запис"
+        other: "%{count} відомих облікових записів"
+      moderation:
+        all: Усі
+        limited: Обмежені
+        title: Модерація
+      private_comment: Приватний коментар
+      public_comment: Публічний коментар
       title: Відомі інстанції
+      total_blocked_by_us: Заблокованих нами
+      total_followed_by_them: Вони стежать за
+      total_followed_by_us: Ми стежимо за
+      total_reported: Звітів про них
+      total_storage: Мультимедійні вкладення
     invites:
+      deactivate_all: Деактивувати всі
       filter:
         all: Все
         available: Доступно
         expired: Просрочено
         title: Фільтр
       title: Запрошення
+    pending_accounts:
+      title: Облікові записи у черзі (%{count})
     relays:
+      add_new: Додати новий ретранслятор
+      delete: Видалити
+      description_html: "<strong>Ретлянслятор дмухів</strong> (federation relay) — це проміжний сервер, що обмінюється великими обсягами публічних дмухів між серверами, які цього хочуть. Він може допомогти маленьким та середнім серверам отримувати вміст з усього <strong>федесвіту</strong> (fediverse). Без нього локальним користувачам довелося б вручну підписуватися на людей з віддалених серверів."
+      disable: Вимкнути
+      disabled: Вимкнено
+      enable: Увімкнути
+      enable_hint: Коли ретрансляцію увімкнено, ваш сервер буде підписаний на усі публічні дмухи з цього ретранслятора, та почне посилати до нього свої публічні дмухи.
+      enabled: Увімкнено
+      inbox_url: URL ретранслятора
+      pending: Очікуємо на підтвердження ретранслятором
+      save_and_enable: Зберегти та увімкнути
+      setup: Налаштування з'єднання з ретранслятором
+      signatures_not_enabled: Ретранслятори не будуть добре працювати поки ввімкнений безопасний режим або режим білого списка
       status: Статус
+      title: Ретранслятори
     report_notes:
       created_msg: Скарга успішно створена!
       destroyed_msg: Скарга успішно видалена!
     reports:
       account:
-        note: примітка
+        note: нотатка
         report: скарга
       action_taken_by: Дія виконана
       are_you_sure: Ви впевнені?
@@ -275,7 +416,7 @@ uk:
         placeholder: Опишіть, які дії були виконані, або інші зміни, що стосуються справи...
       reopen: Перевідкрити скаргу
       report: 'Скарга #%{id}'
-      reported_account: Акаунт порушника
+      reported_account: Обліковий запис порушника
       reported_by: Відправник скарги
       resolved: Вирішено
       resolved_msg: Скаргу успішно вирішено!
@@ -289,32 +430,55 @@ uk:
         desc_html: Кількість локальних постів, активних та нових користувачів у тижневих розрізах
         title: Публікація агрегованої статистики про активність користувачів
       bootstrap_timeline_accounts:
-        title: Підписки за замовчуванням для нових користувачів
+        desc_html: Розділяйте імена користувачів комами. Працюватимуть тільки локальні і розблоковані облікові записи. Якщо порожньо, то типово це всі локальні адміністратори.
+        title: Типові підписки для нових користувачів
       contact_information:
         email: Введіть публічний email
         username: Введіть ім'я користувача
+      custom_css:
+        desc_html: Відобразити вигляд, коли CSS завантажено для кожної сторінки
+        title: Користувацький CSS
+      default_noindex:
+        desc_html: Впливає на усіх користувачів, які не змінили це настроювання самостійно
+      domain_blocks:
+        all: Всi
+        disabled: Нікого
+        title: Показати, які домени заблоковані
+      domain_blocks_rationale:
+        title: Обґрунтування
       hero:
-        desc_html: Відображається на головній сторінці. Рекомендована як мінімум 600x100 пікселів. Якщо не вказано, буде використано передпоказ інстанції
+        desc_html: Відображається на головній сторінці. Рекомендовано як мінімум 600x100 пікселів. Якщо не вказано, буде використано передпоказ інстанції
         title: Банер інстанції
+      mascot:
+        desc_html: Відображається на багатьох сторінках. Щонайменше 293×205 пікселів рекомендовано. Якщо не вказано, буде використано талісман інстанції
+        title: Талісман
       peers_api_enabled:
         desc_html: Доменні ім'я, помічені цією інстанцією федисвіту
         title: Опублікувати список знайдених інстанцій
       preview_sensitive_media:
-        desc_html: Передпоказ посилання на інших сайтах буде відображати мініатюру навіть якщо медіа відмічене як вразливе
-        title: Показувати вразливе медія у перепоказі OpenGraph
+        desc_html: Передпоказ посилання на інших сайтах буде відображати мініатюру навіть якщо медіа відмічене як дражливе
+        title: Показувати дражливе медіа у передпоказах OpenGraph
+      profile_directory:
+        desc_html: Дозволити користувачам бути видимими
+        title: Увімкнути каталог профілів
       registrations:
         closed_message:
           desc_html: Відображається на титульній сторінці, коли реєстрація закрита <br>Можна використовувати HTML-теги
           title: Повідомлення про закриту реєстрацію
         deletion:
-          desc_html: Дозволити будь-кому видаляти свій аккаунт
-          title: Дозволити видалення аккаунтів
+          desc_html: Дозволити будь-кому видаляти свій обліковий запис
+          title: Видалення відкритого облікового запису
         min_invite_role:
           disabled: Ніхто
           title: Дозволити запрошення від
+      registrations_mode:
+        modes:
+          approved: Для входу потрібне схвалення
+          none: Ніхто не може увійти
+          open: Будь-хто може увійти
       show_known_fediverse_at_about_page:
-        desc_html: Коли увімкнено, будуть показані пости з усього відомого федисвіту у передпоказі. Інакше будуть показані локальні пости.
-        title: Показувати доступний федисвіт у передпоказі фіду
+        desc_html: Коли увімкнено, будуть показані пости з усього відомого федисвіту у передпоказі. Інакше будуть показані лише локальні дмухи.
+        title: Показувати доступний федисвіт у передпоказі стрічки
       show_staff_badge:
         desc_html: Відмічати персонал на сторінці користувачів
         title: Показувати персонал
@@ -324,44 +488,87 @@ uk:
       site_description_extended:
         desc_html: Відображається на сторінці додаткової информації<br>Можна використовувати HTML-теги
         title: Розширений опис сайту
+      site_short_description:
+        title: Короткий опис сервера
       site_terms:
         desc_html: |-
           Ви можене написати власну політику приватності, умови використанні та інші законні штуки<br>
           Можете використовувати HTML теги
         title: Особливі умови використання
       site_title: Назва сайту
+      spam_check_enabled:
+        desc_html: Mastodon може автоматично глушити та автоматично звітувати про облікові записи, які надсилають повторні небажані повідомлення. Можливі хибно-позитивні спрацьовування.
+        title: Автоматизація антиспаму
       thumbnail:
         desc_html: Використовується для передпоказів через OpenGraph та API. Бажано розміром 1200х640 пікселів
         title: Мініатюра інстанції
       timeline_preview:
-        desc_html: Показувати публічний фід на головній сторінці
+        desc_html: Показувати публічну стрічку на головній сторінці
         title: Передпоказ фіду
       title: Налаштування сайту
+      trends:
+        desc_html: Відображати розглянуті хештеґи, які популярні зараз
+        title: Популярні хештеги
     statuses:
-      back_to_account: Назад на сторінку профілю
+      back_to_account: Назад до сторінки облікового запису
       batch:
         delete: Видалити
-        nsfw_off: Відмітити сприйнятливим
-        nsfw_on: Відмітити несприйнятливим
+        nsfw_off: Відмітити прийнятним
+        nsfw_on: Відмітити неприйнятним
+      deleted: Видалено
       failed_to_execute: Не вийшло
       media:
         title: Медіа
       no_media: Немає медіа
-      title: Статуси аккаунтів
+      no_status_selected: Жодного статуса не було змінено, оскільки жодного не було вибрано
+      title: Статуси облікових записів
       with_media: З медіа
-    subscriptions:
-      confirmed: Підтверджено
-      expires_in: Спливає через
-      last_delivery: Остання доставка
-      topic: Тема
+    tags:
+      accounts_today: Унікальних використань за сьогодні
+      accounts_week: Унікальних використань за тиждень
+      breakdown: Аналіз використання за сьогодні за джерелом
+      context: Контекст
+      directory: У каталозі
+      in_directory: "%{count} у каталозі"
+      name: Хештеґ
+      review: Переглянути статус
+      reviewed: Переглянуто
+      title: Хештеґи
+      trending_right_now: Популярні саме зараз
+      unique_uses_today: "%{count} постять сьогодні"
+      unreviewed: Не переглянуто
+      updated_msg: Параметри хештеґів успішно оновлені
     title: Адміністрування
+    warning_presets:
+      add_new: Додати новий
+      delete: Видалити
+      edit: Редагувати
+      edit_preset: Редагувати шаблон попередження
+      title: Управління шаблонами попереджень
   admin_mailer:
+    new_pending_account:
+      body: Деталі нового облікового запису наведено нижче. Ви можете схвалити або відхилити цю заяву.
+      subject: Новий обліковий запис надіслано на розгляд на %{instance} (%{username})
     new_report:
-      body: "%{reporter} поскаржився(-лася) %{target}"
-      body_remote: Хтось з домену %{domain} поскаржився(-лася) %{target}
+      body: "%{reporter} поскаржився(-лася) на %{target}"
+      body_remote: Хтось з домену %{domain} поскаржився(-лася) на %{target}
       subject: Нова скарга до %{instance} (#%{id})
+    new_trending_tag:
+      subject: Новий хештеґ надіслано на розгляд до %{instance} (#%{name})
+  aliases:
+    add_new: Створити псевдонім
+    created_msg: Новий псевдонім успішно створено. Тепер ви можете починати переміщення зі старого облікового запису.
+    deleted_msg: Псевдонім успішно видалено. Переміщення з того облікового запису до цього більше не можливе.
+  appearance:
+    advanced_web_interface: Розширений web-інтерфейс
+    advanced_web_interface_hint: 'Розширений веб-інтерфейс дає змогу бачити багато стовпчиків одночасно: основна сторінка, сповіщення, глобальна стрічка, будь-які списки та хештеґи. Потребує широкого екрана.'
+    animations_and_accessibility: Анімація та доступність
+    confirmation_dialogs: Діалоги підтвердження
+    discovery: Виявлення
+    sensitive_content: Дражливий вміст
   application_mailer:
     notification_preferences: Змінити налаштування e-mail
+    salutation: "%{name},"
     settings: 'Змінити налаштування e-mail: %{link}'
     view: 'Перегляд:'
     view_profile: Показати профіль
@@ -375,26 +582,46 @@ uk:
     warning: Будьте дуже обережні з цими даними. Ніколи не діліться ними ні з ким!
     your_token: Ваш токен доступу
   auth:
+    apply_for_account: Запитати запрошення
     change_password: Пароль
-    confirm_email: Підтвердьте e-mail адресу
-    delete_account: Видалити аккаунт
-    delete_account_html: Якщо ви хочете видалити аккаунт, ви можете <a href="%{path}">перейти сюди</a>. Вас попросять підтвердити дію.
+    checkbox_agreement_html: Я погоджуюсь з <a href="%{rules_path}" target="_blank">правилами сервера</a> та <a href="%{terms_path}" target="_blank">умовами використання</a>
+    checkbox_agreement_without_rules_html: Я погоджуюся з <a href="%{terms_path}" target="_blank">умовами використання</a>
+    delete_account: Видалити обліковий запис
+    delete_account_html: Якщо ви хочете видалити свій обліковий запис, ви можете <a href="%{path}">перейти сюди</a>. Вас попросять підтвердити дію.
+    description:
+      prefix_invited_by_user: "@%{name} запрошує вас приєднатися до цього сервера Mastodon!"
+      prefix_sign_up: Зареєструйтеся на Mastodon сьогодні!
+      suffix: Маючи обліковий запис, ви зможете підписуватися на людей, публікувати пости та листуватися з користувачами будь-якого сервера Mastodon!
     didnt_get_confirmation: Ви не отримали інструкції з підтвердження?
-    forgot_password: Забули свій пароль?
+    forgot_password: Забули пароль?
     invalid_reset_password_token: Токен скидання паролю неправильний або просрочений. Спробуйте попросити новий.
     login: Увійти
     logout: Вийти
-    migrate_account: Переїхати до іншого аккаунту
-    migrate_account_html: Якщо ви бажаєте, щоб відвідувачі цього акканту були перенаправлені до іншого, ви можете <a href="%{path}">налаштувати це тут</a>.
+    migrate_account: Переїхати на інший обліковий запис
+    migrate_account_html: Якщо ви бажаєте перенаправити цей обліковий запис на інший, ви можете <a href="%{path}">налаштувати це тут</a>.
     or_log_in_with: Або увійдіть з
+    providers:
+      cas: CAS
+      saml: SAML
     register: Зареєструватися
+    registration_closed: "%{instance} не приймає нових членів"
     resend_confirmation: Повторно відправити інструкції з підтвердження
     reset_password: Скинути пароль
     security: Зміна паролю
     set_new_password: Встановити новий пароль
+    setup:
+      email_below_hint_html: Якщо ця електронна адреса не є вірною, ви можете змінити її тут та отримати новий лист для підтвердження.
+      email_settings_hint_html: Електронний лист-підтвердження було вислано до %{email}. Якщо ця адреса електронної пошти не є вірною, ви можете змінити її в налаштуваннях облікового запису.
+      title: Налаштування
+    status:
+      account_status: Статус облікового запису
+      confirming: Очікуємо на завершення підтвердження за допомогою електронної пошти.
+      functional: Ваш обліковий запис повністю робочій.
+      pending: Ваша заява очікує на розгляд нашим персоналом. Це може зайняти деякий час. Ви отримаєте електронний лист, якщо ваша заява буде схвалена.
+    trouble_logging_in: Проблема під час входу?
   authorize_follow:
-    already_following: Ви вже підписані на цей аккаунт
-    error: На жаль, при пошуку віддаленого аккаунту виникла помилка
+    already_following: Ви вже слідкуєте за цим обліковим записом
+    error: На жаль, під час пошуку віддаленого облікового запису сталася помилка
     follow: Підписатися
     follow_request: 'Вам надіслали запит на підписку:'
     following: 'Ура! Ви тепер підписані на:'
@@ -418,17 +645,32 @@ uk:
       x_months: "%{count}міс"
       x_seconds: "%{count}сек"
   deletes:
-    bad_password_msg: Гарна спроба, гакери! Неправильний пароль
-    confirm_password: Введіть актуальний пароль щоб перевірити що ви це ви
-    description_html: Це <strong>безвідворотно і назавжди</strong> видалить контент з вашого аккаунту та деактивує його. Ваше ім'я користувача буде залишатися зарезервованим для уникнення вашої деперсоналізації.
-    proceed: Видалити аккаунт
-    success_msg: Ваш аккаунт було успішно видалено
-    warning_html: Ми можемо гарантувати видалення контенти <b>лише з цього сайту</b>. Контент, що був поширений залишає сліди. Сервери, що є офлайн та ті, що відписалися від наших оновлень не запишуть змін до своїх баз даних.
-    warning_title: Про доступність поширеного контенту
+    confirm_password: Введіть актуальний пароль, щоб перевірити що ви це ви
+    proceed: Видалити обліковий запис
+    success_msg: Ваш обліковий запис було успішно видалено
+    warning:
+      before: 'До того як продовжити, будь ласка уважно прочитайте це:'
+      caches: Інформація, кешована іншими серверами, може залишитися
+      data_removal: Ваші пости та інші дані будуть видалені назавжди
+      email_change_html: Ви можете <a href="%{path}">змінити вашу електронну адресу</a>, не видаляючи ваш обліковий запис
+      email_contact_html: Якщо його все ще немає, ви можете написали до <a href="mailto:%{email}">%{email}</a> для допомоги
+      email_reconfirmation_html: Якщо ви не отримали електронного листа з підтвердженням, ви можете <a href="%{path}">запросити його знову</a>
+      irreversible: Буде неможливо відновити ваш обліковий запис
+      more_details_html: Подробиці за посиланням <a href="%{terms_path}">політика конфіденційності</a>.
+      username_available: Ваше ім'я користувача стане доступним для використання
+      username_unavailable: Ваше ім'я користувача залишиться недоступним для використання
+  directories:
+    directory: Каталог профілів
+    explanation: Шукайте користувачів за їх інтересами
+    explore_mastodon: Досліджуйте %{title}
+  domain_validator:
+    invalid_domain: не є допустимим ім'ям домену
   errors:
+    '400': Ваш запит був недійсним або неправильним.
     '403': У Вас немає доступу до перегляду даної сторінки.
-    '404': Сторінка, яку Ви шукали, не існує.
-    '410': Сторінка, яку Ви шукали, більше не існує.
+    '404': Сторінки, яку Ви шукали, не існує.
+    '406': Ця сторінка недоступна у запрошеному форматі.
+    '410': Сторінки, яку Ви шукали, більше не існує.
     '422':
       content: Перевірка безпеки не вдалася. Можливо, Ви блокуєте cookies?
       title: Перевірка безпеки не вдалася
@@ -436,7 +678,11 @@ uk:
     '500':
       content: Пробачте, та щось пішло не так з нашого боку.
       title: Ця сторінка неправильна
+    '503': Ця сторінка не може бути оброблена через тимчасову відмову сервера.
     noscript_html: Для використання веб-застосунку Mastodon, будь-ласка увімкніть JavaScript. Якщо у вас немає такої можливості, скористайтесь одним із <a href="%{apps_path}">нативних застосунків</a> для Mastodon для вашої платформи.
+  existing_username_validator:
+    not_found: не вдалося знайти локального користувача з таким ім'ям
+    not_found_multiple: не вдалося знайти %{usernames}
   exports:
     archive_takeout:
       date: Дата
@@ -446,14 +692,22 @@ uk:
       request: Зробити запит на архів
       size: Розмір
     blocks: Список блокувань
+    csv: CSV
+    domain_blocks: Блокування доменів
     follows: Підписки
+    lists: Списки
     mutes: Список глушення
     storage: Ваш медіаконтент
+  featured_tags:
+    add_new: Додати новий
+    errors:
+      limit: Ви досягли максимальної кількості хештеґів
+    hint_html: "<strong>Що таке виділені хештеґи?</strong> Це ті, що відображаються ни видному місці у вашому публічному профілі. Вони дають змогу людям фільтрувати ваші публічні пости за цими хештеґами. Це дуже корисно для відстеження мистецьких творів та довготривалих проектів."
   filters:
     contexts:
-      home: Ваш фід
+      home: Ваша стрічка
       notifications: Сповіщення
-      public: Публічний фід
+      public: Глобальні стрічки
       thread: Повідомлення
     edit:
       title: Редагувати фільтр
@@ -465,14 +719,48 @@ uk:
       title: Фільтри
     new:
       title: Додати фільтр
+  footer:
+    developers: Розробникам
+    more: Більше…
+    resources: Ресурси
+    trending_now: Актуальні
   generic:
+    all: Усі
     changes_saved_msg: Зміни успішно збережені!
+    copy: Копіювати
+    order_by: Сортувати за
     save_changes: Зберегти зміни
+    validation_errors:
+      few: Щось досі не гаразд! Перегляньте %{count} повідомлень про помилки
+      many: Щось досі не гаразд! Перегляньте %{count} повідомлень про помилки
+      one: Щось досі не гаразд! Перегляньте повідомлення про помилку
+      other: Щось досі не гаразд! Перегляньте %{count} повідомлень про помилки
+  html_validator:
+    invalid_markup: 'містить неприпустиму HTML розмітку: %{error}'
+  identity_proofs:
+    active: Активне
+    authorize: Так, авторизувати
+    authorize_connection_prompt: Авторизувати це зашифроване з'єднання?
+    errors:
+      failed: Не вдалося встановити це зашифроване з'єднання. Спробуйте ще раз за допомогою %{provider}.
+    i_am_html: Я %{username} з %{service}.
+    identity: Ідентичність
+    inactive: Неактивний
+    publicize_checkbox: 'Та дмухнути це:'
+    publicize_toot: 'Це доведено! Я таки %{username} з %{service}: %{url}'
+    status: Стан перевірки
+    view_proof: Переглянути доказ
   imports:
+    modes:
+      merge: Злиття
+      merge_long: Зберегти існуючі записи та додати нові
+      overwrite: Перезаписувати
+      overwrite_long: Замінити поточні записи новими
     preface: Вы можете завантажити деякі дані, наприклад, списки людей, на яких Ви підписані чи яких блокуєте, в Ваш акаунт на цій інстанції з файлів, експортованих з іншої інстанції.
     success: Ваші дані були успішно загружені та будуть оброблені в найближчий момент
     types:
       blocking: Список блокувань
+      domain_blocking: Список заблокованих сайтів
       following: Підписки
       muting: Список глушення
     upload: Завантажити
@@ -490,6 +778,11 @@ uk:
     expires_in_prompt: Ніколи
     generate: Згенерувати
     invited_by: 'Вас запросив(-ла):'
+    max_uses:
+      few: "%{count} використання"
+      many: "%{count} використань"
+      one: 1 використання
+      other: "%{count} використань"
     max_uses_prompt: Без обмеження
     prompt: Генеруйте та діліться посиланням з іншими для надання доступу до сайту
     table:
@@ -498,16 +791,14 @@ uk:
     title: Запросити людей
   lists:
     errors:
-      limit: Ви досягнули максимальної кількості списків
+      limit: Ви досягли максимальної кількості списків
   media_attachments:
     validations:
       images_and_video: Не можна додати відео до статусу з зображеннями
       too_many: Не можна додати більше 4 файлів
   migrations:
-    acct: username@domain нового аккаунту
-    currently_redirecting: 'Ваш профіль налаштований перенаправляти на:'
-    proceed: Зберегти
-    updated_msg: Переїзд вашого аккаунту успішно оновлений!
+    acct: username@domain нового облікового запису
+    cancel: Скасувати перенаправлення
   moderation:
     title: Модерація
   notification_mailer:
@@ -531,12 +822,12 @@ uk:
       subject: Користувачу %{name} сподобався ваш статус
       title: Нове вподобання
     follow:
-      body: "%{name} тепер підписаний на вас!"
+      body: "%{name} тепер підписаний(-а) на вас!"
       subject: "%{name} тепер підписаний(-а) на вас"
-      title: Новий підписник
+      title: Новий підписник(-ця)
     follow_request:
       action: Керувати запитами на підписку
-      body: "%{name} запитав Вас про підписку"
+      body: "%{name} запитав(-ла) Вас про підписку"
       subject: "%{name} хоче підписатися на Вас"
       title: Новий запит на підписку
     mention:
@@ -546,11 +837,12 @@ uk:
       title: Нова згадка
     reblog:
       body: 'Ваш статус було передмухнуто %{name}:'
-      subject: "%{name} передмухнув ваш статус"
+      subject: "%{name} передмухнув(-ла) ваш статус"
       title: Нове передмухування
   number:
     human:
       decimal_units:
+        format: "%n%u"
         units:
           billion: млрд
           million: млн
@@ -560,49 +852,138 @@ uk:
   pagination:
     newer: Новіше
     next: Далі
+    older: Старіші
     prev: Назад
+    truncate: "&hellip;"
+  polls:
+    errors:
+      already_voted: Ви вже голосували в цьому опитуванні
+      duplicate_options: містить повторювані варіанти
+      duration_too_long: надто далеко у майбутньому
+      duration_too_short: надто мала тривалість
+      expired: Це опитування вже завершено
+      over_character_limit: не може бути довше ніж %{max} символів кожен
+      too_few_options: має містити більше ніж один варіант
+      too_many_options: не може мати більше ніж %{max} варіантів
   preferences:
     other: Інше
+    posting_defaults: Промовчання для постів
+    public_timelines: Глобальні стрічки
+  relationships:
+    activity: Діяльність облікового запису
+    dormant: Неактивні
+    last_active: Крайня активність
+    most_recent: За часом створення
+    moved: Переміщено
+    mutual: Взаємні уподобання
+    primary: Первинні
+    relationship: Зв’язок
+    remove_selected_domains: Видалити усіх підписників з обраних доменів
+    remove_selected_followers: Видалити обраних підписників
+    remove_selected_follows: Не стежити за обраними користувачами
+    status: Статус облікового запису
   remote_follow:
     acct: Введіть username@domain, яким ви хочете підписатися
-    missing_resource: Пошук потрібного перенаправлення URL для Вашого аккаунта закінчився невдачею
-    no_account_html: Не маєте аккаунту? Не біда, ви можете <a href='%{sign_up_path}' target='_blank'>зареєструватися</a>
+    missing_resource: Не вдалося знайти необхідний URL переадресації для вашого облікового запису
+    no_account_html: Не маєте облікового запису? Ви можете <a href='%{sign_up_path}' target='_blank'>зареєструватися тут</a>
     proceed: Перейти до підписки
     prompt: 'Ви хочете підписатися на:'
-  remote_unfollow:
-    error: Помилка
-    title: Заголовок
-    unfollowed: Відписані
+    reason_html: "<strong>Чому це необхідно?</strong> <code>%{instance}</code> можливо, не є сервером, на якому ви зареєстровані, тому ми маємо спрямувати вас до вашого домашнього сервера."
+  remote_interaction:
+    favourite:
+      proceed: Перейти до додавання в улюблені
+      prompt: 'Ви хочете зробити улюбленим цей дмух:'
+    reblog:
+      proceed: Перейти до передмухування
+      prompt: 'Ви хочете передмухнути цей дмух:'
+    reply:
+      proceed: Перейти до відповіді
+      prompt: 'Ви хочете відповісти на цей дмух:'
+  scheduled_statuses:
+    over_daily_limit: Ви перевищили ліміт в %{limit} запланованих дмухів на сьогодні
+    over_total_limit: Ви перевищили ліміт в %{limit} запланованих дмухів
+    too_soon: Запланована дата має бути в майбутньому
   sessions:
     activity: Остання активність
     browser: Браузер
     browsers:
+      alipay: Alipay
+      blackberry: Blackberry
+      chrome: Хром
+      edge: Microsoft Edge
+      electron: Electron
+      firefox: Firefox
       generic: Невідомий браузер
+      ie: Internet Explorer
+      micro_messenger: MicroMessenger
+      nokia: Nokia S40 Ovi Browser
+      opera: Опера
+      otter: Otter
+      phantom_js: PhantomJS
+      qq: QQ Browser
+      safari: Сафарі
+      uc_browser: UCBrowser
+      weibo: Weibo
     current_session: Активна сесія
     description: "%{browser} на %{platform}"
-    explanation: Це веб-браузери, нині авторизовані до вашого аккаунту Mastodon.
+    explanation: Це веб-браузери, авторизовані у вашому обліковому записі Mastodon.
+    ip: IP
     platforms:
+      adobe_air: Adobe Air
+      android: Android
+      blackberry: Blackberry
+      chrome_os: ChromeOS
+      firefox_os: Firefox OS
+      ios: iOS
+      linux: Linux
+      mac: Mac
       other: невідома платформа
+      windows: Windows
+      windows_mobile: Windows Mobile
+      windows_phone: Windows Phone
     revoke: Закінчити
     revoke_success: Сесія успішно закінчена
     title: Сесії
   settings:
+    account: Обліковий запис
+    account_settings: Налаштування облікового запису
+    appearance: Вигляд
     authorized_apps: Авторизовані застосунки
-    back: Назад у Mastodon
-    delete: Видалення аккаунту
+    back: Назад до Mastodon
+    delete: Видалення облікового запису
     development: Розробка
     edit_profile: Редагувати профіль
     export: Експорт даних
+    featured_tags: Рекомендовані хештеґи
+    identity_proofs: Докази ідентичності
     import: Імпорт
-    migrate: Міграція акаунту
+    import_and_export: Імпорт та експорт
+    migrate: Міграція облікового запису
     notifications: Сповіщення
     preferences: Налаштування
+    profile: Профіль
+    relationships: Підписки та підписники
     two_factor_authentication: Двофакторна авторизація
   statuses:
     attached:
       description: 'Прикріплено: %{attached}'
+      image:
+        few: "%{count} зображень"
+        many: "%{count} зображень"
+        one: "%{count} зображення"
+        other: "%{count} зображення"
+      video:
+        few: "%{count} відео"
+        many: "%{count} відео"
+        one: "%{count} відео"
+        other: "%{count} відео"
     boosted_from_html: Просунуто від %{acct_link}
     content_warning: 'Попередження про контент: %{warning}'
+    disallowed_hashtags:
+      few: 'заборонених хештеґа: %{tags}'
+      many: 'заборонених хештеґів: %{tags}'
+      one: 'заборонений хештеґ: %{tags}'
+      other: 'заборонених хештеґів: %{tags}'
     language_detection: Автоматично визначати мову
     open_in_web: Відкрити у вебі
     over_character_limit: перевищено ліміт символів (%{max})
@@ -611,7 +992,16 @@ uk:
       ownership: Не можна закріпити чужий пост
       private: Не можна закріпити непублічний пост
       reblog: Не можна закріпити просунутий пост
+    poll:
+      total_votes:
+        few: "%{count} голоса"
+        many: "%{count} голосів"
+        one: "%{count} голос"
+        other: "%{count} голоси"
+      vote: Проголосувати
     show_more: Детальніше
+    sign_in_to_participate: Увійдіть, щоб брати участь у бесіді
+    title: '%{name}: "%{quote}"'
     visibilities:
       private: Для підписників
       private_long: Показувати тільки підписникам
@@ -622,13 +1012,20 @@ uk:
   stream_entries:
     pinned: Закріплений пост
     reblogged: передмухнув(-ла)
-    sensitive_content: Несприйнятливий контент
+    sensitive_content: Дражливий контент
+  tags:
+    does_not_match_previous_name: не збігається з попереднім ім'ям
   terms:
+    body_html: "<h2>Політика конфіденційності</h2>\n<h3 id=\"collect\">Яку інформацію ми збираємо?</h3>\n\n<ul>\n<li><em>Основна інформація про обліковий запис</em>: Якщо ви реєструєтесь на цьому сервері, вас можуть попросити ввести ім’я користувача, електронну адресу та пароль. Ви також можете ввести додаткову інформацію профілю, наприклад, ім'я для відображення та біографію, завантажити зображення профілю та зображення заголовка. Ім'я користувача, відображуване ім’я, біографія, зображення профілю та зображення заголовка завжди є загальнодоступними.</li>\n<li><em>Повідомлення, підписки та інша публічна інформація</em>: Список людей, на яких ви підписані, є публічним, це ж стосується і списка ваших підписників. Коли ви надсилаєте повідомлення, дата та час зберігаються, а також програма, за допомогою якої ви надіслали повідомлення. Повідомлення можуть містити мультимедійні вкладення, такі як зображення та відео. Загальнодоступні публікації, навіть приховані зі стрічок, доступні для всіх. Коли ви розміщуєте публікацію у своєму профілі, це також загальнодоступна інформація. Ваші публікації доставляються вашим підписникам, у деяких випадках це означає, що вони доставляються на інші сервери і копії зберігаються там. Коли ви видаляєте публікації, ця інформація також доставляється вашим \nпідписникам. Перепости та вподобання завжди публічні.</li>\n<li><em>Прямі публікації та пости лише для підписників</em>: Усі повідомлення зберігаються та обробляються на сервері. Публікації лише для підписників доставляються вашим підписникам та користувачам, які згадуються в них, а прямі повідомлення надсилаються лише тим користувачам, які в них згадуються. У \nдеяких випадках це означає, що вони доставляються на інші сервери і копії зберігаються там. Ми докладаємо сумлінних зусиль, щоб обмежити доступ до цих постів лише уповноваженим особам, але інші сервери можуть цього не зробити. Тому важливо переглянути сервери, до яких належать ваші підписники. Ви можете переключити параметр для схвалення та відхилення нових підписників вручну в налаштуваннях. <em>Будь ласка, майте на увазі, що оператори нашого сервера та будь-якого приймаючого сервера, можуть переглядати такі повідомлення</em>, і що одержувачі можуть робити скріншот, копіювати або повторно ділитися ними. <em>Не діліться будь-якою небезпечною інформацією на Mastodon.</em></li>\n<li><em>IP-адреси та інші метадані</em>: Коли ви входите в систему, ми записуємо IP-адресу, з якої ви входите, а також назву веб-переглядача. Усі сеанси, якими ви ввійшли в систему, доступні вам для перегляду та скасування в налаштуваннях. Остання використана IP-адреса зберігається до 12 місяців. Ми також можемо зберігати журнали серверів, які включають IP-адресу кожного запиту на наш сервер.</li>\n</ul>\n\n<hr class=\"spacer\"/>\n\n<h3 id=\"use\">Для чого ми використовуємо вашу інформацію?</h3>\n\n<p>Будь-яка інформація, яку ми збираємо від вас, може використовуватися такими способами:</p>\n\n<ul>\n<li>Для забезпечення основної функціональності Mastodon. Ви можете взаємодіяти з вмістом інших людей та розміщувати власний вміст лише тоді, коли ви ввійшли в систему. Наприклад, ви можете підписатись на інших людей, щоб переглядати їх публікації об’єднаними на вашій власній персоналізованій локальній стрічці. </li>\n<li>Щоб сприяти модерації спільноти, наприклад, порівнюючи вашу IP-адресу з іншими відомими адресами для визначення ухилення від бану чи інших порушень.</li>\n<li>Електронна адреса, яку ви вводите, може використовуватися для надсилання вам інформації, сповіщень про інших людей, які взаємодіють з вашим вмістом або надсилають вам повідомлення, а також для відповіді на запити та/або інші запитання./li>\n</ul>\n\n<hr class=\"spacer\"/>\n\n<h3 id=\"protect\">Як ми захищаємо вашу інформацію?</h3>\n\n<p>Ми застосовуємо різноманітні заходи безпеки для підтримки безпеки вашої особистої інформації під час введення, подання чи доступу до вашої особистої інформації. Крім усього іншого, сеанс вашого веб-переглядача, а також трафік між вашими програмами та API захищені SSL, а ваш пароль хеширується за допомогою сильного одностороннього алгоритму. Ви можете дозволити двофакторну автентифікацію для подальшого захисту доступу до свого облікового запису.</p>\n\n<hr class=\"spacer\"/>\n\n<h3 id=\"data-retention\">Яка наша політика збереження даних?</h3>\n\n<p>Ми докладемо зусиль для того, щоб:</p>\n\n<ul>\n<li>Зберігати журнали сервера, що містять IP-адресу всіх запитів на цьому сервері, але більше 90 днів.</li>\n<li>Зберігати IP-адреси, пов’язані з зареєстрованими користувачами, не більше 12 місяців.</li>\n</ul>\n\n<p>Ви можете запитати та завантажити архів свого вмісту, включаючи ваші публікації, медіа-додатки, зображення профілю та зображення заголовка.</p>\n\n<p>Ви можете в будь-який час безповоротно видалити свій обліковий запис.</p>\n\n<hr class=\"spacer\"/>\n\n<h3 id=\"cookies\">Чи використовуємо ми файли cookie?</h3>\n\n<p>Так. Файли cookie — це невеликі файли, які сайт або його постачальник послуг передає на жорсткий диск вашого комп'ютера через веб-браузер (якщо ви це дозволите). Ці файли cookie дозволяють сайту розпізнавати ваш веб-переглядач і, якщо у вас зареєстрований обліковий запис, пов’язувати його зі своїм зареєстрованим обліковим записом.</p>\n\n<p>Ми використовуємо файли cookie, щоб зрозуміти і зберегти ваші налаштування для майбутніх відвідувань. </p>\n\n<hr class=\"spacer\"/>\n\n<h3 id=\"disclose\">Чи розкриваємо ми будь-яку інформацію іншим сторонам?</h3>\n\n<p>Ми не продаємо, не торгуємо та іншим чином не передаємо назовні вашої особистої інформації. Це не стосується довірених третіх осіб, які допомагають нам керувати нашим сайтом, вести наш бізнес або обслуговувати вас, якщо ці сторони погоджуються зберігати цю інформацію конфіденційною. Ми також можемо оприлюднити вашу інформацію, коли вважаємо, що випуск доцільний для дотримання законодавства, \nзастосування політики нашого веб-сайта чи захисту наших або інших прав, власності чи безпеки.</p>\n\n<p>Ваш загальнодоступний вміст може завантажуватися іншими серверами в мережі. Ваші загальнодоступні публікації та публікації лише для підписників, доставляються на сервери, де \"проживають\" ваші підписники, а прямі повідомлення надходять на сервери одержувачів, якщо ці підписники або одержувачі проживають на іншому сервері, ніж цей./p>\n\n<p>Коли ви дозволяєте додатку використовувати ваш обліковий запис, залежно від обсягу дозволів, які ви затверджуєте, він може отримати доступ до вашої інформації про загальнодоступний профіль, список ваших підписок, ваші підписники, ваші списки, всі ваші публікації та вибране. Програми ніколи не можуть отримати доступ до вашої електронної адреси чи пароля.</p>\n\n<hr class=\"spacer\"/>\n\n<h3 id=\"children\">Використання сайту дітьми</h3>\n\n<p>Якщо цей сервер знаходиться в ЄС або ЄЕП: наш сайт, продукти та послуги спрямовані на людей, яким не менше 16 років. Якщо вам не виповнилося 16 років, відповідно до вимог GDPR (<a href=\"https://en.wikipedia.org/wiki/General_Data_Protection_Regulation\">Загальне положення про захист даних</a>) не використовуйте цей веб-сайт.</p>\n\n<p>Якщо цей сервер знаходиться в США: наш сайт, продукти та послуги спрямовані на людей, яким не менше 13 років. Якщо вам не виповнилося 13 років, відповідно до вимог COPPA (<a href=\"https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act\">Закон про захист конфіденційності дітей в Інтернеті</a>) не використовуйте цей сайт.</p>\n\n<p>Законодавчі вимоги можуть бути різними, якщо цей сервер знаходиться в іншій юрисдикції.</p>\n\n<hr class=\"spacer\"/>\n\n<h3 id=\"changes\">Зміни в нашій Політиці конфіденційності</h3>\n\n<p>Якщо ми вирішимо змінити нашу політику конфіденційності, ми опублікуємо ці зміни на цій сторінці. </p>\n\n<p>Цей документ є CC-BY-SA. Востаннє оновлено 7 березня 2018 року.</p>\n\n<p>Первісно адаптовано з <a href=\"https://github.com/discourse/discourse\">політики конфіденційності дискурсу</a>.</p>\n"
     title: Умови використання та Політика приватності %{instance}
   themes:
     contrast: Висока контрасність
     default: Mastodon
     mastodon-light: Mastodon (світла)
+  time:
+    formats:
+      default: "%b %d, %Y, %H:%M"
+      month: "%b %Y"
   two_factor_authentication:
     code_hint: Для підтверждення введіть код, згенерований застосунком аутентифікатора
     description_html: При увімкненні <strong>двофакторної аутентифікації</strong>, вхід буде вимагати від Вас використовування Вашого телефона, який згенерує вхідний код.
@@ -638,36 +1035,58 @@ uk:
     enabled_success: Двофакторна аутентифікація успішно увімкнена
     generate_recovery_codes: Згенерувати коди відновлення
     instructions_html: "<strong>Відскануйте цей QR-код за допомогою Google Authenticator чи іншого TOTP-застосунку на Вашому телефоні</strong>. З цього моменту він буде генерувати коди, які буде необхідно ввести для входу."
-    lost_recovery_codes: Коди відновлення дозволяють повернути доступ до акаунту у випадку втрати телефону. Якщо Ви втратили Ваші коди відновлення, Ви можете знову згенерувати їх тут. Тоді ваші старі коди відновлення будуть анульовані.
+    lost_recovery_codes: Коди відновлення дозволяють повернути доступ до вашого облікового запису у випадку втрати телефону. Якщо ви втратили ваші коди відновлення, ви можете знову згенерувати їх тут. Ваші старі коди відновлення будуть анульовані.
     manual_instructions: 'Якщо Ви не можете відсканувати QR-код та хочете ввести його вручну, секрет представлений тут відкритим текстом:'
     recovery_codes: Запасні коди відновлення
     recovery_codes_regenerated: Коди відновлення успішно згенеровані
-    recovery_instructions_html: У випадку втрати доступу до вашого телефону ви можете використати один з кодів відновлення, вказаних нижче, щоб повернути доступ до акаунту. Тримайте коди відновлення у безпеці, наприклад, роздруйте їх та зберігайте їх з іншими важливими документами.
+    recovery_instructions_html: У випадку втрати доступу до вашого телефону ви можете використати один з нижчевказаних кодів відновлення, щоб повернути доступ до вашого облікового запису. <strong>Тримайте коди відновлення у безпеці</strong>, наприклад, роздрукуйте їх та зберігайте разом з іншими важливими документами.
     setup: Налаштувати
     wrong_code: Введений код неправильний! Чи правильно встановлений час на сервері та пристрої?
   user_mailer:
     backup_ready:
-      explanation: Ви зробили запит на повний архів вашого аккаунту Mastodon. Він вже готовий для завантаження!
+      explanation: Ви робили запит повної резервної копії вашого облікового запису Mastodon. Вона вже готова для завантаження!
       subject: Ваш архів готовий до завантаження
       title: Винесення архіву
+    warning:
+      explanation:
+        disable: Поки ваш обліковий запис заморожений, його дані залишаються незмінними. Проте ви не зможете виконувати будь-які дії над обліковим записом, доки його не буде розблоковано.
+        silence: Поки ваш обліковий запис обмежено, ваші дмухи на цьому сервері бачитимуть лише ті люди, які вже слідкують за вами, а вас може бути виключено з різних публічних списків. Тим не менш, інші можуть слідкувати за вами вручну.
+        suspend: Ваш обліковий запис було призупинено, а всі ваші дмухи і вивантажені медіафайли - безповоротно видалено з цього сервера та серверів, де ви мали послідовників.
+      get_in_touch: Ви можете відповісти на цей електронний лист, щоб зконтактувати з працівниками %{instance}.
+      review_server_policies: Переглянути політики сервера
+      subject:
+        disable: Ваш обліковий запис %{acct} було заморожено
+        none: Попередження для %{acct}
+        silence: Ваш обліковий запис %{acct} було обмежено
+        suspend: Ваш обліковий запис %{acct} було призупинено
+      title:
+        disable: Обліковий запис заморожено
+        none: Попередження
+        silence: Ообліковий запис обмежено
+        suspend: Обліковий запис призупинено
     welcome:
       edit_profile_action: Налаштувати профіль
-      edit_profile_step: Ви можете налаштувати профіль під себе завантаживши аватар, шпалери, змінивши відображуване ім'я тощо. Якщо ви захочете переглядати нових підписників до того, як вони зможуть підписатися на вас, ви можете заблокувати свій аккаунт.
+      edit_profile_step: Ви можете налаштувати ваш профіль, завантаживши аватар, шпалери, змінивши відображуване ім'я тощо. Якщо ви захочете переглядати нових підписників до того, як вони зможуть підписатися на вас, ви можете заблокувати свій обліковий запис.
       explanation: Ось декілька порад для початку
       final_action: Почати постити
-      final_step: 'Почність постити! Навіть не підписавшись на вас, інші зможуть побачити ваші пости, наприкоал, у локальному фіді та у хештеґах. Якщо ви хочете представитися, можете скористатися хештеґом #introductions.'
+      final_step: 'Почність постити! Навіть не підписавшись на вас, інші зможуть побачити ваші пости, наприклад, у локальній стрічці та у хештеґах. Якщо ви хочете представитися, можете скористатися хештеґом #introductions.'
       full_handle: Ваше звернення
       full_handle_hint: Те, що ви хочете сказати друзям, щоб вони могли написати вам або підписатися з інших сайтів.
       review_preferences_action: Змінити налаштування
       review_preferences_step: Переконайтеся у тому, що ви налаштували все необхідне, як от які e-mail повідомлення ви хочете отримувати, або який рівень приватності ви хочете встановити вашим постам за замовчуванням. Якщо хочете, ви можете увімкнути автоматичне програвання GIF анімацій.
       subject: Ласкаво просимо до Mastodon
       tip_federated_timeline: Федерований фід є широким поглядом на мережу Mastodon. Але він включає лише людей, на яких підписані ваші сусіди по сайту, тому він не є повним.
-      tip_following: Ви автоматично підписані на адміністратора(-ів) сервера. Для того, щоб знайти ще цікавих людей, дослідіть локальний та федерований фіди.
+      tip_following: Ви автоматично підписані на адміністратора(-ів) сервера. Для того, щоб знайти ще цікавих людей, дослідіть локальну та глобальну стрічки.
       tip_local_timeline: Локальний фід - це погляд згори на людей на %{instance}. Це ваші прямі сусіди!
       tip_mobile_webapp: Якщо ваш мобільний браузер пропонує вам додати Mastodon на робочий стіл, ви можете отримувати push-сповіщення. Все може виглядати як нативний застосунок у багатьох речах.
       tips: Поради
       title: Ласкаво просимо, %{name}!
   users:
+    follow_limit_reached: Не можна слідкувати більш ніж за %{limit} людей
     invalid_email: Введена адреса e-mail неправильна
     invalid_otp_token: Введено неправильний код
     otp_lost_help_html: Якщо ви втратили доступ до обох, ви можете отримати доступ з %{email}
+    seamless_external_login: Ви увійшли за допомогою зовнішнього сервісу, тому налаштування паролю та електронної пошти недоступні.
+    signed_in_as: 'Ви увійшли як:'
+  verification:
+    verification: Підтвердження
diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml
index 0c9b291ad3d301fd27b8b41b07bff9ab9b288af3..55685c82e90d008a9173db9a3dc515c95d977f27 100644
--- a/config/locales/zh-CN.yml
+++ b/config/locales/zh-CN.yml
@@ -10,18 +10,14 @@ zh-CN:
     api: API
     apps: 移动应用
     apps_platforms: 在 iOS、Android 和其他平台上使用 Mastodon
-    browse_directory: 浏览用户资料目录并按兴趣筛选
+    browse_directory: 浏览用户目录并按兴趣筛选
     browse_public_posts: 浏览 Mastodon 上公共嘟文的实时信息流
     contact: 联系方式
     contact_missing: 未设定
     contact_unavailable: 未公开
     discover_users: 发现用户
     documentation: 文档
-    extended_description_html: |
-      <h3>这里可以写一些规定</h3>
-      <p>本站尚未设置详细介绍。</p>
     federation_hint_html: 在%{instance} 上拥有账户后,你可以关注任何 Mastodon 服务器或其他服务器上的人。
-    generic_description: "%{domain} 是这个庞大网络中的一台服务器"
     get_apps: 尝试移动应用
     hosted_on: 一个在 %{domain} 上运行的 Mastodon 实例
     learn_more: 了解详情
@@ -32,7 +28,7 @@ zh-CN:
     status_count_after:
       other: 条嘟文
     status_count_before: 他们共嘟出了
-    tagline: 关注朋友并发现新朋友
+    tagline: 关注并发现新朋友
     terms: 使用条款
     user_count_after:
       other: 位用户
@@ -90,7 +86,7 @@ zh-CN:
         title: 为 %{username} 更改电子邮件地址
       confirm: 确认
       confirmed: 已确认
-      confirming: 确认
+      confirming: 确认中
       deleted: 已删除
       demote: 降任
       disable: 停用
@@ -179,6 +175,7 @@ zh-CN:
       username: 用户名
       warn: 警告
       web: 站内页面
+      whitelisted: 已加入白名单
     action_logs:
       actions:
         assigned_to_self_report: "%{name} 接管了举报 %{target}"
@@ -243,13 +240,16 @@ zh-CN:
       config: 服务器配置
       feature_deletions: 帐户删除
       feature_invites: 邀请链接
-      feature_profile_directory: 用户资料目录
+      feature_profile_directory: 用户目录
       feature_registrations: 公开注册
-      feature_relay: 同步中继
+      feature_relay: 中继服务器
+      feature_spam_check: 反垃圾
       feature_timeline_preview: 时间轴预览
       features: 功能
       hidden_service: 匿名服务连通性
       open_reports: 待处理举报数
+      pending_tags: 等待审核的标签
+      pending_users: 等待审核的用户
       recent_users: 新用户
       search: 全文搜索
       single_user_mode: 单用户模式
@@ -261,11 +261,18 @@ zh-CN:
       week_interactions: 本周互动数
       week_users_active: 本周活跃用户数
       week_users_new: 本周新用户数
+      whitelist_mode: 白名单模式
+    domain_allows:
+      add_new: 白名单域名
+      created_msg: 域名已被列入白名单
+      destroyed_msg: 域名已从白名单中移除
+      undo: 从白名单中移除
     domain_blocks:
       add_new: 添加新屏蔽域名
       created_msg: 正在进行域名屏蔽
       destroyed_msg: 域名屏蔽已撤销
       domain: 域名
+      edit: 编辑域名屏蔽
       existing_domain_block_html: 您已经对 %{name} 施加了更严格的限制,您需要先 <a href="%{unblock_url}">解封</a>。
       new:
         create: 添加屏蔽
@@ -276,6 +283,8 @@ zh-CN:
           silence: 自动隐藏
           suspend: 自动封禁
         title: 添加域名屏蔽
+      private_comment: 私密评论
+      public_comment: 公开评论
       reject_media: 拒绝接收媒体文件
       reject_media_hint: 删除本地已缓存的媒体文件,并且不再接收来自该域名的任何媒体文件。此选项不影响封禁
       reject_reports: 拒绝接收举报
@@ -294,6 +303,7 @@ zh-CN:
         title: 撤销对 %{domain} 的域名屏蔽
         undo: 撤销
       undo: 撤销屏蔽域名
+      view: 查看域名屏蔽
     email_domain_blocks:
       add_new: 添加新条目
       created_msg: 电子邮件域名屏蔽添加成功
@@ -316,6 +326,8 @@ zh-CN:
         all: 全部
         limited: 受限的
         title: 运营
+      private_comment: 私密评论
+      public_comment: 公开评论
       title: 已知实例
       total_blocked_by_us: 被我方屏蔽的
       total_followed_by_them: 被对方关注的
@@ -333,9 +345,9 @@ zh-CN:
     pending_accounts:
       title: 待处理的帐户 (%{count})
     relays:
-      add_new: 添加新的中继
+      add_new: 订阅新的中继
       delete: 删除
-      description_html: "<strong>同步中继</strong>是一种中间服务器,各实例可以通过订阅中继和向中继推送信息的方式来大量交换公开嘟文。<strong>它可以帮助中小型实例发现网络中的内容</strong>,而无需本地用户手动关注其他远程实例上的用户。"
+      description_html: "<strong>中继服务器</strong>是一个信息统合服务器,各服务器可以通过订阅中继服务器和向中继服务器推送信息来交换大量公开嘟文。<strong>它可以帮助中小型服务器发现联邦宇宙中的其他服务器的内容</strong>,而无需本站用户手动关注其他远程服务器上的用户。"
       disable: 禁用
       disabled: 已禁用
       enable: 启用
@@ -400,14 +412,14 @@ zh-CN:
         desc_html: 用于在首页展示。推荐分辨率 293×205px 以上。未指定的情况下将使用默认吉祥物。
         title: 吉祥物图像
       peers_api_enabled:
-        desc_html: 截至目前本实例在网络中已发现的域名
+        desc_html: 截至目前本服务器在联邦宇宙中已发现的域名
         title: 公开已知实例的列表
       preview_sensitive_media:
         desc_html: 始终在站外链接预览中展示缩略图,无论媒体内容是否标记为敏感
         title: 在 OpenGraph 预览中显示敏感媒体内容
       profile_directory:
-        desc_html: 允许用户可被发现
-        title: 启用用户资料目录
+        desc_html: 允许用户被发现
+        title: 启用用户目录
       registrations:
         closed_message:
           desc_html: 本站关闭注册期间的提示信息。可以使用 HTML 标签
@@ -443,6 +455,9 @@ zh-CN:
         desc_html: 可以填写自己的隐私权政策、使用条款或其他法律文本。可以使用 HTML 标签
         title: 自定义使用条款
       site_title: 本站名称
+      spam_check_enabled:
+        desc_html: Mastodon可以自动隐藏和举报重复发送垃圾消息的账户。但是本功能有可能误伤无辜。
+        title: 自动反垃圾
       thumbnail:
         desc_html: 用于在 OpenGraph 和 API 中显示预览图。推荐分辨率 1200×630px
         title: 本站缩略图
@@ -450,12 +465,15 @@ zh-CN:
         desc_html: 在主页显示公共时间轴
         title: 时间轴预览
       title: 网站设置
+      trends:
+        title: 热门标签
     statuses:
       back_to_account: 返回帐户信息页
       batch:
         delete: 删除
         nsfw_off: 标记为非敏感内容
         nsfw_on: 标记为敏感内容
+      deleted: 已删除
       failed_to_execute: 执行失败
       media:
         title: 媒体文件
@@ -463,21 +481,15 @@ zh-CN:
       no_status_selected: 因为没有嘟文被选中,所以没有更改
       title: 帐户嘟文
       with_media: 含有媒体文件
-    subscriptions:
-      callback_url: 回调 URL
-      confirmed: 已确认
-      expires_in: 失效时间
-      last_delivery: 最后一次接收数据的时间
-      title: WebSub
-      topic: 话题
     tags:
-      accounts: 帐户
-      hidden: 隐藏
-      hide: 从目录隐藏
-      name: 话题标签
+      accounts_today: 今日活跃用户
+      accounts_week: 本周活跃用户
+      directory: 在目录中
+      review: 审核状态
+      reviewed: 已审核
       title: 话题标签
-      unhide: 在目录中显示
-      visible: 可见
+      trending_right_now: 当前热门
+      unreviewed: 未审核
     title: 管理
     warning_presets:
       add_new: 添加新条目
@@ -498,6 +510,7 @@ zh-CN:
     advanced_web_interface_hint: 如果你想使用整个屏幕宽度,高级 web 界面允许您配置多个不同的栏目,可以同时看到更多的信息:主页、通知、跨站时间轴、任意数量的列表和话题标签。
     animations_and_accessibility: 动画和访问选项
     confirmation_dialogs: 确认对话框
+    discovery: 发现
     sensitive_content: 敏感内容
   application_mailer:
     notification_preferences: 更改电子邮件首选项
@@ -518,7 +531,6 @@ zh-CN:
     apply_for_account: 请求邀请
     change_password: 密码
     checkbox_agreement_html: 我同意 <a href="%{rules_path}" target="_blank">服务器规则</a> 和 <a href="%{terms_path}" target="_blank">服务条款</a>
-    confirm_email: 确认电子邮件地址
     delete_account: 删除帐户
     delete_account_html: 如果你想删除你的帐户,请<a href="%{path}">点击这里继续</a>。你需要确认你的操作。
     didnt_get_confirmation: 没有收到确认邮件?
@@ -538,6 +550,11 @@ zh-CN:
     reset_password: 重置密码
     security: 帐户安全
     set_new_password: 设置新密码
+    setup:
+      title: 初始设置
+    status:
+      account_status: 帐户状态
+      confirming: 等待电子邮件确认完成。
     trouble_logging_in: 登录有问题?
   authorize_follow:
     already_following: 你已经在关注此用户了
@@ -565,25 +582,20 @@ zh-CN:
       x_months: "%{count}个月"
       x_seconds: "%{count}ç§’"
   deletes:
-    bad_password_msg: 想得美,黑客!密码输入错误
     confirm_password: 输入你当前的密码来验证身份
-    description_html: 继续操作将会<strong>永久地、不可撤销地</strong>删除帐户中的所有内容,然后冻结帐户。你的用户名将会被保留,以防有人冒用你的身份。
     proceed: 删除帐户
     success_msg: 你的帐户已经成功删除
-    warning_html: 我们只能保证本服务器上的内容将会被彻底删除。对于已经被广泛传播的内容,它们在本服务器以外的某些地方可能仍然可见。此外,失去连接的服务器以及停止接收订阅的服务器所存储的数据亦无法删除。
-    warning_title: 关于已传播的内容的警告
   directories:
-    directory: 用户资料目录
-    enabled: 您目前已被列入目录中。
-    enabled_but_waiting: 您已选择列入目录,但是您没有达到关注者数量下限 (%{min_followers} 名) 。
+    directory: 用户目录
     explanation: 根据兴趣发现用户
     explore_mastodon: 探索 %{title}
-    how_to_enable: 您目前没有选择选择列入到目录中。您可以在下面选择列入。可以在个人简介中加上话题标签,话题标签也会显示在用户资料目录里!
-    people:
-      other: "%{count} 人"
+  domain_validator:
+    invalid_domain: 不是一个有效的域名
   errors:
+    '400': 您提交的请求无效或格式不正确。
     '403': 你没有访问这个页面的权限。
     '404': 无法找到你所要访问的页面。
+    '406': This page is not available in the requested format.
     '410': 你所要访问的页面此处已不存在。
     '422':
       content: 无法确认登录信息。你是不是屏蔽了 Cookie?
@@ -592,6 +604,7 @@ zh-CN:
     '500':
       content: 抱歉,我们的后台出错了。
       title: 这个页面有问题
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: 使用 Mastodon 网页版应用需要启用 JavaScript。你也可以选择适用于你的平台的 <a href="%{apps_path}">Mastodon 应用</a>。
   existing_username_validator:
     not_found: 在本站找不到此用户
@@ -635,6 +648,7 @@ zh-CN:
     developers: 开发者
     more: 更多…
     resources: 资源
+    trending_now: 现在流行
   generic:
     all: 全部
     changes_saved_msg: 更改保存成功!
@@ -708,9 +722,6 @@ zh-CN:
       too_many: 最多只能添加 4 张图片
   migrations:
     acct: 新帐户的 用户名@域名
-    currently_redirecting: 目前你的个人资料页显示的新帐户是:
-    proceed: 保存
-    updated_msg: 帐户迁移设置更新成功!
   moderation:
     title: 运营
   notification_mailer:
@@ -748,6 +759,7 @@ zh-CN:
   number:
     human:
       decimal_units:
+        format: "%n%u"
         units:
           billion: B
           million: M
@@ -759,6 +771,7 @@ zh-CN:
     next: 下一页
     older: æ›´æ—©
     prev: 上一页
+    truncate: "&hellip;"
   polls:
     errors:
       already_voted: 你已经在这里投过票了
@@ -803,10 +816,6 @@ zh-CN:
     reply:
       proceed: 确认回复
       prompt: 您想要回复此嘟文:
-  remote_unfollow:
-    error: 错误
-    title: 标题
-    unfollowed: 已取消关注
   scheduled_statuses:
     over_daily_limit: 您已超出每日定时嘟文的上限(%{limit} 条)
     over_total_limit: 您已超出定时嘟文的上限(%{limit} 条)
@@ -870,7 +879,7 @@ zh-CN:
     notifications: 通知
     preferences: 首选项
     profile: 个人资料
-    relationships: 正在关注以及关注者
+    relationships: 关注管理
     two_factor_authentication: 双重认证
   statuses:
     attached:
@@ -909,7 +918,90 @@ zh-CN:
     pinned: 置顶嘟文
     reblogged: 转嘟
     sensitive_content: 敏感内容
+  tags:
+    does_not_match_previous_name: 和之前的名称不匹配
   terms:
+    body_html: |
+      <h2>隐私政策</h2>
+      <h3 id="collect">我们收集什么信息?</h3>
+
+      <ul>
+      <li><em>基本帐户信息</em>:如果您在此服务器上注册,可能会要求您输入用户名,电子邮件地址和密码。 您还可以输入其他个人资料信息,例如显示名称和传记,并上传个人资料照片和标题图像。 用户名,显示名称,传记,个人资料图片和标题图片始终公开列出。</li>
+      <li><em>帖子,关注和其他公共信息</em>: 您关注的人员列表会公开列出,您的粉丝也是如此。 提交邮件时,会存储日期和时间以及您提交邮件的应用程序。 消息可能包含媒体附件,例如图片和视频。 公开和非上市帖子可公开获取。 当您在个人资料中添加帖子时,这也是公开信息。 您的帖子会发送给您的关注者,在某些情况下,这意味着他们会将其发送到不同的服务器,并将副本存储在那里。 当您删除帖子时,同样会将其发送给您的关注者。 重新记录或赞成其他职位的行为始终是公开的。</li>
+      <li><em>直接和关注者的帖子</em>: 所有帖子都在服务器上存储和处理。 仅限关注者的帖子会发送给您的关注者和用户,并且直接帖子仅会发送给他们中提到的用户。 在某些情况下,这意味着它们被传送到不同的服务器并且副本存储在那里。 我们善意努力限制只有授权人员访问这些帖子,但其他服务器可能无法这样做。 因此,查看您的关注者所属的服务器非常重要。 您可以在设置中切换选项以手动批准和拒绝新关注者。 <em>请记住,服务器和任何接收服务器的操作员可能会查看此类消息</em>, 并且收件人可以截图,复制或以其他方式重新共享它们。 <em> 不要在 Mastodon 上分享任何危险信息。</em></li>
+      <li><em>IP和其他元数据</em>: 登录时,我们会记录您登录的IP地址以及浏览器应用程序的名称。 所有登录的会话都可供您在设置中查看和撤销。 使用的最新IP地址最长可存储12个月。 我们还可以保留服务器日志,其中包括我们服务器的每个请求的IP地址。</li>
+      </ul>
+
+      <hr class="spacer" />
+
+      <h3 id="use">我们将您的信息用于什么?</h3>
+
+      <p>我们向您收集的任何信息均可通过以下方式使用:</p>
+
+      <ul>
+      <li>提供Mastodon的核心功能。 您只能在登录时与其他人的内容进行互动并发布您自己的内容。例如,您可以关注其他人在您自己的个性化家庭时间轴中查看他们的组合帖子。</li>
+      <li>为了帮助社区适度,例如将您的IP地址与其他已知的IP地址进行比较,以确定禁止逃税或其他违规行为。</li>
+      <li>您提供的电子邮件地址可能用于向您发送信息,有关其他人与您的内容交互或向您发送消息的通知,以及回复查询和/或其他请求或问题。</li>
+      </ul>
+
+      <hr class="spacer" />
+
+      <h3 id="protect">我们如何保护您的信息?</h3>
+
+      <p>当您输入,提交或访问您的个人信息时,我们会实施各种安全措施以维护您的个人信息的安全。 除此之外,您的浏览器会话以及应用程序和API之间的流量都使用SSL进行保护,您的密码使用强大的单向算法进行哈希处理。 您可以启用双因素身份验证,以进一步保护对您帐户的访问。</p>
+
+      <hr class="spacer" />
+
+      <h3 id="data-retention">我们的数据保留政策是什么?</h3>
+
+      <p>我们真诚的努力:</p>
+
+      <ul>
+      <li>保留包含此服务器的所有请求的IP地址的服务器日志,只要保留此类日志,不超过90天。</li>
+      <li>保留与注册用户关联的IP地址不超过12个月。</li>
+      </ul>
+
+      <p>您可以请求并下载我们内容的存档,包括您的帖子,媒体附件,个人资料图片和标题图片。</p>
+
+      <p>您可以随时不可逆转地删除您的帐户。</p>
+
+      <hr class="spacer"/>
+
+      <h3 id="cookies">我们使用 cookies 吗?</h3>
+
+      <p>是。 Cookie是网站或其服务提供商通过Web浏览器传输到计算机硬盘的小文件(如果允许)。 这些cookie使网站能够识别您的浏览器,如果您有注册帐户,则将其与您的注册帐户相关联。</p>
+
+      <p>我们使用Cookie来了解并保存您对未来访问的偏好。</p>
+
+      <hr class="spacer" />
+
+      <h3 id="disclose">我们是否透露任何信息给其他方?</h3>
+
+      <p>我们不会将您的个人身份信息出售,交易或以其他方式转让给外方。 这不包括协助我们操作我们的网站,开展业务或为您服务的受信任的第三方,只要这些方同意保密这些信息。 当我们认为发布适合遵守法律,执行我们的网站政策或保护我们或他人的权利,财产或安全时,我们也可能会发布您的信息。</p>
+
+      <p>您的公共内容可能会被网络中的其他服务器下载。 您的公开帖子和关注者帖子会发送到关注者所在的服务器,并且直接邮件会传递到收件人的服务器,只要这些关注者或收件人位于与此不同的服务器上。</p>
+
+      <p>当您授权应用程序使用您的帐户时,根据您批准的权限范围,它可能会访问您的公开个人资料信息,以下列表,您的关注者,您的列表,所有帖子和您的收藏夹。 应用程序永远不能访问您的电子邮件地址或密码。</p>
+
+      <hr class="spacer" />
+
+      <h3 id="children">儿童使用网站</h3>
+
+      <p>如果此服务器位于欧盟或欧洲经济区:我们的网站,产品和服务都是针对至少16岁的人。 如果您未满16岁,则符合GDPR的要求(<a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">General Data Protection Regulation</a>) 不要使用这个网站。</p>
+
+      <p>如果此服务器位于美国:我们的网站,产品和服务均面向至少13岁的人。 如果您未满13岁,则符合COPPA的要求 (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) 不要使用这个网站。</p>
+
+      <p>如果此服务器位于另一个辖区,则法律要求可能不同。</p>
+
+      <hr class="spacer" />
+
+      <h3 id="changes">我们隐私政策的变更</h3>
+
+      <p>如果我们决定更改我们的隐私政策,我们会在此页面上发布这些更改。</p>
+
+      <p>本文件为CC-BY-SA。 它最后更新于2018年3月7日。</p>
+
+      <p>最初改编自 <a href="https://github.com/discourse/discourse">Discourse 隐私政策</a>.</p>
     title: "%{instance} 使用条款和隐私权政策"
   themes:
     contrast: Mastodon(高对比度)
@@ -945,7 +1037,7 @@ zh-CN:
         disable: 虽然您的帐户被冻结,您的帐户数据仍然完整;但是您无法在解锁前执行任何操作。
         silence: 当您的帐户受限时,只有已经关注过你的人才会这台服务器上看到你的嘟文,并且您会被排除在各种公共列表之外。但是,其他人仍然可以手动关注你。
         suspend: 您的帐户已被封禁,所有的嘟文和您上传的媒体文件都已经从该服务器和您的关注者的服务器上删除并且不可恢复。
-      review_server_policies: 审阅服务器条款
+      review_server_policies: 查看服务器政策
       subject:
         disable: 您的帐户 %{acct} 已被冻结
         none: 对 %{acct} 的警告
diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml
index 25e7475a88d5ec5e584f4b90af7ffe643c48212b..338cf810ece545acd54b94754361eb5707e9b5c0 100644
--- a/config/locales/zh-HK.yml
+++ b/config/locales/zh-HK.yml
@@ -8,10 +8,6 @@ zh-HK:
     contact: 聯絡
     contact_missing: 未設定
     contact_unavailable: 未公開
-    extended_description_html: |
-      <h3>這裡可以寫一些網站規則</h3>
-      <p>本站未有詳細介紹</p>
-    generic_description: "%{domain} 是 Mastodon 網絡中其中一個服務站"
     hosted_on: 在 %{domain} 運作的 Mastodon 服務站
     learn_more: 了解更多
     source_code: 源代碼
@@ -327,13 +323,6 @@ zh-HK:
       no_media: 不含媒體檔案
       title: 帳戶文章
       with_media: 含有媒體檔案
-    subscriptions:
-      callback_url: 回傳 URL
-      confirmed: 確定
-      expires_in: 期限
-      last_delivery: 資料最後送抵時間
-      title: PuSH 訂閱
-      topic: 所訂閱資源
     title: 管理
   admin_mailer:
     new_report:
@@ -357,7 +346,6 @@ zh-HK:
     your_token: token
   auth:
     change_password: 密碼
-    confirm_email: 確認電郵
     delete_account: 刪除帳戶
     delete_account_html: 如果你想刪除你的帳戶,請<a href="%{path}">點擊這裡繼續</a>。你需要確認你的操作。
     didnt_get_confirmation: 沒有收到確認指示電郵?
@@ -399,16 +387,14 @@ zh-HK:
       x_months: "%{count}個月"
       x_seconds: "%{count}ç§’"
   deletes:
-    bad_password_msg: 想得美,黑客!密碼輸入錯誤
     confirm_password: 輸入你現在的密碼來驗證身份
-    description_html: 繼續操作將會<strong>永久地、不可還原地</strong>刪除帳戶中的所有內容,然後凍結帳戶。你的用戶名將會被保留,以防有人冒用你的身份。
     proceed: 刪除帳戶
     success_msg: 你的帳戶已經成功刪除
-    warning_html: 我們只能保證本服務站上的內容將會被徹底刪除。對於已經被廣泛傳播的內容,它們在本服務站以外的某些地方可能仍然可見。此外,失去連接的服務站以及停止接收訂閱的服務站所存儲的數據亦無法刪除。
-    warning_title: 關於已傳播的內容的警告
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': 你沒有觀看本頁的權限。
     '404': 找不到內容。
+    '406': This page is not available in the requested format.
     '410': 內容已被刪除。
     '422':
       content: 無法確認登入資訊。會不會你阻擋了本站使用 Cookies 的權限?
@@ -417,6 +403,7 @@ zh-HK:
     '500':
       content: 抱歉,我們的後台出錯了。
       title: 這個頁面有問題
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: 使用 Mastodon 網頁版應用需要啟用 JavaScript。你也可以選擇適用於你的平台的 <a href="%{apps_path}">Mastodon 應用</a>。
   exports:
     archive_takeout:
@@ -474,9 +461,6 @@ zh-HK:
       too_many: 不可以加入超過 4 個檔案
   migrations:
     acct: 新帳戶的 用戶名@域名
-    currently_redirecting: 目前你的個人資料頁顯示的新帳戶是:
-    proceed: 保存
-    updated_msg: 帳戶遷移設置更新成功!
   moderation:
     title: 營運
   notification_mailer:
@@ -524,10 +508,6 @@ zh-HK:
     missing_resource: 無法找到你用戶的轉接網址
     proceed: 下一步
     prompt: 你希望關注︰
-  remote_unfollow:
-    error: 錯誤
-    title: 標題
-    unfollowed: 取消關注
   sessions:
     activity: 最近活動
     browser: 瀏覽器
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index d3dcf5133f5f4203aa923456606298784f6dcd16..bb6e761c7067c7a1a87b0d320015343c3058bebd 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -16,10 +16,6 @@ zh-TW:
     contact_unavailable: 未公開
     discover_users: 探索使用者
     documentation: 文件
-    extended_description_html: |
-      <h3>這裡可以寫一些網站規則</h3>
-      <p>本站點未有詳細介紹</p>
-    generic_description: "%{domain} 是 Mastodon 網路中其中一個站點"
     get_apps: 嘗試行動應用程式
     hosted_on: 在 %{domain} 運作的 Mastodon 站點
     learn_more: 了解詳細
@@ -401,13 +397,6 @@ zh-TW:
       no_media: 不含媒體檔案
       title: 帳戶嘟文
       with_media: 含有媒體檔案
-    subscriptions:
-      callback_url: 回傳網址
-      confirmed: 已確認
-      expires_in: 期限
-      last_delivery: 最後遞送
-      title: WebSub 訂閱
-      topic: 主題
     title: 管理介面
   admin_mailer:
     new_report:
@@ -431,7 +420,6 @@ zh-TW:
     your_token: ä½ çš„ token
   auth:
     change_password: 密碼
-    confirm_email: 確認電子信箱位址
     delete_account: 刪除帳戶
     delete_account_html: 如果你想刪除你的帳戶,請<a href="%{path}">點擊這裡繼續</a>。你需要確認你的操作。
     didnt_get_confirmation: 沒有收到驗證信?
@@ -473,16 +461,14 @@ zh-TW:
       x_months: "%{count}個月"
       x_seconds: "%{count}ç§’"
   deletes:
-    bad_password_msg: 想得美,駭客! 密碼輸入錯誤
     confirm_password: 輸入你現在的密碼來驗證身份
-    description_html: 繼續操作將會<strong>永久地、不可還原地</strong>刪除帳戶中的所有內容,然後凍結帳戶。你的使用者名稱將會被保留,以防有人冒用你的身份。
     proceed: 刪除帳戶
     success_msg: 你的帳戶已經成功刪除
-    warning_html: 我們只能保證本伺服器上的內容將會被徹底刪除。對於已經被廣泛傳播的內容,它們在本伺服器以外的某些地方可能仍然可見。此外,離線伺服器以及停止接收訂閱的伺服器所儲存的資料亦無法刪除。
-    warning_title: 關於已傳播的內容警告
   errors:
+    '400': The request you submitted was invalid or malformed.
     '403': 你沒有觀看這個頁面的權限。
     '404': 您所尋找的網頁不存在。
+    '406': This page is not available in the requested format.
     '410': 您所尋找的網頁此處已不存在。
     '422':
       content: 安全驗證失敗。請確定有開啟瀏覽器 Cookies 功能?
@@ -491,6 +477,7 @@ zh-TW:
     '500':
       content: 抱歉,我們的後台出現問題了。
       title: 這個頁面有問題
+    '503': The page could not be served due to a temporary server failure.
     noscript_html: 使用 Mastodon 網頁版應用需要啟用 JavaScript。你也可以選擇適用於你的平台的 <a href="%{apps_path}">Mastodon 應用</a>。
   exports:
     archive_takeout:
@@ -544,9 +531,6 @@ zh-TW:
       too_many: 無法加入超過 4 個檔案
   migrations:
     acct: 新帳戶的 使用者名稱@站點網域
-    currently_redirecting: 目前你的個人資料頁顯示的新帳戶是:
-    proceed: 儲存
-    updated_msg: 帳戶搬遷設定更新成功!
   moderation:
     title: 營運
   notification_mailer:
@@ -587,10 +571,6 @@ zh-TW:
     missing_resource: 無法找到資源
     proceed: 下一步
     prompt: '您希望關注:'
-  remote_unfollow:
-    error: 錯誤
-    title: 標題
-    unfollowed: 取消關注
   sessions:
     activity: 最近活動
     browser: 瀏覽器
diff --git a/config/navigation.rb b/config/navigation.rb
index df10241892997bfde20f25ac999d5e8ac7b8ae0a..eebd4f75e38ba3ba336fbe6ac2d1f93050972a99 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -4,42 +4,42 @@ SimpleNavigation::Configuration.run do |navigation|
   navigation.items do |n|
     n.item :web, safe_join([fa_icon('chevron-left fw'), t('settings.back')]), root_url
 
-    n.item :profile, safe_join([fa_icon('user fw'), t('settings.profile')]), settings_profile_url do |s|
-      s.item :profile, safe_join([fa_icon('pencil fw'), t('settings.appearance')]), settings_profile_url, highlights_on: %r{/settings/profile|/settings/migration}
+    n.item :profile, safe_join([fa_icon('user fw'), t('settings.profile')]), settings_profile_url, if: -> { current_user.functional? } do |s|
+      s.item :profile, safe_join([fa_icon('pencil fw'), t('settings.appearance')]), settings_profile_url
       s.item :featured_tags, safe_join([fa_icon('hashtag fw'), t('settings.featured_tags')]), settings_featured_tags_url
       s.item :identity_proofs, safe_join([fa_icon('key fw'), t('settings.identity_proofs')]), settings_identity_proofs_path, highlights_on: %r{/settings/identity_proofs*}, if: proc { current_account.identity_proofs.exists? }
     end
 
-    n.item :preferences, safe_join([fa_icon('cog fw'), t('settings.preferences')]), settings_preferences_url do |s|
+    n.item :preferences, safe_join([fa_icon('cog fw'), t('settings.preferences')]), settings_preferences_url, if: -> { current_user.functional? } do |s|
       s.item :appearance, safe_join([fa_icon('desktop fw'), t('settings.appearance')]), settings_preferences_appearance_url
       s.item :notifications, safe_join([fa_icon('bell fw'), t('settings.notifications')]), settings_preferences_notifications_url
       s.item :other, safe_join([fa_icon('cog fw'), t('preferences.other')]), settings_preferences_other_url
     end
 
-    n.item :relationships, safe_join([fa_icon('users fw'), t('settings.relationships')]), relationships_url
-    n.item :filters, safe_join([fa_icon('filter fw'), t('filters.index.title')]), filters_path, highlights_on: %r{/filters}
+    n.item :relationships, safe_join([fa_icon('users fw'), t('settings.relationships')]), relationships_url, if: -> { current_user.functional? }
+    n.item :filters, safe_join([fa_icon('filter fw'), t('filters.index.title')]), filters_path, highlights_on: %r{/filters}, if: -> { current_user.functional? }
 
     n.item :security, safe_join([fa_icon('lock fw'), t('settings.account')]), edit_user_registration_url do |s|
-      s.item :password, safe_join([fa_icon('lock fw'), t('settings.account_settings')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete}
+      s.item :password, safe_join([fa_icon('lock fw'), t('settings.account_settings')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete|/settings/migration|/settings/aliases}
       s.item :two_factor_authentication, safe_join([fa_icon('mobile fw'), t('settings.two_factor_authentication')]), settings_two_factor_authentication_url, highlights_on: %r{/settings/two_factor_authentication}
       s.item :authorized_apps, safe_join([fa_icon('list fw'), t('settings.authorized_apps')]), oauth_authorized_applications_url
     end
 
     n.item :data, safe_join([fa_icon('cloud-download fw'), t('settings.import_and_export')]), settings_export_url do |s|
-      s.item :import, safe_join([fa_icon('cloud-upload fw'), t('settings.import')]), settings_import_url
+      s.item :import, safe_join([fa_icon('cloud-upload fw'), t('settings.import')]), settings_import_url, if: -> { current_user.functional? }
       s.item :export, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_url
     end
 
-    n.item :invites, safe_join([fa_icon('user-plus fw'), t('invites.title')]), invites_path, if: proc { Setting.min_invite_role == 'user' }
-    n.item :development, safe_join([fa_icon('code fw'), t('settings.development')]), settings_applications_url
+    n.item :invites, safe_join([fa_icon('user-plus fw'), t('invites.title')]), invites_path, if: proc { Setting.min_invite_role == 'user' && current_user.functional? }
+    n.item :development, safe_join([fa_icon('code fw'), t('settings.development')]), settings_applications_url, if: -> { current_user.functional? }
 
     n.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), admin_reports_url, if: proc { current_user.staff? } do |s|
       s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url
       s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
       s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts|/admin/pending_accounts}
       s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path
-      s.item :tags, safe_join([fa_icon('tag fw'), t('admin.tags.title')]), admin_tags_path
-      s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url(limited: '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks}, if: -> { current_user.admin? }
+      s.item :tags, safe_join([fa_icon('hashtag fw'), t('admin.tags.title')]), admin_tags_path, highlights_on: %r{/admin/tags}
+      s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url(limited: whitelist_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.admin? }
       s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? }
     end
 
@@ -47,8 +47,7 @@ SimpleNavigation::Configuration.run do |navigation|
       s.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_url
       s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/settings}
       s.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis}
-      s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/relays}
-      s.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url, if: -> { current_user.admin? }
+      s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? && !whitelist_mode? }, highlights_on: %r{/admin/relays}
       s.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? }
       s.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? }
     end
diff --git a/config/puma.rb b/config/puma.rb
index 6a96867d54d27e04f2090bc4191131da39b8a76c..224be79036556e4fa1cc86f16ffc9b05169cce56 100644
--- a/config/puma.rb
+++ b/config/puma.rb
@@ -1,3 +1,5 @@
+persistent_timeout ENV.fetch('PERSISTENT_TIMEOUT') { 20 }.to_i
+
 threads_count = ENV.fetch('MAX_THREADS') { 5 }.to_i
 threads threads_count, threads_count
 
diff --git a/config/routes.rb b/config/routes.rb
index 764db8db2e61882d1dc2507de5a50fa04beb2f58..e43e201a5d78f0a18280da8e76e9d4c6eaf4a2d1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -6,8 +6,12 @@ require 'sidekiq-scheduler/web'
 Sidekiq::Web.set :session_secret, Rails.application.secrets[:secret_key_base]
 
 Rails.application.routes.draw do
+  root 'home#index'
+
   mount LetterOpenerWeb::Engine, at: 'letter_opener' if Rails.env.development?
 
+  health_check_routes
+
   authenticate :user, lambda { |u| u.admin? } do
     mount Sidekiq::Web, at: 'sidekiq', as: :sidekiq
     mount PgHero::Engine, at: 'pghero', as: :pghero
@@ -20,17 +24,28 @@ Rails.application.routes.draw do
   end
 
   get '.well-known/host-meta', to: 'well_known/host_meta#show', as: :host_meta, defaults: { format: 'xml' }
+  get '.well-known/nodeinfo', to: 'well_known/nodeinfo#index', as: :nodeinfo, defaults: { format: 'json' }
   get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger
   get '.well-known/change-password', to: redirect('/auth/edit')
   get '.well-known/keybase-proof-config', to: 'well_known/keybase_proof_config#show'
 
+  get '/nodeinfo/2.0', to: 'well_known/nodeinfo#show', as: :nodeinfo_schema
+
   get 'manifest', to: 'manifests#show', defaults: { format: 'json' }
   get 'intent', to: 'intents#show'
   get 'custom.css', to: 'custom_css#show', as: :custom_css
 
+  resource :instance_actor, path: 'actor', only: [:show] do
+    resource :inbox, only: [:create], module: :activitypub
+  end
+
   devise_scope :user do
     get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite
-    match '/auth/finish_signup' => 'auth/confirmations#finish_signup', via: [:get, :patch], as: :finish_signup
+
+    namespace :auth do
+      resource :setup, only: [:show, :update], controller: :setup
+      resource :challenge, only: [:create], controller: :challenges
+    end
   end
 
   devise_for :users, path: 'auth', controllers: {
@@ -45,12 +60,6 @@ Rails.application.routes.draw do
   get '/authorize_follow', to: redirect { |_, request| "/authorize_interaction?#{request.params.to_query}" }
 
   resources :accounts, path: 'users', only: [:show], param: :username do
-    resources :stream_entries, path: 'updates', only: [:show] do
-      member do
-        get :embed
-      end
-    end
-
     get :remote_follow,  to: 'remote_follow#new'
     post :remote_follow, to: 'remote_follow#create'
 
@@ -58,8 +67,9 @@ Rails.application.routes.draw do
       member do
         get :activity
         get :embed
-        get :replies
       end
+
+      resources :replies, only: [:index], module: :activitypub
     end
 
     resources :followers, only: [:index], controller: :follower_accounts
@@ -127,8 +137,13 @@ Rails.application.routes.draw do
     end
 
     resource :delete, only: [:show, :destroy]
-    resource :migration, only: [:show, :update]
+    resource :migration, only: [:show, :create]
 
+    namespace :migration do
+      resource :redirect, only: [:new, :create, :destroy]
+    end
+
+    resources :aliases, only: [:index, :create, :destroy]
     resources :sessions, only: [:destroy]
     resources :featured_tags, only: [:index, :create, :destroy]
   end
@@ -146,16 +161,18 @@ Rails.application.routes.draw do
   get '/public', to: 'public_timelines#show', as: :public_timeline
   get '/media_proxy/:id/(*any)', to: 'media_proxy#show', as: :media_proxy
 
-  # Remote follow
-  resource :remote_unfollow, only: [:create]
   resource :authorize_interaction, only: [:show, :create]
   resource :share, only: [:show, :create]
 
   namespace :admin do
     get '/dashboard', to: 'dashboard#index'
 
-    resources :subscriptions, only: [:index]
-    resources :domain_blocks, only: [:new, :create, :show, :destroy]
+    resources :domain_allows, only: [:new, :create, :show, :destroy]
+    resources :domain_blocks, only: [:new, :create, :show, :destroy, :update] do
+      member do
+        get :edit
+      end
+    end
     resources :email_domain_blocks, only: [:index, :new, :create, :destroy]
     resources :action_logs, only: [:index]
     resources :warning_presets, except: [:new]
@@ -191,8 +208,6 @@ Rails.application.routes.draw do
 
     resources :accounts, only: [:index, :show] do
       member do
-        post :subscribe
-        post :unsubscribe
         post :enable
         post :unsilence
         post :unsuspend
@@ -236,20 +251,19 @@ Rails.application.routes.draw do
       resource :two_factor_authentication, only: [:destroy]
     end
 
-    resources :custom_emojis, only: [:index, :new, :create, :update, :destroy] do
-      member do
-        post :copy
-        post :enable
-        post :disable
+    resources :custom_emojis, only: [:index, :new, :create] do
+      collection do
+        post :batch
       end
     end
 
     resources :account_moderation_notes, only: [:create, :destroy]
 
-    resources :tags, only: [:index] do
-      member do
-        post :hide
-        post :unhide
+    resources :tags, only: [:index, :show, :update] do
+      collection do
+        post :approve_all
+        post :reject_all
+        post :batch
       end
     end
   end
@@ -257,16 +271,6 @@ Rails.application.routes.draw do
   get '/admin', to: redirect('/admin/dashboard', status: 302)
 
   namespace :api do
-    # PubSubHubbub outgoing subscriptions
-    resources :subscriptions, only: [:show]
-    post '/subscriptions/:id', to: 'subscriptions#update'
-
-    # PubSubHubbub incoming subscriptions
-    post '/push', to: 'push#update', as: :push
-
-    # Salmon
-    post '/salmon/:id', to: 'salmon#update', as: :salmon
-
     # OEmbed
     get '/oembed', to: 'oembed#show', as: :oembed
 
@@ -294,12 +298,10 @@ Rails.application.routes.draw do
 
         member do
           get :context
-          get :card
         end
       end
 
       namespace :timelines do
-        resource :direct, only: :show, controller: :direct
         resource :home, only: :show, controller: :home
         resource :public, only: :show, controller: :public
         resources :tag, only: :show
@@ -318,16 +320,15 @@ Rails.application.routes.draw do
         end
       end
 
-      get '/search', to: 'search#index', as: :search
-
-      resources :follows,      only: [:create]
       resources :media,        only: [:create, :update]
       resources :blocks,       only: [:index]
       resources :mutes,        only: [:index]
       resources :favourites,   only: [:index]
       resources :reports,      only: [:create]
+      resources :trends,       only: [:index]
       resources :filters,      only: [:index, :create, :show, :update, :destroy]
       resources :endorsements, only: [:index]
+      resources :markers,      only: [:index, :create]
 
       namespace :apps do
         get :verify_credentials, to: 'credentials#show'
@@ -341,6 +342,7 @@ Rails.application.routes.draw do
       end
 
       resource :domain_blocks, only: [:show, :create, :destroy]
+      resource :directory, only: [:show]
 
       resources :follow_requests, only: [:index] do
         member do
@@ -352,7 +354,6 @@ Rails.application.routes.draw do
       resources :notifications, only: [:index, :show] do
         collection do
           post :clear
-          post :dismiss # Deprecated
         end
 
         member do
@@ -391,6 +392,12 @@ Rails.application.routes.draw do
         resource :accounts, only: [:show, :create, :destroy], controller: 'lists/accounts'
       end
 
+      namespace :featured_tags do
+        get :suggestions, to: 'suggestions#index'
+      end
+
+      resources :featured_tags, only: [:index, :create, :destroy]
+
       resources :polls, only: [:create, :show] do
         resources :votes, only: :create, controller: 'polls/votes'
       end
@@ -440,14 +447,10 @@ Rails.application.routes.draw do
 
   get '/web/(*any)', to: 'home#index', as: :web
 
-  get '/about',      to: 'about#show'
-  get '/about/more', to: 'about#more'
-  get '/terms',      to: 'about#terms'
-
-  root 'home#index'
+  get '/about',        to: 'about#show'
+  get '/about/more',   to: 'about#more'
+  get '/terms',        to: 'about#terms'
 
-  match '*unmatched_route',
-        via: :all,
-        to: 'application#raise_not_found',
-        format: false
+  match '/', via: [:post, :put, :patch, :delete], to: 'application#raise_not_found', format: false
+  match '*unmatched_route', via: :all, to: 'application#raise_not_found', format: false
 end
diff --git a/config/settings.yml b/config/settings.yml
index da6ee643a2b0838af7a2993653c8026279a6ba7c..a39eb2ea80bcd872125cd21676aae0c0d1a2d7d4 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -33,6 +33,10 @@ defaults: &defaults
   theme: 'default'
   aggregate_reblogs: true
   advanced_layout: false
+  use_blurhash: true
+  use_pending_items: false
+  trends: true
+  trendable_by_default: false
   notification_emails:
     follow: false
     reblog: false
@@ -42,6 +46,7 @@ defaults: &defaults
     digest: true
     report: true
     pending_account: true
+    trending_tag: true
   interactions:
     must_be_follower: false
     must_be_following: false
@@ -60,6 +65,9 @@ defaults: &defaults
   activity_api_enabled: true
   peers_api_enabled: true
   show_known_fediverse_at_about_page: true
+  spam_check_enabled: true
+  show_domain_blocks: 'disabled'
+  show_domain_blocks_rationale: 'disabled'
 
 development:
   <<: *defaults
diff --git a/config/sidekiq.yml b/config/sidekiq.yml
index 0ec1742abfeb2547627c6691f972e553ff36dd58..5de25de234be1f5ebcf246090c4a91cb9bce14cf 100644
--- a/config/sidekiq.yml
+++ b/config/sidekiq.yml
@@ -9,9 +9,9 @@
   scheduled_statuses_scheduler:
     every: '5m'
     class: Scheduler::ScheduledStatusesScheduler
-  subscriptions_scheduler:
-    cron: '<%= Random.rand(0..59) %> <%= Random.rand(4..6) %> * * *'
-    class: Scheduler::SubscriptionsScheduler
+  trending_tags_scheduler:
+    every: '5m'
+    class: Scheduler::TrendingTagsScheduler
   media_cleanup_scheduler:
     cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *'
     class: Scheduler::MediaCleanupScheduler
@@ -24,9 +24,6 @@
   user_cleanup_scheduler:
     cron: '<%= Random.rand(0..59) %> <%= Random.rand(4..6) %> * * *'
     class: Scheduler::UserCleanupScheduler
-  subscriptions_cleanup_scheduler:
-    cron: '<%= Random.rand(0..59) %> <%= Random.rand(1..3) %> * * 0'
-    class: Scheduler::SubscriptionsCleanupScheduler
   ip_cleanup_scheduler:
     cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *'
     class: Scheduler::IpCleanupScheduler
diff --git a/config/webpack/development.js b/config/webpack/development.js
index 1e50a4f468efb19a792f13abc0c6f294b3be6c2b..56f6e43f03a3423d253cbbf426b3a49bc43e9b62 100644
--- a/config/webpack/development.js
+++ b/config/webpack/development.js
@@ -56,5 +56,6 @@ module.exports = merge(sharedConfig, {
       settings.dev_server.watch_options,
       watchOptions
     ),
+    writeToDisk: filePath => /ocr/.test(filePath),
   },
 });
diff --git a/config/webpack/rules/css.js b/config/webpack/rules/css.js
index 3b5b51232029fdfbda34b49a4408c11e073e4d70..bc1f42c13482bffbfb6890ec5cc45f021fd79a9b 100644
--- a/config/webpack/rules/css.js
+++ b/config/webpack/rules/css.js
@@ -9,7 +9,6 @@ module.exports = {
       options: {
         sourceMap: true,
         importLoaders: 2,
-        localIdentName: '[name]__[local]___[hash:base64:5]',
       },
     },
     {
diff --git a/config/webpack/shared.js b/config/webpack/shared.js
index cb4e5a85f0e70eba3376c64a78a9fc8609961ca7..d5e399ced80a1320c7c9967e6469f90b37136180 100644
--- a/config/webpack/shared.js
+++ b/config/webpack/shared.js
@@ -5,6 +5,7 @@ const { basename, dirname, join, relative, resolve } = require('path');
 const { sync } = require('glob');
 const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 const AssetsManifestPlugin = require('webpack-assets-manifest');
+const CopyPlugin = require('copy-webpack-plugin');
 const extname = require('path-complete-extname');
 const { env, settings, themes, output } = require('./configuration');
 const rules = require('./rules');
@@ -84,6 +85,10 @@ module.exports = {
       writeToDisk: true,
       publicPath: true,
     }),
+    new CopyPlugin([
+      { from: 'node_modules/tesseract.js/dist/worker.min.js', to: 'ocr' },
+      { from: 'node_modules/tesseract.js-core/tesseract-core.wasm.js', to: 'ocr' },
+    ]),
   ],
 
   resolve: {
diff --git a/crowdin.yml b/crowdin.yml
index f94417f2ed7b776f30cc4e90f01812319120fd2b..88a24d6211d50910a1568b3186540f5e5fc97689 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -1,4 +1,4 @@
-commit_message: "[ci skip]"
+commit_message: '[ci skip]'
 files:
   - source: /app/javascript/mastodon/locales/en.json
     translation: /app/javascript/mastodon/locales/%two_letters_code%.json
diff --git a/db/migrate/20161202132159_add_in_reply_to_account_id_to_statuses.rb b/db/migrate/20161202132159_add_in_reply_to_account_id_to_statuses.rb
index 2c24b53d09edc347f1b757e9eea9abb75180987c..3a559ccd6da2da644d458d2bf399fe17caf34b9b 100644
--- a/db/migrate/20161202132159_add_in_reply_to_account_id_to_statuses.rb
+++ b/db/migrate/20161202132159_add_in_reply_to_account_id_to_statuses.rb
@@ -3,7 +3,7 @@ class AddInReplyToAccountIdToStatuses < ActiveRecord::Migration[5.0]
     add_column :statuses, :in_reply_to_account_id, :integer, null: true, default: nil
 
     ActiveRecord::Base.transaction do
-      Status.where.not(in_reply_to_id: nil).includes(:thread).find_each do |status|
+      Status.unscoped.where.not(in_reply_to_id: nil).includes(:thread).find_each do |status|
         next if status.thread.nil?
 
         status.in_reply_to_account_id = status.thread.account_id
diff --git a/db/migrate/20170209184350_add_reply_to_statuses.rb b/db/migrate/20170209184350_add_reply_to_statuses.rb
index c5074728bf1f8bee38ced0fce5b3353dcdfd0509..b8b5c130681a5994623654fab3196433bcaa4792 100644
--- a/db/migrate/20170209184350_add_reply_to_statuses.rb
+++ b/db/migrate/20170209184350_add_reply_to_statuses.rb
@@ -1,7 +1,7 @@
 class AddReplyToStatuses < ActiveRecord::Migration[5.0]
   def up
     add_column :statuses, :reply, :boolean, nil: false, default: false
-    Status.update_all('reply = (in_reply_to_id IS NOT NULL)')
+    Status.unscoped.update_all('reply = (in_reply_to_id IS NOT NULL)')
   end
 
   def down
diff --git a/db/migrate/20170918125918_ids_to_bigints.rb b/db/migrate/20170918125918_ids_to_bigints.rb
index c6feed8f9187877d4afca73b34af543fa65b6389..8e19468db851a25941b34e1cfe753c7ffe6c7e24 100644
--- a/db/migrate/20170918125918_ids_to_bigints.rb
+++ b/db/migrate/20170918125918_ids_to_bigints.rb
@@ -5,70 +5,70 @@ class IdsToBigints < ActiveRecord::Migration[5.1]
 
   disable_ddl_transaction!
 
-  INCLUDED_COLUMNS = [
-    [:account_domain_blocks, :account_id],
-    [:account_domain_blocks, :id],
-    [:accounts, :id],
-    [:blocks, :account_id],
-    [:blocks, :id],
-    [:blocks, :target_account_id],
-    [:conversation_mutes, :account_id],
-    [:conversation_mutes, :id],
-    [:domain_blocks, :id],
-    [:favourites, :account_id],
-    [:favourites, :id],
-    [:favourites, :status_id],
-    [:follow_requests, :account_id],
-    [:follow_requests, :id],
-    [:follow_requests, :target_account_id],
-    [:follows, :account_id],
-    [:follows, :id],
-    [:follows, :target_account_id],
-    [:imports, :account_id],
-    [:imports, :id],
-    [:media_attachments, :account_id],
-    [:media_attachments, :id],
-    [:mentions, :account_id],
-    [:mentions, :id],
-    [:mutes, :account_id],
-    [:mutes, :id],
-    [:mutes, :target_account_id],
-    [:notifications, :account_id],
-    [:notifications, :from_account_id],
-    [:notifications, :id],
-    [:oauth_access_grants, :application_id],
-    [:oauth_access_grants, :id],
-    [:oauth_access_grants, :resource_owner_id],
-    [:oauth_access_tokens, :application_id],
-    [:oauth_access_tokens, :id],
-    [:oauth_access_tokens, :resource_owner_id],
-    [:oauth_applications, :id],
-    [:oauth_applications, :owner_id],
-    [:reports, :account_id],
-    [:reports, :action_taken_by_account_id],
-    [:reports, :id],
-    [:reports, :target_account_id],
-    [:session_activations, :access_token_id],
-    [:session_activations, :user_id],
-    [:session_activations, :web_push_subscription_id],
-    [:settings, :id],
-    [:settings, :thing_id],
-    [:statuses, :account_id],
-    [:statuses, :application_id],
-    [:statuses, :in_reply_to_account_id],
-    [:stream_entries, :account_id],
-    [:stream_entries, :id],
-    [:subscriptions, :account_id],
-    [:subscriptions, :id],
-    [:tags, :id],
-    [:users, :account_id],
-    [:users, :id],
-    [:web_settings, :id],
-    [:web_settings, :user_id],
-  ]
-  INCLUDED_COLUMNS << [:deprecated_preview_cards, :id] if table_exists?(:deprecated_preview_cards)
-
   def migrate_columns(to_type)
+    included_columns = [
+      [:account_domain_blocks, :account_id],
+      [:account_domain_blocks, :id],
+      [:accounts, :id],
+      [:blocks, :account_id],
+      [:blocks, :id],
+      [:blocks, :target_account_id],
+      [:conversation_mutes, :account_id],
+      [:conversation_mutes, :id],
+      [:domain_blocks, :id],
+      [:favourites, :account_id],
+      [:favourites, :id],
+      [:favourites, :status_id],
+      [:follow_requests, :account_id],
+      [:follow_requests, :id],
+      [:follow_requests, :target_account_id],
+      [:follows, :account_id],
+      [:follows, :id],
+      [:follows, :target_account_id],
+      [:imports, :account_id],
+      [:imports, :id],
+      [:media_attachments, :account_id],
+      [:media_attachments, :id],
+      [:mentions, :account_id],
+      [:mentions, :id],
+      [:mutes, :account_id],
+      [:mutes, :id],
+      [:mutes, :target_account_id],
+      [:notifications, :account_id],
+      [:notifications, :from_account_id],
+      [:notifications, :id],
+      [:oauth_access_grants, :application_id],
+      [:oauth_access_grants, :id],
+      [:oauth_access_grants, :resource_owner_id],
+      [:oauth_access_tokens, :application_id],
+      [:oauth_access_tokens, :id],
+      [:oauth_access_tokens, :resource_owner_id],
+      [:oauth_applications, :id],
+      [:oauth_applications, :owner_id],
+      [:reports, :account_id],
+      [:reports, :action_taken_by_account_id],
+      [:reports, :id],
+      [:reports, :target_account_id],
+      [:session_activations, :access_token_id],
+      [:session_activations, :user_id],
+      [:session_activations, :web_push_subscription_id],
+      [:settings, :id],
+      [:settings, :thing_id],
+      [:statuses, :account_id],
+      [:statuses, :application_id],
+      [:statuses, :in_reply_to_account_id],
+      [:stream_entries, :account_id],
+      [:stream_entries, :id],
+      [:subscriptions, :account_id],
+      [:subscriptions, :id],
+      [:tags, :id],
+      [:users, :account_id],
+      [:users, :id],
+      [:web_settings, :id],
+      [:web_settings, :user_id],
+    ]
+    included_columns << [:deprecated_preview_cards, :id] if table_exists?(:deprecated_preview_cards)
+
     # Print out a warning that this will probably take a while.
     say ''
     say 'WARNING: This migration may take a *long* time for large instances'
@@ -86,7 +86,7 @@ class IdsToBigints < ActiveRecord::Migration[5.1]
       sleep 1
     end
 
-    tables = INCLUDED_COLUMNS.map(&:first).uniq
+    tables = included_columns.map(&:first).uniq
     table_sizes = {}
 
     # Sort tables by their size
@@ -94,7 +94,7 @@ class IdsToBigints < ActiveRecord::Migration[5.1]
       table_sizes[table] = estimate_rows_in_table(table)
     end
 
-    ordered_columns = INCLUDED_COLUMNS.sort_by do |col_parts|
+    ordered_columns = included_columns.sort_by do |col_parts|
       [-table_sizes[col_parts.first], col_parts.last]
     end
 
diff --git a/db/migrate/20180528141303_fix_accounts_unique_index.rb b/db/migrate/20180528141303_fix_accounts_unique_index.rb
index bd4e158b7ecccc39892957ceab8b869b452d41b0..bbbf28d8175ddf9b9f720f91a74f3ca80e5deb4d 100644
--- a/db/migrate/20180528141303_fix_accounts_unique_index.rb
+++ b/db/migrate/20180528141303_fix_accounts_unique_index.rb
@@ -12,6 +12,11 @@ class FixAccountsUniqueIndex < ActiveRecord::Migration[5.2]
     end
   end
 
+  class StreamEntry < ApplicationRecord
+    # Dummy class, to make migration possible across version changes
+    belongs_to :account, inverse_of: :stream_entries
+  end
+
   disable_ddl_transaction!
 
   def up
diff --git a/db/migrate/20181024224956_migrate_account_conversations.rb b/db/migrate/20181024224956_migrate_account_conversations.rb
index b718f9e1d524e73782bc5824372307c8cc24944b..d4bc3b91d9245e7aa7455d96aac3d4cc22ad5452 100644
--- a/db/migrate/20181024224956_migrate_account_conversations.rb
+++ b/db/migrate/20181024224956_migrate_account_conversations.rb
@@ -52,6 +52,6 @@ class MigrateAccountConversations < ActiveRecord::Migration[5.2]
   end
 
   def notifications_about_direct_statuses
-    Notification.joins(mention: :status).where(activity_type: 'Mention', statuses: { visibility: :direct })
+    Notification.joins('INNER JOIN mentions ON mentions.id = notifications.activity_id INNER JOIN statuses ON statuses.id = mentions.status_id').where(activity_type: 'Mention', statuses: { visibility: :direct })
   end
 end
diff --git a/db/migrate/20190403141604_add_comment_to_invites.rb b/db/migrate/20190403141604_add_comment_to_invites.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f0d7b1dcdd3c39c9176073a1af1bed3cd11c954b
--- /dev/null
+++ b/db/migrate/20190403141604_add_comment_to_invites.rb
@@ -0,0 +1,5 @@
+class AddCommentToInvites < ActiveRecord::Migration[5.2]
+  def change
+    add_column :invites, :comment, :text
+  end
+end
diff --git a/db/migrate/20190627222225_create_custom_emoji_categories.rb b/db/migrate/20190627222225_create_custom_emoji_categories.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4713793e660568c8301d042755a5dfd03ef62b40
--- /dev/null
+++ b/db/migrate/20190627222225_create_custom_emoji_categories.rb
@@ -0,0 +1,9 @@
+class CreateCustomEmojiCategories < ActiveRecord::Migration[5.2]
+  def change
+    create_table :custom_emoji_categories do |t|
+      t.string :name, index: { unique: true }
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20190627222826_add_category_id_to_custom_emojis.rb b/db/migrate/20190627222826_add_category_id_to_custom_emojis.rb
new file mode 100644
index 0000000000000000000000000000000000000000..873b4d05fe10036abb7e9e0c3508fd59f8bf4fdf
--- /dev/null
+++ b/db/migrate/20190627222826_add_category_id_to_custom_emojis.rb
@@ -0,0 +1,5 @@
+class AddCategoryIdToCustomEmojis < ActiveRecord::Migration[5.2]
+  def change
+    add_column :custom_emojis, :category_id, :bigint
+  end
+end
diff --git a/db/migrate/20190701022101_add_trust_level_to_accounts.rb b/db/migrate/20190701022101_add_trust_level_to_accounts.rb
new file mode 100644
index 0000000000000000000000000000000000000000..917486d2ed7eb3e72f9f90db129054029bac142f
--- /dev/null
+++ b/db/migrate/20190701022101_add_trust_level_to_accounts.rb
@@ -0,0 +1,5 @@
+class AddTrustLevelToAccounts < ActiveRecord::Migration[5.2]
+  def change
+    add_column :accounts, :trust_level, :integer
+  end
+end
diff --git a/db/migrate/20190705002136_create_domain_allows.rb b/db/migrate/20190705002136_create_domain_allows.rb
new file mode 100644
index 0000000000000000000000000000000000000000..83b0728d9d5571baf8193eea66885ef46af4f9da
--- /dev/null
+++ b/db/migrate/20190705002136_create_domain_allows.rb
@@ -0,0 +1,9 @@
+class CreateDomainAllows < ActiveRecord::Migration[5.2]
+  def change
+    create_table :domain_allows do |t|
+      t.string :domain, default: '', null: false, index: { unique: true }
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20190715164535_add_instance_actor.rb b/db/migrate/20190715164535_add_instance_actor.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a26d54949320aa7aead2492b9a6a10fad4597eb3
--- /dev/null
+++ b/db/migrate/20190715164535_add_instance_actor.rb
@@ -0,0 +1,9 @@
+class AddInstanceActor < ActiveRecord::Migration[5.2]
+  def up
+    Account.create!(id: -99, actor_type: 'Application', locked: true, username: Rails.configuration.x.local_domain)
+  end
+
+  def down
+    Account.find_by(id: -99, actor_type: 'Application').destroy!
+  end
+end
diff --git a/db/migrate/20190726175042_add_case_insensitive_index_to_tags.rb b/db/migrate/20190726175042_add_case_insensitive_index_to_tags.rb
new file mode 100644
index 0000000000000000000000000000000000000000..057fc86baa8fd25b399cbdbc1ed1229f6dfd0848
--- /dev/null
+++ b/db/migrate/20190726175042_add_case_insensitive_index_to_tags.rb
@@ -0,0 +1,28 @@
+class AddCaseInsensitiveIndexToTags < ActiveRecord::Migration[5.2]
+  disable_ddl_transaction!
+
+  def up
+    Tag.connection.select_all('SELECT string_agg(id::text, \',\') AS ids FROM tags GROUP BY lower(name) HAVING count(*) > 1').to_hash.each do |row|
+      canonical_tag_id  = row['ids'].split(',').first
+      redundant_tag_ids = row['ids'].split(',')[1..-1]
+
+      safety_assured do
+        execute "UPDATE accounts_tags AS t0 SET tag_id = #{canonical_tag_id} WHERE tag_id IN (#{redundant_tag_ids.join(', ')}) AND NOT EXISTS (SELECT t1.tag_id FROM accounts_tags AS t1 WHERE t1.tag_id = #{canonical_tag_id} AND t1.account_id = t0.account_id)"
+        execute "UPDATE statuses_tags AS t0 SET tag_id = #{canonical_tag_id} WHERE tag_id IN (#{redundant_tag_ids.join(', ')}) AND NOT EXISTS (SELECT t1.tag_id FROM statuses_tags AS t1 WHERE t1.tag_id = #{canonical_tag_id} AND t1.status_id = t0.status_id)"
+        execute "UPDATE featured_tags AS t0 SET tag_id = #{canonical_tag_id} WHERE tag_id IN (#{redundant_tag_ids.join(', ')})  AND NOT EXISTS (SELECT t1.tag_id FROM featured_tags AS t1 WHERE t1.tag_id = #{canonical_tag_id} AND t1.account_id = t0.account_id)"
+      end
+
+      Tag.where(id: redundant_tag_ids).in_batches.delete_all
+    end
+
+    safety_assured { execute 'CREATE UNIQUE INDEX CONCURRENTLY index_tags_on_name_lower ON tags (lower(name))' }
+    remove_index :tags, name: 'index_tags_on_name'
+    remove_index :tags, name: 'hashtag_search_index'
+  end
+
+  def down
+    add_index :tags, :name, unique: true, algorithm: :concurrently
+    safety_assured { execute 'CREATE INDEX CONCURRENTLY hashtag_search_index ON tags (name text_pattern_ops)' }
+    remove_index :tags, name: 'index_tags_on_name_lower'
+  end
+end
diff --git a/db/migrate/20190729185330_add_score_to_tags.rb b/db/migrate/20190729185330_add_score_to_tags.rb
new file mode 100644
index 0000000000000000000000000000000000000000..75fee4b57052c1dc14248532dedf1164acfc35d5
--- /dev/null
+++ b/db/migrate/20190729185330_add_score_to_tags.rb
@@ -0,0 +1,5 @@
+class AddScoreToTags < ActiveRecord::Migration[5.2]
+  def change
+    add_column :tags, :score, :int
+  end
+end
diff --git a/db/migrate/20190805123746_add_capabilities_to_tags.rb b/db/migrate/20190805123746_add_capabilities_to_tags.rb
new file mode 100644
index 0000000000000000000000000000000000000000..43c7763b1bf0ac1e44de9f20c783a2ca7d11dd39
--- /dev/null
+++ b/db/migrate/20190805123746_add_capabilities_to_tags.rb
@@ -0,0 +1,9 @@
+class AddCapabilitiesToTags < ActiveRecord::Migration[5.2]
+  def change
+    add_column :tags, :usable, :boolean
+    add_column :tags, :trendable, :boolean
+    add_column :tags, :listable, :boolean
+    add_column :tags, :reviewed_at, :datetime
+    add_column :tags, :requested_review_at, :datetime
+  end
+end
diff --git a/db/migrate/20190807135426_add_comments_to_domain_blocks.rb b/db/migrate/20190807135426_add_comments_to_domain_blocks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b660a71adb92d33b7a28342fd77b9d1a9ae1659d
--- /dev/null
+++ b/db/migrate/20190807135426_add_comments_to_domain_blocks.rb
@@ -0,0 +1,7 @@
+class AddCommentsToDomainBlocks < ActiveRecord::Migration[5.2]
+  def change
+    add_column :domain_blocks, :private_comment, :text
+    add_column :domain_blocks, :public_comment, :text
+  end
+end
+
diff --git a/db/migrate/20190815225426_add_last_status_at_to_tags.rb b/db/migrate/20190815225426_add_last_status_at_to_tags.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d83537c47f9e8282f72b54c8294a3d36009577a6
--- /dev/null
+++ b/db/migrate/20190815225426_add_last_status_at_to_tags.rb
@@ -0,0 +1,6 @@
+class AddLastStatusAtToTags < ActiveRecord::Migration[5.2]
+  def change
+    add_column :tags, :last_status_at, :datetime
+    add_column :tags, :last_trend_at, :datetime
+  end
+end
diff --git a/db/migrate/20190819134503_add_deleted_at_to_statuses.rb b/db/migrate/20190819134503_add_deleted_at_to_statuses.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5af109097e8b10afa8afe9e2658bc9bfd373c01f
--- /dev/null
+++ b/db/migrate/20190819134503_add_deleted_at_to_statuses.rb
@@ -0,0 +1,5 @@
+class AddDeletedAtToStatuses < ActiveRecord::Migration[5.2]
+  def change
+    add_column :statuses, :deleted_at, :datetime
+  end
+end
diff --git a/db/migrate/20190820003045_update_statuses_index.rb b/db/migrate/20190820003045_update_statuses_index.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5c2ea1f6a2416461cb0d4f787665ccc88b5c254d
--- /dev/null
+++ b/db/migrate/20190820003045_update_statuses_index.rb
@@ -0,0 +1,13 @@
+class UpdateStatusesIndex < ActiveRecord::Migration[5.2]
+  disable_ddl_transaction!
+
+  def up
+    safety_assured { add_index :statuses, [:account_id, :id, :visibility, :updated_at], where: 'deleted_at IS NULL', order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20190820 }
+    remove_index :statuses, name: :index_statuses_20180106
+  end
+
+  def down
+    safety_assured { add_index :statuses, [:account_id, :id, :visibility, :updated_at], order: { id: :desc }, algorithm: :concurrently, name: :index_statuses_20180106 }
+    remove_index :statuses, name: :index_statuses_20190820
+  end
+end
diff --git a/db/migrate/20190823221802_add_local_index_to_statuses.rb b/db/migrate/20190823221802_add_local_index_to_statuses.rb
new file mode 100644
index 0000000000000000000000000000000000000000..deca25c3515ef17c1853ebc5533ec310c2f8bd6c
--- /dev/null
+++ b/db/migrate/20190823221802_add_local_index_to_statuses.rb
@@ -0,0 +1,11 @@
+class AddLocalIndexToStatuses < ActiveRecord::Migration[5.2]
+  disable_ddl_transaction!
+
+  def up
+    add_index :statuses, [:id, :account_id], name: :index_statuses_local_20190824, algorithm: :concurrently, order: { id: :desc }, where: '(local OR (uri IS NULL)) AND deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))'
+  end
+
+  def down
+    remove_index :statuses, name: :index_statuses_local_20190824
+  end
+end
diff --git a/db/migrate/20190901035623_add_max_score_to_tags.rb b/db/migrate/20190901035623_add_max_score_to_tags.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f936e987182f26a0b3e00bda3e4d49fa8f8d3ca3
--- /dev/null
+++ b/db/migrate/20190901035623_add_max_score_to_tags.rb
@@ -0,0 +1,6 @@
+class AddMaxScoreToTags < ActiveRecord::Migration[5.2]
+  def change
+    add_column :tags, :max_score, :float
+    add_column :tags, :max_score_at, :datetime
+  end
+end
diff --git a/db/migrate/20190904222339_create_markers.rb b/db/migrate/20190904222339_create_markers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..71ca70ac3d07af5c8b3d3dc967df2f4d5b668209
--- /dev/null
+++ b/db/migrate/20190904222339_create_markers.rb
@@ -0,0 +1,14 @@
+class CreateMarkers < ActiveRecord::Migration[5.2]
+  def change
+    create_table :markers do |t|
+      t.references :user, foreign_key: { on_delete: :cascade, index: false }
+      t.string :timeline, default: '', null: false
+      t.bigint :last_read_id, default: 0, null: false
+      t.integer :lock_version, default: 0, null: false
+
+      t.timestamps
+    end
+
+    add_index :markers, [:user_id, :timeline], unique: true
+  end
+end
diff --git a/db/migrate/20190914202517_create_account_migrations.rb b/db/migrate/20190914202517_create_account_migrations.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cb9d71c0964302f8c5cc63b02c11fad2da00bef4
--- /dev/null
+++ b/db/migrate/20190914202517_create_account_migrations.rb
@@ -0,0 +1,12 @@
+class CreateAccountMigrations < ActiveRecord::Migration[5.2]
+  def change
+    create_table :account_migrations do |t|
+      t.belongs_to :account, foreign_key: { on_delete: :cascade }
+      t.string :acct, null: false, default: ''
+      t.bigint :followers_count, null: false, default: 0
+      t.belongs_to :target_account, foreign_key: { to_table: :accounts, on_delete: :nullify }
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20190915194355_create_account_aliases.rb b/db/migrate/20190915194355_create_account_aliases.rb
new file mode 100644
index 0000000000000000000000000000000000000000..32ce031d91884c58c3165cfd86c663ec9fdd1120
--- /dev/null
+++ b/db/migrate/20190915194355_create_account_aliases.rb
@@ -0,0 +1,11 @@
+class CreateAccountAliases < ActiveRecord::Migration[5.2]
+  def change
+    create_table :account_aliases do |t|
+      t.belongs_to :account, foreign_key: { on_delete: :cascade }
+      t.string :acct, null: false, default: ''
+      t.string :uri, null: false, default: ''
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20190917213523_add_remember_token_index.rb b/db/migrate/20190917213523_add_remember_token_index.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c5b41ce644651cfe27a9ab3a3f9f0021da1063e3
--- /dev/null
+++ b/db/migrate/20190917213523_add_remember_token_index.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddRememberTokenIndex < ActiveRecord::Migration[5.2]
+  disable_ddl_transaction!
+
+  def change
+    add_index :users, :remember_token, algorithm: :concurrently, unique: true
+  end
+end
diff --git a/db/migrate/20190927232842_add_voters_count_to_polls.rb b/db/migrate/20190927232842_add_voters_count_to_polls.rb
new file mode 100644
index 0000000000000000000000000000000000000000..84638570090e28302133f2827c89d8039e966424
--- /dev/null
+++ b/db/migrate/20190927232842_add_voters_count_to_polls.rb
@@ -0,0 +1,5 @@
+class AddVotersCountToPolls < ActiveRecord::Migration[5.2]
+  def change
+    add_column :polls, :voters_count, :bigint
+  end
+end
diff --git a/db/migrate/20191001213028_add_lock_version_to_account_stats.rb b/db/migrate/20191001213028_add_lock_version_to_account_stats.rb
new file mode 100644
index 0000000000000000000000000000000000000000..47f37cca29d8042e0f3700cca350f091d1c6733f
--- /dev/null
+++ b/db/migrate/20191001213028_add_lock_version_to_account_stats.rb
@@ -0,0 +1,15 @@
+require Rails.root.join('lib', 'mastodon', 'migration_helpers')
+
+class AddLockVersionToAccountStats < ActiveRecord::Migration[5.2]
+  include Mastodon::MigrationHelpers
+
+  disable_ddl_transaction!
+
+  def up
+    safety_assured { add_column_with_default :account_stats, :lock_version, :integer, allow_null: false, default: 0 }
+  end
+
+  def down
+    remove_column :account_stats, :lock_version
+  end
+end
diff --git a/db/migrate/20191007013357_update_pt_locales.rb b/db/migrate/20191007013357_update_pt_locales.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b7288d38a0d7c88b9ef01e06fc9a188d05472718
--- /dev/null
+++ b/db/migrate/20191007013357_update_pt_locales.rb
@@ -0,0 +1,11 @@
+class UpdatePtLocales < ActiveRecord::Migration[5.2]
+  disable_ddl_transaction!
+
+  def up
+    User.where(locale: 'pt').in_batches.update_all(locale: 'pt-PT')
+  end
+
+  def down
+    User.where(locale: 'pt-PT').in_batches.update_all(locale: 'pt')
+  end
+end
diff --git a/db/post_migrate/20190706233204_drop_stream_entries.rb b/db/post_migrate/20190706233204_drop_stream_entries.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1fecece0551828fbff60adfea7b64226a9def429
--- /dev/null
+++ b/db/post_migrate/20190706233204_drop_stream_entries.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class DropStreamEntries < ActiveRecord::Migration[5.2]
+  disable_ddl_transaction!
+
+  def up
+    drop_table :stream_entries
+  end
+
+  def down
+    raise ActiveRecord::IrreversibleMigration
+  end
+end
diff --git a/db/post_migrate/20190715031050_drop_subscriptions.rb b/db/post_migrate/20190715031050_drop_subscriptions.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3719afe4a0cbb9977041028d5c8bcc22878c04b1
--- /dev/null
+++ b/db/post_migrate/20190715031050_drop_subscriptions.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class DropSubscriptions < ActiveRecord::Migration[5.2]
+  def up
+    drop_table :subscriptions
+  end
+
+  def down
+    raise ActiveRecord::IrreversibleMigration
+  end
+end
diff --git a/db/post_migrate/20190901040524_remove_score_from_tags.rb b/db/post_migrate/20190901040524_remove_score_from_tags.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a1112700b5d51e771190efa87b143899e65e5719
--- /dev/null
+++ b/db/post_migrate/20190901040524_remove_score_from_tags.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class RemoveScoreFromTags < ActiveRecord::Migration[5.2]
+  disable_ddl_transaction!
+
+  def change
+    safety_assured do
+      remove_column :tags, :score, :int
+      remove_column :tags, :last_trend_at, :datetime
+    end
+  end
+end
diff --git a/db/post_migrate/20190927124642_remove_invalid_web_push_subscription.rb b/db/post_migrate/20190927124642_remove_invalid_web_push_subscription.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c2397476ad0749408d742605d4b53d1184f38f0e
--- /dev/null
+++ b/db/post_migrate/20190927124642_remove_invalid_web_push_subscription.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemoveInvalidWebPushSubscription < ActiveRecord::Migration[5.2]
+  disable_ddl_transaction!
+
+  def up
+    invalid_web_push_subscriptions = Web::PushSubscription.where(endpoint: '')
+                                                          .or(Web::PushSubscription.where(key_p256dh: ''))
+                                                          .or(Web::PushSubscription.where(key_auth: ''))
+                                                          .preload(:session_activation)
+    invalid_web_push_subscriptions.find_each do |web_push_subscription|
+      web_push_subscription.session_activation&.update!(web_push_subscription_id: nil)
+      web_push_subscription.destroy!
+    end
+  end
+
+  def down; end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f5a0448114d1da7d3d616b9e46587d0dbf8f7e6e..cd1f26281c5134d3ff7b19f2336645ed4d9c09e0 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,11 +10,20 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2019_05_29_143559) do
+ActiveRecord::Schema.define(version: 2019_10_07_013357) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
 
+  create_table "account_aliases", force: :cascade do |t|
+    t.bigint "account_id"
+    t.string "acct", default: "", null: false
+    t.string "uri", default: "", null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["account_id"], name: "index_account_aliases_on_account_id"
+  end
+
   create_table "account_conversations", force: :cascade do |t|
     t.bigint "account_id"
     t.bigint "conversation_id"
@@ -49,6 +58,17 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.index ["account_id"], name: "index_account_identity_proofs_on_account_id"
   end
 
+  create_table "account_migrations", force: :cascade do |t|
+    t.bigint "account_id"
+    t.string "acct", default: "", null: false
+    t.bigint "followers_count", default: 0, null: false
+    t.bigint "target_account_id"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["account_id"], name: "index_account_migrations_on_account_id"
+    t.index ["target_account_id"], name: "index_account_migrations_on_target_account_id"
+  end
+
   create_table "account_moderation_notes", force: :cascade do |t|
     t.text "content", null: false
     t.bigint "account_id", null: false
@@ -77,6 +97,7 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
     t.datetime "last_status_at"
+    t.integer "lock_version", default: 0, null: false
     t.index ["account_id"], name: "index_account_stats_on_account_id", unique: true
   end
 
@@ -148,6 +169,7 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.string "also_known_as", array: true
     t.datetime "silenced_at"
     t.datetime "suspended_at"
+    t.integer "trust_level"
     t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
     t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", unique: true
     t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id"
@@ -208,6 +230,13 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.index ["uri"], name: "index_conversations_on_uri", unique: true
   end
 
+  create_table "custom_emoji_categories", force: :cascade do |t|
+    t.string "name"
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["name"], name: "index_custom_emoji_categories_on_name", unique: true
+  end
+
   create_table "custom_emojis", force: :cascade do |t|
     t.string "shortcode", default: "", null: false
     t.string "domain"
@@ -221,6 +250,7 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.string "uri"
     t.string "image_remote_url"
     t.boolean "visible_in_picker", default: true, null: false
+    t.bigint "category_id"
     t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true
   end
 
@@ -236,6 +266,13 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.index ["account_id"], name: "index_custom_filters_on_account_id"
   end
 
+  create_table "domain_allows", force: :cascade do |t|
+    t.string "domain", default: "", null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["domain"], name: "index_domain_allows_on_domain", unique: true
+  end
+
   create_table "domain_blocks", force: :cascade do |t|
     t.string "domain", default: "", null: false
     t.datetime "created_at", null: false
@@ -243,6 +280,8 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.integer "severity", default: 0
     t.boolean "reject_media", default: false, null: false
     t.boolean "reject_reports", default: false, null: false
+    t.text "private_comment"
+    t.text "public_comment"
     t.index ["domain"], name: "index_domain_blocks_on_domain", unique: true
   end
 
@@ -326,6 +365,7 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
     t.boolean "autofollow", default: false, null: false
+    t.text "comment"
     t.index ["code"], name: "index_invites_on_code", unique: true
     t.index ["user_id"], name: "index_invites_on_user_id"
   end
@@ -347,6 +387,17 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.index ["account_id"], name: "index_lists_on_account_id"
   end
 
+  create_table "markers", force: :cascade do |t|
+    t.bigint "user_id"
+    t.string "timeline", default: "", null: false
+    t.bigint "last_read_id", default: 0, null: false
+    t.integer "lock_version", default: 0, null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["user_id", "timeline"], name: "index_markers_on_user_id_and_timeline", unique: true
+    t.index ["user_id"], name: "index_markers_on_user_id"
+  end
+
   create_table "media_attachments", force: :cascade do |t|
     t.bigint "status_id"
     t.string "file_file_name"
@@ -479,6 +530,7 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
     t.integer "lock_version", default: 0, null: false
+    t.bigint "voters_count"
     t.index ["account_id"], name: "index_polls_on_account_id"
     t.index ["status_id"], name: "index_polls_on_status_id"
   end
@@ -625,8 +677,10 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.bigint "application_id"
     t.bigint "in_reply_to_account_id"
     t.bigint "poll_id"
+    t.datetime "deleted_at"
     t.boolean "local_only"
-    t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20180106", order: { id: :desc }
+    t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)"
+    t.index ["id", "account_id"], name: "index_statuses_local_20190824", order: { id: :desc }, where: "((local OR (uri IS NULL)) AND (deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
     t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id"
     t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id"
     t.index ["reblog_of_id", "account_id"], name: "index_statuses_on_reblog_of_id_and_account_id"
@@ -668,8 +722,15 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.string "name", default: "", null: false
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
-    t.index "lower((name)::text) text_pattern_ops", name: "hashtag_search_index"
-    t.index ["name"], name: "index_tags_on_name", unique: true
+    t.boolean "usable"
+    t.boolean "trendable"
+    t.boolean "listable"
+    t.datetime "reviewed_at"
+    t.datetime "requested_review_at"
+    t.datetime "last_status_at"
+    t.float "max_score"
+    t.datetime "max_score_at"
+    t.index "lower((name)::text)", name: "index_tags_on_name_lower", unique: true
   end
 
   create_table "tombstones", force: :cascade do |t|
@@ -729,6 +790,7 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
     t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id"
     t.index ["email"], name: "index_users_on_email", unique: true
+    t.index ["remember_token"], name: "index_users_on_remember_token", unique: true
     t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
   end
 
@@ -753,10 +815,13 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
     t.index ["user_id"], name: "index_web_settings_on_user_id", unique: true
   end
 
+  add_foreign_key "account_aliases", "accounts", on_delete: :cascade
   add_foreign_key "account_conversations", "accounts", on_delete: :cascade
   add_foreign_key "account_conversations", "conversations", on_delete: :cascade
   add_foreign_key "account_domain_blocks", "accounts", name: "fk_206c6029bd", on_delete: :cascade
   add_foreign_key "account_identity_proofs", "accounts", on_delete: :cascade
+  add_foreign_key "account_migrations", "accounts", column: "target_account_id", on_delete: :nullify
+  add_foreign_key "account_migrations", "accounts", on_delete: :cascade
   add_foreign_key "account_moderation_notes", "accounts"
   add_foreign_key "account_moderation_notes", "accounts", column: "target_account_id"
   add_foreign_key "account_pins", "accounts", column: "target_account_id", on_delete: :cascade
@@ -788,6 +853,7 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
   add_foreign_key "list_accounts", "follows", on_delete: :cascade
   add_foreign_key "list_accounts", "lists", on_delete: :cascade
   add_foreign_key "lists", "accounts", on_delete: :cascade
+  add_foreign_key "markers", "users", on_delete: :cascade
   add_foreign_key "media_attachments", "accounts", name: "fk_96dd81e81b", on_delete: :nullify
   add_foreign_key "media_attachments", "scheduled_statuses", on_delete: :nullify
   add_foreign_key "media_attachments", "statuses", on_delete: :nullify
@@ -824,8 +890,6 @@ ActiveRecord::Schema.define(version: 2019_05_29_143559) do
   add_foreign_key "statuses", "statuses", column: "reblog_of_id", on_delete: :cascade
   add_foreign_key "statuses_tags", "statuses", on_delete: :cascade
   add_foreign_key "statuses_tags", "tags", name: "fk_3081861e21", on_delete: :cascade
-  add_foreign_key "stream_entries", "accounts", name: "fk_5659b17554", on_delete: :cascade
-  add_foreign_key "subscriptions", "accounts", name: "fk_9847d1cbb5", on_delete: :cascade
   add_foreign_key "tombstones", "accounts", on_delete: :cascade
   add_foreign_key "user_invite_requests", "users", on_delete: :cascade
   add_foreign_key "users", "accounts", name: "fk_50500f500d", on_delete: :cascade
diff --git a/db/seeds.rb b/db/seeds.rb
index 9a6e9dd78ed1edf0e061ff29b91670181eb469ab..0bfb5d0db5a709b1e432a86600ac1d6133844d9d 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -1,7 +1,10 @@
-Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow')
+Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push')
+
+domain = ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain
+account = Account.find_or_initialize_by(id: -99, actor_type: 'Application', locked: true, username: domain)
+account.save!
 
 if Rails.env.development?
-  domain = ENV['LOCAL_DOMAIN'] || Rails.configuration.x.local_domain
   admin  = Account.where(username: 'admin').first_or_initialize(username: 'admin')
   admin.save(validate: false)
   User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, admin: true, account: admin, agreement: true, approved: true).save!
diff --git a/dist/nginx.conf b/dist/nginx.conf
index 7c429bad42e5b25127db73d487968dd66e4e62e8..b6591e8976d13c7f3fdc11fa94ce7e0356b579b5 100644
--- a/dist/nginx.conf
+++ b/dist/nginx.conf
@@ -19,7 +19,7 @@ server {
   listen [::]:443 ssl http2;
   server_name example.com;
 
-  ssl_protocols TLSv1.2;
+  ssl_protocols TLSv1.2 TLSv1.3;
   ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
   ssl_prefer_server_ciphers on;
   ssl_session_cache shared:SSL:10m;
diff --git a/docker-compose.yml b/docker-compose.yml
index 7406849663e61280d44fcbfda16176692eaaee65..20649e4245c06749c836d7cdef36baaecfa424d2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -43,7 +43,7 @@ services:
       - external_network
       - internal_network
     healthcheck:
-      test: ["CMD-SHELL", "wget -q --spider --header 'x-forwarded-proto: https' --proxy=off localhost:3000/api/v1/instance || exit 1"]
+      test: ["CMD-SHELL", "wget -q --spider --proxy=off localhost:3000/health || exit 1"]
     ports:
       - "127.0.0.1:3000:3000"
     depends_on:
@@ -63,7 +63,7 @@ services:
       - external_network
       - internal_network
     healthcheck:
-      test: ["CMD-SHELL", "wget -q --spider --header 'x-forwarded-proto: https' --proxy=off localhost:4000/api/v1/streaming/health || exit 1"]
+      test: ["CMD-SHELL", "wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1"]
     ports:
       - "127.0.0.1:4000:4000"
     depends_on:
diff --git a/lib/chewy/strategy/custom_sidekiq.rb b/lib/chewy/strategy/custom_sidekiq.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3e54326ba8b31b01c76059f7495f0aa54f0b33db
--- /dev/null
+++ b/lib/chewy/strategy/custom_sidekiq.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Chewy
+  class Strategy
+    class CustomSidekiq < Base
+      class Worker
+        include ::Sidekiq::Worker
+
+        sidekiq_options queue: 'pull'
+
+        def perform(type, ids, options = {})
+          options[:refresh] = !Chewy.disable_refresh_async if Chewy.disable_refresh_async
+          type.constantize.import!(ids, options)
+        end
+      end
+
+      def update(type, objects, _options = {})
+        return unless Chewy.enabled?
+
+        ids = type.root.id ? Array.wrap(objects) : type.adapter.identify(objects)
+
+        return if ids.empty?
+
+        Worker.perform_async(type.name, ids)
+      end
+
+      def leave; end
+    end
+  end
+end
diff --git a/lib/cli.rb b/lib/cli.rb
index be276583d22568659eaa77aabfaf313875f1867b..fbdf49fc34a6766640382a7c10521337cb229fbd 100644
--- a/lib/cli.rb
+++ b/lib/cli.rb
@@ -9,6 +9,7 @@ require_relative 'mastodon/search_cli'
 require_relative 'mastodon/settings_cli'
 require_relative 'mastodon/statuses_cli'
 require_relative 'mastodon/domains_cli'
+require_relative 'mastodon/preview_cards_cli'
 require_relative 'mastodon/cache_cli'
 require_relative 'mastodon/version'
 
@@ -42,6 +43,9 @@ module Mastodon
     desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains'
     subcommand 'domains', Mastodon::DomainsCLI
 
+    desc 'preview_cards SUBCOMMAND ...ARGS', 'Manage preview cards'
+    subcommand 'preview_cards', Mastodon::PreviewCardsCLI
+
     desc 'cache SUBCOMMAND ...ARGS', 'Manage cache'
     subcommand 'cache', Mastodon::CacheCLI
 
diff --git a/lib/devise/ldap_authenticatable.rb b/lib/devise/ldap_authenticatable.rb
deleted file mode 100644
index 6903d468dc4958a8d2cf504f626e3f3871591be4..0000000000000000000000000000000000000000
--- a/lib/devise/ldap_authenticatable.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require 'net/ldap'
-require 'devise/strategies/authenticatable'
-
-module Devise
-  module Strategies
-    class LdapAuthenticatable < Authenticatable
-      def authenticate!
-        if params[:user]
-          ldap = Net::LDAP.new(
-            host: Devise.ldap_host,
-            port: Devise.ldap_port,
-            base: Devise.ldap_base,
-            encryption: {
-              method: Devise.ldap_method,
-              tls_options: tls_options,
-            },
-            auth: {
-              method: :simple,
-              username: Devise.ldap_bind_dn,
-              password: Devise.ldap_password,
-            },
-            connect_timeout: 10
-          )
-
-          filter = format(Devise.ldap_search_filter, uid: Devise.ldap_uid, email: email)
-
-          if (user_info = ldap.bind_as(base: Devise.ldap_base, filter: filter, password: password))
-            user = User.ldap_get_user(user_info.first)
-            success!(user)
-          else
-            return fail(:invalid)
-          end
-        end
-      end
-
-      def email
-        params[:user][:email]
-      end
-
-      def password
-        params[:user][:password]
-      end
-
-      def tls_options
-        OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.tap do |options|
-          options[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if Devise.ldap_tls_no_verify
-        end
-      end
-    end
-  end
-end
-
-Warden::Strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable)
diff --git a/lib/devise/two_factor_ldap_authenticatable.rb b/lib/devise/two_factor_ldap_authenticatable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..065aa2de88acab28682cfab4680d0239b994aa04
--- /dev/null
+++ b/lib/devise/two_factor_ldap_authenticatable.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'net/ldap'
+require 'devise/strategies/base'
+
+module Devise
+  module Strategies
+    class TwoFactorLdapAuthenticatable < Base
+      def valid?
+        valid_params? && mapping.to.respond_to?(:authenticate_with_ldap)
+      end
+
+      def authenticate!
+        resource = mapping.to.authenticate_with_ldap(params[scope])
+
+        if resource && !resource.otp_required_for_login?
+          success!(resource)
+        else
+          fail(:invalid)
+        end
+      end
+
+      protected
+
+      def valid_params?
+        params[scope] && params[scope][:password].present?
+      end
+    end
+  end
+end
+
+Warden::Strategies.add(:two_factor_ldap_authenticatable, Devise::Strategies::TwoFactorLdapAuthenticatable)
diff --git a/lib/devise/two_factor_pam_authenticatable.rb b/lib/devise/two_factor_pam_authenticatable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5ce723b331d319217ad02a34250ee294f9ce39e4
--- /dev/null
+++ b/lib/devise/two_factor_pam_authenticatable.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'devise/strategies/base'
+
+module Devise
+  module Strategies
+    class TwoFactorPamAuthenticatable < Base
+      def valid?
+        valid_params? && mapping.to.respond_to?(:authenticate_with_pam)
+      end
+
+      def authenticate!
+        resource = mapping.to.authenticate_with_pam(params[scope])
+
+        if resource && !resource.otp_required_for_login?
+          success!(resource)
+        else
+          fail(:invalid)
+        end
+      end
+
+      protected
+
+      def valid_params?
+        params[scope] && params[scope][:password].present?
+      end
+    end
+  end
+end
+
+Warden::Strategies.add(:two_factor_pam_authenticatable, Devise::Strategies::TwoFactorPamAuthenticatable)
diff --git a/lib/json_ld/identity.rb b/lib/json_ld/identity.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4fb3f8e9d9c20114665d57f8f7213ef4faf0ac26
--- /dev/null
+++ b/lib/json_ld/identity.rb
@@ -0,0 +1,87 @@
+# -*- encoding: utf-8 -*-
+# frozen_string_literal: true
+# This file generated automatically from http://w3id.org/identity/v1
+require 'json/ld'
+class JSON::LD::Context
+  add_preloaded("http://w3id.org/identity/v1") do
+    new(term_definitions: {
+      "Credential" => TermDefinition.new("Credential", id: "https://w3id.org/credentials#Credential", simple: true),
+      "CryptographicKey" => TermDefinition.new("CryptographicKey", id: "https://w3id.org/security#Key", simple: true),
+      "CryptographicKeyCredential" => TermDefinition.new("CryptographicKeyCredential", id: "https://w3id.org/credentials#CryptographicKeyCredential", simple: true),
+      "EncryptedMessage" => TermDefinition.new("EncryptedMessage", id: "https://w3id.org/security#EncryptedMessage", simple: true),
+      "GraphSignature2012" => TermDefinition.new("GraphSignature2012", id: "https://w3id.org/security#GraphSignature2012", simple: true),
+      "Group" => TermDefinition.new("Group", id: "https://www.w3.org/ns/activitystreams#Group", simple: true),
+      "Identity" => TermDefinition.new("Identity", id: "https://w3id.org/identity#Identity", simple: true),
+      "LinkedDataSignature2015" => TermDefinition.new("LinkedDataSignature2015", id: "https://w3id.org/security#LinkedDataSignature2015", simple: true),
+      "Organization" => TermDefinition.new("Organization", id: "http://schema.org/Organization", simple: true),
+      "Person" => TermDefinition.new("Person", id: "http://schema.org/Person", simple: true),
+      "PostalAddress" => TermDefinition.new("PostalAddress", id: "http://schema.org/PostalAddress", simple: true),
+      "about" => TermDefinition.new("about", id: "http://schema.org/about", type_mapping: "@id"),
+      "accessControl" => TermDefinition.new("accessControl", id: "https://w3id.org/permissions#accessControl", type_mapping: "@id"),
+      "address" => TermDefinition.new("address", id: "http://schema.org/address", type_mapping: "@id"),
+      "addressCountry" => TermDefinition.new("addressCountry", id: "http://schema.org/addressCountry", simple: true),
+      "addressLocality" => TermDefinition.new("addressLocality", id: "http://schema.org/addressLocality", simple: true),
+      "addressRegion" => TermDefinition.new("addressRegion", id: "http://schema.org/addressRegion", simple: true),
+      "cipherAlgorithm" => TermDefinition.new("cipherAlgorithm", id: "https://w3id.org/security#cipherAlgorithm", simple: true),
+      "cipherData" => TermDefinition.new("cipherData", id: "https://w3id.org/security#cipherData", simple: true),
+      "cipherKey" => TermDefinition.new("cipherKey", id: "https://w3id.org/security#cipherKey", simple: true),
+      "claim" => TermDefinition.new("claim", id: "https://w3id.org/credentials#claim", type_mapping: "@id"),
+      "comment" => TermDefinition.new("comment", id: "http://www.w3.org/2000/01/rdf-schema#comment", simple: true),
+      "created" => TermDefinition.new("created", id: "http://purl.org/dc/terms/created", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
+      "creator" => TermDefinition.new("creator", id: "http://purl.org/dc/terms/creator", type_mapping: "@id"),
+      "cred" => TermDefinition.new("cred", id: "https://w3id.org/credentials#", simple: true, prefix: true),
+      "credential" => TermDefinition.new("credential", id: "https://w3id.org/credentials#credential", type_mapping: "@id"),
+      "dc" => TermDefinition.new("dc", id: "http://purl.org/dc/terms/", simple: true, prefix: true),
+      "description" => TermDefinition.new("description", id: "http://schema.org/description", simple: true),
+      "digestAlgorithm" => TermDefinition.new("digestAlgorithm", id: "https://w3id.org/security#digestAlgorithm", simple: true),
+      "digestValue" => TermDefinition.new("digestValue", id: "https://w3id.org/security#digestValue", simple: true),
+      "domain" => TermDefinition.new("domain", id: "https://w3id.org/security#domain", simple: true),
+      "email" => TermDefinition.new("email", id: "http://schema.org/email", simple: true),
+      "expires" => TermDefinition.new("expires", id: "https://w3id.org/security#expiration", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
+      "familyName" => TermDefinition.new("familyName", id: "http://schema.org/familyName", simple: true),
+      "givenName" => TermDefinition.new("givenName", id: "http://schema.org/givenName", simple: true),
+      "id" => TermDefinition.new("id", id: "@id", simple: true),
+      "identity" => TermDefinition.new("identity", id: "https://w3id.org/identity#", simple: true, prefix: true),
+      "identityService" => TermDefinition.new("identityService", id: "https://w3id.org/identity#identityService", type_mapping: "@id"),
+      "idp" => TermDefinition.new("idp", id: "https://w3id.org/identity#idp", type_mapping: "@id"),
+      "image" => TermDefinition.new("image", id: "http://schema.org/image", type_mapping: "@id"),
+      "initializationVector" => TermDefinition.new("initializationVector", id: "https://w3id.org/security#initializationVector", simple: true),
+      "issued" => TermDefinition.new("issued", id: "https://w3id.org/credentials#issued", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
+      "issuer" => TermDefinition.new("issuer", id: "https://w3id.org/credentials#issuer", type_mapping: "@id"),
+      "label" => TermDefinition.new("label", id: "http://www.w3.org/2000/01/rdf-schema#label", simple: true),
+      "member" => TermDefinition.new("member", id: "http://schema.org/member", type_mapping: "@id"),
+      "memberOf" => TermDefinition.new("memberOf", id: "http://schema.org/memberOf", type_mapping: "@id"),
+      "name" => TermDefinition.new("name", id: "http://schema.org/name", simple: true),
+      "nonce" => TermDefinition.new("nonce", id: "https://w3id.org/security#nonce", simple: true),
+      "normalizationAlgorithm" => TermDefinition.new("normalizationAlgorithm", id: "https://w3id.org/security#normalizationAlgorithm", simple: true),
+      "owner" => TermDefinition.new("owner", id: "https://w3id.org/security#owner", type_mapping: "@id"),
+      "password" => TermDefinition.new("password", id: "https://w3id.org/security#password", simple: true),
+      "paymentProcessor" => TermDefinition.new("paymentProcessor", id: "https://w3id.org/payswarm#processor", simple: true),
+      "perm" => TermDefinition.new("perm", id: "https://w3id.org/permissions#", simple: true, prefix: true),
+      "postalCode" => TermDefinition.new("postalCode", id: "http://schema.org/postalCode", simple: true),
+      "preferences" => TermDefinition.new("preferences", id: "https://w3id.org/payswarm#preferences", type_mapping: "@vocab"),
+      "privateKey" => TermDefinition.new("privateKey", id: "https://w3id.org/security#privateKey", type_mapping: "@id"),
+      "privateKeyPem" => TermDefinition.new("privateKeyPem", id: "https://w3id.org/security#privateKeyPem", simple: true),
+      "ps" => TermDefinition.new("ps", id: "https://w3id.org/payswarm#", simple: true, prefix: true),
+      "publicKey" => TermDefinition.new("publicKey", id: "https://w3id.org/security#publicKey", type_mapping: "@id"),
+      "publicKeyPem" => TermDefinition.new("publicKeyPem", id: "https://w3id.org/security#publicKeyPem", simple: true),
+      "publicKeyService" => TermDefinition.new("publicKeyService", id: "https://w3id.org/security#publicKeyService", type_mapping: "@id"),
+      "rdf" => TermDefinition.new("rdf", id: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", simple: true, prefix: true),
+      "rdfs" => TermDefinition.new("rdfs", id: "http://www.w3.org/2000/01/rdf-schema#", simple: true, prefix: true),
+      "recipient" => TermDefinition.new("recipient", id: "https://w3id.org/credentials#recipient", type_mapping: "@id"),
+      "revoked" => TermDefinition.new("revoked", id: "https://w3id.org/security#revoked", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
+      "schema" => TermDefinition.new("schema", id: "http://schema.org/", simple: true, prefix: true),
+      "sec" => TermDefinition.new("sec", id: "https://w3id.org/security#", simple: true, prefix: true),
+      "signature" => TermDefinition.new("signature", id: "https://w3id.org/security#signature", simple: true),
+      "signatureAlgorithm" => TermDefinition.new("signatureAlgorithm", id: "https://w3id.org/security#signatureAlgorithm", simple: true),
+      "signatureValue" => TermDefinition.new("signatureValue", id: "https://w3id.org/security#signatureValue", simple: true),
+      "streetAddress" => TermDefinition.new("streetAddress", id: "http://schema.org/streetAddress", simple: true),
+      "title" => TermDefinition.new("title", id: "http://purl.org/dc/terms/title", simple: true),
+      "type" => TermDefinition.new("type", id: "@type", simple: true),
+      "url" => TermDefinition.new("url", id: "http://schema.org/url", type_mapping: "@id"),
+      "writePermission" => TermDefinition.new("writePermission", id: "https://w3id.org/permissions#writePermission", type_mapping: "@id"),
+      "xsd" => TermDefinition.new("xsd", id: "http://www.w3.org/2001/XMLSchema#", simple: true, prefix: true)
+    })
+  end
+  alias_preloaded("https://w3id.org/identity/v1", "http://w3id.org/identity/v1")
+end
diff --git a/lib/json_ld/security.rb b/lib/json_ld/security.rb
index 1230206f035d1e315fec6522ac06eab6877163a5..a6fbce95f584316fa7a95f24c084972549007e94 100644
--- a/lib/json_ld/security.rb
+++ b/lib/json_ld/security.rb
@@ -1,9 +1,9 @@
 # -*- encoding: utf-8 -*-
 # frozen_string_literal: true
-# This file generated automatically from https://w3id.org/security/v1
+# This file generated automatically from http://w3id.org/security/v1
 require 'json/ld'
 class JSON::LD::Context
-  add_preloaded("https://w3id.org/security/v1") do
+  add_preloaded("http://w3id.org/security/v1") do
     new(processingMode: "json-ld-1.0", term_definitions: {
       "CryptographicKey" => TermDefinition.new("CryptographicKey", id: "https://w3id.org/security#Key", simple: true),
       "EcdsaKoblitzSignature2016" => TermDefinition.new("EcdsaKoblitzSignature2016", id: "https://w3id.org/security#EcdsaKoblitzSignature2016", simple: true),
@@ -47,4 +47,5 @@ class JSON::LD::Context
       "xsd" => TermDefinition.new("xsd", id: "http://www.w3.org/2001/XMLSchema#", simple: true, prefix: true)
     })
   end
+  alias_preloaded("https://w3id.org/security/v1", "http://w3id.org/security/v1")
 end
diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb
index 7d02153131c89682fb062dfe239bdf8b2b661d73..6dbb7568993c1a817065cdbf8150718b552b3df7 100644
--- a/lib/mastodon/accounts_cli.rb
+++ b/lib/mastodon/accounts_cli.rb
@@ -7,6 +7,8 @@ require_relative 'cli_helper'
 
 module Mastodon
   class AccountsCLI < Thor
+    include CLIHelper
+
     def self.exit_on_failure?
       true
     end
@@ -26,18 +28,20 @@ module Mastodon
       if options[:all]
         processed = 0
         delay     = 0
+        scope     = Account.local.without_suspended
+        progress  = create_progress_bar(scope.count)
 
-        Account.local.without_suspended.find_in_batches do |accounts|
+        scope.find_in_batches do |accounts|
           accounts.each do |account|
             rotate_keys_for_account(account, delay)
+            progress.increment
             processed += 1
-            say('.', :green, false)
           end
 
           delay += 5.minutes
         end
 
-        say
+        progress.finish
         say("OK, rotated keys for #{processed} accounts", :green)
       elsif username.present?
         rotate_keys_for_account(Account.find_local(username))
@@ -181,7 +185,7 @@ module Mastodon
       end
 
       say("Deleting user with #{account.statuses_count} statuses, this might take a while...")
-      SuspendAccountService.new.call(account, including_user: true)
+      SuspendAccountService.new.call(account, reserve_email: false)
       say('OK', :green)
     end
 
@@ -206,6 +210,7 @@ module Mastodon
       say('OK', :green)
     end
 
+    option :concurrency, type: :numeric, default: 5, aliases: [:c]
     option :dry_run, type: :boolean
     desc 'cull', 'Remove remote accounts that no longer exist'
     long_desc <<-LONG_DESC
@@ -215,63 +220,45 @@ module Mastodon
 
       Accounts that have had confirmed activity within the last week
       are excluded from the checks.
-
-      Domains that are unreachable are not checked.
-
-      With the --dry-run option, no deletes will actually be carried
-      out.
     LONG_DESC
     def cull
       skip_threshold = 7.days.ago
-      culled         = 0
-      dry_run_culled = []
-      skip_domains   = Set.new
       dry_run        = options[:dry_run] ? ' (DRY RUN)' : ''
+      skip_domains   = Concurrent::Set.new
 
-      Account.remote.where(protocol: :activitypub).partitioned.find_each do |account|
-        next if account.updated_at >= skip_threshold || (account.last_webfingered_at.present? && account.last_webfingered_at >= skip_threshold)
+      processed, culled = parallelize_with_progress(Account.remote.where(protocol: :activitypub).partitioned) do |account|
+        next if account.updated_at >= skip_threshold || (account.last_webfingered_at.present? && account.last_webfingered_at >= skip_threshold) || skip_domains.include?(account.domain)
 
         code = 0
-        unless skip_domains.include?(account.domain)
-          begin
-            code = Request.new(:head, account.uri).perform(&:code)
-          rescue HTTP::ConnectionError
-            skip_domains << account.domain
-          rescue StandardError
-            next
-          end
+
+        begin
+          code = Request.new(:head, account.uri).perform(&:code)
+        rescue HTTP::ConnectionError
+          skip_domains << account.domain
         end
 
         if [404, 410].include?(code)
-          if options[:dry_run]
-            dry_run_culled << account.acct
-          else
-            SuspendAccountService.new.call(account, destroy: true)
-          end
-          culled += 1
-          say('+', :green, false)
+          SuspendAccountService.new.call(account, reserve_username: false) unless options[:dry_run]
+          1
         else
-          account.touch # Touch account even during dry run to avoid getting the account into the window again
-          say('.', nil, false)
+          # Touch account even during dry run to avoid getting the account into the window again
+          account.touch
         end
       end
 
-      say
-      say("Removed #{culled} accounts. #{skip_domains.size} servers skipped#{dry_run}", skip_domains.empty? ? :green : :yellow)
+      say("Visited #{processed} accounts, removed #{culled}#{dry_run}", :green)
 
       unless skip_domains.empty?
-        say('The following servers were not available during the check:', :yellow)
+        say('The following domains were not available during the check:', :yellow)
         skip_domains.each { |domain| say('    ' + domain) }
       end
-
-      unless dry_run_culled.empty?
-        say('The following accounts would have been deleted:', :green)
-        dry_run_culled.each { |account| say('    ' + account) }
-      end
     end
 
     option :all, type: :boolean
     option :domain
+    option :concurrency, type: :numeric, default: 5, aliases: [:c]
+    option :verbose, type: :boolean, aliases: [:v]
+    option :dry_run, type: :boolean
     desc 'refresh [USERNAME]', 'Fetch remote user data and files'
     long_desc <<-LONG_DESC
       Fetch remote user data and files for one or multiple accounts.
@@ -280,21 +267,23 @@ module Mastodon
       Through the --domain option, this can be narrowed down to a
       specific domain only. Otherwise, a single remote account must
       be specified with USERNAME.
-
-      All processing is done in the background through Sidekiq.
     LONG_DESC
     def refresh(username = nil)
+      dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
+
       if options[:domain] || options[:all]
-        queued = 0
         scope  = Account.remote
         scope  = scope.where(domain: options[:domain]) if options[:domain]
 
-        scope.select(:id).reorder(nil).find_in_batches do |accounts|
-          Maintenance::RedownloadAccountMediaWorker.push_bulk(accounts.map(&:id))
-          queued += accounts.size
+        processed, = parallelize_with_progress(scope) do |account|
+          next if options[:dry_run]
+
+          account.reset_avatar!
+          account.reset_header!
+          account.save
         end
 
-        say("Scheduled refreshment of #{queued} accounts", :green, true)
+        say("Refreshed #{processed} accounts#{dry_run}", :green, true)
       elsif username.present?
         username, domain = username.split('@')
         account = Account.find_remote(username, domain)
@@ -304,72 +293,53 @@ module Mastodon
           exit(1)
         end
 
-        Maintenance::RedownloadAccountMediaWorker.perform_async(account.id)
-        say('OK', :green)
+        unless options[:dry_run]
+          account.reset_avatar!
+          account.reset_header!
+          account.save
+        end
+
+        say("OK#{dry_run}", :green)
       else
         say('No account(s) given', :red)
         exit(1)
       end
     end
 
-    desc 'follow ACCT', 'Make all local accounts follow account specified by ACCT'
-    long_desc <<-LONG_DESC
-      Make all local accounts follow an account specified by ACCT. ACCT can be
-      a simple username, in case of a local user. It can also be in the format
-      username@domain, in case of a remote user.
-    LONG_DESC
-    def follow(acct)
-      target_account = ResolveAccountService.new.call(acct)
-      processed      = 0
-      failed         = 0
+    option :concurrency, type: :numeric, default: 5, aliases: [:c]
+    option :verbose, type: :boolean, aliases: [:v]
+    desc 'follow USERNAME', 'Make all local accounts follow account specified by USERNAME'
+    def follow(username)
+      target_account = Account.find_local(username)
 
       if target_account.nil?
-        say("Target account (#{acct}) could not be resolved", :red)
+        say('No such account', :red)
         exit(1)
       end
 
-      Account.local.without_suspended.find_each do |account|
-        begin
-          FollowService.new.call(account, target_account)
-          processed += 1
-          say('.', :green, false)
-        rescue StandardError
-          failed += 1
-          say('.', :red, false)
-        end
+      processed, = parallelize_with_progress(Account.local.without_suspended) do |account|
+        FollowService.new.call(account, target_account)
       end
 
-      say("OK, followed target from #{processed} accounts, skipped #{failed}", :green)
+      say("OK, followed target from #{processed} accounts", :green)
     end
 
+    option :concurrency, type: :numeric, default: 5, aliases: [:c]
+    option :verbose, type: :boolean, aliases: [:v]
     desc 'unfollow ACCT', 'Make all local accounts unfollow account specified by ACCT'
-    long_desc <<-LONG_DESC
-      Make all local accounts unfollow an account specified by ACCT. ACCT can be
-      a simple username, in case of a local user. It can also be in the format
-      username@domain, in case of a remote user.
-    LONG_DESC
     def unfollow(acct)
       target_account = Account.find_remote(*acct.split('@'))
-      processed      = 0
-      failed         = 0
 
       if target_account.nil?
-        say("Target account (#{acct}) was not found", :red)
+        say('No such account', :red)
         exit(1)
       end
 
-      target_account.followers.local.find_each do |account|
-        begin
-          UnfollowService.new.call(account, target_account)
-          processed += 1
-          say('.', :green, false)
-        rescue StandardError
-          failed += 1
-          say('.', :red, false)
-        end
+      parallelize_with_progress(target_account.followers.local) do |account|
+        UnfollowService.new.call(account, target_account)
       end
 
-      say("OK, unfollowed target from #{processed} accounts, skipped #{failed}", :green)
+      say("OK, unfollowed target from #{processed} accounts", :green)
     end
 
     option :follows, type: :boolean, default: false
@@ -392,51 +362,50 @@ module Mastodon
       account = Account.find_local(username)
 
       if account.nil?
-        say('No user with such username', :red)
+        say('No such account', :red)
         exit(1)
       end
 
-      if options[:follows]
-        processed = 0
-        failed    = 0
+      total     = 0
+      total    += Account.where(id: ::Follow.where(account: account).select(:target_account_id)).count if options[:follows]
+      total    += Account.where(id: ::Follow.where(target_account: account).select(:account_id)).count if options[:followers]
+      progress  = create_progress_bar(total)
+      processed = 0
 
-        say("Unfollowing #{account.username}'s followees, this might take a while...")
+      if options[:follows]
+        scope = Account.where(id: ::Follow.where(account: account).select(:target_account_id))
 
-        Account.where(id: ::Follow.where(account: account).select(:target_account_id)).find_each do |target_account|
+        scope.find_each do |target_account|
           begin
             UnfollowService.new.call(account, target_account)
+          rescue => e
+            progress.log pastel.red("Error processing #{target_account.id}: #{e}")
+          ensure
+            progress.increment
             processed += 1
-            say('.', :green, false)
-          rescue StandardError
-            failed += 1
-            say('.', :red, false)
           end
         end
 
         BootstrapTimelineWorker.perform_async(account.id)
-
-        say("OK, unfollowed #{processed} followees, skipped #{failed}", :green)
       end
 
       if options[:followers]
-        processed = 0
-        failed    = 0
-
-        say("Removing #{account.username}'s followers, this might take a while...")
+        scope = Account.where(id: ::Follow.where(target_account: account).select(:account_id))
 
-        Account.where(id: ::Follow.where(target_account: account).select(:account_id)).find_each do |target_account|
+        scope.find_each do |target_account|
           begin
             UnfollowService.new.call(target_account, account)
+          rescue => e
+            progress.log pastel.red("Error processing #{target_account.id}: #{e}")
+          ensure
+            progress.increment
             processed += 1
-            say('.', :green, false)
-          rescue StandardError
-            failed += 1
-            say('.', :red, false)
           end
         end
-
-        say("OK, removed #{processed} followers, skipped #{failed}", :green)
       end
+
+      progress.finish
+      say("Processed #{processed} relationships", :green, true)
     end
 
     option :number, type: :numeric, aliases: [:n]
diff --git a/lib/mastodon/cache_cli.rb b/lib/mastodon/cache_cli.rb
index e9b6667b3b610a5a0bbb4169a35a8c93e1698421..803404c34f2599be54e8f23b2364bffac69e2877 100644
--- a/lib/mastodon/cache_cli.rb
+++ b/lib/mastodon/cache_cli.rb
@@ -6,6 +6,8 @@ require_relative 'cli_helper'
 
 module Mastodon
   class CacheCLI < Thor
+    include CLIHelper
+
     def self.exit_on_failure?
       true
     end
@@ -15,5 +17,44 @@ module Mastodon
       Rails.cache.clear
       say('OK', :green)
     end
+
+    option :concurrency, type: :numeric, default: 5, aliases: [:c]
+    option :verbose, type: :boolean, aliases: [:v]
+    desc 'recount TYPE', 'Update hard-cached counters'
+    long_desc <<~LONG_DESC
+      Update hard-cached counters of TYPE by counting referenced
+      records from scratch. TYPE can be "accounts" or "statuses".
+
+      It may take a very long time to finish, depending on the
+      size of the database.
+    LONG_DESC
+    def recount(type)
+      case type
+      when 'accounts'
+        processed, = parallelize_with_progress(Account.local.includes(:account_stat)) do |account|
+          account_stat                 = account.account_stat
+          account_stat.following_count = account.active_relationships.count
+          account_stat.followers_count = account.passive_relationships.count
+          account_stat.statuses_count  = account.statuses.where.not(visibility: :direct).count
+
+          account_stat.save if account_stat.changed?
+        end
+      when 'statuses'
+        processed, = parallelize_with_progress(Status.includes(:status_stat)) do |status|
+          status_stat                  = status.status_stat
+          status_stat.replies_count    = status.replies.where.not(visibility: :direct).count
+          status_stat.reblogs_count    = status.reblogs.count
+          status_stat.favourites_count = status.favourites.count
+
+          status_stat.save if status_stat.changed?
+        end
+      else
+        say("Unknown type: #{type}", :red)
+        exit(1)
+      end
+
+      say
+      say("OK, recounted #{processed} records", :green)
+    end
   end
 end
diff --git a/lib/mastodon/cli_helper.rb b/lib/mastodon/cli_helper.rb
index 2f807d08c84f94dbefc9f304dcff4dc8616cd6f8..ec4d9a81e5f2a0cc06ec578ac411d6aa557a807c 100644
--- a/lib/mastodon/cli_helper.rb
+++ b/lib/mastodon/cli_helper.rb
@@ -7,3 +7,66 @@ ActiveRecord::Base.logger    = dev_null
 ActiveJob::Base.logger       = dev_null
 HttpLog.configuration.logger = dev_null
 Paperclip.options[:log]      = false
+
+module Mastodon
+  module CLIHelper
+    def create_progress_bar(total = nil)
+      ProgressBar.create(total: total, format: '%c/%u |%b%i| %e')
+    end
+
+    def parallelize_with_progress(scope)
+      if options[:concurrency] < 1
+        say('Cannot run with this concurrency setting, must be at least 1', :red)
+        exit(1)
+      end
+
+      ActiveRecord::Base.configurations[Rails.env]['pool'] = options[:concurrency] + 1
+
+      progress  = create_progress_bar(scope.count)
+      pool      = Concurrent::FixedThreadPool.new(options[:concurrency])
+      total     = Concurrent::AtomicFixnum.new(0)
+      aggregate = Concurrent::AtomicFixnum.new(0)
+
+      scope.reorder(nil).find_in_batches do |items|
+        futures = []
+
+        items.each do |item|
+          futures << Concurrent::Future.execute(executor: pool) do
+            begin
+              if !progress.total.nil? && progress.progress + 1 > progress.total
+                # The number of items has changed between start and now,
+                # since there is no good way to predict the final count from
+                # here, just change the progress bar to an indeterminate one
+
+                progress.total = nil
+              end
+
+              progress.log("Processing #{item.id}") if options[:verbose]
+
+              result = ActiveRecord::Base.connection_pool.with_connection do
+                yield(item)
+              end
+
+              aggregate.increment(result) if result.is_a?(Integer)
+            rescue => e
+              progress.log pastel.red("Error processing #{item.id}: #{e}")
+            ensure
+              progress.increment
+            end
+          end
+        end
+
+        total.increment(items.size)
+        futures.map(&:value)
+      end
+
+      progress.stop
+
+      [total.value, aggregate.value]
+    end
+
+    def pastel
+      @pastel ||= Pastel.new
+    end
+  end
+end
diff --git a/lib/mastodon/domains_cli.rb b/lib/mastodon/domains_cli.rb
index b081581fe5797089d3e2a2f00c85209c33a303c8..b5435bb5e5204384e2e65774ea6934dd97cf26cc 100644
--- a/lib/mastodon/domains_cli.rb
+++ b/lib/mastodon/domains_cli.rb
@@ -7,41 +7,58 @@ require_relative 'cli_helper'
 
 module Mastodon
   class DomainsCLI < Thor
+    include CLIHelper
+
     def self.exit_on_failure?
       true
     end
 
+    option :concurrency, type: :numeric, default: 5, aliases: [:c]
+    option :verbose, type: :boolean, aliases: [:v]
     option :dry_run, type: :boolean
-    desc 'purge DOMAIN', 'Remove accounts from a DOMAIN without a trace'
+    option :whitelist_mode, type: :boolean
+    desc 'purge [DOMAIN...]', 'Remove accounts from a DOMAIN without a trace'
     long_desc <<-LONG_DESC
       Remove all accounts from a given DOMAIN without leaving behind any
       records. Unlike a suspension, if the DOMAIN still exists in the wild,
       it means the accounts could return if they are resolved again.
+
+      When the --whitelist-mode option is given, instead of purging accounts
+      from a single domain, all accounts from domains that are not whitelisted
+      are removed from the database.
     LONG_DESC
-    def purge(domain)
-      removed = 0
+    def purge(*domains)
       dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
 
-      Account.where(domain: domain).find_each do |account|
-        SuspendAccountService.new.call(account, destroy: true) unless options[:dry_run]
-        removed += 1
-        say('.', :green, false)
+      scope = begin
+        if options[:whitelist_mode]
+          Account.remote.where.not(domain: DomainAllow.pluck(:domain))
+        elsif !domains.empty?
+          Account.remote.where(domain: domains)
+        else
+          say('No domain(s) given', :red)
+          exit(1)
+        end
+      end
+
+      processed, = parallelize_with_progress(scope) do |account|
+        SuspendAccountService.new.call(account, reserve_username: false, skip_side_effects: true) unless options[:dry_run]
       end
 
-      DomainBlock.where(domain: domain).destroy_all unless options[:dry_run]
+      DomainBlock.where(domain: domains).destroy_all unless options[:dry_run]
 
-      say
-      say("Removed #{removed} accounts#{dry_run}", :green)
+      say("Removed #{processed} accounts#{dry_run}", :green)
 
-      custom_emojis = CustomEmoji.where(domain: domain)
+      custom_emojis = CustomEmoji.where(domain: domains)
       custom_emojis_count = custom_emojis.count
       custom_emojis.destroy_all unless options[:dry_run]
+
       say("Removed #{custom_emojis_count} custom emojis", :green)
     end
 
     option :concurrency, type: :numeric, default: 50, aliases: [:c]
-    option :silent, type: :boolean, default: false, aliases: [:s]
     option :format, type: :string, default: 'summary', aliases: [:f]
+    option :exclude_suspended, type: :boolean, default: false, aliases: [:x]
     desc 'crawl [START]', 'Crawl all known peers, optionally beginning at START'
     long_desc <<-LONG_DESC
       Crawl the fediverse by using the Mastodon REST API endpoints that expose
@@ -52,26 +69,31 @@ module Mastodon
       The --concurrency (-c) option controls the number of threads performing HTTP
       requests at the same time. More threads means the crawl may complete faster.
 
-      The --silent (-s) option controls progress output.
-
       The --format (-f) option controls how the data is displayed at the end. By
       default (`summary`), a summary of the statistics is returned. The other options
       are `domains`, which returns a newline-delimited list of all discovered peers,
       and `json`, which dumps all the aggregated data raw.
+
+      The --exclude-suspended (-x) option means that domains that are suspended
+      instance-wide do not appear in the output and are not included in summaries.
+      This also excludes subdomains of any of those domains.
     LONG_DESC
     def crawl(start = nil)
-      stats     = Concurrent::Hash.new
-      processed = Concurrent::AtomicFixnum.new(0)
-      failed    = Concurrent::AtomicFixnum.new(0)
-      start_at  = Time.now.to_f
-      seed      = start ? [start] : Account.remote.domains
+      stats           = Concurrent::Hash.new
+      processed       = Concurrent::AtomicFixnum.new(0)
+      failed          = Concurrent::AtomicFixnum.new(0)
+      start_at        = Time.now.to_f
+      seed            = start ? [start] : Account.remote.domains
+      blocked_domains = Regexp.new('\\.?' + DomainBlock.where(severity: 1).pluck(:domain).join('|') + '$')
+      progress        = create_progress_bar
 
       pool = Concurrent::ThreadPoolExecutor.new(min_threads: 0, max_threads: options[:concurrency], idletime: 10, auto_terminate: true, max_queue: 0)
 
       work_unit = ->(domain) do
         next if stats.key?(domain)
+        next if options[:exclude_suspended] && domain.match(blocked_domains)
+
         stats[domain] = nil
-        processed.increment
 
         begin
           Request.new(:get, "https://#{domain}/api/v1/instance").perform do |res|
@@ -91,11 +113,11 @@ module Mastodon
             next unless res.code == 200
             stats[domain]['activity'] = Oj.load(res.to_s)
           end
-
-          say('.', :green, false) unless options[:silent]
         rescue StandardError
           failed.increment
-          say('.', :red, false) unless options[:silent]
+        ensure
+          processed.increment
+          progress.increment unless progress.finished?
         end
       end
 
@@ -109,10 +131,9 @@ module Mastodon
       pool.shutdown
       pool.wait_for_termination(20)
     ensure
+      progress.finish
       pool.shutdown
 
-      say unless options[:silent]
-
       case options[:format]
       when 'summary'
         stats_to_summary(stats, processed, failed, start_at)
diff --git a/lib/mastodon/emoji_cli.rb b/lib/mastodon/emoji_cli.rb
index 97a822e4558dead73b7f38948230477c8d1d17c5..beac1b1fd22606f3821f71c98d8ad78bb5e2fc65 100644
--- a/lib/mastodon/emoji_cli.rb
+++ b/lib/mastodon/emoji_cli.rb
@@ -15,6 +15,7 @@ module Mastodon
     option :suffix
     option :overwrite, type: :boolean
     option :unlisted, type: :boolean
+    option :category
     desc 'import PATH', 'Import emoji from a TAR GZIP archive at PATH'
     long_desc <<-LONG_DESC
       Imports custom emoji from a TAR GZIP archive specified by PATH.
@@ -22,6 +23,9 @@ module Mastodon
       Existing emoji will be skipped unless the --overwrite option
       is provided, in which case they will be overwritten.
 
+      You can specifiy a --category under which the emojis will be
+      grouped together.
+
       With the --prefix option, a prefix can be added to all
       generated shortcodes. Likewise, the --suffix option controls
       the suffix of all shortcodes.
@@ -33,6 +37,7 @@ module Mastodon
       imported = 0
       skipped  = 0
       failed   = 0
+      category = options[:category] ? CustomEmojiCategory.find_or_create_by(name: options[:category]) : nil
 
       Gem::Package::TarReader.new(Zlib::GzipReader.open(path)) do |tar|
         tar.each do |entry|
@@ -50,6 +55,7 @@ module Mastodon
           custom_emoji.image = StringIO.new(entry.read)
           custom_emoji.image_file_name = File.basename(entry.full_name)
           custom_emoji.visible_in_picker = !options[:unlisted]
+          custom_emoji.category = category
 
           if custom_emoji.save
             imported += 1
diff --git a/lib/mastodon/feeds_cli.rb b/lib/mastodon/feeds_cli.rb
index fe11c3df400c1890d8d20b61911a11064b01b0be..578ea15c587238d440e479fe044d4522b6d6a99c 100644
--- a/lib/mastodon/feeds_cli.rb
+++ b/lib/mastodon/feeds_cli.rb
@@ -6,55 +6,32 @@ require_relative 'cli_helper'
 
 module Mastodon
   class FeedsCLI < Thor
+    include CLIHelper
+
     def self.exit_on_failure?
       true
     end
 
     option :all, type: :boolean, default: false
-    option :background, type: :boolean, default: false
+    option :concurrency, type: :numeric, default: 5, aliases: [:c]
+    option :verbose, type: :boolean, aliases: [:v]
     option :dry_run, type: :boolean, default: false
-    option :verbose, type: :boolean, default: false
     desc 'build [USERNAME]', 'Build home and list feeds for one or all users'
     long_desc <<-LONG_DESC
       Build home and list feeds that are stored in Redis from the database.
 
       With the --all option, all active users will be processed.
       Otherwise, a single user specified by USERNAME.
-
-      With the --background option, regeneration will be queued into Sidekiq,
-      and the command will exit as soon as possible.
-
-      With the --dry-run option, no work will be done.
-
-      With the --verbose option, when accounts are processed sequentially in the
-      foreground, the IDs of the accounts will be printed.
     LONG_DESC
     def build(username = nil)
       dry_run = options[:dry_run] ? '(DRY RUN)' : ''
 
       if options[:all] || username.nil?
-        processed = 0
-        queued    = 0
-
-        User.active.select(:id, :account_id).reorder(nil).find_in_batches do |users|
-          if options[:background]
-            RegenerationWorker.push_bulk(users.map(&:account_id)) unless options[:dry_run]
-            queued += users.size
-          else
-            users.each do |user|
-              RegenerationWorker.new.perform(user.account_id) unless options[:dry_run]
-              options[:verbose] ? say(user.account_id) : say('.', :green, false)
-              processed += 1
-            end
-          end
+        processed, = parallelize_with_progress(Account.joins(:user).merge(User.active)) do |account|
+          PrecomputeFeedService.new.call(account) unless options[:dry_run]
         end
 
-        if options[:background]
-          say("Scheduled feed regeneration for #{queued} accounts #{dry_run}", :green, true)
-        else
-          say
-          say("Regenerated feeds for #{processed} accounts #{dry_run}", :green, true)
-        end
+        say("Regenerated feeds for #{processed} accounts #{dry_run}", :green, true)
       elsif username.present?
         account = Account.find_local(username)
 
@@ -63,11 +40,7 @@ module Mastodon
           exit(1)
         end
 
-        if options[:background]
-          RegenerationWorker.perform_async(account.id) unless options[:dry_run]
-        else
-          RegenerationWorker.new.perform(account.id) unless options[:dry_run]
-        end
+        PrecomputeFeedService.new.call(account) unless options[:dry_run]
 
         say("OK #{dry_run}", :green, true)
       else
diff --git a/lib/mastodon/media_cli.rb b/lib/mastodon/media_cli.rb
index 6152d5a0950ccaf42502d6b0ca5908abaec65290..e48175134c78783a0eb41423e11e9e43697c6773 100644
--- a/lib/mastodon/media_cli.rb
+++ b/lib/mastodon/media_cli.rb
@@ -7,14 +7,15 @@ require_relative 'cli_helper'
 module Mastodon
   class MediaCLI < Thor
     include ActionView::Helpers::NumberHelper
+    include CLIHelper
 
     def self.exit_on_failure?
       true
     end
 
-    option :days, type: :numeric, default: 7
-    option :background, type: :boolean, default: false
-    option :verbose, type: :boolean, default: false
+    option :days, type: :numeric, default: 7, aliases: [:d]
+    option :concurrency, type: :numeric, default: 5, aliases: [:c]
+    option :verbose, type: :boolean, default: false, aliases: [:v]
     option :dry_run, type: :boolean, default: false
     desc 'remove', 'Remove remote media files'
     long_desc <<-DESC
@@ -22,49 +23,95 @@ module Mastodon
 
       The --days option specifies how old media attachments have to be before
       they are removed. It defaults to 7 days.
+    DESC
+    def remove
+      time_ago = options[:days].days.ago
+      dry_run  = options[:dry_run] ? '(DRY RUN)' : ''
+
+      processed, aggregate = parallelize_with_progress(MediaAttachment.cached.where.not(remote_url: '').where('created_at < ?', time_ago)) do |media_attachment|
+        next if media_attachment.file.blank?
+
+        size = media_attachment.file_file_size
+
+        unless options[:dry_run]
+          media_attachment.file.destroy
+          media_attachment.save
+        end
+
+        size
+      end
+
+      say("Removed #{processed} media attachments (approx. #{number_to_human_size(aggregate)}) #{dry_run}", :green, true)
+    end
+
+    option :account, type: :string
+    option :domain, type: :string
+    option :status, type: :numeric
+    option :concurrency, type: :numeric, default: 5, aliases: [:c]
+    option :verbose, type: :boolean, default: false, aliases: [:v]
+    option :dry_run, type: :boolean, default: false
+    option :force, type: :boolean, default: false
+    desc 'refresh', 'Fetch remote media files'
+    long_desc <<-DESC
+      Re-downloads media attachments from other servers. You must specify the
+      source of media attachments with one of the following options:
 
-      With the --background option, instead of deleting the files sequentially,
-      they will be queued into Sidekiq and the command will exit as soon as
-      possible. In Sidekiq they will be processed with higher concurrency, but
-      it may impact other operations of the Mastodon server, and it may overload
-      the underlying file storage.
+      Use the --status option to download attachments from a specific status,
+      using the status local numeric ID.
 
-      With the --dry-run option, no work will be done.
+      Use the --account option to download attachments from a specific account,
+      using username@domain handle of the account.
 
-      With the --verbose option, when media attachments are processed sequentially in the
-      foreground, the IDs of the media attachments will be printed.
+      Use the --domain option to download attachments from a specific domain.
+
+      By default, attachments that are believed to be already downloaded will
+      not be re-downloaded. To force re-download of every URL, use --force.
     DESC
-    def remove
-      time_ago  = options[:days].days.ago
-      queued    = 0
-      processed = 0
-      size      = 0
-      dry_run   = options[:dry_run] ? '(DRY RUN)' : ''
-
-      if options[:background]
-        MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).select(:id, :file_file_size).reorder(nil).find_in_batches do |media_attachments|
-          queued += media_attachments.size
-          size   += media_attachments.reduce(0) { |sum, m| sum + (m.file_file_size || 0) }
-          Maintenance::UncacheMediaWorker.push_bulk(media_attachments.map(&:id)) unless options[:dry_run]
+    def refresh
+      dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
+
+      if options[:status]
+        scope = MediaAttachment.where(status_id: options[:status])
+      elsif options[:account]
+        username, domain = username.split('@')
+        account = Account.find_remote(username, domain)
+
+        if account.nil?
+          say('No such account', :red)
+          exit(1)
         end
+
+        scope = MediaAttachment.where(account_id: account.id)
+      elsif options[:domain]
+        scope = MediaAttachment.joins(:account).merge(Account.by_domain_and_subdomains(options[:domain]))
       else
-        MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).reorder(nil).find_in_batches do |media_attachments|
-          media_attachments.each do |m|
-            size += m.file_file_size || 0
-            Maintenance::UncacheMediaWorker.new.perform(m) unless options[:dry_run]
-            options[:verbose] ? say(m.id) : say('.', :green, false)
-            processed += 1
-          end
-        end
+        exit(1)
       end
 
-      say
+      processed, aggregate = parallelize_with_progress(scope) do |media_attachment|
+        next if media_attachment.remote_url.blank? || (!options[:force] && media_attachment.file_file_name.present?)
 
-      if options[:background]
-        say("Scheduled the deletion of #{queued} media attachments (approx. #{number_to_human_size(size)}) #{dry_run}", :green, true)
-      else
-        say("Removed #{processed} media attachments (approx. #{number_to_human_size(size)}) #{dry_run}", :green, true)
+        unless options[:dry_run]
+          media_attachment.reset_file!
+          media_attachment.save
+        end
+
+        media_attachment.file_file_size
       end
+
+      say("Downloaded #{processed} media attachments (approx. #{number_to_human_size(aggregate)})#{dry_run}", :green, true)
+    end
+
+    desc 'usage', 'Calculate disk space consumed by Mastodon'
+    def usage
+      say("Attachments:\t#{number_to_human_size(MediaAttachment.sum(:file_file_size))} (#{number_to_human_size(MediaAttachment.where(account: Account.local).sum(:file_file_size))} local)")
+      say("Custom emoji:\t#{number_to_human_size(CustomEmoji.sum(:image_file_size))} (#{number_to_human_size(CustomEmoji.local.sum(:image_file_size))} local)")
+      say("Preview cards:\t#{number_to_human_size(PreviewCard.sum(:image_file_size))}")
+      say("Avatars:\t#{number_to_human_size(Account.sum(:avatar_file_size))} (#{number_to_human_size(Account.local.sum(:avatar_file_size))} local)")
+      say("Headers:\t#{number_to_human_size(Account.sum(:header_file_size))} (#{number_to_human_size(Account.local.sum(:header_file_size))} local)")
+      say("Backups:\t#{number_to_human_size(Backup.sum(:dump_file_size))}")
+      say("Imports:\t#{number_to_human_size(Import.sum(:data_file_size))}")
+      say("Settings:\t#{number_to_human_size(SiteUpload.sum(:file_file_size))}")
     end
   end
 end
diff --git a/lib/mastodon/preview_cards_cli.rb b/lib/mastodon/preview_cards_cli.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cf4407250c5578604ab42f3397d3ae0a9bcbce12
--- /dev/null
+++ b/lib/mastodon/preview_cards_cli.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'tty-prompt'
+require_relative '../../config/boot'
+require_relative '../../config/environment'
+require_relative 'cli_helper'
+
+module Mastodon
+  class PreviewCardsCLI < Thor
+    include ActionView::Helpers::NumberHelper
+    include CLIHelper
+
+    def self.exit_on_failure?
+      true
+    end
+
+    option :days, type: :numeric, default: 180
+    option :concurrency, type: :numeric, default: 5, aliases: [:c]
+    option :verbose, type: :boolean, aliases: [:v]
+    option :dry_run, type: :boolean, default: false
+    option :link, type: :boolean, default: false
+    desc 'remove', 'Remove preview cards'
+    long_desc <<-DESC
+      Removes local thumbnails for preview cards.
+
+      The --days option specifies how old preview cards have to be before
+      they are removed. It defaults to 180 days. Since preview cards will
+      not be re-fetched unless the link is re-posted after 2 weeks from
+      last time, it is not recommended to delete preview cards within the
+      last 14 days.
+
+      With the --link option, only link-type preview cards will be deleted,
+      leaving video and photo cards untouched.
+    DESC
+    def remove
+      time_ago = options[:days].days.ago
+      dry_run  = options[:dry_run] ? ' (DRY RUN)' : ''
+      link     = options[:link] ? 'link-type ' : ''
+      scope    = PreviewCard.cached
+      scope    = scope.where(type: :link) if options[:link]
+      scope    = scope.where('updated_at < ?', time_ago)
+
+      processed, aggregate = parallelize_with_progress(scope) do |preview_card|
+        next if preview_card.image.blank?
+
+        size = preview_card.image_file_size
+
+        unless options[:dry_run]
+          preview_card.image.destroy
+          preview_card.save
+        end
+
+        size
+      end
+
+      say("Removed #{processed} #{link}preview cards (approx. #{number_to_human_size(aggregate)})#{dry_run}", :green, true)
+    end
+  end
+end
diff --git a/lib/mastodon/search_cli.rb b/lib/mastodon/search_cli.rb
index 42ad93f1edbfa271a7a9cd091a4848adcbc1165a..8bd5f9543ffe38092477c9f840b687c54170e9f5 100644
--- a/lib/mastodon/search_cli.rb
+++ b/lib/mastodon/search_cli.rb
@@ -6,6 +6,7 @@ require_relative 'cli_helper'
 
 module Mastodon
   class SearchCLI < Thor
+    option :processes, default: 2, aliases: [:p]
     desc 'deploy', 'Create or update an ElasticSearch index and populate it'
     long_desc <<~LONG_DESC
       If ElasticSearch is empty, this command will create the necessary indices
@@ -13,10 +14,28 @@ module Mastodon
 
       This command will also upgrade indices if the underlying schema has been
       changed since the last run.
+
+      With the --processes option, parallelize execution of the command. The
+      default is 2. If "auto" is specified, the number is automatically
+      derived from available CPUs.
     LONG_DESC
     def deploy
-      processed = Chewy::RakeHelper.upgrade
-      Chewy::RakeHelper.sync(except: processed)
+      processed = Chewy::RakeHelper.upgrade(parallel: processes)
+      Chewy::RakeHelper.sync(except: processed, parallel: processes)
+    end
+
+    private
+
+    def processes
+      return true if options[:processes] == 'auto'
+
+      num = options[:processes].to_i
+
+      if num < 2
+        nil
+      else
+        num
+      end
     end
   end
 end
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index 99d709c980da5b516829314b91c727e0181c4de1..f3ead6d8d96d6a2e8d4b9fe902b03cee74f0798b 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -5,15 +5,15 @@ module Mastodon
     module_function
 
     def major
-      2
+      3
     end
 
     def minor
-      9
+      0
     end
 
     def patch
-      3
+      1
     end
 
     def flags
diff --git a/lib/paperclip/gif_transcoder.rb b/lib/paperclip/gif_transcoder.rb
index cbab6fd99a29e7ac7f95a966c0614ee5f27904b9..64f12f9637e57fd63953aa2aa2e6293dd9a40eee 100644
--- a/lib/paperclip/gif_transcoder.rb
+++ b/lib/paperclip/gif_transcoder.rb
@@ -1,5 +1,103 @@
 # frozen_string_literal: true
 
+class GifReader
+  attr_reader :animated
+
+  EXTENSION_LABELS = [0xf9, 0x01, 0xff].freeze
+  GIF_HEADERS      = %w(GIF87a GIF89a).freeze
+
+  class GifReaderException; end
+
+  class UnknownImageType < GifReaderException; end
+
+  class CannotParseImage < GifReaderException; end
+
+  def self.animated?(path)
+    new(path).animated
+  rescue GifReaderException
+    false
+  end
+
+  def initialize(path, max_frames = 2)
+    @path      = path
+    @nb_frames = 0
+
+    File.open(path, 'rb') do |s|
+      raise UnknownImageType unless GIF_HEADERS.include?(s.read(6))
+
+      # Skip to "packed byte"
+      s.seek(4, IO::SEEK_CUR)
+
+      # "Packed byte" gives us the size of the GIF color table
+      packed_byte, = s.read(1).unpack('C')
+
+      # Skip background color and aspect ratio
+      s.seek(2, IO::SEEK_CUR)
+
+      if packed_byte & 0x80 != 0
+        # GIF uses a global color table, skip it
+        s.seek(3 * (1 << ((packed_byte & 0x07) + 1)), IO::SEEK_CUR)
+      end
+
+      # Now read data
+      while @nb_frames < max_frames
+        separator = s.read(1)
+
+        case separator
+        when ',' # Image block
+          @nb_frames += 1
+
+          # Skip to "packed byte"
+          s.seek(8, IO::SEEK_CUR)
+          packed_byte, = s.read(1).unpack('C')
+
+          if packed_byte & 0x80 != 0
+            # Image uses a local color table, skip it
+            s.seek(3 * (1 << ((packed_byte & 0x07) + 1)), IO::SEEK_CUR)
+          end
+
+          # Skip lzw min code size
+          raise InvalidValue unless s.read(1).unpack('C')[0] >= 2
+
+          # Skip image data sub-blocks
+          skip_sub_blocks!(s)
+        when '!' # Extension block
+          skip_extension_block!(s)
+        when ';' # Trailer
+          break
+        else
+          raise CannotParseImage
+        end
+      end
+    end
+
+    @animated = @nb_frames > 1
+  end
+
+  private
+
+  def skip_extension_block!(file)
+    if EXTENSION_LABELS.include?(file.read(1).unpack('C')[0])
+      block_size, = file.read(1).unpack('C')
+      file.seek(block_size, IO::SEEK_CUR)
+    end
+
+    # Read until extension block end marker
+    skip_sub_blocks!(file)
+  end
+
+  # Skip sub-blocks up until block end marker
+  def skip_sub_blocks!(file)
+    loop do
+      size, = file.read(1).unpack('C')
+
+      break if size.zero?
+
+      file.seek(size, IO::SEEK_CUR)
+    end
+  end
+end
+
 module Paperclip
   # This transcoder is only to be used for the MediaAttachment model
   # to convert animated gifs to webm
@@ -19,8 +117,7 @@ module Paperclip
     private
 
     def needs_convert?
-      num_frames = identify('-format %n :file', file: file.path).to_i
-      options[:style] == :original && num_frames > 1
+      options[:style] == :original && GifReader.animated?(file.path)
     end
   end
 end
diff --git a/lib/paperclip/video_transcoder.rb b/lib/paperclip/video_transcoder.rb
index c3504c17c4fbef2c959d2e86d924b67994530fed..66f7feda5603201043366962b72f8cbf3ff7a3e3 100644
--- a/lib/paperclip/video_transcoder.rb
+++ b/lib/paperclip/video_transcoder.rb
@@ -6,7 +6,9 @@ module Paperclip
   class VideoTranscoder < Paperclip::Processor
     def make
       meta = ::Av.cli.identify(@file.path)
+
       attachment.instance.type = MediaAttachment.types[:gifv] unless meta[:audio_encode]
+      options[:format] = File.extname(attachment.instance.file_file_name)[1..-1] if options[:keep_same_format]
 
       Paperclip::Transcoder.make(file, options, attachment)
     end
diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake
index ee9657b0eff06c272091bc7775b8c90e7b9850d2..2e92e8dedbe115588ab0ca52969670706eb18883 100644
--- a/lib/tasks/mastodon.rake
+++ b/lib/tasks/mastodon.rake
@@ -135,7 +135,7 @@ namespace :mastodon do
       prompt.say "\n"
 
       if prompt.yes?('Do you want to store uploaded files on the cloud?', default: false)
-        case prompt.select('Provider', ['Amazon S3', 'Wasabi', 'Minio'])
+        case prompt.select('Provider', ['Amazon S3', 'Wasabi', 'Minio', 'Google Cloud Storage'])
         when 'Amazon S3'
           env['S3_ENABLED']  = 'true'
           env['S3_PROTOCOL'] = 'https'
@@ -217,6 +217,34 @@ namespace :mastodon do
             q.required true
             q.modify :strip
           end
+        when 'Google Cloud Storage'
+          env['S3_ENABLED']             = 'true'
+          env['S3_PROTOCOL']            = 'https'
+          env['S3_HOSTNAME']            = 'storage.googleapis.com'
+          env['S3_ENDPOINT']            = 'https://storage.googleapis.com'
+          env['S3_MULTIPART_THRESHOLD'] = 50.megabytes
+
+          env['S3_BUCKET'] = prompt.ask('GCS bucket name:') do |q|
+            q.required true
+            q.default "files.#{env['LOCAL_DOMAIN']}"
+            q.modify :strip
+          end
+
+          env['S3_REGION'] = prompt.ask('GCS region:') do |q|
+            q.required true
+            q.default 'us-west1'
+            q.modify :strip
+          end
+
+          env['AWS_ACCESS_KEY_ID'] = prompt.ask('GCS access key:') do |q|
+            q.required true
+            q.modify :strip
+          end
+
+          env['AWS_SECRET_ACCESS_KEY'] = prompt.ask('GCS secret key:') do |q|
+            q.required true
+            q.modify :strip
+          end
         end
 
         if prompt.yes?('Do you want to access the uploaded files from your own domain?')
diff --git a/lib/tasks/repo.rake b/lib/tasks/repo.rake
index 8ceec3085461f3e556e10c829942f383968c17fc..d1de17b7c0347da7ce820a6c90c7bdafa4b747d6 100644
--- a/lib/tasks/repo.rake
+++ b/lib/tasks/repo.rake
@@ -76,4 +76,19 @@ namespace :repo do
       tmp.unlink
     end
   end
+
+  task check_locales_files: :environment do
+    pastel = Pastel.new
+
+    missing_yaml_files = I18n.available_locales.reject { |locale| File.exist?(Rails.root.join('config', 'locales', "#{locale}.yml")) }
+    missing_json_files = I18n.available_locales.reject { |locale| File.exist?(Rails.root.join('app', 'javascript', 'mastodon', 'locales', "#{locale}.json")) }
+
+    if missing_json_files.empty? && missing_yaml_files.empty?
+      puts pastel.green('OK')
+    else
+      puts pastel.red("Missing YAML files: #{pastel.bold(missing_yaml_files.join(', '))}") unless missing_yaml_files.empty?
+      puts pastel.red("Missing JSON files: #{pastel.bold(missing_json_files.join(', '))}") unless missing_json_files.empty?
+      exit(1)
+    end
+  end
 end
diff --git a/package.json b/package.json
index c691d8e4345c5c1220a307461f933fb674a0a83d..433496b84ad54cb159db48d2923a534136ce6071 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
 {
-  "name": "mastodon",
+  "name": "@tootsuite/mastodon",
   "license": "AGPL-3.0-or-later",
   "engines": {
-    "node": ">=8.12 <12"
+    "node": ">=8.12 <13"
   },
   "scripts": {
     "postversion": "git push --tags",
@@ -60,22 +60,22 @@
   "private": true,
   "dependencies": {
     "@babel/core": "^7.4.5",
-    "@babel/plugin-proposal-class-properties": "^7.4.4",
+    "@babel/plugin-proposal-class-properties": "^7.5.5",
     "@babel/plugin-proposal-decorators": "^7.4.4",
     "@babel/plugin-proposal-object-rest-spread": "^7.4.4",
     "@babel/plugin-syntax-dynamic-import": "^7.2.0",
     "@babel/plugin-transform-react-inline-elements": "^7.2.0",
     "@babel/plugin-transform-react-jsx-self": "^7.2.0",
-    "@babel/plugin-transform-react-jsx-source": "^7.2.0",
-    "@babel/plugin-transform-runtime": "^7.4.4",
-    "@babel/preset-env": "^7.4.5",
+    "@babel/plugin-transform-react-jsx-source": "^7.5.0",
+    "@babel/plugin-transform-runtime": "^7.5.5",
+    "@babel/preset-env": "^7.6.0",
     "@babel/preset-react": "^7.0.0",
-    "@babel/runtime": "^7.4.5",
-    "@clusterws/cws": "^0.14.0",
+    "@babel/runtime": "^7.5.4",
+    "@clusterws/cws": "^0.15.0",
     "array-includes": "^3.0.3",
-    "autoprefixer": "^9.6.0",
+    "autoprefixer": "^9.6.1",
     "axios": "^0.19.0",
-    "babel-loader": "^8.0.5",
+    "babel-loader": "^8.0.6",
     "babel-plugin-lodash": "^3.3.4",
     "babel-plugin-preval": "^3.0.1",
     "babel-plugin-react-intl": "^3.1.3",
@@ -83,33 +83,35 @@
     "babel-runtime": "^6.26.0",
     "blurhash": "^1.0.0",
     "classnames": "^2.2.5",
-    "compression-webpack-plugin": "^2.0.0",
+    "compression-webpack-plugin": "^3.0.0",
+    "copy-webpack-plugin": "^5.0.4",
     "cross-env": "^5.1.4",
-    "css-loader": "^2.1.1",
+    "css-loader": "^3.2.0",
     "cssnano": "^4.1.10",
     "detect-passive-events": "^1.0.2",
     "dotenv": "^8.0.0",
     "emoji-mart": "Gargron/emoji-mart#build",
-    "es6-symbol": "^3.1.1",
+    "es6-symbol": "^3.1.2",
     "escape-html": "^1.0.3",
     "exif-js": "^2.3.0",
     "express": "^4.17.1",
-    "file-loader": "^4.0.0",
+    "file-loader": "^4.2.0",
     "font-awesome": "^4.7.0",
     "glob": "^7.1.1",
+    "history": "^4.10.1",
     "http-link-header": "^1.0.2",
     "immutable": "^3.8.2",
     "imports-loader": "^0.8.0",
     "intersection-observer": "^0.7.0",
     "intl": "^1.2.5",
     "intl-messageformat": "^2.2.0",
-    "intl-relativeformat": "^2.2.0",
+    "intl-relativeformat": "^6.4.3",
     "is-nan": "^1.2.1",
     "js-yaml": "^3.13.1",
-    "lodash": "^4.7.11",
+    "lodash": "^4.17.14",
     "mark-loader": "^0.1.6",
     "marky": "^1.2.1",
-    "mini-css-extract-plugin": "^0.5.0",
+    "mini-css-extract-plugin": "^0.8.0",
     "mkdirp": "^0.5.1",
     "npmlog": "^4.1.2",
     "object-assign": "^4.1.1",
@@ -133,7 +135,7 @@
     "react-motion": "^0.5.2",
     "react-notification": "^6.8.4",
     "react-overlays": "^0.8.3",
-    "react-redux": "^6.0.1",
+    "react-redux": "^7.1.1",
     "react-redux-loading-bar": "^4.0.8",
     "react-router-dom": "^4.1.1",
     "react-router-scroll-4": "^1.0.0-beta.1",
@@ -143,44 +145,46 @@
     "react-textarea-autosize": "^7.1.0",
     "react-toggle": "^4.0.1",
     "redis": "^2.7.1",
-    "redux": "^4.0.1",
+    "redux": "^4.0.4",
     "redux-immutable": "^4.0.0",
     "redux-thunk": "^2.2.0",
     "rellax": "^1.10.0",
     "requestidlecallback": "^0.3.0",
     "reselect": "^4.0.0",
-    "rimraf": "^2.6.3",
-    "sass": "^1.20.3",
+    "rimraf": "^3.0.0",
+    "sass": "^1.22.12",
     "sass-loader": "^7.0.3",
-    "stringz": "^1.0.0",
+    "stringz": "^2.0.0",
     "substring-trie": "^1.0.2",
-    "terser-webpack-plugin": "^1.3.0",
+    "terser-webpack-plugin": "^1.4.1",
+    "tesseract.js": "^2.0.0-alpha.16",
     "throng": "^4.0.0",
     "tiny-queue": "^0.2.1",
     "uuid": "^3.1.0",
-    "webpack": "^4.34.0",
+    "wavesurfer.js": "^3.0.0",
+    "webpack": "^4.35.3",
     "webpack-assets-manifest": "^3.1.1",
     "webpack-bundle-analyzer": "^3.3.2",
-    "webpack-cli": "^3.3.4",
+    "webpack-cli": "^3.3.7",
     "webpack-merge": "^4.2.1",
     "websocket.js": "^0.1.12"
   },
   "devDependencies": {
-    "babel-eslint": "^10.0.1",
-    "babel-jest": "^24.8.0",
+    "babel-eslint": "^10.0.3",
+    "babel-jest": "^24.9.0",
     "enzyme": "^3.10.0",
     "enzyme-adapter-react-16": "^1.14.0",
-    "eslint": "^5.11.1",
-    "eslint-plugin-import": "~2.17.3",
-    "eslint-plugin-jsx-a11y": "~6.2.1",
-    "eslint-plugin-promise": "~4.1.1",
-    "eslint-plugin-react": "~7.12.1",
-    "jest": "^24.8.0",
+    "eslint": "^6.5.0",
+    "eslint-plugin-import": "~2.18.2",
+    "eslint-plugin-jsx-a11y": "~6.2.3",
+    "eslint-plugin-promise": "~4.2.1",
+    "eslint-plugin-react": "~7.14.3",
+    "jest": "^24.9.0",
     "raf": "^3.4.1",
     "react-intl-translations-manager": "^5.0.3",
     "react-test-renderer": "^16.8.6",
     "sass-lint": "^1.13.1",
-    "webpack-dev-server": "^3.5.1",
-    "yargs": "^12.0.5"
+    "webpack-dev-server": "^3.8.0",
+    "yargs": "^13.3.0"
   }
 }
diff --git a/public/ocr/lang-data/eng.traineddata.gz b/public/ocr/lang-data/eng.traineddata.gz
new file mode 100644
index 0000000000000000000000000000000000000000..e83c1267675ff3a6641b110002363782db7a1885
Binary files /dev/null and b/public/ocr/lang-data/eng.traineddata.gz differ
diff --git a/scalingo.json b/scalingo.json
index dd8fb5530cfd5494a205e6eae478082faffb5dec..324356df0c262b1d386c4542a968f9c5ac97dff2 100644
--- a/scalingo.json
+++ b/scalingo.json
@@ -8,15 +8,6 @@
       "description": "The domain that your Mastodon instance will run on (this can be appname.scalingo.io or a custom domain)",
       "required": true
     },
-    "LOCAL_HTTPS": {
-      "description": "Will your domain support HTTPS? (Automatic for *.scalingo.io, requires manual configuration for custom domains)",
-      "value": "true",
-      "required": true
-    },
-    "PAPERCLIP_SECRET": {
-      "description": "The secret key for storing media files",
-      "generator": "secret"
-    },
     "SECRET_KEY_BASE": {
       "description": "The secret key base",
       "generator": "secret"
diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb
index b728d719f9dea829be2294a2a24d3e7fcbe91f3e..3d2a0665da8a4b4b8e44d31a2148f03284806a02 100644
--- a/spec/controllers/accounts_controller_spec.rb
+++ b/spec/controllers/accounts_controller_spec.rb
@@ -48,37 +48,6 @@ RSpec.describe AccountsController, type: :controller do
       end
     end
 
-    context 'atom' do
-      let(:format) { 'atom' }
-      let(:content_type) { 'application/atom+xml' }
-
-      shared_examples 'responsed streams' do
-        it 'assigns @entries' do
-          entries = assigns(:entries).to_a
-          expect(entries.size).to eq expected_statuses.size
-          entries.each.zip(expected_statuses.each) do |entry, expected_status|
-            expect(entry.status).to eq expected_status
-          end
-        end
-      end
-
-      include_examples 'responses'
-
-      context 'without max_id nor since_id' do
-        let(:expected_statuses) { [status7, status6, status5, status4, status3, status2, status1] }
-
-        include_examples 'responsed streams'
-      end
-
-      context 'with max_id and since_id' do
-        let(:max_id) { status4.stream_entry.id }
-        let(:since_id) { status1.stream_entry.id }
-        let(:expected_statuses) { [status3, status2] }
-
-        include_examples 'responsed streams'
-      end
-    end
-
     context 'activitystreams2' do
       let(:format) { 'json' }
       let(:content_type) { 'application/activity+json' }
diff --git a/spec/controllers/activitypub/inboxes_controller_spec.rb b/spec/controllers/activitypub/inboxes_controller_spec.rb
index eab4b8c3e65c2d04ee4d1c89b8aef3e7e71410d6..a9ee7549002175eaee3cdd391a5cd23db5d1fa58 100644
--- a/spec/controllers/activitypub/inboxes_controller_spec.rb
+++ b/spec/controllers/activitypub/inboxes_controller_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 RSpec.describe ActivityPub::InboxesController, type: :controller do
   describe 'POST #create' do
-    context 'if signed_request_account' do
+    context 'with signed_request_account' do
       it 'returns 202' do
         allow(controller).to receive(:signed_request_account) do
           Fabricate(:account)
@@ -15,7 +15,7 @@ RSpec.describe ActivityPub::InboxesController, type: :controller do
       end
     end
 
-    context 'not signed_request_account' do
+    context 'without signed_request_account' do
       it 'returns 401' do
         allow(controller).to receive(:signed_request_account) do
           false
diff --git a/spec/controllers/admin/accounts_controller_spec.rb b/spec/controllers/admin/accounts_controller_spec.rb
index a348ab3d75eddd9daf9805bf5372591c4c72a859..608606ff90c0f37519b00cbba9812afbac8704c3 100644
--- a/spec/controllers/admin/accounts_controller_spec.rb
+++ b/spec/controllers/admin/accounts_controller_spec.rb
@@ -75,44 +75,6 @@ RSpec.describe Admin::AccountsController, type: :controller do
     end
   end
 
-  describe 'POST #subscribe' do
-    subject { post :subscribe, params: { id: account.id } }
-
-    let(:current_user) { Fabricate(:user, admin: admin) }
-    let(:account) { Fabricate(:account) }
-
-    context 'when user is admin' do
-      let(:admin) { true }
-
-      it { is_expected.to redirect_to admin_account_path(account.id) }
-    end
-
-    context 'when user is not admin' do
-      let(:admin) { false }
-
-      it { is_expected.to have_http_status :forbidden }
-    end
-  end
-
-  describe 'POST #unsubscribe' do
-    subject { post :unsubscribe, params: { id: account.id } }
-
-    let(:current_user) { Fabricate(:user, admin: admin) }
-    let(:account) { Fabricate(:account) }
-
-    context 'when user is admin' do
-      let(:admin) { true }
-
-      it { is_expected.to redirect_to admin_account_path(account.id) }
-    end
-
-    context 'when user is not admin' do
-      let(:admin) { false }
-
-      it { is_expected.to have_http_status :forbidden }
-    end
-  end
-
   describe 'POST #memorialize' do
     subject { post :memorialize, params: { id: account.id } }
 
diff --git a/spec/controllers/admin/custom_emojis_controller_spec.rb b/spec/controllers/admin/custom_emojis_controller_spec.rb
index b7e2894e90af2c8c74f6564fc496bcd29a55746c..a8d96948caebad57d6d9654509c9ea162420841d 100644
--- a/spec/controllers/admin/custom_emojis_controller_spec.rb
+++ b/spec/controllers/admin/custom_emojis_controller_spec.rb
@@ -52,64 +52,4 @@ describe Admin::CustomEmojisController do
       end
     end
   end
-
-  describe 'PUT #update' do
-    let(:custom_emoji) { Fabricate(:custom_emoji, shortcode: 'test') }
-    let(:image) { fixture_file_upload(Rails.root.join('spec', 'fixtures', 'files', 'emojo.png'), 'image/png') }
-
-    before do
-      put :update, params: { id: custom_emoji.id, custom_emoji: params }
-    end
-
-    context 'when parameter is valid' do
-      let(:params) { { shortcode: 'updated', image: image } }
-
-      it 'succeeds in updating custom emoji' do
-        expect(flash[:notice]).to eq I18n.t('admin.custom_emojis.updated_msg')
-        expect(custom_emoji.reload).to have_attributes(shortcode: 'updated')
-      end
-    end
-
-    context 'when parameter is invalid' do
-      let(:params) { { shortcode: 'u', image: image } }
-
-      it 'fails to update custom emoji' do
-        expect(flash[:alert]).to eq I18n.t('admin.custom_emojis.update_failed_msg')
-        expect(custom_emoji.reload).to have_attributes(shortcode: 'test')
-      end
-    end
-  end
-
-  describe 'POST #copy' do
-    subject { post :copy, params: { id: custom_emoji.id } }
-
-    let(:custom_emoji) { Fabricate(:custom_emoji, shortcode: 'test') }
-
-    it 'copies custom emoji' do
-      expect { subject }.to change { CustomEmoji.where(shortcode: 'test').count }.by(1)
-      expect(flash[:notice]).to eq I18n.t('admin.custom_emojis.copied_msg')
-    end
-  end
-
-  describe 'POST #enable' do
-    let(:custom_emoji) { Fabricate(:custom_emoji, shortcode: 'test', disabled: true) }
-
-    before { post :enable, params: { id: custom_emoji.id } }
-
-    it 'enables custom emoji' do
-      expect(response).to redirect_to admin_custom_emojis_path
-      expect(custom_emoji.reload).to have_attributes(disabled: false)
-    end
-  end
-
-  describe 'POST #disable' do
-    let(:custom_emoji) { Fabricate(:custom_emoji, shortcode: 'test', disabled: false) }
-
-    before { post :disable, params: { id: custom_emoji.id } }
-
-    it 'enables custom emoji' do
-      expect(response).to redirect_to admin_custom_emojis_path
-      expect(custom_emoji.reload).to have_attributes(disabled: true)
-    end
-  end
 end
diff --git a/spec/controllers/admin/reported_statuses_controller_spec.rb b/spec/controllers/admin/reported_statuses_controller_spec.rb
index c358506d6dce88b136487dbefd266d00719aec8d..2a1598123c1db962163d979d4d0838af54f6878b 100644
--- a/spec/controllers/admin/reported_statuses_controller_spec.rb
+++ b/spec/controllers/admin/reported_statuses_controller_spec.rb
@@ -47,7 +47,7 @@ describe Admin::ReportedStatusesController do
       it 'removes a status' do
         allow(RemovalWorker).to receive(:perform_async)
         subject.call
-        expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first)
+        expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: true)
       end
     end
 
diff --git a/spec/controllers/admin/statuses_controller_spec.rb b/spec/controllers/admin/statuses_controller_spec.rb
index 1a08c10b7e873fd1c2052419601e999d797acee3..d9690d83f228acc5469e95c614952eafa9ffe554 100644
--- a/spec/controllers/admin/statuses_controller_spec.rb
+++ b/spec/controllers/admin/statuses_controller_spec.rb
@@ -65,7 +65,7 @@ describe Admin::StatusesController do
       it 'removes a status' do
         allow(RemovalWorker).to receive(:perform_async)
         subject.call
-        expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first)
+        expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: true)
       end
     end
 
diff --git a/spec/controllers/admin/subscriptions_controller_spec.rb b/spec/controllers/admin/subscriptions_controller_spec.rb
deleted file mode 100644
index 967152abe36195704f00a935f0f8173519f6bd78..0000000000000000000000000000000000000000
--- a/spec/controllers/admin/subscriptions_controller_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-require 'rails_helper'
-
-RSpec.describe Admin::SubscriptionsController, type: :controller do
-  render_views
-
-  describe 'GET #index' do
-    around do |example|
-      default_per_page = Subscription.default_per_page
-      Subscription.paginates_per 1
-      example.run
-      Subscription.paginates_per default_per_page
-    end
-
-    before do
-      sign_in Fabricate(:user, admin: true), scope: :user
-    end
-
-    it 'renders subscriptions' do
-      Fabricate(:subscription)
-      specified = Fabricate(:subscription)
-
-      get :index
-
-      subscriptions = assigns(:subscriptions)
-      expect(subscriptions.count).to eq 1
-      expect(subscriptions[0]).to eq specified
-
-      expect(response).to have_http_status(200)
-    end
-  end
-end
diff --git a/spec/controllers/admin/tags_controller_spec.rb b/spec/controllers/admin/tags_controller_spec.rb
index 3af994071ce724cb09b66fd26f4d601cd1c39679..5c1944fc770dcd225dca79d48db20abc22804818 100644
--- a/spec/controllers/admin/tags_controller_spec.rb
+++ b/spec/controllers/admin/tags_controller_spec.rb
@@ -10,62 +10,14 @@ RSpec.describe Admin::TagsController, type: :controller do
   end
 
   describe 'GET #index' do
-    before do
-      account_tag_stat = Fabricate(:tag).account_tag_stat
-      account_tag_stat.update(hidden: hidden, accounts_count: 1)
-      get :index, params: { hidden: hidden }
-    end
-
-    context 'with hidden tags' do
-      let(:hidden) { true }
-
-      it 'returns status 200' do
-        expect(response).to have_http_status(200)
-      end
-    end
-
-    context 'without hidden tags' do
-      let(:hidden) { false }
-
-      it 'returns status 200' do
-        expect(response).to have_http_status(200)
-      end
-    end
-  end
-
-  describe 'POST #hide' do
-    let(:tag) { Fabricate(:tag) }
+    let!(:tag) { Fabricate(:tag) }
 
     before do
-      tag.account_tag_stat.update(hidden: false)
-      post :hide, params: { id: tag.id }
-    end
-
-    it 'hides tag' do
-      tag.reload
-      expect(tag).to be_hidden
-    end
-
-    it 'redirects to admin_tags_path' do
-      expect(response).to redirect_to(admin_tags_path(controller.instance_variable_get(:@filter_params)))
-    end
-  end
-
-  describe 'POST #unhide' do
-    let(:tag) { Fabricate(:tag) }
-
-    before do
-      tag.account_tag_stat.update(hidden: true)
-      post :unhide, params: { id: tag.id }
-    end
-
-    it 'unhides tag' do
-      tag.reload
-      expect(tag).not_to be_hidden
+      get :index
     end
 
-    it 'redirects to admin_tags_path' do
-      expect(response).to redirect_to(admin_tags_path(controller.instance_variable_get(:@filter_params)))
+    it 'returns status 200' do
+      expect(response).to have_http_status(200)
     end
   end
 end
diff --git a/spec/controllers/api/base_controller_spec.rb b/spec/controllers/api/base_controller_spec.rb
index 750ccc8cf67235865fa2790eec50794990640618..05a42d1c19597b608061fc01ccaddbc7a615a992 100644
--- a/spec/controllers/api/base_controller_spec.rb
+++ b/spec/controllers/api/base_controller_spec.rb
@@ -15,7 +15,7 @@ describe Api::BaseController do
     end
   end
 
-  describe 'Forgery protection' do
+  describe 'forgery protection' do
     before do
       routes.draw { post 'success' => 'api/base#success' }
     end
@@ -27,7 +27,45 @@ describe Api::BaseController do
     end
   end
 
-  describe 'Error handling' do
+  describe 'non-functional accounts handling' do
+    let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+    let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
+
+    controller do
+      before_action :require_user!
+    end
+
+    before do
+      routes.draw { post 'success' => 'api/base#success' }
+      allow(controller).to receive(:doorkeeper_token) { token }
+    end
+
+    it 'returns http forbidden for unconfirmed accounts' do
+      user.update(confirmed_at: nil)
+      post 'success'
+      expect(response).to have_http_status(403)
+    end
+
+    it 'returns http forbidden for pending accounts' do
+      user.update(approved: false)
+      post 'success'
+      expect(response).to have_http_status(403)
+    end
+
+    it 'returns http forbidden for disabled accounts' do
+      user.update(disabled: true)
+      post 'success'
+      expect(response).to have_http_status(403)
+    end
+
+    it 'returns http forbidden for suspended accounts' do
+      user.account.suspend!
+      post 'success'
+      expect(response).to have_http_status(403)
+    end
+  end
+
+  describe 'error handling' do
     ERRORS_WITH_CODES = {
       ActiveRecord::RecordInvalid => 422,
       Mastodon::ValidationError => 422,
diff --git a/spec/controllers/api/oembed_controller_spec.rb b/spec/controllers/api/oembed_controller_spec.rb
index 7fee15a3532748a9c6d349b0f31b2c65d66da62c..b9082bde1e7d255ef8296e2916373692d8939bcd 100644
--- a/spec/controllers/api/oembed_controller_spec.rb
+++ b/spec/controllers/api/oembed_controller_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Api::OEmbedController, type: :controller do
   describe 'GET #show' do
     before do
       request.host = Rails.configuration.x.local_domain
-      get :show, params: { url: account_stream_entry_url(alice, status.stream_entry) }, format: :json
+      get :show, params: { url: short_account_status_url(alice, status) }, format: :json
     end
 
     it 'returns http success' do
diff --git a/spec/controllers/api/push_controller_spec.rb b/spec/controllers/api/push_controller_spec.rb
deleted file mode 100644
index d769d8554fcce3c339a7e09af63aaf06d0e01408..0000000000000000000000000000000000000000
--- a/spec/controllers/api/push_controller_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Api::PushController, type: :controller do
-  describe 'POST #update' do
-    context 'with hub.mode=subscribe' do
-      it 'creates a subscription' do
-        service = double(call: ['', 202])
-        allow(Pubsubhubbub::SubscribeService).to receive(:new).and_return(service)
-        account = Fabricate(:account)
-        account_topic_url = "https://#{Rails.configuration.x.local_domain}/users/#{account.username}.atom"
-        post :update, params: {
-          'hub.mode' => 'subscribe',
-          'hub.topic' => account_topic_url,
-          'hub.callback' => 'https://callback.host/api',
-          'hub.lease_seconds' => '3600',
-          'hub.secret' => 'as1234df',
-        }
-
-        expect(service).to have_received(:call).with(
-          account,
-          'https://callback.host/api',
-          'as1234df',
-          '3600',
-          nil
-        )
-        expect(response).to have_http_status(202)
-      end
-    end
-
-    context 'with hub.mode=unsubscribe' do
-      it 'unsubscribes the account' do
-        service = double(call: ['', 202])
-        allow(Pubsubhubbub::UnsubscribeService).to receive(:new).and_return(service)
-        account = Fabricate(:account)
-        account_topic_url = "https://#{Rails.configuration.x.local_domain}/users/#{account.username}.atom"
-        post :update, params: {
-          'hub.mode' => 'unsubscribe',
-          'hub.topic' => account_topic_url,
-          'hub.callback' => 'https://callback.host/api',
-        }
-
-        expect(service).to have_received(:call).with(
-          account,
-          'https://callback.host/api',
-        )
-        expect(response).to have_http_status(202)
-      end
-    end
-
-    context 'with unknown mode' do
-      it 'returns an unknown mode error' do
-        post :update, params: { 'hub.mode' => 'fake' }
-
-        expect(response).to have_http_status(422)
-        expect(response.body).to match(/Unknown mode/)
-      end
-    end
-  end
-end
diff --git a/spec/controllers/api/salmon_controller_spec.rb b/spec/controllers/api/salmon_controller_spec.rb
deleted file mode 100644
index 235a29af0041e5207b145b521867789fbd36ed65..0000000000000000000000000000000000000000
--- a/spec/controllers/api/salmon_controller_spec.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Api::SalmonController, type: :controller do
-  render_views
-
-  let(:account) { Fabricate(:user, account: Fabricate(:account, username: 'catsrgr8')).account }
-
-  before do
-    stub_request(:get, "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt'))
-    stub_request(:get, "https://quitter.no/.well-known/webfinger?resource=acct:gargron@quitter.no").to_return(request_fixture('webfinger.txt'))
-    stub_request(:get, "https://quitter.no/api/statuses/user_timeline/7477.atom").to_return(request_fixture('feed.txt'))
-    stub_request(:get, "https://quitter.no/avatar/7477-300-20160211190340.png").to_return(request_fixture('avatar.txt'))
-  end
-
-  describe 'POST #update' do
-    context 'with valid post data' do
-      before do
-        post :update, params: { id: account.id }, body: File.read(Rails.root.join('spec', 'fixtures', 'salmon', 'mention.xml'))
-      end
-
-      it 'contains XML in the request body' do
-        expect(request.body.read).to be_a String
-      end
-
-      it 'returns http success' do
-        expect(response).to have_http_status(202)
-      end
-
-      it 'creates remote account' do
-        expect(Account.find_by(username: 'gargron', domain: 'quitter.no')).to_not be_nil
-      end
-
-      it 'creates status' do
-        expect(Status.find_by(uri: 'tag:quitter.no,2016-03-20:noticeId=1276923:objectType=note')).to_not be_nil
-      end
-
-      it 'creates mention for target account' do
-        expect(account.mentions.count).to eq 1
-      end
-    end
-
-    context 'with empty post data' do
-      before do
-        post :update, params: { id: account.id }, body: ''
-      end
-
-      it 'returns http client error' do
-        expect(response).to have_http_status(400)
-      end
-    end
-
-    context 'with invalid post data' do
-      before do
-        service = double(call: false)
-        allow(VerifySalmonService).to receive(:new).and_return(service)
-
-        post :update, params: { id: account.id }, body: File.read(Rails.root.join('spec', 'fixtures', 'salmon', 'mention.xml'))
-      end
-
-      it 'returns http client error' do
-        expect(response).to have_http_status(401)
-      end
-    end
-  end
-end
diff --git a/spec/controllers/api/subscriptions_controller_spec.rb b/spec/controllers/api/subscriptions_controller_spec.rb
deleted file mode 100644
index 7a4252fe6737ec9778099e0ad4c6a3c2c2bd07a5..0000000000000000000000000000000000000000
--- a/spec/controllers/api/subscriptions_controller_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Api::SubscriptionsController, type: :controller do
-  render_views
-
-  let(:account) { Fabricate(:account, username: 'gargron', domain: 'quitter.no', remote_url: 'topic_url', secret: 'abc') }
-
-  describe 'GET #show' do
-    context 'with valid subscription' do
-      before do
-        get :show, params: { :id => account.id, 'hub.topic' => 'topic_url', 'hub.challenge' => '456', 'hub.lease_seconds' => "#{86400 * 30}" }
-      end
-
-      it 'returns http success' do
-        expect(response).to have_http_status(200)
-      end
-
-      it 'echoes back the challenge' do
-        expect(response.body).to match '456'
-      end
-    end
-
-    context 'with invalid subscription' do
-      before do
-        expect_any_instance_of(Account).to receive_message_chain(:subscription, :valid?).and_return(false)
-        get :show, params: { :id => account.id }
-      end
-
-      it 'returns http success' do
-        expect(response).to have_http_status(404)
-      end
-    end
-  end
-
-  describe 'POST #update' do
-    let(:feed) { File.read(Rails.root.join('spec', 'fixtures', 'push', 'feed.atom')) }
-
-    before do
-      stub_request(:post, "https://quitter.no/main/push/hub").to_return(:status => 200, :body => "", :headers => {})
-      stub_request(:get, "https://quitter.no/avatar/7477-300-20160211190340.png").to_return(request_fixture('avatar.txt'))
-      stub_request(:get, "https://quitter.no/notice/1269244").to_return(status: 404)
-      stub_request(:get, "https://quitter.no/notice/1265331").to_return(status: 404)
-      stub_request(:get, "https://community.highlandarrow.com/notice/54411").to_return(status: 404)
-      stub_request(:get, "https://community.highlandarrow.com/notice/53857").to_return(status: 404)
-      stub_request(:get, "https://community.highlandarrow.com/notice/51852").to_return(status: 404)
-      stub_request(:get, "https://social.umeahackerspace.se/notice/424348").to_return(status: 404)
-      stub_request(:get, "https://community.highlandarrow.com/notice/50467").to_return(status: 404)
-      stub_request(:get, "https://quitter.no/notice/1243309").to_return(status: 404)
-      stub_request(:get, "https://quitter.no/user/7477").to_return(status: 404)
-      stub_request(:any, "https://community.highlandarrow.com/user/1").to_return(status: 404)
-      stub_request(:any, "https://social.umeahackerspace.se/user/2").to_return(status: 404)
-      stub_request(:any, "https://gs.kawa-kun.com/user/2").to_return(status: 404)
-      stub_request(:any, "https://mastodon.social/users/Gargron").to_return(status: 404)
-
-      request.env['HTTP_X_HUB_SIGNATURE'] = "sha1=#{OpenSSL::HMAC.hexdigest('sha1', 'abc', feed)}"
-
-      post :update, params: { id: account.id }, body: feed
-    end
-
-    it 'returns http success' do
-      expect(response).to have_http_status(200)
-    end
-
-    it 'creates statuses for feed' do
-      expect(account.statuses.count).to_not eq 0
-    end
-  end
-end
diff --git a/spec/controllers/api/v1/follow_requests_controller_spec.rb b/spec/controllers/api/v1/follow_requests_controller_spec.rb
index 87292d9ce1ee452e12203753d5b845da0fc405f1..ae92a9627a11346bd8b3d76c5dde929c2a7241e8 100644
--- a/spec/controllers/api/v1/follow_requests_controller_spec.rb
+++ b/spec/controllers/api/v1/follow_requests_controller_spec.rb
@@ -38,6 +38,12 @@ RSpec.describe Api::V1::FollowRequestsController, type: :controller do
     it 'allows follower to follow' do
       expect(follower.following?(user.account)).to be true
     end
+
+    it 'returns JSON with followed_by=true' do
+      json = body_as_json
+
+      expect(json[:followed_by]).to be true
+    end
   end
 
   describe 'POST #reject' do
@@ -54,5 +60,11 @@ RSpec.describe Api::V1::FollowRequestsController, type: :controller do
     it 'removes follow request' do
       expect(FollowRequest.where(target_account: user.account, account: follower).count).to eq 0
     end
+
+    it 'returns JSON with followed_by=false' do
+      json = body_as_json
+
+      expect(json[:followed_by]).to be false
+    end
   end
 end
diff --git a/spec/controllers/api/v1/follows_controller_spec.rb b/spec/controllers/api/v1/follows_controller_spec.rb
deleted file mode 100644
index 089e0fe5ecadcb5462143bda6f924d16f2324a3c..0000000000000000000000000000000000000000
--- a/spec/controllers/api/v1/follows_controller_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Api::V1::FollowsController, type: :controller do
-  render_views
-
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:follows') }
-
-  before do
-    allow(controller).to receive(:doorkeeper_token) { token }
-  end
-
-  describe 'POST #create' do
-    before do
-      stub_request(:get,  "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt'))
-      stub_request(:get,  "https://quitter.no/.well-known/webfinger?resource=acct:gargron@quitter.no").to_return(request_fixture('webfinger.txt'))
-      stub_request(:head, "https://quitter.no/api/statuses/user_timeline/7477.atom").to_return(:status => 405, :body => "", :headers => {})
-      stub_request(:get,  "https://quitter.no/api/statuses/user_timeline/7477.atom").to_return(request_fixture('feed.txt'))
-      stub_request(:get,  "https://quitter.no/avatar/7477-300-20160211190340.png").to_return(request_fixture('avatar.txt'))
-      stub_request(:post, "https://quitter.no/main/push/hub").to_return(:status => 200, :body => "", :headers => {})
-      stub_request(:post, "https://quitter.no/main/salmon/user/7477").to_return(:status => 200, :body => "", :headers => {})
-
-      post :create, params: { uri: 'gargron@quitter.no' }
-    end
-
-    it 'returns http success' do
-      expect(response).to have_http_status(200)
-    end
-
-    it 'creates account for remote user' do
-      expect(Account.find_by(username: 'gargron', domain: 'quitter.no')).to_not be_nil
-    end
-
-    it 'creates a follow relation between user and remote user' do
-      expect(user.account.following?(Account.find_by(username: 'gargron', domain: 'quitter.no'))).to be true
-    end
-
-    it 'sends a salmon slap to the remote user' do
-      expect(a_request(:post, "https://quitter.no/main/salmon/user/7477")).to have_been_made
-    end
-
-    it 'subscribes to remote hub' do
-      expect(a_request(:post, "https://quitter.no/main/push/hub")).to have_been_made
-    end
-
-    it 'returns http success if already following, too' do
-      post :create, params: { uri: 'gargron@quitter.no' }
-      expect(response).to have_http_status(200)
-    end
-  end
-end
diff --git a/spec/controllers/api/v1/markers_controller_spec.rb b/spec/controllers/api/v1/markers_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..556a75b9b1a0fb8cbe508316e793e6fa3b680608
--- /dev/null
+++ b/spec/controllers/api/v1/markers_controller_spec.rb
@@ -0,0 +1,65 @@
+require 'rails_helper'
+
+RSpec.describe Api::V1::MarkersController, type: :controller do
+  render_views
+
+  let!(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let!(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses write:statuses') }
+
+  before { allow(controller).to receive(:doorkeeper_token) { token } }
+
+  describe 'GET #index' do
+    before do
+      Fabricate(:marker, timeline: 'home', last_read_id: 123, user: user)
+      Fabricate(:marker, timeline: 'notifications', last_read_id: 456, user: user)
+
+      get :index, params: { timeline: %w(home notifications) }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'returns markers' do
+      json = body_as_json
+
+      expect(json.key?(:home)).to be true
+      expect(json[:home][:last_read_id]).to eq '123'
+      expect(json.key?(:notifications)).to be true
+      expect(json[:notifications][:last_read_id]).to eq '456'
+    end
+  end
+
+  describe 'POST #create' do
+    context 'when no marker exists' do
+      before do
+        post :create, params: { home: { last_read_id: '69420' } }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
+
+      it 'creates a marker' do
+        expect(user.markers.first.timeline).to eq 'home'
+        expect(user.markers.first.last_read_id).to eq 69420
+      end
+    end
+
+    context 'when a marker exists' do
+      before do
+        post :create, params: { home: { last_read_id: '69420' } }
+        post :create, params: { home: { last_read_id: '70120' } }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
+
+      it 'updates a marker' do
+        expect(user.markers.first.timeline).to eq 'home'
+        expect(user.markers.first.last_read_id).to eq 70120
+      end
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/search_controller_spec.rb b/spec/controllers/api/v1/search_controller_spec.rb
deleted file mode 100644
index c9e544cc790ac4fe4066b0da229526287ccbc7ab..0000000000000000000000000000000000000000
--- a/spec/controllers/api/v1/search_controller_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Api::V1::SearchController, type: :controller do
-  render_views
-
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:search') }
-
-  before do
-    allow(controller).to receive(:doorkeeper_token) { token }
-  end
-
-  describe 'GET #index' do
-    it 'returns http success' do
-      get :index, params: { q: 'test' }
-
-      expect(response).to have_http_status(200)
-    end
-  end
-end
diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb
index 8bc3b0c6718bb85cb5a40928906b8e552c2877bf..9ff5fcd3b54b46fbe7253c9de6a904223cc3170f 100644
--- a/spec/controllers/api/v1/statuses_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses_controller_spec.rb
@@ -91,13 +91,6 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
           expect(response).to have_http_status(404)
         end
       end
-
-      describe 'GET #card' do
-        it 'returns http unautharized' do
-          get :card, params: { id: status.id }
-          expect(response).to have_http_status(404)
-        end
-      end
     end
 
     context 'with a public status' do
@@ -120,13 +113,6 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
           expect(response).to have_http_status(200)
         end
       end
-
-      describe 'GET #card' do
-        it 'returns http success' do
-          get :card, params: { id: status.id }
-          expect(response).to have_http_status(200)
-        end
-      end
     end
   end
 end
diff --git a/spec/controllers/api/v1/timelines/direct_controller_spec.rb b/spec/controllers/api/v1/timelines/direct_controller_spec.rb
deleted file mode 100644
index a22c2cbea598b386671e8037c2e46b595d012718..0000000000000000000000000000000000000000
--- a/spec/controllers/api/v1/timelines/direct_controller_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Api::V1::Timelines::DirectController, type: :controller do
-  let(:user)  { Fabricate(:user) }
-  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses') }
-
-  describe 'GET #show' do
-    it 'returns 200' do
-      allow(controller).to receive(:doorkeeper_token) { token }
-      get :show
-
-      expect(response).to have_http_status(200)
-    end
-  end
-end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 33cc7108716d46f9adaf7ca1dfa7a291fadea746..da4a794cddc30d94adcdbc8c200e2cf01173987b 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -110,6 +110,7 @@ describe ApplicationController, type: :controller do
       sign_in current_user
 
       allow(Setting).to receive(:[]).with('theme').and_return 'contrast'
+      allow(Setting).to receive(:[]).with('noindex').and_return false
 
       expect(controller.view_context.current_theme).to eq 'contrast'
     end
@@ -187,10 +188,10 @@ describe ApplicationController, type: :controller do
       expect(response).to have_http_status(200)
     end
 
-    it 'returns http 403 if user who signed in is suspended' do
+    it 'redirects to account status page' do
       sign_in(Fabricate(:user, account: Fabricate(:account, suspended: true)))
       get 'success'
-      expect(response).to have_http_status(403)
+      expect(response).to redirect_to(edit_user_registration_path)
     end
   end
 
@@ -360,9 +361,5 @@ describe ApplicationController, type: :controller do
     context 'Status' do
       include_examples 'cacheable', :status, Status
     end
-
-    context 'StreamEntry' do
-      include_examples 'receives :with_includes', :stream_entry, StreamEntry
-    end
   end
 end
diff --git a/spec/controllers/auth/challenges_controller_spec.rb b/spec/controllers/auth/challenges_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2a6ca301ef702c246a988244182f5acc506c7257
--- /dev/null
+++ b/spec/controllers/auth/challenges_controller_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Auth::ChallengesController, type: :controller do
+  render_views
+
+  let(:password) { 'foobar12345' }
+  let(:user) { Fabricate(:user, password: password) }
+
+  before do
+    sign_in user
+  end
+
+  describe 'POST #create' do
+    let(:return_to) { edit_user_registration_path }
+
+    context 'with correct password' do
+      before { post :create, params: { form_challenge: { return_to: return_to, current_password: password } } }
+
+      it 'redirects back' do
+        expect(response).to redirect_to(return_to)
+      end
+
+      it 'sets session' do
+        expect(session[:challenge_passed_at]).to_not be_nil
+      end
+    end
+
+    context 'with incorrect password' do
+      before { post :create, params: { form_challenge: { return_to: return_to, current_password: 'hhfggjjd562' } } }
+
+      it 'renders challenge' do
+        expect(response).to render_template('auth/challenges/new')
+      end
+
+      it 'displays error' do
+        expect(response.body).to include 'Invalid password'
+      end
+
+      it 'does not set session' do
+        expect(session[:challenge_passed_at]).to be_nil
+      end
+    end
+  end
+end
diff --git a/spec/controllers/auth/confirmations_controller_spec.rb b/spec/controllers/auth/confirmations_controller_spec.rb
index e9a471fc5a9e2700beb7f462526bac3b0adbbfb7..0b6b74ff902c9f45c7915c1803ee05bc65d06aa2 100644
--- a/spec/controllers/auth/confirmations_controller_spec.rb
+++ b/spec/controllers/auth/confirmations_controller_spec.rb
@@ -50,45 +50,4 @@ describe Auth::ConfirmationsController, type: :controller do
       end
     end
   end
-
-  describe 'GET #finish_signup' do
-    subject { get :finish_signup }
-
-    let(:user) { Fabricate(:user) }
-    before do
-      sign_in user, scope: :user
-      @request.env['devise.mapping'] = Devise.mappings[:user]
-    end
-
-    it 'renders finish_signup' do
-      is_expected.to render_template :finish_signup
-      expect(assigns(:user)).to have_attributes id: user.id
-    end
-  end
-
-  describe 'PATCH #finish_signup' do
-    subject { patch :finish_signup, params: { user: { email: email } } }
-
-    let(:user) { Fabricate(:user) }
-    before do
-      sign_in user, scope: :user
-      @request.env['devise.mapping'] = Devise.mappings[:user]
-    end
-
-    context 'when email is valid' do
-      let(:email) { 'new_' + user.email }
-
-      it 'redirects to root_path' do
-        is_expected.to redirect_to root_path
-      end
-    end
-
-    context 'when email is invalid' do
-      let(:email) { '' }
-
-      it 'renders finish_signup' do
-        is_expected.to render_template :finish_signup
-      end
-    end
-  end
 end
diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb
index a4337039e154f6c7993a9c6535f646dc7616abc5..3e11b34b5398fa7d155ac2c81f6a7d9207846366 100644
--- a/spec/controllers/auth/registrations_controller_spec.rb
+++ b/spec/controllers/auth/registrations_controller_spec.rb
@@ -46,6 +46,15 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
       post :update
       expect(response).to have_http_status(200)
     end
+
+    context 'when suspended' do
+      it 'returns http forbidden' do
+        request.env["devise.mapping"] = Devise.mappings[:user]
+        sign_in(Fabricate(:user, account_attributes: { username: 'test', suspended_at: Time.now.utc }), scope: :user)
+        post :update
+        expect(response).to have_http_status(403)
+      end
+    end
   end
 
   describe 'GET #new' do
@@ -94,9 +103,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
       end
 
-      it 'redirects to login page' do
+      it 'redirects to setup' do
         subject
-        expect(response).to redirect_to new_user_session_path
+        expect(response).to redirect_to auth_setup_path
       end
 
       it 'creates user' do
@@ -120,9 +129,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
       end
 
-      it 'redirects to login page' do
+      it 'redirects to setup' do
         subject
-        expect(response).to redirect_to new_user_session_path
+        expect(response).to redirect_to auth_setup_path
       end
 
       it 'creates user' do
@@ -148,9 +157,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
       end
 
-      it 'redirects to login page' do
+      it 'redirects to setup' do
         subject
-        expect(response).to redirect_to new_user_session_path
+        expect(response).to redirect_to auth_setup_path
       end
 
       it 'creates user' do
@@ -176,9 +185,9 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
         post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
       end
 
-      it 'redirects to login page' do
+      it 'redirects to setup' do
         subject
-        expect(response).to redirect_to new_user_session_path
+        expect(response).to redirect_to auth_setup_path
       end
 
       it 'creates user' do
diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb
index 71fcc1a6e3d2a65b63a313455436446d876670fc..1950c173af33595a1b18d9f9d452b54229aadffc 100644
--- a/spec/controllers/auth/sessions_controller_spec.rb
+++ b/spec/controllers/auth/sessions_controller_spec.rb
@@ -5,11 +5,11 @@ require 'rails_helper'
 RSpec.describe Auth::SessionsController, type: :controller do
   render_views
 
-  describe 'GET #new' do
-    before do
-      request.env['devise.mapping'] = Devise.mappings[:user]
-    end
+  before do
+    request.env['devise.mapping'] = Devise.mappings[:user]
+  end
 
+  describe 'GET #new' do
     it 'returns http success' do
       get :new
       expect(response).to have_http_status(200)
@@ -19,10 +19,6 @@ RSpec.describe Auth::SessionsController, type: :controller do
   describe 'DELETE #destroy' do
     let(:user) { Fabricate(:user) }
 
-    before do
-      request.env['devise.mapping'] = Devise.mappings[:user]
-    end
-
     context 'with a regular user' do
       it 'redirects to home after sign out' do
         sign_in(user, scope: :user)
@@ -51,10 +47,6 @@ RSpec.describe Auth::SessionsController, type: :controller do
   end
 
   describe 'POST #create' do
-    before do
-      request.env['devise.mapping'] = Devise.mappings[:user]
-    end
-
     context 'using PAM authentication', if: ENV['PAM_ENABLED'] == 'true' do
       context 'using a valid password' do
         before do
@@ -88,7 +80,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
         let(:user) do
           account = Fabricate.build(:account, username: 'pam_user1')
           account.save!(validate: false)
-          user = Fabricate(:user, email: 'pam@example.com', password: nil, account: account)
+          user = Fabricate(:user, email: 'pam@example.com', password: nil, account: account, external: true)
           user
         end
 
@@ -160,8 +152,8 @@ RSpec.describe Auth::SessionsController, type: :controller do
         let(:unconfirmed_user) { user.tap { |u| u.update!(confirmed_at: nil) } }
         let(:accept_language) { 'fr' }
 
-        it 'shows a translated login error' do
-          expect(flash[:alert]).to eq(I18n.t('devise.failure.unconfirmed', locale: accept_language))
+        it 'redirects to home' do
+          expect(response).to redirect_to(root_path)
         end
       end
 
@@ -191,11 +183,11 @@ RSpec.describe Auth::SessionsController, type: :controller do
     end
 
     context 'using two-factor authentication' do
-      let(:user) do
-        Fabricate(:user, email: 'x@y.com', password: 'abcdefgh',
-                         otp_required_for_login: true, otp_secret: User.generate_otp_secret(32))
+      let!(:user) do
+        Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32))
       end
-      let(:recovery_codes) do
+
+      let!(:recovery_codes) do
         codes = user.generate_otp_backup_codes!
         user.save
         return codes
diff --git a/spec/controllers/concerns/account_controller_concern_spec.rb b/spec/controllers/concerns/account_controller_concern_spec.rb
index ea2b4a2a1deb1d62da709029b1a6ae95d48bc806..7ea214a7d3fb5fd243e1bfdc572d4527d834d14a 100644
--- a/spec/controllers/concerns/account_controller_concern_spec.rb
+++ b/spec/controllers/concerns/account_controller_concern_spec.rb
@@ -41,7 +41,7 @@ describe ApplicationController, type: :controller do
     it 'sets link headers' do
       account = Fabricate(:account, username: 'username', user: Fabricate(:user))
       get 'success', params: { account_username: 'username' }
-      expect(response.headers['Link'].to_s).to eq '<http://test.host/.well-known/webfinger?resource=acct%3Ausername%40cb6e6126.ngrok.io>; rel="lrdd"; type="application/xrd+xml", <http://test.host/users/username.atom>; rel="alternate"; type="application/atom+xml", <https://cb6e6126.ngrok.io/users/username>; rel="alternate"; type="application/activity+json"'
+      expect(response.headers['Link'].to_s).to eq '<http://test.host/.well-known/webfinger?resource=acct%3Ausername%40cb6e6126.ngrok.io>; rel="lrdd"; type="application/jrd+json", <https://cb6e6126.ngrok.io/users/username>; rel="alternate"; type="application/activity+json"'
     end
 
     it 'returns http success' do
diff --git a/spec/controllers/concerns/challengable_concern_spec.rb b/spec/controllers/concerns/challengable_concern_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4db3b740db23ab775a1c27645327e2f67671f7ed
--- /dev/null
+++ b/spec/controllers/concerns/challengable_concern_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe ChallengableConcern, type: :controller do
+  controller(ApplicationController) do
+    include ChallengableConcern
+
+    before_action :require_challenge!
+
+    def foo
+      render plain: 'foo'
+    end
+
+    def bar
+      render plain: 'bar'
+    end
+  end
+
+  before do
+    routes.draw do
+      get  'foo' => 'anonymous#foo'
+      post 'bar' => 'anonymous#bar'
+    end
+  end
+
+  context 'with a no-password user' do
+    let(:user) { Fabricate(:user, external: true, password: nil) }
+
+    before do
+      sign_in user
+    end
+
+    context 'for GET requests' do
+      before { get :foo }
+
+      it 'does not ask for password' do
+        expect(response.body).to eq 'foo'
+      end
+    end
+
+    context 'for POST requests' do
+      before { post :bar }
+
+      it 'does not ask for password' do
+        expect(response.body).to eq 'bar'
+      end
+    end
+  end
+
+  context 'with recent challenge in session' do
+    let(:password) { 'foobar12345' }
+    let(:user) { Fabricate(:user, password: password) }
+
+    before do
+      sign_in user
+    end
+
+    context 'for GET requests' do
+      before { get :foo, session: { challenge_passed_at: Time.now.utc } }
+
+      it 'does not ask for password' do
+        expect(response.body).to eq 'foo'
+      end
+    end
+
+    context 'for POST requests' do
+      before { post :bar, session: { challenge_passed_at: Time.now.utc } }
+
+      it 'does not ask for password' do
+        expect(response.body).to eq 'bar'
+      end
+    end
+  end
+
+  context 'with a password user' do
+    let(:password) { 'foobar12345' }
+    let(:user) { Fabricate(:user, password: password) }
+
+    before do
+      sign_in user
+    end
+
+    context 'for GET requests' do
+      before { get :foo }
+
+      it 'renders challenge' do
+        expect(response).to render_template('auth/challenges/new')
+      end
+
+      # See Auth::ChallengesControllerSpec
+    end
+
+    context 'for POST requests' do
+      before { post :bar }
+
+      it 'renders challenge' do
+        expect(response).to render_template('auth/challenges/new')
+      end
+
+      it 'accepts correct password' do
+        post :bar, params: { form_challenge: { current_password: password } }
+        expect(response.body).to eq 'bar'
+        expect(session[:challenge_passed_at]).to_not be_nil
+      end
+
+      it 'rejects wrong password' do
+        post :bar, params: { form_challenge: { current_password: 'dddfff888123' } }
+        expect(response.body).to render_template('auth/challenges/new')
+        expect(session[:challenge_passed_at]).to be_nil
+      end
+    end
+  end
+end
diff --git a/spec/controllers/concerns/signature_verification_spec.rb b/spec/controllers/concerns/signature_verification_spec.rb
index 72069009716267e8b3b086f36c8c9b131705782a..1fa19f54d78df7d69b5d0b71268e1a3e492d30b5 100644
--- a/spec/controllers/concerns/signature_verification_spec.rb
+++ b/spec/controllers/concerns/signature_verification_spec.rb
@@ -38,7 +38,7 @@ describe ApplicationController, type: :controller do
   end
 
   context 'with signature header' do
-    let!(:author) { Fabricate(:account) }
+    let!(:author) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/actor') }
 
     context 'without body' do
       before do
diff --git a/spec/controllers/home_controller_spec.rb b/spec/controllers/home_controller_spec.rb
index f43cf0c27ec510ab3d06d736f819430815ee73ef..941f1dd9146cb8ba461cb6172ee1eed466a659bc 100644
--- a/spec/controllers/home_controller_spec.rb
+++ b/spec/controllers/home_controller_spec.rb
@@ -27,16 +27,6 @@ RSpec.describe HomeController, type: :controller do
         subject
         expect(assigns(:body_classes)).to eq 'app-body'
       end
-
-      it 'assigns @initial_state_json' do
-        subject
-        initial_state_json = json_str_to_hash(assigns(:initial_state_json))
-        expect(initial_state_json[:meta]).to_not be_nil
-        expect(initial_state_json[:compose]).to_not be_nil
-        expect(initial_state_json[:accounts]).to_not be_nil
-        expect(initial_state_json[:settings]).to_not be_nil
-        expect(initial_state_json[:media_attachments]).to_not be_nil
-      end
     end
   end
 end
diff --git a/spec/controllers/remote_follow_controller_spec.rb b/spec/controllers/remote_follow_controller_spec.rb
index 5088c2e656bf8b508801cf4195de762cc07bb70b..d79dd294952cd55baccad59ffdbcc45d932b172b 100644
--- a/spec/controllers/remote_follow_controller_spec.rb
+++ b/spec/controllers/remote_follow_controller_spec.rb
@@ -66,9 +66,7 @@ describe RemoteFollowController do
         end
 
         it 'redirects to the remote location' do
-          address = "http://example.com/follow_me?acct=test_user%40#{Rails.configuration.x.local_domain}"
-
-          expect(response).to redirect_to(address)
+          expect(response).to redirect_to("http://example.com/follow_me?acct=https%3A%2F%2F#{Rails.configuration.x.local_domain}%2Fusers%2Ftest_user")
         end
       end
     end
diff --git a/spec/controllers/remote_unfollows_controller_spec.rb b/spec/controllers/remote_unfollows_controller_spec.rb
deleted file mode 100644
index a1a55ede0b19906a74904fe5b5566409df7cd154..0000000000000000000000000000000000000000
--- a/spec/controllers/remote_unfollows_controller_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe RemoteUnfollowsController do
-  render_views
-
-  describe '#create' do
-    subject { post :create, params: { acct: acct } }
-
-    let(:current_user) { Fabricate(:user, account: current_account) }
-    let(:current_account) { Fabricate(:account) }
-    let(:remote_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
-    before do
-      sign_in current_user
-      current_account.follow!(remote_account)
-      stub_request(:post, 'http://example.com/inbox') { { status: 200 } }
-    end
-
-    context 'when successfully unfollow remote account' do
-      let(:acct) { "acct:#{remote_account.username}@#{remote_account.domain}" }
-
-      it do
-        is_expected.to render_template :success
-        expect(current_account.following?(remote_account)).to be false
-      end
-    end
-
-    context 'when fails to unfollow remote account' do
-      let(:acct) { "acct:#{remote_account.username + '_test'}@#{remote_account.domain}" }
-
-      it do
-        is_expected.to render_template :error
-        expect(current_account.following?(remote_account)).to be true
-      end
-    end
-  end
-end
diff --git a/spec/controllers/settings/deletes_controller_spec.rb b/spec/controllers/settings/deletes_controller_spec.rb
index 35fd64e9b9805329bf5a7af97bbaac10d2ceb027..996872efd1af639726dc957e2981fd7787559c7d 100644
--- a/spec/controllers/settings/deletes_controller_spec.rb
+++ b/spec/controllers/settings/deletes_controller_spec.rb
@@ -15,6 +15,15 @@ describe Settings::DeletesController do
         get :show
         expect(response).to have_http_status(200)
       end
+
+      context 'when suspended' do
+        let(:user) { Fabricate(:user, account_attributes: { username: 'alice', suspended_at: Time.now.utc }) }
+
+        it 'returns http forbidden' do
+          get :show
+          expect(response).to have_http_status(403)
+        end
+      end
     end
 
     context 'when not signed in' do
@@ -49,6 +58,14 @@ describe Settings::DeletesController do
         it 'marks account as suspended' do
           expect(user.account.reload).to be_suspended
         end
+
+        context 'when suspended' do
+          let(:user) { Fabricate(:user, account_attributes: { username: 'alice', suspended_at: Time.now.utc }) }
+
+          it 'returns http forbidden' do
+            expect(response).to have_http_status(403)
+          end
+        end
       end
 
       context 'with incorrect password' do
diff --git a/spec/controllers/settings/identity_proofs_controller_spec.rb b/spec/controllers/settings/identity_proofs_controller_spec.rb
index 2a0f91088a9bbc9df92f0cb9774ae3b71367c6b6..261e980d4bec766bc330421b1aaba661c07c1e73 100644
--- a/spec/controllers/settings/identity_proofs_controller_spec.rb
+++ b/spec/controllers/settings/identity_proofs_controller_spec.rb
@@ -8,8 +8,8 @@ describe Settings::IdentityProofsController do
   let(:valid_token) { '1'*66 }
   let(:kbname) { 'kbuser' }
   let(:provider) { 'keybase' }
-  let(:findable_id) { Faker::Number.number(5) }
-  let(:unfindable_id) { Faker::Number.number(5) }
+  let(:findable_id) { Faker::Number.number(digits: 5) }
+  let(:unfindable_id) { Faker::Number.number(digits: 5) }
   let(:new_proof_params) do
     { provider: provider, provider_username: kbname, token: valid_token, username: user.account.username }
   end
diff --git a/spec/controllers/settings/migrations_controller_spec.rb b/spec/controllers/settings/migrations_controller_spec.rb
index 4d814a45e2977013e2042f25f5b8d8a48a24715c..36e4ba86e467af182843ce8882dac4614268d728 100644
--- a/spec/controllers/settings/migrations_controller_spec.rb
+++ b/spec/controllers/settings/migrations_controller_spec.rb
@@ -21,6 +21,7 @@ describe Settings::MigrationsController do
 
       let(:user) { Fabricate(:user, account: account) }
       let(:account) { Fabricate(:account, moved_to_account: moved_to_account) }
+
       before { sign_in user, scope: :user }
 
       context 'when user does not have moved to account' do
@@ -32,7 +33,7 @@ describe Settings::MigrationsController do
         end
       end
 
-      context 'when user does not have moved to account' do
+      context 'when user has a moved to account' do
         let(:moved_to_account) { Fabricate(:account) }
 
         it 'renders show page' do
@@ -43,21 +44,22 @@ describe Settings::MigrationsController do
     end
   end
 
-  describe 'PUT #update' do
+  describe 'POST #create' do
     context 'when user is not sign in' do
-      subject { put :update }
+      subject { post :create }
 
       it_behaves_like 'authenticate user'
     end
 
     context 'when user is sign in' do
-      subject { put :update, params: { migration: { acct: acct } } }
+      subject { post :create, params: { account_migration: { acct: acct, current_password: '12345678' } } }
+
+      let(:user) { Fabricate(:user, password: '12345678') }
 
-      let(:user) { Fabricate(:user) }
       before { sign_in user, scope: :user }
 
       context 'when migration account is changed' do
-        let(:acct) { Fabricate(:account) }
+        let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) }
 
         it 'updates moved to account' do
           is_expected.to redirect_to settings_migration_path
diff --git a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb
index 478f2458552a71f7d7e575ec3b7adcfa379ca77f..336f131279fc11a61269c69cd1463da922b8aa86 100644
--- a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb
+++ b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb
@@ -24,7 +24,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
     context 'when signed in' do
       subject do
         sign_in user, scope: :user
-        get :new
+        get :new, session: { challenge_passed_at: Time.now.utc }
       end
 
       include_examples 'renders :new'
@@ -37,7 +37,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
 
     it 'redirects if user do not have otp_secret' do
       sign_in user_without_otp_secret, scope: :user
-      get :new
+      get :new, session: { challenge_passed_at: Time.now.utc }
       expect(response).to redirect_to('/settings/two_factor_authentication')
     end
   end
@@ -50,7 +50,8 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
 
       describe 'when form_two_factor_confirmation parameter is not provided' do
         it 'raises ActionController::ParameterMissing' do
-          expect { post :create, params: {} }.to raise_error(ActionController::ParameterMissing)
+          post :create, params: {}, session: { challenge_passed_at: Time.now.utc }
+          expect(response).to have_http_status(400)
         end
       end
 
@@ -67,7 +68,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
             true
           end
 
-          post :create, params: { form_two_factor_confirmation: { code: '123456' } }
+          post :create, params: { form_two_factor_confirmation: { otp_attempt: '123456' } }, session: { challenge_passed_at: Time.now.utc }
 
           expect(assigns(:recovery_codes)).to eq otp_backup_codes
           expect(flash[:notice]).to eq 'Two-factor authentication successfully enabled'
@@ -84,7 +85,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
             false
           end
 
-          post :create, params: { form_two_factor_confirmation: { code: '123456' } }
+          post :create, params: { form_two_factor_confirmation: { otp_attempt: '123456' } }, session: { challenge_passed_at: Time.now.utc }
         end
 
         it 'renders the new view' do
@@ -98,7 +99,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
 
     context 'when not signed in' do
       it 'redirects if not signed in' do
-        post :create, params: { form_two_factor_confirmation: { code: '123456' } }
+        post :create, params: { form_two_factor_confirmation: { otp_attempt: '123456' } }
         expect(response).to redirect_to('/auth/sign_in')
       end
     end
diff --git a/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb
index c04760e535efbe9c13e12c5f2f161cf0b63fd804..630cec428e7264298987568fa30f72a7b98711d9 100644
--- a/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb
+++ b/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb
@@ -15,7 +15,7 @@ describe Settings::TwoFactorAuthentication::RecoveryCodesController do
       end
 
       sign_in user, scope: :user
-      post :create
+      post :create, session: { challenge_passed_at: Time.now.utc }
 
       expect(assigns(:recovery_codes)).to eq otp_backup_codes
       expect(flash[:notice]).to eq 'Recovery codes successfully regenerated'
diff --git a/spec/controllers/settings/two_factor_authentications_controller_spec.rb b/spec/controllers/settings/two_factor_authentications_controller_spec.rb
index 9f27222ad37c2a55a2b3026388f40de84d4aacca..9df9763fd33bd848db890502e20d45da159b848e 100644
--- a/spec/controllers/settings/two_factor_authentications_controller_spec.rb
+++ b/spec/controllers/settings/two_factor_authentications_controller_spec.rb
@@ -58,7 +58,7 @@ describe Settings::TwoFactorAuthenticationsController do
       describe 'when creation succeeds' do
         it 'updates user secret' do
           before = user.otp_secret
-          post :create
+          post :create, session: { challenge_passed_at: Time.now.utc }
 
           expect(user.reload.otp_secret).not_to eq(before)
           expect(response).to redirect_to(new_settings_two_factor_authentication_confirmation_path)
@@ -91,7 +91,7 @@ describe Settings::TwoFactorAuthenticationsController do
           true
         end
 
-        post :destroy, params: { form_two_factor_confirmation: { code: '123456' } }
+        post :destroy, params: { form_two_factor_confirmation: { otp_attempt: '123456' } }
 
         expect(response).to redirect_to(settings_two_factor_authentication_path)
         user.reload
@@ -105,14 +105,15 @@ describe Settings::TwoFactorAuthenticationsController do
           false
         end
 
-        post :destroy, params: { form_two_factor_confirmation: { code: '057772' } }
+        post :destroy, params: { form_two_factor_confirmation: { otp_attempt: '057772' } }
 
         user.reload
         expect(user.otp_required_for_login).to eq(true)
       end
 
       it 'raises ActionController::ParameterMissing if code is missing' do
-        expect { post :destroy }.to raise_error(ActionController::ParameterMissing)
+        post :destroy
+        expect(response).to have_http_status(400)
       end
     end
 
diff --git a/spec/controllers/shares_controller_spec.rb b/spec/controllers/shares_controller_spec.rb
index a74e9af561cc66b81f26079db1cbbc60bc611023..d6de3016ae47c3a5234b70a9a2447c285b896ba5 100644
--- a/spec/controllers/shares_controller_spec.rb
+++ b/spec/controllers/shares_controller_spec.rb
@@ -7,15 +7,12 @@ describe SharesController do
   before { sign_in user }
 
   describe 'GTE #show' do
-    subject(:initial_state_json) { JSON.parse(assigns(:initial_state_json), symbolize_names: true) }
     subject(:body_classes) { assigns(:body_classes) }
 
     before { get :show, params: { title: 'test title', text: 'test text', url: 'url1 url2' } }
 
-    it 'assigns json' do
+    it 'returns http success' do
       expect(response).to have_http_status :ok
-      expect(initial_state_json[:compose][:text]).to eq 'test title test text url1 url2'
-      expect(initial_state_json[:meta][:me]).to eq user.account.id.to_s
       expect(body_classes).to eq 'modal-layout compose-standalone'
     end
   end
diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb
index 1bb6636c603e8827e4f3e5594fdd01dbc30bd3a3..6905dae10e3b12156b0268f0c8fe170e13cb1237 100644
--- a/spec/controllers/statuses_controller_spec.rb
+++ b/spec/controllers/statuses_controller_spec.rb
@@ -55,18 +55,6 @@ describe StatusesController do
         expect(assigns(:status)).to eq status
       end
 
-      it 'assigns @stream_entry' do
-        status = Fabricate(:status)
-        get :show, params: { account_username: status.account.username, id: status.id }
-        expect(assigns(:stream_entry)).to eq status.stream_entry
-      end
-
-      it 'assigns @type' do
-        status = Fabricate(:status)
-        get :show, params: { account_username: status.account.username, id: status.id }
-        expect(assigns(:type)).to eq 'status'
-      end
-
       it 'assigns @ancestors for ancestors of the status if it is a reply' do
         ancestor = Fabricate(:status)
         status = Fabricate(:status, in_reply_to_id: ancestor.id)
@@ -104,7 +92,7 @@ describe StatusesController do
       end
 
       it 'assigns @max_descendant_thread_id for the last thread if it is hitting the status limit' do
-        stub_const 'StatusesController::DESCENDANTS_LIMIT', 1
+        stub_const 'StatusControllerConcern::DESCENDANTS_LIMIT', 1
         status = Fabricate(:status)
         child = Fabricate(:status, in_reply_to_id: status.id)
 
@@ -115,7 +103,7 @@ describe StatusesController do
       end
 
       it 'assigns @descendant_threads for threads with :next_status key if they are hitting the depth limit' do
-        stub_const 'StatusesController::DESCENDANTS_DEPTH_LIMIT', 2
+        stub_const 'StatusControllerConcern::DESCENDANTS_DEPTH_LIMIT', 2
         status = Fabricate(:status)
         child0 = Fabricate(:status, in_reply_to_id: status.id)
         child1 = Fabricate(:status, in_reply_to_id: child0.id)
@@ -135,10 +123,10 @@ describe StatusesController do
         expect(response).to have_http_status(200)
       end
 
-      it 'renders stream_entries/show' do
+      it 'renders statuses/show' do
         status = Fabricate(:status)
         get :show, params: { account_username: status.account.username, id: status.id }
-        expect(response).to render_template 'stream_entries/show'
+        expect(response).to render_template 'statuses/show'
       end
     end
   end
diff --git a/spec/controllers/stream_entries_controller_spec.rb b/spec/controllers/stream_entries_controller_spec.rb
deleted file mode 100644
index eb7fdf9d7823d284c21ccfde3b9ad4248ba280dc..0000000000000000000000000000000000000000
--- a/spec/controllers/stream_entries_controller_spec.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe StreamEntriesController, type: :controller do
-  render_views
-
-  shared_examples 'before_action' do |route|
-    context 'when account is not suspended and stream_entry is available' do
-      it 'assigns instance variables' do
-        status = Fabricate(:status)
-
-        get route, params: { account_username: status.account.username, id: status.stream_entry.id }
-
-        expect(assigns(:account)).to eq status.account
-        expect(assigns(:stream_entry)).to eq status.stream_entry
-        expect(assigns(:type)).to eq 'status'
-      end
-
-      it 'sets Link headers' do
-        alice = Fabricate(:account, username: 'alice')
-        status = Fabricate(:status, account: alice)
-
-        get route, params: { account_username: alice.username, id: status.stream_entry.id }
-
-        expect(response.headers['Link'].to_s).to eq "<http://test.host/users/alice/updates/#{status.stream_entry.id}.atom>; rel=\"alternate\"; type=\"application/atom+xml\", <https://cb6e6126.ngrok.io/users/alice/statuses/#{status.id}>; rel=\"alternate\"; type=\"application/activity+json\""
-      end
-    end
-
-    context 'when account is suspended' do
-      it 'returns http status 410' do
-        account = Fabricate(:account, suspended: true)
-        status = Fabricate(:status, account: account)
-
-        get route, params: { account_username: account.username, id: status.stream_entry.id }
-
-        expect(response).to have_http_status(410)
-      end
-    end
-
-    context 'when activity is nil' do
-      it 'raises ActiveRecord::RecordNotFound' do
-        account = Fabricate(:account)
-        stream_entry = Fabricate.build(:stream_entry, account: account, activity: nil, activity_type: 'Status')
-        stream_entry.save!(validate: false)
-
-        get route, params: { account_username: account.username, id: stream_entry.id }
-
-        expect(response).to have_http_status(404)
-      end
-    end
-
-    context 'when it is hidden and it is not permitted' do
-      it 'raises ActiveRecord::RecordNotFound' do
-        status = Fabricate(:status)
-        user = Fabricate(:user)
-        status.account.block!(user.account)
-        status.stream_entry.update!(hidden: true)
-
-        sign_in(user)
-        get route, params: { account_username: status.account.username, id: status.stream_entry.id }
-
-        expect(response).to have_http_status(404)
-      end
-    end
-  end
-
-  describe 'GET #show' do
-    include_examples 'before_action', :show
-
-    it 'redirects to status page' do
-      status = Fabricate(:status)
-
-      get :show, params: { account_username: status.account.username, id: status.stream_entry.id }
-
-      expect(response).to redirect_to(short_account_status_url(status.account, status))
-    end
-
-    it 'returns http success with Atom' do
-      status = Fabricate(:status)
-      get :show, params: { account_username: status.account.username, id: status.stream_entry.id }, format: 'atom'
-      expect(response).to have_http_status(200)
-    end
-  end
-
-  describe 'GET #embed' do
-    include_examples 'before_action', :embed
-
-    it 'redirects to new embed page' do
-      status = Fabricate(:status)
-
-      get :embed, params: { account_username: status.account.username, id: status.stream_entry.id }
-
-      expect(response).to redirect_to(embed_short_account_status_url(status.account, status))
-    end
-  end
-end
diff --git a/spec/controllers/well_known/nodeinfo_controller_spec.rb b/spec/controllers/well_known/nodeinfo_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..12e1fa4159ce0113fadf73d27e19ba9620dd8c60
--- /dev/null
+++ b/spec/controllers/well_known/nodeinfo_controller_spec.rb
@@ -0,0 +1,36 @@
+require 'rails_helper'
+
+describe WellKnown::NodeInfoController, type: :controller do
+  render_views
+
+  describe 'GET #index' do
+    it 'returns json document pointing to node info' do
+      get :index
+
+      expect(response).to have_http_status(200)
+      expect(response.content_type).to eq 'application/json'
+
+      json = body_as_json
+
+      expect(json[:links]).to be_an Array
+      expect(json[:links][0][:rel]).to eq 'http://nodeinfo.diaspora.software/ns/schema/2.0'
+      expect(json[:links][0][:href]).to include 'nodeinfo/2.0'
+    end
+  end
+
+  describe 'GET #show' do
+    it 'returns json document with node info properties' do
+      get :show
+
+      expect(response).to have_http_status(200)
+      expect(response.content_type).to eq 'application/json'
+
+      json = body_as_json
+
+      expect(json[:version]).to eq '2.0'
+      expect(json[:usage]).to be_a Hash
+      expect(json[:software]).to be_a Hash
+      expect(json[:protocols]).to be_an Array
+    end
+  end
+end
diff --git a/spec/controllers/well_known/webfinger_controller_spec.rb b/spec/controllers/well_known/webfinger_controller_spec.rb
index b05745ea3bf0448536d6aa4f26adc8fa1cd36ce2..20275aa63bc33f958a2e3cfe2575c44800067fad 100644
--- a/spec/controllers/well_known/webfinger_controller_spec.rb
+++ b/spec/controllers/well_known/webfinger_controller_spec.rb
@@ -56,17 +56,6 @@ PEM
       expect(json[:aliases]).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice')
     end
 
-    it 'returns JSON when account can be found' do
-      get :show, params: { resource: alice.to_webfinger_s }, format: :xml
-
-      xml = Nokogiri::XML(response.body)
-
-      expect(response).to have_http_status(200)
-      expect(response.content_type).to eq 'application/xrd+xml'
-      expect(xml.at_xpath('//xmlns:Subject').content).to eq 'acct:alice@cb6e6126.ngrok.io'
-      expect(xml.xpath('//xmlns:Alias').map(&:content)).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice')
-    end
-
     it 'returns http not found when account cannot be found' do
       get :show, params: { resource: 'acct:not@existing.com' }, format: :json
 
diff --git a/spec/fabricators/account_alias_fabricator.rb b/spec/fabricators/account_alias_fabricator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..94dde9bb8c1b4c1815edf5a4fd53a255f472bad5
--- /dev/null
+++ b/spec/fabricators/account_alias_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:account_alias) do
+  account
+  acct 'test@example.com'
+  uri 'https://example.com/users/test'
+end
diff --git a/spec/fabricators/account_fabricator.rb b/spec/fabricators/account_fabricator.rb
index f12464ef3e675a6728d867d4c41735f09c9f0128..ab900c5fa03edbb5499bacf173bdcc440f21b440 100644
--- a/spec/fabricators/account_fabricator.rb
+++ b/spec/fabricators/account_fabricator.rb
@@ -4,7 +4,7 @@ private_key = keypair.to_pem
 
 Fabricator(:account) do
   transient :suspended, :silenced
-  username            { sequence(:username) { |i| "#{Faker::Internet.user_name(nil, %w(_))}#{i}" } }
+  username            { sequence(:username) { |i| "#{Faker::Internet.user_name(separators: %w(_))}#{i}" } }
   last_webfingered_at { Time.now.utc }
   public_key          { public_key }
   private_key         { private_key }
diff --git a/spec/fabricators/account_identity_proof_fabricator.rb b/spec/fabricators/account_identity_proof_fabricator.rb
index 94f40dfd6b7cdf31f4e7e88c8df51588cc980a4b..7b932fa968792347974219f1b28536f67f8194ce 100644
--- a/spec/fabricators/account_identity_proof_fabricator.rb
+++ b/spec/fabricators/account_identity_proof_fabricator.rb
@@ -1,7 +1,7 @@
 Fabricator(:account_identity_proof) do
   account
   provider 'keybase'
-  provider_username { sequence(:provider_username) { |i| "#{Faker::Lorem.characters(15)}" } }
+  provider_username { sequence(:provider_username) { |i| "#{Faker::Lorem.characters(number: 15)}" } }
   token { sequence(:token) { |i| "#{i}#{Faker::Crypto.sha1()*2}"[0..65] } }
   verified false
   live false
diff --git a/spec/fabricators/account_migration_fabricator.rb b/spec/fabricators/account_migration_fabricator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3b3fc20773a0c4ebddecc6c4197038596e0a5a3b
--- /dev/null
+++ b/spec/fabricators/account_migration_fabricator.rb
@@ -0,0 +1,6 @@
+Fabricator(:account_migration) do
+  account
+  target_account
+  followers_count 1234
+  acct 'test@example.com'
+end
diff --git a/spec/fabricators/custom_emoji_category_fabricator.rb b/spec/fabricators/custom_emoji_category_fabricator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f593b95ed444d7e7d01112dc605febfe8652c72e
--- /dev/null
+++ b/spec/fabricators/custom_emoji_category_fabricator.rb
@@ -0,0 +1,3 @@
+Fabricator(:custom_emoji_category) do
+  name "MyString"
+end
diff --git a/spec/fabricators/domain_allow_fabricator.rb b/spec/fabricators/domain_allow_fabricator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6226b1e20b265e8efe87f2a13693c5ffc3f72aad
--- /dev/null
+++ b/spec/fabricators/domain_allow_fabricator.rb
@@ -0,0 +1,3 @@
+Fabricator(:domain_allow) do
+  domain "MyString"
+end
diff --git a/spec/fabricators/marker_fabricator.rb b/spec/fabricators/marker_fabricator.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0c94150e0dc651b90db28d6d3eda4a71babb94b3
--- /dev/null
+++ b/spec/fabricators/marker_fabricator.rb
@@ -0,0 +1,6 @@
+Fabricator(:marker) do
+  user
+  timeline     'home'
+  last_read_id 0
+  lock_version 0
+end
diff --git a/spec/fabricators/stream_entry_fabricator.rb b/spec/fabricators/stream_entry_fabricator.rb
deleted file mode 100644
index f33822c7c593a520062582014034b7c3ba2ff979..0000000000000000000000000000000000000000
--- a/spec/fabricators/stream_entry_fabricator.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-Fabricator(:stream_entry) do
-  account
-  activity { Fabricate(:status) }
-  hidden { [true, false].sample }
-end
diff --git a/spec/fabricators/subscription_fabricator.rb b/spec/fabricators/subscription_fabricator.rb
deleted file mode 100644
index 347dab5dfdbbc2cf4e38cc1fd1deec9f3bf2e390..0000000000000000000000000000000000000000
--- a/spec/fabricators/subscription_fabricator.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-Fabricator(:subscription) do
-  account
-  callback_url "http://example.com/callback"
-  secret       "foobar"
-  expires_at   "2016-11-28 11:30:07"
-  confirmed    false
-end
diff --git a/spec/features/log_in_spec.rb b/spec/features/log_in_spec.rb
index 53a1f9b126daa13f11bf7347890667163fc8e19d..f6c26cd0f13b869dfb1ecc3e4a0bb662c0b59fee 100644
--- a/spec/features/log_in_spec.rb
+++ b/spec/features/log_in_spec.rb
@@ -31,12 +31,12 @@ feature "Log in" do
   context do
     given(:confirmed_at) { nil }
 
-    scenario "A unconfirmed user is not able to log in" do
+    scenario "A unconfirmed user is able to log in" do
       fill_in "user_email", with: email
       fill_in "user_password", with: password
       click_on I18n.t('auth.login')
 
-      is_expected.to have_css(".flash-message", text: failure_message("unconfirmed"))
+      is_expected.to have_css("div.admin-wrapper")
     end
   end
 
diff --git a/spec/fixtures/requests/webfinger.txt b/spec/fixtures/requests/webfinger.txt
index edb8a2dbb563d7233f817b5d5fd52db12a2a8b15..f337ecae6f150131905920e4b96943e8785c3a96 100644
--- a/spec/fixtures/requests/webfinger.txt
+++ b/spec/fixtures/requests/webfinger.txt
@@ -8,4 +8,4 @@ Access-Control-Allow-Origin: *
 Vary: Accept-Encoding,Cookie
 Strict-Transport-Security: max-age=31536000; includeSubdomains;
 
-{"subject":"acct:gargron@quitter.no","aliases":["https:\/\/quitter.no\/user\/7477","https:\/\/quitter.no\/gargron","https:\/\/quitter.no\/index.php\/user\/7477","https:\/\/quitter.no\/index.php\/gargron"],"links":[{"rel":"http:\/\/webfinger.net\/rel\/profile-page","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/gmpg.org\/xfn\/11","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"describedby","type":"application\/rdf+xml","href":"https:\/\/quitter.no\/gargron\/foaf"},{"rel":"http:\/\/apinamespace.org\/atom","type":"application\/atomsvc+xml","href":"https:\/\/quitter.no\/api\/statusnet\/app\/service\/gargron.xml"},{"rel":"http:\/\/apinamespace.org\/twitter","href":"https:\/\/quitter.no\/api\/"},{"rel":"http:\/\/specs.openid.net\/auth\/2.0\/provider","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/schemas.google.com\/g\/2010#updates-from","type":"application\/atom+xml","href":"https:\/\/quitter.no\/api\/statuses\/user_timeline\/7477.atom"},{"rel":"magic-public-key","href":"data:application\/magic-public-key,RSA.1ZBkHTavLvxH3FzlKv4O6WtlILKRFfNami3_Rcu8EuogtXSYiS-bB6hElZfUCSHbC4uLemOA34PEhz__CDMozax1iI_t8dzjDnh1x0iFSup7pSfW9iXk_WU3Dm74yWWW2jildY41vWgrEstuQ1dJ8vVFfSJ9T_tO4c-T9y8vDI8=.AQAB"},{"rel":"salmon","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-replies","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-mention","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/ostatus.org\/schema\/1.0\/subscribe","template":"https:\/\/quitter.no\/main\/ostatussub?profile={uri}"}]}
\ No newline at end of file
+{"subject":"acct:gargron@quitter.no","aliases":["https:\/\/quitter.no\/user\/7477","https:\/\/quitter.no\/gargron","https:\/\/quitter.no\/index.php\/user\/7477","https:\/\/quitter.no\/index.php\/gargron"],"links":[{"rel":"http:\/\/webfinger.net\/rel\/profile-page","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/gmpg.org\/xfn\/11","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"describedby","type":"application\/rdf+xml","href":"https:\/\/quitter.no\/gargron\/foaf"},{"rel":"http:\/\/apinamespace.org\/atom","type":"application\/atomsvc+xml","href":"https:\/\/quitter.no\/api\/statusnet\/app\/service\/gargron.xml"},{"rel":"http:\/\/apinamespace.org\/twitter","href":"https:\/\/quitter.no\/api\/"},{"rel":"http:\/\/specs.openid.net\/auth\/2.0\/provider","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/schemas.google.com\/g\/2010#updates-from","type":"application\/atom+xml","href":"https:\/\/quitter.no\/api\/statuses\/user_timeline\/7477.atom"},{"rel":"magic-public-key","href":"data:application\/magic-public-key,RSA.1ZBkHTavLvxH3FzlKv4O6WtlILKRFfNami3_Rcu8EuogtXSYiS-bB6hElZfUCSHbC4uLemOA34PEhz__CDMozax1iI_t8dzjDnh1x0iFSup7pSfW9iXk_WU3Dm74yWWW2jildY41vWgrEstuQ1dJ8vVFfSJ9T_tO4c-T9y8vDI8=.AQAB"},{"rel":"salmon","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-replies","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-mention","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/ostatus.org\/schema\/1.0\/subscribe","template":"https:\/\/quitter.no\/main\/ostatussub?profile={uri}"}]}
diff --git a/spec/helpers/admin/account_moderation_notes_helper_spec.rb b/spec/helpers/admin/account_moderation_notes_helper_spec.rb
index c07f6c4b886b37492c52b8ec0376ccc7ec380949..ddfe8b46f9f1fbd7cccee8266e75f236e0116111 100644
--- a/spec/helpers/admin/account_moderation_notes_helper_spec.rb
+++ b/spec/helpers/admin/account_moderation_notes_helper_spec.rb
@@ -3,7 +3,7 @@
 require 'rails_helper'
 
 RSpec.describe Admin::AccountModerationNotesHelper, type: :helper do
-  include StreamEntriesHelper
+  include StatusesHelper
 
   describe '#admin_account_link_to' do
     context 'account is nil' do
diff --git a/spec/helpers/stream_entries_helper_spec.rb b/spec/helpers/statuses_helper_spec.rb
similarity index 94%
rename from spec/helpers/stream_entries_helper_spec.rb
rename to spec/helpers/statuses_helper_spec.rb
index 845b9974ea1b5ce7446f7869443fbb3343f96227..510955a2ff5349cd460965ee20c60efcbf8dfa8a 100644
--- a/spec/helpers/stream_entries_helper_spec.rb
+++ b/spec/helpers/statuses_helper_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe StreamEntriesHelper, type: :helper do
+RSpec.describe StatusesHelper, type: :helper do
   describe '#display_name' do
     it 'uses the display name when it exists' do
       account = Account.new(display_name: "Display", username: "Username")
@@ -70,13 +70,13 @@ RSpec.describe StreamEntriesHelper, type: :helper do
   end
 
   def set_not_embedded_view
-    params[:controller] = "not_#{StreamEntriesHelper::EMBEDDED_CONTROLLER}"
-    params[:action] = "not_#{StreamEntriesHelper::EMBEDDED_ACTION}"
+    params[:controller] = "not_#{StatusesHelper::EMBEDDED_CONTROLLER}"
+    params[:action] = "not_#{StatusesHelper::EMBEDDED_ACTION}"
   end
 
   def set_embedded_view
-    params[:controller] = StreamEntriesHelper::EMBEDDED_CONTROLLER
-    params[:action] = StreamEntriesHelper::EMBEDDED_ACTION
+    params[:controller] = StatusesHelper::EMBEDDED_CONTROLLER
+    params[:action] = StatusesHelper::EMBEDDED_ACTION
   end
 
   describe '#style_classes' do
diff --git a/spec/lib/activitypub/activity/follow_spec.rb b/spec/lib/activitypub/activity/follow_spec.rb
index 6bbacdbe644d2c2f2085651e7cfbe2b6a760373c..05112cc184f2a65aa2437b2aa7276b251f48f711 100644
--- a/spec/lib/activitypub/activity/follow_spec.rb
+++ b/spec/lib/activitypub/activity/follow_spec.rb
@@ -31,6 +31,36 @@ RSpec.describe ActivityPub::Activity::Follow do
       end
     end
 
+    context 'silenced account following an unlocked account' do
+      before do
+        sender.touch(:silenced_at)
+        subject.perform
+      end
+
+      it 'does not create a follow from sender to recipient' do
+        expect(sender.following?(recipient)).to be false
+      end
+
+      it 'creates a follow request' do
+        expect(sender.requested?(recipient)).to be true
+      end
+    end
+
+    context 'unlocked account muting the sender' do
+      before do
+        recipient.mute!(sender)
+        subject.perform
+      end
+
+      it 'creates a follow from sender to recipient' do
+        expect(sender.following?(recipient)).to be true
+      end
+
+      it 'does not create a follow request' do
+        expect(sender.requested?(recipient)).to be false
+      end
+    end
+
     context 'locked account' do
       before do
         recipient.update(locked: true)
diff --git a/spec/lib/activitypub/activity/update_spec.rb b/spec/lib/activitypub/activity/update_spec.rb
index fbfc585cf9736ce991dd4b3c071f7f25c101358d..42da2986066417dce5bd7bd3c6f752178f3883da 100644
--- a/spec/lib/activitypub/activity/update_spec.rb
+++ b/spec/lib/activitypub/activity/update_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe ActivityPub::Activity::Update do
   end
 
   let(:actor_json) do
-    ActiveModelSerializers::SerializableResource.new(modified_sender, serializer: ActivityPub::ActorSerializer, key_transform: :camel_lower).as_json
+    ActiveModelSerializers::SerializableResource.new(modified_sender, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter).as_json
   end
 
   let(:json) do
diff --git a/spec/lib/activitypub/tag_manager_spec.rb b/spec/lib/activitypub/tag_manager_spec.rb
index 6d246629e786d3b6dfacc407af4f5bb602ce55bb..1c5c6f0edd3bdd67b396620f959a6ca77a17050b 100644
--- a/spec/lib/activitypub/tag_manager_spec.rb
+++ b/spec/lib/activitypub/tag_manager_spec.rb
@@ -143,12 +143,6 @@ RSpec.describe ActivityPub::TagManager do
       expect(subject.uri_to_resource(OStatus::TagManager.instance.uri_for(status), Status)).to eq status
     end
 
-    it 'returns the local status for OStatus StreamEntry URL' do
-      status = Fabricate(:status)
-      stream_entry_url = account_stream_entry_url(status.account, status.stream_entry)
-      expect(subject.uri_to_resource(stream_entry_url, Status)).to eq status
-    end
-
     it 'returns the remote status by matching URI without fragment part' do
       status = Fabricate(:status, uri: 'https://example.com/123')
       expect(subject.uri_to_resource('https://example.com/123#456', Status)).to eq status
diff --git a/spec/lib/connection_pool/shared_connection_pool_spec.rb b/spec/lib/connection_pool/shared_connection_pool_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1144645580b5ffbaa2db47c0e754a0d1e440365e
--- /dev/null
+++ b/spec/lib/connection_pool/shared_connection_pool_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe ConnectionPool::SharedConnectionPool do
+  class MiniConnection
+    attr_reader :site
+
+    def initialize(site)
+      @site = site
+    end
+  end
+
+  subject { described_class.new(size: 5, timeout: 5) { |site| MiniConnection.new(site) } }
+
+  describe '#with' do
+    it 'runs a block with a connection' do
+      block_run = false
+
+      subject.with('foo') do |connection|
+        expect(connection).to be_a MiniConnection
+        block_run = true
+      end
+
+      expect(block_run).to be true
+    end
+  end
+end
diff --git a/spec/lib/connection_pool/shared_timed_stack_spec.rb b/spec/lib/connection_pool/shared_timed_stack_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f680c596670fcabe8ff765a9e3357b3a1fd7e1b3
--- /dev/null
+++ b/spec/lib/connection_pool/shared_timed_stack_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe ConnectionPool::SharedTimedStack do
+  class MiniConnection
+    attr_reader :site
+
+    def initialize(site)
+      @site = site
+    end
+  end
+
+  subject { described_class.new(5) { |site| MiniConnection.new(site) } }
+
+  describe '#push' do
+    it 'keeps the connection in the stack' do
+      subject.push(MiniConnection.new('foo'))
+      expect(subject.size).to eq 1
+    end
+  end
+
+  describe '#pop' do
+    it 'returns a connection' do
+      expect(subject.pop('foo')).to be_a MiniConnection
+    end
+
+    it 'returns the same connection that was pushed in' do
+      connection = MiniConnection.new('foo')
+      subject.push(connection)
+      expect(subject.pop('foo')).to be connection
+    end
+
+    it 'does not create more than maximum amount of connections' do
+      expect { 6.times { subject.pop('foo', 0) } }.to raise_error Timeout::Error
+    end
+
+    it 'repurposes a connection for a different site when maximum amount is reached' do
+      5.times { subject.push(MiniConnection.new('foo')) }
+      expect(subject.pop('bar')).to be_a MiniConnection
+    end
+  end
+
+  describe '#empty?' do
+    it 'returns true when no connections on the stack' do
+      expect(subject.empty?).to be true
+    end
+
+    it 'returns false when there are connections on the stack' do
+      subject.push(MiniConnection.new('foo'))
+      expect(subject.empty?).to be false
+    end
+  end
+
+  describe '#size' do
+    it 'returns the number of connections on the stack' do
+      2.times { subject.push(MiniConnection.new('foo')) }
+      expect(subject.size).to eq 2
+    end
+  end
+end
diff --git a/spec/lib/ostatus/atom_serializer_spec.rb b/spec/lib/ostatus/atom_serializer_spec.rb
deleted file mode 100644
index 891871c1c7fabbabd3ed938348951e87bdd55316..0000000000000000000000000000000000000000
--- a/spec/lib/ostatus/atom_serializer_spec.rb
+++ /dev/null
@@ -1,1560 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe OStatus::AtomSerializer do
-  shared_examples 'follow request salmon' do
-    it 'appends author element with account' do
-      account = Fabricate(:account, domain: nil, username: 'username')
-      follow_request = Fabricate(:follow_request, account: account)
-
-      follow_request_salmon = serialize(follow_request)
-
-      expect(follow_request_salmon.author.id.text).to eq 'https://cb6e6126.ngrok.io/users/username'
-    end
-
-    it 'appends activity:object-type element with activity type' do
-      follow_request = Fabricate(:follow_request)
-
-      follow_request_salmon = serialize(follow_request)
-
-      object_type = follow_request_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
-    end
-
-    it 'appends activity:verb element with request_friend type' do
-      follow_request = Fabricate(:follow_request)
-
-      follow_request_salmon = serialize(follow_request)
-
-      verb = follow_request_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq OStatus::TagManager::VERBS[:request_friend]
-    end
-
-    it 'appends activity:object with target account' do
-      target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id')
-      follow_request = Fabricate(:follow_request, target_account: target_account)
-
-      follow_request_salmon = serialize(follow_request)
-
-      object = follow_request_salmon.nodes.find { |node| node.name == 'activity:object' }
-      expect(object.id.text).to eq 'https://domain.test/id'
-    end
-  end
-
-  shared_examples 'namespaces' do
-    it 'adds namespaces' do
-      element = serialize
-
-      expect(element['xmlns']).to eq OStatus::TagManager::XMLNS
-      expect(element['xmlns:thr']).to eq OStatus::TagManager::THR_XMLNS
-      expect(element['xmlns:activity']).to eq OStatus::TagManager::AS_XMLNS
-      expect(element['xmlns:poco']).to eq OStatus::TagManager::POCO_XMLNS
-      expect(element['xmlns:media']).to eq OStatus::TagManager::MEDIA_XMLNS
-      expect(element['xmlns:ostatus']).to eq OStatus::TagManager::OS_XMLNS
-      expect(element['xmlns:mastodon']).to eq OStatus::TagManager::MTDN_XMLNS
-    end
-  end
-
-  shared_examples 'no namespaces' do
-    it 'does not add namespaces' do
-      expect(serialize['xmlns']).to eq nil
-    end
-  end
-
-  shared_examples 'status attributes' do
-    it 'appends summary element with spoiler text if present' do
-      status = Fabricate(:status, language: :ca, spoiler_text: 'spoiler text')
-
-      element = serialize(status)
-
-      summary = element.summary
-      expect(summary['xml:lang']).to eq 'ca'
-      expect(summary.text).to eq 'spoiler text'
-    end
-
-    it 'does not append summary element with spoiler text if not present' do
-      status = Fabricate(:status, spoiler_text: '')
-      element = serialize(status)
-      element.nodes.each { |node| expect(node.name).not_to eq 'summary' }
-    end
-
-    it 'appends content element with formatted status' do
-      status = Fabricate(:status, language: :ca, text: 'text')
-
-      element = serialize(status)
-
-      content = element.content
-      expect(content[:type]).to eq 'html'
-      expect(content['xml:lang']).to eq 'ca'
-      expect(content.text).to eq '<p>text</p>'
-    end
-
-    it 'appends link elements for mentioned accounts' do
-      account = Fabricate(:account, username: 'username')
-      status = Fabricate(:status)
-      Fabricate(:mention, account: account, status: status)
-
-      element = serialize(status)
-
-      mentioned = element.nodes.find do |node|
-        node.name == 'link' &&
-          node[:rel] == 'mentioned' &&
-          node['ostatus:object-type'] == OStatus::TagManager::TYPES[:person]
-      end
-
-      expect(mentioned[:href]).to eq 'https://cb6e6126.ngrok.io/users/username'
-    end
-
-    it 'appends link elements for emojis' do
-      Fabricate(:custom_emoji)
-
-      status  = Fabricate(:status, text: ':coolcat:')
-      element = serialize(status)
-      emoji   = element.nodes.find { |node| node.name == 'link' && node[:rel] == 'emoji' }
-
-      expect(emoji[:name]).to eq 'coolcat'
-      expect(emoji[:href]).to_not be_blank
-    end
-  end
-
-  describe 'render' do
-    it 'returns XML with emojis' do
-      element = Ox::Element.new('tag')
-      element << '💩'
-      xml = OStatus::AtomSerializer.render(element)
-
-      expect(xml).to eq "<?xml version=\"1.0\"?>\n<tag>💩</tag>\n"
-    end
-
-    it 'returns XML, stripping invalid characters like \b and \v' do
-      element = Ox::Element.new('tag')
-      element << "im l33t\b haxo\b\vr"
-      xml = OStatus::AtomSerializer.render(element)
-
-      expect(xml).to eq "<?xml version=\"1.0\"?>\n<tag>im l33t haxor</tag>\n"
-    end
-  end
-
-  describe '#author' do
-    context 'when note is present' do
-      it 'appends poco:note element with note for local account' do
-        account = Fabricate(:account, domain: nil, note: '<p>note</p>')
-
-        author = OStatus::AtomSerializer.new.author(account)
-
-        note = author.nodes.find { |node| node.name == 'poco:note' }
-        expect(note.text).to eq '<p>note</p>'
-      end
-
-      it 'appends poco:note element with tags-stripped note for remote account' do
-        account = Fabricate(:account, domain: 'remote', note: '<p>note</p>')
-
-        author = OStatus::AtomSerializer.new.author(account)
-
-        note = author.nodes.find { |node| node.name == 'poco:note' }
-        expect(note.text).to eq 'note'
-      end
-
-      it 'appends summary element with type attribute and simplified note if present' do
-        account = Fabricate(:account, note: 'note')
-        author = OStatus::AtomSerializer.new.author(account)
-        expect(author.summary.text).to eq '<p>note</p>'
-        expect(author.summary[:type]).to eq 'html'
-      end
-    end
-
-    context 'when note is not present' do
-      it 'does not append poco:note element' do
-        account = Fabricate(:account, note: '')
-        author = OStatus::AtomSerializer.new.author(account)
-        author.nodes.each { |node| expect(node.name).not_to eq 'poco:note' }
-      end
-
-      it 'does not append summary element' do
-        account = Fabricate(:account, note: '')
-        author = OStatus::AtomSerializer.new.author(account)
-        author.nodes.each { |node| expect(node.name).not_to eq 'summary' }
-      end
-    end
-
-    it 'returns author element' do
-      account = Fabricate(:account)
-      author = OStatus::AtomSerializer.new.author(account)
-      expect(author.name).to eq 'author'
-    end
-
-    it 'appends activity:object-type element with person type' do
-      account = Fabricate(:account, domain: nil, username: 'username')
-
-      author = OStatus::AtomSerializer.new.author(account)
-
-      object_type = author.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq OStatus::TagManager::TYPES[:person]
-    end
-
-    it 'appends email element with username and domain for local account' do
-      account = Fabricate(:account, username: 'username')
-      author = OStatus::AtomSerializer.new.author(account)
-      expect(author.email.text).to eq 'username@cb6e6126.ngrok.io'
-    end
-
-    it 'appends email element with username and domain for remote user' do
-      account = Fabricate(:account, domain: 'domain', username: 'username')
-      author = OStatus::AtomSerializer.new.author(account)
-      expect(author.email.text).to eq 'username@domain'
-    end
-
-    it 'appends link element for an alternative' do
-      account = Fabricate(:account, domain: nil, username: 'username')
-
-      author = OStatus::AtomSerializer.new.author(account)
-
-      link = author.nodes.find { |node| node.name == 'link' && node[:rel] == 'alternate' && node[:type] == 'text/html' }
-      expect(link[:type]).to eq 'text/html'
-      expect(link[:rel]).to eq 'alternate'
-      expect(link[:href]).to eq 'https://cb6e6126.ngrok.io/@username'
-    end
-
-    it 'has link element for avatar if present' do
-      account = Fabricate(:account, avatar: attachment_fixture('avatar.gif'))
-
-      author = OStatus::AtomSerializer.new.author(account)
-
-      link = author.nodes.find { |node| node.name == 'link' && node[:rel] == 'avatar' }
-      expect(link[:type]).to eq 'image/gif'
-      expect(link['media:width']).to eq '120'
-      expect(link['media:height']).to eq '120'
-      expect(link[:href]).to match  /^https:\/\/cb6e6126.ngrok.io\/system\/accounts\/avatars\/.+\/original\/avatar.gif/
-    end
-
-    it 'does not have link element for avatar if not present' do
-      account = Fabricate(:account, avatar: nil)
-
-      author = OStatus::AtomSerializer.new.author(account)
-
-      author.nodes.each do |node|
-        expect(node[:rel]).not_to eq 'avatar' if node.name == 'link'
-      end
-    end
-
-    it 'appends link element for header if present' do
-      account = Fabricate(:account, header: attachment_fixture('avatar.gif'))
-
-      author = OStatus::AtomSerializer.new.author(account)
-
-      link = author.nodes.find { |node| node.name == 'link' && node[:rel] == 'header' }
-      expect(link[:type]).to eq 'image/gif'
-      expect(link['media:width']).to eq '700'
-      expect(link['media:height']).to eq '335'
-      expect(link[:href]).to match  /^https:\/\/cb6e6126.ngrok.io\/system\/accounts\/headers\/.+\/original\/avatar.gif/
-    end
-
-    it 'does not append link element for header if not present' do
-      account = Fabricate(:account, header: nil)
-
-      author = OStatus::AtomSerializer.new.author(account)
-
-      author.nodes.each do |node|
-        expect(node[:rel]).not_to eq 'header' if node.name == 'link'
-      end
-    end
-
-    it 'appends poco:displayName element with display name if present' do
-      account = Fabricate(:account, display_name: 'display name')
-
-      author = OStatus::AtomSerializer.new.author(account)
-
-      display_name = author.nodes.find { |node| node.name == 'poco:displayName' }
-      expect(display_name.text).to eq 'display name'
-    end
-
-    it 'does not append poco:displayName element with display name if not present' do
-      account = Fabricate(:account, display_name: '')
-      author = OStatus::AtomSerializer.new.author(account)
-      author.nodes.each { |node| expect(node.name).not_to eq 'poco:displayName' }
-    end
-
-    it "appends mastodon:scope element with 'private' if locked" do
-      account = Fabricate(:account, locked: true)
-
-      author = OStatus::AtomSerializer.new.author(account)
-
-      scope = author.nodes.find { |node| node.name == 'mastodon:scope' }
-      expect(scope.text).to eq 'private'
-    end
-
-    it "appends mastodon:scope element with 'public' if unlocked" do
-      account = Fabricate(:account, locked: false)
-
-      author = OStatus::AtomSerializer.new.author(account)
-
-      scope = author.nodes.find { |node| node.name == 'mastodon:scope' }
-      expect(scope.text).to eq 'public'
-    end
-
-    it 'includes URI' do
-      account = Fabricate(:account, domain: nil, username: 'username')
-
-      author = OStatus::AtomSerializer.new.author(account)
-
-      expect(author.id.text).to eq 'https://cb6e6126.ngrok.io/users/username'
-      expect(author.uri.text).to eq 'https://cb6e6126.ngrok.io/users/username'
-    end
-
-    it 'includes username' do
-      account = Fabricate(:account, username: 'username')
-
-      author = OStatus::AtomSerializer.new.author(account)
-
-      name = author.nodes.find { |node| node.name == 'name' }
-      username = author.nodes.find { |node| node.name == 'poco:preferredUsername' }
-      expect(name.text).to eq 'username'
-      expect(username.text).to eq 'username'
-    end
-  end
-
-  describe '#entry' do
-    shared_examples 'not root' do
-      include_examples 'no namespaces' do
-        def serialize
-          subject
-        end
-      end
-
-      it 'does not append author element' do
-        subject.nodes.each { |node| expect(node.name).not_to eq 'author' }
-      end
-    end
-
-    context 'it is root' do
-      include_examples 'namespaces' do
-        def serialize
-          stream_entry = Fabricate(:stream_entry)
-          OStatus::AtomSerializer.new.entry(stream_entry, true)
-        end
-      end
-
-      it 'appends author element' do
-        account = Fabricate(:account, username: 'username')
-        status = Fabricate(:status, account: account)
-
-        entry = OStatus::AtomSerializer.new.entry(status.stream_entry, true)
-
-        expect(entry.author.id.text).to eq 'https://cb6e6126.ngrok.io/users/username'
-      end
-    end
-
-    context 'if status is present' do
-      include_examples 'status attributes' do
-        def serialize(status)
-          OStatus::AtomSerializer.new.entry(status.stream_entry, true)
-        end
-      end
-
-      it 'appends link element for the public collection if status is publicly visible' do
-        status = Fabricate(:status, visibility: :public)
-
-        entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-
-        mentioned_person = entry.nodes.find do |node|
-          node.name == 'link' &&
-          node[:rel] == 'mentioned' &&
-          node['ostatus:object-type'] == OStatus::TagManager::TYPES[:collection]
-        end
-        expect(mentioned_person[:href]).to eq OStatus::TagManager::COLLECTIONS[:public]
-      end
-
-      it 'does not append link element for the public collection if status is not publicly visible' do
-        status = Fabricate(:status, visibility: :private)
-
-        entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-
-        entry.nodes.each do |node|
-          if node.name == 'link' &&
-             node[:rel] == 'mentioned' &&
-             node['ostatus:object-type'] == OStatus::TagManager::TYPES[:collection]
-            expect(mentioned_collection[:href]).not_to eq OStatus::TagManager::COLLECTIONS[:public]
-          end
-        end
-      end
-
-      it 'appends category elements for tags' do
-        tag = Fabricate(:tag, name: 'tag')
-        status = Fabricate(:status, tags: [ tag ])
-
-        entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-
-        expect(entry.category[:term]).to eq 'tag'
-      end
-
-      it 'appends link elements for media attachments' do
-        file = attachment_fixture('attachment.jpg')
-        media_attachment = Fabricate(:media_attachment, file: file)
-        status = Fabricate(:status, media_attachments: [ media_attachment ])
-
-        entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-
-        enclosure = entry.nodes.find { |node| node.name == 'link' && node[:rel] == 'enclosure' }
-        expect(enclosure[:type]).to eq 'image/jpeg'
-        expect(enclosure[:href]).to match /^https:\/\/cb6e6126.ngrok.io\/system\/media_attachments\/files\/.+\/original\/attachment.jpg$/
-      end
-
-      it 'appends mastodon:scope element with visibility' do
-        status = Fabricate(:status, visibility: :public)
-
-        entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-
-        scope = entry.nodes.find { |node| node.name == 'mastodon:scope' }
-        expect(scope.text).to eq 'public'
-      end
-
-      it 'returns element whose rendered view triggers creation when processed' do
-        remote_account = Account.create!(username: 'username')
-        remote_status = Fabricate(:status, account: remote_account, created_at: '2000-01-01T00:00:00Z')
-
-        entry = OStatus::AtomSerializer.new.entry(remote_status.stream_entry, true)
-        entry.nodes.delete_if { |node| node[:type] == 'application/activity+json' } # Remove ActivityPub link to simplify test
-        xml = OStatus::AtomSerializer.render(entry).gsub('cb6e6126.ngrok.io', 'remote.test')
-
-        remote_status.destroy!
-        remote_account.destroy!
-
-        account = Account.create!(
-          domain: 'remote.test',
-          username: 'username',
-          last_webfingered_at: Time.now.utc
-        )
-
-        ProcessFeedService.new.call(xml, account)
-
-        expect(Status.find_by(uri: "https://remote.test/users/#{remote_status.account.to_param}/statuses/#{remote_status.id}")).to be_instance_of Status
-      end
-    end
-
-    context 'if status is not present' do
-      it 'appends content element saying status is deleted' do
-        status = Fabricate(:status)
-        status.destroy!
-
-        entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-
-        expect(entry.content.text).to eq 'Deleted status'
-      end
-
-      it 'appends title element saying the status is deleted' do
-        account = Fabricate(:account, username: 'username')
-        status = Fabricate(:status, account: account)
-        status.destroy!
-
-        entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-
-        expect(entry.title.text).to eq 'username deleted status'
-      end
-    end
-
-    context 'it is not root' do
-      let(:stream_entry) { Fabricate(:stream_entry) }
-      subject { OStatus::AtomSerializer.new.entry(stream_entry, false) }
-      include_examples 'not root'
-    end
-
-    context 'without root parameter' do
-      let(:stream_entry) { Fabricate(:stream_entry) }
-      subject { OStatus::AtomSerializer.new.entry(stream_entry) }
-      include_examples 'not root'
-    end
-
-    it 'returns entry element' do
-      stream_entry = Fabricate(:stream_entry)
-      entry = OStatus::AtomSerializer.new.entry(stream_entry)
-      expect(entry.name).to eq 'entry'
-    end
-
-    it 'appends id element with unique tag' do
-      status = Fabricate(:status, reblog_of_id: nil, created_at: '2000-01-01T00:00:00Z')
-
-      entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-
-      expect(entry.id.text).to eq "https://cb6e6126.ngrok.io/users/#{status.account.to_param}/statuses/#{status.id}"
-    end
-
-    it 'appends published element with created date' do
-      stream_entry = Fabricate(:stream_entry, created_at: '2000-01-01T00:00:00Z')
-      entry = OStatus::AtomSerializer.new.entry(stream_entry)
-      expect(entry.published.text).to eq '2000-01-01T00:00:00Z'
-    end
-
-    it 'appends updated element with updated date' do
-      stream_entry = Fabricate(:stream_entry, updated_at: '2000-01-01T00:00:00Z')
-      entry = OStatus::AtomSerializer.new.entry(stream_entry)
-      expect(entry.updated.text).to eq '2000-01-01T00:00:00Z'
-    end
-
-    it 'appends title element with status title' do
-      account = Fabricate(:account, username: 'username')
-      status = Fabricate(:status, account: account, reblog_of_id: nil)
-      entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-      expect(entry.title.text).to eq 'New status by username'
-    end
-
-    it 'appends activity:object-type element with object type' do
-      status = Fabricate(:status)
-      entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-      object_type = entry.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq OStatus::TagManager::TYPES[:note]
-    end
-
-    it 'appends activity:verb element with object type' do
-      status = Fabricate(:status)
-
-      entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-
-      object_type = entry.nodes.find { |node| node.name == 'activity:verb' }
-      expect(object_type.text).to eq OStatus::TagManager::VERBS[:post]
-    end
-
-    it 'appends activity:object element with target if present' do
-      reblogged = Fabricate(:status, created_at: '2000-01-01T00:00:00Z')
-      reblog = Fabricate(:status, reblog: reblogged)
-
-      entry = OStatus::AtomSerializer.new.entry(reblog.stream_entry)
-
-      object = entry.nodes.find { |node| node.name == 'activity:object' }
-      expect(object.id.text).to eq "https://cb6e6126.ngrok.io/users/#{reblogged.account.to_param}/statuses/#{reblogged.id}"
-    end
-
-    it 'does not append activity:object element if target is not present' do
-      status = Fabricate(:status, reblog_of_id: nil)
-      entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-      entry.nodes.each { |node| expect(node.name).not_to eq 'activity:object' }
-    end
-
-    it 'appends link element for an alternative' do
-      account = Fabricate(:account, username: 'username')
-      status = Fabricate(:status, account: account)
-
-      entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-
-      link = entry.nodes.find { |node| node.name == 'link' && node[:rel] == 'alternate' && node[:type] == 'text/html' }
-      expect(link[:type]).to eq 'text/html'
-      expect(link[:href]).to eq "https://cb6e6126.ngrok.io/@username/#{status.id}"
-    end
-
-    it 'appends link element for itself' do
-      account = Fabricate(:account, username: 'username')
-      status = Fabricate(:status, account: account)
-
-      entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-
-      link = entry.nodes.find { |node| node.name == 'link' && node[:rel] == 'self' }
-      expect(link[:type]).to eq 'application/atom+xml'
-      expect(link[:href]).to eq "https://cb6e6126.ngrok.io/users/username/updates/#{status.stream_entry.id}.atom"
-    end
-
-    it 'appends thr:in-reply-to element if threaded' do
-      in_reply_to_status = Fabricate(:status, created_at: '2000-01-01T00:00:00Z', reblog_of_id: nil)
-      reply_status = Fabricate(:status, in_reply_to_id: in_reply_to_status.id)
-
-      entry = OStatus::AtomSerializer.new.entry(reply_status.stream_entry)
-
-      in_reply_to = entry.nodes.find { |node| node.name == 'thr:in-reply-to' }
-      expect(in_reply_to[:ref]).to eq "https://cb6e6126.ngrok.io/users/#{in_reply_to_status.account.to_param}/statuses/#{in_reply_to_status.id}"
-    end
-
-    it 'does not append thr:in-reply-to element if not threaded' do
-      status = Fabricate(:status)
-      entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-      entry.nodes.each { |node| expect(node.name).not_to eq 'thr:in-reply-to' }
-    end
-
-    it 'appends ostatus:conversation if conversation id is present' do
-      status = Fabricate(:status)
-      status.conversation.update!(created_at: '2000-01-01T00:00:00Z')
-
-      entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-
-      conversation = entry.nodes.find { |node| node.name == 'ostatus:conversation' }
-      expect(conversation[:ref]).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{status.conversation_id}:objectType=Conversation"
-    end
-
-    it 'does not append ostatus:conversation if conversation id is not present' do
-      status = Fabricate.build(:status, conversation_id: nil)
-      status.save!(validate: false)
-
-      entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
-
-      entry.nodes.each { |node| expect(node.name).not_to eq 'ostatus:conversation' }
-    end
-  end
-
-  describe '#feed' do
-    include_examples 'namespaces' do
-      def serialize
-        account = Fabricate(:account)
-        OStatus::AtomSerializer.new.feed(account, [])
-      end
-    end
-
-    it 'returns feed element' do
-      account = Fabricate(:account)
-      feed = OStatus::AtomSerializer.new.feed(account, [])
-      expect(feed.name).to eq 'feed'
-    end
-
-    it 'appends id element with account Atom URL' do
-      account = Fabricate(:account, username: 'username')
-      feed = OStatus::AtomSerializer.new.feed(account, [])
-      expect(feed.id.text).to eq 'https://cb6e6126.ngrok.io/users/username.atom'
-    end
-
-    it 'appends title element with account display name if present' do
-      account = Fabricate(:account, display_name: 'display name')
-      feed = OStatus::AtomSerializer.new.feed(account, [])
-      expect(feed.title.text).to eq 'display name'
-    end
-
-    it 'does not append title element with account username if account display name is not present' do
-      account = Fabricate(:account, display_name: '', username: 'username')
-      feed = OStatus::AtomSerializer.new.feed(account, [])
-      expect(feed.title.text).to eq 'username'
-    end
-
-    it 'appends subtitle element with account note' do
-      account = Fabricate(:account, note: 'note')
-      feed = OStatus::AtomSerializer.new.feed(account, [])
-      expect(feed.subtitle.text).to eq 'note'
-    end
-
-    it 'appends updated element with date account got updated' do
-      account = Fabricate(:account, updated_at: '2000-01-01T00:00:00Z')
-      feed = OStatus::AtomSerializer.new.feed(account, [])
-      expect(feed.updated.text).to eq '2000-01-01T00:00:00Z'
-    end
-
-    it 'appends logo element with full asset URL for original account avatar' do
-      account = Fabricate(:account, avatar: attachment_fixture('avatar.gif'))
-      feed = OStatus::AtomSerializer.new.feed(account, [])
-      expect(feed.logo.text).to match /^https:\/\/cb6e6126.ngrok.io\/system\/accounts\/avatars\/.+\/original\/avatar.gif/
-    end
-
-    it 'appends author element' do
-      account = Fabricate(:account, username: 'username')
-      feed = OStatus::AtomSerializer.new.feed(account, [])
-      expect(feed.author.id.text).to eq 'https://cb6e6126.ngrok.io/users/username'
-    end
-
-    it 'appends link element for an alternative' do
-      account = Fabricate(:account, username: 'username')
-
-      feed = OStatus::AtomSerializer.new.feed(account, [])
-
-      link = feed.nodes.find { |node| node.name == 'link' && node[:rel] == 'alternate' && node[:type] == 'text/html' }
-      expect(link[:type]).to eq 'text/html'
-      expect(link[:href]).to eq 'https://cb6e6126.ngrok.io/@username'
-    end
-
-    it 'appends link element for itself' do
-      account = Fabricate(:account, username: 'username')
-
-      feed = OStatus::AtomSerializer.new.feed(account, [])
-
-      link = feed.nodes.find { |node| node.name == 'link' && node[:rel] == 'self' }
-      expect(link[:type]).to eq 'application/atom+xml'
-      expect(link[:href]).to eq 'https://cb6e6126.ngrok.io/users/username.atom'
-    end
-
-    it 'appends link element for the next if it has 20 stream entries' do
-      account = Fabricate(:account, username: 'username')
-      stream_entry = Fabricate(:stream_entry)
-
-      feed = OStatus::AtomSerializer.new.feed(account, Array.new(20, stream_entry))
-
-      link = feed.nodes.find { |node| node.name == 'link' && node[:rel] == 'next' }
-      expect(link[:type]).to eq 'application/atom+xml'
-      expect(link[:href]).to eq "https://cb6e6126.ngrok.io/users/username.atom?max_id=#{stream_entry.id}"
-    end
-
-    it 'does not append link element for the next if it does not have 20 stream entries' do
-      account = Fabricate(:account, username: 'username')
-
-      feed = OStatus::AtomSerializer.new.feed(account, [])
-
-      feed.nodes.each do |node|
-        expect(node[:rel]).not_to eq 'next' if node.name == 'link'
-      end
-    end
-
-    it 'appends link element for hub' do
-      account = Fabricate(:account, username: 'username')
-
-      feed = OStatus::AtomSerializer.new.feed(account, [])
-
-      link = feed.nodes.find { |node| node.name == 'link' && node[:rel] == 'hub' }
-      expect(link[:href]).to eq 'https://cb6e6126.ngrok.io/api/push'
-    end
-
-    it 'appends link element for Salmon' do
-      account = Fabricate(:account, username: 'username')
-
-      feed = OStatus::AtomSerializer.new.feed(account, [])
-
-      link = feed.nodes.find { |node| node.name == 'link' && node[:rel] == 'salmon' }
-      expect(link[:href]).to start_with 'https://cb6e6126.ngrok.io/api/salmon/'
-    end
-
-    it 'appends stream entries' do
-      account = Fabricate(:account, username: 'username')
-      status = Fabricate(:status, account: account)
-
-      feed = OStatus::AtomSerializer.new.feed(account, [status.stream_entry])
-
-      expect(feed.entry.title.text).to eq 'New status by username'
-    end
-  end
-
-  describe '#block_salmon' do
-    include_examples 'namespaces' do
-      def serialize
-        block = Fabricate(:block)
-        OStatus::AtomSerializer.new.block_salmon(block)
-      end
-    end
-
-    it 'returns entry element' do
-      block = Fabricate(:block)
-      block_salmon = OStatus::AtomSerializer.new.block_salmon(block)
-      expect(block_salmon.name).to eq 'entry'
-    end
-
-    it 'appends id element with unique tag' do
-      block = Fabricate(:block)
-
-      time_before = Time.zone.now
-      block_salmon = OStatus::AtomSerializer.new.block_salmon(block)
-      time_after = Time.zone.now
-
-      expect(block_salmon.id.text).to(
-        eq(OStatus::TagManager.instance.unique_tag(time_before.utc, block.id, 'Block'))
-          .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, block.id, 'Block')))
-      )
-    end
-
-    it 'appends title element with description' do
-      account = Fabricate(:account, domain: nil, username: 'account')
-      target_account = Fabricate(:account, domain: 'remote', username: 'target_account')
-      block = Fabricate(:block, account: account, target_account: target_account)
-
-      block_salmon = OStatus::AtomSerializer.new.block_salmon(block)
-
-      expect(block_salmon.title.text).to eq 'account no longer wishes to interact with target_account@remote'
-    end
-
-    it 'appends author element with account' do
-      account = Fabricate(:account, domain: nil, username: 'account')
-      block = Fabricate(:block, account: account)
-
-      block_salmon = OStatus::AtomSerializer.new.block_salmon(block)
-
-      expect(block_salmon.author.id.text).to eq 'https://cb6e6126.ngrok.io/users/account'
-    end
-
-    it 'appends activity:object-type element with activity type' do
-      block = Fabricate(:block)
-
-      block_salmon = OStatus::AtomSerializer.new.block_salmon(block)
-
-      object_type = block_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
-    end
-
-    it 'appends activity:verb element with block' do
-      block = Fabricate(:block)
-
-      block_salmon = OStatus::AtomSerializer.new.block_salmon(block)
-
-      verb = block_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq OStatus::TagManager::VERBS[:block]
-    end
-
-    it 'appends activity:object element with target account' do
-      target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id')
-      block = Fabricate(:block, target_account: target_account)
-
-      block_salmon = OStatus::AtomSerializer.new.block_salmon(block)
-
-      object = block_salmon.nodes.find { |node| node.name == 'activity:object' }
-      expect(object.id.text).to eq 'https://domain.test/id'
-    end
-
-    it 'returns element whose rendered view triggers block when processed' do
-      block = Fabricate(:block)
-      block_salmon = OStatus::AtomSerializer.new.block_salmon(block)
-      xml = OStatus::AtomSerializer.render(block_salmon)
-      envelope = OStatus2::Salmon.new.pack(xml, block.account.keypair)
-      block.destroy!
-
-      ProcessInteractionService.new.call(envelope, block.target_account)
-
-      expect(block.account.blocking?(block.target_account)).to be true
-    end
-  end
-
-  describe '#unblock_salmon' do
-    include_examples 'namespaces' do
-      def serialize
-        block = Fabricate(:block)
-        OStatus::AtomSerializer.new.unblock_salmon(block)
-      end
-    end
-
-    it 'returns entry element' do
-      block = Fabricate(:block)
-      unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block)
-      expect(unblock_salmon.name).to eq 'entry'
-    end
-
-    it 'appends id element with unique tag' do
-      block = Fabricate(:block)
-
-      time_before = Time.zone.now
-      unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block)
-      time_after = Time.zone.now
-
-      expect(unblock_salmon.id.text).to(
-        eq(OStatus::TagManager.instance.unique_tag(time_before.utc, block.id, 'Block'))
-          .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, block.id, 'Block')))
-      )
-    end
-
-    it 'appends title element with description' do
-      account = Fabricate(:account, domain: nil, username: 'account')
-      target_account = Fabricate(:account, domain: 'remote', username: 'target_account')
-      block = Fabricate(:block, account: account, target_account: target_account)
-
-      unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block)
-
-      expect(unblock_salmon.title.text).to eq 'account no longer blocks target_account@remote'
-    end
-
-    it 'appends author element with account' do
-      account = Fabricate(:account, domain: nil, username: 'account')
-      block = Fabricate(:block, account: account)
-
-      unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block)
-
-      expect(unblock_salmon.author.id.text).to eq 'https://cb6e6126.ngrok.io/users/account'
-    end
-
-    it 'appends activity:object-type element with activity type' do
-      block = Fabricate(:block)
-
-      unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block)
-
-      object_type = unblock_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
-    end
-
-    it 'appends activity:verb element with block' do
-      block = Fabricate(:block)
-
-      unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block)
-
-      verb = unblock_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq OStatus::TagManager::VERBS[:unblock]
-    end
-
-    it 'appends activity:object element with target account' do
-      target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id')
-      block = Fabricate(:block, target_account: target_account)
-
-      unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block)
-
-      object = unblock_salmon.nodes.find { |node| node.name == 'activity:object' }
-      expect(object.id.text).to eq 'https://domain.test/id'
-    end
-
-    it 'returns element whose rendered view triggers block when processed' do
-      block = Fabricate(:block)
-      unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block)
-      xml = OStatus::AtomSerializer.render(unblock_salmon)
-      envelope = OStatus2::Salmon.new.pack(xml, block.account.keypair)
-
-      ProcessInteractionService.new.call(envelope, block.target_account)
-
-      expect { block.reload }.to raise_error ActiveRecord::RecordNotFound
-    end
-  end
-
-  describe '#favourite_salmon' do
-    include_examples 'namespaces' do
-      def serialize
-        favourite = Fabricate(:favourite)
-        OStatus::AtomSerializer.new.favourite_salmon(favourite)
-      end
-    end
-
-    it 'returns entry element' do
-      favourite = Fabricate(:favourite)
-      favourite_salmon = OStatus::AtomSerializer.new.favourite_salmon(favourite)
-      expect(favourite_salmon.name).to eq 'entry'
-    end
-
-    it 'appends id element with unique tag' do
-      favourite = Fabricate(:favourite, created_at: '2000-01-01T00:00:00Z')
-      favourite_salmon = OStatus::AtomSerializer.new.favourite_salmon(favourite)
-      expect(favourite_salmon.id.text).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{favourite.id}:objectType=Favourite"
-    end
-
-    it 'appends author element with account' do
-      account = Fabricate(:account, domain: nil, username: 'username')
-      favourite = Fabricate(:favourite, account: account)
-
-      favourite_salmon = OStatus::AtomSerializer.new.favourite_salmon(favourite)
-
-      expect(favourite_salmon.author.id.text).to eq 'https://cb6e6126.ngrok.io/users/username'
-    end
-
-    it 'appends activity:object-type element with activity type' do
-      favourite = Fabricate(:favourite)
-
-      favourite_salmon = OStatus::AtomSerializer.new.favourite_salmon(favourite)
-
-      object_type = favourite_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq 'http://activitystrea.ms/schema/1.0/activity'
-    end
-
-    it 'appends activity:verb element with favorite' do
-      favourite = Fabricate(:favourite)
-
-      favourite_salmon = OStatus::AtomSerializer.new.favourite_salmon(favourite)
-
-      verb = favourite_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq OStatus::TagManager::VERBS[:favorite]
-    end
-
-    it 'appends activity:object element with status' do
-      status = Fabricate(:status, created_at: '2000-01-01T00:00:00Z')
-      favourite = Fabricate(:favourite, status: status)
-
-      favourite_salmon = OStatus::AtomSerializer.new.favourite_salmon(favourite)
-
-      object = favourite_salmon.nodes.find { |node| node.name == 'activity:object' }
-      expect(object.id.text).to eq "https://cb6e6126.ngrok.io/users/#{status.account.to_param}/statuses/#{status.id}"
-    end
-
-    it 'appends thr:in-reply-to element for status' do
-      status_account = Fabricate(:account, username: 'username')
-      status = Fabricate(:status, account: status_account, created_at: '2000-01-01T00:00:00Z')
-      favourite = Fabricate(:favourite, status: status)
-
-      favourite_salmon = OStatus::AtomSerializer.new.favourite_salmon(favourite)
-
-      in_reply_to = favourite_salmon.nodes.find { |node| node.name == 'thr:in-reply-to' }
-      expect(in_reply_to.ref).to eq "https://cb6e6126.ngrok.io/users/#{status.account.to_param}/statuses/#{status.id}"
-      expect(in_reply_to.href).to eq "https://cb6e6126.ngrok.io/@username/#{status.id}"
-    end
-
-    it 'includes description' do
-      account = Fabricate(:account, domain: nil, username: 'account')
-      status_account = Fabricate(:account, domain: 'remote', username: 'status_account')
-      status = Fabricate(:status, account: status_account)
-      favourite = Fabricate(:favourite, account: account, status: status)
-
-      favourite_salmon = OStatus::AtomSerializer.new.favourite_salmon(favourite)
-
-      expect(favourite_salmon.title.text).to eq 'account favourited a status by status_account@remote'
-      expect(favourite_salmon.content.text).to eq 'account favourited a status by status_account@remote'
-    end
-
-    it 'returns element whose rendered view triggers favourite when processed' do
-      favourite = Fabricate(:favourite)
-      favourite_salmon = OStatus::AtomSerializer.new.favourite_salmon(favourite)
-      xml = OStatus::AtomSerializer.render(favourite_salmon)
-      envelope = OStatus2::Salmon.new.pack(xml, favourite.account.keypair)
-      favourite.destroy!
-
-      ProcessInteractionService.new.call(envelope, favourite.status.account)
-      expect(favourite.account.favourited?(favourite.status)).to be true
-    end
-  end
-
-  describe '#unfavourite_salmon' do
-    include_examples 'namespaces' do
-      def serialize
-        favourite = Fabricate(:favourite)
-        OStatus::AtomSerializer.new.favourite_salmon(favourite)
-      end
-    end
-
-    it 'returns entry element' do
-      favourite = Fabricate(:favourite)
-      unfavourite_salmon = OStatus::AtomSerializer.new.unfavourite_salmon(favourite)
-      expect(unfavourite_salmon.name).to eq 'entry'
-    end
-
-    it 'appends id element with unique tag' do
-      favourite = Fabricate(:favourite)
-
-      time_before = Time.zone.now
-      unfavourite_salmon = OStatus::AtomSerializer.new.unfavourite_salmon(favourite)
-      time_after = Time.zone.now
-
-      expect(unfavourite_salmon.id.text).to(
-        eq(OStatus::TagManager.instance.unique_tag(time_before.utc, favourite.id, 'Favourite'))
-          .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, favourite.id, 'Favourite')))
-      )
-    end
-
-    it 'appends author element with account' do
-      account = Fabricate(:account, domain: nil, username: 'username')
-      favourite = Fabricate(:favourite, account: account)
-
-      unfavourite_salmon = OStatus::AtomSerializer.new.unfavourite_salmon(favourite)
-
-      expect(unfavourite_salmon.author.id.text).to eq 'https://cb6e6126.ngrok.io/users/username'
-    end
-
-    it 'appends activity:object-type element with activity type' do
-      favourite = Fabricate(:favourite)
-
-      unfavourite_salmon = OStatus::AtomSerializer.new.unfavourite_salmon(favourite)
-
-      object_type = unfavourite_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq 'http://activitystrea.ms/schema/1.0/activity'
-    end
-
-    it 'appends activity:verb element with favorite' do
-      favourite = Fabricate(:favourite)
-
-      unfavourite_salmon = OStatus::AtomSerializer.new.unfavourite_salmon(favourite)
-
-      verb = unfavourite_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq OStatus::TagManager::VERBS[:unfavorite]
-    end
-
-    it 'appends activity:object element with status' do
-      status = Fabricate(:status, created_at: '2000-01-01T00:00:00Z')
-      favourite = Fabricate(:favourite, status: status)
-
-      unfavourite_salmon = OStatus::AtomSerializer.new.unfavourite_salmon(favourite)
-
-      object = unfavourite_salmon.nodes.find { |node| node.name == 'activity:object' }
-      expect(object.id.text).to eq "https://cb6e6126.ngrok.io/users/#{status.account.to_param}/statuses/#{status.id}"
-    end
-
-    it 'appends thr:in-reply-to element for status' do
-      status_account = Fabricate(:account, username: 'username')
-      status = Fabricate(:status, account: status_account, created_at: '2000-01-01T00:00:00Z')
-      favourite = Fabricate(:favourite, status: status)
-
-      unfavourite_salmon = OStatus::AtomSerializer.new.unfavourite_salmon(favourite)
-
-      in_reply_to = unfavourite_salmon.nodes.find { |node| node.name == 'thr:in-reply-to' }
-      expect(in_reply_to.ref).to eq "https://cb6e6126.ngrok.io/users/#{status.account.to_param}/statuses/#{status.id}"
-      expect(in_reply_to.href).to eq "https://cb6e6126.ngrok.io/@username/#{status.id}"
-    end
-
-    it 'includes description' do
-      account = Fabricate(:account, domain: nil, username: 'account')
-      status_account = Fabricate(:account, domain: 'remote', username: 'status_account')
-      status = Fabricate(:status, account: status_account)
-      favourite = Fabricate(:favourite, account: account, status: status)
-
-      unfavourite_salmon = OStatus::AtomSerializer.new.unfavourite_salmon(favourite)
-
-      expect(unfavourite_salmon.title.text).to eq 'account no longer favourites a status by status_account@remote'
-      expect(unfavourite_salmon.content.text).to eq 'account no longer favourites a status by status_account@remote'
-    end
-
-    it 'returns element whose rendered view triggers unfavourite when processed' do
-      favourite = Fabricate(:favourite)
-      unfavourite_salmon = OStatus::AtomSerializer.new.unfavourite_salmon(favourite)
-      xml = OStatus::AtomSerializer.render(unfavourite_salmon)
-      envelope = OStatus2::Salmon.new.pack(xml, favourite.account.keypair)
-
-      ProcessInteractionService.new.call(envelope, favourite.status.account)
-      expect { favourite.reload }.to raise_error ActiveRecord::RecordNotFound
-    end
-  end
-
-  describe '#follow_salmon' do
-    include_examples 'namespaces' do
-      def serialize
-        follow = Fabricate(:follow)
-        OStatus::AtomSerializer.new.follow_salmon(follow)
-      end
-    end
-
-    it 'returns entry element' do
-      follow = Fabricate(:follow)
-      follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow)
-      expect(follow_salmon.name).to eq 'entry'
-    end
-
-    it 'appends id element with unique tag' do
-      follow = Fabricate(:follow, created_at: '2000-01-01T00:00:00Z')
-      follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow)
-      expect(follow_salmon.id.text).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{follow.id}:objectType=Follow"
-    end
-
-    it 'appends author element with account' do
-      account = Fabricate(:account, domain: nil, username: 'username')
-      follow = Fabricate(:follow, account: account)
-
-      follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow)
-
-      expect(follow_salmon.author.id.text).to eq 'https://cb6e6126.ngrok.io/users/username'
-    end
-
-    it 'appends activity:object-type element with activity type' do
-      follow = Fabricate(:follow)
-
-      follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow)
-
-      object_type = follow_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
-    end
-
-    it 'appends activity:verb element with follow' do
-      follow = Fabricate(:follow)
-
-      follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow)
-
-      verb = follow_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq OStatus::TagManager::VERBS[:follow]
-    end
-
-    it 'appends activity:object element with target account' do
-      target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id')
-      follow = Fabricate(:follow, target_account: target_account)
-
-      follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow)
-
-      object = follow_salmon.nodes.find { |node| node.name == 'activity:object' }
-      expect(object.id.text).to eq 'https://domain.test/id'
-    end
-
-    it 'includes description' do
-      account = Fabricate(:account, domain: nil, username: 'account')
-      target_account = Fabricate(:account, domain: 'remote', username: 'target_account')
-      follow = Fabricate(:follow, account: account, target_account: target_account)
-
-      follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow)
-
-      expect(follow_salmon.title.text).to eq 'account started following target_account@remote'
-      expect(follow_salmon.content.text).to eq 'account started following target_account@remote'
-    end
-
-    it 'returns element whose rendered view triggers follow when processed' do
-      follow = Fabricate(:follow)
-      follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow)
-      xml = OStatus::AtomSerializer.render(follow_salmon)
-      follow.destroy!
-      envelope = OStatus2::Salmon.new.pack(xml, follow.account.keypair)
-
-      ProcessInteractionService.new.call(envelope, follow.target_account)
-
-      expect(follow.account.following?(follow.target_account)).to be true
-    end
-  end
-
-  describe '#unfollow_salmon' do
-    include_examples 'namespaces' do
-      def serialize
-        follow = Fabricate(:follow)
-        follow.destroy!
-        OStatus::AtomSerializer.new.unfollow_salmon(follow)
-      end
-    end
-
-    it 'returns entry element' do
-      follow = Fabricate(:follow)
-      follow.destroy!
-
-      unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow)
-
-      expect(unfollow_salmon.name).to eq 'entry'
-    end
-
-    it 'appends id element with unique tag' do
-      follow = Fabricate(:follow)
-      follow.destroy!
-
-      time_before = Time.zone.now
-      unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow)
-      time_after = Time.zone.now
-
-      expect(unfollow_salmon.id.text).to(
-        eq(OStatus::TagManager.instance.unique_tag(time_before.utc, follow.id, 'Follow'))
-          .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, follow.id, 'Follow')))
-      )
-    end
-
-    it 'appends title element with description' do
-      account = Fabricate(:account, domain: nil, username: 'account')
-      target_account = Fabricate(:account, domain: 'remote', username: 'target_account')
-      follow = Fabricate(:follow, account: account, target_account: target_account)
-      follow.destroy!
-
-      unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow)
-
-      expect(unfollow_salmon.title.text).to eq 'account is no longer following target_account@remote'
-    end
-
-    it 'appends content element with description' do
-      account = Fabricate(:account, domain: nil, username: 'account')
-      target_account = Fabricate(:account, domain: 'remote', username: 'target_account')
-      follow = Fabricate(:follow, account: account, target_account: target_account)
-      follow.destroy!
-
-      unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow)
-
-      expect(unfollow_salmon.content.text).to eq 'account is no longer following target_account@remote'
-    end
-
-    it 'appends author element with account' do
-      account = Fabricate(:account, domain: nil, username: 'username')
-      follow = Fabricate(:follow, account: account)
-      follow.destroy!
-
-      unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow)
-
-      expect(unfollow_salmon.author.id.text).to eq 'https://cb6e6126.ngrok.io/users/username'
-    end
-
-    it 'appends activity:object-type element with activity type' do
-      follow = Fabricate(:follow)
-      follow.destroy!
-
-      unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow)
-
-      object_type = unfollow_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
-    end
-
-    it 'appends activity:verb element with follow' do
-      follow = Fabricate(:follow)
-      follow.destroy!
-
-      unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow)
-
-      verb = unfollow_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq OStatus::TagManager::VERBS[:unfollow]
-    end
-
-    it 'appends activity:object element with target account' do
-      target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id')
-      follow = Fabricate(:follow, target_account: target_account)
-      follow.destroy!
-
-      unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow)
-
-      object = unfollow_salmon.nodes.find { |node| node.name == 'activity:object' }
-      expect(object.id.text).to eq 'https://domain.test/id'
-    end
-
-    it 'returns element whose rendered view triggers unfollow when processed' do
-      follow = Fabricate(:follow)
-      follow.destroy!
-      unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow)
-      xml = OStatus::AtomSerializer.render(unfollow_salmon)
-      follow.account.follow!(follow.target_account)
-      envelope = OStatus2::Salmon.new.pack(xml, follow.account.keypair)
-
-      ProcessInteractionService.new.call(envelope, follow.target_account)
-
-      expect(follow.account.following?(follow.target_account)).to be false
-    end
-  end
-
-  describe '#follow_request_salmon' do
-    include_examples 'namespaces' do
-      def serialize
-        follow_request = Fabricate(:follow_request)
-        OStatus::AtomSerializer.new.follow_request_salmon(follow_request)
-      end
-    end
-
-    context do
-      def serialize(follow_request)
-        OStatus::AtomSerializer.new.follow_request_salmon(follow_request)
-      end
-
-      it_behaves_like 'follow request salmon'
-
-      it 'appends id element with unique tag' do
-        follow_request = Fabricate(:follow_request, created_at: '2000-01-01T00:00:00Z')
-        follow_request_salmon = serialize(follow_request)
-        expect(follow_request_salmon.id.text).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{follow_request.id}:objectType=FollowRequest"
-      end
-
-      it 'appends title element with description' do
-        account = Fabricate(:account, domain: nil, username: 'account')
-        target_account = Fabricate(:account, domain: 'remote', username: 'target_account')
-        follow_request = Fabricate(:follow_request, account: account, target_account: target_account)
-        follow_request_salmon = serialize(follow_request)
-        expect(follow_request_salmon.title.text).to eq 'account requested to follow target_account@remote'
-      end
-
-      it 'returns element whose rendered view triggers follow request when processed' do
-        follow_request = Fabricate(:follow_request)
-        follow_request_salmon = serialize(follow_request)
-        xml = OStatus::AtomSerializer.render(follow_request_salmon)
-        envelope = OStatus2::Salmon.new.pack(xml, follow_request.account.keypair)
-        follow_request.destroy!
-
-        ProcessInteractionService.new.call(envelope, follow_request.target_account)
-
-        expect(follow_request.account.requested?(follow_request.target_account)).to eq true
-      end
-    end
-  end
-
-  describe '#authorize_follow_request_salmon' do
-    include_examples 'namespaces' do
-      def serialize
-        follow_request = Fabricate(:follow_request)
-        OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request)
-      end
-    end
-
-    it_behaves_like 'follow request salmon' do
-      def serialize(follow_request)
-        authorize_follow_request_salmon = OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request)
-        authorize_follow_request_salmon.nodes.find { |node| node.name == 'activity:object' }
-      end
-    end
-
-    it 'appends id element with unique tag' do
-      follow_request = Fabricate(:follow_request)
-
-      time_before = Time.zone.now
-      authorize_follow_request_salmon = OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request)
-      time_after = Time.zone.now
-
-      expect(authorize_follow_request_salmon.id.text).to(
-        eq(OStatus::TagManager.instance.unique_tag(time_before.utc, follow_request.id, 'FollowRequest'))
-          .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, follow_request.id, 'FollowRequest')))
-      )
-    end
-
-    it 'appends title element with description' do
-      account = Fabricate(:account, domain: 'remote', username: 'account')
-      target_account = Fabricate(:account, domain: nil, username: 'target_account')
-      follow_request = Fabricate(:follow_request, account: account, target_account: target_account)
-
-      authorize_follow_request_salmon = OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request)
-
-      expect(authorize_follow_request_salmon.title.text).to eq 'target_account authorizes follow request by account@remote'
-    end
-
-    it 'appends activity:object-type element with activity type' do
-      follow_request = Fabricate(:follow_request)
-
-      authorize_follow_request_salmon = OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request)
-
-      object_type = authorize_follow_request_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
-    end
-
-    it 'appends activity:verb element with authorize' do
-      follow_request = Fabricate(:follow_request)
-
-      authorize_follow_request_salmon = OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request)
-
-      verb = authorize_follow_request_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq OStatus::TagManager::VERBS[:authorize]
-    end
-
-    it 'returns element whose rendered view creates follow from follow request when processed' do
-      follow_request = Fabricate(:follow_request)
-      authorize_follow_request_salmon = OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request)
-      xml = OStatus::AtomSerializer.render(authorize_follow_request_salmon)
-      envelope = OStatus2::Salmon.new.pack(xml, follow_request.target_account.keypair)
-
-      ProcessInteractionService.new.call(envelope, follow_request.account)
-
-      expect(follow_request.account.following?(follow_request.target_account)).to eq true
-      expect { follow_request.reload }.to raise_error ActiveRecord::RecordNotFound
-    end
-  end
-
-  describe '#reject_follow_request_salmon' do
-    include_examples 'namespaces' do
-      def serialize
-        follow_request = Fabricate(:follow_request)
-        OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request)
-      end
-    end
-
-    it_behaves_like 'follow request salmon' do
-      def serialize(follow_request)
-        reject_follow_request_salmon = OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request)
-        reject_follow_request_salmon.nodes.find { |node| node.name == 'activity:object' }
-      end
-    end
-
-    it 'appends id element with unique tag' do
-      follow_request = Fabricate(:follow_request)
-
-      time_before = Time.zone.now
-      reject_follow_request_salmon = OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request)
-      time_after = Time.zone.now
-
-      expect(reject_follow_request_salmon.id.text).to(
-        eq(OStatus::TagManager.instance.unique_tag(time_before.utc, follow_request.id, 'FollowRequest'))
-          .or(OStatus::TagManager.instance.unique_tag(time_after.utc, follow_request.id, 'FollowRequest'))
-      )
-    end
-
-    it 'appends title element with description' do
-      account = Fabricate(:account, domain: 'remote', username: 'account')
-      target_account = Fabricate(:account, domain: nil, username: 'target_account')
-      follow_request = Fabricate(:follow_request, account: account, target_account: target_account)
-      reject_follow_request_salmon = OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request)
-      expect(reject_follow_request_salmon.title.text).to eq 'target_account rejects follow request by account@remote'
-    end
-
-    it 'appends activity:object-type element with activity type' do
-      follow_request = Fabricate(:follow_request)
-      reject_follow_request_salmon = OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request)
-      object_type = reject_follow_request_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
-    end
-
-    it 'appends activity:verb element with authorize' do
-      follow_request = Fabricate(:follow_request)
-      reject_follow_request_salmon = OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request)
-      verb = reject_follow_request_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq OStatus::TagManager::VERBS[:reject]
-    end
-
-    it 'returns element whose rendered view deletes follow request when processed' do
-      follow_request = Fabricate(:follow_request)
-      reject_follow_request_salmon = OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request)
-      xml = OStatus::AtomSerializer.render(reject_follow_request_salmon)
-      envelope = OStatus2::Salmon.new.pack(xml, follow_request.target_account.keypair)
-
-      ProcessInteractionService.new.call(envelope, follow_request.account)
-
-      expect(follow_request.account.following?(follow_request.target_account)).to eq false
-      expect { follow_request.reload }.to raise_error ActiveRecord::RecordNotFound
-    end
-  end
-
-  describe '#object' do
-    include_examples 'status attributes' do
-      def serialize(status)
-        OStatus::AtomSerializer.new.object(status)
-      end
-    end
-
-    it 'returns activity:object element' do
-      status = Fabricate(:status)
-      object = OStatus::AtomSerializer.new.object(status)
-      expect(object.name).to eq 'activity:object'
-    end
-
-    it 'appends id element with URL for status' do
-      status = Fabricate(:status, created_at: '2000-01-01T00:00:00Z')
-      object = OStatus::AtomSerializer.new.object(status)
-      expect(object.id.text).to eq "https://cb6e6126.ngrok.io/users/#{status.account.to_param}/statuses/#{status.id}"
-    end
-
-    it 'appends published element with created date' do
-      status = Fabricate(:status, created_at: '2000-01-01T00:00:00Z')
-      object = OStatus::AtomSerializer.new.object(status)
-      expect(object.published.text).to eq '2000-01-01T00:00:00Z'
-    end
-
-    it 'appends updated element with updated date' do
-      status = Fabricate(:status)
-      status.updated_at = '2000-01-01T00:00:00Z'
-      object = OStatus::AtomSerializer.new.object(status)
-      expect(object.updated.text).to eq '2000-01-01T00:00:00Z'
-    end
-
-    it 'appends title element with title' do
-      account = Fabricate(:account, username: 'username')
-      status = Fabricate(:status, account: account)
-
-      object = OStatus::AtomSerializer.new.object(status)
-
-      expect(object.title.text).to eq 'New status by username'
-    end
-
-    it 'appends author element with account' do
-      account = Fabricate(:account, username: 'username')
-      status = Fabricate(:status, account: account)
-
-      entry = OStatus::AtomSerializer.new.object(status)
-
-      expect(entry.author.id.text).to eq 'https://cb6e6126.ngrok.io/users/username'
-    end
-
-    it 'appends activity:object-type element with object type' do
-      status = Fabricate(:status)
-
-      entry = OStatus::AtomSerializer.new.object(status)
-
-      object_type = entry.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq OStatus::TagManager::TYPES[:note]
-    end
-
-    it 'appends activity:verb element with verb' do
-      status = Fabricate(:status)
-
-      entry = OStatus::AtomSerializer.new.object(status)
-
-      object_type = entry.nodes.find { |node| node.name == 'activity:verb' }
-      expect(object_type.text).to eq OStatus::TagManager::VERBS[:post]
-    end
-
-    it 'appends link element for an alternative' do
-      account = Fabricate(:account, username: 'username')
-      status = Fabricate(:status, account: account)
-
-      entry = OStatus::AtomSerializer.new.object(status)
-
-      link = entry.nodes.find { |node| node.name == 'link' && node[:rel] == 'alternate' && node[:type] == 'text/html' }
-      expect(link[:type]).to eq 'text/html'
-      expect(link[:href]).to eq "https://cb6e6126.ngrok.io/@username/#{status.id}"
-    end
-
-    it 'appends thr:in-reply-to element if it is a reply and thread is not nil' do
-      account = Fabricate(:account, username: 'username')
-      thread = Fabricate(:status, account: account, created_at: '2000-01-01T00:00:00Z')
-      reply = Fabricate(:status, thread: thread)
-
-      entry = OStatus::AtomSerializer.new.object(reply)
-
-      in_reply_to = entry.nodes.find { |node| node.name == 'thr:in-reply-to' }
-      expect(in_reply_to.ref).to eq "https://cb6e6126.ngrok.io/users/#{thread.account.to_param}/statuses/#{thread.id}"
-      expect(in_reply_to.href).to eq "https://cb6e6126.ngrok.io/@username/#{thread.id}"
-    end
-
-    it 'does not append thr:in-reply-to element if thread is nil' do
-      status = Fabricate(:status, thread: nil)
-      entry = OStatus::AtomSerializer.new.object(status)
-      entry.nodes.each { |node| expect(node.name).not_to eq 'thr:in-reply-to' }
-    end
-
-    it 'does not append ostatus:conversation element if conversation_id is nil' do
-      status = Fabricate.build(:status, conversation_id: nil)
-      status.save!(validate: false)
-
-      entry = OStatus::AtomSerializer.new.object(status)
-
-      entry.nodes.each { |node| expect(node.name).not_to eq 'ostatus:conversation' }
-    end
-
-    it 'appends ostatus:conversation element if conversation_id is not nil' do
-      status = Fabricate(:status)
-      status.conversation.update!(created_at: '2000-01-01T00:00:00Z')
-
-      entry = OStatus::AtomSerializer.new.object(status)
-
-      conversation = entry.nodes.find { |node| node.name == 'ostatus:conversation' }
-      expect(conversation[:ref]).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{status.conversation.id}:objectType=Conversation"
-    end
-  end
-end
diff --git a/spec/lib/request_pool_spec.rb b/spec/lib/request_pool_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4a144d7c7f7402e64e7321dbfec4a6969819d1c7
--- /dev/null
+++ b/spec/lib/request_pool_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe RequestPool do
+  subject { described_class.new }
+
+  describe '#with' do
+    it 'returns a HTTP client for a host' do
+      subject.with('http://example.com') do |http_client|
+        expect(http_client).to be_a HTTP::Client
+      end
+    end
+
+    it 'returns the same instance of HTTP client within the same thread for the same host' do
+      test_client = nil
+
+      subject.with('http://example.com') { |http_client| test_client = http_client }
+      expect(test_client).to_not be_nil
+      subject.with('http://example.com') { |http_client| expect(http_client).to be test_client }
+    end
+
+    it 'returns different HTTP clients for different hosts' do
+      test_client = nil
+
+      subject.with('http://example.com') { |http_client| test_client = http_client }
+      expect(test_client).to_not be_nil
+      subject.with('http://example.org') { |http_client| expect(http_client).to_not be test_client }
+    end
+
+    it 'grows to the number of threads accessing it' do
+      stub_request(:get, 'http://example.com/').to_return(status: 200, body: 'Hello!')
+
+      subject
+
+      threads = 20.times.map do |i|
+        Thread.new do
+          20.times do
+            subject.with('http://example.com') do |http_client|
+              http_client.get('/').flush
+            end
+          end
+        end
+      end
+
+      threads.map(&:join)
+
+      expect(subject.size).to be > 1
+    end
+
+    it 'closes idle connections' do
+      stub_request(:get, 'http://example.com/').to_return(status: 200, body: 'Hello!')
+
+      subject.with('http://example.com') do |http_client|
+        http_client.get('/').flush
+      end
+
+      expect(subject.size).to eq 1
+      sleep RequestPool::MAX_IDLE_TIME + 30 + 1
+      expect(subject.size).to eq 0
+    end
+  end
+end
diff --git a/spec/lib/spam_check_spec.rb b/spec/lib/spam_check_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d4d66a49930737df8fe489c48871d5151e400b4d
--- /dev/null
+++ b/spec/lib/spam_check_spec.rb
@@ -0,0 +1,192 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe SpamCheck do
+  let!(:sender) { Fabricate(:account) }
+  let!(:alice) { Fabricate(:account, username: 'alice') }
+  let!(:bob) { Fabricate(:account, username: 'bob') }
+
+  def status_with_html(text, options = {})
+    status = PostStatusService.new.call(sender, { text: text }.merge(options))
+    status.update_columns(text: Formatter.instance.format(status), local: false)
+    status
+  end
+
+  describe '#hashable_text' do
+    it 'removes mentions from HTML for remote statuses' do
+      status = status_with_html('@alice Hello')
+      expect(described_class.new(status).hashable_text).to eq 'hello'
+    end
+
+    it 'removes mentions from text for local statuses' do
+      status = PostStatusService.new.call(alice, text: "Hey @#{sender.username}, how are you?")
+      expect(described_class.new(status).hashable_text).to eq 'hey , how are you?'
+    end
+  end
+
+  describe '#insufficient_data?' do
+    it 'returns true when there is no text' do
+      status = status_with_html('@alice')
+      expect(described_class.new(status).insufficient_data?).to be true
+    end
+
+    it 'returns false when there is text' do
+      status = status_with_html('@alice h')
+      expect(described_class.new(status).insufficient_data?).to be false
+    end
+  end
+
+  describe '#digest' do
+    it 'returns a string' do
+      status = status_with_html('@alice Hello world')
+      expect(described_class.new(status).digest).to be_a String
+    end
+  end
+
+  describe '#spam?' do
+    it 'returns false for a unique status' do
+      status = status_with_html('@alice Hello')
+      expect(described_class.new(status).spam?).to be false
+    end
+
+    it 'returns false for different statuses to the same recipient' do
+      status1 = status_with_html('@alice Hello')
+      described_class.new(status1).remember!
+      status2 = status_with_html('@alice Are you available to talk?')
+      expect(described_class.new(status2).spam?).to be false
+    end
+
+    it 'returns false for statuses with different content warnings' do
+      status1 = status_with_html('@alice Are you available to talk?')
+      described_class.new(status1).remember!
+      status2 = status_with_html('@alice Are you available to talk?', spoiler_text: 'This is a completely different matter than what I was talking about previously, I swear!')
+      expect(described_class.new(status2).spam?).to be false
+    end
+
+    it 'returns false for different statuses to different recipients' do
+      status1 = status_with_html('@alice How is it going?')
+      described_class.new(status1).remember!
+      status2 = status_with_html('@bob Are you okay?')
+      expect(described_class.new(status2).spam?).to be false
+    end
+
+    it 'returns false for very short different statuses to different recipients' do
+      status1 = status_with_html('@alice 🙄')
+      described_class.new(status1).remember!
+      status2 = status_with_html('@bob Huh?')
+      expect(described_class.new(status2).spam?).to be false
+    end
+
+    it 'returns false for statuses with no text' do
+      status1 = status_with_html('@alice')
+      described_class.new(status1).remember!
+      status2 = status_with_html('@bob')
+      expect(described_class.new(status2).spam?).to be false
+    end
+
+    it 'returns true for duplicate statuses to the same recipient' do
+      described_class::THRESHOLD.times do
+        status1 = status_with_html('@alice Hello')
+        described_class.new(status1).remember!
+      end
+
+      status2 = status_with_html('@alice Hello')
+      expect(described_class.new(status2).spam?).to be true
+    end
+
+    it 'returns true for duplicate statuses to different recipients' do
+      described_class::THRESHOLD.times do
+        status1 = status_with_html('@alice Hello')
+        described_class.new(status1).remember!
+      end
+
+      status2 = status_with_html('@bob Hello')
+      expect(described_class.new(status2).spam?).to be true
+    end
+
+    it 'returns true for nearly identical statuses with random numbers' do
+      source_text = 'Sodium, atomic number 11, was first isolated by Humphry Davy in 1807. A chemical component of salt, he named it Na in honor of the saltiest region on earth, North America.'
+
+      described_class::THRESHOLD.times do
+        status1 = status_with_html('@alice ' + source_text + ' 1234')
+        described_class.new(status1).remember!
+      end
+
+      status2 = status_with_html('@bob ' + source_text + ' 9568')
+      expect(described_class.new(status2).spam?).to be true
+    end
+  end
+
+  describe '#skip?' do
+    it 'returns true when the sender is already silenced' do
+      status = status_with_html('@alice Hello')
+      sender.silence!
+      expect(described_class.new(status).skip?).to be true
+    end
+
+    it 'returns true when the mentioned person follows the sender' do
+      status = status_with_html('@alice Hello')
+      alice.follow!(sender)
+      expect(described_class.new(status).skip?).to be true
+    end
+
+    it 'returns false when even one mentioned person doesn\'t follow the sender' do
+      status = status_with_html('@alice @bob Hello')
+      alice.follow!(sender)
+      expect(described_class.new(status).skip?).to be false
+    end
+
+    it 'returns true when the sender is replying to a status that mentions the sender' do
+      parent = PostStatusService.new.call(alice, text: "Hey @#{sender.username}, how are you?")
+      status = status_with_html('@alice @bob Hello', thread: parent)
+      expect(described_class.new(status).skip?).to be true
+    end
+  end
+
+  describe '#remember!' do
+    let(:status) { status_with_html('@alice') }
+    let(:spam_check) { described_class.new(status) }
+    let(:redis_key) { spam_check.send(:redis_key) }
+
+    it 'remembers' do
+      expect(Redis.current.exists(redis_key)).to be true
+      spam_check.remember!
+      expect(Redis.current.exists(redis_key)).to be true
+    end
+  end
+
+  describe '#reset!' do
+    let(:status) { status_with_html('@alice') }
+    let(:spam_check) { described_class.new(status) }
+    let(:redis_key) { spam_check.send(:redis_key) }
+
+    before do
+      spam_check.remember!
+    end
+
+    it 'resets' do
+      expect(Redis.current.exists(redis_key)).to be true
+      spam_check.reset!
+      expect(Redis.current.exists(redis_key)).to be false
+    end
+  end
+
+  describe '#flag!' do
+    let!(:status1) { status_with_html('@alice General Kenobi you are a bold one') }
+    let!(:status2) { status_with_html('@alice @bob General Kenobi, you are a bold one') }
+
+    before do
+      described_class.new(status1).remember!
+      described_class.new(status2).flag!
+    end
+
+    it 'creates a report about the account' do
+      expect(sender.targeted_reports.unresolved.count).to eq 1
+    end
+
+    it 'attaches both matching statuses to the report' do
+      expect(sender.targeted_reports.first.status_ids).to include(status1.id, status2.id)
+    end
+  end
+end
diff --git a/spec/lib/status_finder_spec.rb b/spec/lib/status_finder_spec.rb
index 6b4ee434f3cff813c963b0f33425a90ab9c425a4..61483f4bfe9bc2e6116f84adb0d2a55e7e37ea88 100644
--- a/spec/lib/status_finder_spec.rb
+++ b/spec/lib/status_finder_spec.rb
@@ -25,15 +25,6 @@ describe StatusFinder do
       end
     end
 
-    context 'with a stream entry url' do
-      let(:stream_entry) { Fabricate(:stream_entry) }
-      let(:url) { account_stream_entry_url(stream_entry.account, stream_entry) }
-
-      it 'finds the stream entry' do
-        expect(subject.status).to eq(stream_entry.status)
-      end
-    end
-
     context 'with a remote url even if id exists on local' do
       let(:status) { Fabricate(:status) }
       let(:url) { "https://example.com/users/test/statuses/#{status.id}" }
diff --git a/spec/lib/tag_manager_spec.rb b/spec/lib/tag_manager_spec.rb
index 3a804ac0f0aebcdbd0a83c3d65d49edbe7ab894b..e9a7aa93441ddec877a26c7baff92a4763040e53 100644
--- a/spec/lib/tag_manager_spec.rb
+++ b/spec/lib/tag_manager_spec.rb
@@ -119,46 +119,4 @@ RSpec.describe TagManager do
       expect(TagManager.instance.same_acct?('username', 'incorrect@Cb6E6126.nGrOk.Io')).to eq false
     end
   end
-
-  describe '#url_for' do
-    let(:alice) { Fabricate(:account, username: 'alice') }
-
-    subject { TagManager.instance.url_for(target) }
-
-    context 'activity object' do
-      let(:target) { Fabricate(:status, account: alice, reblog: Fabricate(:status)).stream_entry }
-
-      it 'returns the unique tag for status' do
-        expect(target.object_type).to eq :activity
-        is_expected.to eq "https://cb6e6126.ngrok.io/@alice/#{target.id}"
-      end
-    end
-
-    context 'comment object' do
-      let(:target) { Fabricate(:status, account: alice, reply: true) }
-
-      it 'returns the unique tag for status' do
-        expect(target.object_type).to eq :comment
-        is_expected.to eq "https://cb6e6126.ngrok.io/@alice/#{target.id}"
-      end
-    end
-
-    context 'note object' do
-      let(:target) { Fabricate(:status, account: alice, reply: false, thread: nil) }
-
-      it 'returns the unique tag for status' do
-        expect(target.object_type).to eq :note
-        is_expected.to eq "https://cb6e6126.ngrok.io/@alice/#{target.id}"
-      end
-    end
-
-    context 'person object' do
-      let(:target) { alice }
-
-      it 'returns the URL for account' do
-        expect(target.object_type).to eq :person
-        is_expected.to eq 'https://cb6e6126.ngrok.io/@alice'
-      end
-    end
-  end
 end
diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb
index 53c8364944579c9ef74eca4fa4ce9ddca9206597..464f177d0e6008caf76f02d423a09f5ea3a530b1 100644
--- a/spec/mailers/previews/user_mailer_preview.rb
+++ b/spec/mailers/previews/user_mailer_preview.rb
@@ -18,6 +18,21 @@ class UserMailerPreview < ActionMailer::Preview
     UserMailer.password_change(User.first)
   end
 
+  # Preview this email at http://localhost:3000/rails/mailers/user_mailer/two_factor_disabled
+  def two_factor_disabled
+    UserMailer.two_factor_disabled(User.first)
+  end
+
+  # Preview this email at http://localhost:3000/rails/mailers/user_mailer/two_factor_enabled
+  def two_factor_enabled
+    UserMailer.two_factor_enabled(User.first)
+  end
+
+  # Preview this email at http://localhost:3000/rails/mailers/user_mailer/two_factor_recovery_codes_changed
+  def two_factor_recovery_codes_changed
+    UserMailer.two_factor_recovery_codes_changed(User.first)
+  end
+
   # Preview this email at http://localhost:3000/rails/mailers/user_mailer/reconfirmation_instructions
   def reconfirmation_instructions
     user = User.first
@@ -42,6 +57,6 @@ class UserMailerPreview < ActionMailer::Preview
 
   # Preview this email at http://localhost:3000/rails/mailers/user_mailer/warning
   def warning
-    UserMailer.warning(User.first, AccountWarning.new(text: '', action: :silence))
+    UserMailer.warning(User.first, AccountWarning.new(text: '', action: :silence), [Status.first.id])
   end
 end
diff --git a/spec/models/account_alias_spec.rb b/spec/models/account_alias_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..27ec215aa48eb0af604fed038daeebecc55241d2
--- /dev/null
+++ b/spec/models/account_alias_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe AccountAlias, type: :model do
+
+end
diff --git a/spec/models/account_migration_spec.rb b/spec/models/account_migration_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8461b4b28ec71372164dc740957f9718ea7cd540
--- /dev/null
+++ b/spec/models/account_migration_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe AccountMigration, type: :model do
+
+end
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index ce9ea250d1329602110666e4be933c51c7380b5f..b2f6234cb9f0e6b7955ba67591803ead896e2cf0 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -126,8 +126,8 @@ RSpec.describe Account, type: :model do
       end
 
       it 'sets default avatar, header, avatar_remote_url, and header_remote_url' do
-        expect(account.avatar_remote_url).to eq ''
-        expect(account.header_remote_url).to eq ''
+        expect(account.avatar_remote_url).to eq 'https://remote.test/invalid_avatar'
+        expect(account.header_remote_url).to eq expectation.header_remote_url
         expect(account.avatar_file_name).to  eq nil
         expect(account.header_file_name).to  eq nil
       end
@@ -450,7 +450,7 @@ RSpec.describe Account, type: :model do
   describe '.domains' do
     it 'returns domains' do
       Fabricate(:account, domain: 'domain')
-      expect(Account.domains).to match_array(['domain'])
+      expect(Account.remote.domains).to match_array(['domain'])
     end
   end
 
@@ -583,26 +583,43 @@ RSpec.describe Account, type: :model do
         expect(account.valid?).to be true
       end
 
+      it 'is valid if we are creating an instance actor account with a period' do
+        account = Fabricate.build(:account, id: -99, actor_type: 'Application', locked: true, username: 'example.com')
+        expect(account.valid?).to be true
+      end
+
+      it 'is valid if we are creating a possibly-conflicting instance actor account' do
+        account_1 = Fabricate(:account, username: 'examplecom')
+        account_2 = Fabricate.build(:account, id: -99, actor_type: 'Application', locked: true, username: 'example.com')
+        expect(account_2.valid?).to be true
+      end
+
       it 'is invalid if the username doesn\'t only contains letters, numbers and underscores' do
         account = Fabricate.build(:account, username: 'the-doctor')
         account.valid?
         expect(account).to model_have_error_on_field(:username)
       end
 
+      it 'is invalid if the username contains a period' do
+        account = Fabricate.build(:account, username: 'the.doctor')
+        account.valid?
+        expect(account).to model_have_error_on_field(:username)
+      end
+
       it 'is invalid if the username is longer then 30 characters' do
-        account = Fabricate.build(:account, username: Faker::Lorem.characters(31))
+        account = Fabricate.build(:account, username: Faker::Lorem.characters(number: 31))
         account.valid?
         expect(account).to model_have_error_on_field(:username)
       end
 
       it 'is invalid if the display name is longer than 30 characters' do
-        account = Fabricate.build(:account, display_name: Faker::Lorem.characters(31))
+        account = Fabricate.build(:account, display_name: Faker::Lorem.characters(number: 31))
         account.valid?
         expect(account).to model_have_error_on_field(:display_name)
       end
 
       it 'is invalid if the note is longer than 500 characters' do
-        account = Fabricate.build(:account, note: Faker::Lorem.characters(501))
+        account = Fabricate.build(:account, note: Faker::Lorem.characters(number: 501))
         account.valid?
         expect(account).to model_have_error_on_field(:note)
       end
@@ -636,19 +653,19 @@ RSpec.describe Account, type: :model do
       end
 
       it 'is valid even if the username is longer then 30 characters' do
-        account = Fabricate.build(:account, domain: 'domain', username: Faker::Lorem.characters(31))
+        account = Fabricate.build(:account, domain: 'domain', username: Faker::Lorem.characters(number: 31))
         account.valid?
         expect(account).not_to model_have_error_on_field(:username)
       end
 
       it 'is valid even if the display name is longer than 30 characters' do
-        account = Fabricate.build(:account, domain: 'domain', display_name: Faker::Lorem.characters(31))
+        account = Fabricate.build(:account, domain: 'domain', display_name: Faker::Lorem.characters(number: 31))
         account.valid?
         expect(account).not_to model_have_error_on_field(:display_name)
       end
 
       it 'is valid even if the note is longer than 500 characters' do
-        account = Fabricate.build(:account, domain: 'domain', note: Faker::Lorem.characters(501))
+        account = Fabricate.build(:account, domain: 'domain', note: Faker::Lorem.characters(number: 501))
         account.valid?
         expect(account).not_to model_have_error_on_field(:note)
       end
@@ -665,7 +682,7 @@ RSpec.describe Account, type: :model do
           { username: 'b', domain: 'b' },
         ].map(&method(:Fabricate).curry(2).call(:account))
 
-        expect(Account.alphabetic).to eq matches
+        expect(Account.where('id > 0').alphabetic).to eq matches
       end
     end
 
@@ -732,7 +749,7 @@ RSpec.describe Account, type: :model do
         2.times { Fabricate(:account, domain: 'example.com') }
         Fabricate(:account, domain: 'example2.com')
 
-        results = Account.by_domain_accounts
+        results = Account.where('id > 0').by_domain_accounts
         expect(results.length).to eq 2
         expect(results.first.domain).to eq 'example.com'
         expect(results.first.accounts_count).to eq 2
@@ -745,7 +762,7 @@ RSpec.describe Account, type: :model do
       it 'returns an array of accounts who do not have a domain' do
         account_1 = Fabricate(:account, domain: nil)
         account_2 = Fabricate(:account, domain: 'example.com')
-        expect(Account.local).to match_array([account_1])
+        expect(Account.where('id > 0').local).to match_array([account_1])
       end
     end
 
@@ -756,14 +773,14 @@ RSpec.describe Account, type: :model do
           matches[index] = Fabricate(:account, domain: matches[index])
         end
 
-        expect(Account.partitioned).to match_array(matches)
+        expect(Account.where('id > 0').partitioned).to match_array(matches)
       end
     end
 
     describe 'recent' do
       it 'returns a relation of accounts sorted by recent creation' do
         matches = 2.times.map { Fabricate(:account) }
-        expect(Account.recent).to match_array(matches)
+        expect(Account.where('id > 0').recent).to match_array(matches)
       end
     end
 
@@ -787,7 +804,7 @@ RSpec.describe Account, type: :model do
   context 'when is local' do
     # Test disabled because test environment omits autogenerating keys for performance
     xit 'generates keys' do
-      account = Account.create!(domain: nil, username: Faker::Internet.user_name(nil, ['_']))
+      account = Account.create!(domain: nil, username: Faker::Internet.user_name(separators: ['_']))
       expect(account.keypair.private?).to eq true
     end
   end
@@ -795,12 +812,12 @@ RSpec.describe Account, type: :model do
   context 'when is remote' do
     it 'does not generate keys' do
       key = OpenSSL::PKey::RSA.new(1024).public_key
-      account = Account.create!(domain: 'remote', username: Faker::Internet.user_name(nil, ['_']), public_key: key.to_pem)
+      account = Account.create!(domain: 'remote', username: Faker::Internet.user_name(separators: ['_']), public_key: key.to_pem)
       expect(account.keypair.params).to eq key.params
     end
 
     it 'normalizes domain' do
-      account = Account.create!(domain: 'にゃん', username: Faker::Internet.user_name(nil, ['_']))
+      account = Account.create!(domain: 'にゃん', username: Faker::Internet.user_name(separators: ['_']))
       expect(account.domain).to eq 'xn--r9j5b5b'
     end
   end
diff --git a/spec/models/account_stat_spec.rb b/spec/models/account_stat_spec.rb
index a94185109c774d8701e914b1d2fe6d4b945f8447..8adc0d1d638d8bf46dbb025754d4759753f3dc7f 100644
--- a/spec/models/account_stat_spec.rb
+++ b/spec/models/account_stat_spec.rb
@@ -1,4 +1,57 @@
 require 'rails_helper'
 
 RSpec.describe AccountStat, type: :model do
+  describe '#increment_count!' do
+    it 'increments the count' do
+      account_stat = AccountStat.create(account: Fabricate(:account))
+      expect(account_stat.followers_count).to eq 0
+      account_stat.increment_count!(:followers_count)
+      expect(account_stat.followers_count).to eq 1
+    end
+
+    it 'increments the count in multi-threaded an environment' do
+      account_stat   = AccountStat.create(account: Fabricate(:account), statuses_count: 0)
+      increment_by   = 15
+      wait_for_start = true
+
+      threads = Array.new(increment_by) do
+        Thread.new do
+          true while wait_for_start
+          AccountStat.find(account_stat.id).increment_count!(:statuses_count)
+        end
+      end
+
+      wait_for_start = false
+      threads.each(&:join)
+
+      expect(account_stat.reload.statuses_count).to eq increment_by
+    end
+  end
+
+  describe '#decrement_count!' do
+    it 'decrements the count' do
+      account_stat = AccountStat.create(account: Fabricate(:account), followers_count: 15)
+      expect(account_stat.followers_count).to eq 15
+      account_stat.decrement_count!(:followers_count)
+      expect(account_stat.followers_count).to eq 14
+    end
+
+    it 'decrements the count in multi-threaded an environment' do
+      account_stat   = AccountStat.create(account: Fabricate(:account), statuses_count: 15)
+      decrement_by   = 10
+      wait_for_start = true
+
+      threads = Array.new(decrement_by) do
+        Thread.new do
+          true while wait_for_start
+          AccountStat.find(account_stat.id).decrement_count!(:statuses_count)
+        end
+      end
+
+      wait_for_start = false
+      threads.each(&:join)
+
+      expect(account_stat.reload.statuses_count).to eq 5
+    end
+  end
 end
diff --git a/spec/models/admin/account_action_spec.rb b/spec/models/admin/account_action_spec.rb
index a3db60cfc85c95585a9713b9fbe9d2c90f084477..87fc285007867a129ec9a1baae59194cff9194e8 100644
--- a/spec/models/admin/account_action_spec.rb
+++ b/spec/models/admin/account_action_spec.rb
@@ -58,8 +58,8 @@ RSpec.describe Admin::AccountAction, type: :model do
       end.to change { Admin::ActionLog.count }.by 1
     end
 
-    it 'calls queue_email!' do
-      expect(account_action).to receive(:queue_email!)
+    it 'calls process_email!' do
+      expect(account_action).to receive(:process_email!)
       subject
     end
 
diff --git a/spec/models/concerns/remotable_spec.rb b/spec/models/concerns/remotable_spec.rb
index a4289cc45e79aca0e8e251eb1b6113007a4eabeb..99a60cbf652334804b256b255bb03cce017c2380 100644
--- a/spec/models/concerns/remotable_spec.rb
+++ b/spec/models/concerns/remotable_spec.rb
@@ -18,6 +18,8 @@ RSpec.describe Remotable do
 
     def hoge=(arg); end
 
+    def hoge_file_name; end
+
     def hoge_file_name=(arg); end
 
     def has_attribute?(arg); end
@@ -109,12 +111,21 @@ RSpec.describe Remotable do
       end
 
       context 'foo[attribute_name] == url' do
-        it 'makes no request' do
+        it 'makes no request if file is saved' do
           allow(foo).to receive(:[]).with(attribute_name).and_return(url)
+          allow(foo).to receive(:hoge_file_name).and_return('foo.jpg')
 
           foo.hoge_remote_url = url
           expect(request).not_to have_been_requested
         end
+
+        it 'makes request if file is not saved' do
+          allow(foo).to receive(:[]).with(attribute_name).and_return(url)
+          allow(foo).to receive(:hoge_file_name).and_return(nil)
+
+          foo.hoge_remote_url = url
+          expect(request).to have_been_requested
+        end
       end
 
       context "scheme is https, parsed_url.host isn't empty, and foo[attribute_name] != url" do
diff --git a/spec/models/concerns/streamable_spec.rb b/spec/models/concerns/streamable_spec.rb
deleted file mode 100644
index b5f2d51923e95b3dc863541e30b64397862a9974..0000000000000000000000000000000000000000
--- a/spec/models/concerns/streamable_spec.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Streamable do
-  class Parent
-    def title; end
-
-    def target; end
-
-    def thread; end
-
-    def self.has_one(*); end
-
-    def self.after_create; end
-  end
-
-  class Child < Parent
-    include Streamable
-  end
-
-  child = Child.new
-
-  describe '#title' do
-    it 'calls Parent#title' do
-      expect_any_instance_of(Parent).to receive(:title)
-      child.title
-    end
-  end
-
-  describe '#content' do
-    it 'calls #title' do
-      expect_any_instance_of(Parent).to receive(:title)
-      child.content
-    end
-  end
-
-  describe '#target' do
-    it 'calls Parent#target' do
-      expect_any_instance_of(Parent).to receive(:target)
-      child.target
-    end
-  end
-
-  describe '#object_type' do
-    it 'returns :activity' do
-      expect(child.object_type).to eq :activity
-    end
-  end
-
-  describe '#thread' do
-    it 'calls Parent#thread' do
-      expect_any_instance_of(Parent).to receive(:thread)
-      child.thread
-    end
-  end
-
-  describe '#hidden?' do
-    it 'returns false' do
-      expect(child.hidden?).to be false
-    end
-  end
-end
diff --git a/spec/models/custom_emoji_category_spec.rb b/spec/models/custom_emoji_category_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..160033f4d489b86de9d82297dee48c842f84ae8c
--- /dev/null
+++ b/spec/models/custom_emoji_category_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe CustomEmojiCategory, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/domain_allow_spec.rb b/spec/models/domain_allow_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e65435127d5d17dc176410bed0574e6582a52cfc
--- /dev/null
+++ b/spec/models/domain_allow_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe DomainAllow, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/form/status_batch_spec.rb b/spec/models/form/status_batch_spec.rb
index 00c790a11f936a12b25eb806588f38bd43c66285..68d84a7379b8c6104c2a40ccd498c909d6a2c446 100644
--- a/spec/models/form/status_batch_spec.rb
+++ b/spec/models/form/status_batch_spec.rb
@@ -41,12 +41,12 @@ describe Form::StatusBatch do
 
     it 'call RemovalWorker' do
       form.save
-      expect(RemovalWorker).to have_received(:perform_async).with(status.id)
+      expect(RemovalWorker).to have_received(:perform_async).with(status.id, immediate: true)
     end
 
     it 'do not call RemovalWorker' do
       form.save
-      expect(RemovalWorker).not_to have_received(:perform_async).with(another_status.id)
+      expect(RemovalWorker).not_to have_received(:perform_async).with(another_status.id, immediate: true)
     end
   end
 end
diff --git a/spec/models/home_feed_spec.rb b/spec/models/home_feed_spec.rb
index 3acb997f1faba7ab3056dd4f53714a43a9987d2d..ee7a83960a05271044680fc8e75a4163bacedd64 100644
--- a/spec/models/home_feed_spec.rb
+++ b/spec/models/home_feed_spec.rb
@@ -34,11 +34,10 @@ RSpec.describe HomeFeed, type: :model do
         Redis.current.set("account:#{account.id}:regeneration", true)
       end
 
-      it 'gets statuses with ids in the range from database' do
+      it 'returns nothing' do
         results = subject.get(3)
 
-        expect(results.map(&:id)).to eq [10, 3, 2]
-        expect(results.first.attributes.keys).to include('id', 'updated_at')
+        expect(results.map(&:id)).to eq []
       end
     end
   end
diff --git a/spec/models/marker_spec.rb b/spec/models/marker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d716aa75c20cf9b351c2646f63f8a63f3db5a493
--- /dev/null
+++ b/spec/models/marker_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Marker, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/poll_vote_spec.rb b/spec/models/poll_vote_spec.rb
index 354afd5350bd24da958398323795ff8aaf098bb1..563f346993a3977cc9e91d8056312154917274b5 100644
--- a/spec/models/poll_vote_spec.rb
+++ b/spec/models/poll_vote_spec.rb
@@ -1,5 +1,13 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe PollVote, type: :model do
-  pending "add some examples to (or delete) #{__FILE__}"
+  describe '#object_type' do
+    let(:poll_vote) { Fabricate.build(:poll_vote) }
+
+    it 'returns :vote' do
+      expect(poll_vote.object_type).to eq :vote
+    end
+  end
 end
diff --git a/spec/models/remote_follow_spec.rb b/spec/models/remote_follow_spec.rb
index ed2667b28aad82eb4242543284033974ff57b1c5..5b4c19b5bbecc8efee1daaacf9088f6f5dcd8291 100644
--- a/spec/models/remote_follow_spec.rb
+++ b/spec/models/remote_follow_spec.rb
@@ -61,7 +61,7 @@ RSpec.describe RemoteFollow do
     subject { remote_follow.subscribe_address_for(account) }
 
     it 'returns subscribe address' do
-      is_expected.to eq 'https://quitter.no/main/ostatussub?profile=alice%40cb6e6126.ngrok.io'
+      is_expected.to eq 'https://quitter.no/main/ostatussub?profile=https%3A%2F%2Fcb6e6126.ngrok.io%2Fusers%2Falice'
     end
   end
 end
diff --git a/spec/models/remote_profile_spec.rb b/spec/models/remote_profile_spec.rb
deleted file mode 100644
index da5048f0a4ede4c7cf2ad1e78ce6ba00e0ba7567..0000000000000000000000000000000000000000
--- a/spec/models/remote_profile_spec.rb
+++ /dev/null
@@ -1,143 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe RemoteProfile do
-  let(:remote_profile) { RemoteProfile.new(body) }
-  let(:body) do
-    <<-XML
-      <feed xmlns="http://www.w3.org/2005/Atom">
-      <author>John</author>
-    XML
-  end
-
-  describe '.initialize' do
-    it 'calls Nokogiri::XML.parse' do
-      expect(Nokogiri::XML).to receive(:parse).with(body, nil, 'utf-8')
-      RemoteProfile.new(body)
-    end
-
-    it 'sets document' do
-      remote_profile = RemoteProfile.new(body)
-      expect(remote_profile).not_to be nil
-    end
-  end
-
-  describe '#root' do
-    let(:document) { remote_profile.document }
-
-    it 'callse document.at_xpath' do
-      expect(document).to receive(:at_xpath).with(
-        '/atom:feed|/atom:entry',
-        atom: OStatus::TagManager::XMLNS
-      )
-
-      remote_profile.root
-    end
-  end
-
-  describe '#author' do
-    let(:root) { remote_profile.root }
-
-    it 'calls root.at_xpath' do
-      expect(root).to receive(:at_xpath).with(
-        './atom:author|./dfrn:owner',
-        atom: OStatus::TagManager::XMLNS,
-        dfrn: OStatus::TagManager::DFRN_XMLNS
-      )
-
-      remote_profile.author
-    end
-  end
-
-  describe '#hub_link' do
-    let(:root) { remote_profile.root }
-
-    it 'calls #link_href_from_xml' do
-      expect(remote_profile).to receive(:link_href_from_xml).with(root, 'hub')
-      remote_profile.hub_link
-    end
-  end
-
-  describe '#display_name' do
-    let(:author) { remote_profile.author }
-
-    it 'calls author.at_xpath.content' do
-      expect(author).to receive_message_chain(:at_xpath, :content).with(
-        './poco:displayName',
-        poco: OStatus::TagManager::POCO_XMLNS
-      ).with(no_args)
-
-      remote_profile.display_name
-    end
-  end
-
-  describe '#note' do
-    let(:author) { remote_profile.author }
-
-    it 'calls author.at_xpath.content' do
-      expect(author).to receive_message_chain(:at_xpath, :content).with(
-        './atom:summary|./poco:note',
-        atom: OStatus::TagManager::XMLNS,
-        poco: OStatus::TagManager::POCO_XMLNS
-      ).with(no_args)
-
-      remote_profile.note
-    end
-  end
-
-  describe '#scope' do
-    let(:author) { remote_profile.author }
-
-    it 'calls author.at_xpath.content' do
-      expect(author).to receive_message_chain(:at_xpath, :content).with(
-        './mastodon:scope',
-        mastodon: OStatus::TagManager::MTDN_XMLNS
-      ).with(no_args)
-
-      remote_profile.scope
-    end
-  end
-
-  describe '#avatar' do
-    let(:author) { remote_profile.author }
-
-    it 'calls #link_href_from_xml' do
-      expect(remote_profile).to receive(:link_href_from_xml).with(author, 'avatar')
-      remote_profile.avatar
-    end
-  end
-
-  describe '#header' do
-    let(:author) { remote_profile.author }
-
-    it 'calls #link_href_from_xml' do
-      expect(remote_profile).to receive(:link_href_from_xml).with(author, 'header')
-      remote_profile.header
-    end
-  end
-
-  describe '#locked?' do
-    before do
-      allow(remote_profile).to receive(:scope).and_return(scope)
-    end
-
-    subject { remote_profile.locked? }
-
-    context 'scope is private' do
-      let(:scope) { 'private' }
-
-      it 'returns true' do
-        is_expected.to be true
-      end
-    end
-
-    context 'scope is not private' do
-      let(:scope) { 'public' }
-
-      it 'returns false' do
-        is_expected.to be false
-      end
-    end
-  end
-end
diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb
index a0cd0800da570a04f4025eeed664825170bc168e..312954c9dc9f4bdd27f740d134ef8eee1cdf7de2 100644
--- a/spec/models/report_spec.rb
+++ b/spec/models/report_spec.rb
@@ -125,7 +125,7 @@ describe Report do
     end
 
     it 'is invalid if comment is longer than 1000 characters' do
-      report = Fabricate.build(:report, comment: Faker::Lorem.characters(1001))
+      report = Fabricate.build(:report, comment: Faker::Lorem.characters(number: 1001))
       report.valid?
       expect(report).to model_have_error_on_field(:comment)
     end
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index e7229087ffd98cd7ffe0f04ae549b42783e2eaf2..0b6086978d890af4c93764d13a151d300734422e 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -296,99 +296,6 @@ RSpec.describe Status, type: :model do
     end
   end
 
-  describe '.as_home_timeline' do
-    let(:account) { Fabricate(:account) }
-    let(:followed) { Fabricate(:account) }
-    let(:not_followed) { Fabricate(:account) }
-
-    before do
-      Fabricate(:follow, account: account, target_account: followed)
-
-      @self_status = Fabricate(:status, account: account, visibility: :public)
-      @self_direct_status = Fabricate(:status, account: account, visibility: :direct)
-      @followed_status = Fabricate(:status, account: followed, visibility: :public)
-      @followed_direct_status = Fabricate(:status, account: followed, visibility: :direct)
-      @not_followed_status = Fabricate(:status, account: not_followed, visibility: :public)
-
-      @results = Status.as_home_timeline(account)
-    end
-
-    it 'includes statuses from self' do
-      expect(@results).to include(@self_status)
-    end
-
-    it 'does not include direct statuses from self' do
-      expect(@results).to_not include(@self_direct_status)
-    end
-
-    it 'includes statuses from followed' do
-      expect(@results).to include(@followed_status)
-    end
-
-    it 'does not include direct statuses mentioning recipient from followed' do
-      Fabricate(:mention, account: account, status: @followed_direct_status)
-      expect(@results).to_not include(@followed_direct_status)
-    end
-
-    it 'does not include direct statuses not mentioning recipient from followed' do
-      expect(@results).not_to include(@followed_direct_status)
-    end
-
-    it 'does not include statuses from non-followed' do
-      expect(@results).not_to include(@not_followed_status)
-    end
-  end
-
-  describe '.as_direct_timeline' do
-    let(:account) { Fabricate(:account) }
-    let(:followed) { Fabricate(:account) }
-    let(:not_followed) { Fabricate(:account) }
-
-    before do
-      Fabricate(:follow, account: account, target_account: followed)
-
-      @self_public_status = Fabricate(:status, account: account, visibility: :public)
-      @self_direct_status = Fabricate(:status, account: account, visibility: :direct)
-      @followed_public_status = Fabricate(:status, account: followed, visibility: :public)
-      @followed_direct_status = Fabricate(:status, account: followed, visibility: :direct)
-      @not_followed_direct_status = Fabricate(:status, account: not_followed, visibility: :direct)
-
-      @results = Status.as_direct_timeline(account)
-    end
-
-    it 'does not include public statuses from self' do
-      expect(@results).to_not include(@self_public_status)
-    end
-
-    it 'includes direct statuses from self' do
-      expect(@results).to include(@self_direct_status)
-    end
-
-    it 'does not include public statuses from followed' do
-      expect(@results).to_not include(@followed_public_status)
-    end
-
-    it 'does not include direct statuses not mentioning recipient from followed' do
-      expect(@results).to_not include(@followed_direct_status)
-    end
-
-    it 'does not include direct statuses not mentioning recipient from non-followed' do
-      expect(@results).to_not include(@not_followed_direct_status)
-    end
-
-    it 'includes direct statuses mentioning recipient from followed' do
-      Fabricate(:mention, account: account, status: @followed_direct_status)
-      results2 = Status.as_direct_timeline(account)
-      expect(results2).to include(@followed_direct_status)
-    end
-
-    it 'includes direct statuses mentioning recipient from non-followed' do
-      Fabricate(:mention, account: account, status: @not_followed_direct_status)
-      results2 = Status.as_direct_timeline(account)
-      expect(results2).to include(@not_followed_direct_status)
-    end
-  end
-
   describe '.as_public_timeline' do
     it 'only includes statuses with public visibility' do
       public_status = Fabricate(:status, visibility: :public)
diff --git a/spec/models/stream_entry_spec.rb b/spec/models/stream_entry_spec.rb
deleted file mode 100644
index 8f8bfbd5837566711f583e6b86c33b1772c6e866..0000000000000000000000000000000000000000
--- a/spec/models/stream_entry_spec.rb
+++ /dev/null
@@ -1,192 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe StreamEntry, type: :model do
-  let(:alice)     { Fabricate(:account, username: 'alice') }
-  let(:bob)       { Fabricate(:account, username: 'bob') }
-  let(:status)    { Fabricate(:status, account: alice) }
-  let(:reblog)    { Fabricate(:status, account: bob, reblog: status) }
-  let(:reply)     { Fabricate(:status, account: bob, thread: status) }
-  let(:stream_entry) { Fabricate(:stream_entry, activity: activity) }
-  let(:activity)     { reblog }
-
-  describe '#object_type' do
-    before do
-      allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
-      allow(stream_entry).to receive(:targeted?).and_return(targeted)
-    end
-
-    subject { stream_entry.object_type }
-
-    context 'orphaned? is true' do
-      let(:orphaned) { true }
-      let(:targeted) { false }
-
-      it 'returns :activity' do
-        is_expected.to be :activity
-      end
-    end
-
-    context 'targeted? is true' do
-      let(:orphaned) { false }
-      let(:targeted) { true }
-
-      it 'returns :activity' do
-        is_expected.to be :activity
-      end
-    end
-
-    context 'orphaned? and targeted? are false' do
-      let(:orphaned) { false }
-      let(:targeted) { false }
-
-      context 'activity is reblog' do
-        let(:activity) { reblog }
-
-        it 'returns :note' do
-          is_expected.to be :note
-        end
-      end
-
-      context 'activity is reply' do
-        let(:activity) { reply }
-
-        it 'returns :comment' do
-          is_expected.to be :comment
-        end
-      end
-    end
-  end
-
-  describe '#verb' do
-    before do
-      allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
-    end
-
-    subject { stream_entry.verb }
-
-    context 'orphaned? is true' do
-      let(:orphaned) { true }
-
-      it 'returns :delete' do
-        is_expected.to be :delete
-      end
-    end
-
-    context 'orphaned? is false' do
-      let(:orphaned) { false }
-
-      context 'activity is reblog' do
-        let(:activity) { reblog }
-
-        it 'returns :share' do
-          is_expected.to be :share
-        end
-      end
-
-      context 'activity is reply' do
-        let(:activity) { reply }
-
-        it 'returns :post' do
-          is_expected.to be :post
-        end
-      end
-    end
-  end
-
-  describe '#mentions' do
-    before do
-      allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
-    end
-
-    subject { stream_entry.mentions }
-
-    context 'orphaned? is true' do
-      let(:orphaned) { true }
-
-      it 'returns []' do
-        is_expected.to eq []
-      end
-    end
-
-    context 'orphaned? is false' do
-      before do
-        reblog.mentions << Fabricate(:mention, account: alice)
-        reblog.mentions << Fabricate(:mention, account: bob)
-      end
-
-      let(:orphaned) { false }
-
-      it 'returns [Account] includes alice and bob' do
-        is_expected.to eq [alice, bob]
-      end
-    end
-  end
-
-  describe '#targeted?' do
-    it 'returns true for a reblog' do
-      expect(reblog.stream_entry.targeted?).to be true
-    end
-
-    it 'returns false otherwise' do
-      expect(status.stream_entry.targeted?).to be false
-    end
-  end
-
-  describe '#threaded?' do
-    it 'returns true for a reply' do
-      expect(reply.stream_entry.threaded?).to be true
-    end
-
-    it 'returns false otherwise' do
-      expect(status.stream_entry.threaded?).to be false
-    end
-  end
-
-  describe 'delegated methods' do
-    context 'with a nil status' do
-      subject { described_class.new(status: nil) }
-
-      it 'returns nil for target' do
-        expect(subject.target).to be_nil
-      end
-
-      it 'returns nil for title' do
-        expect(subject.title).to be_nil
-      end
-
-      it 'returns nil for content' do
-        expect(subject.content).to be_nil
-      end
-
-      it 'returns nil for thread' do
-        expect(subject.thread).to be_nil
-      end
-    end
-
-    context 'with a real status' do
-      let(:original) { Fabricate(:status, text: 'Test status') }
-      let(:status) { Fabricate(:status, reblog: original, thread: original) }
-      subject { described_class.new(status: status) }
-
-      it 'delegates target' do
-        expect(status.target).not_to be_nil
-        expect(subject.target).to eq(status.target)
-      end
-
-      it 'delegates title' do
-        expect(status.title).not_to be_nil
-        expect(subject.title).to eq(status.title)
-      end
-
-      it 'delegates content' do
-        expect(status.content).not_to be_nil
-        expect(subject.content).to eq(status.content)
-      end
-
-      it 'delegates thread' do
-        expect(status.thread).not_to be_nil
-        expect(subject.thread).to eq(status.thread)
-      end
-    end
-  end
-end
diff --git a/spec/models/subscription_spec.rb b/spec/models/subscription_spec.rb
deleted file mode 100644
index b83979d13799f4d3134d2a0dfd2f62658d6daada..0000000000000000000000000000000000000000
--- a/spec/models/subscription_spec.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Subscription, type: :model do
-  let(:alice) { Fabricate(:account, username: 'alice') }
-
-  subject { Fabricate(:subscription, account: alice) }
-
-  describe '#expired?' do
-    it 'return true when expires_at is past' do
-      subject.expires_at = 2.days.ago
-      expect(subject.expired?).to be true
-    end
-
-    it 'return false when expires_at is future' do
-      subject.expires_at = 2.days.from_now
-      expect(subject.expired?).to be false
-    end
-  end
-
-  describe 'lease_seconds' do
-    it 'returns the time remaining until expiration' do
-      datetime = 1.day.from_now
-      subscription = Subscription.new(expires_at: datetime)
-      travel_to(datetime - 12.hours) do
-        expect(subscription.lease_seconds).to eq(12.hours)
-      end
-    end
-  end
-
-  describe 'lease_seconds=' do
-    it 'sets expires_at to min expiration when small value is provided' do
-      subscription = Subscription.new
-      datetime = 1.day.from_now
-      too_low = Subscription::MIN_EXPIRATION - 1000
-      travel_to(datetime) do
-        subscription.lease_seconds = too_low
-      end
-
-      expected = datetime + Subscription::MIN_EXPIRATION.seconds
-      expect(subscription.expires_at).to be_within(1.0).of(expected)
-    end
-
-    it 'sets expires_at to value when valid value is provided' do
-      subscription = Subscription.new
-      datetime = 1.day.from_now
-      valid = Subscription::MIN_EXPIRATION + 1000
-      travel_to(datetime) do
-        subscription.lease_seconds = valid
-      end
-
-      expected = datetime + valid.seconds
-      expect(subscription.expires_at).to be_within(1.0).of(expected)
-    end
-
-    it 'sets expires_at to max expiration when large value is provided' do
-      subscription = Subscription.new
-      datetime = 1.day.from_now
-      too_high = Subscription::MAX_EXPIRATION + 1000
-      travel_to(datetime) do
-        subscription.lease_seconds = too_high
-      end
-
-      expected = datetime + Subscription::MAX_EXPIRATION.seconds
-      expect(subscription.expires_at).to be_within(1.0).of(expected)
-    end
-  end
-end
diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb
index 9a30ceaa527d34e23d354b9467d1262fa5c147d0..df876593c9e026d9b1f0c103041a34719e112234 100644
--- a/spec/models/tag_spec.rb
+++ b/spec/models/tag_spec.rb
@@ -62,6 +62,10 @@ RSpec.describe Tag, type: :model do
       expect(subject.match('hello #one·two·three').to_s).to eq ' #one·two·three'
     end
 
+    it 'matches ZWNJ' do
+      expect(subject.match('just add #نرم‌افزار and').to_s).to eq ' #نرم‌افزار'
+    end
+
     it 'does not match middle dots at the start' do
       expect(subject.match('hello #·one·two·three')).to be_nil
     end
@@ -82,6 +86,40 @@ RSpec.describe Tag, type: :model do
     end
   end
 
+  describe '.find_normalized' do
+    it 'returns tag for a multibyte case-insensitive name' do
+      upcase_string   = 'abcABCabcABCやゆよ'
+      downcase_string = 'abcabcabcabcやゆよ';
+
+      tag = Fabricate(:tag, name: downcase_string)
+      expect(Tag.find_normalized(upcase_string)).to eq tag
+    end
+  end
+
+  describe '.matching_name' do
+    it 'returns tags for multibyte case-insensitive names' do
+      upcase_string   = 'abcABCabcABCやゆよ'
+      downcase_string = 'abcabcabcabcやゆよ';
+
+      tag = Fabricate(:tag, name: downcase_string)
+      expect(Tag.matching_name(upcase_string)).to eq [tag]
+    end
+  end
+
+  describe '.find_or_create_by_names' do
+    it 'runs a passed block once per tag regardless of duplicates' do
+      upcase_string   = 'abcABCabcABCやゆよ'
+      downcase_string = 'abcabcabcabcやゆよ';
+      count           = 0
+
+      Tag.find_or_create_by_names([upcase_string, downcase_string]) do |tag|
+        count += 1
+      end
+
+      expect(count).to eq 1
+    end
+  end
+
   describe '.search_for' do
     it 'finds tag records with matching names' do
       tag = Fabricate(:tag, name: "match")
@@ -102,8 +140,8 @@ RSpec.describe Tag, type: :model do
     end
 
     it 'finds the exact matching tag as the first item' do
-      similar_tag = Fabricate(:tag, name: "matchlater")
-      tag = Fabricate(:tag, name: "match")
+      similar_tag = Fabricate(:tag, name: "matchlater", reviewed_at: Time.now.utc)
+      tag = Fabricate(:tag, name: "match", reviewed_at: Time.now.utc)
 
       results = Tag.search_for("match")
 
diff --git a/spec/models/trending_tags_spec.rb b/spec/models/trending_tags_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b6122c994817bb17fb551708bdaa0d19795eed98
--- /dev/null
+++ b/spec/models/trending_tags_spec.rb
@@ -0,0 +1,68 @@
+require 'rails_helper'
+
+RSpec.describe TrendingTags do
+  describe '.record_use!' do
+    pending
+  end
+
+  describe '.update!' do
+    let!(:at_time) { Time.now.utc }
+    let!(:tag1) { Fabricate(:tag, name: 'Catstodon') }
+    let!(:tag2) { Fabricate(:tag, name: 'DogsOfMastodon') }
+    let!(:tag3) { Fabricate(:tag, name: 'OCs') }
+
+    before do
+      allow(Redis.current).to receive(:pfcount) do |key|
+        case key
+        when "activity:tags:#{tag1.id}:#{(at_time - 1.day).beginning_of_day.to_i}:accounts"
+          2
+        when "activity:tags:#{tag1.id}:#{at_time.beginning_of_day.to_i}:accounts"
+          16
+        when "activity:tags:#{tag2.id}:#{(at_time - 1.day).beginning_of_day.to_i}:accounts"
+          0
+        when "activity:tags:#{tag2.id}:#{at_time.beginning_of_day.to_i}:accounts"
+          4
+        when "activity:tags:#{tag3.id}:#{(at_time - 1.day).beginning_of_day.to_i}:accounts"
+          13
+        end
+      end
+
+      Redis.current.zadd('trending_tags', 0.9, tag3.id)
+      Redis.current.sadd("trending_tags:used:#{at_time.beginning_of_day.to_i}", [tag1.id, tag2.id])
+
+      tag3.update(max_score: 0.9, max_score_at: (at_time - 1.day).beginning_of_day + 12.hours)
+
+      described_class.update!(at_time)
+    end
+
+    it 'calculates and re-calculates scores' do
+      expect(described_class.get(10, filtered: false)).to eq [tag1, tag3]
+    end
+
+    it 'omits hashtags below threshold' do
+      expect(described_class.get(10, filtered: false)).to_not include(tag2)
+    end
+
+    it 'decays scores' do
+      expect(Redis.current.zscore('trending_tags', tag3.id)).to be < 0.9
+    end
+  end
+
+  describe '.trending?' do
+    let(:tag) { Fabricate(:tag) }
+
+    before do
+      10.times { |i| Redis.current.zadd('trending_tags', i + 1, Fabricate(:tag).id) }
+    end
+
+    it 'returns true if the hashtag is within limit' do
+      Redis.current.zadd('trending_tags', 11, tag.id)
+      expect(described_class.trending?(tag)).to be true
+    end
+
+    it 'returns false if the hashtag is outside the limit' do
+      Redis.current.zadd('trending_tags', 0, tag.id)
+      expect(described_class.trending?(tag)).to be false
+    end
+  end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 856254ce4b6acd1908b5ace92e3e0681c0145b94..d7c0b5359338969d5a27e8eb4fe117a628dca2d4 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -506,7 +506,7 @@ RSpec.describe User, type: :model do
       context 'when user is not confirmed' do
         let(:confirmed_at) { nil }
 
-        it { is_expected.to be false }
+        it { is_expected.to be true }
       end
     end
 
@@ -522,7 +522,7 @@ RSpec.describe User, type: :model do
       context 'when user is not confirmed' do
         let(:confirmed_at) { nil }
 
-        it { is_expected.to be false }
+        it { is_expected.to be true }
       end
     end
   end
diff --git a/spec/policies/subscription_policy_spec.rb b/spec/policies/subscription_policy_spec.rb
deleted file mode 100644
index 21d60c15fc0c95d55c15828ae23d98e65cd3a868..0000000000000000000000000000000000000000
--- a/spec/policies/subscription_policy_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-require 'pundit/rspec'
-
-RSpec.describe SubscriptionPolicy do
-  let(:subject) { described_class }
-  let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
-
-  permissions :index? do
-    context 'admin?' do
-      it 'permits' do
-        expect(subject).to permit(admin, Subscription)
-      end
-    end
-
-    context '!admin?' do
-      it 'denies' do
-        expect(subject).to_not permit(john, Subscription)
-      end
-    end
-  end
-end
diff --git a/spec/policies/tag_policy_spec.rb b/spec/policies/tag_policy_spec.rb
index c7afaa7c93d0f6eab29832da761cb4812a5861ee..c63875dc0888a6724db99043eb3537936d3ae55a 100644
--- a/spec/policies/tag_policy_spec.rb
+++ b/spec/policies/tag_policy_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe TagPolicy do
   let(:admin)   { Fabricate(:user, admin: true).account }
   let(:john)    { Fabricate(:user).account }
 
-  permissions :index?, :hide?, :unhide? do
+  permissions :index?, :show?, :update? do
     context 'staff?' do
       it 'permits' do
         expect(subject).to permit(admin, Tag)
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 3a5e7491e515198888d2bc25733fab67fbec2a97..6fbceca539606821c22f4d0b7ec5688d28a63884 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -12,7 +12,7 @@ require 'capybara/rspec'
 Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
 
 ActiveRecord::Migration.maintain_test_schema!
-WebMock.disable_net_connect!
+WebMock.disable_net_connect!(allow: Chewy.settings[:host])
 Redis.current = Redis::Namespace.new("mastodon_test#{ENV['TEST_ENV_NUMBER']}", redis: Redis.current)
 Sidekiq::Testing.inline!
 Sidekiq::Logging.logger = nil
diff --git a/spec/requests/link_headers_spec.rb b/spec/requests/link_headers_spec.rb
index 3dc408d9270dd8ff8b55085d1e645fffe31a9013..712ee262b80ca4b5cc9a7af5fecdc8c8ca719a25 100644
--- a/spec/requests/link_headers_spec.rb
+++ b/spec/requests/link_headers_spec.rb
@@ -11,16 +11,16 @@ describe 'Link headers' do
     end
 
     it 'contains webfinger url in link header' do
-      link_header = link_header_with_type('application/xrd+xml')
+      link_header = link_header_with_type('application/jrd+json')
 
       expect(link_header.href).to match 'http://www.example.com/.well-known/webfinger?resource=acct%3Atest%40cb6e6126.ngrok.io'
       expect(link_header.attr_pairs.first).to eq %w(rel lrdd)
     end
 
-    it 'contains atom url in link header' do
-      link_header = link_header_with_type('application/atom+xml')
+    it 'contains activitypub url in link header' do
+      link_header = link_header_with_type('application/activity+json')
 
-      expect(link_header.href).to eq 'http://www.example.com/users/test.atom'
+      expect(link_header.href).to eq 'https://cb6e6126.ngrok.io/users/test'
       expect(link_header.attr_pairs.first).to eq %w(rel alternate)
     end
 
diff --git a/spec/requests/webfinger_request_spec.rb b/spec/requests/webfinger_request_spec.rb
index 7f9e1162e9c43c6bd50e7f44318c89bfd3fde4ca..48823714e2087b545719c0f5235ebb3b6ccf6c93 100644
--- a/spec/requests/webfinger_request_spec.rb
+++ b/spec/requests/webfinger_request_spec.rb
@@ -12,23 +12,6 @@ describe 'The webfinger route' do
     end
   end
 
-  describe 'asking for xml format' do
-    it 'returns an xml response for xml format' do
-      get webfinger_url(resource: alice.to_webfinger_s, format: :xml)
-
-      expect(response).to have_http_status(200)
-      expect(response.content_type).to eq 'application/xrd+xml'
-    end
-
-    it 'returns an xml response for xml accept header' do
-      headers = { 'HTTP_ACCEPT' => 'application/xrd+xml' }
-      get webfinger_url(resource: alice.to_webfinger_s), headers: headers
-
-      expect(response).to have_http_status(200)
-      expect(response.content_type).to eq 'application/xrd+xml'
-    end
-  end
-
   describe 'asking for json format' do
     it 'returns a json response for json format' do
       get webfinger_url(resource: alice.to_webfinger_s, format: :json)
diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb
index 7b071b378e04b06fe6894ad4090205982f85e7bf..5b7182586c5f2f4406eb180ffa0eadfb35f29814 100644
--- a/spec/services/account_search_service_spec.rb
+++ b/spec/services/account_search_service_spec.rb
@@ -1,126 +1,56 @@
 require 'rails_helper'
 
 describe AccountSearchService, type: :service do
-  describe '.call' do
-    describe 'with a query to ignore' do
+  describe '#call' do
+    context 'with a query to ignore' do
       it 'returns empty array for missing query' do
         results = subject.call('', nil, limit: 10)
 
         expect(results).to eq []
       end
-      it 'returns empty array for hashtag query' do
-        results = subject.call('#tag', nil, limit: 10)
 
-        expect(results).to eq []
-      end
       it 'returns empty array for limit zero' do
         Fabricate(:account, username: 'match')
+
         results = subject.call('match', nil, limit: 0)
 
         expect(results).to eq []
       end
     end
 
-    describe 'searching for a simple term that is not an exact match' do
+    context 'searching for a simple term that is not an exact match' do
       it 'does not return a nil entry in the array for the exact match' do
-        match = Fabricate(:account, username: 'matchingusername')
-
+        account = Fabricate(:account, username: 'matchingusername')
         results = subject.call('match', nil, limit: 5)
-        expect(results).to eq [match]
-      end
-    end
 
-    describe 'searching local and remote users' do
-      describe "when only '@'" do
-        before do
-          allow(Account).to receive(:find_local)
-          allow(Account).to receive(:search_for)
-          subject.call('@', nil, limit: 10)
-        end
-
-        it 'uses find_local with empty query to look for local accounts' do
-          expect(Account).to have_received(:find_local).with('')
-        end
-      end
-
-      describe 'when no domain' do
-        before do
-          allow(Account).to receive(:find_local)
-          allow(Account).to receive(:search_for)
-          subject.call('one', nil, limit: 10)
-        end
-
-        it 'uses find_local to look for local accounts' do
-          expect(Account).to have_received(:find_local).with('one')
-        end
-
-        it 'uses search_for to find matches' do
-          expect(Account).to have_received(:search_for).with('one', 10, 0)
-        end
-      end
-
-      describe 'when there is a domain' do
-        before do
-          allow(Account).to receive(:find_remote)
-        end
-
-        it 'uses find_remote to look for remote accounts' do
-          subject.call('two@example.com', nil, limit: 10)
-          expect(Account).to have_received(:find_remote).with('two', 'example.com')
-        end
-
-        describe 'and there is no account provided' do
-          it 'uses search_for to find matches' do
-            allow(Account).to receive(:search_for)
-            subject.call('two@example.com', nil, limit: 10, resolve: false)
-
-            expect(Account).to have_received(:search_for).with('two example.com', 10, 0)
-          end
-        end
-
-        describe 'and there is an account provided' do
-          it 'uses advanced_search_for to find matches' do
-            account = Fabricate(:account)
-            allow(Account).to receive(:advanced_search_for)
-            subject.call('two@example.com', account, limit: 10, resolve: false)
-
-            expect(Account).to have_received(:advanced_search_for).with('two example.com', account, 10, nil, 0)
-          end
-        end
+        expect(results).to eq [account]
       end
     end
 
-    describe 'with an exact match' do
-      it 'returns exact match first, and does not return duplicates' do
-        partial = Fabricate(:account, username: 'exactness')
-        exact = Fabricate(:account, username: 'exact')
-
-        results = subject.call('exact', nil, limit: 10)
-        expect(results.size).to eq 2
-        expect(results).to eq [exact, partial]
-      end
-    end
-
-    describe 'when there is a local domain' do
+    context 'when there is a local domain' do
       around do |example|
         before = Rails.configuration.x.local_domain
+
         example.run
+
         Rails.configuration.x.local_domain = before
       end
 
       it 'returns exact match first' do
         remote     = Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e')
         remote_too = Fabricate(:account, username: 'b', domain: 'remote', display_name: 'e')
-        exact = Fabricate(:account, username: 'e')
+        exact      = Fabricate(:account, username: 'e')
+
         Rails.configuration.x.local_domain = 'example.com'
 
         results = subject.call('e@example.com', nil, limit: 2)
+
         expect(results.size).to eq 2
         expect(results).to eq([exact, remote]).or eq([exact, remote_too])
       end
     end
 
-    describe 'when there is a domain but no exact match' do
+    context 'when there is a domain but no exact match' do
       it 'follows the remote account when resolve is true' do
         service = double(call: nil)
         allow(ResolveAccountService).to receive(:new).and_return(service)
@@ -138,23 +68,21 @@ describe AccountSearchService, type: :service do
       end
     end
 
-    describe 'should not include suspended accounts' do
-      it 'returns the fuzzy match first, and does not return suspended exacts' do
-        partial = Fabricate(:account, username: 'exactness')
-        exact = Fabricate(:account, username: 'exact', suspended: true)
+    it 'returns the fuzzy match first, and does not return suspended exacts' do
+      partial = Fabricate(:account, username: 'exactness')
+      exact   = Fabricate(:account, username: 'exact', suspended: true)
+      results = subject.call('exact', nil, limit: 10)
 
-        results = subject.call('exact', nil, limit: 10)
-        expect(results.size).to eq 1
-        expect(results).to eq [partial]
-      end
+      expect(results.size).to eq 1
+      expect(results).to eq [partial]
+    end
 
-      it "does not return suspended remote accounts" do
-        remote = Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e', suspended: true)
+    it "does not return suspended remote accounts" do
+      remote  = Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e', suspended: true)
+      results = subject.call('a@example.com', nil, limit: 2)
 
-        results = subject.call('a@example.com', nil, limit: 2)
-        expect(results.size).to eq 0
-        expect(results).to eq []
-      end
+      expect(results.size).to eq 0
+      expect(results).to eq []
     end
   end
 end
diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb
index 9ae4099969405194c47006a7c3313a6afb5ab869..78dd59e3b6ccb743afc2d07988025462338b0ea1 100644
--- a/spec/services/activitypub/fetch_remote_status_service_spec.rb
+++ b/spec/services/activitypub/fetch_remote_status_service_spec.rb
@@ -71,6 +71,39 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
       end
     end
 
+    context 'with Audio object' do
+      let(:object) do
+        {
+          '@context': 'https://www.w3.org/ns/activitystreams',
+          id: "https://#{valid_domain}/@foo/1234",
+          type: 'Audio',
+          name: 'Nyan Cat 10 hours remix',
+          attributedTo: ActivityPub::TagManager.instance.uri_for(sender),
+          url: [
+            {
+              type: 'Link',
+              mimeType: 'application/x-bittorrent',
+              href: "https://#{valid_domain}/12345.torrent",
+            },
+
+            {
+              type: 'Link',
+              mimeType: 'text/html',
+              href: "https://#{valid_domain}/watch?v=12345",
+            },
+          ],
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.url).to eq "https://#{valid_domain}/watch?v=12345"
+        expect(strip_tags(status.text)).to eq "Nyan Cat 10 hours remix https://#{valid_domain}/watch?v=12345"
+      end
+    end
+
     context 'with wrong id' do
       let(:note) do
         {
diff --git a/spec/services/app_sign_up_service_spec.rb b/spec/services/app_sign_up_service_spec.rb
index 7948bb53be10e4546129e1bd3fd6674f2f422a66..e7c7f3ba15e6bfcb69df2996297f7e556be44cb7 100644
--- a/spec/services/app_sign_up_service_spec.rb
+++ b/spec/services/app_sign_up_service_spec.rb
@@ -38,6 +38,15 @@ RSpec.describe AppSignUpService, type: :service do
       user = User.find_by(id: access_token.resource_owner_id)
       expect(user).to_not be_nil
       expect(user.account).to_not be_nil
+      expect(user.invite_request).to be_nil
+    end
+
+    it 'creates an account with invite request text' do
+      access_token = subject.call(app, good_params.merge(reason: 'Foo bar'))
+      expect(access_token).to_not be_nil
+      user = User.find_by(id: access_token.resource_owner_id)
+      expect(user).to_not be_nil
+      expect(user.invite_request&.text).to eq 'Foo bar'
     end
   end
 end
diff --git a/spec/services/authorize_follow_service_spec.rb b/spec/services/authorize_follow_service_spec.rb
index 562ef004124712f11ae7342b8fbc1998f758f294..ce56d57a6217673bd0ffa5b37bc1b260b048a720 100644
--- a/spec/services/authorize_follow_service_spec.rb
+++ b/spec/services/authorize_follow_service_spec.rb
@@ -38,13 +38,6 @@ RSpec.describe AuthorizeFollowService, type: :service do
     it 'creates follow relation' do
       expect(bob.following?(sender)).to be true
     end
-
-    it 'sends a follow request authorization salmon slap' do
-      expect(a_request(:post, "http://salmon.example.com/").with { |req|
-        xml = OStatus2::Salmon.new.unpack(req.body)
-        xml.match(OStatus::TagManager::VERBS[:authorize])
-      }).to have_been_made.once
-    end
   end
 
   describe 'remote ActivityPub' do
diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb
index e53623449f78d56abce08e5169cd53c72a2558a5..f84256f187e17238b4b95026b65d9de32d9a005a 100644
--- a/spec/services/batched_remove_status_service_spec.rb
+++ b/spec/services/batched_remove_status_service_spec.rb
@@ -14,11 +14,8 @@ RSpec.describe BatchedRemoveStatusService, type: :service do
   before do
     allow(Redis.current).to receive_messages(publish: nil)
 
-    stub_request(:post, 'http://example.com/push').to_return(status: 200, body: '', headers: {})
-    stub_request(:post, 'http://example.com/salmon').to_return(status: 200, body: '', headers: {})
     stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
 
-    Fabricate(:subscription, account: alice, callback_url: 'http://example.com/push', confirmed: true, expires_at: 30.days.from_now)
     jeff.user.update(current_sign_in_at: Time.zone.now)
     jeff.follow!(alice)
     hank.follow!(alice)
@@ -49,19 +46,6 @@ RSpec.describe BatchedRemoveStatusService, type: :service do
     expect(Redis.current).to have_received(:publish).with('timeline:public', any_args).at_least(:once)
   end
 
-  it 'sends PuSH update to PuSH subscribers' do
-    expect(a_request(:post, 'http://example.com/push').with { |req|
-      matches = req.body.match(OStatus::TagManager::VERBS[:delete])
-    }).to have_been_made.at_least_once
-  end
-
-  it 'sends Salmon slap to previously mentioned users' do
-    expect(a_request(:post, "http://example.com/salmon").with { |req|
-      xml = OStatus2::Salmon.new.unpack(req.body)
-      xml.match(OStatus::TagManager::VERBS[:delete])
-    }).to have_been_made.once
-  end
-
   it 'sends delete activity to followers' do
     expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.at_least_once
   end
diff --git a/spec/services/block_service_spec.rb b/spec/services/block_service_spec.rb
index 6584bb90ed64c78bd8806ac80f8c1fa182594727..de20dd026502ec95a271bd0c24d1a4d0a798d314 100644
--- a/spec/services/block_service_spec.rb
+++ b/spec/services/block_service_spec.rb
@@ -28,13 +28,6 @@ RSpec.describe BlockService, type: :service do
     it 'creates a blocking relation' do
       expect(sender.blocking?(bob)).to be true
     end
-
-    it 'sends a block salmon slap' do
-      expect(a_request(:post, "http://salmon.example.com/").with { |req|
-        xml = OStatus2::Salmon.new.unpack(req.body)
-        xml.match(OStatus::TagManager::VERBS[:block])
-      }).to have_been_made.once
-    end
   end
 
   describe 'remote ActivityPub' do
diff --git a/spec/services/bootstrap_timeline_service_spec.rb b/spec/services/bootstrap_timeline_service_spec.rb
index a765de79127baf61314f396a6af3679066a48c98..a28d2407c1cf7ca25b778c3a6558c3925d1e7a70 100644
--- a/spec/services/bootstrap_timeline_service_spec.rb
+++ b/spec/services/bootstrap_timeline_service_spec.rb
@@ -22,9 +22,10 @@ RSpec.describe BootstrapTimelineService, type: :service do
     context 'when setting is set' do
       let!(:alice) { Fabricate(:account, username: 'alice') }
       let!(:bob)   { Fabricate(:account, username: 'bob') }
+      let!(:eve)   { Fabricate(:account, username: 'eve', suspended: true) }
 
       before do
-        Setting.bootstrap_timeline_accounts = 'alice, bob'
+        Setting.bootstrap_timeline_accounts = 'alice, @bob, eve, unknown'
         subject.call(source_account)
       end
 
@@ -32,6 +33,10 @@ RSpec.describe BootstrapTimelineService, type: :service do
         expect(source_account.following?(alice)).to be true
         expect(source_account.following?(bob)).to be true
       end
+
+      it 'does not follow suspended account' do
+        expect(source_account.following?(eve)).to be false
+      end
     end
   end
 end
diff --git a/spec/services/favourite_service_spec.rb b/spec/services/favourite_service_spec.rb
index 0a20ccf6e4c4713472cab6275c3bf8eeea1a862e..4c29ea77b72c6d6beb7ddd4497886ad3a8a79316 100644
--- a/spec/services/favourite_service_spec.rb
+++ b/spec/services/favourite_service_spec.rb
@@ -30,13 +30,6 @@ RSpec.describe FavouriteService, type: :service do
     it 'creates a favourite' do
       expect(status.favourites.first).to_not be_nil
     end
-
-    it 'sends a salmon slap' do
-      expect(a_request(:post, "http://salmon.example.com/").with { |req|
-        xml = OStatus2::Salmon.new.unpack(req.body)
-        xml.match(OStatus::TagManager::VERBS[:favorite])
-      }).to have_been_made.once
-    end
   end
 
   describe 'remote ActivityPub' do
diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb
index 50c60aafd1d6dec13d8b5cd06482da34ca14bd4a..9761c5f0649623ec3193fa8ccaf35075f20f14a3 100644
--- a/spec/services/fetch_link_card_service_spec.rb
+++ b/spec/services/fetch_link_card_service_spec.rb
@@ -4,20 +4,13 @@ RSpec.describe FetchLinkCardService, type: :service do
   subject { FetchLinkCardService.new }
 
   before do
-    stub_request(:head, 'http://example.xn--fiqs8s/').to_return(status: 200, headers: { 'Content-Type' => 'text/html' })
     stub_request(:get, 'http://example.xn--fiqs8s/').to_return(request_fixture('idn.txt'))
-    stub_request(:head, 'http://example.com/sjis').to_return(status: 200, headers: { 'Content-Type' => 'text/html' })
     stub_request(:get, 'http://example.com/sjis').to_return(request_fixture('sjis.txt'))
-    stub_request(:head, 'http://example.com/sjis_with_wrong_charset').to_return(status: 200, headers: { 'Content-Type' => 'text/html' })
     stub_request(:get, 'http://example.com/sjis_with_wrong_charset').to_return(request_fixture('sjis_with_wrong_charset.txt'))
-    stub_request(:head, 'http://example.com/koi8-r').to_return(status: 200, headers: { 'Content-Type' => 'text/html' })
     stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt'))
-    stub_request(:head, 'http://example.com/日本語').to_return(status: 200, headers: { 'Content-Type' => 'text/html' })
     stub_request(:get, 'http://example.com/日本語').to_return(request_fixture('sjis.txt'))
-    stub_request(:head, 'https://github.com/qbi/WannaCry').to_return(status: 404)
-    stub_request(:head, 'http://example.com/test-').to_return(status: 200, headers: { 'Content-Type' => 'text/html' })
+    stub_request(:get, 'https://github.com/qbi/WannaCry').to_return(status: 404)
     stub_request(:get, 'http://example.com/test-').to_return(request_fixture('idn.txt'))
-    stub_request(:head, 'http://example.com/windows-1251').to_return(status: 200, headers: { 'Content-Type' => 'text/html' })
     stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt'))
 
     subject.call(status)
@@ -90,11 +83,11 @@ RSpec.describe FetchLinkCardService, type: :service do
     let(:status) { Fabricate(:status, account: Fabricate(:account, domain: 'example.com'), text: 'Habt ihr ein paar gute Links zu #<span class="tag"><a href="https://quitter.se/tag/wannacry" target="_blank" rel="tag noopener" title="https://quitter.se/tag/wannacry">Wannacry</a></span> herumfliegen?   Ich will mal unter <br> <a href="https://github.com/qbi/WannaCry" target="_blank" rel="noopener" title="https://github.com/qbi/WannaCry">https://github.com/qbi/WannaCry</a> was sammeln. !<a href="http://sn.jonkman.ca/group/416/id" target="_blank" rel="noopener" title="http://sn.jonkman.ca/group/416/id">security</a>&nbsp;') }
 
     it 'parses out URLs' do
-      expect(a_request(:head, 'https://github.com/qbi/WannaCry')).to have_been_made.at_least_once
+      expect(a_request(:get, 'https://github.com/qbi/WannaCry')).to have_been_made.at_least_once
     end
 
     it 'ignores URLs to hashtags' do
-      expect(a_request(:head, 'https://quitter.se/tag/wannacry')).to_not have_been_made
+      expect(a_request(:get, 'https://quitter.se/tag/wannacry')).to_not have_been_made
     end
   end
 end
diff --git a/spec/services/fetch_remote_account_service_spec.rb b/spec/services/fetch_remote_account_service_spec.rb
index 3cd86708be4339746ce8f8ca9a63783e1bd2bc90..ee7325be28671b9c0fd61f34388d23e736e77b2a 100644
--- a/spec/services/fetch_remote_account_service_spec.rb
+++ b/spec/services/fetch_remote_account_service_spec.rb
@@ -4,6 +4,7 @@ RSpec.describe FetchRemoteAccountService, type: :service do
   let(:url) { 'https://example.com/alice' }
   let(:prefetched_body) { nil }
   let(:protocol) { :ostatus }
+
   subject { FetchRemoteAccountService.new.call(url, prefetched_body, protocol) }
 
   let(:actor) do
@@ -36,36 +37,6 @@ RSpec.describe FetchRemoteAccountService, type: :service do
     include_examples 'return Account'
   end
 
-  context 'protocol is :ostatus' do
-    let(:prefetched_body) { xml }
-    let(:protocol) { :ostatus }
-
-    before do
-      stub_request(:get, "https://kickass.zone/.well-known/webfinger?resource=acct:localhost@kickass.zone").to_return(request_fixture('webfinger-hacker3.txt'))
-      stub_request(:get, "https://kickass.zone/api/statuses/user_timeline/7477.atom").to_return(request_fixture('feed.txt'))
-    end
-
-    include_examples 'return Account'
-
-    it 'does not update account information if XML comes from an unverified domain' do
-      feed_xml = <<-XML.squish
-        <?xml version="1.0" encoding="UTF-8"?>
-        <feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:georss="http://www.georss.org/georss" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:media="http://purl.org/syndication/atommedia" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:statusnet="http://status.net/schema/api/1/">
-          <author>
-            <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-            <uri>http://kickass.zone/users/localhost</uri>
-            <name>localhost</name>
-            <poco:preferredUsername>localhost</poco:preferredUsername>
-            <poco:displayName>Villain!!!</poco:displayName>
-          </author>
-        </feed>
-      XML
-
-      returned_account = described_class.new.call('https://real-fake-domains.com/alice', feed_xml, :ostatus)
-      expect(returned_account.display_name).to_not eq 'Villain!!!'
-    end
-  end
-
   context 'when prefetched_body is nil' do
     context 'protocol is :activitypub' do
       before do
@@ -75,15 +46,5 @@ RSpec.describe FetchRemoteAccountService, type: :service do
 
       include_examples 'return Account'
     end
-
-    context 'protocol is :ostatus' do
-      before do
-        stub_request(:get, url).to_return(status: 200, body: xml, headers: { 'Content-Type' => 'application/atom+xml' })
-        stub_request(:get, "https://kickass.zone/.well-known/webfinger?resource=acct:localhost@kickass.zone").to_return(request_fixture('webfinger-hacker3.txt'))
-        stub_request(:get, "https://kickass.zone/api/statuses/user_timeline/7477.atom").to_return(request_fixture('feed.txt'))
-      end
-
-      include_examples 'return Account'
-    end
   end
 end
diff --git a/spec/services/fetch_atom_service_spec.rb b/spec/services/fetch_resource_service_spec.rb
similarity index 50%
rename from spec/services/fetch_atom_service_spec.rb
rename to spec/services/fetch_resource_service_spec.rb
index 495540004e9504d9d782283f7e91dd6379647991..f836147d354f3d13aff473705c8830a0983ba59c 100644
--- a/spec/services/fetch_atom_service_spec.rb
+++ b/spec/services/fetch_resource_service_spec.rb
@@ -1,73 +1,80 @@
 require 'rails_helper'
 
-RSpec.describe FetchAtomService, type: :service do
+RSpec.describe FetchResourceService, type: :service do
   describe '#call' do
     let(:url) { 'http://example.com' }
-    subject { FetchAtomService.new.call(url) }
 
-    context 'url is blank' do
+    subject { described_class.new.call(url) }
+
+    context 'with blank url' do
       let(:url) { '' }
       it { is_expected.to be_nil }
     end
 
-    context 'request failed' do
+    context 'when request fails' do
       before do
-        WebMock.stub_request(:get, url).to_return(status: 500, body: '', headers: {})
+        stub_request(:get, url).to_return(status: 500, body: '', headers: {})
       end
 
       it { is_expected.to be_nil }
     end
 
-    context 'raise OpenSSL::SSL::SSLError' do
+    context 'when OpenSSL::SSL::SSLError is raised' do
       before do
-        allow(Request).to receive_message_chain(:new, :add_headers, :perform).and_raise(OpenSSL::SSL::SSLError)
+        allow(Request).to receive_message_chain(:new, :add_headers, :on_behalf_of, :perform).and_raise(OpenSSL::SSL::SSLError)
       end
 
-      it 'output log and return nil' do
-        expect_any_instance_of(ActiveSupport::Logger).to receive(:debug).with('SSL error: OpenSSL::SSL::SSLError')
-        is_expected.to be_nil
-      end
+      it { is_expected.to be_nil }
     end
 
-    context 'raise HTTP::ConnectionError' do
+    context 'when HTTP::ConnectionError is raised' do
       before do
-        allow(Request).to receive_message_chain(:new, :add_headers, :perform).and_raise(HTTP::ConnectionError)
+        allow(Request).to receive_message_chain(:new, :add_headers, :on_behalf_of, :perform).and_raise(HTTP::ConnectionError)
       end
 
-      it 'output log and return nil' do
-        expect_any_instance_of(ActiveSupport::Logger).to receive(:debug).with('HTTP ConnectionError: HTTP::ConnectionError')
-        is_expected.to be_nil
-      end
+      it { is_expected.to be_nil }
     end
 
-    context 'response success' do
+    context 'when request succeeds' do
       let(:body) { '' }
-      let(:headers) { { 'Content-Type' => content_type } }
-      let(:json) {
-        { id: 1,
+
+      let(:content_type) { 'application/json' }
+
+      let(:headers) do
+        { 'Content-Type' => content_type }
+      end
+
+      let(:json) do
+        {
+          id: 1,
           '@context': ActivityPub::TagManager::CONTEXT,
           type: 'Note',
         }.to_json
-      }
+      end
 
       before do
-        WebMock.stub_request(:get, url).to_return(status: 200, body: body, headers: headers)
+        stub_request(:get, url).to_return(status: 200, body: body, headers: headers)
+      end
+
+      it 'signs request' do
+        subject
+        expect(a_request(:get, url).with(headers: { 'Signature' => /keyId="#{Regexp.escape(ActivityPub::TagManager.instance.uri_for(Account.representative) + '#main-key')}"/ })).to have_been_made
       end
 
-      context 'content type is application/atom+xml' do
+      context 'when content type is application/atom+xml' do
         let(:content_type) { 'application/atom+xml' }
 
-        it { is_expected.to eq [url, { :prefetched_body => "" }, :ostatus] }
+        it { is_expected.to eq nil }
       end
 
-      context 'content_type is activity+json' do
+      context 'when content type is activity+json' do
         let(:content_type) { 'application/activity+json; charset=utf-8' }
         let(:body) { json }
 
         it { is_expected.to eq [1, { prefetched_body: body, id: true }, :activitypub] }
       end
 
-      context 'content_type is ld+json with profile' do
+      context 'when content type is ld+json with profile' do
         let(:content_type) { 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' }
         let(:body) { json }
 
@@ -75,17 +82,17 @@ RSpec.describe FetchAtomService, type: :service do
       end
 
       before do
-        WebMock.stub_request(:get, url).to_return(status: 200, body: body, headers: headers)
-        WebMock.stub_request(:get, 'http://example.com/foo').to_return(status: 200, body: json, headers: { 'Content-Type' => 'application/activity+json' })
+        stub_request(:get, url).to_return(status: 200, body: body, headers: headers)
+        stub_request(:get, 'http://example.com/foo').to_return(status: 200, body: json, headers: { 'Content-Type' => 'application/activity+json' })
       end
 
-      context 'has link header' do
+      context 'when link header is present' do
         let(:headers) { { 'Link' => '<http://example.com/foo>; rel="alternate"; type="application/activity+json"', } }
 
         it { is_expected.to eq [1, { prefetched_body: json, id: true }, :activitypub] }
       end
 
-      context 'content type is text/html' do
+      context 'when content type is text/html' do
         let(:content_type) { 'text/html' }
         let(:body) { '<html><head><link rel="alternate" href="http://example.com/foo" type="application/activity+json"/></head></html>' }
 
diff --git a/spec/services/follow_service_spec.rb b/spec/services/follow_service_spec.rb
index 3c4ec59be07a8cc45cf3f132bd27c7456731de24..ae863a9f0e98451ce3dc04e683fd6e9162ad67fb 100644
--- a/spec/services/follow_service_spec.rb
+++ b/spec/services/follow_service_spec.rb
@@ -30,6 +30,33 @@ RSpec.describe FollowService, type: :service do
       end
     end
 
+    describe 'unlocked account, from silenced account' do
+      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+
+      before do
+        sender.touch(:silenced_at)
+        subject.call(sender, bob.acct)
+      end
+
+      it 'creates a follow request with reblogs' do
+        expect(FollowRequest.find_by(account: sender, target_account: bob, show_reblogs: true)).to_not be_nil
+      end
+    end
+
+    describe 'unlocked account, from a muted account' do
+      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+
+      before do
+        bob.mute!(sender)
+        subject.call(sender, bob.acct)
+      end
+
+      it 'creates a following relation with reblogs' do
+        expect(sender.following?(bob)).to be true
+        expect(sender.muting_reblogs?(bob)).to be false
+      end
+    end
+
     describe 'unlocked account' do
       let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
 
@@ -96,74 +123,6 @@ RSpec.describe FollowService, type: :service do
     end
   end
 
-  context 'remote OStatus account' do
-    describe 'locked account' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, protocol: :ostatus, locked: true, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
-
-      before do
-        stub_request(:post, "http://salmon.example.com/").to_return(:status => 200, :body => "", :headers => {})
-        subject.call(sender, bob.acct)
-      end
-
-      it 'creates a follow request' do
-        expect(FollowRequest.find_by(account: sender, target_account: bob)).to_not be_nil
-      end
-
-      it 'sends a follow request salmon slap' do
-        expect(a_request(:post, "http://salmon.example.com/").with { |req|
-          xml = OStatus2::Salmon.new.unpack(req.body)
-          xml.match(OStatus::TagManager::VERBS[:request_friend])
-        }).to have_been_made.once
-      end
-    end
-
-    describe 'unlocked account' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, protocol: :ostatus, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com', hub_url: 'http://hub.example.com')).account }
-
-      before do
-        stub_request(:post, "http://salmon.example.com/").to_return(:status => 200, :body => "", :headers => {})
-        stub_request(:post, "http://hub.example.com/").to_return(status: 202)
-        subject.call(sender, bob.acct)
-      end
-
-      it 'creates a following relation' do
-        expect(sender.following?(bob)).to be true
-      end
-
-      it 'sends a follow salmon slap' do
-        expect(a_request(:post, "http://salmon.example.com/").with { |req|
-          xml = OStatus2::Salmon.new.unpack(req.body)
-          xml.match(OStatus::TagManager::VERBS[:follow])
-        }).to have_been_made.once
-      end
-
-      it 'subscribes to PuSH' do
-        expect(a_request(:post, "http://hub.example.com/")).to have_been_made.once
-      end
-    end
-
-    describe 'already followed account' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, protocol: :ostatus, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com', hub_url: 'http://hub.example.com')).account }
-
-      before do
-        sender.follow!(bob)
-        subject.call(sender, bob.acct)
-      end
-
-      it 'keeps a following relation' do
-        expect(sender.following?(bob)).to be true
-      end
-
-      it 'does not send a follow salmon slap' do
-        expect(a_request(:post, "http://salmon.example.com/")).not_to have_been_made
-      end
-
-      it 'does not subscribe to PuSH' do
-        expect(a_request(:post, "http://hub.example.com/")).not_to have_been_made
-      end
-    end
-  end
-
   context 'remote ActivityPub account' do
     let(:bob) { Fabricate(:user, account: Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox')).account }
 
diff --git a/spec/services/import_service_spec.rb b/spec/services/import_service_spec.rb
index 5cf2dadf06c9c22396549327a7bcbcc2671bb6a4..5355133f4000826dcca7529f38015d7c0dc5abf1 100644
--- a/spec/services/import_service_spec.rb
+++ b/spec/services/import_service_spec.rb
@@ -3,7 +3,11 @@ require 'rails_helper'
 RSpec.describe ImportService, type: :service do
   let!(:account) { Fabricate(:account, locked: false) }
   let!(:bob)     { Fabricate(:account, username: 'bob', locked: false) }
-  let!(:eve)     { Fabricate(:account, username: 'eve', domain: 'example.com', locked: false) }
+  let!(:eve)     { Fabricate(:account, username: 'eve', domain: 'example.com', locked: false, protocol: :activitypub, inbox_url: 'https://example.com/inbox') }
+
+  before do
+    stub_request(:post, "https://example.com/inbox").to_return(status: 200)
+  end
 
   context 'import old-style list of muted users' do
     subject { ImportService.new }
@@ -95,7 +99,8 @@ RSpec.describe ImportService, type: :service do
       let(:import) { Import.create(account: account, type: 'following', data: csv) }
       it 'follows the listed accounts, including boosts' do
         subject.call(import)
-        expect(account.following.count).to eq 2
+        expect(account.following.count).to eq 1
+        expect(account.follow_requests.count).to eq 1
         expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
       end
     end
@@ -106,7 +111,8 @@ RSpec.describe ImportService, type: :service do
       it 'follows the listed accounts, including notifications' do
         account.follow!(bob, reblogs: false)
         subject.call(import)
-        expect(account.following.count).to eq 2
+        expect(account.following.count).to eq 1
+        expect(account.follow_requests.count).to eq 1
         expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
       end
     end
@@ -117,7 +123,8 @@ RSpec.describe ImportService, type: :service do
       it 'mutes the listed accounts, including notifications' do
         account.follow!(bob, reblogs: false)
         subject.call(import)
-        expect(account.following.count).to eq 2
+        expect(account.following.count).to eq 1
+        expect(account.follow_requests.count).to eq 1
         expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
       end
     end
@@ -136,9 +143,10 @@ RSpec.describe ImportService, type: :service do
       let(:import) { Import.create(account: account, type: 'following', data: csv) }
       it 'follows the listed accounts, respecting boosts' do
         subject.call(import)
-        expect(account.following.count).to eq 2
+        expect(account.following.count).to eq 1
+        expect(account.follow_requests.count).to eq 1
         expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
-        expect(Follow.find_by(account: account, target_account: eve).show_reblogs).to be false
+        expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false
       end
     end
 
@@ -148,9 +156,10 @@ RSpec.describe ImportService, type: :service do
       it 'mutes the listed accounts, respecting notifications' do
         account.follow!(bob, reblogs: true)
         subject.call(import)
-        expect(account.following.count).to eq 2
+        expect(account.following.count).to eq 1
+        expect(account.follow_requests.count).to eq 1
         expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
-        expect(Follow.find_by(account: account, target_account: eve).show_reblogs).to be false
+        expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false
       end
     end
 
@@ -160,9 +169,10 @@ RSpec.describe ImportService, type: :service do
       it 'mutes the listed accounts, respecting notifications' do
         account.follow!(bob, reblogs: true)
         subject.call(import)
-        expect(account.following.count).to eq 2
+        expect(account.following.count).to eq 1
+        expect(account.follow_requests.count).to eq 1
         expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
-        expect(Follow.find_by(account: account, target_account: eve).show_reblogs).to be false
+        expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false
       end
     end
   end
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index facbe977f2333096f345c8b9e27ad082ae3bb63f..bf06f50e9bd3dc68563181b8eda13175a36c0d7f 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -144,7 +144,6 @@ RSpec.describe PostStatusService, type: :service do
 
   it 'gets distributed' do
     allow(DistributionWorker).to receive(:perform_async)
-    allow(Pubsubhubbub::DistributionWorker).to receive(:perform_async)
     allow(ActivityPub::DistributionWorker).to receive(:perform_async)
 
     account = Fabricate(:account)
@@ -152,7 +151,6 @@ RSpec.describe PostStatusService, type: :service do
     status = subject.call(account, text: "test status update")
 
     expect(DistributionWorker).to have_received(:perform_async).with(status.id)
-    expect(Pubsubhubbub::DistributionWorker).to have_received(:perform_async).with(status.stream_entry.id)
     expect(ActivityPub::DistributionWorker).to have_received(:perform_async).with(status.id)
   end
 
diff --git a/spec/services/process_feed_service_spec.rb b/spec/services/process_feed_service_spec.rb
deleted file mode 100644
index 9d3465f3f8ddac3eb9ce125d41b92fc0681a7507..0000000000000000000000000000000000000000
--- a/spec/services/process_feed_service_spec.rb
+++ /dev/null
@@ -1,252 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe ProcessFeedService, type: :service do
-  subject { ProcessFeedService.new }
-
-  describe 'processing a feed' do
-    let(:body) { File.read(Rails.root.join('spec', 'fixtures', 'xml', 'mastodon.atom')) }
-    let(:account) { Fabricate(:account, username: 'localhost', domain: 'kickass.zone') }
-
-    before do
-      stub_request(:post, "https://pubsubhubbub.superfeedr.com/").to_return(:status => 200, :body => "", :headers => {})
-      stub_request(:head, "http://kickass.zone/media/2").to_return(:status => 404)
-      stub_request(:head, "http://kickass.zone/media/3").to_return(:status => 404)
-      stub_request(:get, "http://kickass.zone/system/accounts/avatars/000/000/001/large/eris.png").to_return(request_fixture('avatar.txt'))
-      stub_request(:get, "http://kickass.zone/system/media_attachments/files/000/000/002/original/morpheus_linux.jpg?1476059910").to_return(request_fixture('attachment1.txt'))
-      stub_request(:get, "http://kickass.zone/system/media_attachments/files/000/000/003/original/gizmo.jpg?1476060065").to_return(request_fixture('attachment2.txt'))
-    end
-
-    context 'when domain does not reject media' do
-      before do
-        subject.call(body, account)
-      end
-
-      it 'updates remote user\'s account information' do
-        account.reload
-        expect(account.display_name).to eq '::1'
-        expect(account).to have_attached_file(:avatar)
-        expect(account.avatar_file_name).not_to be_nil
-      end
-
-      it 'creates posts' do
-        expect(Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=1:objectType=Status')).to_not be_nil
-        expect(Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=2:objectType=Status')).to_not be_nil
-      end
-
-      it 'marks replies as replies' do
-        status = Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=2:objectType=Status')
-        expect(status.reply?).to be true
-      end
-
-      it 'sets account being replied to when possible' do
-        status = Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=2:objectType=Status')
-        expect(status.in_reply_to_account_id).to eq status.account_id
-      end
-
-      it 'ignores delete statuses unless they existed before' do
-        expect(Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=3:objectType=Status')).to be_nil
-        expect(Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=12:objectType=Status')).to be_nil
-      end
-
-      it 'does not create statuses for follows' do
-        expect(Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=1:objectType=Follow')).to be_nil
-        expect(Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=2:objectType=Follow')).to be_nil
-        expect(Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=4:objectType=Follow')).to be_nil
-        expect(Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=7:objectType=Follow')).to be_nil
-      end
-
-      it 'does not create statuses for favourites' do
-        expect(Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=2:objectType=Favourite')).to be_nil
-        expect(Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=3:objectType=Favourite')).to be_nil
-      end
-
-      it 'creates posts with media' do
-        status = Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=14:objectType=Status')
-
-        expect(status).to_not be_nil
-        expect(status.media_attachments.first).to have_attached_file(:file)
-        expect(status.media_attachments.first.image?).to be true
-        expect(status.media_attachments.first.file_file_name).not_to be_nil
-      end
-    end
-
-    context 'when domain is set to reject media' do
-      let!(:domain_block) { Fabricate(:domain_block, domain: 'kickass.zone', reject_media: true) }
-
-      before do
-        subject.call(body, account)
-      end
-
-      it 'updates remote user\'s account information' do
-        account.reload
-        expect(account.display_name).to eq '::1'
-      end
-
-      it 'rejects remote user\'s avatar' do
-        account.reload
-        expect(account.display_name).to eq '::1'
-        expect(account.avatar_file_name).to be_nil
-      end
-
-      it 'creates posts' do
-        expect(Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=1:objectType=Status')).to_not be_nil
-        expect(Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=2:objectType=Status')).to_not be_nil
-      end
-
-      it 'creates posts with remote-only media' do
-        status = Status.find_by(uri: 'tag:kickass.zone,2016-10-10:objectId=14:objectType=Status')
-
-        expect(status).to_not be_nil
-        expect(status.media_attachments.first.file_file_name).to be_nil
-        expect(status.media_attachments.first.unknown?).to be true
-      end
-    end
-  end
-
-  it 'does not accept tampered reblogs' do
-    good_actor = Fabricate(:account, username: 'tracer', domain: 'overwatch.com')
-
-    real_body = <<XML
-<?xml version="1.0"?>
-<entry xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0">
-  <id>tag:overwatch.com,2017-04-27:objectId=4467137:objectType=Status</id>
-  <published>2017-04-27T13:49:25Z</published>
-  <updated>2017-04-27T13:49:25Z</updated>
-  <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
-  <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
-  <author>
-    <id>https://overwatch.com/users/tracer</id>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-    <uri>https://overwatch.com/users/tracer</uri>
-    <name>tracer</name>
-  </author>
-  <content type="html">Overwatch rocks</content>
-</entry>
-XML
-
-    stub_request(:get, 'https://overwatch.com/users/tracer/updates/1').to_return(status: 200, body: real_body, headers: { 'Content-Type' => 'application/atom+xml' })
-
-    bad_actor = Fabricate(:account, username: 'sombra', domain: 'talon.xyz')
-
-    body = <<XML
-<?xml version="1.0"?>
-<entry xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0">
-  <id>tag:talon.xyz,2017-04-27:objectId=4467137:objectType=Status</id>
-  <published>2017-04-27T13:49:25Z</published>
-  <updated>2017-04-27T13:49:25Z</updated>
-  <author>
-    <id>https://talon.xyz/users/sombra</id>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-    <uri>https://talon.xyz/users/sombra</uri>
-    <name>sombra</name>
-  </author>
-  <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
-  <activity:verb>http://activitystrea.ms/schema/1.0/share</activity:verb>
-  <content type="html">Overwatch SUCKS AHAHA</content>
-  <activity:object>
-    <id>tag:overwatch.com,2017-04-27:objectId=4467137:objectType=Status</id>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
-    <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
-    <author>
-      <id>https://overwatch.com/users/tracer</id>
-      <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-      <uri>https://overwatch.com/users/tracer</uri>
-      <name>tracer</name>
-    </author>
-    <content type="html">Overwatch SUCKS AHAHA</content>
-    <link rel="alternate" type="text/html" href="https://overwatch.com/users/tracer/updates/1" />
-  </activity:object>
-</entry>
-XML
-    created_statuses = subject.call(body, bad_actor)
-
-    expect(created_statuses.first.reblog?).to be true
-    expect(created_statuses.first.account_id).to eq bad_actor.id
-    expect(created_statuses.first.reblog.account_id).to eq good_actor.id
-    expect(created_statuses.first.reblog.text).to eq 'Overwatch rocks'
-  end
-
-  it 'ignores reblogs if it failed to retrieve reblogged statuses' do
-    stub_request(:get, 'https://overwatch.com/users/tracer/updates/1').to_return(status: 404)
-
-    actor = Fabricate(:account, username: 'tracer', domain: 'overwatch.com')
-
-    body = <<XML
-<?xml version="1.0"?>
-<entry xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0">
-  <id>tag:overwatch.com,2017-04-27:objectId=4467137:objectType=Status</id>
-  <published>2017-04-27T13:49:25Z</published>
-  <updated>2017-04-27T13:49:25Z</updated>
-  <author>
-    <id>https://overwatch.com/users/tracer</id>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-    <uri>https://overwatch.com/users/tracer</uri>
-    <name>tracer</name>
-  </author>
-  <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
-  <activity:verb>http://activitystrea.ms/schema/1.0/share</activity:verb>
-  <content type="html">Overwatch rocks</content>
-  <activity:object>
-    <id>tag:overwatch.com,2017-04-27:objectId=4467137:objectType=Status</id>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
-    <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
-    <author>
-      <id>https://overwatch.com/users/tracer</id>
-      <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-      <uri>https://overwatch.com/users/tracer</uri>
-      <name>tracer</name>
-    </author>
-    <content type="html">Overwatch rocks</content>
-    <link rel="alternate" type="text/html" href="https://overwatch.com/users/tracer/updates/1" />
-  </activity:object>
-XML
-
-    created_statuses = subject.call(body, actor)
-
-    expect(created_statuses).to eq []
-  end
-
-  it 'ignores statuses with an out-of-order delete' do
-    sender = Fabricate(:account, username: 'tracer', domain: 'overwatch.com')
-
-    delete_body = <<XML
-<?xml version="1.0"?>
-<entry xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0">
-  <id>tag:overwatch.com,2017-04-27:objectId=4487555:objectType=Status</id>
-  <published>2017-04-27T13:49:25Z</published>
-  <updated>2017-04-27T13:49:25Z</updated>
-  <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
-  <activity:verb>http://activitystrea.ms/schema/1.0/delete</activity:verb>
-  <author>
-    <id>https://overwatch.com/users/tracer</id>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-    <uri>https://overwatch.com/users/tracer</uri>
-    <name>tracer</name>
-  </author>
-</entry>
-XML
-
-    status_body = <<XML
-<?xml version="1.0"?>
-<entry xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0">
-  <id>tag:overwatch.com,2017-04-27:objectId=4487555:objectType=Status</id>
-  <published>2017-04-27T13:49:25Z</published>
-  <updated>2017-04-27T13:49:25Z</updated>
-  <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
-  <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
-  <author>
-    <id>https://overwatch.com/users/tracer</id>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-    <uri>https://overwatch.com/users/tracer</uri>
-    <name>tracer</name>
-  </author>
-  <content type="html">Overwatch rocks</content>
-</entry>
-XML
-
-    subject.call(delete_body, sender)
-    created_statuses = subject.call(status_body, sender)
-
-    expect(created_statuses).to be_empty
-  end
-end
diff --git a/spec/services/process_interaction_service_spec.rb b/spec/services/process_interaction_service_spec.rb
deleted file mode 100644
index b858c19d0f94eec99d9f9524c9c51cb249f93dfe..0000000000000000000000000000000000000000
--- a/spec/services/process_interaction_service_spec.rb
+++ /dev/null
@@ -1,151 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe ProcessInteractionService, type: :service do
-  let(:receiver) { Fabricate(:user, email: 'alice@example.com', account: Fabricate(:account, username: 'alice')).account }
-  let(:sender)   { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
-  let(:remote_sender) { Fabricate(:account, username: 'carol', domain: 'localdomain.com', uri: 'https://webdomain.com/users/carol') }
-
-  subject { ProcessInteractionService.new }
-
-  describe 'status delete slap' do
-    let(:remote_status) { Fabricate(:status, account: remote_sender) }
-    let(:envelope) { OStatus2::Salmon.new.pack(payload, sender.keypair) }
-    let(:payload) {
-      <<~XML
-        <entry xmlns="http://www.w3.org/2005/Atom" xmlns:activity="http://activitystrea.ms/spec/1.0/">
-          <author>
-            <email>carol@localdomain.com</email>
-            <name>carol</name>
-            <uri>https://webdomain.com/users/carol</uri>
-          </author>
-
-          <id>#{remote_status.id}</id>
-          <activity:verb>http://activitystrea.ms/schema/1.0/delete</activity:verb>
-        </entry>
-      XML
-    }
-
-    before do
-      receiver.update(locked: true)
-      remote_sender.update(private_key: sender.private_key, public_key: remote_sender.public_key)
-    end
-
-    it 'deletes a record' do
-      expect(RemovalWorker).to receive(:perform_async).with(remote_status.id)
-      subject.call(envelope, receiver)
-    end
-  end
-
-  describe 'follow request slap' do
-    before do
-      receiver.update(locked: true)
-
-      payload = <<XML
-<entry xmlns="http://www.w3.org/2005/Atom" xmlns:activity="http://activitystrea.ms/spec/1.0/">
-  <author>
-    <name>bob</name>
-    <uri>https://cb6e6126.ngrok.io/users/bob</uri>
-  </author>
-
-  <id>someIdHere</id>
-  <activity:verb>http://activitystrea.ms/schema/1.0/request-friend</activity:verb>
-</entry>
-XML
-
-      envelope = OStatus2::Salmon.new.pack(payload, sender.keypair)
-      subject.call(envelope, receiver)
-    end
-
-    it 'creates a record' do
-      expect(FollowRequest.find_by(account: sender, target_account: receiver)).to_not be_nil
-    end
-  end
-
-  describe 'follow request slap from known remote user identified by email' do
-    before do
-      receiver.update(locked: true)
-      # Copy already-generated key
-      remote_sender.update(private_key: sender.private_key, public_key: remote_sender.public_key)
-
-      payload = <<XML
-<entry xmlns="http://www.w3.org/2005/Atom" xmlns:activity="http://activitystrea.ms/spec/1.0/">
-  <author>
-    <email>carol@localdomain.com</email>
-    <name>carol</name>
-    <uri>https://webdomain.com/users/carol</uri>
-  </author>
-
-  <id>someIdHere</id>
-  <activity:verb>http://activitystrea.ms/schema/1.0/request-friend</activity:verb>
-</entry>
-XML
-
-      envelope = OStatus2::Salmon.new.pack(payload, remote_sender.keypair)
-      subject.call(envelope, receiver)
-    end
-
-    it 'creates a record' do
-      expect(FollowRequest.find_by(account: remote_sender, target_account: receiver)).to_not be_nil
-    end
-  end
-
-  describe 'follow request authorization slap' do
-    before do
-      receiver.update(locked: true)
-      FollowRequest.create(account: sender, target_account: receiver)
-
-      payload = <<XML
-<entry xmlns="http://www.w3.org/2005/Atom" xmlns:activity="http://activitystrea.ms/spec/1.0/">
-  <author>
-    <name>alice</name>
-    <uri>https://cb6e6126.ngrok.io/users/alice</uri>
-  </author>
-
-  <id>someIdHere</id>
-  <activity:verb>http://activitystrea.ms/schema/1.0/authorize</activity:verb>
-</entry>
-XML
-
-      envelope = OStatus2::Salmon.new.pack(payload, receiver.keypair)
-      subject.call(envelope, sender)
-    end
-
-    it 'creates a follow relationship' do
-      expect(Follow.find_by(account: sender, target_account: receiver)).to_not be_nil
-    end
-
-    it 'removes the follow request' do
-      expect(FollowRequest.find_by(account: sender, target_account: receiver)).to be_nil
-    end
-  end
-
-  describe 'follow request rejection slap' do
-    before do
-      receiver.update(locked: true)
-      FollowRequest.create(account: sender, target_account: receiver)
-
-      payload = <<XML
-<entry xmlns="http://www.w3.org/2005/Atom" xmlns:activity="http://activitystrea.ms/spec/1.0/">
-  <author>
-    <name>alice</name>
-    <uri>https://cb6e6126.ngrok.io/users/alice</uri>
-  </author>
-
-  <id>someIdHere</id>
-  <activity:verb>http://activitystrea.ms/schema/1.0/reject</activity:verb>
-</entry>
-XML
-
-      envelope = OStatus2::Salmon.new.pack(payload, receiver.keypair)
-      subject.call(envelope, sender)
-    end
-
-    it 'does not create a follow relationship' do
-      expect(Follow.find_by(account: sender, target_account: receiver)).to be_nil
-    end
-
-    it 'removes the follow request' do
-      expect(FollowRequest.find_by(account: sender, target_account: receiver)).to be_nil
-    end
-  end
-end
diff --git a/spec/services/process_mentions_service_spec.rb b/spec/services/process_mentions_service_spec.rb
index 8a6bb44acaad6ba4abf6ae4aa241a125512bc8ad..b1abd79b0f2b5ceb8d12ca374f3038428df067b2 100644
--- a/spec/services/process_mentions_service_spec.rb
+++ b/spec/services/process_mentions_service_spec.rb
@@ -15,12 +15,8 @@ RSpec.describe ProcessMentionsService, type: :service do
       subject.call(status)
     end
 
-    it 'creates a mention' do
-      expect(remote_user.mentions.where(status: status).count).to eq 1
-    end
-
-    it 'posts to remote user\'s Salmon end point' do
-      expect(a_request(:post, remote_user.salmon_url)).to have_been_made.once
+    it 'does not create a mention' do
+      expect(remote_user.mentions.where(status: status).count).to eq 0
     end
   end
 
diff --git a/spec/services/pubsubhubbub/subscribe_service_spec.rb b/spec/services/pubsubhubbub/subscribe_service_spec.rb
deleted file mode 100644
index 01c956230a73bd52be20c0c03f245f2f44bb2fcd..0000000000000000000000000000000000000000
--- a/spec/services/pubsubhubbub/subscribe_service_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe Pubsubhubbub::SubscribeService, type: :service do
-  describe '#call' do
-    subject { described_class.new }
-    let(:user_account) { Fabricate(:account) }
-
-    context 'with a nil account' do
-      it 'returns the invalid topic status results' do
-        result = service_call(account: nil)
-
-        expect(result).to eq invalid_topic_status
-      end
-    end
-
-    context 'with an invalid callback url' do
-      it 'returns invalid callback status when callback is blank' do
-        result = service_call(callback: '')
-
-        expect(result).to eq invalid_callback_status
-      end
-      it 'returns invalid callback status when callback is not a URI' do
-        result = service_call(callback: 'invalid-hostname')
-
-        expect(result).to eq invalid_callback_status
-      end
-    end
-
-    context 'with a blocked domain in the callback' do
-      it 'returns callback not allowed' do
-        Fabricate(:domain_block, domain: 'test.host', severity: :suspend)
-        result = service_call(callback: 'https://test.host/api')
-
-        expect(result).to eq not_allowed_callback_status
-      end
-    end
-
-    context 'with a valid account and callback' do
-      it 'returns success status and confirms subscription' do
-        allow(Pubsubhubbub::ConfirmationWorker).to receive(:perform_async).and_return(nil)
-        subscription = Fabricate(:subscription, account: user_account)
-
-        result = service_call(callback: subscription.callback_url)
-        expect(result).to eq success_status
-        expect(Pubsubhubbub::ConfirmationWorker).to have_received(:perform_async).with(subscription.id, 'subscribe', 'asdf', 3600)
-      end
-    end
-  end
-
-  def service_call(account: user_account, callback: 'https://callback.host', secret: 'asdf', lease_seconds: 3600)
-    subject.call(account, callback, secret, lease_seconds)
-  end
-
-  def invalid_topic_status
-    ['Invalid topic URL', 422]
-  end
-
-  def invalid_callback_status
-    ['Invalid callback URL', 422]
-  end
-
-  def not_allowed_callback_status
-    ['Callback URL not allowed', 403]
-  end
-
-  def success_status
-    ['', 202]
-  end
-end
diff --git a/spec/services/pubsubhubbub/unsubscribe_service_spec.rb b/spec/services/pubsubhubbub/unsubscribe_service_spec.rb
deleted file mode 100644
index 7ed9fc5af5fcdd2f64656d0f795848626cdc01cb..0000000000000000000000000000000000000000
--- a/spec/services/pubsubhubbub/unsubscribe_service_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe Pubsubhubbub::UnsubscribeService, type: :service do
-  describe '#call' do
-    subject { described_class.new }
-
-    context 'with a nil account' do
-      it 'returns an invalid topic status' do
-        result = subject.call(nil, 'callback.host')
-
-        expect(result).to eq invalid_topic_status
-      end
-    end
-
-    context 'with a valid account' do
-      let(:account) { Fabricate(:account) }
-
-      it 'returns a valid topic status and does not run confirm when no subscription' do
-        allow(Pubsubhubbub::ConfirmationWorker).to receive(:perform_async).and_return(nil)
-        result = subject.call(account, 'callback.host')
-
-        expect(result).to eq valid_topic_status
-        expect(Pubsubhubbub::ConfirmationWorker).not_to have_received(:perform_async)
-      end
-
-      it 'returns a valid topic status and does run confirm when there is a subscription' do
-        subscription = Fabricate(:subscription, account: account, callback_url: 'callback.host')
-        allow(Pubsubhubbub::ConfirmationWorker).to receive(:perform_async).and_return(nil)
-        result = subject.call(account, 'callback.host')
-
-        expect(result).to eq valid_topic_status
-        expect(Pubsubhubbub::ConfirmationWorker).to have_received(:perform_async).with(subscription.id, 'unsubscribe')
-      end
-    end
-
-    def invalid_topic_status
-      ['Invalid topic URL', 422]
-    end
-
-    def valid_topic_status
-      ['', 202]
-    end
-  end
-end
diff --git a/spec/services/reblog_service_spec.rb b/spec/services/reblog_service_spec.rb
index 9d84c41d5eaadeabf18f0180ebbf693c4c442d96..58fb46f0ff1bb7f1bd7e1105632d30b69d8c67b6 100644
--- a/spec/services/reblog_service_spec.rb
+++ b/spec/services/reblog_service_spec.rb
@@ -46,10 +46,6 @@ RSpec.describe ReblogService, type: :service do
     it 'creates a reblog' do
       expect(status.reblogs.count).to eq 1
     end
-
-    it 'sends a Salmon slap for a remote reblog' do
-      expect(a_request(:post, 'http://salmon.example.com')).to have_been_made
-    end
   end
 
   context 'ActivityPub' do
diff --git a/spec/services/reject_follow_service_spec.rb b/spec/services/reject_follow_service_spec.rb
index e5ac37ed9037a091e38ea2f395ced23598dba662..1aec060db7a8f9e20dba5c0d1140bd282eda0861 100644
--- a/spec/services/reject_follow_service_spec.rb
+++ b/spec/services/reject_follow_service_spec.rb
@@ -38,13 +38,6 @@ RSpec.describe RejectFollowService, type: :service do
     it 'does not create follow relation' do
       expect(bob.following?(sender)).to be false
     end
-
-    it 'sends a follow request rejection salmon slap' do
-      expect(a_request(:post, "http://salmon.example.com/").with { |req|
-        xml = OStatus2::Salmon.new.unpack(req.body)
-        xml.match(OStatus::TagManager::VERBS[:reject])
-      }).to have_been_made.once
-    end
   end
 
   describe 'remote ActivityPub' do
diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb
index 7bba83a60213f7edd345580834bb9f5b2534837c..06676ec45e20dd4120ab40fa7e2c9f749010ae02 100644
--- a/spec/services/remove_status_service_spec.rb
+++ b/spec/services/remove_status_service_spec.rb
@@ -10,12 +10,9 @@ RSpec.describe RemoveStatusService, type: :service do
   let!(:bill)   { Fabricate(:account, username: 'bill', protocol: :activitypub, domain: 'example2.com', inbox_url: 'http://example2.com/inbox') }
 
   before do
-    stub_request(:post, 'http://example.com/push').to_return(status: 200, body: '', headers: {})
-    stub_request(:post, 'http://example.com/salmon').to_return(status: 200, body: '', headers: {})
     stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
     stub_request(:post, 'http://example2.com/inbox').to_return(status: 200)
 
-    Fabricate(:subscription, account: alice, callback_url: 'http://example.com/push', confirmed: true, expires_at: 30.days.from_now)
     jeff.follow!(alice)
     hank.follow!(alice)
 
@@ -32,23 +29,10 @@ RSpec.describe RemoveStatusService, type: :service do
     expect(HomeFeed.new(jeff).get(10)).to_not include(@status.id)
   end
 
-  it 'sends PuSH update to PuSH subscribers' do
-    expect(a_request(:post, 'http://example.com/push').with { |req|
-      req.body.match(OStatus::TagManager::VERBS[:delete])
-    }).to have_been_made
-  end
-
   it 'sends delete activity to followers' do
     expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.twice
   end
 
-  it 'sends Salmon slap to previously mentioned users' do
-    expect(a_request(:post, "http://example.com/salmon").with { |req|
-      xml = OStatus2::Salmon.new.unpack(req.body)
-      xml.match(OStatus::TagManager::VERBS[:delete])
-    }).to have_been_made.once
-  end
-
   it 'sends delete activity to rebloggers' do
     expect(a_request(:post, 'http://example2.com/inbox')).to have_been_made
   end
diff --git a/spec/services/resolve_account_service_spec.rb b/spec/services/resolve_account_service_spec.rb
index 27a85af7c9778bd1469fc585af837396d8d41609..cea942e39cb8b0aab2d7b628819fd6b65268b827 100644
--- a/spec/services/resolve_account_service_spec.rb
+++ b/spec/services/resolve_account_service_spec.rb
@@ -6,19 +6,13 @@ RSpec.describe ResolveAccountService, type: :service do
   before do
     stub_request(:get, "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt'))
     stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:catsrgr8@example.com").to_return(status: 404)
-    stub_request(:get, "https://redirected.com/.well-known/host-meta").to_return(request_fixture('redirected.host-meta.txt'))
     stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404)
-    stub_request(:get, "https://quitter.no/.well-known/webfinger?resource=acct:gargron@quitter.no").to_return(request_fixture('webfinger.txt'))
-    stub_request(:get, "https://redirected.com/.well-known/webfinger?resource=acct:gargron@redirected.com").to_return(request_fixture('webfinger.txt'))
-    stub_request(:get, "https://redirected.com/.well-known/webfinger?resource=acct:hacker1@redirected.com").to_return(request_fixture('webfinger-hacker1.txt'))
-    stub_request(:get, "https://redirected.com/.well-known/webfinger?resource=acct:hacker2@redirected.com").to_return(request_fixture('webfinger-hacker2.txt'))
-    stub_request(:get, "https://quitter.no/.well-known/webfinger?resource=acct:catsrgr8@quitter.no").to_return(status: 404)
-    stub_request(:get, "https://quitter.no/api/statuses/user_timeline/7477.atom").to_return(request_fixture('feed.txt'))
     stub_request(:get, "https://quitter.no/avatar/7477-300-20160211190340.png").to_return(request_fixture('avatar.txt'))
-    stub_request(:get, "https://localdomain.com/.well-known/host-meta").to_return(request_fixture('localdomain-hostmeta.txt'))
-    stub_request(:get, "https://localdomain.com/.well-known/webfinger?resource=acct:foo@localdomain.com").to_return(status: 404)
-    stub_request(:get, "https://webdomain.com/.well-known/webfinger?resource=acct:foo@localdomain.com").to_return(request_fixture('localdomain-webfinger.txt'))
-    stub_request(:get, "https://webdomain.com/users/foo.atom").to_return(request_fixture('localdomain-feed.txt'))
+    stub_request(:get, "https://quitter.no/.well-known/webfinger?resource=acct:catsrgr8@quitter.no").to_return(status: 404)
+    stub_request(:get, "https://ap.example.com/.well-known/webfinger?resource=acct:foo@ap.example.com").to_return(request_fixture('activitypub-webfinger.txt'))
+    stub_request(:get, "https://ap.example.com/users/foo").to_return(request_fixture('activitypub-actor.txt'))
+    stub_request(:get, "https://ap.example.com/users/foo.atom").to_return(request_fixture('activitypub-feed.txt'))
+    stub_request(:get, %r{https://ap.example.com/users/foo/\w+}).to_return(status: 404)
   end
 
   it 'raises error if no such user can be resolved via webfinger' do
@@ -29,74 +23,7 @@ RSpec.describe ResolveAccountService, type: :service do
     expect(subject.call('catsrgr8@example.com')).to be_nil
   end
 
-  it 'prevents hijacking existing accounts' do
-    account = subject.call('hacker1@redirected.com')
-    expect(account.salmon_url).to_not eq 'https://hacker.com/main/salmon/user/7477'
-  end
-
-  it 'prevents hijacking inexisting accounts' do
-    expect(subject.call('hacker2@redirected.com')).to be_nil
-  end
-
-  context 'with an OStatus account' do
-    it 'returns an already existing remote account' do
-      old_account      = Fabricate(:account, username: 'gargron', domain: 'quitter.no')
-      returned_account = subject.call('gargron@quitter.no')
-
-      expect(old_account.id).to eq returned_account.id
-    end
-
-    it 'returns a new remote account' do
-      account = subject.call('gargron@quitter.no')
-
-      expect(account.username).to eq 'gargron'
-      expect(account.domain).to eq 'quitter.no'
-      expect(account.remote_url).to eq 'https://quitter.no/api/statuses/user_timeline/7477.atom'
-    end
-
-    it 'follows a legitimate account redirection' do
-      account = subject.call('gargron@redirected.com')
-
-      expect(account.username).to eq 'gargron'
-      expect(account.domain).to eq 'quitter.no'
-      expect(account.remote_url).to eq 'https://quitter.no/api/statuses/user_timeline/7477.atom'
-    end
-
-    it 'returns a new remote account' do
-      account = subject.call('foo@localdomain.com')
-
-      expect(account.username).to eq 'foo'
-      expect(account.domain).to eq 'localdomain.com'
-      expect(account.remote_url).to eq 'https://webdomain.com/users/foo.atom'
-    end
-  end
-
   context 'with an ActivityPub account' do
-    before do
-      stub_request(:get, "https://ap.example.com/.well-known/webfinger?resource=acct:foo@ap.example.com").to_return(request_fixture('activitypub-webfinger.txt'))
-      stub_request(:get, "https://ap.example.com/users/foo").to_return(request_fixture('activitypub-actor.txt'))
-      stub_request(:get, "https://ap.example.com/users/foo.atom").to_return(request_fixture('activitypub-feed.txt'))
-      stub_request(:get, %r{https://ap.example.com/users/foo/\w+}).to_return(status: 404)
-    end
-
-    it 'fallback to OStatus if actor json could not be fetched' do
-      stub_request(:get, "https://ap.example.com/users/foo").to_return(status: 404)
-
-      account = subject.call('foo@ap.example.com')
-
-      expect(account.ostatus?).to eq true
-      expect(account.remote_url).to eq 'https://ap.example.com/users/foo.atom'
-    end
-
-    it 'fallback to OStatus if actor json did not have inbox_url' do
-      stub_request(:get, "https://ap.example.com/users/foo").to_return(request_fixture('activitypub-actor-noinbox.txt'))
-
-      account = subject.call('foo@ap.example.com')
-
-      expect(account.ostatus?).to eq true
-      expect(account.remote_url).to eq 'https://ap.example.com/users/foo.atom'
-    end
-
     it 'returns new remote account' do
       account = subject.call('foo@ap.example.com')
 
@@ -124,13 +51,19 @@ RSpec.describe ResolveAccountService, type: :service do
   it 'processes one remote account at a time using locks' do
     wait_for_start = true
     fail_occurred  = false
-    return_values  = []
+    return_values  = Concurrent::Array.new
+
+    # Preload classes that throw circular dependency errors in threads
+    Account
+    TagManager
+    DomainBlock
 
     threads = Array.new(5) do
       Thread.new do
         true while wait_for_start
+
         begin
-          return_values << described_class.new.call('foo@localdomain.com')
+          return_values << described_class.new.call('foo@ap.example.com')
         rescue ActiveRecord::RecordNotUnique
           fail_occurred = true
         end
diff --git a/spec/services/resolve_url_service_spec.rb b/spec/services/resolve_url_service_spec.rb
index 7bb5d1940242df65ff49924213d6fec67524eba8..aa4204637187d980ef2fc705fc4b442d63ff1375 100644
--- a/spec/services/resolve_url_service_spec.rb
+++ b/spec/services/resolve_url_service_spec.rb
@@ -6,48 +6,14 @@ describe ResolveURLService, type: :service do
   subject { described_class.new }
 
   describe '#call' do
-    it 'returns nil when there is no atom url' do
-      url = 'http://example.com/missing-atom'
+    it 'returns nil when there is no resource url' do
+      url     = 'http://example.com/missing-resource'
       service = double
-      allow(FetchAtomService).to receive(:new).and_return service
-      allow(service).to receive(:call).with(url).and_return(nil)
-
-      result = subject.call(url)
-      expect(result).to be_nil
-    end
-
-    it 'fetches remote accounts for feed types' do
-      url = 'http://example.com/atom-feed'
-      service = double
-      allow(FetchAtomService).to receive(:new).and_return service
-      feed_url = 'http://feed-url'
-      feed_content = '<feed>contents</feed>'
-      allow(service).to receive(:call).with(url).and_return([feed_url, { prefetched_body: feed_content }])
-
-      account_service = double
-      allow(FetchRemoteAccountService).to receive(:new).and_return(account_service)
-      allow(account_service).to receive(:call)
-
-      _result = subject.call(url)
 
-      expect(account_service).to have_received(:call).with(feed_url, feed_content, nil)
-    end
-
-    it 'fetches remote statuses for entry types' do
-      url = 'http://example.com/atom-entry'
-      service = double
-      allow(FetchAtomService).to receive(:new).and_return service
-      feed_url = 'http://feed-url'
-      feed_content = '<entry>contents</entry>'
-      allow(service).to receive(:call).with(url).and_return([feed_url, { prefetched_body: feed_content }])
-
-      account_service = double
-      allow(FetchRemoteStatusService).to receive(:new).and_return(account_service)
-      allow(account_service).to receive(:call)
-
-      _result = subject.call(url)
+      allow(FetchResourceService).to receive(:new).and_return service
+      allow(service).to receive(:call).with(url).and_return(nil)
 
-      expect(account_service).to have_received(:call).with(feed_url, feed_content, nil)
+      expect(subject.call(url)).to be_nil
     end
   end
 end
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index d064cd9b85d5f682135364fbd8c674b8304d6e25..739bb9cf5d8db78183a604f38a043a4aa78e9e66 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -27,7 +27,7 @@ describe SearchService, type: :service do
         it 'returns the empty results' do
           service = double(call: nil)
           allow(ResolveURLService).to receive(:new).and_return(service)
-          results = subject.call(@query, nil, 10)
+          results = subject.call(@query, nil, 10, resolve: true)
 
           expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
           expect(results).to eq empty_results
@@ -40,7 +40,7 @@ describe SearchService, type: :service do
           service = double(call: account)
           allow(ResolveURLService).to receive(:new).and_return(service)
 
-          results = subject.call(@query, nil, 10)
+          results = subject.call(@query, nil, 10, resolve: true)
           expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
           expect(results).to eq empty_results.merge(accounts: [account])
         end
@@ -52,7 +52,7 @@ describe SearchService, type: :service do
           service = double(call: status)
           allow(ResolveURLService).to receive(:new).and_return(service)
 
-          results = subject.call(@query, nil, 10)
+          results = subject.call(@query, nil, 10, resolve: true)
           expect(service).to have_received(:call).with(@query, on_behalf_of: nil)
           expect(results).to eq empty_results.merge(statuses: [status])
         end
@@ -77,10 +77,10 @@ describe SearchService, type: :service do
         it 'includes the tag in the results' do
           query = '#tag'
           tag = Tag.new
-          allow(Tag).to receive(:search_for).with('tag', 10, 0).and_return([tag])
+          allow(Tag).to receive(:search_for).with('tag', 10, 0, exclude_unreviewed: nil).and_return([tag])
 
           results = subject.call(query, nil, 10)
-          expect(Tag).to have_received(:search_for).with('tag', 10, 0)
+          expect(Tag).to have_received(:search_for).with('tag', 10, 0, exclude_unreviewed: nil)
           expect(results).to eq empty_results.merge(hashtags: [tag])
         end
         it 'does not include tag when starts with @ character' do
diff --git a/spec/services/send_interaction_service_spec.rb b/spec/services/send_interaction_service_spec.rb
deleted file mode 100644
index 710d8184cfc38fe367dc81f2b65facdaf76344d7..0000000000000000000000000000000000000000
--- a/spec/services/send_interaction_service_spec.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe SendInteractionService, type: :service do
-  subject { SendInteractionService.new }
-
-  it 'sends an XML envelope to the Salmon end point of remote user'
-end
diff --git a/spec/services/subscribe_service_spec.rb b/spec/services/subscribe_service_spec.rb
deleted file mode 100644
index 10bdb1ba8f757e907d32d58ca3ba64e29f8a0839..0000000000000000000000000000000000000000
--- a/spec/services/subscribe_service_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe SubscribeService, type: :service do
-  let(:account) { Fabricate(:account, username: 'bob', domain: 'example.com', hub_url: 'http://hub.example.com') }
-  subject { SubscribeService.new }
-
-  it 'sends subscription request to PuSH hub' do
-    stub_request(:post, 'http://hub.example.com/').to_return(status: 202)
-    subject.call(account)
-    expect(a_request(:post, 'http://hub.example.com/')).to have_been_made.once
-  end
-
-  it 'generates and keeps PuSH secret on successful call' do
-    stub_request(:post, 'http://hub.example.com/').to_return(status: 202)
-    subject.call(account)
-    expect(account.secret).to_not be_blank
-  end
-
-  it 'fails silently if PuSH hub forbids subscription' do
-    stub_request(:post, 'http://hub.example.com/').to_return(status: 403)
-    subject.call(account)
-  end
-
-  it 'fails silently if PuSH hub is not found' do
-    stub_request(:post, 'http://hub.example.com/').to_return(status: 404)
-    subject.call(account)
-  end
-
-  it 'fails loudly if there is a network error' do
-    stub_request(:post, 'http://hub.example.com/').to_raise(HTTP::Error)
-    expect { subject.call(account) }.to raise_error HTTP::Error
-  end
-
-  it 'fails loudly if PuSH hub is unavailable' do
-    stub_request(:post, 'http://hub.example.com/').to_return(status: 503)
-    expect { subject.call(account) }.to raise_error Mastodon::UnexpectedResponseError
-  end
-
-  it 'fails loudly if rate limited' do
-    stub_request(:post, 'http://hub.example.com/').to_return(status: 429)
-    expect { subject.call(account) }.to raise_error Mastodon::UnexpectedResponseError
-  end
-end
diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb
index 6f45762aa3fb6d99a2d41d20c63e331069b5bfb5..eebbbc12ad8b95feff8beff8de5e7eac33f955be 100644
--- a/spec/services/suspend_account_service_spec.rb
+++ b/spec/services/suspend_account_service_spec.rb
@@ -18,7 +18,6 @@ RSpec.describe SuspendAccountService, type: :service do
     let!(:favourite) { Fabricate(:favourite, account: account) }
     let!(:active_relationship) { Fabricate(:follow, account: account) }
     let!(:passive_relationship) { Fabricate(:follow, target_account: account) }
-    let!(:subscription) { Fabricate(:subscription, account: account) }
     let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', protocol: :activitypub) }
     let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
 
@@ -27,14 +26,12 @@ RSpec.describe SuspendAccountService, type: :service do
         [
           account.statuses,
           account.media_attachments,
-          account.stream_entries,
           account.notifications,
           account.favourites,
           account.active_relationships,
           account.passive_relationships,
-          account.subscriptions
         ].map(&:count)
-      }.from([1, 1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0, 0])
+      }.from([1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0])
     end
 
     it 'sends a delete actor activity to all known inboxes' do
@@ -63,21 +60,18 @@ RSpec.describe SuspendAccountService, type: :service do
     let!(:favourite) { Fabricate(:favourite, account: remote_bob) }
     let!(:active_relationship) { Fabricate(:follow, account: remote_bob, target_account: account) }
     let!(:passive_relationship) { Fabricate(:follow, target_account: remote_bob) }
-    let!(:subscription) { Fabricate(:subscription, account: remote_bob) }
 
     it 'deletes associated records' do
       is_expected.to change {
         [
           remote_bob.statuses,
           remote_bob.media_attachments,
-          remote_bob.stream_entries,
           remote_bob.notifications,
           remote_bob.favourites,
           remote_bob.active_relationships,
           remote_bob.passive_relationships,
-          remote_bob.subscriptions
         ].map(&:count)
-      }.from([1, 1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0, 0])
+      }.from([1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0])
     end
 
     it 'sends a reject follow to follwer inboxes' do
diff --git a/spec/services/unblock_domain_service_spec.rb b/spec/services/unblock_domain_service_spec.rb
index 619aefb5c0c535f291b09b2950a2437662419201..27dbc92ada08e7e9ce88adacf9c810abfec675be 100644
--- a/spec/services/unblock_domain_service_spec.rb
+++ b/spec/services/unblock_domain_service_spec.rb
@@ -31,7 +31,7 @@ describe UnblockDomainService, type: :service do
       subject.call(@domain_block)
       expect_deleted_domain_block
       expect(@suspended.reload.suspended?).to be false
-      expect(@silenced.reload.silenced?).to be true
+      expect(@silenced.reload.silenced?).to be false
       expect(@independently_suspended.reload.suspended?).to be true
       expect(@independently_silenced.reload.silenced?).to be true
     end
diff --git a/spec/services/unblock_service_spec.rb b/spec/services/unblock_service_spec.rb
index 5835b912ba489c97b0f168af14423b33137d8dfc..6350c683441cd95676cf320466bbcc78a9b53c0a 100644
--- a/spec/services/unblock_service_spec.rb
+++ b/spec/services/unblock_service_spec.rb
@@ -30,13 +30,6 @@ RSpec.describe UnblockService, type: :service do
     it 'destroys the blocking relation' do
       expect(sender.blocking?(bob)).to be false
     end
-
-    it 'sends an unblock salmon slap' do
-      expect(a_request(:post, "http://salmon.example.com/").with { |req|
-        xml = OStatus2::Salmon.new.unpack(req.body)
-        xml.match(OStatus::TagManager::VERBS[:unblock])
-      }).to have_been_made.once
-    end
   end
 
   describe 'remote ActivityPub' do
diff --git a/spec/services/unfollow_service_spec.rb b/spec/services/unfollow_service_spec.rb
index 8a2881ab17f29e26811411d397fadce0fb4d6265..84b5dafbc4f2dd12c494f27554afcbeedbdcd0da 100644
--- a/spec/services/unfollow_service_spec.rb
+++ b/spec/services/unfollow_service_spec.rb
@@ -30,13 +30,6 @@ RSpec.describe UnfollowService, type: :service do
     it 'destroys the following relation' do
       expect(sender.following?(bob)).to be false
     end
-
-    it 'sends an unfollow salmon slap' do
-      expect(a_request(:post, "http://salmon.example.com/").with { |req|
-        xml = OStatus2::Salmon.new.unpack(req.body)
-        xml.match(OStatus::TagManager::VERBS[:unfollow])
-      }).to have_been_made.once
-    end
   end
 
   describe 'remote ActivityPub' do
diff --git a/spec/services/unsubscribe_service_spec.rb b/spec/services/unsubscribe_service_spec.rb
deleted file mode 100644
index 54d4b1b53cfd51f9137accbf8d418ac01b486bd2..0000000000000000000000000000000000000000
--- a/spec/services/unsubscribe_service_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe UnsubscribeService, type: :service do
-  let(:account) { Fabricate(:account, username: 'bob', domain: 'example.com', hub_url: 'http://hub.example.com') }
-  subject { UnsubscribeService.new }
-
-  it 'removes the secret and resets expiration on account' do
-    stub_request(:post, 'http://hub.example.com/').to_return(status: 204)
-    subject.call(account)
-    account.reload
-
-    expect(account.secret).to be_blank
-    expect(account.subscription_expires_at).to be_blank
-  end
-
-  it 'logs error on subscription failure' do
-    logger = stub_logger
-    stub_request(:post, 'http://hub.example.com/').to_return(status: 404)
-    subject.call(account)
-
-    expect(logger).to have_received(:debug).with(/unsubscribe for bob@example.com failed/)
-  end
-
-  it 'logs error on connection failure' do
-    logger = stub_logger
-    stub_request(:post, 'http://hub.example.com/').to_raise(HTTP::Error)
-    subject.call(account)
-
-    expect(logger).to have_received(:debug).with(/unsubscribe for bob@example.com failed/)
-  end
-
-  def stub_logger
-    double(debug: nil).tap do |logger|
-      allow(Rails).to receive(:logger).and_return(logger)
-    end
-  end
-end
diff --git a/spec/services/update_account_service_spec.rb b/spec/services/update_account_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..960b26891f78780f18f10c3cd8de59dc5738827d
--- /dev/null
+++ b/spec/services/update_account_service_spec.rb
@@ -0,0 +1,38 @@
+require 'rails_helper'
+
+RSpec.describe UpdateAccountService, type: :service do
+  subject { UpdateAccountService.new }
+
+  describe 'switching form locked to unlocked accounts' do
+    let(:account) { Fabricate(:account, locked: true) }
+    let(:alice)   { Fabricate(:user, email: 'alice@example.com', account: Fabricate(:account, username: 'alice')).account }
+    let(:bob)     { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:eve)     { Fabricate(:user, email: 'eve@example.com', account: Fabricate(:account, username: 'eve')).account }
+
+    before do
+      bob.touch(:silenced_at)
+      account.mute!(eve)
+
+      FollowService.new.call(alice, account)
+      FollowService.new.call(bob, account)
+      FollowService.new.call(eve, account)
+
+      subject.call(account, { locked: false })
+    end
+
+    it 'auto-accepts pending follow requests' do
+      expect(alice.following?(account)).to be true
+      expect(alice.requested?(account)).to be false
+    end
+
+    it 'does not auto-accept pending follow requests from silenced users' do
+      expect(bob.following?(account)).to be false
+      expect(bob.requested?(account)).to be true
+    end
+
+    it 'auto-accepts pending follow requests from muted users so as to not leak mute' do
+      expect(eve.following?(account)).to be true
+      expect(eve.requested?(account)).to be false
+    end
+  end
+end
diff --git a/spec/services/update_remote_profile_service_spec.rb b/spec/services/update_remote_profile_service_spec.rb
deleted file mode 100644
index f3ea70b801b7ff943d08c95241e1b0a1435351dc..0000000000000000000000000000000000000000
--- a/spec/services/update_remote_profile_service_spec.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe UpdateRemoteProfileService, type: :service do
-  let(:xml) { File.read(Rails.root.join('spec', 'fixtures', 'push', 'feed.atom')) }
-
-  subject { UpdateRemoteProfileService.new }
-
-  before do
-    stub_request(:get, 'https://quitter.no/avatar/7477-300-20160211190340.png').to_return(request_fixture('avatar.txt'))
-  end
-
-  context 'with updated details' do
-    let(:remote_account) { Fabricate(:account, username: 'bob', domain: 'example.com') }
-
-    before do
-      subject.call(xml, remote_account)
-    end
-
-    it 'downloads new avatar' do
-      expect(a_request(:get, 'https://quitter.no/avatar/7477-300-20160211190340.png')).to have_been_made
-    end
-
-    it 'sets the avatar remote url' do
-      expect(remote_account.reload.avatar_remote_url).to eq 'https://quitter.no/avatar/7477-300-20160211190340.png'
-    end
-
-    it 'sets display name' do
-      expect(remote_account.reload.display_name).to eq 'DIGITAL CAT'
-    end
-
-    it 'sets note' do
-      expect(remote_account.reload.note).to eq 'Software engineer, free time musician and DIGITAL SPORTS enthusiast. Likes cats. Warning: May contain memes'
-    end
-  end
-
-  context 'with unchanged details' do
-    let(:remote_account) { Fabricate(:account, username: 'bob', domain: 'example.com', display_name: 'DIGITAL CAT', note: 'Software engineer, free time musician and DIGITAL SPORTS enthusiast. Likes cats. Warning: May contain memes', avatar_remote_url: 'https://quitter.no/avatar/7477-300-20160211190340.png') }
-
-    before do
-      subject.call(xml, remote_account)
-    end
-
-    it 'does not re-download avatar' do
-      expect(a_request(:get, 'https://quitter.no/avatar/7477-300-20160211190340.png')).to have_been_made.once
-    end
-
-    it 'sets the avatar remote url' do
-      expect(remote_account.reload.avatar_remote_url).to eq 'https://quitter.no/avatar/7477-300-20160211190340.png'
-    end
-
-    it 'sets display name' do
-      expect(remote_account.reload.display_name).to eq 'DIGITAL CAT'
-    end
-
-    it 'sets note' do
-      expect(remote_account.reload.note).to eq 'Software engineer, free time musician and DIGITAL SPORTS enthusiast. Likes cats. Warning: May contain memes'
-    end
-  end
-
-  context 'with updated details from a domain set to reject media' do
-    let(:remote_account) { Fabricate(:account, username: 'bob', domain: 'example.com') }
-    let!(:domain_block) { Fabricate(:domain_block, domain: 'example.com', reject_media: true) }
-
-    before do
-      subject.call(xml, remote_account)
-    end
-
-    it 'does not the avatar remote url' do
-      expect(remote_account.reload.avatar_remote_url).to be_nil
-    end
-
-    it 'sets display name' do
-      expect(remote_account.reload.display_name).to eq 'DIGITAL CAT'
-    end
-
-    it 'sets note' do
-      expect(remote_account.reload.note).to eq 'Software engineer, free time musician and DIGITAL SPORTS enthusiast. Likes cats. Warning: May contain memes'
-    end
-
-    it 'does not set store the avatar' do
-      expect(remote_account.reload.avatar_file_name).to be_nil
-    end
-  end
-end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 0cd1f91d021521f7756efc86d8270bbe826551b3..45ba1bbd9bd976ecdc4b59b6814f0832b82be51f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -27,6 +27,7 @@ RSpec.configure do |config|
   end
 
   config.before :suite do
+    Rails.application.load_seed
     Chewy.strategy(:bypass)
   end
 
diff --git a/spec/validators/disallowed_hashtags_validator_spec.rb b/spec/validators/disallowed_hashtags_validator_spec.rb
index 8ec1302ab9f6b6bd13613eaeb38cbf7a5da15e93..9deec0bb90e2b7d5bf642da67817b28363bdc89d 100644
--- a/spec/validators/disallowed_hashtags_validator_spec.rb
+++ b/spec/validators/disallowed_hashtags_validator_spec.rb
@@ -3,42 +3,44 @@
 require 'rails_helper'
 
 RSpec.describe DisallowedHashtagsValidator, type: :validator do
+  let(:disallowed_tags) { [] }
+
   describe '#validate' do
     before do
-      allow_any_instance_of(described_class).to receive(:select_tags) { tags }
+      disallowed_tags.each { |name| Fabricate(:tag, name: name, usable: false) }
       described_class.new.validate(status)
     end
 
-    let(:status) { double(errors: errors, local?: local, reblog?: reblog, text: '') }
+    let(:status) { double(errors: errors, local?: local, reblog?: reblog, text: disallowed_tags.map { |x| '#' + x }.join(' ')) }
     let(:errors) { double(add: nil) }
 
-    context 'unless status.local? && !status.reblog?' do
+    context 'for a remote reblog' do
       let(:local)  { false }
       let(:reblog) { true }
 
-      it 'not calls errors.add' do
+      it 'does not add errors' do
         expect(errors).not_to have_received(:add).with(:text, any_args)
       end
     end
 
-    context 'status.local? && !status.reblog?' do
+    context 'for a local original status' do
       let(:local)  { true }
       let(:reblog) { false }
 
-      context 'tags.empty?' do
-        let(:tags) { [] }
+      context 'when does not contain any disallowed hashtags' do
+        let(:disallowed_tags) { [] }
 
-        it 'not calls errors.add' do
+        it 'does not add errors' do
           expect(errors).not_to have_received(:add).with(:text, any_args)
         end
       end
 
-      context '!tags.empty?' do
-        let(:tags) { %w(a b c) }
+      context 'when contains disallowed hashtags' do
+        let(:disallowed_tags) { %w(a b c) }
 
-        it 'calls errors.add' do
+        it 'adds an error' do
           expect(errors).to have_received(:add)
-            .with(:text, I18n.t('statuses.disallowed_hashtags', tags: tags.join(', '), count: tags.size))
+            .with(:text, I18n.t('statuses.disallowed_hashtags', tags: disallowed_tags.join(', '), count: disallowed_tags.size))
         end
       end
     end
diff --git a/spec/views/stream_entries/show.html.haml_spec.rb b/spec/views/statuses/show.html.haml_spec.rb
similarity index 83%
rename from spec/views/stream_entries/show.html.haml_spec.rb
rename to spec/views/statuses/show.html.haml_spec.rb
index 93f0adb991c399082f7f34f8182117347352b8a0..dbda3b66550075a55769233a8f1538cbffe254dc 100644
--- a/spec/views/stream_entries/show.html.haml_spec.rb
+++ b/spec/views/statuses/show.html.haml_spec.rb
@@ -2,10 +2,9 @@
 
 require 'rails_helper'
 
-describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true do
+describe 'statuses/show.html.haml', without_verify_partial_doubles: true do
   before do
     double(:api_oembed_url => '')
-    double(:account_stream_entry_url => '')
     allow(view).to receive(:show_landing_strip?).and_return(true)
     allow(view).to receive(:site_title).and_return('example site')
     allow(view).to receive(:site_hostname).and_return('example.com')
@@ -23,9 +22,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d
     reply  =  Fabricate(:status, account: bob, thread: status, text: 'Hello Alice')
 
     assign(:status, status)
-    assign(:stream_entry, status.stream_entry)
     assign(:account, alice)
-    assign(:type, status.stream_entry.activity_type.downcase)
     assign(:descendant_threads, [])
 
     render
@@ -46,11 +43,9 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d
     comment =  Fabricate(:status, account: carl, thread: reply, text: 'Hello Bob')
 
     assign(:status, reply)
-    assign(:stream_entry, reply.stream_entry)
     assign(:account, alice)
-    assign(:type, reply.stream_entry.activity_type.downcase)
-    assign(:ancestors, reply.stream_entry.activity.ancestors(1, bob))
-    assign(:descendant_threads, [{ statuses: reply.stream_entry.activity.descendants(1) }])
+    assign(:ancestors, reply.ancestors(1, bob))
+    assign(:descendant_threads, [{ statuses: reply.descendants(1) }])
 
     render
 
@@ -71,9 +66,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d
     status  =  Fabricate(:status, account: alice, text: 'Hello World')
 
     assign(:status, status)
-    assign(:stream_entry, status.stream_entry)
     assign(:account, alice)
-    assign(:type, status.stream_entry.activity_type.downcase)
     assign(:descendant_threads, [])
 
     render
diff --git a/spec/workers/after_remote_follow_request_worker_spec.rb b/spec/workers/after_remote_follow_request_worker_spec.rb
deleted file mode 100644
index bd623cca508eb05a96cc4f9c37601abebf5dfa92..0000000000000000000000000000000000000000
--- a/spec/workers/after_remote_follow_request_worker_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe AfterRemoteFollowRequestWorker do
-  subject { described_class.new }
-  let(:follow_request) { Fabricate(:follow_request) }
-  describe 'perform' do
-    context 'when the follow_request does not exist' do
-      it 'catches a raise and returns true' do
-        allow(FollowService).to receive(:new)
-        result = subject.perform('aaa')
-
-        expect(result).to eq(true)
-        expect(FollowService).not_to have_received(:new)
-      end
-    end
-
-    context 'when the account cannot be updated' do
-      it 'returns nil and does not call service when account is nil' do
-        allow(FollowService).to receive(:new)
-        service = double(call: nil)
-        allow(FetchRemoteAccountService).to receive(:new).and_return(service)
-
-        result = subject.perform(follow_request.id)
-
-        expect(result).to be_nil
-        expect(FollowService).not_to have_received(:new)
-      end
-
-      it 'returns nil and does not call service when account is locked' do
-        allow(FollowService).to receive(:new)
-        service = double(call: double(locked?: true))
-        allow(FetchRemoteAccountService).to receive(:new).and_return(service)
-
-        result = subject.perform(follow_request.id)
-
-        expect(result).to be_nil
-        expect(FollowService).not_to have_received(:new)
-      end
-    end
-
-    context 'when the account is updated' do
-      it 'calls the follow service and destroys the follow' do
-        follow_service = double(call: nil)
-        allow(FollowService).to receive(:new).and_return(follow_service)
-        account = Fabricate(:account, locked: false)
-        service = double(call: account)
-        allow(FetchRemoteAccountService).to receive(:new).and_return(service)
-
-        result = subject.perform(follow_request.id)
-
-        expect(result).to be_nil
-        expect(follow_service).to have_received(:call).with(follow_request.account, account.acct)
-        expect { follow_request.reload }.to raise_error(ActiveRecord::RecordNotFound)
-      end
-    end
-  end
-end
diff --git a/spec/workers/after_remote_follow_worker_spec.rb b/spec/workers/after_remote_follow_worker_spec.rb
deleted file mode 100644
index d93c469f9d0505c221a1df086ba4de4c66eb9166..0000000000000000000000000000000000000000
--- a/spec/workers/after_remote_follow_worker_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe AfterRemoteFollowWorker do
-  subject { described_class.new }
-  let(:follow) { Fabricate(:follow) }
-  describe 'perform' do
-    context 'when the follow does not exist' do
-      it 'catches a raise and returns true' do
-        allow(FollowService).to receive(:new)
-        result = subject.perform('aaa')
-
-        expect(result).to eq(true)
-        expect(FollowService).not_to have_received(:new)
-      end
-    end
-
-    context 'when the account cannot be updated' do
-      it 'returns nil and does not call service when account is nil' do
-        allow(FollowService).to receive(:new)
-        service = double(call: nil)
-        allow(FetchRemoteAccountService).to receive(:new).and_return(service)
-
-        result = subject.perform(follow.id)
-
-        expect(result).to be_nil
-        expect(FollowService).not_to have_received(:new)
-      end
-
-      it 'returns nil and does not call service when account is not locked' do
-        allow(FollowService).to receive(:new)
-        service = double(call: double(locked?: false))
-        allow(FetchRemoteAccountService).to receive(:new).and_return(service)
-
-        result = subject.perform(follow.id)
-
-        expect(result).to be_nil
-        expect(FollowService).not_to have_received(:new)
-      end
-    end
-
-    context 'when the account is updated' do
-      it 'calls the follow service and destroys the follow' do
-        follow_service = double(call: nil)
-        allow(FollowService).to receive(:new).and_return(follow_service)
-        account = Fabricate(:account, locked: true)
-        service = double(call: account)
-        allow(FetchRemoteAccountService).to receive(:new).and_return(service)
-
-        result = subject.perform(follow.id)
-
-        expect(result).to be_nil
-        expect(follow_service).to have_received(:call).with(follow.account, account.acct)
-        expect { follow.reload }.to raise_error(ActiveRecord::RecordNotFound)
-      end
-    end
-  end
-end
diff --git a/spec/workers/domain_block_worker_spec.rb b/spec/workers/domain_block_worker_spec.rb
index c4138501ffc9848d5ee04dae25ee4e243acc578d..48b3e38c40cef21580a345439eeb5fbd294ca326 100644
--- a/spec/workers/domain_block_worker_spec.rb
+++ b/spec/workers/domain_block_worker_spec.rb
@@ -14,7 +14,7 @@ describe DomainBlockWorker do
       result = subject.perform(domain_block.id)
 
       expect(result).to be_nil
-      expect(service).to have_received(:call).with(domain_block)
+      expect(service).to have_received(:call).with(domain_block, false)
     end
 
     it 'calls domain block service for relevant domain block' do
diff --git a/spec/workers/pubsubhubbub/confirmation_worker_spec.rb b/spec/workers/pubsubhubbub/confirmation_worker_spec.rb
deleted file mode 100644
index 1eecdd2b5b622f944f35a51da8d6fb0fb5e9dade..0000000000000000000000000000000000000000
--- a/spec/workers/pubsubhubbub/confirmation_worker_spec.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe Pubsubhubbub::ConfirmationWorker do
-  include RoutingHelper
-
-  subject { described_class.new }
-
-  let!(:alice) { Fabricate(:account, username: 'alice') }
-  let!(:subscription) { Fabricate(:subscription, account: alice, callback_url: 'http://example.com/api', confirmed: false, expires_at: 3.days.from_now, secret: nil) }
-
-  describe 'perform' do
-    describe 'with subscribe mode' do
-      it 'confirms and updates subscription when challenge matches' do
-        stub_random_value
-        stub_request(:get, url_for_mode('subscribe'))
-          .with(headers: http_headers)
-          .to_return(status: 200, body: challenge_value, headers: {})
-
-        seconds = 10.days.seconds.to_i
-        subject.perform(subscription.id, 'subscribe', 'asdf', seconds)
-
-        subscription.reload
-        expect(subscription.secret).to eq 'asdf'
-        expect(subscription.confirmed).to eq true
-        expect(subscription.expires_at).to be_within(5).of(10.days.from_now)
-      end
-
-      it 'does not update subscription when challenge does not match' do
-        stub_random_value
-        stub_request(:get, url_for_mode('subscribe'))
-          .with(headers: http_headers)
-          .to_return(status: 200, body: 'wrong value', headers: {})
-
-        seconds = 10.days.seconds.to_i
-        subject.perform(subscription.id, 'subscribe', 'asdf', seconds)
-
-        subscription.reload
-        expect(subscription.secret).to be_blank
-        expect(subscription.confirmed).to eq false
-        expect(subscription.expires_at).to be_within(5).of(3.days.from_now)
-      end
-    end
-
-    describe 'with unsubscribe mode' do
-      it 'confirms and destroys subscription when challenge matches' do
-        stub_random_value
-        stub_request(:get, url_for_mode('unsubscribe'))
-          .with(headers: http_headers)
-          .to_return(status: 200, body: challenge_value, headers: {})
-
-        seconds = 10.days.seconds.to_i
-        subject.perform(subscription.id, 'unsubscribe', 'asdf', seconds)
-
-        expect { subscription.reload }.to raise_error(ActiveRecord::RecordNotFound)
-      end
-
-      it 'does not destroy subscription when challenge does not match' do
-        stub_random_value
-        stub_request(:get, url_for_mode('unsubscribe'))
-          .with(headers: http_headers)
-          .to_return(status: 200, body: 'wrong value', headers: {})
-
-        seconds = 10.days.seconds.to_i
-        subject.perform(subscription.id, 'unsubscribe', 'asdf', seconds)
-
-        expect { subscription.reload }.not_to raise_error
-      end
-    end
-  end
-
-  def url_for_mode(mode)
-    "http://example.com/api?hub.challenge=#{challenge_value}&hub.lease_seconds=863999&hub.mode=#{mode}&hub.topic=https://#{Rails.configuration.x.local_domain}/users/alice.atom"
-  end
-
-  def stub_random_value
-    allow(SecureRandom).to receive(:hex).and_return(challenge_value)
-  end
-
-  def challenge_value
-    '1a2s3d4f'
-  end
-
-  def http_headers
-    { 'Connection' => 'close', 'Host' => 'example.com' }
-  end
-end
diff --git a/spec/workers/pubsubhubbub/delivery_worker_spec.rb b/spec/workers/pubsubhubbub/delivery_worker_spec.rb
deleted file mode 100644
index c0e0d51866f4926f2e79f7e21bf041d117b46115..0000000000000000000000000000000000000000
--- a/spec/workers/pubsubhubbub/delivery_worker_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe Pubsubhubbub::DeliveryWorker do
-  include RoutingHelper
-  subject { described_class.new }
-
-  let(:payload) { 'test' }
-
-  describe 'perform' do
-    it 'raises when subscription does not exist' do
-      expect { subject.perform 123, payload }.to raise_error(ActiveRecord::RecordNotFound)
-    end
-
-    it 'does not attempt to deliver when domain blocked' do
-      _domain_block = Fabricate(:domain_block, domain: 'example.com', severity: :suspend)
-      subscription = Fabricate(:subscription, callback_url: 'https://example.com/api', last_successful_delivery_at: 2.days.ago)
-
-      subject.perform(subscription.id, payload)
-
-      expect(subscription.reload.last_successful_delivery_at).to be_within(2).of(2.days.ago)
-    end
-
-    it 'raises when request fails' do
-      subscription = Fabricate(:subscription)
-
-      stub_request_to_respond_with(subscription, 500)
-      expect { subject.perform(subscription.id, payload) }.to raise_error Mastodon::UnexpectedResponseError
-    end
-
-    it 'updates subscriptions when delivery succeeds' do
-      subscription = Fabricate(:subscription)
-
-      stub_request_to_respond_with(subscription, 200)
-      subject.perform(subscription.id, payload)
-
-      expect(subscription.reload.last_successful_delivery_at).to be_within(2).of(Time.now.utc)
-    end
-
-    it 'updates subscription without a secret when delivery succeeds' do
-      subscription = Fabricate(:subscription, secret: nil)
-
-      stub_request_to_respond_with(subscription, 200)
-      subject.perform(subscription.id, payload)
-
-      expect(subscription.reload.last_successful_delivery_at).to be_within(2).of(Time.now.utc)
-    end
-
-    def stub_request_to_respond_with(subscription, code)
-      stub_request(:post, 'http://example.com/callback')
-        .with(body: payload, headers: expected_headers(subscription))
-        .to_return(status: code, body: '', headers: {})
-    end
-
-    def expected_headers(subscription)
-      {
-        'Connection' => 'close',
-        'Content-Type' => 'application/atom+xml',
-        'Host' => 'example.com',
-        'Link' => "<https://#{Rails.configuration.x.local_domain}/api/push>; rel=\"hub\", <https://#{Rails.configuration.x.local_domain}/users/#{subscription.account.username}.atom>; rel=\"self\"",
-      }.tap do |basic|
-        known_digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), subscription.secret.to_s, payload)
-        basic.merge('X-Hub-Signature' => "sha1=#{known_digest}") if subscription.secret?
-      end
-    end
-  end
-end
diff --git a/spec/workers/pubsubhubbub/distribution_worker_spec.rb b/spec/workers/pubsubhubbub/distribution_worker_spec.rb
deleted file mode 100644
index 5844850795fcf76da55b8fca4f569a106a285a77..0000000000000000000000000000000000000000
--- a/spec/workers/pubsubhubbub/distribution_worker_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'rails_helper'
-
-describe Pubsubhubbub::DistributionWorker do
-  subject { Pubsubhubbub::DistributionWorker.new }
-
-  let!(:alice) { Fabricate(:account, username: 'alice') }
-  let!(:bob) { Fabricate(:account, username: 'bob', domain: 'example2.com') }
-  let!(:anonymous_subscription) { Fabricate(:subscription, account: alice, callback_url: 'http://example1.com', confirmed: true, lease_seconds: 3600) }
-  let!(:subscription_with_follower) { Fabricate(:subscription, account: alice, callback_url: 'http://example2.com', confirmed: true, lease_seconds: 3600) }
-
-  before do
-    bob.follow!(alice)
-  end
-
-  describe 'with public status' do
-    let(:status) { Fabricate(:status, account: alice, text: 'Hello', visibility: :public) }
-
-    it 'delivers payload to all subscriptions' do
-      allow(Pubsubhubbub::DeliveryWorker).to receive(:push_bulk)
-      subject.perform(status.stream_entry.id)
-      expect(Pubsubhubbub::DeliveryWorker).to have_received(:push_bulk).with([anonymous_subscription.id, subscription_with_follower.id])
-    end
-  end
-
-  context 'when OStatus privacy is not used' do
-    describe 'with private status' do
-      let(:status) { Fabricate(:status, account: alice, text: 'Hello', visibility: :private) }
-
-      it 'does not deliver anything' do
-        allow(Pubsubhubbub::DeliveryWorker).to receive(:push_bulk)
-        subject.perform(status.stream_entry.id)
-        expect(Pubsubhubbub::DeliveryWorker).to_not have_received(:push_bulk)
-      end
-    end
-
-    describe 'with direct status' do
-      let(:status) { Fabricate(:status, account: alice, text: 'Hello', visibility: :direct) }
-
-      it 'does not deliver payload' do
-        allow(Pubsubhubbub::DeliveryWorker).to receive(:push_bulk)
-        subject.perform(status.stream_entry.id)
-        expect(Pubsubhubbub::DeliveryWorker).to_not have_received(:push_bulk)
-      end
-    end
-  end
-end
diff --git a/spec/workers/scheduler/subscriptions_scheduler_spec.rb b/spec/workers/scheduler/subscriptions_scheduler_spec.rb
deleted file mode 100644
index a7d1046de614855c1dd2414e975ad4cd5abd398b..0000000000000000000000000000000000000000
--- a/spec/workers/scheduler/subscriptions_scheduler_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'rails_helper'
-
-describe Scheduler::SubscriptionsScheduler do
-  subject { Scheduler::SubscriptionsScheduler.new }
-
-  let!(:expiring_account1) { Fabricate(:account, subscription_expires_at: 20.minutes.from_now, domain: 'example.com', followers_count: 1, hub_url: 'http://hub.example.com') }
-  let!(:expiring_account2) { Fabricate(:account, subscription_expires_at: 4.hours.from_now, domain: 'example.org', followers_count: 1, hub_url: 'http://hub.example.org') }
-
-  before do
-    stub_request(:post, 'http://hub.example.com/').to_return(status: 202)
-    stub_request(:post, 'http://hub.example.org/').to_return(status: 202)
-  end
-
-  it 're-subscribes for all expiring accounts' do
-    subject.perform
-    expect(a_request(:post, 'http://hub.example.com/')).to have_been_made.once
-    expect(a_request(:post, 'http://hub.example.org/')).to have_been_made.once
-  end
-end
diff --git a/streaming/index.js b/streaming/index.js
index 68e19621aed813d12e3e5e3b025823f746bd25db..f743f574451e233ed7250aff0543db5966f9a332 100644
--- a/streaming/index.js
+++ b/streaming/index.js
@@ -12,6 +12,7 @@ const uuid = require('uuid');
 const fs = require('fs');
 
 const env = process.env.NODE_ENV || 'development';
+const alwaysRequireAuth = process.env.WHITELIST_MODE === 'true' || process.env.AUTHORIZED_FETCH === 'true';
 
 dotenv.config({
   path: env === 'production' ? '.env.production' : '.env',
@@ -271,7 +272,7 @@ const startWorker = (workerId) => {
 
   const wsVerifyClient = (info, cb) => {
     const location = url.parse(info.req.url, true);
-    const authRequired = !PUBLIC_STREAMS.some(stream => stream === location.query.stream);
+    const authRequired = alwaysRequireAuth || !PUBLIC_STREAMS.some(stream => stream === location.query.stream);
     const allowedScopes = [];
 
     if (authRequired) {
@@ -306,7 +307,7 @@ const startWorker = (workerId) => {
       return;
     }
 
-    const authRequired = !PUBLIC_ENDPOINTS.some(endpoint => endpoint === req.path);
+    const authRequired = alwaysRequireAuth || !PUBLIC_ENDPOINTS.some(endpoint => endpoint === req.path);
     const allowedScopes = [];
 
     if (authRequired) {
diff --git a/yarn.lock b/yarn.lock
index b9b47c18552dd931b8e0328ade104b3a9c157853..74323bf5bb7f2685e91ff79d1300f8a0d5a84290 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,10 +2,10 @@
 # yarn lockfile v1
 
 
-"@babel/code-frame@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
-  integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d"
+  integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==
   dependencies:
     "@babel/highlight" "^7.0.0"
 
@@ -60,7 +60,7 @@
     source-map "^0.5.0"
     trim-right "^1.0.1"
 
-"@babel/generator@^7.2.2", "@babel/generator@^7.4.4":
+"@babel/generator@^7.4.4":
   version "7.4.4"
   resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041"
   integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==
@@ -71,6 +71,17 @@
     source-map "^0.5.0"
     trim-right "^1.0.1"
 
+"@babel/generator@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.5.5.tgz#873a7f936a3c89491b43536d12245b626664e3cf"
+  integrity sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==
+  dependencies:
+    "@babel/types" "^7.5.5"
+    jsesc "^2.5.1"
+    lodash "^4.17.13"
+    source-map "^0.5.0"
+    trim-right "^1.0.1"
+
 "@babel/helper-annotate-as-pure@^7.0.0":
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32"
@@ -103,26 +114,26 @@
     "@babel/traverse" "^7.4.4"
     "@babel/types" "^7.4.4"
 
-"@babel/helper-create-class-features-plugin@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz#fc3d690af6554cc9efc607364a82d48f58736dba"
-  integrity sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA==
+"@babel/helper-create-class-features-plugin@^7.4.4", "@babel/helper-create-class-features-plugin@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.5.5.tgz#401f302c8ddbc0edd36f7c6b2887d8fa1122e5a4"
+  integrity sha512-ZsxkyYiRA7Bg+ZTRpPvB6AbOFKTFFK4LrvTet8lInm0V468MWCaSYJE+I7v2z2r8KNLtYiV+K5kTCnR7dvyZjg==
   dependencies:
     "@babel/helper-function-name" "^7.1.0"
-    "@babel/helper-member-expression-to-functions" "^7.0.0"
+    "@babel/helper-member-expression-to-functions" "^7.5.5"
     "@babel/helper-optimise-call-expression" "^7.0.0"
     "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-replace-supers" "^7.4.4"
+    "@babel/helper-replace-supers" "^7.5.5"
     "@babel/helper-split-export-declaration" "^7.4.4"
 
-"@babel/helper-define-map@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz#6969d1f570b46bdc900d1eba8e5d59c48ba2c12a"
-  integrity sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==
+"@babel/helper-define-map@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz#3dec32c2046f37e09b28c93eb0b103fd2a25d369"
+  integrity sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==
   dependencies:
     "@babel/helper-function-name" "^7.1.0"
-    "@babel/types" "^7.4.4"
-    lodash "^4.17.11"
+    "@babel/types" "^7.5.5"
+    lodash "^4.17.13"
 
 "@babel/helper-explode-assignable-expression@^7.1.0":
   version "7.1.0"
@@ -155,12 +166,12 @@
   dependencies:
     "@babel/types" "^7.4.4"
 
-"@babel/helper-member-expression-to-functions@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz#8cd14b0a0df7ff00f009e7d7a436945f47c7a16f"
-  integrity sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==
+"@babel/helper-member-expression-to-functions@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz#1fb5b8ec4453a93c439ee9fe3aeea4a84b76b590"
+  integrity sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==
   dependencies:
-    "@babel/types" "^7.0.0"
+    "@babel/types" "^7.5.5"
 
 "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.0.0-beta.49":
   version "7.0.0"
@@ -211,15 +222,15 @@
     "@babel/traverse" "^7.1.0"
     "@babel/types" "^7.0.0"
 
-"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz#aee41783ebe4f2d3ab3ae775e1cc6f1a90cefa27"
-  integrity sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==
+"@babel/helper-replace-supers@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz#f84ce43df031222d2bad068d2626cb5799c34bc2"
+  integrity sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==
   dependencies:
-    "@babel/helper-member-expression-to-functions" "^7.0.0"
+    "@babel/helper-member-expression-to-functions" "^7.5.5"
     "@babel/helper-optimise-call-expression" "^7.0.0"
-    "@babel/traverse" "^7.4.4"
-    "@babel/types" "^7.4.4"
+    "@babel/traverse" "^7.5.5"
+    "@babel/types" "^7.5.5"
 
 "@babel/helper-simple-access@^7.1.0":
   version "7.1.0"
@@ -229,7 +240,7 @@
     "@babel/template" "^7.1.0"
     "@babel/types" "^7.0.0"
 
-"@babel/helper-split-export-declaration@^7.0.0", "@babel/helper-split-export-declaration@^7.4.4":
+"@babel/helper-split-export-declaration@^7.4.4":
   version "7.4.4"
   resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677"
   integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==
@@ -273,20 +284,10 @@
     esutils "^2.0.2"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.0.0":
-  version "7.2.3"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.2.3.tgz#32f5df65744b70888d17872ec106b02434ba1489"
-  integrity sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==
-
-"@babel/parser@^7.1.0", "@babel/parser@^7.3.4":
-  version "7.3.4"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c"
-  integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==
-
-"@babel/parser@^7.2.2", "@babel/parser@^7.2.3", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5":
-  version "7.4.5"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872"
-  integrity sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==
+"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5", "@babel/parser@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.5.tgz#02f077ac8817d3df4a832ef59de67565e71cca4b"
+  integrity sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==
 
 "@babel/plugin-proposal-async-generator-functions@^7.2.0":
   version "7.2.0"
@@ -297,12 +298,12 @@
     "@babel/helper-remap-async-to-generator" "^7.1.0"
     "@babel/plugin-syntax-async-generators" "^7.2.0"
 
-"@babel/plugin-proposal-class-properties@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.4.tgz#93a6486eed86d53452ab9bab35e368e9461198ce"
-  integrity sha512-WjKTI8g8d5w1Bc9zgwSz2nfrsNQsXcCf9J9cdCvrJV6RF56yztwm4TmJC0MgJ9tvwO9gUA/mcYe89bLdGfiXFg==
+"@babel/plugin-proposal-class-properties@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.5.tgz#a974cfae1e37c3110e71f3c6a2e48b8e71958cd4"
+  integrity sha512-AF79FsnWFxjlaosgdi421vmYG6/jg79bVD0dpD44QdgobzHKuLZ6S3vl8la9qIeSwGi8i1fS0O1mfuDAAdo1/A==
   dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.4.4"
+    "@babel/helper-create-class-features-plugin" "^7.5.5"
     "@babel/helper-plugin-utils" "^7.0.0"
 
 "@babel/plugin-proposal-decorators@^7.4.4":
@@ -314,6 +315,14 @@
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/plugin-syntax-decorators" "^7.2.0"
 
+"@babel/plugin-proposal-dynamic-import@^7.5.0":
+  version "7.5.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz#e532202db4838723691b10a67b8ce509e397c506"
+  integrity sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@babel/plugin-syntax-dynamic-import" "^7.2.0"
+
 "@babel/plugin-proposal-json-strings@^7.2.0":
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317"
@@ -322,10 +331,10 @@
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/plugin-syntax-json-strings" "^7.2.0"
 
-"@babel/plugin-proposal-object-rest-spread@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.4.tgz#1ef173fcf24b3e2df92a678f027673b55e7e3005"
-  integrity sha512-dMBG6cSPBbHeEBdFXeQ2QLc5gUpg4Vkaz8octD4aoW/ISO+jBOcsuxYL7bsb5WSu8RLP6boxrBIALEHgoHtO9g==
+"@babel/plugin-proposal-object-rest-spread@^7.4.4", "@babel/plugin-proposal-object-rest-spread@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz#61939744f71ba76a3ae46b5eea18a54c16d22e58"
+  integrity sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/plugin-syntax-object-rest-spread" "^7.2.0"
@@ -403,10 +412,10 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
-"@babel/plugin-transform-async-to-generator@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz#a3f1d01f2f21cadab20b33a82133116f14fb5894"
-  integrity sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA==
+"@babel/plugin-transform-async-to-generator@^7.5.0":
+  version "7.5.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz#89a3848a0166623b5bc481164b5936ab947e887e"
+  integrity sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==
   dependencies:
     "@babel/helper-module-imports" "^7.0.0"
     "@babel/helper-plugin-utils" "^7.0.0"
@@ -419,25 +428,25 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
-"@babel/plugin-transform-block-scoping@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz#c13279fabf6b916661531841a23c4b7dae29646d"
-  integrity sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA==
+"@babel/plugin-transform-block-scoping@^7.6.0":
+  version "7.6.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.0.tgz#c49e21228c4bbd4068a35667e6d951c75439b1dc"
+  integrity sha512-tIt4E23+kw6TgL/edACZwP1OUKrjOTyMrFMLoT5IOFrfMRabCgekjqFd5o6PaAMildBu46oFkekIdMuGkkPEpA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
-    lodash "^4.17.11"
+    lodash "^4.17.13"
 
-"@babel/plugin-transform-classes@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz#0ce4094cdafd709721076d3b9c38ad31ca715eb6"
-  integrity sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw==
+"@babel/plugin-transform-classes@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz#d094299d9bd680a14a2a0edae38305ad60fb4de9"
+  integrity sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==
   dependencies:
     "@babel/helper-annotate-as-pure" "^7.0.0"
-    "@babel/helper-define-map" "^7.4.4"
+    "@babel/helper-define-map" "^7.5.5"
     "@babel/helper-function-name" "^7.1.0"
     "@babel/helper-optimise-call-expression" "^7.0.0"
     "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-replace-supers" "^7.4.4"
+    "@babel/helper-replace-supers" "^7.5.5"
     "@babel/helper-split-export-declaration" "^7.4.4"
     globals "^11.1.0"
 
@@ -448,10 +457,10 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
-"@babel/plugin-transform-destructuring@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.4.tgz#9d964717829cc9e4b601fc82a26a71a4d8faf20f"
-  integrity sha512-/aOx+nW0w8eHiEHm+BTERB2oJn5D127iye/SUQl7NjHy0lf+j7h4MKMMSOwdazGq9OxgiNADncE+SRJkCxjZpQ==
+"@babel/plugin-transform-destructuring@^7.6.0":
+  version "7.6.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz#44bbe08b57f4480094d57d9ffbcd96d309075ba6"
+  integrity sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
@@ -464,10 +473,10 @@
     "@babel/helper-regex" "^7.4.4"
     regexpu-core "^4.5.4"
 
-"@babel/plugin-transform-duplicate-keys@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz#d952c4930f312a4dbfff18f0b2914e60c35530b3"
-  integrity sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==
+"@babel/plugin-transform-duplicate-keys@^7.5.0":
+  version "7.5.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz#c5dbf5106bf84cdf691222c0974c12b1df931853"
+  integrity sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
@@ -508,30 +517,33 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
-"@babel/plugin-transform-modules-amd@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz#82a9bce45b95441f617a24011dc89d12da7f4ee6"
-  integrity sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==
+"@babel/plugin-transform-modules-amd@^7.5.0":
+  version "7.5.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz#ef00435d46da0a5961aa728a1d2ecff063e4fb91"
+  integrity sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==
   dependencies:
     "@babel/helper-module-transforms" "^7.1.0"
     "@babel/helper-plugin-utils" "^7.0.0"
+    babel-plugin-dynamic-import-node "^2.3.0"
 
-"@babel/plugin-transform-modules-commonjs@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz#0bef4713d30f1d78c2e59b3d6db40e60192cac1e"
-  integrity sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw==
+"@babel/plugin-transform-modules-commonjs@^7.6.0":
+  version "7.6.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.6.0.tgz#39dfe957de4420445f1fcf88b68a2e4aa4515486"
+  integrity sha512-Ma93Ix95PNSEngqomy5LSBMAQvYKVe3dy+JlVJSHEXZR5ASL9lQBedMiCyVtmTLraIDVRE3ZjTZvmXXD2Ozw3g==
   dependencies:
     "@babel/helper-module-transforms" "^7.4.4"
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/helper-simple-access" "^7.1.0"
+    babel-plugin-dynamic-import-node "^2.3.0"
 
-"@babel/plugin-transform-modules-systemjs@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.4.tgz#dc83c5665b07d6c2a7b224c00ac63659ea36a405"
-  integrity sha512-MSiModfILQc3/oqnG7NrP1jHaSPryO6tA2kOMmAQApz5dayPxWiHqmq4sWH2xF5LcQK56LlbKByCd8Aah/OIkQ==
+"@babel/plugin-transform-modules-systemjs@^7.5.0":
+  version "7.5.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz#e75266a13ef94202db2a0620977756f51d52d249"
+  integrity sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==
   dependencies:
     "@babel/helper-hoist-variables" "^7.4.4"
     "@babel/helper-plugin-utils" "^7.0.0"
+    babel-plugin-dynamic-import-node "^2.3.0"
 
 "@babel/plugin-transform-modules-umd@^7.2.0":
   version "7.2.0"
@@ -541,12 +553,12 @@
     "@babel/helper-module-transforms" "^7.1.0"
     "@babel/helper-plugin-utils" "^7.0.0"
 
-"@babel/plugin-transform-named-capturing-groups-regex@^7.4.5":
-  version "7.4.5"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz#9d269fd28a370258199b4294736813a60bbdd106"
-  integrity sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg==
+"@babel/plugin-transform-named-capturing-groups-regex@^7.6.0":
+  version "7.6.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.0.tgz#1e6e663097813bb4f53d42df0750cf28ad3bb3f1"
+  integrity sha512-jem7uytlmrRl3iCAuQyw8BpB4c4LWvSpvIeXKpMb+7j84lkx4m4mYr5ErAcmN5KM7B6BqrAvRGjBIbbzqCczew==
   dependencies:
-    regexp-tree "^0.1.6"
+    regexp-tree "^0.1.13"
 
 "@babel/plugin-transform-new-target@^7.4.4":
   version "7.4.4"
@@ -555,13 +567,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
-"@babel/plugin-transform-object-super@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz#b35d4c10f56bab5d650047dad0f1d8e8814b6598"
-  integrity sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==
+"@babel/plugin-transform-object-super@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz#c70021df834073c65eb613b8679cc4a381d1a9f9"
+  integrity sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-replace-supers" "^7.1.0"
+    "@babel/helper-replace-supers" "^7.5.5"
 
 "@babel/plugin-transform-parameters@^7.4.4":
   version "7.4.4"
@@ -602,10 +614,10 @@
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/plugin-syntax-jsx" "^7.2.0"
 
-"@babel/plugin-transform-react-jsx-source@^7.0.0", "@babel/plugin-transform-react-jsx-source@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.2.0.tgz#20c8c60f0140f5dd3cd63418d452801cf3f7180f"
-  integrity sha512-A32OkKTp4i5U6aE88GwwcuV4HAprUgHcTq0sSafLxjr6AW0QahrCRCjxogkbbcdtpbXkuTOlgpjophCxb6sh5g==
+"@babel/plugin-transform-react-jsx-source@^7.0.0", "@babel/plugin-transform-react-jsx-source@^7.5.0":
+  version "7.5.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.5.0.tgz#583b10c49cf057e237085bcbd8cc960bd83bd96b"
+  integrity sha512-58Q+Jsy4IDCZx7kqEZuSDdam/1oW8OdDX8f+Loo6xyxdfg1yF0GE2XNJQSTZCaMol93+FBzpWiPEwtbMloAcPg==
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/plugin-syntax-jsx" "^7.2.0"
@@ -633,10 +645,10 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
-"@babel/plugin-transform-runtime@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.4.4.tgz#a50f5d16e9c3a4ac18a1a9f9803c107c380bce08"
-  integrity sha512-aMVojEjPszvau3NRg+TIH14ynZLvPewH4xhlCW1w6A3rkxTS1m4uwzRclYR9oS+rl/dr+kT+pzbfHuAWP/lc7Q==
+"@babel/plugin-transform-runtime@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.5.5.tgz#a6331afbfc59189d2135b2e09474457a8e3d28bc"
+  integrity sha512-6Xmeidsun5rkwnGfMOp6/z9nSzWpHFNVr2Jx7kwoq4mVatQfQx5S56drBgEHF+XQbKOdIaOiMIINvp/kAwMN+w==
   dependencies:
     "@babel/helper-module-imports" "^7.0.0"
     "@babel/helper-plugin-utils" "^7.0.0"
@@ -689,43 +701,45 @@
     "@babel/helper-regex" "^7.4.4"
     regexpu-core "^4.5.4"
 
-"@babel/preset-env@^7.4.5":
-  version "7.4.5"
-  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.5.tgz#2fad7f62983d5af563b5f3139242755884998a58"
-  integrity sha512-f2yNVXM+FsR5V8UwcFeIHzHWgnhXg3NpRmy0ADvALpnhB0SLbCvrCRr4BLOUYbQNLS+Z0Yer46x9dJXpXewI7w==
+"@babel/preset-env@^7.6.0":
+  version "7.6.0"
+  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.0.tgz#aae4141c506100bb2bfaa4ac2a5c12b395619e50"
+  integrity sha512-1efzxFv/TcPsNXlRhMzRnkBFMeIqBBgzwmZwlFDw5Ubj0AGLeufxugirwZmkkX/ayi3owsSqoQ4fw8LkfK9SYg==
   dependencies:
     "@babel/helper-module-imports" "^7.0.0"
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/plugin-proposal-async-generator-functions" "^7.2.0"
+    "@babel/plugin-proposal-dynamic-import" "^7.5.0"
     "@babel/plugin-proposal-json-strings" "^7.2.0"
-    "@babel/plugin-proposal-object-rest-spread" "^7.4.4"
+    "@babel/plugin-proposal-object-rest-spread" "^7.5.5"
     "@babel/plugin-proposal-optional-catch-binding" "^7.2.0"
     "@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
     "@babel/plugin-syntax-async-generators" "^7.2.0"
+    "@babel/plugin-syntax-dynamic-import" "^7.2.0"
     "@babel/plugin-syntax-json-strings" "^7.2.0"
     "@babel/plugin-syntax-object-rest-spread" "^7.2.0"
     "@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
     "@babel/plugin-transform-arrow-functions" "^7.2.0"
-    "@babel/plugin-transform-async-to-generator" "^7.4.4"
+    "@babel/plugin-transform-async-to-generator" "^7.5.0"
     "@babel/plugin-transform-block-scoped-functions" "^7.2.0"
-    "@babel/plugin-transform-block-scoping" "^7.4.4"
-    "@babel/plugin-transform-classes" "^7.4.4"
+    "@babel/plugin-transform-block-scoping" "^7.6.0"
+    "@babel/plugin-transform-classes" "^7.5.5"
     "@babel/plugin-transform-computed-properties" "^7.2.0"
-    "@babel/plugin-transform-destructuring" "^7.4.4"
+    "@babel/plugin-transform-destructuring" "^7.6.0"
     "@babel/plugin-transform-dotall-regex" "^7.4.4"
-    "@babel/plugin-transform-duplicate-keys" "^7.2.0"
+    "@babel/plugin-transform-duplicate-keys" "^7.5.0"
     "@babel/plugin-transform-exponentiation-operator" "^7.2.0"
     "@babel/plugin-transform-for-of" "^7.4.4"
     "@babel/plugin-transform-function-name" "^7.4.4"
     "@babel/plugin-transform-literals" "^7.2.0"
     "@babel/plugin-transform-member-expression-literals" "^7.2.0"
-    "@babel/plugin-transform-modules-amd" "^7.2.0"
-    "@babel/plugin-transform-modules-commonjs" "^7.4.4"
-    "@babel/plugin-transform-modules-systemjs" "^7.4.4"
+    "@babel/plugin-transform-modules-amd" "^7.5.0"
+    "@babel/plugin-transform-modules-commonjs" "^7.6.0"
+    "@babel/plugin-transform-modules-systemjs" "^7.5.0"
     "@babel/plugin-transform-modules-umd" "^7.2.0"
-    "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.5"
+    "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.0"
     "@babel/plugin-transform-new-target" "^7.4.4"
-    "@babel/plugin-transform-object-super" "^7.2.0"
+    "@babel/plugin-transform-object-super" "^7.5.5"
     "@babel/plugin-transform-parameters" "^7.4.4"
     "@babel/plugin-transform-property-literals" "^7.2.0"
     "@babel/plugin-transform-regenerator" "^7.4.5"
@@ -736,7 +750,7 @@
     "@babel/plugin-transform-template-literals" "^7.4.4"
     "@babel/plugin-transform-typeof-symbol" "^7.2.0"
     "@babel/plugin-transform-unicode-regex" "^7.4.4"
-    "@babel/types" "^7.4.4"
+    "@babel/types" "^7.6.0"
     browserslist "^4.6.0"
     core-js-compat "^3.1.1"
     invariant "^2.2.2"
@@ -768,10 +782,10 @@
   dependencies:
     regenerator-runtime "^0.12.0"
 
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5":
-  version "7.4.5"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12"
-  integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
+  integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
   dependencies:
     regenerator-runtime "^0.13.2"
 
@@ -793,82 +807,34 @@
     "@babel/parser" "^7.4.4"
     "@babel/types" "^7.4.4"
 
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.5":
-  version "7.2.3"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8"
-  integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.3.4", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5", "@babel/traverse@^7.5.5":
+  version "7.5.5"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.5.5.tgz#f664f8f368ed32988cd648da9f72d5ca70f165bb"
+  integrity sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==
   dependencies:
-    "@babel/code-frame" "^7.0.0"
-    "@babel/generator" "^7.2.2"
-    "@babel/helper-function-name" "^7.1.0"
-    "@babel/helper-split-export-declaration" "^7.0.0"
-    "@babel/parser" "^7.2.3"
-    "@babel/types" "^7.2.2"
-    debug "^4.1.0"
-    globals "^11.1.0"
-    lodash "^4.17.10"
-
-"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5":
-  version "7.4.5"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.5.tgz#4e92d1728fd2f1897dafdd321efbff92156c3216"
-  integrity sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==
-  dependencies:
-    "@babel/code-frame" "^7.0.0"
-    "@babel/generator" "^7.4.4"
+    "@babel/code-frame" "^7.5.5"
+    "@babel/generator" "^7.5.5"
     "@babel/helper-function-name" "^7.1.0"
     "@babel/helper-split-export-declaration" "^7.4.4"
-    "@babel/parser" "^7.4.5"
-    "@babel/types" "^7.4.4"
-    debug "^4.1.0"
-    globals "^11.1.0"
-    lodash "^4.17.11"
-
-"@babel/traverse@^7.3.4":
-  version "7.3.4"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.3.4.tgz#1330aab72234f8dea091b08c4f8b9d05c7119e06"
-  integrity sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==
-  dependencies:
-    "@babel/code-frame" "^7.0.0"
-    "@babel/generator" "^7.3.4"
-    "@babel/helper-function-name" "^7.1.0"
-    "@babel/helper-split-export-declaration" "^7.0.0"
-    "@babel/parser" "^7.3.4"
-    "@babel/types" "^7.3.4"
+    "@babel/parser" "^7.5.5"
+    "@babel/types" "^7.5.5"
     debug "^4.1.0"
     globals "^11.1.0"
-    lodash "^4.17.11"
-
-"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0"
-  integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==
-  dependencies:
-    esutils "^2.0.2"
-    lodash "^4.17.11"
-    to-fast-properties "^2.0.0"
+    lodash "^4.17.13"
 
-"@babel/types@^7.0.0-beta.49":
-  version "7.2.2"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.2.tgz#44e10fc24e33af524488b716cdaee5360ea8ed1e"
-  integrity sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==
+"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.3.4", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.6.0":
+  version "7.6.1"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.1.tgz#53abf3308add3ac2a2884d539151c57c4b3ac648"
+  integrity sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==
   dependencies:
     esutils "^2.0.2"
-    lodash "^4.17.10"
+    lodash "^4.17.13"
     to-fast-properties "^2.0.0"
 
-"@babel/types@^7.3.0", "@babel/types@^7.3.4":
-  version "7.3.4"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.4.tgz#bf482eaeaffb367a28abbf9357a94963235d90ed"
-  integrity sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==
-  dependencies:
-    esutils "^2.0.2"
-    lodash "^4.17.11"
-    to-fast-properties "^2.0.0"
-
-"@clusterws/cws@^0.14.0":
-  version "0.14.0"
-  resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.14.0.tgz#242824b6884454001340222a836db6f6c5e62bfb"
-  integrity sha512-knZj3KZNHIAGsX7TUc/0Q5gcx2bKMMcTPsAOZomLKdK5a4o/umKFlttWRH84Yr1nVlQy+UMO23qfDR8gRZ/4cw==
+"@clusterws/cws@^0.15.0":
+  version "0.15.0"
+  resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.15.0.tgz#1d585927252d1cd92e676c952fa6d69df14a0d07"
+  integrity sha512-41QpCngw86n41hIdU5Nx2QJmmxZuA9FPtDkjONrYpk27L7HjL1kj6J5oWEjbr14yXLfigZit3VY+oACDCGbiHw==
 
 "@cnakazawa/watch@^1.0.3":
   version "1.0.3"
@@ -934,67 +900,77 @@
     chalk "^2.0.1"
     slash "^2.0.0"
 
-"@jest/core@^24.8.0":
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.8.0.tgz#fbbdcd42a41d0d39cddbc9f520c8bab0c33eed5b"
-  integrity sha512-R9rhAJwCBQzaRnrRgAdVfnglUuATXdwTRsYqs6NMdVcAl5euG8LtWDe+fVkN27YfKVBW61IojVsXKaOmSnqd/A==
+"@jest/console@^24.9.0":
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0"
+  integrity sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==
+  dependencies:
+    "@jest/source-map" "^24.9.0"
+    chalk "^2.0.1"
+    slash "^2.0.0"
+
+"@jest/core@^24.9.0":
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.9.0.tgz#2ceccd0b93181f9c4850e74f2a9ad43d351369c4"
+  integrity sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==
   dependencies:
     "@jest/console" "^24.7.1"
-    "@jest/reporters" "^24.8.0"
-    "@jest/test-result" "^24.8.0"
-    "@jest/transform" "^24.8.0"
-    "@jest/types" "^24.8.0"
+    "@jest/reporters" "^24.9.0"
+    "@jest/test-result" "^24.9.0"
+    "@jest/transform" "^24.9.0"
+    "@jest/types" "^24.9.0"
     ansi-escapes "^3.0.0"
     chalk "^2.0.1"
     exit "^0.1.2"
     graceful-fs "^4.1.15"
-    jest-changed-files "^24.8.0"
-    jest-config "^24.8.0"
-    jest-haste-map "^24.8.0"
-    jest-message-util "^24.8.0"
+    jest-changed-files "^24.9.0"
+    jest-config "^24.9.0"
+    jest-haste-map "^24.9.0"
+    jest-message-util "^24.9.0"
     jest-regex-util "^24.3.0"
-    jest-resolve-dependencies "^24.8.0"
-    jest-runner "^24.8.0"
-    jest-runtime "^24.8.0"
-    jest-snapshot "^24.8.0"
-    jest-util "^24.8.0"
-    jest-validate "^24.8.0"
-    jest-watcher "^24.8.0"
+    jest-resolve "^24.9.0"
+    jest-resolve-dependencies "^24.9.0"
+    jest-runner "^24.9.0"
+    jest-runtime "^24.9.0"
+    jest-snapshot "^24.9.0"
+    jest-util "^24.9.0"
+    jest-validate "^24.9.0"
+    jest-watcher "^24.9.0"
     micromatch "^3.1.10"
     p-each-series "^1.0.0"
-    pirates "^4.0.1"
     realpath-native "^1.1.0"
     rimraf "^2.5.4"
+    slash "^2.0.0"
     strip-ansi "^5.0.0"
 
-"@jest/environment@^24.8.0":
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.8.0.tgz#0342261383c776bdd652168f68065ef144af0eac"
-  integrity sha512-vlGt2HLg7qM+vtBrSkjDxk9K0YtRBi7HfRFaDxoRtyi+DyVChzhF20duvpdAnKVBV6W5tym8jm0U9EfXbDk1tw==
-  dependencies:
-    "@jest/fake-timers" "^24.8.0"
-    "@jest/transform" "^24.8.0"
-    "@jest/types" "^24.8.0"
-    jest-mock "^24.8.0"
-
-"@jest/fake-timers@^24.8.0":
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.8.0.tgz#2e5b80a4f78f284bcb4bd5714b8e10dd36a8d3d1"
-  integrity sha512-2M4d5MufVXwi6VzZhJ9f5S/wU4ud2ck0kxPof1Iz3zWx6Y+V2eJrES9jEktB6O3o/oEyk+il/uNu9PvASjWXQw==
-  dependencies:
-    "@jest/types" "^24.8.0"
-    jest-message-util "^24.8.0"
-    jest-mock "^24.8.0"
-
-"@jest/reporters@^24.8.0":
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.8.0.tgz#075169cd029bddec54b8f2c0fc489fd0b9e05729"
-  integrity sha512-eZ9TyUYpyIIXfYCrw0UHUWUvE35vx5I92HGMgS93Pv7du+GHIzl+/vh8Qj9MCWFK/4TqyttVBPakWMOfZRIfxw==
-  dependencies:
-    "@jest/environment" "^24.8.0"
-    "@jest/test-result" "^24.8.0"
-    "@jest/transform" "^24.8.0"
-    "@jest/types" "^24.8.0"
+"@jest/environment@^24.9.0":
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.9.0.tgz#21e3afa2d65c0586cbd6cbefe208bafade44ab18"
+  integrity sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==
+  dependencies:
+    "@jest/fake-timers" "^24.9.0"
+    "@jest/transform" "^24.9.0"
+    "@jest/types" "^24.9.0"
+    jest-mock "^24.9.0"
+
+"@jest/fake-timers@^24.9.0":
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93"
+  integrity sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==
+  dependencies:
+    "@jest/types" "^24.9.0"
+    jest-message-util "^24.9.0"
+    jest-mock "^24.9.0"
+
+"@jest/reporters@^24.9.0":
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.9.0.tgz#86660eff8e2b9661d042a8e98a028b8d631a5b43"
+  integrity sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==
+  dependencies:
+    "@jest/environment" "^24.9.0"
+    "@jest/test-result" "^24.9.0"
+    "@jest/transform" "^24.9.0"
+    "@jest/types" "^24.9.0"
     chalk "^2.0.1"
     exit "^0.1.2"
     glob "^7.1.2"
@@ -1002,13 +978,13 @@
     istanbul-lib-instrument "^3.0.1"
     istanbul-lib-report "^2.0.4"
     istanbul-lib-source-maps "^3.0.1"
-    istanbul-reports "^2.1.1"
-    jest-haste-map "^24.8.0"
-    jest-resolve "^24.8.0"
-    jest-runtime "^24.8.0"
-    jest-util "^24.8.0"
+    istanbul-reports "^2.2.6"
+    jest-haste-map "^24.9.0"
+    jest-resolve "^24.9.0"
+    jest-runtime "^24.9.0"
+    jest-util "^24.9.0"
     jest-worker "^24.6.0"
-    node-notifier "^5.2.1"
+    node-notifier "^5.4.2"
     slash "^2.0.0"
     source-map "^0.6.0"
     string-length "^2.0.0"
@@ -1022,54 +998,64 @@
     graceful-fs "^4.1.15"
     source-map "^0.6.0"
 
-"@jest/test-result@^24.8.0":
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.8.0.tgz#7675d0aaf9d2484caa65e048d9b467d160f8e9d3"
-  integrity sha512-+YdLlxwizlfqkFDh7Mc7ONPQAhA4YylU1s529vVM1rsf67vGZH/2GGm5uO8QzPeVyaVMobCQ7FTxl38QrKRlng==
+"@jest/source-map@^24.9.0":
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.9.0.tgz#0e263a94430be4b41da683ccc1e6bffe2a191714"
+  integrity sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==
   dependencies:
-    "@jest/console" "^24.7.1"
-    "@jest/types" "^24.8.0"
+    callsites "^3.0.0"
+    graceful-fs "^4.1.15"
+    source-map "^0.6.0"
+
+"@jest/test-result@^24.9.0":
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.9.0.tgz#11796e8aa9dbf88ea025757b3152595ad06ba0ca"
+  integrity sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==
+  dependencies:
+    "@jest/console" "^24.9.0"
+    "@jest/types" "^24.9.0"
     "@types/istanbul-lib-coverage" "^2.0.0"
 
-"@jest/test-sequencer@^24.8.0":
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.8.0.tgz#2f993bcf6ef5eb4e65e8233a95a3320248cf994b"
-  integrity sha512-OzL/2yHyPdCHXEzhoBuq37CE99nkme15eHkAzXRVqthreWZamEMA0WoetwstsQBCXABhczpK03JNbc4L01vvLg==
+"@jest/test-sequencer@^24.9.0":
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz#f8f334f35b625a4f2f355f2fe7e6036dad2e6b31"
+  integrity sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==
   dependencies:
-    "@jest/test-result" "^24.8.0"
-    jest-haste-map "^24.8.0"
-    jest-runner "^24.8.0"
-    jest-runtime "^24.8.0"
+    "@jest/test-result" "^24.9.0"
+    jest-haste-map "^24.9.0"
+    jest-runner "^24.9.0"
+    jest-runtime "^24.9.0"
 
-"@jest/transform@^24.8.0":
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.8.0.tgz#628fb99dce4f9d254c6fd9341e3eea262e06fef5"
-  integrity sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA==
+"@jest/transform@^24.9.0":
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.9.0.tgz#4ae2768b296553fadab09e9ec119543c90b16c56"
+  integrity sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==
   dependencies:
     "@babel/core" "^7.1.0"
-    "@jest/types" "^24.8.0"
+    "@jest/types" "^24.9.0"
     babel-plugin-istanbul "^5.1.0"
     chalk "^2.0.1"
     convert-source-map "^1.4.0"
     fast-json-stable-stringify "^2.0.0"
     graceful-fs "^4.1.15"
-    jest-haste-map "^24.8.0"
-    jest-regex-util "^24.3.0"
-    jest-util "^24.8.0"
+    jest-haste-map "^24.9.0"
+    jest-regex-util "^24.9.0"
+    jest-util "^24.9.0"
     micromatch "^3.1.10"
+    pirates "^4.0.1"
     realpath-native "^1.1.0"
     slash "^2.0.0"
     source-map "^0.6.1"
     write-file-atomic "2.4.1"
 
-"@jest/types@^24.8.0":
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad"
-  integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg==
+"@jest/types@^24.9.0":
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59"
+  integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==
   dependencies:
     "@types/istanbul-lib-coverage" "^2.0.0"
     "@types/istanbul-reports" "^1.1.1"
-    "@types/yargs" "^12.0.9"
+    "@types/yargs" "^13.0.0"
 
 "@types/babel__core@^7.1.0":
   version "7.1.0"
@@ -1144,9 +1130,9 @@
   integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
 
 "@types/node@*":
-  version "10.12.18"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
-  integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==
+  version "12.7.8"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.8.tgz#cb1bf6800238898bc2ff6ffa5702c3cadd350708"
+  integrity sha512-FMdVn84tJJdV+xe+53sYiZS4R5yn1mAIxfj+DVoNiQjTYz1+OYmjwEZr1ev9nU0axXwda0QDbYl06QHanRVH3A==
 
 "@types/q@^1.5.1":
   version "1.5.1"
@@ -1165,10 +1151,17 @@
   resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
   integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
 
-"@types/yargs@^12.0.2", "@types/yargs@^12.0.9":
-  version "12.0.9"
-  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0"
-  integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==
+"@types/yargs-parser@*":
+  version "13.1.0"
+  resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228"
+  integrity sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==
+
+"@types/yargs@^13.0.0":
+  version "13.0.2"
+  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.2.tgz#a64674fc0149574ecd90ba746e932b5a5f7b3653"
+  integrity sha512-lwwgizwk/bIIU+3ELORkyuOgDjCh7zuWDFqRtPPhhVgq9N1F7CvLNKg1TX4f2duwtKQ0p044Au9r1PLIXHrIzQ==
+  dependencies:
+    "@types/yargs-parser" "*"
 
 "@webassemblyjs/ast@1.8.5":
   version "1.8.5"
@@ -1344,11 +1337,6 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
     mime-types "~2.1.24"
     negotiator "0.6.2"
 
-acorn-dynamic-import@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948"
-  integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==
-
 acorn-globals@^4.1.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.0.tgz#e3b6f8da3c1552a95ae627571f7dd6923bb54103"
@@ -1364,16 +1352,21 @@ acorn-jsx@^3.0.0:
   dependencies:
     acorn "^3.0.4"
 
-acorn-jsx@^5.0.0:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e"
-  integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==
+acorn-jsx@^5.0.2:
+  version "5.0.2"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.2.tgz#84b68ea44b373c4f8686023a551f61a21b7c4a4f"
+  integrity sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==
 
-acorn-walk@^6.0.1, acorn-walk@^6.1.1:
+acorn-walk@^6.0.1:
   version "6.1.1"
   resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.1.tgz#d363b66f5fac5f018ff9c3a1e7b6f8e310cc3913"
   integrity sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==
 
+acorn-walk@^6.1.1:
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c"
+  integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==
+
 acorn@^3.0.4:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
@@ -1384,10 +1377,20 @@ acorn@^5.5.0, acorn@^5.5.3:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
   integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
 
-acorn@^6.0.1, acorn@^6.0.2, acorn@^6.0.5, acorn@^6.0.7:
-  version "6.1.1"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f"
-  integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==
+acorn@^6.0.1:
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.0.tgz#67f0da2fc339d6cfb5d6fb244fd449f33cd8bbe3"
+  integrity sha512-8oe72N3WPMjA+2zVG71Ia0nXZ8DpQH+QyyHO+p06jT8eg8FGG3FbcUIi8KziHlAfheJQZeoqbvq1mQSQHXKYLw==
+
+acorn@^6.0.7, acorn@^6.2.1:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e"
+  integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==
+
+acorn@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.0.0.tgz#26b8d1cd9a9b700350b71c0905546f64d1284e7a"
+  integrity sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==
 
 airbnb-prop-types@^2.13.2:
   version "2.13.2"
@@ -1415,10 +1418,10 @@ ajv-keywords@^1.0.0:
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
   integrity sha1-MU3QpLM2j609/NxU7eYXG4htrzw=
 
-ajv-keywords@^3.1.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
-  integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=
+ajv-keywords@^3.1.0, ajv-keywords@^3.4.1:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
+  integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
 
 ajv@^4.7.0:
   version "4.11.8"
@@ -1428,10 +1431,10 @@ ajv@^4.7.0:
     co "^4.6.0"
     json-stable-stringify "^1.0.1"
 
-ajv@^6.1.0, ajv@^6.5.3, ajv@^6.5.5, ajv@^6.6.1:
-  version "6.6.2"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.2.tgz#caceccf474bf3fc3ce3b147443711a24063cc30d"
-  integrity sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==
+ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5, ajv@^6.9.1:
+  version "6.10.2"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
+  integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
   dependencies:
     fast-deep-equal "^2.0.1"
     fast-json-stable-stringify "^2.0.0"
@@ -1444,9 +1447,9 @@ alphanum-sort@^1.0.0:
   integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=
 
 ansi-colors@^3.0.0:
-  version "3.2.3"
-  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813"
-  integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
+  integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
 
 ansi-escapes@^1.1.0:
   version "1.4.0"
@@ -1458,6 +1461,11 @@ ansi-escapes@^3.0.0:
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30"
   integrity sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==
 
+ansi-escapes@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
+  integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
+
 ansi-html@0.0.7:
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
@@ -1478,6 +1486,11 @@ ansi-regex@^4.0.0:
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9"
   integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==
 
+ansi-regex@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
+  integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
+
 ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -1498,6 +1511,14 @@ anymatch@^2.0.0:
     micromatch "^3.1.4"
     normalize-path "^2.1.1"
 
+anymatch@^3.0.1:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.0.3.tgz#2fb624fe0e84bccab00afee3d0006ed310f22f09"
+  integrity sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==
+  dependencies:
+    normalize-path "^3.0.0"
+    picomatch "^2.0.4"
+
 aproba@^1.0.3, aproba@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
@@ -1635,10 +1656,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
   integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
 
 assert@^1.1.1:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
-  integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb"
+  integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==
   dependencies:
+    object-assign "^4.1.1"
     util "0.10.3"
 
 assign-symbols@^1.0.0:
@@ -1662,22 +1684,15 @@ async-each@^1.0.1:
   integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
 
 async-limiter@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
-  integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
+  integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
 
 async@^1.5.2:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
   integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
 
-async@^2.5.0:
-  version "2.6.1"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
-  integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
-  dependencies:
-    lodash "^4.17.10"
-
 asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -1688,18 +1703,18 @@ atob@^2.1.1:
   resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
   integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
 
-autoprefixer@^9.6.0:
-  version "9.6.0"
-  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.0.tgz#0111c6bde2ad20c6f17995a33fad7cf6854b4c87"
-  integrity sha512-kuip9YilBqhirhHEGHaBTZKXL//xxGnzvsD0FtBQa6z+A69qZD6s/BAX9VzDF1i9VKDquTJDQaPLSEhOnL6FvQ==
+autoprefixer@^9.6.1:
+  version "9.6.1"
+  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47"
+  integrity sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==
   dependencies:
-    browserslist "^4.6.1"
-    caniuse-lite "^1.0.30000971"
+    browserslist "^4.6.3"
+    caniuse-lite "^1.0.30000980"
     chalk "^2.4.2"
     normalize-range "^0.1.2"
     num2fraction "^1.2.2"
-    postcss "^7.0.16"
-    postcss-value-parser "^3.3.1"
+    postcss "^7.0.17"
+    postcss-value-parser "^4.0.0"
 
 aws-sign2@~0.7.0:
   version "0.7.0"
@@ -1711,6 +1726,14 @@ aws4@^1.8.0:
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
   integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
 
+axios@^0.18.0:
+  version "0.18.1"
+  resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3"
+  integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==
+  dependencies:
+    follow-redirects "1.5.10"
+    is-buffer "^2.0.2"
+
 axios@^0.19.0:
   version "0.19.0"
   resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8"
@@ -1726,40 +1749,47 @@ axobject-query@^2.0.2:
   dependencies:
     ast-types-flow "0.0.7"
 
-babel-eslint@^10.0.1:
-  version "10.0.1"
-  resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed"
-  integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==
+babel-eslint@^10.0.3:
+  version "10.0.3"
+  resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a"
+  integrity sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==
   dependencies:
     "@babel/code-frame" "^7.0.0"
     "@babel/parser" "^7.0.0"
     "@babel/traverse" "^7.0.0"
     "@babel/types" "^7.0.0"
-    eslint-scope "3.7.1"
     eslint-visitor-keys "^1.0.0"
+    resolve "^1.12.0"
 
-babel-jest@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.8.0.tgz#5c15ff2b28e20b0f45df43fe6b7f2aae93dba589"
-  integrity sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw==
+babel-jest@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54"
+  integrity sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==
   dependencies:
-    "@jest/transform" "^24.8.0"
-    "@jest/types" "^24.8.0"
+    "@jest/transform" "^24.9.0"
+    "@jest/types" "^24.9.0"
     "@types/babel__core" "^7.1.0"
     babel-plugin-istanbul "^5.1.0"
-    babel-preset-jest "^24.6.0"
+    babel-preset-jest "^24.9.0"
     chalk "^2.4.2"
     slash "^2.0.0"
 
-babel-loader@^8.0.5:
-  version "8.0.5"
-  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.5.tgz#225322d7509c2157655840bba52e46b6c2f2fe33"
-  integrity sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw==
+babel-loader@^8.0.6:
+  version "8.0.6"
+  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb"
+  integrity sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==
   dependencies:
     find-cache-dir "^2.0.0"
     loader-utils "^1.0.2"
     mkdirp "^0.5.1"
-    util.promisify "^1.0.0"
+    pify "^4.0.1"
+
+babel-plugin-dynamic-import-node@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f"
+  integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==
+  dependencies:
+    object.assign "^4.1.0"
 
 babel-plugin-emotion@^9.2.11:
   version "9.2.11"
@@ -1788,10 +1818,10 @@ babel-plugin-istanbul@^5.1.0:
     istanbul-lib-instrument "^3.0.0"
     test-exclude "^5.0.0"
 
-babel-plugin-jest-hoist@^24.6.0:
-  version "24.6.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz#f7f7f7ad150ee96d7a5e8e2c5da8319579e78019"
-  integrity sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w==
+babel-plugin-jest-hoist@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz#4f837091eb407e01447c8843cbec546d0002d756"
+  integrity sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==
   dependencies:
     "@types/babel__traverse" "^7.0.6"
 
@@ -1849,13 +1879,13 @@ babel-plugin-transform-react-remove-prop-types@^0.4.24:
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a"
   integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==
 
-babel-preset-jest@^24.6.0:
-  version "24.6.0"
-  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz#66f06136eefce87797539c0d63f1769cc3915984"
-  integrity sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw==
+babel-preset-jest@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz#192b521e2217fb1d1f67cf73f70c336650ad3cdc"
+  integrity sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==
   dependencies:
     "@babel/plugin-syntax-object-rest-spread" "^7.0.0"
-    babel-plugin-jest-hoist "^24.6.0"
+    babel-plugin-jest-hoist "^24.9.0"
 
 babel-runtime@^6.26.0:
   version "6.26.0"
@@ -1878,9 +1908,9 @@ balanced-match@^1.0.0:
   integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
 
 base64-js@^1.0.2:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
-  integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
+  integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
 
 base@^0.11.1:
   version "0.11.2"
@@ -1908,14 +1938,14 @@ bcrypt-pbkdf@^1.0.0:
     tweetnacl "^0.14.3"
 
 bfj@^6.1.1:
-  version "6.1.1"
-  resolved "https://registry.yarnpkg.com/bfj/-/bfj-6.1.1.tgz#05a3b7784fbd72cfa3c22e56002ef99336516c48"
-  integrity sha512-+GUNvzHR4nRyGybQc2WpNJL4MJazMuvf92ueIyA0bIkPRwhhQu3IfZQ2PSoVPpCBJfmoSdOxu5rnotfFLlvYRQ==
+  version "6.1.2"
+  resolved "https://registry.yarnpkg.com/bfj/-/bfj-6.1.2.tgz#325c861a822bcb358a41c78a33b8e6e2086dde7f"
+  integrity sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==
   dependencies:
-    bluebird "^3.5.1"
-    check-types "^7.3.0"
-    hoopy "^0.1.2"
-    tryer "^1.0.0"
+    bluebird "^3.5.5"
+    check-types "^8.0.3"
+    hoopy "^0.1.4"
+    tryer "^1.0.1"
 
 big.js@^3.1.3:
   version "3.2.0"
@@ -1928,20 +1958,35 @@ big.js@^5.2.2:
   integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
 
 binary-extensions@^1.0.0:
-  version "1.12.0"
-  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14"
-  integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==
+  version "1.13.1"
+  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
+  integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
+
+binary-extensions@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
+  integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
 
-bluebird@^3.5.1, bluebird@^3.5.3:
+bluebird@^3.5.3:
   version "3.5.3"
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
   integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
 
+bluebird@^3.5.5:
+  version "3.5.5"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
+  integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==
+
 blurhash@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.0.0.tgz#9087bc5cc4d482f1305059d7410df4133adcab2e"
   integrity sha512-x6fpZnd6AWde4U9m7xhUB44qIvGV4W6OdTAXGabYm4oZUOOGh5K1HAEoGAQn3iG4gbbPn9RSGce3VfNgGsX/Vw==
 
+bmp-js@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233"
+  integrity sha1-4Fpj95amwf8l9Hcex62twUjAcjM=
+
 bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
   version "4.11.8"
   resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
@@ -2004,6 +2049,13 @@ braces@^2.3.1, braces@^2.3.2:
     split-string "^3.0.2"
     to-regex "^3.0.1"
 
+braces@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+  integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+  dependencies:
+    fill-range "^7.0.1"
+
 bricks.js@^1.7.0:
   version "1.8.0"
   resolved "https://registry.yarnpkg.com/bricks.js/-/bricks.js-1.8.0.tgz#8fdeb3c0226af251f4d5727a7df7f9ac0092b4b2"
@@ -2087,14 +2139,14 @@ browserify-zlib@^0.2.0:
   dependencies:
     pako "~1.0.5"
 
-browserslist@^4.0.0, browserslist@^4.6.0, browserslist@^4.6.1:
-  version "4.6.2"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.2.tgz#574c665950915c2ac73a4594b8537a9eba26203f"
-  integrity sha512-2neU/V0giQy9h3XMPwLhEY3+Ao0uHSwHvU8Q1Ea6AgLVL1sXbX3dzPrJ8NWe5Hi4PoTkCYXOtVR9rfRLI0J/8Q==
+browserslist@^4.0.0, browserslist@^4.6.0, browserslist@^4.6.3:
+  version "4.6.6"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.6.tgz#6e4bf467cde520bc9dbdf3747dafa03531cec453"
+  integrity sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==
   dependencies:
-    caniuse-lite "^1.0.30000974"
-    electron-to-chromium "^1.3.150"
-    node-releases "^1.1.23"
+    caniuse-lite "^1.0.30000984"
+    electron-to-chromium "^1.3.191"
+    node-releases "^1.1.25"
 
 bser@^2.0.0:
   version "2.0.0"
@@ -2152,7 +2204,7 @@ bytes@3.1.0:
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
   integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
 
-cacache@^11.2.0, cacache@^11.3.2:
+cacache@^11.2.0:
   version "11.3.2"
   resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.2.tgz#2d81e308e3d258ca38125b676b98b2ac9ce69bfa"
   integrity sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==
@@ -2172,6 +2224,47 @@ cacache@^11.2.0, cacache@^11.3.2:
     unique-filename "^1.1.1"
     y18n "^4.0.0"
 
+cacache@^11.3.3:
+  version "11.3.3"
+  resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.3.tgz#8bd29df8c6a718a6ebd2d010da4d7972ae3bbadc"
+  integrity sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==
+  dependencies:
+    bluebird "^3.5.5"
+    chownr "^1.1.1"
+    figgy-pudding "^3.5.1"
+    glob "^7.1.4"
+    graceful-fs "^4.1.15"
+    lru-cache "^5.1.1"
+    mississippi "^3.0.0"
+    mkdirp "^0.5.1"
+    move-concurrently "^1.0.1"
+    promise-inflight "^1.0.1"
+    rimraf "^2.6.3"
+    ssri "^6.0.1"
+    unique-filename "^1.1.1"
+    y18n "^4.0.0"
+
+cacache@^12.0.2:
+  version "12.0.3"
+  resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390"
+  integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==
+  dependencies:
+    bluebird "^3.5.5"
+    chownr "^1.1.1"
+    figgy-pudding "^3.5.1"
+    glob "^7.1.4"
+    graceful-fs "^4.1.15"
+    infer-owner "^1.0.3"
+    lru-cache "^5.1.1"
+    mississippi "^3.0.0"
+    mkdirp "^0.5.1"
+    move-concurrently "^1.0.1"
+    promise-inflight "^1.0.1"
+    rimraf "^2.6.3"
+    ssri "^6.0.1"
+    unique-filename "^1.1.1"
+    y18n "^4.0.0"
+
 cache-base@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
@@ -2223,15 +2316,10 @@ callsites@^3.0.0:
   resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3"
   integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==
 
-camelcase@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
-  integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==
-
-camelcase@^5.2.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.2.0.tgz#e7522abda5ed94cc0489e1b8466610e88404cf45"
-  integrity sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ==
+camelcase@^5.0.0, camelcase@^5.3.1:
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+  integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
 
 caniuse-api@^3.0.0:
   version "3.0.0"
@@ -2243,10 +2331,10 @@ caniuse-api@^3.0.0:
     lodash.memoize "^4.1.2"
     lodash.uniq "^4.5.0"
 
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000971, caniuse-lite@^1.0.30000974:
-  version "1.0.30000974"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000974.tgz#b7afe14ee004e97ce6dc73e3f878290a12928ad8"
-  integrity sha512-xc3rkNS/Zc3CmpMKuczWEdY2sZgx09BkAxfvkxlAEBTqcMHeL8QnPqhKse+5sRTi3nrw2pJwToD2WvKn1Uhvww==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30000984:
+  version "1.0.30000986"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000986.tgz#f34350e367cc900509511574817ac092112bf7ab"
+  integrity sha512-pM+LnkoAX0+QnIH3tpW5EnkmfpEoqOD8FAcoBvsl3Xh6DXkgctiCxeCbXphP/k3XJtJzm+zOAJbi6U6IVkpWZQ==
 
 capture-exit@^1.2.0:
   version "1.2.0"
@@ -2260,6 +2348,15 @@ caseless@~0.12.0:
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
   integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
 
+chalk@2.4.2, chalk@^2.0, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2:
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+  integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+  dependencies:
+    ansi-styles "^3.2.1"
+    escape-string-regexp "^1.0.5"
+    supports-color "^5.3.0"
+
 chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -2271,25 +2368,21 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
 
-chalk@^2.0, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2:
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
-  integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
-  dependencies:
-    ansi-styles "^3.2.1"
-    escape-string-regexp "^1.0.5"
-    supports-color "^5.3.0"
-
 chardet@^0.7.0:
   version "0.7.0"
   resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
   integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
 
-check-types@^7.3.0:
+check-types@^7.4.0:
   version "7.4.0"
   resolved "https://registry.yarnpkg.com/check-types/-/check-types-7.4.0.tgz#0378ec1b9616ec71f774931a3c6516fad8c152f4"
   integrity sha512-YbulWHdfP99UfZ73NcUDlNJhEIDgm9Doq9GhpyXbF+7Aegi3CVV7qqMCKTTqJxlvEvnQBp9IA+dxsGN6xK/nSg==
 
+check-types@^8.0.3:
+  version "8.0.3"
+  resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"
+  integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==
+
 cheerio@^1.0.0-rc.2:
   version "1.0.0-rc.2"
   resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db"
@@ -2302,10 +2395,25 @@ cheerio@^1.0.0-rc.2:
     lodash "^4.15.0"
     parse5 "^3.0.1"
 
-chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.1.6:
-  version "2.1.6"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5"
-  integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==
+"chokidar@>=2.0.0 <4.0.0":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.0.2.tgz#0d1cd6d04eb2df0327446188cd13736a3367d681"
+  integrity sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==
+  dependencies:
+    anymatch "^3.0.1"
+    braces "^3.0.2"
+    glob-parent "^5.0.0"
+    is-binary-path "^2.1.0"
+    is-glob "^4.0.1"
+    normalize-path "^3.0.0"
+    readdirp "^3.1.1"
+  optionalDependencies:
+    fsevents "^2.0.6"
+
+chokidar@^2.0.2, chokidar@^2.1.8:
+  version "2.1.8"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
+  integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==
   dependencies:
     anymatch "^2.0.0"
     async-each "^1.0.1"
@@ -2322,14 +2430,14 @@ chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.1.6:
     fsevents "^1.2.7"
 
 chownr@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494"
-  integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142"
+  integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==
 
-chrome-trace-event@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz#45a91bd2c20c9411f0963b5aaeb9a1b95e09cc48"
-  integrity sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==
+chrome-trace-event@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4"
+  integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==
   dependencies:
     tslib "^1.9.0"
 
@@ -2394,6 +2502,15 @@ cliui@^4.0.0:
     strip-ansi "^4.0.0"
     wrap-ansi "^2.0.0"
 
+cliui@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
+  integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
+  dependencies:
+    string-width "^3.1.0"
+    strip-ansi "^5.2.0"
+    wrap-ansi "^5.1.0"
+
 clone-deep@^2.0.1:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713"
@@ -2476,15 +2593,15 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
   dependencies:
     delayed-stream "~1.0.0"
 
-commander@^2.11.0, commander@^2.18.0, commander@^2.19.0, commander@^2.8.1:
+commander@^2.11.0, commander@^2.19.0, commander@^2.8.1, commander@~2.20.0:
   version "2.20.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
   integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
 
-commander@~2.17.1:
-  version "2.17.1"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
-  integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
+commander@^2.18.0, commander@^2.20.0:
+  version "2.20.1"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.1.tgz#3863ce3ca92d0831dcf2a102f5fb4b5926afd0f9"
+  integrity sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==
 
 commondir@^1.0.1:
   version "1.0.1"
@@ -2492,9 +2609,9 @@ commondir@^1.0.1:
   integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
 
 component-emitter@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
-  integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
+  integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
 
 compressible@~2.0.16:
   version "2.0.17"
@@ -2503,13 +2620,13 @@ compressible@~2.0.16:
   dependencies:
     mime-db ">= 1.40.0 < 2"
 
-compression-webpack-plugin@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-2.0.0.tgz#46476350c1eb27f783dccc79ac2f709baa2cffbc"
-  integrity sha512-bDgd7oTUZC8EkRx8j0sjyCfeiO+e5sFcfgaFcjVhfQf5lLya7oY2BczxcJ7IUuVjz5m6fy8IECFmVFew3xLk8Q==
+compression-webpack-plugin@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-3.0.0.tgz#097d2e4d95c3a14cb5c8ed20899009ab5b9bbca0"
+  integrity sha512-ls+oKw4eRbvaSv/hj9NmctihhBcR26j76JxV0bLRLcWhrUBdQFgd06z/Kgg7exyQvtWWP484wZxs0gIUX3NO0Q==
   dependencies:
     cacache "^11.2.0"
-    find-cache-dir "^2.0.0"
+    find-cache-dir "^3.0.0"
     neo-async "^2.5.0"
     schema-utils "^1.0.0"
     serialize-javascript "^1.4.0"
@@ -2616,6 +2733,24 @@ copy-descriptor@^0.1.0:
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
   integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
 
+copy-webpack-plugin@^5.0.4:
+  version "5.0.4"
+  resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.0.4.tgz#c78126f604e24f194c6ec2f43a64e232b5d43655"
+  integrity sha512-YBuYGpSzoCHSSDGyHy6VJ7SHojKp6WHT4D7ItcQFNAYx2hrwkMe56e97xfVR0/ovDuMTrMffXUiltvQljtAGeg==
+  dependencies:
+    cacache "^11.3.3"
+    find-cache-dir "^2.1.0"
+    glob-parent "^3.1.0"
+    globby "^7.1.1"
+    is-glob "^4.0.1"
+    loader-utils "^1.2.3"
+    minimatch "^3.0.4"
+    normalize-path "^3.0.0"
+    p-limit "^2.2.0"
+    schema-utils "^1.0.0"
+    serialize-javascript "^1.7.0"
+    webpack-log "^2.0.0"
+
 core-js-compat@^3.1.1:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.1.3.tgz#0cc3ba4c7f62928c2837e1cffbe8dc78b4f1ae14"
@@ -2727,7 +2862,7 @@ cross-env@^5.1.4:
     cross-spawn "^6.0.5"
     is-windows "^1.0.0"
 
-cross-spawn@^6.0.0, cross-spawn@^6.0.5:
+cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
   version "6.0.5"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
   integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
@@ -2800,22 +2935,23 @@ css-list-helpers@^1.0.1:
   dependencies:
     tcomb "^2.5.0"
 
-css-loader@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-2.1.1.tgz#d8254f72e412bb2238bb44dd674ffbef497333ea"
-  integrity sha512-OcKJU/lt232vl1P9EEDamhoO9iKY3tIjY5GU+XDLblAykTdgs6Ux9P1hTHve8nFKy5KPpOXOsVI/hIwi3841+w==
+css-loader@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.2.0.tgz#bb570d89c194f763627fcf1f80059c6832d009b2"
+  integrity sha512-QTF3Ud5H7DaZotgdcJjGMvyDj5F3Pn1j/sC6VBEOVp94cbwqyIBdcs/quzj4MC1BKQSrTpQznegH/5giYbhnCQ==
   dependencies:
-    camelcase "^5.2.0"
-    icss-utils "^4.1.0"
+    camelcase "^5.3.1"
+    cssesc "^3.0.0"
+    icss-utils "^4.1.1"
     loader-utils "^1.2.3"
     normalize-path "^3.0.0"
-    postcss "^7.0.14"
+    postcss "^7.0.17"
     postcss-modules-extract-imports "^2.0.0"
-    postcss-modules-local-by-default "^2.0.6"
+    postcss-modules-local-by-default "^3.0.2"
     postcss-modules-scope "^2.1.0"
-    postcss-modules-values "^2.0.0"
-    postcss-value-parser "^3.3.0"
-    schema-utils "^1.0.0"
+    postcss-modules-values "^3.0.0"
+    postcss-value-parser "^4.0.0"
+    schema-utils "^2.0.0"
 
 css-select-base-adapter@~0.1.0:
   version "0.1.1"
@@ -2985,17 +3121,18 @@ csstype@^2.5.2:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.5.tgz#1cd1dff742ebf4d7c991470ae71e12bb6751e034"
   integrity sha512-JsTaiksRsel5n7XwqPAfB0l3TFKdpjW/kgAELf9vrb5adGA7UCPLajKK5s3nFrcFm3Rkyp/Qkgl73ENc1UY3cA==
 
-cyclist@~0.2.2:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
-  integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=
+cyclist@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
+  integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
 
-d@1:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
-  integrity sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=
+d@1, d@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
+  integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
   dependencies:
-    es5-ext "^0.10.9"
+    es5-ext "^0.10.50"
+    type "^1.0.1"
 
 damerau-levenshtein@^1.0.4:
   version "1.0.4"
@@ -3023,7 +3160,7 @@ date-now@^0.1.4:
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
   integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=
 
-debug@2.6.9, debug@^2.1.1, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
+debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -3037,7 +3174,7 @@ debug@=3.1.0:
   dependencies:
     ms "2.0.0"
 
-debug@^3.2.5:
+debug@^3.0.0, debug@^3.2.5, debug@^3.2.6:
   version "3.2.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
   integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@@ -3062,9 +3199,16 @@ decode-uri-component@^0.2.0:
   integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
 
 deep-equal@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
-  integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.0.tgz#3103cdf8ab6d32cf4a8df7865458f2b8d33f3745"
+  integrity sha512-ZbfWJq/wN1Z273o7mUSjILYqehAktR2NVoSrOukDkU9kg2v/Uv89yU4Cvz8seJeAmtN5oqiefKq8FPuXOboqLw==
+  dependencies:
+    is-arguments "^1.0.4"
+    is-date-object "^1.0.1"
+    is-regex "^1.0.4"
+    object-is "^1.0.1"
+    object-keys "^1.1.1"
+    regexp.prototype.flags "^1.2.0"
 
 deep-extend@^0.5.1:
   version "0.5.1"
@@ -3184,10 +3328,10 @@ detect-passive-events@^1.0.2:
   resolved "https://registry.yarnpkg.com/detect-passive-events/-/detect-passive-events-1.0.4.tgz#6ed477e6e5bceb79079735dcd357789d37f9a91a"
   integrity sha1-btR35uW863kHlzXc01d4nTf5qRo=
 
-diff-sequences@^24.3.0:
-  version "24.3.0"
-  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975"
-  integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==
+diff-sequences@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
+  integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
 
 diffie-hellman@^5.0.0:
   version "5.0.3"
@@ -3198,6 +3342,13 @@ diffie-hellman@^5.0.0:
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
+dir-glob@^2.0.0:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
+  integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==
+  dependencies:
+    path-type "^3.0.0"
+
 discontinuous-range@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a"
@@ -3238,6 +3389,13 @@ doctrine@^2.1.0:
   dependencies:
     esutils "^2.0.2"
 
+doctrine@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
+  integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
+  dependencies:
+    esutils "^2.0.2"
+
 dom-helpers@^3.2.1, dom-helpers@^3.3.1:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
@@ -3321,9 +3479,9 @@ duplexer@^0.1.1:
   integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=
 
 duplexify@^3.4.2, duplexify@^3.6.0:
-  version "3.6.1"
-  resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.1.tgz#b1a7a29c4abfd639585efaecce80d666b1e34125"
-  integrity sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==
+  version "3.7.1"
+  resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
+  integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
   dependencies:
     end-of-stream "^1.0.0"
     inherits "^2.0.1"
@@ -3343,20 +3501,25 @@ ee-first@1.1.1:
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
   integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
 
-ejs@^2.3.4, ejs@^2.6.1:
+ejs@^2.3.4:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
   integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
 
-electron-to-chromium@^1.3.150:
-  version "1.3.163"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.163.tgz#7fc3d637f5d8fa4ca4a052cad0de7675bd98b911"
-  integrity sha512-uCEoqQeKrjlhUSUudY0XvyNVDhWR5XmnCIV0WXr2Qo9PVzEVXI75LHGtzwro9Qh8NNetRjSitrm8AfQvPGaSqA==
+ejs@^2.6.1:
+  version "2.7.1"
+  resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.1.tgz#5b5ab57f718b79d4aca9254457afecd36fa80228"
+  integrity sha512-kS/gEPzZs3Y1rRsbGX4UOSjtP/CeJP0CxSNZHYxGfVM/VgLcv0ZqM7C45YyTj2DI2g7+P9Dd24C+IMIg6D0nYQ==
+
+electron-to-chromium@^1.3.191:
+  version "1.3.203"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.203.tgz#76de1b76eaaf7208e587a26b8e45407535a00abd"
+  integrity sha512-Z1FjJKEBhYrCNmnususVk8khiBabVI/bSJB/295V4ghVt4MFmtbP+mXgRZLQZinEBI469U6FtiGgpXnlLs6qiQ==
 
 elliptic@^6.0.0:
-  version "6.4.1"
-  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a"
-  integrity sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==
+  version "6.5.1"
+  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.1.tgz#c380f5f909bf1b9b4428d028cd18d3b0efd6b52b"
+  integrity sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==
   dependencies:
     bn.js "^4.4.0"
     brorand "^1.0.1"
@@ -3367,10 +3530,10 @@ elliptic@^6.0.0:
     minimalistic-crypto-utils "^1.0.0"
 
 emoji-mart@Gargron/emoji-mart#build:
-  version "2.6.2"
-  resolved "https://codeload.github.com/Gargron/emoji-mart/tar.gz/ff00dc470b5b2d9f145a6d6e977a54de5df2b4c9"
+  version "2.6.3"
+  resolved "https://codeload.github.com/Gargron/emoji-mart/tar.gz/934f314fd8322276765066e8a2a6be5bac61b1cf"
 
-emoji-regex@^7.0.2:
+emoji-regex@^7.0.1, emoji-regex@^7.0.2:
   version "7.0.3"
   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
   integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
@@ -3401,13 +3564,13 @@ encoding@^0.1.11:
     iconv-lite "~0.4.13"
 
 end-of-stream@^1.0.0, end-of-stream@^1.1.0:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
-  integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==
+  version "1.4.4"
+  resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
+  integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
   dependencies:
     once "^1.4.0"
 
-enhanced-resolve@^4.1.0:
+enhanced-resolve@4.1.0, enhanced-resolve@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f"
   integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==
@@ -3520,24 +3683,15 @@ es-to-primitive@^1.1.1, es-to-primitive@^1.2.0:
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
 
-es5-ext@^0.10.14:
-  version "0.10.50"
-  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.50.tgz#6d0e23a0abdb27018e5ac4fd09b412bc5517a778"
-  integrity sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==
+es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@^0.10.51, es5-ext@~0.10.14:
+  version "0.10.51"
+  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.51.tgz#ed2d7d9d48a12df86e0299287e93a09ff478842f"
+  integrity sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==
   dependencies:
     es6-iterator "~2.0.3"
     es6-symbol "~3.1.1"
     next-tick "^1.0.0"
 
-es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14:
-  version "0.10.46"
-  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.46.tgz#efd99f67c5a7ec789baa3daa7f79870388f7f572"
-  integrity sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==
-  dependencies:
-    es6-iterator "~2.0.3"
-    es6-symbol "~3.1.1"
-    next-tick "1"
-
 es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
@@ -3570,13 +3724,21 @@ es6-set@~0.1.5:
     es6-symbol "3.1.1"
     event-emitter "~0.3.5"
 
-es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1:
+es6-symbol@3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
   integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=
   dependencies:
-    d "1"
-    es5-ext "~0.10.14"
+    d "1"
+    es5-ext "~0.10.14"
+
+es6-symbol@^3.1.1, es6-symbol@^3.1.2, es6-symbol@~3.1.1:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.2.tgz#859fdd34f32e905ff06d752e7171ddd4444a7ed1"
+  integrity sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==
+  dependencies:
+    d "^1.0.1"
+    es5-ext "^0.10.51"
 
 es6-weak-map@^2.0.1:
   version "2.0.2"
@@ -3636,10 +3798,10 @@ eslint-module-utils@^2.4.0:
     debug "^2.6.8"
     pkg-dir "^2.0.0"
 
-eslint-plugin-import@~2.17.3:
-  version "2.17.3"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.17.3.tgz#00548b4434c18faebaba04b24ae6198f280de189"
-  integrity sha512-qeVf/UwXFJbeyLbxuY8RgqDyEKCkqV7YC+E5S5uOjAp4tOc8zj01JP3ucoBM8JcEqd1qRasJSg6LLlisirfy0Q==
+eslint-plugin-import@~2.18.2:
+  version "2.18.2"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6"
+  integrity sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==
   dependencies:
     array-includes "^3.0.3"
     contains-path "^0.1.0"
@@ -3648,16 +3810,17 @@ eslint-plugin-import@~2.17.3:
     eslint-import-resolver-node "^0.3.2"
     eslint-module-utils "^2.4.0"
     has "^1.0.3"
-    lodash "^4.17.11"
     minimatch "^3.0.4"
+    object.values "^1.1.0"
     read-pkg-up "^2.0.0"
     resolve "^1.11.0"
 
-eslint-plugin-jsx-a11y@~6.2.1:
-  version "6.2.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.1.tgz#4ebba9f339b600ff415ae4166e3e2e008831cf0c"
-  integrity sha512-cjN2ObWrRz0TTw7vEcGQrx+YltMvZoOEx4hWU8eEERDnBIU00OTq7Vr+jA7DFKxiwLNv4tTh5Pq2GUNEa8b6+w==
+eslint-plugin-jsx-a11y@~6.2.3:
+  version "6.2.3"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz#b872a09d5de51af70a97db1eea7dc933043708aa"
+  integrity sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==
   dependencies:
+    "@babel/runtime" "^7.4.5"
     aria-query "^3.0.0"
     array-includes "^3.0.3"
     ast-types-flow "^0.0.7"
@@ -3665,51 +3828,55 @@ eslint-plugin-jsx-a11y@~6.2.1:
     damerau-levenshtein "^1.0.4"
     emoji-regex "^7.0.2"
     has "^1.0.3"
-    jsx-ast-utils "^2.0.1"
+    jsx-ast-utils "^2.2.1"
 
-eslint-plugin-promise@~4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.1.1.tgz#1e08cb68b5b2cd8839f8d5864c796f56d82746db"
-  integrity sha512-faAHw7uzlNPy7b45J1guyjazw28M+7gJokKUjC5JSFoYfUEyy6Gw/i7YQvmv2Yk00sUjWcmzXQLpU1Ki/C2IZQ==
+eslint-plugin-promise@~4.2.1:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
+  integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
 
-eslint-plugin-react@~7.12.1:
-  version "7.12.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.12.1.tgz#b9c4639f72469ff317ac31e3bd630d22d0dbf8f4"
-  integrity sha512-1YyXVhp6KSB+xRC1BWzmlA4BH9Wp9jMMBE6AJizxuk+bg/KUJpQGRwsU1/q1pV8rM6oEdLCxunXn7Nfh2BOWBg==
+eslint-plugin-react@~7.14.3:
+  version "7.14.3"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz#911030dd7e98ba49e1b2208599571846a66bdf13"
+  integrity sha512-EzdyyBWC4Uz2hPYBiEJrKCUi2Fn+BJ9B/pJQcjw5X+x/H2Nm59S4MJIvL4O5NEE0+WbnQwEBxWY03oUk+Bc3FA==
   dependencies:
     array-includes "^3.0.3"
     doctrine "^2.1.0"
     has "^1.0.3"
-    jsx-ast-utils "^2.0.1"
+    jsx-ast-utils "^2.1.0"
+    object.entries "^1.1.0"
     object.fromentries "^2.0.0"
-    prop-types "^15.6.2"
-    resolve "^1.9.0"
+    object.values "^1.1.0"
+    prop-types "^15.7.2"
+    resolve "^1.10.1"
 
-eslint-scope@3.7.1:
-  version "3.7.1"
-  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
-  integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=
+eslint-scope@^4.0.3:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
+  integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==
   dependencies:
     esrecurse "^4.1.0"
     estraverse "^4.1.1"
 
-eslint-scope@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172"
-  integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==
+eslint-scope@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
+  integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==
   dependencies:
     esrecurse "^4.1.0"
     estraverse "^4.1.1"
 
-eslint-utils@^1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512"
-  integrity sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==
+eslint-utils@^1.4.2:
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab"
+  integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==
+  dependencies:
+    eslint-visitor-keys "^1.0.0"
 
-eslint-visitor-keys@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
-  integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==
+eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
+  integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
 
 eslint@^2.7.0:
   version "2.13.1"
@@ -3750,48 +3917,48 @@ eslint@^2.7.0:
     text-table "~0.2.0"
     user-home "^2.0.0"
 
-eslint@^5.11.1:
-  version "5.11.1"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.11.1.tgz#8deda83db9f354bf9d3f53f9677af7e0e13eadda"
-  integrity sha512-gOKhM8JwlFOc2acbOrkYR05NW8M6DCMSvfcJiBB5NDxRE1gv8kbvxKaC9u69e6ZGEMWXcswA/7eKR229cEIpvg==
+eslint@^6.5.0:
+  version "6.5.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.5.0.tgz#304623eec903969dd5c9f2d61c6ce3d6ecec8750"
+  integrity sha512-IIbSW+vKOqMatPmS9ayyku4tvWxHY2iricSRtOz6+ZA5IPRlgXzEL0u/j6dr4eha0ugmhMwDTqxtmNu3kj9O4w==
   dependencies:
     "@babel/code-frame" "^7.0.0"
-    ajv "^6.5.3"
+    ajv "^6.10.0"
     chalk "^2.1.0"
     cross-spawn "^6.0.5"
     debug "^4.0.1"
-    doctrine "^2.1.0"
-    eslint-scope "^4.0.0"
-    eslint-utils "^1.3.1"
-    eslint-visitor-keys "^1.0.0"
-    espree "^5.0.0"
+    doctrine "^3.0.0"
+    eslint-scope "^5.0.0"
+    eslint-utils "^1.4.2"
+    eslint-visitor-keys "^1.1.0"
+    espree "^6.1.1"
     esquery "^1.0.1"
     esutils "^2.0.2"
-    file-entry-cache "^2.0.0"
+    file-entry-cache "^5.0.1"
     functional-red-black-tree "^1.0.1"
-    glob "^7.1.2"
+    glob-parent "^5.0.0"
     globals "^11.7.0"
     ignore "^4.0.6"
+    import-fresh "^3.0.0"
     imurmurhash "^0.1.4"
-    inquirer "^6.1.0"
-    js-yaml "^3.12.0"
+    inquirer "^6.4.1"
+    is-glob "^4.0.0"
+    js-yaml "^3.13.1"
     json-stable-stringify-without-jsonify "^1.0.1"
     levn "^0.3.0"
-    lodash "^4.17.5"
+    lodash "^4.17.14"
     minimatch "^3.0.4"
     mkdirp "^0.5.1"
     natural-compare "^1.4.0"
     optionator "^0.8.2"
-    path-is-inside "^1.0.2"
-    pluralize "^7.0.0"
     progress "^2.0.0"
     regexpp "^2.0.1"
-    require-uncached "^1.0.3"
-    semver "^5.5.1"
-    strip-ansi "^4.0.0"
-    strip-json-comments "^2.0.1"
-    table "^5.0.2"
+    semver "^6.1.2"
+    strip-ansi "^5.2.0"
+    strip-json-comments "^3.0.1"
+    table "^5.2.3"
     text-table "^0.2.0"
+    v8-compile-cache "^2.0.3"
 
 espree@^3.1.6:
   version "3.5.4"
@@ -3801,14 +3968,14 @@ espree@^3.1.6:
     acorn "^5.5.0"
     acorn-jsx "^3.0.0"
 
-espree@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.0.tgz#fc7f984b62b36a0f543b13fb9cd7b9f4a7f5b65c"
-  integrity sha512-1MpUfwsdS9MMoN7ZXqAr9e9UKdVHDcvrJpyx7mm1WuQlx/ygErEQBzgi5Nh5qBHIoYweprhtMkTCb9GhcAIcsA==
+espree@^6.1.1:
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.1.tgz#7f80e5f7257fc47db450022d723e356daeb1e5de"
+  integrity sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==
   dependencies:
-    acorn "^6.0.2"
-    acorn-jsx "^5.0.0"
-    eslint-visitor-keys "^1.0.0"
+    acorn "^7.0.0"
+    acorn-jsx "^5.0.2"
+    eslint-visitor-keys "^1.1.0"
 
 esprima@^3.1.3:
   version "3.1.3"
@@ -3835,9 +4002,9 @@ esrecurse@^4.1.0:
     estraverse "^4.1.0"
 
 estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
-  integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
+  integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
 
 esutils@^2.0.0, esutils@^2.0.2:
   version "2.0.2"
@@ -3857,15 +4024,15 @@ event-emitter@~0.3.5:
     d "1"
     es5-ext "~0.10.14"
 
-eventemitter3@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
-  integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==
+eventemitter3@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb"
+  integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==
 
-events@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
-  integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
+events@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
+  integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==
 
 eventsource@^1.0.7:
   version "1.0.7"
@@ -3935,17 +4102,17 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
   dependencies:
     homedir-polyfill "^1.0.1"
 
-expect@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/expect/-/expect-24.8.0.tgz#471f8ec256b7b6129ca2524b2a62f030df38718d"
-  integrity sha512-/zYvP8iMDrzaaxHVa724eJBCKqSHmO0FA7EDkBiRHxg6OipmMn1fN+C8T9L9K8yr7UONkOifu6+LLH+z76CnaA==
+expect@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca"
+  integrity sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==
   dependencies:
-    "@jest/types" "^24.8.0"
+    "@jest/types" "^24.9.0"
     ansi-styles "^3.2.0"
-    jest-get-type "^24.8.0"
-    jest-matcher-utils "^24.8.0"
-    jest-message-util "^24.8.0"
-    jest-regex-util "^24.3.0"
+    jest-get-type "^24.9.0"
+    jest-matcher-utils "^24.9.0"
+    jest-message-util "^24.9.0"
+    jest-regex-util "^24.9.0"
 
 express@^4.16.3, express@^4.17.1:
   version "4.17.1"
@@ -4003,7 +4170,7 @@ extend@~3.0.2:
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
   integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
 
-external-editor@^3.0.0:
+external-editor@^3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27"
   integrity sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==
@@ -4059,9 +4226,9 @@ faye-websocket@^0.10.0:
     websocket-driver ">=0.5.1"
 
 faye-websocket@~0.11.1:
-  version "0.11.1"
-  resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38"
-  integrity sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=
+  version "0.11.3"
+  resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"
+  integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==
   dependencies:
     websocket-driver ">=0.5.1"
 
@@ -4113,21 +4280,25 @@ file-entry-cache@^1.1.1:
     flat-cache "^1.2.1"
     object-assign "^4.0.1"
 
-file-entry-cache@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361"
-  integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=
+file-entry-cache@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c"
+  integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==
   dependencies:
-    flat-cache "^1.2.1"
-    object-assign "^4.0.1"
+    flat-cache "^2.0.1"
 
-file-loader@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.0.0.tgz#c3570783fefb6e1bc0978a856f4bf5825b966c2a"
-  integrity sha512-roAbL6IdSGczwfXxhMi6Zq+jD4IfUpL0jWHD7fvmjdOVb7xBfdRUHe4LpBgO23VtVK5AW1OlWZo0p34Jvx3iWg==
+file-loader@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.2.0.tgz#5fb124d2369d7075d70a9a5abecd12e60a95215e"
+  integrity sha512-+xZnaK5R8kBJrHK0/6HRlrKNamvVS5rjyuju+rnyxRGuwUJwpAMsVzUl5dz6rK8brkzjV6JpcFNjp6NqV0g1OQ==
   dependencies:
-    loader-utils "^1.2.2"
-    schema-utils "^1.0.0"
+    loader-utils "^1.2.3"
+    schema-utils "^2.0.0"
+
+file-type@^10.5.0:
+  version "10.11.0"
+  resolved "https://registry.yarnpkg.com/file-type/-/file-type-10.11.0.tgz#2961d09e4675b9fb9a3ee6b69e9cd23f43fd1890"
+  integrity sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==
 
 filesize@^3.6.1:
   version "3.6.1"
@@ -4144,6 +4315,13 @@ fill-range@^4.0.0:
     repeat-string "^1.6.1"
     to-regex-range "^2.1.0"
 
+fill-range@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+  integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+  dependencies:
+    to-regex-range "^5.0.1"
+
 finalhandler@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
@@ -4157,15 +4335,24 @@ finalhandler@~1.1.2:
     statuses "~1.5.0"
     unpipe "~1.0.0"
 
-find-cache-dir@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d"
-  integrity sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==
+find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
+  integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
   dependencies:
     commondir "^1.0.1"
-    make-dir "^1.0.0"
+    make-dir "^2.0.0"
     pkg-dir "^3.0.0"
 
+find-cache-dir@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.0.0.tgz#cd4b7dd97b7185b7e17dbfe2d6e4115ee3eeb8fc"
+  integrity sha512-t7ulV1fmbxh5G9l/492O1p5+EBbr3uwpt6odhFTMc+nWyhmbloe+ja9BZ8pIBtqFWhOmCWVjx+pTW4zDkFoclw==
+  dependencies:
+    commondir "^1.0.1"
+    make-dir "^3.0.0"
+    pkg-dir "^4.1.0"
+
 find-root@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
@@ -4185,13 +4372,21 @@ find-up@^3.0.0:
   dependencies:
     locate-path "^3.0.0"
 
-findup-sync@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc"
-  integrity sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=
+find-up@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
+  integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
+  dependencies:
+    locate-path "^5.0.0"
+    path-exists "^4.0.0"
+
+findup-sync@3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1"
+  integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==
   dependencies:
     detect-file "^1.0.0"
-    is-glob "^3.1.0"
+    is-glob "^4.0.0"
     micromatch "^3.0.4"
     resolve-dir "^1.0.1"
 
@@ -4205,13 +4400,27 @@ flat-cache@^1.2.1:
     rimraf "~2.6.2"
     write "^0.2.1"
 
+flat-cache@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
+  integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==
+  dependencies:
+    flatted "^2.0.0"
+    rimraf "2.6.3"
+    write "1.0.3"
+
+flatted@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916"
+  integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==
+
 flush-write-stream@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd"
-  integrity sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
+  integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==
   dependencies:
-    inherits "^2.0.1"
-    readable-stream "^2.0.4"
+    inherits "^2.0.3"
+    readable-stream "^2.3.6"
 
 follow-redirects@1.5.10:
   version "1.5.10"
@@ -4221,11 +4430,11 @@ follow-redirects@1.5.10:
     debug "=3.1.0"
 
 follow-redirects@^1.0.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.6.0.tgz#d12452c031e8c67eb6637d861bfc7a8090167933"
-  integrity sha512-4Oh4eI3S9OueVV41AgJ1oLjpaJUhbJ7JDGOMhe0AFqoSejl5Q2nn3eGglAzRUKVKZE8jG5MNn66TjCJMAnpsWA==
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.9.0.tgz#8d5bcdc65b7108fe1508649c79c12d732dcedb4f"
+  integrity sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==
   dependencies:
-    debug "=3.1.0"
+    debug "^3.0.0"
 
 font-awesome@^4.7.0:
   version "4.7.0"
@@ -4314,11 +4523,11 @@ fs-extra@^8.0.1:
     universalify "^0.1.0"
 
 fs-minipass@^1.2.5:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
-  integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
+  integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==
   dependencies:
-    minipass "^2.2.1"
+    minipass "^2.6.0"
 
 fs-write-stream-atomic@^1.0.8:
   version "1.0.10"
@@ -4343,6 +4552,11 @@ fsevents@^1.2.7:
     nan "^2.12.1"
     node-pre-gyp "^0.12.0"
 
+fsevents@^2.0.6:
+  version "2.0.7"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a"
+  integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==
+
 function-bind@^1.0.2, function-bind@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@@ -4400,6 +4614,11 @@ get-caller-file@^1.0.1:
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
   integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
 
+get-caller-file@^2.0.1:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+  integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
 get-stream@^4.0.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
@@ -4427,7 +4646,14 @@ glob-parent@^3.1.0:
     is-glob "^3.1.0"
     path-dirname "^1.0.0"
 
-glob@^7.0.0, glob@~7.1.1:
+glob-parent@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954"
+  integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==
+  dependencies:
+    is-glob "^4.0.1"
+
+glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1:
   version "7.1.4"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
   integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
@@ -4439,17 +4665,12 @@ glob@^7.0.0, glob@~7.1.1:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3:
-  version "7.1.3"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
-  integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
+global-modules@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
+  integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==
   dependencies:
-    fs.realpath "^1.0.0"
-    inflight "^1.0.4"
-    inherits "2"
-    minimatch "^3.0.4"
-    once "^1.3.0"
-    path-is-absolute "^1.0.0"
+    global-prefix "^3.0.0"
 
 global-modules@^1.0.0:
   version "1.0.0"
@@ -4471,16 +4692,20 @@ global-prefix@^1.0.1:
     is-windows "^1.0.1"
     which "^1.2.14"
 
-globals@^11.1.0:
+global-prefix@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97"
+  integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==
+  dependencies:
+    ini "^1.3.5"
+    kind-of "^6.0.2"
+    which "^1.3.1"
+
+globals@^11.1.0, globals@^11.7.0:
   version "11.12.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
   integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
 
-globals@^11.7.0:
-  version "11.9.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-11.9.0.tgz#bde236808e987f290768a93d065060d78e6ab249"
-  integrity sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==
-
 globals@^9.2.0:
   version "9.18.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
@@ -4497,6 +4722,18 @@ globby@^6.1.0:
     pify "^2.0.0"
     pinkie-promise "^2.0.0"
 
+globby@^7.1.1:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680"
+  integrity sha1-+yzP+UAfhgCUXfral0QMypcrhoA=
+  dependencies:
+    array-union "^1.0.1"
+    dir-glob "^2.0.0"
+    glob "^7.1.2"
+    ignore "^3.3.5"
+    pify "^3.0.0"
+    slash "^1.0.0"
+
 globule@^1.0.0:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d"
@@ -4513,7 +4750,12 @@ gonzales-pe-sl@^4.2.3:
   dependencies:
     minimist "1.1.x"
 
-graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
+graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02"
+  integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==
+
+graceful-fs@^4.1.6:
   version "4.1.15"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
   integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
@@ -4524,24 +4766,24 @@ growly@^1.3.0:
   integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
 
 gzip-size@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.0.0.tgz#a55ecd99222f4c48fd8c01c625ce3b349d0a0e80"
-  integrity sha512-5iI7omclyqrnWw4XbXAmGhPsABkSIDQonv2K0h61lybgofWa6iZyvrI3r2zsJH4P8Nb64fFVzlvfhs0g7BBxAA==
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274"
+  integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==
   dependencies:
     duplexer "^0.1.1"
-    pify "^3.0.0"
+    pify "^4.0.1"
 
 handle-thing@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754"
   integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==
 
-handlebars@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.0.tgz#0d6a6f34ff1f63cecec8423aa4169827bf787c3a"
-  integrity sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==
+handlebars@^4.1.2:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.2.0.tgz#57ce8d2175b9bbb3d8b3cf3e4217b1aec8ddcb2e"
+  integrity sha512-Kb4xn5Qh1cxAKvQnzNWZ512DhABzyFNmsaJf3OAkWNa4NkaqWcNI8Tao8Tasi0/F4JD9oyG0YxuFyvyR57d+Gw==
   dependencies:
-    async "^2.5.0"
+    neo-async "^2.6.0"
     optimist "^0.6.1"
     source-map "^0.6.1"
   optionalDependencies:
@@ -4646,6 +4888,18 @@ hex-color-regex@^1.1.0:
   resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
   integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
 
+history@^4.10.1:
+  version "4.10.1"
+  resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
+  integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
+  dependencies:
+    "@babel/runtime" "^7.1.2"
+    loose-envify "^1.2.0"
+    resolve-pathname "^3.0.0"
+    tiny-invariant "^1.0.2"
+    tiny-warning "^1.0.0"
+    value-equal "^1.0.1"
+
 history@^4.7.2:
   version "4.7.2"
   resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b"
@@ -4685,7 +4939,7 @@ homedir-polyfill@^1.0.1:
   dependencies:
     parse-passwd "^1.0.0"
 
-hoopy@^0.1.2:
+hoopy@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
   integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==
@@ -4756,7 +5010,7 @@ http-deceiver@^1.2.7:
   resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
   integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=
 
-http-errors@1.7.2, http-errors@~1.7.2:
+http-errors@1.7.2:
   version "1.7.2"
   resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
   integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
@@ -4777,15 +5031,26 @@ http-errors@~1.6.2:
     setprototypeof "1.1.0"
     statuses ">= 1.4.0 < 2"
 
+http-errors@~1.7.2:
+  version "1.7.3"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
+  integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
+  dependencies:
+    depd "~1.1.2"
+    inherits "2.0.4"
+    setprototypeof "1.1.1"
+    statuses ">= 1.5.0 < 2"
+    toidentifier "1.0.0"
+
 http-link-header@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-1.0.2.tgz#bea50f02e1c7996021f1013b428c63f77e0f4e11"
   integrity sha512-z6YOZ8ZEnejkcCWlGZzYXNa6i+ZaTfiTg3WhlV/YvnNya3W/RbX1bMVUMTuCrg/DrtTCQxaFCkXCz4FtLpcebg==
 
-http-parser-js@>=0.4.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.0.tgz#d65edbede84349d0dc30320815a15d39cc3cbbd8"
-  integrity sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==
+"http-parser-js@>=0.4.0 <0.4.11":
+  version "0.4.10"
+  resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4"
+  integrity sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=
 
 http-proxy-middleware@^0.19.1:
   version "0.19.1"
@@ -4798,11 +5063,11 @@ http-proxy-middleware@^0.19.1:
     micromatch "^3.1.10"
 
 http-proxy@^1.17.0:
-  version "1.17.0"
-  resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a"
-  integrity sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==
+  version "1.18.0"
+  resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a"
+  integrity sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==
   dependencies:
-    eventemitter3 "^3.0.0"
+    eventemitter3 "^4.0.0"
     follow-redirects "^1.0.0"
     requires-port "^1.0.0"
 
@@ -4827,22 +5092,22 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
-icss-replace-symbols@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
-  integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=
-
-icss-utils@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.0.tgz#339dbbffb9f8729a243b701e1c29d4cc58c52f0e"
-  integrity sha512-3DEun4VOeMvSczifM3F2cKQrDQ5Pj6WKhkOq6HD4QTnDUAq8MQRxy5TX6Sy1iY6WPBe4gQ3p5vTECjbIkglkkQ==
+icss-utils@^4.0.0, icss-utils@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467"
+  integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==
   dependencies:
     postcss "^7.0.14"
 
+idb-keyval@^3.1.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-3.2.0.tgz#cbbf354deb5684b6cdc84376294fc05932845bd6"
+  integrity sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ==
+
 ieee754@^1.1.4:
-  version "1.1.12"
-  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
-  integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==
+  version "1.1.13"
+  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
+  integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
 
 iferr@^0.1.5:
   version "0.1.5"
@@ -4850,13 +5115,13 @@ iferr@^0.1.5:
   integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
 
 ignore-walk@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8"
-  integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.2.tgz#99d83a246c196ea5c93ef9315ad7b0819c35069b"
+  integrity sha512-EXyErtpHbn75ZTsOADsfx6J/FPo6/5cjev46PXrcTpd8z3BoRkXgYu9/JVqrI7tusjmwCZutGeRJeU0Wo1e4Cw==
   dependencies:
     minimatch "^3.0.4"
 
-ignore@^3.1.2:
+ignore@^3.1.2, ignore@^3.3.5:
   version "3.3.10"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
   integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==
@@ -4886,6 +5151,14 @@ import-fresh@^2.0.0:
     caller-path "^2.0.0"
     resolve-from "^3.0.0"
 
+import-fresh@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390"
+  integrity sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==
+  dependencies:
+    parent-module "^1.0.0"
+    resolve-from "^4.0.0"
+
 import-from@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1"
@@ -4893,7 +5166,7 @@ import-from@^2.1.0:
   dependencies:
     resolve-from "^3.0.0"
 
-import-local@^2.0.0:
+import-local@2.0.0, import-local@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
   integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==
@@ -4919,10 +5192,10 @@ indexes-of@^1.0.1:
   resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
   integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
 
-indexof@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
-  integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
+infer-owner@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
+  integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
 
 inflight@^1.0.4:
   version "1.0.6"
@@ -4932,17 +5205,22 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
-  integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
 
 inherits@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
   integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
 
-ini@^1.3.4, ini@~1.3.0:
+inherits@2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+  integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
+
+ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
   integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
@@ -4966,23 +5244,23 @@ inquirer@^0.12.0:
     strip-ansi "^3.0.0"
     through "^2.3.6"
 
-inquirer@^6.1.0:
-  version "6.2.1"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.1.tgz#9943fc4882161bdb0b0c9276769c75b32dbfcd52"
-  integrity sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==
+inquirer@^6.4.1:
+  version "6.5.0"
+  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.0.tgz#2303317efc9a4ea7ec2e2df6f86569b734accf42"
+  integrity sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==
   dependencies:
-    ansi-escapes "^3.0.0"
-    chalk "^2.0.0"
+    ansi-escapes "^3.2.0"
+    chalk "^2.4.2"
     cli-cursor "^2.1.0"
     cli-width "^2.0.0"
-    external-editor "^3.0.0"
+    external-editor "^3.0.3"
     figures "^2.0.0"
-    lodash "^4.17.10"
+    lodash "^4.17.12"
     mute-stream "0.0.7"
     run-async "^2.2.0"
-    rxjs "^6.1.0"
+    rxjs "^6.4.0"
     string-width "^2.1.0"
-    strip-ansi "^5.0.0"
+    strip-ansi "^5.1.0"
     through "^2.3.6"
 
 internal-ip@^4.3.0:
@@ -4993,7 +5271,7 @@ internal-ip@^4.3.0:
     default-gateway "^4.2.0"
     ipaddr.js "^1.9.0"
 
-interpret@^1.1.0:
+interpret@1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
   integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==
@@ -5025,13 +5303,18 @@ intl-messageformat@^2.0.0, intl-messageformat@^2.1.0, intl-messageformat@^2.2.0:
   dependencies:
     intl-messageformat-parser "1.4.0"
 
-intl-relativeformat@^2.1.0, intl-relativeformat@^2.2.0:
+intl-relativeformat@^2.1.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-2.2.0.tgz#6aca95d019ec8d30b6c5653b6629f9983ea5b6c5"
   integrity sha512-4bV/7kSKaPEmu6ArxXf9xjv1ny74Zkwuey8Pm01NH4zggPP7JHwg2STk8Y3JdspCKRDriwIyLRfEXnj2ZLr4Bw==
   dependencies:
     intl-messageformat "^2.0.0"
 
+intl-relativeformat@^6.4.3:
+  version "6.4.3"
+  resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-6.4.3.tgz#cb5559e1e257cc2e763583502012a354bb777efe"
+  integrity sha512-VxZXZfhuX/zBVfxzE/J6kPUpsyWKYjqtZ3jVGZwr6wzK5BOLVpe1vSlwCQX56w5UjlpL63fS8Nxq0kgTyf1gJA==
+
 intl@^1.2.5:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/intl/-/intl-1.2.5.tgz#82244a2190c4e419f8371f5aa34daa3420e2abde"
@@ -5059,16 +5342,26 @@ ip@^1.1.0, ip@^1.1.5:
   resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
   integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
 
-ipaddr.js@1.9.0, ipaddr.js@^1.9.0:
+ipaddr.js@1.9.0:
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
   integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
 
+ipaddr.js@^1.9.0:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+  integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
+
 is-absolute-url@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
   integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=
 
+is-absolute-url@^3.0.2:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698"
+  integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==
+
 is-accessor-descriptor@^0.1.6:
   version "0.1.6"
   resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
@@ -5083,6 +5376,11 @@ is-accessor-descriptor@^1.0.0:
   dependencies:
     kind-of "^6.0.0"
 
+is-arguments@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3"
+  integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==
+
 is-arrayish@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@@ -5100,6 +5398,13 @@ is-binary-path@^1.0.0:
   dependencies:
     binary-extensions "^1.0.0"
 
+is-binary-path@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+  integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+  dependencies:
+    binary-extensions "^2.0.0"
+
 is-boolean-object@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93"
@@ -5229,10 +5534,10 @@ is-glob@^3.1.0:
   dependencies:
     is-extglob "^2.1.0"
 
-is-glob@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0"
-  integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=
+is-glob@^4.0.0, is-glob@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
+  integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
   dependencies:
     is-extglob "^2.1.1"
 
@@ -5271,15 +5576,20 @@ is-number@^3.0.0:
   dependencies:
     kind-of "^3.0.2"
 
+is-number@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+  integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
 is-obj@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
   integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
 
 is-path-cwd@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.1.0.tgz#2e0c7e463ff5b7a0eb60852d851a6809347a124c"
-  integrity sha512-Sc5j3/YnM8tDeyCsVeKlm/0p95075DyLmDEIkSgQ7mXkrOX+uTCtmQFm0CYzVyJwcCCmO3k8qfJt17SxQwB5Zw==
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb"
+  integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==
 
 is-path-in-cwd@^2.0.0:
   version "2.1.0"
@@ -5295,7 +5605,12 @@ is-path-inside@^2.1.0:
   dependencies:
     path-is-inside "^1.0.2"
 
-is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
+is-plain-obj@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
+  integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
+
+is-plain-object@^2.0.3, is-plain-object@^2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
   integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
@@ -5358,6 +5673,16 @@ is-typedarray@~1.0.0:
   resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
   integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
 
+is-url@1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.2.tgz#498905a593bf47cc2d9e7f738372bbf7696c7f26"
+  integrity sha1-SYkFpZO/R8wtnn9zg3K792lsfyY=
+
+is-url@^1.2.4:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
+  integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==
+
 is-windows@^1.0.0, is-windows@^1.0.1, is-windows@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
@@ -5446,73 +5771,73 @@ istanbul-lib-source-maps@^3.0.1:
     rimraf "^2.6.2"
     source-map "^0.6.1"
 
-istanbul-reports@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.1.1.tgz#72ef16b4ecb9a4a7bd0e2001e00f95d1eec8afa9"
-  integrity sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw==
+istanbul-reports@^2.2.6:
+  version "2.2.6"
+  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.6.tgz#7b4f2660d82b29303a8fe6091f8ca4bf058da1af"
+  integrity sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==
   dependencies:
-    handlebars "^4.1.0"
+    handlebars "^4.1.2"
 
-jest-changed-files@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.8.0.tgz#7e7eb21cf687587a85e50f3d249d1327e15b157b"
-  integrity sha512-qgANC1Yrivsq+UrLXsvJefBKVoCsKB0Hv+mBb6NMjjZ90wwxCDmU3hsCXBya30cH+LnPYjwgcU65i6yJ5Nfuug==
+jest-changed-files@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039"
+  integrity sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==
   dependencies:
-    "@jest/types" "^24.8.0"
+    "@jest/types" "^24.9.0"
     execa "^1.0.0"
     throat "^4.0.0"
 
-jest-cli@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.8.0.tgz#b075ac914492ed114fa338ade7362a301693e989"
-  integrity sha512-+p6J00jSMPQ116ZLlHJJvdf8wbjNbZdeSX9ptfHX06/MSNaXmKihQzx5vQcw0q2G6JsdVkUIdWbOWtSnaYs3yA==
+jest-cli@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.9.0.tgz#ad2de62d07472d419c6abc301fc432b98b10d2af"
+  integrity sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==
   dependencies:
-    "@jest/core" "^24.8.0"
-    "@jest/test-result" "^24.8.0"
-    "@jest/types" "^24.8.0"
+    "@jest/core" "^24.9.0"
+    "@jest/test-result" "^24.9.0"
+    "@jest/types" "^24.9.0"
     chalk "^2.0.1"
     exit "^0.1.2"
     import-local "^2.0.0"
     is-ci "^2.0.0"
-    jest-config "^24.8.0"
-    jest-util "^24.8.0"
-    jest-validate "^24.8.0"
+    jest-config "^24.9.0"
+    jest-util "^24.9.0"
+    jest-validate "^24.9.0"
     prompts "^2.0.1"
     realpath-native "^1.1.0"
-    yargs "^12.0.2"
+    yargs "^13.3.0"
 
-jest-config@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.8.0.tgz#77db3d265a6f726294687cbbccc36f8a76ee0f4f"
-  integrity sha512-Czl3Nn2uEzVGsOeaewGWoDPD8GStxCpAe0zOYs2x2l0fZAgPbCr3uwUkgNKV3LwE13VXythM946cd5rdGkkBZw==
+jest-config@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.9.0.tgz#fb1bbc60c73a46af03590719efa4825e6e4dd1b5"
+  integrity sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==
   dependencies:
     "@babel/core" "^7.1.0"
-    "@jest/test-sequencer" "^24.8.0"
-    "@jest/types" "^24.8.0"
-    babel-jest "^24.8.0"
+    "@jest/test-sequencer" "^24.9.0"
+    "@jest/types" "^24.9.0"
+    babel-jest "^24.9.0"
     chalk "^2.0.1"
     glob "^7.1.1"
-    jest-environment-jsdom "^24.8.0"
-    jest-environment-node "^24.8.0"
-    jest-get-type "^24.8.0"
-    jest-jasmine2 "^24.8.0"
+    jest-environment-jsdom "^24.9.0"
+    jest-environment-node "^24.9.0"
+    jest-get-type "^24.9.0"
+    jest-jasmine2 "^24.9.0"
     jest-regex-util "^24.3.0"
-    jest-resolve "^24.8.0"
-    jest-util "^24.8.0"
-    jest-validate "^24.8.0"
+    jest-resolve "^24.9.0"
+    jest-util "^24.9.0"
+    jest-validate "^24.9.0"
     micromatch "^3.1.10"
-    pretty-format "^24.8.0"
+    pretty-format "^24.9.0"
     realpath-native "^1.1.0"
 
-jest-diff@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.8.0.tgz#146435e7d1e3ffdf293d53ff97e193f1d1546172"
-  integrity sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g==
+jest-diff@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da"
+  integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==
   dependencies:
     chalk "^2.0.1"
-    diff-sequences "^24.3.0"
-    jest-get-type "^24.8.0"
-    pretty-format "^24.8.0"
+    diff-sequences "^24.9.0"
+    jest-get-type "^24.9.0"
+    pretty-format "^24.9.0"
 
 jest-docblock@^24.3.0:
   version "24.3.0"
@@ -5521,123 +5846,124 @@ jest-docblock@^24.3.0:
   dependencies:
     detect-newline "^2.1.0"
 
-jest-each@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.8.0.tgz#a05fd2bf94ddc0b1da66c6d13ec2457f35e52775"
-  integrity sha512-NrwK9gaL5+XgrgoCsd9svsoWdVkK4gnvyhcpzd6m487tXHqIdYeykgq3MKI1u4I+5Zf0tofr70at9dWJDeb+BA==
+jest-each@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.9.0.tgz#eb2da602e2a610898dbc5f1f6df3ba86b55f8b05"
+  integrity sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==
   dependencies:
-    "@jest/types" "^24.8.0"
+    "@jest/types" "^24.9.0"
     chalk "^2.0.1"
-    jest-get-type "^24.8.0"
-    jest-util "^24.8.0"
-    pretty-format "^24.8.0"
-
-jest-environment-jsdom@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz#300f6949a146cabe1c9357ad9e9ecf9f43f38857"
-  integrity sha512-qbvgLmR7PpwjoFjM/sbuqHJt/NCkviuq9vus9NBn/76hhSidO+Z6Bn9tU8friecegbJL8gzZQEMZBQlFWDCwAQ==
-  dependencies:
-    "@jest/environment" "^24.8.0"
-    "@jest/fake-timers" "^24.8.0"
-    "@jest/types" "^24.8.0"
-    jest-mock "^24.8.0"
-    jest-util "^24.8.0"
+    jest-get-type "^24.9.0"
+    jest-util "^24.9.0"
+    pretty-format "^24.9.0"
+
+jest-environment-jsdom@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b"
+  integrity sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==
+  dependencies:
+    "@jest/environment" "^24.9.0"
+    "@jest/fake-timers" "^24.9.0"
+    "@jest/types" "^24.9.0"
+    jest-mock "^24.9.0"
+    jest-util "^24.9.0"
     jsdom "^11.5.1"
 
-jest-environment-node@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.8.0.tgz#d3f726ba8bc53087a60e7a84ca08883a4c892231"
-  integrity sha512-vIGUEScd1cdDgR6sqn2M08sJTRLQp6Dk/eIkCeO4PFHxZMOgy+uYLPMC4ix3PEfM5Au/x3uQ/5Tl0DpXXZsJ/Q==
+jest-environment-node@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.9.0.tgz#333d2d2796f9687f2aeebf0742b519f33c1cbfd3"
+  integrity sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==
   dependencies:
-    "@jest/environment" "^24.8.0"
-    "@jest/fake-timers" "^24.8.0"
-    "@jest/types" "^24.8.0"
-    jest-mock "^24.8.0"
-    jest-util "^24.8.0"
+    "@jest/environment" "^24.9.0"
+    "@jest/fake-timers" "^24.9.0"
+    "@jest/types" "^24.9.0"
+    jest-mock "^24.9.0"
+    jest-util "^24.9.0"
 
-jest-get-type@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc"
-  integrity sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ==
+jest-get-type@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
+  integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==
 
-jest-haste-map@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.8.0.tgz#51794182d877b3ddfd6e6d23920e3fe72f305800"
-  integrity sha512-ZBPRGHdPt1rHajWelXdqygIDpJx8u3xOoLyUBWRW28r3tagrgoepPrzAozW7kW9HrQfhvmiv1tncsxqHJO1onQ==
+jest-haste-map@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d"
+  integrity sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==
   dependencies:
-    "@jest/types" "^24.8.0"
+    "@jest/types" "^24.9.0"
     anymatch "^2.0.0"
     fb-watchman "^2.0.0"
     graceful-fs "^4.1.15"
     invariant "^2.2.4"
-    jest-serializer "^24.4.0"
-    jest-util "^24.8.0"
-    jest-worker "^24.6.0"
+    jest-serializer "^24.9.0"
+    jest-util "^24.9.0"
+    jest-worker "^24.9.0"
     micromatch "^3.1.10"
     sane "^4.0.3"
     walker "^1.0.7"
   optionalDependencies:
     fsevents "^1.2.7"
 
-jest-jasmine2@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.8.0.tgz#a9c7e14c83dd77d8b15e820549ce8987cc8cd898"
-  integrity sha512-cEky88npEE5LKd5jPpTdDCLvKkdyklnaRycBXL6GNmpxe41F0WN44+i7lpQKa/hcbXaQ+rc9RMaM4dsebrYong==
+jest-jasmine2@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz#1f7b1bd3242c1774e62acabb3646d96afc3be6a0"
+  integrity sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==
   dependencies:
     "@babel/traverse" "^7.1.0"
-    "@jest/environment" "^24.8.0"
-    "@jest/test-result" "^24.8.0"
-    "@jest/types" "^24.8.0"
+    "@jest/environment" "^24.9.0"
+    "@jest/test-result" "^24.9.0"
+    "@jest/types" "^24.9.0"
     chalk "^2.0.1"
     co "^4.6.0"
-    expect "^24.8.0"
+    expect "^24.9.0"
     is-generator-fn "^2.0.0"
-    jest-each "^24.8.0"
-    jest-matcher-utils "^24.8.0"
-    jest-message-util "^24.8.0"
-    jest-runtime "^24.8.0"
-    jest-snapshot "^24.8.0"
-    jest-util "^24.8.0"
-    pretty-format "^24.8.0"
+    jest-each "^24.9.0"
+    jest-matcher-utils "^24.9.0"
+    jest-message-util "^24.9.0"
+    jest-runtime "^24.9.0"
+    jest-snapshot "^24.9.0"
+    jest-util "^24.9.0"
+    pretty-format "^24.9.0"
     throat "^4.0.0"
 
-jest-leak-detector@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.8.0.tgz#c0086384e1f650c2d8348095df769f29b48e6980"
-  integrity sha512-cG0yRSK8A831LN8lIHxI3AblB40uhv0z+SsQdW3GoMMVcK+sJwrIIyax5tu3eHHNJ8Fu6IMDpnLda2jhn2pD/g==
+jest-leak-detector@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz#b665dea7c77100c5c4f7dfcb153b65cf07dcf96a"
+  integrity sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==
   dependencies:
-    pretty-format "^24.8.0"
+    jest-get-type "^24.9.0"
+    pretty-format "^24.9.0"
 
-jest-matcher-utils@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz#2bce42204c9af12bde46f83dc839efe8be832495"
-  integrity sha512-lex1yASY51FvUuHgm0GOVj7DCYEouWSlIYmCW7APSqB9v8mXmKSn5+sWVF0MhuASG0bnYY106/49JU1FZNl5hw==
+jest-matcher-utils@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073"
+  integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==
   dependencies:
     chalk "^2.0.1"
-    jest-diff "^24.8.0"
-    jest-get-type "^24.8.0"
-    pretty-format "^24.8.0"
+    jest-diff "^24.9.0"
+    jest-get-type "^24.9.0"
+    pretty-format "^24.9.0"
 
-jest-message-util@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.8.0.tgz#0d6891e72a4beacc0292b638685df42e28d6218b"
-  integrity sha512-p2k71rf/b6ns8btdB0uVdljWo9h0ovpnEe05ZKWceQGfXYr4KkzgKo3PBi8wdnd9OtNh46VpNIJynUn/3MKm1g==
+jest-message-util@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3"
+  integrity sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==
   dependencies:
     "@babel/code-frame" "^7.0.0"
-    "@jest/test-result" "^24.8.0"
-    "@jest/types" "^24.8.0"
+    "@jest/test-result" "^24.9.0"
+    "@jest/types" "^24.9.0"
     "@types/stack-utils" "^1.0.1"
     chalk "^2.0.1"
     micromatch "^3.1.10"
     slash "^2.0.0"
     stack-utils "^1.0.1"
 
-jest-mock@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.8.0.tgz#2f9d14d37699e863f1febf4e4d5a33b7fdbbde56"
-  integrity sha512-6kWugwjGjJw+ZkK4mDa0Df3sDlUTsV47MSrT0nGQ0RBWJbpODDQ8MHDVtGtUYBne3IwZUhtB7elxHspU79WH3A==
+jest-mock@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6"
+  integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==
   dependencies:
-    "@jest/types" "^24.8.0"
+    "@jest/types" "^24.9.0"
 
 jest-pnp-resolver@^1.2.1:
   version "1.2.1"
@@ -5649,113 +5975,119 @@ jest-regex-util@^24.3.0:
   resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.3.0.tgz#d5a65f60be1ae3e310d5214a0307581995227b36"
   integrity sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==
 
-jest-resolve-dependencies@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.8.0.tgz#19eec3241f2045d3f990dba331d0d7526acff8e0"
-  integrity sha512-hyK1qfIf/krV+fSNyhyJeq3elVMhK9Eijlwy+j5jqmZ9QsxwKBiP6qukQxaHtK8k6zql/KYWwCTQ+fDGTIJauw==
+jest-regex-util@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.9.0.tgz#c13fb3380bde22bf6575432c493ea8fe37965636"
+  integrity sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==
+
+jest-resolve-dependencies@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz#ad055198959c4cfba8a4f066c673a3f0786507ab"
+  integrity sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==
   dependencies:
-    "@jest/types" "^24.8.0"
+    "@jest/types" "^24.9.0"
     jest-regex-util "^24.3.0"
-    jest-snapshot "^24.8.0"
+    jest-snapshot "^24.9.0"
 
-jest-resolve@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.8.0.tgz#84b8e5408c1f6a11539793e2b5feb1b6e722439f"
-  integrity sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw==
+jest-resolve@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.9.0.tgz#dff04c7687af34c4dd7e524892d9cf77e5d17321"
+  integrity sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==
   dependencies:
-    "@jest/types" "^24.8.0"
+    "@jest/types" "^24.9.0"
     browser-resolve "^1.11.3"
     chalk "^2.0.1"
     jest-pnp-resolver "^1.2.1"
     realpath-native "^1.1.0"
 
-jest-runner@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.8.0.tgz#4f9ae07b767db27b740d7deffad0cf67ccb4c5bb"
-  integrity sha512-utFqC5BaA3JmznbissSs95X1ZF+d+4WuOWwpM9+Ak356YtMhHE/GXUondZdcyAAOTBEsRGAgH/0TwLzfI9h7ow==
+jest-runner@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.9.0.tgz#574fafdbd54455c2b34b4bdf4365a23857fcdf42"
+  integrity sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==
   dependencies:
     "@jest/console" "^24.7.1"
-    "@jest/environment" "^24.8.0"
-    "@jest/test-result" "^24.8.0"
-    "@jest/types" "^24.8.0"
+    "@jest/environment" "^24.9.0"
+    "@jest/test-result" "^24.9.0"
+    "@jest/types" "^24.9.0"
     chalk "^2.4.2"
     exit "^0.1.2"
     graceful-fs "^4.1.15"
-    jest-config "^24.8.0"
+    jest-config "^24.9.0"
     jest-docblock "^24.3.0"
-    jest-haste-map "^24.8.0"
-    jest-jasmine2 "^24.8.0"
-    jest-leak-detector "^24.8.0"
-    jest-message-util "^24.8.0"
-    jest-resolve "^24.8.0"
-    jest-runtime "^24.8.0"
-    jest-util "^24.8.0"
+    jest-haste-map "^24.9.0"
+    jest-jasmine2 "^24.9.0"
+    jest-leak-detector "^24.9.0"
+    jest-message-util "^24.9.0"
+    jest-resolve "^24.9.0"
+    jest-runtime "^24.9.0"
+    jest-util "^24.9.0"
     jest-worker "^24.6.0"
     source-map-support "^0.5.6"
     throat "^4.0.0"
 
-jest-runtime@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.8.0.tgz#05f94d5b05c21f6dc54e427cd2e4980923350620"
-  integrity sha512-Mq0aIXhvO/3bX44ccT+czU1/57IgOMyy80oM0XR/nyD5zgBcesF84BPabZi39pJVA6UXw+fY2Q1N+4BiVUBWOA==
+jest-runtime@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.9.0.tgz#9f14583af6a4f7314a6a9d9f0226e1a781c8e4ac"
+  integrity sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==
   dependencies:
     "@jest/console" "^24.7.1"
-    "@jest/environment" "^24.8.0"
+    "@jest/environment" "^24.9.0"
     "@jest/source-map" "^24.3.0"
-    "@jest/transform" "^24.8.0"
-    "@jest/types" "^24.8.0"
-    "@types/yargs" "^12.0.2"
+    "@jest/transform" "^24.9.0"
+    "@jest/types" "^24.9.0"
+    "@types/yargs" "^13.0.0"
     chalk "^2.0.1"
     exit "^0.1.2"
     glob "^7.1.3"
     graceful-fs "^4.1.15"
-    jest-config "^24.8.0"
-    jest-haste-map "^24.8.0"
-    jest-message-util "^24.8.0"
-    jest-mock "^24.8.0"
+    jest-config "^24.9.0"
+    jest-haste-map "^24.9.0"
+    jest-message-util "^24.9.0"
+    jest-mock "^24.9.0"
     jest-regex-util "^24.3.0"
-    jest-resolve "^24.8.0"
-    jest-snapshot "^24.8.0"
-    jest-util "^24.8.0"
-    jest-validate "^24.8.0"
+    jest-resolve "^24.9.0"
+    jest-snapshot "^24.9.0"
+    jest-util "^24.9.0"
+    jest-validate "^24.9.0"
     realpath-native "^1.1.0"
     slash "^2.0.0"
     strip-bom "^3.0.0"
-    yargs "^12.0.2"
+    yargs "^13.3.0"
 
-jest-serializer@^24.4.0:
-  version "24.4.0"
-  resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.4.0.tgz#f70c5918c8ea9235ccb1276d232e459080588db3"
-  integrity sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==
+jest-serializer@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.9.0.tgz#e6d7d7ef96d31e8b9079a714754c5d5c58288e73"
+  integrity sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==
 
-jest-snapshot@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.8.0.tgz#3bec6a59da2ff7bc7d097a853fb67f9d415cb7c6"
-  integrity sha512-5ehtWoc8oU9/cAPe6fez6QofVJLBKyqkY2+TlKTOf0VllBB/mqUNdARdcjlZrs9F1Cv+/HKoCS/BknT0+tmfPg==
+jest-snapshot@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.9.0.tgz#ec8e9ca4f2ec0c5c87ae8f925cf97497b0e951ba"
+  integrity sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==
   dependencies:
     "@babel/types" "^7.0.0"
-    "@jest/types" "^24.8.0"
+    "@jest/types" "^24.9.0"
     chalk "^2.0.1"
-    expect "^24.8.0"
-    jest-diff "^24.8.0"
-    jest-matcher-utils "^24.8.0"
-    jest-message-util "^24.8.0"
-    jest-resolve "^24.8.0"
+    expect "^24.9.0"
+    jest-diff "^24.9.0"
+    jest-get-type "^24.9.0"
+    jest-matcher-utils "^24.9.0"
+    jest-message-util "^24.9.0"
+    jest-resolve "^24.9.0"
     mkdirp "^0.5.1"
     natural-compare "^1.4.0"
-    pretty-format "^24.8.0"
-    semver "^5.5.0"
-
-jest-util@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.8.0.tgz#41f0e945da11df44cc76d64ffb915d0716f46cd1"
-  integrity sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA==
-  dependencies:
-    "@jest/console" "^24.7.1"
-    "@jest/fake-timers" "^24.8.0"
-    "@jest/source-map" "^24.3.0"
-    "@jest/test-result" "^24.8.0"
-    "@jest/types" "^24.8.0"
+    pretty-format "^24.9.0"
+    semver "^6.2.0"
+
+jest-util@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162"
+  integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==
+  dependencies:
+    "@jest/console" "^24.9.0"
+    "@jest/fake-timers" "^24.9.0"
+    "@jest/source-map" "^24.9.0"
+    "@jest/test-result" "^24.9.0"
+    "@jest/types" "^24.9.0"
     callsites "^3.0.0"
     chalk "^2.0.1"
     graceful-fs "^4.1.15"
@@ -5764,29 +6096,29 @@ jest-util@^24.8.0:
     slash "^2.0.0"
     source-map "^0.6.0"
 
-jest-validate@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.8.0.tgz#624c41533e6dfe356ffadc6e2423a35c2d3b4849"
-  integrity sha512-+/N7VOEMW1Vzsrk3UWBDYTExTPwf68tavEPKDnJzrC6UlHtUDU/fuEdXqFoHzv9XnQ+zW6X3qMZhJ3YexfeLDA==
+jest-validate@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.9.0.tgz#0775c55360d173cd854e40180756d4ff52def8ab"
+  integrity sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==
   dependencies:
-    "@jest/types" "^24.8.0"
-    camelcase "^5.0.0"
+    "@jest/types" "^24.9.0"
+    camelcase "^5.3.1"
     chalk "^2.0.1"
-    jest-get-type "^24.8.0"
-    leven "^2.1.0"
-    pretty-format "^24.8.0"
-
-jest-watcher@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.8.0.tgz#58d49915ceddd2de85e238f6213cef1c93715de4"
-  integrity sha512-SBjwHt5NedQoVu54M5GEx7cl7IGEFFznvd/HNT8ier7cCAx/Qgu9ZMlaTQkvK22G1YOpcWBLQPFSImmxdn3DAw==
-  dependencies:
-    "@jest/test-result" "^24.8.0"
-    "@jest/types" "^24.8.0"
-    "@types/yargs" "^12.0.9"
+    jest-get-type "^24.9.0"
+    leven "^3.1.0"
+    pretty-format "^24.9.0"
+
+jest-watcher@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.9.0.tgz#4b56e5d1ceff005f5b88e528dc9afc8dd4ed2b3b"
+  integrity sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==
+  dependencies:
+    "@jest/test-result" "^24.9.0"
+    "@jest/types" "^24.9.0"
+    "@types/yargs" "^13.0.0"
     ansi-escapes "^3.0.0"
     chalk "^2.0.1"
-    jest-util "^24.8.0"
+    jest-util "^24.9.0"
     string-length "^2.0.0"
 
 jest-worker@^24.6.0:
@@ -5797,13 +6129,21 @@ jest-worker@^24.6.0:
     merge-stream "^1.0.1"
     supports-color "^6.1.0"
 
-jest@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/jest/-/jest-24.8.0.tgz#d5dff1984d0d1002196e9b7f12f75af1b2809081"
-  integrity sha512-o0HM90RKFRNWmAWvlyV8i5jGZ97pFwkeVoGvPW1EtLTgJc2+jcuqcbbqcSZLE/3f2S5pt0y2ZBETuhpWNl1Reg==
+jest-worker@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5"
+  integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==
+  dependencies:
+    merge-stream "^2.0.0"
+    supports-color "^6.1.0"
+
+jest@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/jest/-/jest-24.9.0.tgz#987d290c05a08b52c56188c1002e368edb007171"
+  integrity sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==
   dependencies:
     import-local "^2.0.0"
-    jest-cli "^24.8.0"
+    jest-cli "^24.9.0"
 
 js-base64@^2.1.9:
   version "2.5.0"
@@ -5913,9 +6253,9 @@ json-stringify-safe@~5.0.1:
   integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
 
 json3@^3.3.2:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
-  integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
+  integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==
 
 json5@^0.5.0:
   version "0.5.1"
@@ -5970,12 +6310,13 @@ jsprim@^1.2.2:
     json-schema "0.2.3"
     verror "1.10.0"
 
-jsx-ast-utils@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f"
-  integrity sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=
+jsx-ast-utils@^2.1.0, jsx-ast-utils@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz#4d4973ebf8b9d2837ee91a8208cc66f3a2776cfb"
+  integrity sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==
   dependencies:
     array-includes "^3.0.3"
+    object.assign "^4.1.0"
 
 keycode@^2.1.7:
   version "2.2.0"
@@ -6038,10 +6379,10 @@ left-pad@^1.3.0:
   resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
   integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==
 
-leven@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
-  integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA=
+leven@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
+  integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
 
 levn@^0.3.0, levn@~0.3.0:
   version "0.3.0"
@@ -6071,10 +6412,10 @@ load-json-file@^4.0.0:
     pify "^3.0.0"
     strip-bom "^3.0.0"
 
-loader-runner@^2.3.0:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.1.tgz#026f12fe7c3115992896ac02ba022ba92971b979"
-  integrity sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw==
+loader-runner@^2.4.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
+  integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
 
 loader-utils@0.2.x:
   version "0.2.17"
@@ -6086,7 +6427,7 @@ loader-utils@0.2.x:
     json5 "^0.5.0"
     object-assign "^4.0.1"
 
-loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.2, loader-utils@^1.2.3:
+loader-utils@1.2.3, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
   integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
@@ -6111,6 +6452,13 @@ locate-path@^3.0.0:
     p-locate "^3.0.0"
     path-exists "^3.0.0"
 
+locate-path@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
+  integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
+  dependencies:
+    p-locate "^4.1.0"
+
 lodash.capitalize@^4.1.0:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9"
@@ -6181,15 +6529,15 @@ lodash.uniq@^4.5.0:
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
-lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.7.11, lodash@~4.17.10:
-  version "4.17.11"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
-  integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
+lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.3.0, lodash@~4.17.10:
+  version "4.17.15"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
+  integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
 
-loglevel@^1.6.2:
-  version "1.6.2"
-  resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.2.tgz#668c77948a03dbd22502a3513ace1f62a80cc372"
-  integrity sha512-Jt2MHrCNdtIe1W6co3tF5KXGRkzF+TYffiQstfXa04mrss9IKXzAAXYWak8LbZseAQY03sH2GzMCMU0ZOUc9bg==
+loglevel@^1.6.4:
+  version "1.6.4"
+  resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.4.tgz#f408f4f006db8354d0577dcf6d33485b3cb90d56"
+  integrity sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g==
 
 loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
   version "1.4.0"
@@ -6205,13 +6553,28 @@ lru-cache@^5.1.1:
   dependencies:
     yallist "^3.0.2"
 
-make-dir@^1.0.0, make-dir@^1.3.0:
+make-dir@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
   integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
   dependencies:
     pify "^3.0.0"
 
+make-dir@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
+  integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
+  dependencies:
+    pify "^4.0.1"
+    semver "^5.6.0"
+
+make-dir@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.0.tgz#1b5f39f6b9270ed33f9f054c5c0f84304989f801"
+  integrity sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==
+  dependencies:
+    semver "^6.0.0"
+
 makeerror@1.0.x:
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
@@ -6273,20 +6636,20 @@ media-typer@0.3.0:
   integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
 
 mem@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/mem/-/mem-4.0.0.tgz#6437690d9471678f6cc83659c00cbafcd6b0cdaf"
-  integrity sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
+  integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==
   dependencies:
     map-age-cleaner "^0.1.1"
-    mimic-fn "^1.0.0"
-    p-is-promise "^1.1.0"
+    mimic-fn "^2.0.0"
+    p-is-promise "^2.0.0"
 
 memoize-one@^5.0.0:
   version "5.0.4"
   resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.4.tgz#005928aced5c43d890a4dfab18ca908b0ec92cbc"
   integrity sha512-P0z5IeAH6qHHGkJIXWw0xC2HNEgkx/9uWWBQw64FJj3/ol14VYdfVGWWr0fXfjhhv3TKVIqUq65os6O4GUNksA==
 
-memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1:
+memory-fs@^0.4.0, memory-fs@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
   integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=
@@ -6306,6 +6669,11 @@ merge-stream@^1.0.1:
   dependencies:
     readable-stream "^2.0.1"
 
+merge-stream@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+  integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
 merge@^1.2.0:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
@@ -6316,7 +6684,7 @@ methods@~1.1.2:
   resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
   integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
 
-micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8:
+micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4:
   version "3.1.10"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
   integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
@@ -6343,24 +6711,29 @@ miller-rabin@^4.0.0:
     bn.js "^4.0.0"
     brorand "^1.0.1"
 
-mime-db@1.40.0, "mime-db@>= 1.40.0 < 2":
+mime-db@1.40.0:
   version "1.40.0"
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
   integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
 
+"mime-db@>= 1.40.0 < 2":
+  version "1.42.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac"
+  integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==
+
 mime-db@~1.37.0:
   version "1.37.0"
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8"
   integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==
 
-mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19:
+mime-types@^2.1.12, mime-types@~2.1.19:
   version "2.1.21"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96"
   integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==
   dependencies:
     mime-db "~1.37.0"
 
-mime-types@~2.1.24:
+mime-types@~2.1.17, mime-types@~2.1.24:
   version "2.1.24"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
   integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
@@ -6372,22 +6745,28 @@ mime@1.6.0:
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
   integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
 
-mime@^2.4.2:
-  version "2.4.3"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.3.tgz#229687331e86f68924e6cb59e1cdd937f18275fe"
-  integrity sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==
+mime@^2.4.4:
+  version "2.4.4"
+  resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5"
+  integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==
 
 mimic-fn@^1.0.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
   integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
 
-mini-css-extract-plugin@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz#ac0059b02b9692515a637115b0cc9fed3a35c7b0"
-  integrity sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw==
+mimic-fn@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+  integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+
+mini-css-extract-plugin@^0.8.0:
+  version "0.8.0"
+  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz#81d41ec4fe58c713a96ad7c723cdb2d0bd4d70e1"
+  integrity sha512-MNpRGbNA52q6U92i0qbVpQNsgk7LExy41MdAlG84FeytfDOtRIf/mCHdEgG8rpTKOaNKiqUnZdlptF469hxqOw==
   dependencies:
     loader-utils "^1.1.0"
+    normalize-url "1.9.1"
     schema-utils "^1.0.0"
     webpack-sources "^1.1.0"
 
@@ -6428,20 +6807,20 @@ minimist@~0.0.1:
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
   integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
 
-minipass@^2.2.1, minipass@^2.3.4:
-  version "2.3.5"
-  resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
-  integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==
+minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0:
+  version "2.9.0"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6"
+  integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==
   dependencies:
     safe-buffer "^5.1.2"
     yallist "^3.0.0"
 
-minizlib@^1.1.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614"
-  integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==
+minizlib@^1.2.1:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
+  integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==
   dependencies:
-    minipass "^2.2.1"
+    minipass "^2.9.0"
 
 mississippi@^3.0.0:
   version "3.0.0"
@@ -6460,9 +6839,9 @@ mississippi@^3.0.0:
     through2 "^2.0.0"
 
 mixin-deep@^1.2.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
-  integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
+  integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
   dependencies:
     for-in "^1.0.2"
     is-extendable "^1.0.1"
@@ -6475,7 +6854,7 @@ mixin-object@^2.0.1:
     for-in "^0.1.3"
     is-extendable "^0.1.1"
 
-mkdirp@0.5.x, mkdirp@^0.5, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5.x, mkdirp@^0.5, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
   integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
@@ -6509,11 +6888,16 @@ ms@2.0.0:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
   integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
 
-ms@2.1.1, ms@^2.1.1:
+ms@2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
   integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
 
+ms@^2.1.1:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+  integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
 multicast-dns-service-types@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"
@@ -6576,11 +6960,11 @@ nearley@^2.7.10:
     semver "^5.4.1"
 
 needle@^2.2.1:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e"
-  integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
+  integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==
   dependencies:
-    debug "^2.1.2"
+    debug "^3.2.6"
     iconv-lite "^0.4.4"
     sax "^1.2.4"
 
@@ -6589,12 +6973,12 @@ negotiator@0.6.2:
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
   integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
 
-neo-async@^2.5.0:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835"
-  integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==
+neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
+  integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
 
-next-tick@1, next-tick@^1.0.0:
+next-tick@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
   integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
@@ -6612,20 +6996,20 @@ node-fetch@^1.0.1:
     encoding "^0.1.11"
     is-stream "^1.0.1"
 
-node-forge@0.7.5:
-  version "0.7.5"
-  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df"
-  integrity sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==
+node-forge@0.8.2:
+  version "0.8.2"
+  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.2.tgz#b4bcc59fb12ce77a8825fc6a783dfe3182499c5a"
+  integrity sha512-mXQ9GBq1N3uDCyV1pdSzgIguwgtVpM7f5/5J4ipz12PKWElmPpVWLDuWl8iXmhysr21+WmX/OJ5UKx82wjomgg==
 
 node-int64@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
   integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=
 
-node-libs-browser@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df"
-  integrity sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==
+node-libs-browser@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
+  integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==
   dependencies:
     assert "^1.1.1"
     browserify-zlib "^0.2.0"
@@ -6634,10 +7018,10 @@ node-libs-browser@^2.0.0:
     constants-browserify "^1.0.0"
     crypto-browserify "^3.11.0"
     domain-browser "^1.1.1"
-    events "^1.0.0"
+    events "^3.0.0"
     https-browserify "^1.0.0"
     os-browserify "^0.3.0"
-    path-browserify "0.0.0"
+    path-browserify "0.0.1"
     process "^0.11.10"
     punycode "^1.2.4"
     querystring-es3 "^0.2.0"
@@ -6648,20 +7032,21 @@ node-libs-browser@^2.0.0:
     timers-browserify "^2.0.4"
     tty-browserify "0.0.0"
     url "^0.11.0"
-    util "^0.10.3"
-    vm-browserify "0.0.4"
+    util "^0.11.0"
+    vm-browserify "^1.0.1"
 
 node-modules-regexp@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40"
   integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
 
-node-notifier@^5.2.1:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.3.0.tgz#c77a4a7b84038733d5fb351aafd8a268bfe19a01"
-  integrity sha512-AhENzCSGZnZJgBARsUjnQ7DnZbzyP+HxlVXuD0xqAnvL8q+OqtSX7lGg9e8nHzwXkMMXNdVeqq4E2M3EUAqX6Q==
+node-notifier@^5.4.2:
+  version "5.4.3"
+  resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.3.tgz#cb72daf94c93904098e28b9c590fd866e464bd50"
+  integrity sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==
   dependencies:
     growly "^1.3.0"
+    is-wsl "^1.1.0"
     semver "^5.5.0"
     shellwords "^0.1.1"
     which "^1.3.0"
@@ -6682,10 +7067,10 @@ node-pre-gyp@^0.12.0:
     semver "^5.3.0"
     tar "^4"
 
-node-releases@^1.1.23:
-  version "1.1.23"
-  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.23.tgz#de7409f72de044a2fa59c097f436ba89c39997f0"
-  integrity sha512-uq1iL79YjfYC0WXoHbC/z28q/9pOl8kSHaXdWmAAc8No+bDwqkZbzIJz55g/MUsPgSGm9LZ7QSUbzTcH5tz47w==
+node-releases@^1.1.25:
+  version "1.1.26"
+  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.26.tgz#f30563edc5c7dc20cf524cc8652ffa7be0762937"
+  integrity sha512-fZPsuhhUHMTlfkhDLGtfY80DSJTjOcx+qD1j5pqPkuhUHVS7xHZIg9EE4DHK8O3f0zTxXHX5VIkDG8pu98/wfQ==
   dependencies:
     semver "^5.3.0"
 
@@ -6731,20 +7116,30 @@ normalize-range@^0.1.2:
   resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
   integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
 
+normalize-url@1.9.1:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
+  integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=
+  dependencies:
+    object-assign "^4.0.1"
+    prepend-http "^1.0.0"
+    query-string "^4.1.0"
+    sort-keys "^1.0.0"
+
 normalize-url@^3.0.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
   integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
 
 npm-bundled@^1.0.1:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979"
-  integrity sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd"
+  integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
 
 npm-packlist@^1.1.6:
-  version "1.1.12"
-  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a"
-  integrity sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g==
+  version "1.4.4"
+  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44"
+  integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==
   dependencies:
     ignore-walk "^3.0.1"
     npm-bundled "^1.0.1"
@@ -6832,6 +7227,11 @@ object-keys@^1.0.11, object-keys@^1.0.12:
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2"
   integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==
 
+object-keys@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+  integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
 object-visit@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
@@ -6941,6 +7341,11 @@ onetime@^2.0.0:
   dependencies:
     mimic-fn "^1.0.0"
 
+opencollective-postinstall@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89"
+  integrity sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==
+
 opener@^1.5.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
@@ -6990,7 +7395,7 @@ os-homedir@^1.0.0:
   resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
   integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
 
-os-locale@^3.0.0:
+os-locale@^3.0.0, os-locale@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
   integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
@@ -7029,10 +7434,10 @@ p-finally@^1.0.0:
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
   integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
 
-p-is-promise@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
-  integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=
+p-is-promise@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
+  integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==
 
 p-limit@^1.1.0:
   version "1.3.0"
@@ -7042,9 +7447,16 @@ p-limit@^1.1.0:
     p-try "^1.0.0"
 
 p-limit@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.1.0.tgz#1d5a0d20fb12707c758a655f6bbc4386b5930d68"
-  integrity sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537"
+  integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==
+  dependencies:
+    p-try "^2.0.0"
+
+p-limit@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2"
+  integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==
   dependencies:
     p-try "^2.0.0"
 
@@ -7062,6 +7474,13 @@ p-locate@^3.0.0:
   dependencies:
     p-limit "^2.0.0"
 
+p-locate@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
+  integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
+  dependencies:
+    p-limit "^2.2.0"
+
 p-map@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
@@ -7072,15 +7491,22 @@ p-reduce@^1.0.0:
   resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa"
   integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=
 
+p-retry@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328"
+  integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==
+  dependencies:
+    retry "^0.12.0"
+
 p-try@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
   integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
 
 p-try@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
-  integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+  integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
 
 packet-reader@0.3.1:
   version "0.3.1"
@@ -7088,29 +7514,37 @@ packet-reader@0.3.1:
   integrity sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc=
 
 pako@~1.0.5:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.7.tgz#2473439021b57f1516c82f58be7275ad8ef1bb27"
-  integrity sha512-3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ==
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"
+  integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==
 
 parallel-transform@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"
-  integrity sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc"
+  integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==
   dependencies:
-    cyclist "~0.2.2"
+    cyclist "^1.0.1"
     inherits "^2.0.3"
     readable-stream "^2.1.5"
 
+parent-module@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+  integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+  dependencies:
+    callsites "^3.0.0"
+
 parse-asn1@^5.0.0:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8"
-  integrity sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==
+  version "5.1.5"
+  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e"
+  integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==
   dependencies:
     asn1.js "^4.0.0"
     browserify-aes "^1.0.0"
     create-hash "^1.1.0"
     evp_bytestokey "^1.0.0"
     pbkdf2 "^3.0.3"
+    safe-buffer "^5.1.1"
 
 parse-css-font@^2.0.2:
   version "2.0.2"
@@ -7169,10 +7603,10 @@ pascalcase@^0.1.1:
   resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
   integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
 
-path-browserify@0.0.0:
-  version "0.0.0"
-  resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
-  integrity sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=
+path-browserify@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
+  integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==
 
 path-complete-extname@^1.0.0:
   version "1.0.0"
@@ -7189,6 +7623,11 @@ path-exists@^3.0.0:
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
   integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
 
+path-exists@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+  integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
 path-is-absolute@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@@ -7306,6 +7745,11 @@ pgpass@1.*:
   dependencies:
     split "^1.0.0"
 
+picomatch@^2.0.4:
+  version "2.0.7"
+  resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6"
+  integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==
+
 pify@^2.0.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@@ -7354,25 +7798,27 @@ pkg-dir@^3.0.0:
   dependencies:
     find-up "^3.0.0"
 
+pkg-dir@^4.1.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
+  integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
+  dependencies:
+    find-up "^4.0.0"
+
 pluralize@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
   integrity sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=
 
-pluralize@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
-  integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==
-
 pn@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
   integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==
 
-portfinder@^1.0.20:
-  version "1.0.20"
-  resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.20.tgz#bea68632e54b2e13ab7b0c4775e9b41bf270e44a"
-  integrity sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==
+portfinder@^1.0.24:
+  version "1.0.24"
+  resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.24.tgz#11efbc6865f12f37624b6531ead1d809ed965cfa"
+  integrity sha512-ekRl7zD2qxYndYflwiryJwMioBI7LI7rVXg3EnLK3sjkouT5eOuhS3gS255XxBksa30VG8UPZYZCdgfGOfkSUg==
   dependencies:
     async "^1.5.2"
     debug "^2.2.0"
@@ -7527,14 +7973,15 @@ postcss-modules-extract-imports@^2.0.0:
   dependencies:
     postcss "^7.0.5"
 
-postcss-modules-local-by-default@^2.0.6:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-2.0.6.tgz#dd9953f6dd476b5fd1ef2d8830c8929760b56e63"
-  integrity sha512-oLUV5YNkeIBa0yQl7EYnxMgy4N6noxmiwZStaEJUSe2xPMcdNc8WmBQuQCx18H5psYbVxz8zoHk0RAAYZXP9gA==
+postcss-modules-local-by-default@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915"
+  integrity sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==
   dependencies:
-    postcss "^7.0.6"
-    postcss-selector-parser "^6.0.0"
-    postcss-value-parser "^3.3.1"
+    icss-utils "^4.1.1"
+    postcss "^7.0.16"
+    postcss-selector-parser "^6.0.2"
+    postcss-value-parser "^4.0.0"
 
 postcss-modules-scope@^2.1.0:
   version "2.1.0"
@@ -7544,12 +7991,12 @@ postcss-modules-scope@^2.1.0:
     postcss "^7.0.6"
     postcss-selector-parser "^6.0.0"
 
-postcss-modules-values@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-2.0.0.tgz#479b46dc0c5ca3dc7fa5270851836b9ec7152f64"
-  integrity sha512-Ki7JZa7ff1N3EIMlPnGTZfUMe69FFwiQPnVSXC9mnn3jozCRBYIxiZd44yJOV2AmabOo4qFf8s0dC/+lweG7+w==
+postcss-modules-values@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10"
+  integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==
   dependencies:
-    icss-replace-symbols "^1.1.0"
+    icss-utils "^4.0.0"
     postcss "^7.0.6"
 
 postcss-normalize-charset@^4.0.1:
@@ -7689,7 +8136,7 @@ postcss-selector-parser@^5.0.0-rc.4:
     indexes-of "^1.0.1"
     uniq "^1.0.1"
 
-postcss-selector-parser@^6.0.0:
+postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c"
   integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==
@@ -7717,11 +8164,16 @@ postcss-unique-selectors@^4.0.1:
     postcss "^7.0.0"
     uniqs "^2.0.0"
 
-postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1:
+postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.1:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
   integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
 
+postcss-value-parser@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz#99a983d365f7b2ad8d0f9b8c3094926eab4b936d"
+  integrity sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==
+
 postcss@^5.0.16:
   version "5.2.18"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5"
@@ -7732,7 +8184,7 @@ postcss@^5.0.16:
     source-map "^0.5.6"
     supports-color "^3.2.3"
 
-postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.5, postcss@^7.0.6:
+postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.5, postcss@^7.0.6:
   version "7.0.17"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f"
   integrity sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==
@@ -7773,17 +8225,17 @@ prelude-ls@~1.1.2:
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
   integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
 
-prettier@^1.17.0:
-  version "1.18.2"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
-  integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
+prepend-http@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
+  integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
 
-pretty-format@^24.8.0:
-  version "24.8.0"
-  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2"
-  integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw==
+pretty-format@^24.9.0:
+  version "24.9.0"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9"
+  integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==
   dependencies:
-    "@jest/types" "^24.8.0"
+    "@jest/types" "^24.9.0"
     ansi-regex "^4.0.0"
     ansi-styles "^3.2.0"
     react-is "^16.8.4"
@@ -7794,9 +8246,9 @@ private@^0.1.6:
   integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
 
 process-nextick-args@~2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
-  integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+  integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
 
 process@^0.11.10:
   version "0.11.10"
@@ -7944,6 +8396,14 @@ qs@~6.5.2:
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
   integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
 
+query-string@^4.1.0:
+  version "4.3.4"
+  resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
+  integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s=
+  dependencies:
+    object-assign "^4.1.0"
+    strict-uri-encode "^1.0.0"
+
 querystring-es3@^0.2.0:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@@ -7954,10 +8414,10 @@ querystring@0.2.0:
   resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
   integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
 
-querystringify@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef"
-  integrity sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==
+querystringify@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
+  integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
 
 quote@^0.4.0:
   version "0.4.0"
@@ -7990,9 +8450,9 @@ randexp@0.4.6:
     ret "~0.1.10"
 
 randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80"
-  integrity sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
+  integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
   dependencies:
     safe-buffer "^5.1.0"
 
@@ -8106,10 +8566,10 @@ react-intl@^2.9.0:
     intl-relativeformat "^2.1.0"
     invariant "^2.1.1"
 
-react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.2, react-is@^16.8.4, react-is@^16.8.6:
-  version "16.8.6"
-  resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
-  integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
+react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0:
+  version "16.9.0"
+  resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.9.0.tgz#21ca9561399aad0ff1a7701c01683e8ca981edcb"
+  integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==
 
 react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4:
   version "3.0.4"
@@ -8161,17 +8621,17 @@ react-redux-loading-bar@^4.0.8:
     prop-types "^15.6.2"
     react-lifecycles-compat "^3.0.2"
 
-react-redux@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-6.0.1.tgz#0d423e2c1cb10ada87293d47e7de7c329623ba4d"
-  integrity sha512-T52I52Kxhbqy/6TEfBv85rQSDz6+Y28V/pf52vDWs1YRXG19mcFOGfHnY2HsNFHyhP+ST34Aih98fvt6tqwVcQ==
+react-redux@^7.1.1:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.1.1.tgz#ce6eee1b734a7a76e0788b3309bf78ff6b34fa0a"
+  integrity sha512-QsW0vcmVVdNQzEkrgzh2W3Ksvr8cqpAv5FhEk7tNEft+5pp7rXxAudTz3VOPawRkLIepItpkEIyLcN/VVXzjTg==
   dependencies:
-    "@babel/runtime" "^7.3.1"
+    "@babel/runtime" "^7.5.5"
     hoist-non-react-statics "^3.3.0"
     invariant "^2.2.4"
     loose-envify "^1.4.0"
     prop-types "^15.7.2"
-    react-is "^16.8.2"
+    react-is "^16.9.0"
 
 react-router-dom@^4.1.1:
   version "4.3.1"
@@ -8337,7 +8797,7 @@ read-pkg@^3.0.0:
     normalize-package-data "^2.3.2"
     path-type "^3.0.0"
 
-"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6:
+"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6:
   version "2.3.6"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
   integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
@@ -8351,9 +8811,9 @@ read-pkg@^3.0.0:
     util-deprecate "~1.0.1"
 
 readable-stream@^3.0.6:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06"
-  integrity sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc"
+  integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==
   dependencies:
     inherits "^2.0.3"
     string_decoder "^1.1.1"
@@ -8368,6 +8828,13 @@ readdirp@^2.2.1:
     micromatch "^3.1.10"
     readable-stream "^2.0.2"
 
+readdirp@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.1.tgz#b158123ac343c8b0f31d65680269cc0fc1025db1"
+  integrity sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==
+  dependencies:
+    picomatch "^2.0.4"
+
 readline2@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35"
@@ -8413,10 +8880,10 @@ redux-thunk@^2.2.0:
   resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
   integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
 
-redux@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.1.tgz#436cae6cc40fbe4727689d7c8fae44808f1bfef5"
-  integrity sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==
+redux@^4.0.4:
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796"
+  integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q==
   dependencies:
     loose-envify "^1.4.0"
     symbol-observable "^1.2.0"
@@ -8468,10 +8935,17 @@ regex-not@^1.0.0, regex-not@^1.0.2:
     extend-shallow "^3.0.2"
     safe-regex "^1.1.0"
 
-regexp-tree@^0.1.6:
-  version "0.1.10"
-  resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.10.tgz#d837816a039c7af8a8d64d7a7c3cf6a1d93450bc"
-  integrity sha512-K1qVSbcedffwuIslMwpe6vGlj+ZXRnGkvjAtFHfDZZZuEdA/h0dxljAPu9vhUo6Rrx2U2AwJ+nSQ6hK+lrP5MQ==
+regexp-tree@^0.1.13:
+  version "0.1.13"
+  resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.13.tgz#5b19ab9377edc68bc3679256840bb29afc158d7f"
+  integrity sha512-hwdV/GQY5F8ReLZWO+W1SRoN5YfpOKY6852+tBFcma72DKBIcHjPRIlIvQN35bCOljuAfP2G2iB0FC/w236mUw==
+
+regexp.prototype.flags@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz#6b30724e306a27833eeb171b66ac8890ba37e41c"
+  integrity sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==
+  dependencies:
+    define-properties "^1.1.2"
 
 regexpp@^2.0.1:
   version "2.0.1"
@@ -8584,12 +9058,17 @@ require-main-filename@^1.0.1:
   resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
   integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
 
+require-main-filename@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
+  integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
+
 require-package-name@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/require-package-name/-/require-package-name-2.0.1.tgz#c11e97276b65b8e2923f75dabf5fb2ef0c3841b9"
   integrity sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk=
 
-require-uncached@^1.0.2, require-uncached@^1.0.3:
+require-uncached@^1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
   integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=
@@ -8632,11 +9111,21 @@ resolve-from@^3.0.0:
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
   integrity sha1-six699nWiBvItuZTM17rywoYh0g=
 
+resolve-from@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+  integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
 resolve-pathname@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879"
   integrity sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==
 
+resolve-pathname@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
+  integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
+
 resolve-url@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -8647,10 +9136,10 @@ resolve@1.1.7:
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
   integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
 
-resolve@^1.10.0, resolve@^1.11.0, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1, resolve@^1.9.0:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232"
-  integrity sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==
+resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1:
+  version "1.12.0"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
+  integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==
   dependencies:
     path-parse "^1.0.6"
 
@@ -8675,6 +9164,11 @@ ret@~0.1.10:
   resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
   integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
 
+retry@^0.12.0:
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
+  integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
+
 rgb-regex@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1"
@@ -8685,13 +9179,27 @@ rgba-regex@^1.0.0:
   resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
   integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
 
-rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2:
+rimraf@2.6.3, rimraf@^2.6.2, rimraf@~2.6.2:
   version "2.6.3"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
   integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
   dependencies:
     glob "^7.1.3"
 
+rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3:
+  version "2.7.1"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+  integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+  dependencies:
+    glob "^7.1.3"
+
+rimraf@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b"
+  integrity sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==
+  dependencies:
+    glob "^7.1.3"
+
 ripemd160@^2.0.0, ripemd160@^2.0.1:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
@@ -8739,18 +9247,23 @@ rx-lite@^3.1.2:
   resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
   integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=
 
-rxjs@^6.1.0:
-  version "6.3.3"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55"
-  integrity sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==
+rxjs@^6.4.0:
+  version "6.5.2"
+  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7"
+  integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==
   dependencies:
     tslib "^1.9.0"
 
-safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
+safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
+  integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
+
 safe-regex@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
@@ -8810,12 +9323,12 @@ sass-loader@^7.0.3:
     pify "^3.0.0"
     semver "^5.5.0"
 
-sass@^1.20.3:
-  version "1.20.3"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.20.3.tgz#18284a7bac6eab9cbb80453288473194f29efb84"
-  integrity sha512-kvf+w5XT7FrmFrCKz1gPHqegufG+gxifC8oQesX/s8gkShdeiTqiuvP0c8TvfBwMAuI1YGOgobZQ2KIJGn//jA==
+sass@^1.22.12:
+  version "1.22.12"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.22.12.tgz#5cbdd38720ffd1857da695331faee9f634bcb5d7"
+  integrity sha512-u5Rxn+dKTPCW5/11kMNxtmqKsxCjcpnqj9CaJoru1NqeJ0DOa9rOM00e0HqmseTAatGkKoLY+jaNecMYevu1gg==
   dependencies:
-    chokidar "^2.0.0"
+    chokidar ">=2.0.0 <4.0.0"
 
 sax@^1.2.4, sax@~1.2.4:
   version "1.2.4"
@@ -8839,6 +9352,14 @@ schema-utils@^1.0.0:
     ajv-errors "^1.0.0"
     ajv-keywords "^3.1.0"
 
+schema-utils@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.0.1.tgz#1eec2e059556af841b7f3a83b61af13d7a3f9196"
+  integrity sha512-HJFKJ4JixDpRur06QHwi8uu2kZbng318ahWEKgBjc0ZklcE4FDvmm2wghb448q0IRaABxIESt8vqPFvwgMB80A==
+  dependencies:
+    ajv "^6.1.0"
+    ajv-keywords "^3.1.0"
+
 scroll-behavior@^0.9.1:
   version "0.9.9"
   resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.9.tgz#ebfe0658455b82ad885b66195215416674dacce2"
@@ -8852,27 +9373,27 @@ select-hose@^2.0.0:
   resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
   integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=
 
-selfsigned@^1.10.4:
-  version "1.10.4"
-  resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.4.tgz#cdd7eccfca4ed7635d47a08bf2d5d3074092e2cd"
-  integrity sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==
+selfsigned@^1.10.6:
+  version "1.10.6"
+  resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.6.tgz#7b3cd37ed9c2034261a173af1a1aae27d8169b67"
+  integrity sha512-i3+CeqxL7DpAazgVpAGdKMwHuL63B5nhJMh9NQ7xmChGkA3jNFflq6Jyo1LLJYcr3idWiNOPWHCrm4zMayLG4w==
   dependencies:
-    node-forge "0.7.5"
+    node-forge "0.8.2"
 
 "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0:
-  version "5.7.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
-  integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
 
 semver@4.3.2:
   version "4.3.2"
   resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7"
   integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=
 
-semver@^6.1.0, semver@^6.1.1:
-  version "6.1.1"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.1.tgz#53f53da9b30b2103cd4f15eab3a18ecbcb210c9b"
-  integrity sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==
+semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
 send@0.17.1:
   version "0.17.1"
@@ -8894,15 +9415,15 @@ send@0.17.1:
     statuses "~1.5.0"
 
 serialize-javascript@^1.4.0:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879"
-  integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==
-
-serialize-javascript@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65"
   integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==
 
+serialize-javascript@^1.7.0:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb"
+  integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==
+
 serve-index@^1.9.1:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
@@ -8931,20 +9452,10 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
   resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
   integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
 
-set-value@^0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1"
-  integrity sha1-fbCPnT0i3H945Trzw79GZuzfzPE=
-  dependencies:
-    extend-shallow "^2.0.1"
-    is-extendable "^0.1.1"
-    is-plain-object "^2.0.1"
-    to-object-path "^0.3.0"
-
-set-value@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274"
-  integrity sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==
+set-value@^2.0.0, set-value@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
+  integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==
   dependencies:
     extend-shallow "^2.0.1"
     is-extendable "^0.1.1"
@@ -9037,10 +9548,10 @@ slice-ansi@0.0.4:
   resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
   integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=
 
-slice-ansi@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.0.0.tgz#5373bdb8559b45676e8541c66916cdd6251612e7"
-  integrity sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ==
+slice-ansi@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
+  integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==
   dependencies:
     ansi-styles "^3.2.0"
     astral-regex "^1.0.0"
@@ -9076,10 +9587,10 @@ snapdragon@^0.8.1:
     source-map-resolve "^0.5.0"
     use "^3.1.0"
 
-sockjs-client@1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.3.0.tgz#12fc9d6cb663da5739d3dc5fb6e8687da95cb177"
-  integrity sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==
+sockjs-client@1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5"
+  integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==
   dependencies:
     debug "^3.2.5"
     eventsource "^1.0.7"
@@ -9096,6 +9607,13 @@ sockjs@0.3.19:
     faye-websocket "^0.10.0"
     uuid "^3.0.1"
 
+sort-keys@^1.0.0:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
+  integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0=
+  dependencies:
+    is-plain-obj "^1.0.0"
+
 source-list-map@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
@@ -9120,10 +9638,10 @@ source-map-support@^0.5.6:
     buffer-from "^1.0.0"
     source-map "^0.6.0"
 
-source-map-support@~0.5.10:
-  version "0.5.12"
-  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599"
-  integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==
+source-map-support@~0.5.12:
+  version "0.5.13"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932"
+  integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==
   dependencies:
     buffer-from "^1.0.0"
     source-map "^0.6.0"
@@ -9186,10 +9704,10 @@ spdy-transport@^3.0.0:
     readable-stream "^3.0.6"
     wbuf "^1.7.3"
 
-spdy@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.0.tgz#81f222b5a743a329aa12cea6a390e60e9b613c52"
-  integrity sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==
+spdy@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.1.tgz#6f12ed1c5db7ea4f24ebb8b89ba58c87c08257f2"
+  integrity sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==
   dependencies:
     debug "^4.1.0"
     handle-thing "^2.0.0"
@@ -9267,9 +9785,9 @@ stealthy-require@^1.1.0:
   integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
 
 stream-browserify@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
-  integrity sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"
+  integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==
   dependencies:
     inherits "~2.0.1"
     readable-stream "^2.0.2"
@@ -9298,6 +9816,11 @@ stream-shift@^1.0.0:
   resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
   integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
 
+strict-uri-encode@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
+  integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
+
 string-length@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
@@ -9323,6 +9846,15 @@ string-width@^1.0.1:
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^4.0.0"
 
+string-width@^3.0.0, string-width@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
+  integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
+  dependencies:
+    emoji-regex "^7.0.1"
+    is-fullwidth-code-point "^2.0.0"
+    strip-ansi "^5.1.0"
+
 string.prototype.trim@^1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea"
@@ -9333,11 +9865,11 @@ string.prototype.trim@^1.1.2:
     function-bind "^1.0.2"
 
 string_decoder@^1.0.0, string_decoder@^1.1.1:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
-  integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
+  integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
   dependencies:
-    safe-buffer "~5.1.0"
+    safe-buffer "~5.2.0"
 
 string_decoder@~1.1.1:
   version "1.1.1"
@@ -9346,10 +9878,10 @@ string_decoder@~1.1.1:
   dependencies:
     safe-buffer "~5.1.0"
 
-stringz@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/stringz/-/stringz-1.0.0.tgz#d2acba994e4ce3c725ee15c86fff4281280d2025"
-  integrity sha512-oaqFaIAmw1MJmdPNiBqocHHrC0VzJTL3CI1z5uXm3NQSE3AyDU152ZPTSJSOKk+9z1Cm3LZzgLFjCTb8SXZvag==
+stringz@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/stringz/-/stringz-2.0.0.tgz#0a092bc64ed9b42eff2936d0401d2398393d54e9"
+  integrity sha512-pRWc5RGpedKEDvQ/ukYs8kS8tKj+cKu5ayOoyOvsavbpiLBcm1dGX6p1o5IagaN11cbfN8tKGpgQ4fHdEq5LBA==
   dependencies:
     unicode-astral-regex "^1.0.1"
 
@@ -9367,12 +9899,12 @@ strip-ansi@^4.0.0:
   dependencies:
     ansi-regex "^3.0.0"
 
-strip-ansi@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz#f78f68b5d0866c20b2c9b8c61b5298508dc8756f"
-  integrity sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==
+strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
+  integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
   dependencies:
-    ansi-regex "^4.0.0"
+    ansi-regex "^4.1.0"
 
 strip-bom@^3.0.0:
   version "3.0.0"
@@ -9384,16 +9916,21 @@ strip-eof@^1.0.0:
   resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
   integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
 
-strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
-  integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
+strip-json-comments@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
+  integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
 
 strip-json-comments@~1.0.1:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
   integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=
 
+strip-json-comments@~2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+  integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
+
 stylehacks@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.1.tgz#3186595d047ab0df813d213e51c8b94e0b9010f2"
@@ -9418,6 +9955,13 @@ substring-trie@^1.0.2:
   resolved "https://registry.yarnpkg.com/substring-trie/-/substring-trie-1.0.2.tgz#7b42592391628b4f2cb17365c6cce4257c7b7af5"
   integrity sha1-e0JZI5Fii08ssXNlxszkJXx7evU=
 
+supports-color@6.1.0, supports-color@^6.0.0, supports-color@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
+  integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
+  dependencies:
+    has-flag "^3.0.0"
+
 supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@@ -9430,20 +9974,13 @@ supports-color@^3.2.3:
   dependencies:
     has-flag "^1.0.0"
 
-supports-color@^5.3.0, supports-color@^5.5.0:
+supports-color@^5.3.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
   integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
   dependencies:
     has-flag "^3.0.0"
 
-supports-color@^6.0.0, supports-color@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
-  integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
-  dependencies:
-    has-flag "^3.0.0"
-
 svgo@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.1.1.tgz#12384b03335bcecd85cfa5f4e3375fed671cb985"
@@ -9486,63 +10023,92 @@ table@^3.7.8:
     slice-ansi "0.0.4"
     string-width "^2.0.0"
 
-table@^5.0.2:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/table/-/table-5.1.1.tgz#92030192f1b7b51b6eeab23ed416862e47b70837"
-  integrity sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw==
+table@^5.2.3:
+  version "5.4.1"
+  resolved "https://registry.yarnpkg.com/table/-/table-5.4.1.tgz#0691ae2ebe8259858efb63e550b6d5f9300171e8"
+  integrity sha512-E6CK1/pZe2N75rGZQotFOdmzWQ1AILtgYbMAbAjvms0S1l5IDB47zG3nCnFGB/w+7nB3vKofbLXCH7HPBo864w==
   dependencies:
-    ajv "^6.6.1"
+    ajv "^6.9.1"
     lodash "^4.17.11"
-    slice-ansi "2.0.0"
-    string-width "^2.1.1"
+    slice-ansi "^2.1.0"
+    string-width "^3.0.0"
 
-tapable@^1.0.0, tapable@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.1.tgz#4d297923c5a72a42360de2ab52dadfaaec00018e"
-  integrity sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==
+tapable@^1.0.0, tapable@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
+  integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
 
 tar@^4:
-  version "4.4.8"
-  resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d"
-  integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==
+  version "4.4.13"
+  resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
+  integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==
   dependencies:
     chownr "^1.1.1"
     fs-minipass "^1.2.5"
-    minipass "^2.3.4"
-    minizlib "^1.1.1"
+    minipass "^2.8.6"
+    minizlib "^1.2.1"
     mkdirp "^0.5.0"
     safe-buffer "^5.1.2"
-    yallist "^3.0.2"
+    yallist "^3.0.3"
 
 tcomb@^2.5.0:
   version "2.7.0"
   resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0"
   integrity sha1-ENYpWAQWaaXVNWe5pO6M3iKxwrA=
 
-terser-webpack-plugin@^1.1.0, terser-webpack-plugin@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.3.0.tgz#69aa22426299f4b5b3775cbed8cb2c5d419aa1d4"
-  integrity sha512-W2YWmxPjjkUcOWa4pBEv4OP4er1aeQJlSo2UhtCFQCuRXEHjOFscO8VyWHj9JLlA0RzQb8Y2/Ta78XZvT54uGg==
+terser-webpack-plugin@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4"
+  integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==
   dependencies:
-    cacache "^11.3.2"
-    find-cache-dir "^2.0.0"
+    cacache "^12.0.2"
+    find-cache-dir "^2.1.0"
     is-wsl "^1.1.0"
-    loader-utils "^1.2.3"
     schema-utils "^1.0.0"
     serialize-javascript "^1.7.0"
     source-map "^0.6.1"
-    terser "^4.0.0"
-    webpack-sources "^1.3.0"
+    terser "^4.1.2"
+    webpack-sources "^1.4.0"
     worker-farm "^1.7.0"
 
-terser@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-4.0.0.tgz#ef356f6f359a963e2cc675517f21c1c382877374"
-  integrity sha512-dOapGTU0hETFl1tCo4t56FN+2jffoKyER9qBGoUFyZ6y7WLoKT0bF+lAYi6B6YsILcGF3q1C2FBh8QcKSCgkgA==
+terser@^4.1.2:
+  version "4.3.4"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.4.tgz#ad91bade95619e3434685d69efa621a5af5f877d"
+  integrity sha512-Kcrn3RiW8NtHBP0ssOAzwa2MsIRQ8lJWiBG/K7JgqPlomA3mtb2DEmp4/hrUA+Jujx+WZ02zqd7GYD+QRBB/2Q==
   dependencies:
-    commander "^2.19.0"
+    commander "^2.20.0"
     source-map "~0.6.1"
-    source-map-support "~0.5.10"
+    source-map-support "~0.5.12"
+
+tesseract.js-core@^2.0.0-beta.12:
+  version "2.0.0-beta.13"
+  resolved "https://registry.yarnpkg.com/tesseract.js-core/-/tesseract.js-core-2.0.0-beta.13.tgz#a21d798e88098898a9bdd935d0553215e03274f8"
+  integrity sha512-GboWV/aV5h+Whito6L6Q3WCFZ2+lgxZGgjY84wSpWbTLEkkZgHsU+dz1or+3rWSABH/nuzHDco1bZRk5+f94mw==
+
+tesseract.js-utils@^1.0.0-beta.8:
+  version "1.0.0-beta.8"
+  resolved "https://registry.yarnpkg.com/tesseract.js-utils/-/tesseract.js-utils-1.0.0-beta.8.tgz#d1ef25c12609a337c3e0ac12a33f9903f3145a68"
+  integrity sha512-qjHBfWfzo2o1ZY9XI0Wh2hmpp38+mIgCMOk60W5Yyie/pBl421VLBKOZUEwQgpbLnOJ24VU6Q8yXsVgtFFHcFg==
+  dependencies:
+    axios "^0.18.0"
+    bmp-js "^0.1.0"
+    file-type "^10.5.0"
+    idb-keyval "^3.1.0"
+    is-url "^1.2.4"
+    zlibjs "^0.3.1"
+
+tesseract.js@^2.0.0-alpha.16:
+  version "2.0.0-alpha.16"
+  resolved "https://registry.yarnpkg.com/tesseract.js/-/tesseract.js-2.0.0-alpha.16.tgz#1e17717234a1464481abe12283f2c3ac79603d2e"
+  integrity sha512-8g3je2Kl8rkAFtpmwilGGj+8rCiPClNQaCjW6IafOPNn7hzFnVdL6fU6rG1Xsrc4Twv0HOa75kbpx5u70/WbTA==
+  dependencies:
+    axios "^0.18.0"
+    check-types "^7.4.0"
+    is-url "1.2.2"
+    opencollective-postinstall "^2.0.2"
+    resolve-url "^0.2.1"
+    tesseract.js-core "^2.0.0-beta.12"
+    tesseract.js-utils "^1.0.0-beta.8"
 
 test-exclude@^5.0.0:
   version "5.1.0"
@@ -9590,9 +10156,9 @@ thunky@^1.0.2:
   integrity sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==
 
 timers-browserify@^2.0.4:
-  version "2.0.10"
-  resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae"
-  integrity sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==
+  version "2.0.11"
+  resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f"
+  integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==
   dependencies:
     setimmediate "^1.0.4"
 
@@ -9601,11 +10167,21 @@ timsort@^0.3.0:
   resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
   integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
 
+tiny-invariant@^1.0.2:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73"
+  integrity sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==
+
 tiny-queue@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/tiny-queue/-/tiny-queue-0.2.1.tgz#25a67f2c6e253b2ca941977b5ef7442ef97a6046"
   integrity sha1-JaZ/LG4lOyypQZd7XvdELvl6YEY=
 
+tiny-warning@^1.0.0:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
+  integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
+
 tmp@^0.0.33:
   version "0.0.33"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -9643,6 +10219,13 @@ to-regex-range@^2.1.0:
     is-number "^3.0.0"
     repeat-string "^1.6.1"
 
+to-regex-range@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+  integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+  dependencies:
+    is-number "^7.0.0"
+
 to-regex@^3.0.1, to-regex@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
@@ -9693,15 +10276,15 @@ trim-right@^1.0.1:
   resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
   integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
 
-tryer@^1.0.0:
+tryer@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
   integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
 
 tslib@^1.9.0:
-  version "1.9.3"
-  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
-  integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
+  integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
 
 tty-browserify@0.0.0:
   version "0.0.0"
@@ -9735,6 +10318,11 @@ type-is@~1.6.17, type-is@~1.6.18:
     media-typer "0.3.0"
     mime-types "~2.1.24"
 
+type@^1.0.1:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/type/-/type-1.0.3.tgz#16f5d39f27a2d28d86e48f8981859e9d3296c179"
+  integrity sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg==
+
 typedarray@^0.0.6:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
@@ -9746,11 +10334,11 @@ ua-parser-js@^0.7.18:
   integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==
 
 uglify-js@^3.1.4:
-  version "3.4.9"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
-  integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"
+  integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==
   dependencies:
-    commander "~2.17.1"
+    commander "~2.20.0"
     source-map "~0.6.1"
 
 unicode-astral-regex@^1.0.1:
@@ -9782,14 +10370,14 @@ unicode-property-aliases-ecmascript@^1.0.4:
   integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==
 
 union-value@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
-  integrity sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
+  integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==
   dependencies:
     arr-union "^3.1.0"
     get-value "^2.0.6"
     is-extendable "^0.1.1"
-    set-value "^0.4.3"
+    set-value "^2.0.1"
 
 uniq@^1.0.1:
   version "1.0.1"
@@ -9809,9 +10397,9 @@ unique-filename@^1.1.1:
     unique-slug "^2.0.0"
 
 unique-slug@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.1.tgz#5e9edc6d1ce8fb264db18a507ef9bd8544451ca6"
-  integrity sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c"
+  integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==
   dependencies:
     imurmurhash "^0.1.4"
 
@@ -9839,9 +10427,9 @@ unset-value@^1.0.0:
     isobject "^3.0.0"
 
 upath@^1.1.1:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068"
-  integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
+  integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
 
 uri-js@^4.2.2:
   version "4.2.2"
@@ -9856,11 +10444,11 @@ urix@^0.1.0:
   integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
 
 url-parse@^1.4.3:
-  version "1.4.4"
-  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8"
-  integrity sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==
+  version "1.4.7"
+  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
+  integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
   dependencies:
-    querystringify "^2.0.0"
+    querystringify "^2.1.1"
     requires-port "^1.0.0"
 
 url@^0.11.0:
@@ -9910,20 +10498,32 @@ util@^0.10.3:
   dependencies:
     inherits "2.0.3"
 
+util@^0.11.0:
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61"
+  integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==
+  dependencies:
+    inherits "2.0.3"
+
 utils-merge@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
   integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
 
-uuid@^3.0.1, uuid@^3.1.0, uuid@^3.3.2:
+uuid@^3.0.1, uuid@^3.3.2:
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
+  integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
+
+uuid@^3.1.0:
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
   integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
 
-v8-compile-cache@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz#a428b28bb26790734c4fc8bc9fa106fccebf6a6c"
-  integrity sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==
+v8-compile-cache@2.0.3, v8-compile-cache@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
+  integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==
 
 validate-npm-package-license@^3.0.1:
   version "3.0.4"
@@ -9938,6 +10538,11 @@ value-equal@^0.4.0:
   resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7"
   integrity sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==
 
+value-equal@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
+  integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
+
 vary@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@@ -9957,12 +10562,10 @@ verror@1.10.0:
     core-util-is "1.0.2"
     extsprintf "^1.2.0"
 
-vm-browserify@0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
-  integrity sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=
-  dependencies:
-    indexof "0.0.1"
+vm-browserify@^1.0.1:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019"
+  integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==
 
 w3c-hr-time@^1.0.1:
   version "1.0.1"
@@ -9992,7 +10595,7 @@ warning@^4.0.1:
   dependencies:
     loose-envify "^1.0.0"
 
-watchpack@^1.5.0:
+watchpack@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
   integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==
@@ -10001,6 +10604,11 @@ watchpack@^1.5.0:
     graceful-fs "^4.1.2"
     neo-async "^2.5.0"
 
+wavesurfer.js@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/wavesurfer.js/-/wavesurfer.js-3.0.0.tgz#35f36d76d59c749dca453cf4e10ee0ec49f454f8"
+  integrity sha512-DANu206c6gb9pSUbYFevsSiXMy8+Ri+CNtqm0UsouUdsn9fVQRtYs8uxzBtXK+rUPlIc6FlO54DU8uWeW3lDzw==
+
 wbuf@^1.1.0, wbuf@^1.7.3:
   version "1.7.3"
   resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df"
@@ -10027,9 +10635,9 @@ webpack-assets-manifest@^3.1.1:
     webpack-sources "^1.0.0"
 
 webpack-bundle-analyzer@^3.3.2:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.3.2.tgz#3da733a900f515914e729fcebcd4c40dde71fc6f"
-  integrity sha512-7qvJLPKB4rRWZGjVp5U1KEjwutbDHSKboAl0IfafnrdXMrgC0tOtZbQD6Rw0u4cmpgRN4O02Fc0t8eAT+FgGzA==
+  version "3.5.2"
+  resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.5.2.tgz#ac02834f4b31de8e27d71e6c7a612301ebddb79f"
+  integrity sha512-g9spCNe25QYUVqHRDkwG414GTok2m7pTTP0wr6l0J50Z3YLS04+BGodTqqoVBL7QfU/U/9p/oiI5XFOyfZ7S/A==
   dependencies:
     acorn "^6.0.7"
     acorn-walk "^6.1.1"
@@ -10040,47 +10648,47 @@ webpack-bundle-analyzer@^3.3.2:
     express "^4.16.3"
     filesize "^3.6.1"
     gzip-size "^5.0.0"
-    lodash "^4.17.10"
+    lodash "^4.17.15"
     mkdirp "^0.5.1"
     opener "^1.5.1"
     ws "^6.0.0"
 
-webpack-cli@^3.3.4:
-  version "3.3.4"
-  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.4.tgz#de27e281c48a897b8c219cb093e261d5f6afe44a"
-  integrity sha512-ubJGQEKMtBSpT+LiL5hXvn2GIOWiRWItR1DGUqJRhwRBeGhpRXjvF5f0erqdRJLErkfqS5/Ldkkedh4AL5Q1ZQ==
-  dependencies:
-    chalk "^2.4.1"
-    cross-spawn "^6.0.5"
-    enhanced-resolve "^4.1.0"
-    findup-sync "^2.0.0"
-    global-modules "^1.0.0"
-    import-local "^2.0.0"
-    interpret "^1.1.0"
-    loader-utils "^1.1.0"
-    prettier "^1.17.0"
-    supports-color "^5.5.0"
-    v8-compile-cache "^2.0.2"
-    yargs "^12.0.5"
-
-webpack-dev-middleware@^3.7.0:
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz#ef751d25f4e9a5c8a35da600c5fda3582b5c6cff"
-  integrity sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==
+webpack-cli@^3.3.7:
+  version "3.3.9"
+  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.9.tgz#79c27e71f94b7fe324d594ab64a8e396b9daa91a"
+  integrity sha512-xwnSxWl8nZtBl/AFJCOn9pG7s5CYUYdZxmmukv+fAHLcBIHM36dImfpQg3WfShZXeArkWlf6QRw24Klcsv8a5A==
+  dependencies:
+    chalk "2.4.2"
+    cross-spawn "6.0.5"
+    enhanced-resolve "4.1.0"
+    findup-sync "3.0.0"
+    global-modules "2.0.0"
+    import-local "2.0.0"
+    interpret "1.2.0"
+    loader-utils "1.2.3"
+    supports-color "6.1.0"
+    v8-compile-cache "2.0.3"
+    yargs "13.2.4"
+
+webpack-dev-middleware@^3.7.1:
+  version "3.7.2"
+  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3"
+  integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==
   dependencies:
     memory-fs "^0.4.1"
-    mime "^2.4.2"
+    mime "^2.4.4"
+    mkdirp "^0.5.1"
     range-parser "^1.2.1"
     webpack-log "^2.0.0"
 
-webpack-dev-server@^3.5.1:
-  version "3.5.1"
-  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.5.1.tgz#4290ac709bb989dc7382c912899f79fd5677dabf"
-  integrity sha512-0IdMGddJcnK9zesZOeHWl4uAOVfypn7DSrdNWtclROkVBXy/TcBN+6eEG1wNfLT9dXVfaRZZsLTJt0mJtgTQgw==
+webpack-dev-server@^3.8.0:
+  version "3.8.1"
+  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.8.1.tgz#485b64c4aadc23f601e72114b40c1b1fea31d9f1"
+  integrity sha512-9F5DnfFA9bsrhpUCAfQic/AXBVHvq+3gQS+x6Zj0yc1fVVE0erKh2MV4IV12TBewuTrYeeTIRwCH9qLMvdNvTw==
   dependencies:
     ansi-html "0.0.7"
     bonjour "^3.5.0"
-    chokidar "^2.1.6"
+    chokidar "^2.1.8"
     compression "^1.7.4"
     connect-history-api-fallback "^1.6.0"
     debug "^4.1.1"
@@ -10091,22 +10699,25 @@ webpack-dev-server@^3.5.1:
     import-local "^2.0.0"
     internal-ip "^4.3.0"
     ip "^1.1.5"
+    is-absolute-url "^3.0.2"
     killable "^1.0.1"
-    loglevel "^1.6.2"
+    loglevel "^1.6.4"
     opn "^5.5.0"
-    portfinder "^1.0.20"
+    p-retry "^3.0.1"
+    portfinder "^1.0.24"
     schema-utils "^1.0.0"
-    selfsigned "^1.10.4"
-    semver "^6.1.1"
+    selfsigned "^1.10.6"
+    semver "^6.3.0"
     serve-index "^1.9.1"
     sockjs "0.3.19"
-    sockjs-client "1.3.0"
-    spdy "^4.0.0"
+    sockjs-client "1.4.0"
+    spdy "^4.0.1"
     strip-ansi "^3.0.1"
     supports-color "^6.1.0"
     url "^0.11.0"
-    webpack-dev-middleware "^3.7.0"
+    webpack-dev-middleware "^3.7.1"
     webpack-log "^2.0.0"
+    ws "^6.2.1"
     yargs "12.0.5"
 
 webpack-log@^2.0.0:
@@ -10118,56 +10729,56 @@ webpack-log@^2.0.0:
     uuid "^3.3.2"
 
 webpack-merge@^4.2.1:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.1.tgz#5e923cf802ea2ace4fd5af1d3247368a633489b4"
-  integrity sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d"
+  integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==
   dependencies:
-    lodash "^4.17.5"
+    lodash "^4.17.15"
 
-webpack-sources@^1.0.0, webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sources@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85"
-  integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==
+webpack-sources@^1.0.0, webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1:
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
+  integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
   dependencies:
     source-list-map "^2.0.0"
     source-map "~0.6.1"
 
-webpack@^4.34.0:
-  version "4.34.0"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.34.0.tgz#a4c30129482f7b4ece4c0842002dedf2b56fab58"
-  integrity sha512-ry2IQy1wJjOefLe1uJLzn5tG/DdIKzQqNlIAd2L84kcaADqNvQDTBlo8UcCNyDaT5FiaB+16jhAkb63YeG3H8Q==
+webpack@^4.35.3:
+  version "4.41.0"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.0.tgz#db6a254bde671769f7c14e90a1a55e73602fc70b"
+  integrity sha512-yNV98U4r7wX1VJAj5kyMsu36T8RPPQntcb5fJLOsMz/pt/WrKC0Vp1bAlqPLkA1LegSwQwf6P+kAbyhRKVQ72g==
   dependencies:
     "@webassemblyjs/ast" "1.8.5"
     "@webassemblyjs/helper-module-context" "1.8.5"
     "@webassemblyjs/wasm-edit" "1.8.5"
     "@webassemblyjs/wasm-parser" "1.8.5"
-    acorn "^6.0.5"
-    acorn-dynamic-import "^4.0.0"
-    ajv "^6.1.0"
-    ajv-keywords "^3.1.0"
-    chrome-trace-event "^1.0.0"
+    acorn "^6.2.1"
+    ajv "^6.10.2"
+    ajv-keywords "^3.4.1"
+    chrome-trace-event "^1.0.2"
     enhanced-resolve "^4.1.0"
-    eslint-scope "^4.0.0"
+    eslint-scope "^4.0.3"
     json-parse-better-errors "^1.0.2"
-    loader-runner "^2.3.0"
-    loader-utils "^1.1.0"
-    memory-fs "~0.4.1"
-    micromatch "^3.1.8"
-    mkdirp "~0.5.0"
-    neo-async "^2.5.0"
-    node-libs-browser "^2.0.0"
+    loader-runner "^2.4.0"
+    loader-utils "^1.2.3"
+    memory-fs "^0.4.1"
+    micromatch "^3.1.10"
+    mkdirp "^0.5.1"
+    neo-async "^2.6.1"
+    node-libs-browser "^2.2.1"
     schema-utils "^1.0.0"
-    tapable "^1.1.0"
-    terser-webpack-plugin "^1.1.0"
-    watchpack "^1.5.0"
-    webpack-sources "^1.3.0"
+    tapable "^1.1.3"
+    terser-webpack-plugin "^1.4.1"
+    watchpack "^1.6.0"
+    webpack-sources "^1.4.1"
 
 websocket-driver@>=0.5.1:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb"
-  integrity sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=
+  version "0.7.3"
+  resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9"
+  integrity sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==
   dependencies:
-    http-parser-js ">=0.4.0"
+    http-parser-js ">=0.4.0 <0.4.11"
+    safe-buffer ">=5.1.0"
     websocket-extensions ">=0.1.1"
 
 websocket-extensions@>=0.1.1:
@@ -10222,7 +10833,7 @@ which-module@^2.0.0:
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
   integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
 
-which@^1.2.14, which@^1.2.9, which@^1.3.0:
+which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
   integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -10261,6 +10872,15 @@ wrap-ansi@^2.0.0:
     string-width "^1.0.1"
     strip-ansi "^3.0.1"
 
+wrap-ansi@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
+  integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
+  dependencies:
+    ansi-styles "^3.2.0"
+    string-width "^3.0.0"
+    strip-ansi "^5.0.0"
+
 wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -10275,6 +10895,13 @@ write-file-atomic@2.4.1:
     imurmurhash "^0.1.4"
     signal-exit "^3.0.2"
 
+write@1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
+  integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==
+  dependencies:
+    mkdirp "^0.5.1"
+
 write@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
@@ -10289,10 +10916,10 @@ ws@^5.2.0:
   dependencies:
     async-limiter "~1.0.0"
 
-ws@^6.0.0:
-  version "6.1.2"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.2.tgz#3cc7462e98792f0ac679424148903ded3b9c3ad8"
-  integrity sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==
+ws@^6.0.0, ws@^6.2.1:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
+  integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
   dependencies:
     async-limiter "~1.0.0"
 
@@ -10302,19 +10929,19 @@ xml-name-validator@^3.0.0:
   integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
 
 xtend@^4.0.0, xtend@~4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
-  integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
+  integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
 
 "y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
   integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
 
-yallist@^3.0.0, yallist@^3.0.2:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
-  integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
+yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
+  integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
 
 yargs-parser@^11.1.1:
   version "11.1.1"
@@ -10324,7 +10951,15 @@ yargs-parser@^11.1.1:
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
-yargs@12.0.5, yargs@^12.0.2, yargs@^12.0.5:
+yargs-parser@^13.1.0, yargs-parser@^13.1.1:
+  version "13.1.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"
+  integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==
+  dependencies:
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
+
+yargs@12.0.5:
   version "12.0.5"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
   integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
@@ -10341,3 +10976,41 @@ yargs@12.0.5, yargs@^12.0.2, yargs@^12.0.5:
     which-module "^2.0.0"
     y18n "^3.2.1 || ^4.0.0"
     yargs-parser "^11.1.1"
+
+yargs@13.2.4:
+  version "13.2.4"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83"
+  integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==
+  dependencies:
+    cliui "^5.0.0"
+    find-up "^3.0.0"
+    get-caller-file "^2.0.1"
+    os-locale "^3.1.0"
+    require-directory "^2.1.1"
+    require-main-filename "^2.0.0"
+    set-blocking "^2.0.0"
+    string-width "^3.0.0"
+    which-module "^2.0.0"
+    y18n "^4.0.0"
+    yargs-parser "^13.1.0"
+
+yargs@^13.3.0:
+  version "13.3.0"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83"
+  integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==
+  dependencies:
+    cliui "^5.0.0"
+    find-up "^3.0.0"
+    get-caller-file "^2.0.1"
+    require-directory "^2.1.1"
+    require-main-filename "^2.0.0"
+    set-blocking "^2.0.0"
+    string-width "^3.0.0"
+    which-module "^2.0.0"
+    y18n "^4.0.0"
+    yargs-parser "^13.1.1"
+
+zlibjs@^0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/zlibjs/-/zlibjs-0.3.1.tgz#50197edb28a1c42ca659cc8b4e6a9ddd6d444554"
+  integrity sha1-UBl+2yihxCymWcyLTmqd3W1ERVQ=