about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.buildpacks1
-rw-r--r--.circleci/config.yml11
-rw-r--r--app.json3
-rw-r--r--app/controllers/api/v1/statuses_controller.rb1
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/javascript/mastodon/actions/compose.js2
-rw-r--r--app/javascript/mastodon/actions/statuses.js3
-rw-r--r--app/javascript/mastodon/actions/streaming.js13
-rw-r--r--app/javascript/mastodon/components/common_counter.js2
-rw-r--r--app/javascript/mastodon/components/scrollable_list.js2
-rw-r--r--app/javascript/mastodon/features/account_timeline/index.js15
-rw-r--r--app/javascript/mastodon/locales/ar.json2
-rw-r--r--app/javascript/mastodon/locales/ast.json2
-rw-r--r--app/javascript/mastodon/locales/bg.json2
-rw-r--r--app/javascript/mastodon/locales/bn.json2
-rw-r--r--app/javascript/mastodon/locales/cs.json2
-rw-r--r--app/javascript/mastodon/locales/cy.json2
-rw-r--r--app/javascript/mastodon/locales/da.json2
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json18
-rw-r--r--app/javascript/mastodon/locales/en.json2
-rw-r--r--app/javascript/mastodon/locales/et.json2
-rw-r--r--app/javascript/mastodon/locales/eu.json2
-rw-r--r--app/javascript/mastodon/locales/fi.json2
-rw-r--r--app/javascript/mastodon/locales/ga.json2
-rw-r--r--app/javascript/mastodon/locales/he.json2
-rw-r--r--app/javascript/mastodon/locales/hi.json2
-rw-r--r--app/javascript/mastodon/locales/hr.json2
-rw-r--r--app/javascript/mastodon/locales/id.json2
-rw-r--r--app/javascript/mastodon/locales/io.json2
-rw-r--r--app/javascript/mastodon/locales/is.json2
-rw-r--r--app/javascript/mastodon/locales/ka.json2
-rw-r--r--app/javascript/mastodon/locales/kab.json2
-rw-r--r--app/javascript/mastodon/locales/kk.json2
-rw-r--r--app/javascript/mastodon/locales/kn.json2
-rw-r--r--app/javascript/mastodon/locales/ku.json2
-rw-r--r--app/javascript/mastodon/locales/lt.json2
-rw-r--r--app/javascript/mastodon/locales/lv.json2
-rw-r--r--app/javascript/mastodon/locales/mk.json2
-rw-r--r--app/javascript/mastodon/locales/ml.json2
-rw-r--r--app/javascript/mastodon/locales/mr.json2
-rw-r--r--app/javascript/mastodon/locales/ms.json2
-rw-r--r--app/javascript/mastodon/locales/nl.json2
-rw-r--r--app/javascript/mastodon/locales/nn.json2
-rw-r--r--app/javascript/mastodon/locales/no.json2
-rw-r--r--app/javascript/mastodon/locales/oc.json2
-rw-r--r--app/javascript/mastodon/locales/pl.json2
-rw-r--r--app/javascript/mastodon/locales/ro.json2
-rw-r--r--app/javascript/mastodon/locales/sc.json2
-rw-r--r--app/javascript/mastodon/locales/sk.json2
-rw-r--r--app/javascript/mastodon/locales/sl.json2
-rw-r--r--app/javascript/mastodon/locales/sr-Latn.json2
-rw-r--r--app/javascript/mastodon/locales/sr.json2
-rw-r--r--app/javascript/mastodon/locales/sv.json2
-rw-r--r--app/javascript/mastodon/locales/szl.json2
-rw-r--r--app/javascript/mastodon/locales/ta.json2
-rw-r--r--app/javascript/mastodon/locales/tai.json2
-rw-r--r--app/javascript/mastodon/locales/te.json2
-rw-r--r--app/javascript/mastodon/locales/th.json2
-rw-r--r--app/javascript/mastodon/locales/tr.json2
-rw-r--r--app/javascript/mastodon/locales/ug.json2
-rw-r--r--app/javascript/mastodon/locales/uk.json2
-rw-r--r--app/javascript/mastodon/locales/ur.json2
-rw-r--r--app/javascript/mastodon/locales/zh-HK.json2
-rw-r--r--app/javascript/mastodon/locales/zh-TW.json2
-rw-r--r--app/javascript/styles/mastodon-light/diff.scss7
-rw-r--r--app/lib/activitypub/activity.rb28
-rw-r--r--app/lib/activitypub/activity/announce.rb39
-rw-r--r--app/lib/activitypub/activity/block.rb7
-rw-r--r--app/lib/activitypub/activity/create.rb2
-rw-r--r--app/lib/activitypub/activity/undo.rb51
-rw-r--r--app/lib/activitypub/activity/update.rb2
-rw-r--r--app/lib/exceptions.rb1
-rw-r--r--app/lib/feed_manager.rb8
-rw-r--r--app/models/concerns/remotable.rb2
-rw-r--r--app/models/media_attachment.rb1
-rw-r--r--app/serializers/rest/media_attachment_serializer.rb6
-rw-r--r--app/services/update_account_service.rb2
-rw-r--r--app/workers/activitypub/processing_worker.rb2
-rw-r--r--chart/values.yaml.template6
-rw-r--r--lib/paperclip/media_type_spoof_detector_extensions.rb2
-rw-r--r--package.json2
-rw-r--r--spec/controllers/accounts_controller_spec.rb31
-rw-r--r--spec/controllers/activitypub/collections_controller_spec.rb23
-rw-r--r--spec/controllers/activitypub/outboxes_controller_spec.rb23
-rw-r--r--spec/controllers/activitypub/replies_controller_spec.rb23
-rw-r--r--spec/controllers/statuses_controller_spec.rb23
-rw-r--r--spec/lib/activitypub/activity/block_spec.rb22
-rw-r--r--spec/lib/activitypub/activity/undo_spec.rb35
-rw-r--r--spec/lib/feed_manager_spec.rb25
-rw-r--r--yarn.lock8
90 files changed, 414 insertions, 144 deletions
diff --git a/.buildpacks b/.buildpacks
index 3450683ce..5e73304a5 100644
--- a/.buildpacks
+++ b/.buildpacks
@@ -1,4 +1,3 @@
 https://github.com/heroku/heroku-buildpack-apt
 https://github.com/Scalingo/ffmpeg-buildpack
-https://github.com/Scalingo/nodejs-buildpack
 https://github.com/Scalingo/ruby-buildpack
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 9f43a0573..862fa126b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -72,11 +72,12 @@ aliases:
         - run:
             name: Set bundler settings
             command: |
-              bundle config clean 'true'
-              bundle config deployment 'true'
-              bundle config with 'pam_authentication'
-              bundle config without 'development production'
-              bundle config frozen 'true'
+              bundle config --local clean 'true'
+              bundle config --local deployment 'true'
+              bundle config --local with 'pam_authentication'
+              bundle config --local without 'development production'
+              bundle config --local frozen 'true'
+              bundle config --local path $BUNDLE_PATH
         - run:
             name: Install bundler dependencies
             command: bundle check || (bundle install && bundle clean)
diff --git a/app.json b/app.json
index 211f17d81..e4f7cf403 100644
--- a/app.json
+++ b/app.json
@@ -89,9 +89,6 @@
       "url": "https://github.com/heroku/heroku-buildpack-apt"
     },
     {
-      "url": "heroku/nodejs"
-    },
-    {
       "url": "heroku/ruby"
     }
   ],
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index b3edce676..c8529318f 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -58,6 +58,7 @@ class Api::V1::StatusesController < Api::BaseController
 
     @status.discard
     RemovalWorker.perform_async(@status.id, redraft: true)
+    @status.account.statuses_count = @status.account.statuses_count - 1
 
     render json: @status, serializer: REST::StatusSerializer, source_requested: true
   end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 63d9f91fb..e996c2217 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -56,7 +56,7 @@ class ApplicationController < ActionController::Base
   end
 
   def store_current_location
-    store_location_for(:user, request.url) unless request.format == :json
+    store_location_for(:user, request.url) unless [:json, :rss].include?(request.format&.to_sym)
   end
 
   def require_admin!
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index 601a2913c..6ef12f7b9 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -163,7 +163,6 @@ export function submitCompose(routerHistory) {
 
       // To make the app more responsive, immediately push the status
       // into the columns
-
       const insertIfOnline = timelineId => {
         const timeline = getState().getIn(['timelines', timelineId]);
 
@@ -181,6 +180,7 @@ export function submitCompose(routerHistory) {
         if (!response.data.local_only) {
           insertIfOnline('public');
         }
+        insertIfOnline(`account:${response.data.account.id}`);
       }
     }).catch(function (error) {
       dispatch(submitComposeFail(error));
diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js
index 5640201c6..e565e0b0a 100644
--- a/app/javascript/mastodon/actions/statuses.js
+++ b/app/javascript/mastodon/actions/statuses.js
@@ -3,7 +3,7 @@ import openDB from '../storage/db';
 import { evictStatus } from '../storage/modifier';
 
 import { deleteFromTimelines } from './timelines';
-import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus } from './importer';
+import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus, importFetchedAccount } from './importer';
 import { ensureComposeIsVisible } from './compose';
 
 export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
@@ -155,6 +155,7 @@ export function deleteStatus(id, routerHistory, withRedraft = false) {
       evictStatus(id);
       dispatch(deleteStatusSuccess(id));
       dispatch(deleteFromTimelines(id));
+      dispatch(importFetchedAccount(response.data.account));
 
       if (withRedraft) {
         dispatch(redraft(status, response.data.text));
diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js
index d998fcac4..7cecff66e 100644
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@ -71,9 +71,10 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
       dispatch(fetchAnnouncements(done))))));
 };
 
-export const connectUserStream      = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
-export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
-export const connectPublicStream    = ({ onlyMedia, onlyRemote } = {}) => connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`);
-export const connectHashtagStream   = (id, tag, local, accept) => connectTimelineStream(`hashtag:${id}${local ? ':local' : ''}`, `hashtag${local ? ':local' : ''}&tag=${tag}`, null, accept);
-export const connectDirectStream    = () => connectTimelineStream('direct', 'direct');
-export const connectListStream      = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);
+export const connectUserStream         = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
+export const connectUserTimelineStream = (accountId) => connectTimelineStream(`account:${accountId}`, 'user');
+export const connectCommunityStream    = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
+export const connectPublicStream       = ({ onlyMedia, onlyRemote } = {}) => connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`);
+export const connectHashtagStream      = (id, tag, local, accept) => connectTimelineStream(`hashtag:${id}${local ? ':local' : ''}`, `hashtag${local ? ':local' : ''}&tag=${tag}`, null, accept);
+export const connectDirectStream       = () => connectTimelineStream('direct', 'direct');
+export const connectListStream         = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);
diff --git a/app/javascript/mastodon/components/common_counter.js b/app/javascript/mastodon/components/common_counter.js
index 4fdf3babf..e10cd9b76 100644
--- a/app/javascript/mastodon/components/common_counter.js
+++ b/app/javascript/mastodon/components/common_counter.js
@@ -37,7 +37,7 @@ export function counterRenderer(counterType, isBold = true) {
     return (displayNumber, pluralReady) => (
       <FormattedMessage
         id='account.following_counter'
-        defaultMessage='{count, plural, other {{counter} Following}}'
+        defaultMessage='{count, plural, one {{counter} Following} other {{counter} Following}}'
         values={{
           count: pluralReady,
           counter: renderCounter(displayNumber),
diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js
index 35740f226..2689b18ef 100644
--- a/app/javascript/mastodon/components/scrollable_list.js
+++ b/app/javascript/mastodon/components/scrollable_list.js
@@ -20,7 +20,7 @@ const mapStateToProps = (state, { scrollKey }) => {
   };
 };
 
-export default @connect(mapStateToProps)
+export default @connect(mapStateToProps, null, null, { forwardRef: true })
 class ScrollableList extends PureComponent {
 
   static contextTypes = {
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js
index 5ea907a1f..3846cc637 100644
--- a/app/javascript/mastodon/features/account_timeline/index.js
+++ b/app/javascript/mastodon/features/account_timeline/index.js
@@ -15,6 +15,8 @@ import { FormattedMessage } from 'react-intl';
 import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
 import MissingIndicator from 'mastodon/components/missing_indicator';
 import TimelineHint from 'mastodon/components/timeline_hint';
+import { me } from 'mastodon/initial_state';
+import { connectUserTimelineStream } from '../../actions/streaming';
 
 const emptyList = ImmutableList();
 
@@ -73,6 +75,12 @@ class AccountTimeline extends ImmutablePureComponent {
     this.props.dispatch(expandAccountTimeline(accountId, { withReplies }));
   }
 
+  componentDidMount () {
+    if (this.props.params.accountId === me) {
+      this.disconnect = this.props.dispatch(connectUserTimelineStream(me));
+    }
+  }
+
   componentWillReceiveProps (nextProps) {
     if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
       this.props.dispatch(fetchAccount(nextProps.params.accountId));
@@ -86,6 +94,13 @@ class AccountTimeline extends ImmutablePureComponent {
     }
   }
 
+  componentWillUnmount () {
+    if (this.disconnect) {
+      this.disconnect();
+      this.disconnect = null;
+    }
+  }
+
   handleLoadMore = maxId => {
     this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, withReplies: this.props.withReplies }));
   }
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index a0b540432..548471edf 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -16,7 +16,7 @@
   "account.followers": "مُتابِعون",
   "account.followers.empty": "لا أحد يتبع هذا الحساب بعد.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "هذا الحساب لا يتبع أحدًا بعد.",
   "account.follows_you": "يتابعك",
   "account.hide_reblogs": "إخفاء ترقيات @{name}",
diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json
index 491faa057..6c6232262 100644
--- a/app/javascript/mastodon/locales/ast.json
+++ b/app/javascript/mastodon/locales/ast.json
@@ -16,7 +16,7 @@
   "account.followers": "Siguidores",
   "account.followers.empty": "Naide sigue a esti usuariu entá.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Esti usuariu entá nun sigue a naide.",
   "account.follows_you": "Síguete",
   "account.hide_reblogs": "Anubrir les comparticiones de @{name}",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index 472c88afc..16520dbad 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -16,7 +16,7 @@
   "account.followers": "Последователи",
   "account.followers.empty": "Все още никой не следва този потребител.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Този потребител все още не следва никого.",
   "account.follows_you": "Твой последовател",
   "account.hide_reblogs": "Hide boosts from @{name}",
diff --git a/app/javascript/mastodon/locales/bn.json b/app/javascript/mastodon/locales/bn.json
index b41cdf16d..7de1a1022 100644
--- a/app/javascript/mastodon/locales/bn.json
+++ b/app/javascript/mastodon/locales/bn.json
@@ -16,7 +16,7 @@
   "account.followers": "অনুসরণকারী",
   "account.followers.empty": "এই সদস্যকে এখনো কেউ অনুসরণ করে না।.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "এই সদস্য কাওকে এখনো অনুসরণ করেন না.",
   "account.follows_you": "আপনাকে অনুসরণ করে",
   "account.hide_reblogs": "@{name}'র সমর্থনগুলি লুকিয়ে ফেলুন",
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index f991b7a25..0ddd18de8 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -16,7 +16,7 @@
   "account.followers": "Sledující",
   "account.followers.empty": "Tohoto uživatele ještě nikdo nesleduje.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "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}",
diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json
index 984237a32..312a0f97a 100644
--- a/app/javascript/mastodon/locales/cy.json
+++ b/app/javascript/mastodon/locales/cy.json
@@ -16,7 +16,7 @@
   "account.followers": "Dilynwyr",
   "account.followers.empty": "Nid oes neb yn dilyn y defnyddiwr hwn eto.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "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}",
diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json
index e59f5283c..a0fb354e6 100644
--- a/app/javascript/mastodon/locales/da.json
+++ b/app/javascript/mastodon/locales/da.json
@@ -16,7 +16,7 @@
   "account.followers": "Følgere",
   "account.followers.empty": "Der er endnu ingen der følger denne bruger.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Denne bruger følger endnu ikke nogen.",
   "account.follows_you": "Følger dig",
   "account.hide_reblogs": "Skjul fremhævelserne fra @{name}",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 5f7c4a0f6..14febf5c3 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -146,7 +146,7 @@
         "id": "account.statuses_counter"
       },
       {
-        "defaultMessage": "{count, plural, other {{counter} Following}}",
+        "defaultMessage": "{count, plural, one {{counter} Following} other {{counter} Following}}",
         "id": "account.following_counter"
       },
       {
@@ -2660,6 +2660,22 @@
         "id": "status.reblog"
       },
       {
+        "defaultMessage": "Public",
+        "id": "privacy.public.short"
+      },
+      {
+        "defaultMessage": "Unlisted",
+        "id": "privacy.unlisted.short"
+      },
+      {
+        "defaultMessage": "Followers-only",
+        "id": "privacy.private.short"
+      },
+      {
+        "defaultMessage": "Direct",
+        "id": "privacy.direct.short"
+      },
+      {
         "defaultMessage": "You can press {combo} to skip this next time",
         "id": "boost_modal.combo"
       }
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 3e7d29352..025ae6e7d 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -16,7 +16,7 @@
   "account.followers": "Followers",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json
index fc00a6270..558fe38de 100644
--- a/app/javascript/mastodon/locales/et.json
+++ b/app/javascript/mastodon/locales/et.json
@@ -16,7 +16,7 @@
   "account.followers": "Jälgijad",
   "account.followers.empty": "Keegi ei jälgi seda kasutajat veel.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "See kasutaja ei jälgi veel kedagi.",
   "account.follows_you": "Jälgib Teid",
   "account.hide_reblogs": "Peida upitused kasutajalt @{name}",
diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json
index 0a8043d4d..09471ff2a 100644
--- a/app/javascript/mastodon/locales/eu.json
+++ b/app/javascript/mastodon/locales/eu.json
@@ -16,7 +16,7 @@
   "account.followers": "Jarraitzaileak",
   "account.followers.empty": "Ez du inork erabiltzaile hau jarraitzen oraindik.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Erabiltzaile honek ez du inor jarraitzen oraindik.",
   "account.follows_you": "Jarraitzen dizu",
   "account.hide_reblogs": "Ezkutatu @{name}(r)en bultzadak",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index 364427e65..44589aa4a 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -16,7 +16,7 @@
   "account.followers": "Seuraajaa",
   "account.followers.empty": "Tällä käyttäjällä ei ole vielä seuraajia.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "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}",
diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json
index 5a27ed96c..5de7b27a5 100644
--- a/app/javascript/mastodon/locales/ga.json
+++ b/app/javascript/mastodon/locales/ga.json
@@ -16,7 +16,7 @@
   "account.followers": "Followers",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index 81e713dc3..91fce039f 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -16,7 +16,7 @@
   "account.followers": "עוקבים",
   "account.followers.empty": "אף אחד לא עוקב אחר המשתמש הזה עדיין.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "משתמש זה לא עוקב אחר אף אחד עדיין.",
   "account.follows_you": "במעקב אחריך",
   "account.hide_reblogs": "להסתיר הידהודים מאת @{name}",
diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json
index 4cc12830f..ff207d640 100644
--- a/app/javascript/mastodon/locales/hi.json
+++ b/app/javascript/mastodon/locales/hi.json
@@ -16,7 +16,7 @@
   "account.followers": "फॉलोवर",
   "account.followers.empty": "कोई भी इस यूज़र् को फ़ॉलो नहीं करता है",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "यह यूज़र् अभी तक किसी को फॉलो नहीं करता है।",
   "account.follows_you": "आपको फॉलो करता है",
   "account.hide_reblogs": "@{name} के बूस्ट छुपाएं",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index a7e13b2eb..7179bc536 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -16,7 +16,7 @@
   "account.followers": "Sljedbenici",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "te slijedi",
   "account.hide_reblogs": "Hide boosts from @{name}",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index 5b0f345af..91ad76e03 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -16,7 +16,7 @@
   "account.followers": "Pengikut",
   "account.followers.empty": "Tidak ada satupun yang mengkuti pengguna ini saat ini.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Pengguna ini belum mengikuti siapapun.",
   "account.follows_you": "Mengikuti anda",
   "account.hide_reblogs": "Sembunyikan boosts dari @{name}",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index 45e74b675..e79bee21d 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -16,7 +16,7 @@
   "account.followers": "Sequanti",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Sequas tu",
   "account.hide_reblogs": "Hide boosts from @{name}",
diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json
index 4a281cf80..72d8fefaf 100644
--- a/app/javascript/mastodon/locales/is.json
+++ b/app/javascript/mastodon/locales/is.json
@@ -16,7 +16,7 @@
   "account.followers": "Fylgjendur",
   "account.followers.empty": "Ennþá fylgist enginn með þessum notanda.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Þessi notandi fylgist ennþá ekki með neinum.",
   "account.follows_you": "Fylgir þér",
   "account.hide_reblogs": "Fela endurbirtingar fyrir @{name}",
diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json
index 36f27e69d..0051fee1f 100644
--- a/app/javascript/mastodon/locales/ka.json
+++ b/app/javascript/mastodon/locales/ka.json
@@ -16,7 +16,7 @@
   "account.followers": "მიმდევრები",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "მოგყვებათ",
   "account.hide_reblogs": "დაიმალოს ბუსტები @{name}-სგან",
diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json
index dceb434d3..05f72596b 100644
--- a/app/javascript/mastodon/locales/kab.json
+++ b/app/javascript/mastodon/locales/kab.json
@@ -16,7 +16,7 @@
   "account.followers": "Imeḍfaren",
   "account.followers.empty": "Ar tura, ulac yiwen i yeṭṭafaṛen amseqdac-agi.",
   "account.followers_counter": "{count, plural, one {{count} n umeḍfar} other {{count} n imeḍfaren}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Ar tura, amseqdac-agi ur yeṭṭafaṛ yiwen.",
   "account.follows_you": "Yeṭṭafaṛ-ik",
   "account.hide_reblogs": "Ffer ayen i ibeṭṭu @{name}",
diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json
index d7adf5836..322c15fff 100644
--- a/app/javascript/mastodon/locales/kk.json
+++ b/app/javascript/mastodon/locales/kk.json
@@ -16,7 +16,7 @@
   "account.followers": "Оқырмандар",
   "account.followers.empty": "Әлі ешкім жазылмаған.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Ешкімге жазылмапты.",
   "account.follows_you": "Сізге жазылыпты",
   "account.hide_reblogs": "@{name} атты қолданушының әрекеттерін жасыру",
diff --git a/app/javascript/mastodon/locales/kn.json b/app/javascript/mastodon/locales/kn.json
index 43738ba70..e9618e0f2 100644
--- a/app/javascript/mastodon/locales/kn.json
+++ b/app/javascript/mastodon/locales/kn.json
@@ -16,7 +16,7 @@
   "account.followers": "Followers",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
diff --git a/app/javascript/mastodon/locales/ku.json b/app/javascript/mastodon/locales/ku.json
index 36f55e800..e5d833fe8 100644
--- a/app/javascript/mastodon/locales/ku.json
+++ b/app/javascript/mastodon/locales/ku.json
@@ -16,7 +16,7 @@
   "account.followers": "Followers",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json
index 43738ba70..e9618e0f2 100644
--- a/app/javascript/mastodon/locales/lt.json
+++ b/app/javascript/mastodon/locales/lt.json
@@ -16,7 +16,7 @@
   "account.followers": "Followers",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json
index 01fe7a07c..2812760a6 100644
--- a/app/javascript/mastodon/locales/lv.json
+++ b/app/javascript/mastodon/locales/lv.json
@@ -16,7 +16,7 @@
   "account.followers": "Sekotāji",
   "account.followers.empty": "Šim lietotājam nav sekotāju.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "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}",
diff --git a/app/javascript/mastodon/locales/mk.json b/app/javascript/mastodon/locales/mk.json
index 7e9c0dc42..2bb9a2e2a 100644
--- a/app/javascript/mastodon/locales/mk.json
+++ b/app/javascript/mastodon/locales/mk.json
@@ -16,7 +16,7 @@
   "account.followers": "Следбеници",
   "account.followers.empty": "Никој не го следи овој корисник сеуште.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Корисникот не следи никој сеуште.",
   "account.follows_you": "Те следи тебе",
   "account.hide_reblogs": "Сокриј буст од @{name}",
diff --git a/app/javascript/mastodon/locales/ml.json b/app/javascript/mastodon/locales/ml.json
index 78dced532..4a390ad70 100644
--- a/app/javascript/mastodon/locales/ml.json
+++ b/app/javascript/mastodon/locales/ml.json
@@ -16,7 +16,7 @@
   "account.followers": "പിന്തുടരുന്നവർ",
   "account.followers.empty": "ഈ ഉപയോക്താവിനെ ആരും ഇതുവരെ പിന്തുടരുന്നില്ല.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "ഈ ഉപയോക്താവ് ആരേയും ഇതുവരെ പിന്തുടരുന്നില്ല.",
   "account.follows_you": "നിങ്ങളെ പിന്തുടരുന്നു",
   "account.hide_reblogs": "@{name} ബൂസ്റ്റ് ചെയ്തവ മറയ്കുക",
diff --git a/app/javascript/mastodon/locales/mr.json b/app/javascript/mastodon/locales/mr.json
index a56d2544a..5a93ae851 100644
--- a/app/javascript/mastodon/locales/mr.json
+++ b/app/javascript/mastodon/locales/mr.json
@@ -16,7 +16,7 @@
   "account.followers": "अनुयायी",
   "account.followers.empty": "ह्या वापरकर्त्याचा आतापर्यंत कोणी अनुयायी नाही.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "हा वापरकर्ता अजूनपर्यंत कोणाचा अनुयायी नाही.",
   "account.follows_you": "तुमचा अनुयायी आहे",
   "account.hide_reblogs": "@{name} पासून सर्व बूस्ट लपवा",
diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json
index a19483014..5fc777887 100644
--- a/app/javascript/mastodon/locales/ms.json
+++ b/app/javascript/mastodon/locales/ms.json
@@ -16,7 +16,7 @@
   "account.followers": "Followers",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index ad10bb63c..73e2b9a13 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -16,7 +16,7 @@
   "account.followers": "Volgers",
   "account.followers.empty": "Niemand volgt nog deze gebruiker.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Deze gebruiker volgt nog niemand.",
   "account.follows_you": "Volgt jou",
   "account.hide_reblogs": "Verberg boosts van @{name}",
diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json
index ed151565d..b567a3533 100644
--- a/app/javascript/mastodon/locales/nn.json
+++ b/app/javascript/mastodon/locales/nn.json
@@ -16,7 +16,7 @@
   "account.followers": "Fylgjarar",
   "account.followers.empty": "Ingen fylgjer denne brukaren enno.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Denne brukaren fylgjer ikkje nokon enno.",
   "account.follows_you": "Fylgjer deg",
   "account.hide_reblogs": "Gøym fremhevingar frå @{name}",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index 7ddfea5bc..a15e96fdc 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -16,7 +16,7 @@
   "account.followers": "Følgere",
   "account.followers.empty": "Ingen følger denne brukeren ennå.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Denne brukeren følger ikke noen enda.",
   "account.follows_you": "Følger deg",
   "account.hide_reblogs": "Skjul fremhevinger fra @{name}",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index dfee6f157..1aa0193a5 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -16,7 +16,7 @@
   "account.followers": "Seguidors",
   "account.followers.empty": "Degun sèc pas aqueste utilizaire pel moment.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "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}",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index 20b3b2b9f..042482240 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -16,7 +16,7 @@
   "account.followers": "Śledzący",
   "account.followers.empty": "Nikt jeszcze nie śledzi tego użytkownika.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Ten użytkownik nie śledzi jeszcze nikogo.",
   "account.follows_you": "Śledzi Cię",
   "account.hide_reblogs": "Ukryj podbicia od @{name}",
diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json
index c00eca297..544d68102 100644
--- a/app/javascript/mastodon/locales/ro.json
+++ b/app/javascript/mastodon/locales/ro.json
@@ -16,7 +16,7 @@
   "account.followers": "Urmăritori",
   "account.followers.empty": "Acest utilizator nu are încă urmăritori.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Acest utilizator nu urmărește pe nimeni încă.",
   "account.follows_you": "Te urmărește",
   "account.hide_reblogs": "Ascunde impulsurile de la @{name}",
diff --git a/app/javascript/mastodon/locales/sc.json b/app/javascript/mastodon/locales/sc.json
index 916576656..30a3e3374 100644
--- a/app/javascript/mastodon/locales/sc.json
+++ b/app/javascript/mastodon/locales/sc.json
@@ -16,7 +16,7 @@
   "account.followers": "Sighiduras",
   "account.followers.empty": "Nemos sighit ancora custa persone.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Custa persone non sighit ancora a nemos.",
   "account.follows_you": "Ti sighit",
   "account.hide_reblogs": "Cua is cumpartziduras de @{name}",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 569a8cd12..4e48d53c8 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -16,7 +16,7 @@
   "account.followers": "Sledujúci",
   "account.followers.empty": "Tohto používateľa ešte nikto nenásleduje.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Tento používateľ ešte nikoho nenasleduje.",
   "account.follows_you": "Nasleduje ťa",
   "account.hide_reblogs": "Skry vyzdvihnutia od @{name}",
diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json
index d13bf5e36..71ba0d1b6 100644
--- a/app/javascript/mastodon/locales/sl.json
+++ b/app/javascript/mastodon/locales/sl.json
@@ -16,7 +16,7 @@
   "account.followers": "Sledilci",
   "account.followers.empty": "Nihče ne sledi temu uporabniku.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Ta uporabnik še ne sledi nikomur.",
   "account.follows_you": "Sledi tebi",
   "account.hide_reblogs": "Skrij spodbude od @{name}",
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index 7a17e9134..d7be7380f 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -16,7 +16,7 @@
   "account.followers": "Pratioca",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "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}",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index bcaa45ed0..7c42f0e8a 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -16,7 +16,7 @@
   "account.followers": "Пратиоци",
   "account.followers.empty": "Тренутно нико не прати овог корисника.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Корисник тренутно не прати никога.",
   "account.follows_you": "Прати Вас",
   "account.hide_reblogs": "Сакриј подршке које даје корисника @{name}",
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index a0d0807ab..c9251d73c 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -16,7 +16,7 @@
   "account.followers": "Följare",
   "account.followers.empty": "Ingen följer denna användare än.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Denna användare följer inte någon än.",
   "account.follows_you": "Följer dig",
   "account.hide_reblogs": "Dölj knuffar från @{name}",
diff --git a/app/javascript/mastodon/locales/szl.json b/app/javascript/mastodon/locales/szl.json
index 36f55e800..e5d833fe8 100644
--- a/app/javascript/mastodon/locales/szl.json
+++ b/app/javascript/mastodon/locales/szl.json
@@ -16,7 +16,7 @@
   "account.followers": "Followers",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json
index 9caef0582..28f03f781 100644
--- a/app/javascript/mastodon/locales/ta.json
+++ b/app/javascript/mastodon/locales/ta.json
@@ -16,7 +16,7 @@
   "account.followers": "பின்தொடர்பவர்கள்",
   "account.followers.empty": "இதுவரை யாரும் இந்த பயனரைப் பின்தொடரவில்லை.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "இந்த பயனர் இதுவரை யாரையும் பின்தொடரவில்லை.",
   "account.follows_you": "உங்களைப் பின்தொடர்கிறார்",
   "account.hide_reblogs": "இருந்து ஊக்கியாக மறை @{name}",
diff --git a/app/javascript/mastodon/locales/tai.json b/app/javascript/mastodon/locales/tai.json
index 36f55e800..e5d833fe8 100644
--- a/app/javascript/mastodon/locales/tai.json
+++ b/app/javascript/mastodon/locales/tai.json
@@ -16,7 +16,7 @@
   "account.followers": "Followers",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json
index a57efd7c6..4763dcbd3 100644
--- a/app/javascript/mastodon/locales/te.json
+++ b/app/javascript/mastodon/locales/te.json
@@ -16,7 +16,7 @@
   "account.followers": "అనుచరులు",
   "account.followers.empty": "ఈ వినియోగదారుడిని ఇంకా ఎవరూ అనుసరించడంలేదు.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "ఈ వినియోగదారి ఇంకా ఎవరినీ అనుసరించడంలేదు.",
   "account.follows_you": "మిమ్మల్ని అనుసరిస్తున్నారు",
   "account.hide_reblogs": "@{name} నుంచి బూస్ట్ లను దాచిపెట్టు",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 5852ec5ca..a5c6b07cd 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -16,7 +16,7 @@
   "account.followers": "ผู้ติดตาม",
   "account.followers.empty": "ยังไม่มีใครติดตามผู้ใช้นี้",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "ผู้ใช้นี้ยังไม่ได้ติดตามใคร",
   "account.follows_you": "ติดตามคุณ",
   "account.hide_reblogs": "ซ่อนการดันจาก @{name}",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index a5bbbfcd9..351e80f81 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -16,7 +16,7 @@
   "account.followers": "Takipçi",
   "account.followers.empty": "Henüz kimse bu kullanıcıyı takip etmiyor.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Bu kullanıcı henüz kimseyi takip etmiyor.",
   "account.follows_you": "Seni takip ediyor",
   "account.hide_reblogs": "@{name} kişisinin yinelemelerini gizle",
diff --git a/app/javascript/mastodon/locales/ug.json b/app/javascript/mastodon/locales/ug.json
index 36f55e800..e5d833fe8 100644
--- a/app/javascript/mastodon/locales/ug.json
+++ b/app/javascript/mastodon/locales/ug.json
@@ -16,7 +16,7 @@
   "account.followers": "Followers",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 18e151263..244f04a9e 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -16,7 +16,7 @@
   "account.followers": "Підписники",
   "account.followers.empty": "Ніхто ще не підписався на цього користувача.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "Цей користувач ще ні на кого не підписався.",
   "account.follows_you": "Підписаний(-а) на вас",
   "account.hide_reblogs": "Сховати передмухи від @{name}",
diff --git a/app/javascript/mastodon/locales/ur.json b/app/javascript/mastodon/locales/ur.json
index 5924d1487..b94b7c162 100644
--- a/app/javascript/mastodon/locales/ur.json
+++ b/app/javascript/mastodon/locales/ur.json
@@ -16,7 +16,7 @@
   "account.followers": "پیروکار",
   "account.followers.empty": "\"ہنوز اس صارف کی کوئی پیروی نہیں کرتا\".",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "\"یہ صارف ہنوز کسی کی پیروی نہیں کرتا ہے\".",
   "account.follows_you": "آپ کا پیروکار ہے",
   "account.hide_reblogs": "@{name} سے فروغ چھپائیں",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index b537ca4f3..24a63ab6b 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -16,7 +16,7 @@
   "account.followers": "關注的人",
   "account.followers.empty": "尚沒有人關注這位使用者。",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "這位使用者尚未關注任何使用者。",
   "account.follows_you": "關注你",
   "account.hide_reblogs": "隱藏 @{name} 的轉推",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index d674734c1..e964ccd49 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -16,7 +16,7 @@
   "account.followers": "關注者",
   "account.followers.empty": "尚沒有人關注這位使用者。",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
-  "account.following_counter": "{count, plural, other {{counter} Following}}",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
   "account.follows.empty": "這位使用者尚未關注任何使用者。",
   "account.follows_you": "關注了你",
   "account.hide_reblogs": "隱藏來自 @{name} 的轉推",
diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index 7a846bcc6..8c8d69fc4 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -767,10 +767,3 @@ 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/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb
index 0ce279d28..ab946470b 100644
--- a/app/lib/activitypub/activity.rb
+++ b/app/lib/activitypub/activity.rb
@@ -157,6 +157,34 @@ class ActivityPub::Activity
     fetch_remote_original_status
   end
 
+  def dereference_object!
+    return unless @object.is_a?(String)
+    return if invalid_origin?(@object)
+
+    object = fetch_resource(@object, true, signed_fetch_account)
+    return unless object.present? && object.is_a?(Hash) && supported_context?(object)
+
+    @object = object
+  end
+
+  def signed_fetch_account
+    first_mentioned_local_account || first_local_follower
+  end
+
+  def first_mentioned_local_account
+    audience = (as_array(@json['to']) + as_array(@json['cc'])).uniq
+    local_usernames = audience.select { |uri| ActivityPub::TagManager.instance.local_uri?(uri) }
+                              .map { |uri| ActivityPub::TagManager.instance.uri_to_local_id(uri, :username) }
+
+    return if local_usernames.empty?
+
+    Account.local.where(username: local_usernames).first
+  end
+
+  def first_local_follower
+    @account.followers.local.first
+  end
+
   def follow_request_from_object
     @follow_request ||= FollowRequest.find_by(target_account: @account, uri: object_uri) unless object_uri.nil?
   end
diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb
index 34c646668..9e108985a 100644
--- a/app/lib/activitypub/activity/announce.rb
+++ b/app/lib/activitypub/activity/announce.rb
@@ -4,25 +4,32 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
   def perform
     return reject_payload! if delete_arrived_first?(@json['id']) || !related_to_local_activity?
 
-    original_status = status_from_object
+    RedisLock.acquire(lock_options) do |lock|
+      if lock.acquired?
+        original_status = status_from_object
 
-    return reject_payload! if original_status.nil? || !announceable?(original_status)
+        return reject_payload! if original_status.nil? || !announceable?(original_status)
 
-    status = Status.find_by(account: @account, reblog: original_status)
+        @status = Status.find_by(account: @account, reblog: original_status)
 
-    return status unless status.nil?
+        return @status unless @status.nil?
 
-    status = Status.create!(
-      account: @account,
-      reblog: original_status,
-      uri: @json['id'],
-      created_at: @json['published'],
-      override_timestamps: @options[:override_timestamps],
-      visibility: visibility_from_audience
-    )
+        @status = Status.create!(
+          account: @account,
+          reblog: original_status,
+          uri: @json['id'],
+          created_at: @json['published'],
+          override_timestamps: @options[:override_timestamps],
+          visibility: visibility_from_audience
+        )
 
-    distribute(status)
-    status
+        distribute(@status)
+      else
+        raise Mastodon::RaceConditionError
+      end
+    end
+
+    @status
   end
 
   private
@@ -54,4 +61,8 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
   def reblog_of_local_status?
     status_from_uri(object_uri)&.account&.local?
   end
+
+  def lock_options
+    { redis: Redis.current, key: "announce:#{@object['id']}" }
+  end
 end
diff --git a/app/lib/activitypub/activity/block.rb b/app/lib/activitypub/activity/block.rb
index a17a2d50a..90477bf33 100644
--- a/app/lib/activitypub/activity/block.rb
+++ b/app/lib/activitypub/activity/block.rb
@@ -4,7 +4,12 @@ class ActivityPub::Activity::Block < ActivityPub::Activity
   def perform
     target_account = account_from_uri(object_uri)
 
-    return if target_account.nil? || !target_account.local? || @account.blocking?(target_account)
+    return if target_account.nil? || !target_account.local?
+
+    if @account.blocking?(target_account)
+      @account.block_relationships.find_by(target_account: target_account).update(uri: @json['id']) if @json['id'].present?
+      return
+    end
 
     UnfollowService.new.call(target_account, @account) if target_account.following?(@account)
 
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index e81452e3c..08dd98e94 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -2,6 +2,8 @@
 
 class ActivityPub::Activity::Create < ActivityPub::Activity
   def perform
+    dereference_object!
+
     case @object['type']
     when 'EncryptedMessage'
       create_encrypted_message
diff --git a/app/lib/activitypub/activity/undo.rb b/app/lib/activitypub/activity/undo.rb
index 599823c6e..9eff1b71c 100644
--- a/app/lib/activitypub/activity/undo.rb
+++ b/app/lib/activitypub/activity/undo.rb
@@ -13,11 +13,62 @@ class ActivityPub::Activity::Undo < ActivityPub::Activity
       undo_like
     when 'Block'
       undo_block
+    when nil
+      handle_reference
     end
   end
 
   private
 
+  def handle_reference
+    # Some implementations do not inline the object, and as we don't have a
+    # global index, we have to guess what object it is.
+    return if object_uri.nil?
+
+    try_undo_announce || try_undo_accept || try_undo_follow || try_undo_like || try_undo_block || delete_later!(object_uri)
+  end
+
+  def try_undo_announce
+    status = Status.where.not(reblog_of_id: nil).find_by(uri: object_uri, account: @account)
+    if status.present?
+      RemoveStatusService.new.call(status)
+      true
+    else
+      false
+    end
+  end
+
+  def try_undo_accept
+    # We can't currently handle `Undo Accept` as we don't record `Accept`'s uri
+    false
+  end
+
+  def try_undo_follow
+    follow = @account.follow_requests.find_by(uri: object_uri) || @account.active_relationships.find_by(uri: object_uri)
+
+    if follow.present?
+      follow.destroy
+      true
+    else
+      false
+    end
+  end
+
+  def try_undo_like
+    # There is an index on accounts, but an account may have *many* favs, so this may be too costly
+    false
+  end
+
+  def try_undo_block
+    block = @account.block_relationships.find_by(uri: object_uri)
+    if block.present?
+      UnblockService.new.call(@account, block.target_account)
+      true
+    else
+      false
+    end
+  end
+
   def undo_announce
     return if object_uri.nil?
 
diff --git a/app/lib/activitypub/activity/update.rb b/app/lib/activitypub/activity/update.rb
index 70035325b..018e2df54 100644
--- a/app/lib/activitypub/activity/update.rb
+++ b/app/lib/activitypub/activity/update.rb
@@ -4,6 +4,8 @@ class ActivityPub::Activity::Update < ActivityPub::Activity
   SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
 
   def perform
+    dereference_object!
+
     if equals_or_includes_any?(@object['type'], SUPPORTED_TYPES)
       update_account
     elsif equals_or_includes_any?(@object['type'], %w(Question))
diff --git a/app/lib/exceptions.rb b/app/lib/exceptions.rb
index 3362576b0..7c8e77871 100644
--- a/app/lib/exceptions.rb
+++ b/app/lib/exceptions.rb
@@ -7,6 +7,7 @@ module Mastodon
   class HostValidationError < ValidationError; end
   class LengthValidationError < ValidationError; end
   class DimensionsValidationError < ValidationError; end
+  class StreamValidationError < ValidationError; end
   class RaceConditionError < Error; end
   class RateLimitExceededError < Error; end
 
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index 6afdd3b08..2dc60092c 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -139,9 +139,15 @@ class FeedManager
   end
 
   def clear_from_timeline(account, target_account)
+    # Clear from timeline all statuses from or mentionning target_account
     timeline_key        = key(:home, account.id)
     timeline_status_ids = redis.zrange(timeline_key, 0, -1)
-    target_statuses     = Status.where(id: timeline_status_ids, account: target_account)
+    statuses            = Status.where(id: timeline_status_ids).select(:id, :reblog_of_id, :account_id).to_a
+    reblogged_ids       = Status.where(id: statuses.map(&:reblog_of_id).compact, account: target_account).pluck(:id)
+    with_mentions_ids   = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact, account: target_account).pluck(:status_id)
+    target_statuses     = statuses.filter do |status|
+      status.account_id == target_account.id || reblogged_ids.include?(status.reblog_of_id) || with_mentions_ids.include?(status.id) || with_mentions_ids.include?(status.reblog_of_id)
+    end
 
     target_statuses.each do |status|
       unpush_from_home(account, status)
diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb
index c6d0c7f1f..56b9c0164 100644
--- a/app/models/concerns/remotable.rb
+++ b/app/models/concerns/remotable.rb
@@ -29,7 +29,7 @@ module Remotable
         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
+        rescue Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Paperclip::Error, Mastodon::DimensionsValidationError, Mastodon::StreamValidationError => e
           Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}"
         end
 
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 0441fc699..cfdd95b22 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -336,6 +336,7 @@ class MediaAttachment < ApplicationRecord
 
     return unless movie.valid?
 
+    raise Mastodon::StreamValidationError, 'Video has no video stream' if movie.width.nil? || movie.frame_rate.nil?
     raise Mastodon::DimensionsValidationError, "#{movie.width}x#{movie.height} videos are not supported" if movie.width * movie.height > MAX_VIDEO_MATRIX_LIMIT
     raise Mastodon::DimensionsValidationError, "#{movie.frame_rate.to_i}fps videos are not supported" if movie.frame_rate > MAX_VIDEO_FRAME_RATE
   end
diff --git a/app/serializers/rest/media_attachment_serializer.rb b/app/serializers/rest/media_attachment_serializer.rb
index e65f7acf1..a24f95315 100644
--- a/app/serializers/rest/media_attachment_serializer.rb
+++ b/app/serializers/rest/media_attachment_serializer.rb
@@ -4,7 +4,7 @@ class REST::MediaAttachmentSerializer < ActiveModel::Serializer
   include RoutingHelper
 
   attributes :id, :type, :url, :preview_url,
-             :remote_url, :text_url, :meta,
+             :remote_url, :preview_remote_url, :text_url, :meta,
              :description, :blurhash
 
   def id
@@ -35,6 +35,10 @@ class REST::MediaAttachmentSerializer < ActiveModel::Serializer
     end
   end
 
+  def preview_remote_url
+    object.thumbnail_remote_url.presence
+  end
+
   def text_url
     object.local? ? medium_url(object) : nil
   end
diff --git a/app/services/update_account_service.rb b/app/services/update_account_service.rb
index 4172d5774..666db5c71 100644
--- a/app/services/update_account_service.rb
+++ b/app/services/update_account_service.rb
@@ -12,7 +12,7 @@ class UpdateAccountService < BaseService
       check_links(account)
       process_hashtags(account)
     end
-  rescue Mastodon::DimensionsValidationError => de
+  rescue Mastodon::DimensionsValidationError, Mastodon::StreamValidationError => de
     account.errors.add(:avatar, de.message)
     false
   end
diff --git a/app/workers/activitypub/processing_worker.rb b/app/workers/activitypub/processing_worker.rb
index 05139f616..cef595319 100644
--- a/app/workers/activitypub/processing_worker.rb
+++ b/app/workers/activitypub/processing_worker.rb
@@ -3,7 +3,7 @@
 class ActivityPub::ProcessingWorker
   include Sidekiq::Worker
 
-  sidekiq_options backtrace: true
+  sidekiq_options backtrace: true, retry: 8
 
   def perform(account_id, body, delivered_to_account_id = nil)
     ActivityPub::ProcessCollectionService.new.call(body, Account.find(account_id), override_timestamps: true, delivered_to_account_id: delivered_to_account_id, delivery: true)
diff --git a/chart/values.yaml.template b/chart/values.yaml.template
index 4a8286d00..694bc4d42 100644
--- a/chart/values.yaml.template
+++ b/chart/values.yaml.template
@@ -16,6 +16,12 @@ ingress:
     kubernetes.io/ingress.class: nginx
     kubernetes.io/tls-acme: "true"
     # cert-manager.io/cluster-issuer: "letsencrypt"
+    #
+    # ensure that NGINX's upload size matches Mastodon's
+    #   for the K8s ingress controller:
+    # nginx.ingress.kubernetes.io/proxy-body-size: 40m
+    #   for the NGINX ingress controller:
+    # nginx.org/client-max-body-size: 40m
   # this value is used for LOCAL_DOMAIN
   hostname: mastodon.local
   tls:
diff --git a/lib/paperclip/media_type_spoof_detector_extensions.rb b/lib/paperclip/media_type_spoof_detector_extensions.rb
index 363934d8d..43337cc68 100644
--- a/lib/paperclip/media_type_spoof_detector_extensions.rb
+++ b/lib/paperclip/media_type_spoof_detector_extensions.rb
@@ -18,7 +18,7 @@ module Paperclip
       @type_from_mime_magic ||= begin
         begin
           File.open(@file.path) do |file|
-            MimeMagic.by_magic(file)&.type
+            MimeMagic.by_magic(file)&.type || ''
           end
         rescue Errno::ENOENT
           ''
diff --git a/package.json b/package.json
index 5aea0e69c..bea3fc6ea 100644
--- a/package.json
+++ b/package.json
@@ -110,7 +110,7 @@
     "intl-relativeformat": "^6.4.3",
     "is-nan": "^1.3.0",
     "js-yaml": "^3.13.1",
-    "lodash": "^4.17.14",
+    "lodash": "^4.17.19",
     "mark-loader": "^0.1.6",
     "marky": "^1.2.1",
     "mini-css-extract-plugin": "^0.9.0",
diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb
index bd36f5494..93bf2c83f 100644
--- a/spec/controllers/accounts_controller_spec.rb
+++ b/spec/controllers/accounts_controller_spec.rb
@@ -5,6 +5,21 @@ RSpec.describe AccountsController, type: :controller do
 
   let(:account) { Fabricate(:user).account }
 
+  shared_examples 'cachable response' do
+    it 'does not set cookies' do
+      expect(response.cookies).to be_empty
+      expect(response.headers['Set-Cookies']).to be nil
+    end
+
+    it 'does not set sessions' do
+      expect(session).to be_empty
+    end
+
+    it 'returns public Cache-Control header' do
+      expect(response.headers['Cache-Control']).to include 'public'
+    end
+  end
+
   describe 'GET #show' do
     let(:format) { 'html' }
 
@@ -323,9 +338,7 @@ RSpec.describe AccountsController, type: :controller do
           expect(response.content_type).to eq 'application/activity+json'
         end
 
-        it 'returns public Cache-Control header' do
-          expect(response.headers['Cache-Control']).to include 'public'
-        end
+        it_behaves_like 'cachable response'
 
         it 'renders account' do
           json = body_as_json
@@ -343,9 +356,7 @@ RSpec.describe AccountsController, type: :controller do
             expect(response.content_type).to eq 'application/activity+json'
           end
 
-          it 'returns public Cache-Control header' do
-            expect(response.headers['Cache-Control']).to include 'public'
-          end
+          it_behaves_like 'cachable response'
 
           it 'returns Vary header with Signature' do
             expect(response.headers['Vary']).to include 'Signature'
@@ -401,9 +412,7 @@ RSpec.describe AccountsController, type: :controller do
           expect(response.content_type).to eq 'application/activity+json'
         end
 
-        it 'returns public Cache-Control header' do
-          expect(response.headers['Cache-Control']).to include 'public'
-        end
+        it_behaves_like 'cachable response'
 
         it 'renders account' do
           json = body_as_json
@@ -447,9 +456,7 @@ RSpec.describe AccountsController, type: :controller do
           expect(response).to have_http_status(200)
         end
 
-        it 'returns public Cache-Control header' do
-          expect(response.headers['Cache-Control']).to include 'public'
-        end
+        it_behaves_like 'cachable response'
       end
 
       context do
diff --git a/spec/controllers/activitypub/collections_controller_spec.rb b/spec/controllers/activitypub/collections_controller_spec.rb
index 56be49be3..89939d1d2 100644
--- a/spec/controllers/activitypub/collections_controller_spec.rb
+++ b/spec/controllers/activitypub/collections_controller_spec.rb
@@ -6,6 +6,21 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
   let!(:account) { Fabricate(:account) }
   let(:remote_account) { nil }
 
+  shared_examples 'cachable response' do
+    it 'does not set cookies' do
+      expect(response.cookies).to be_empty
+      expect(response.headers['Set-Cookies']).to be nil
+    end
+
+    it 'does not set sessions' do
+      expect(session).to be_empty
+    end
+
+    it 'returns public Cache-Control header' do
+      expect(response.headers['Cache-Control']).to include 'public'
+    end
+  end
+
   before do
     allow(controller).to receive(:signed_request_account).and_return(remote_account)
 
@@ -31,9 +46,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
           expect(response.content_type).to eq 'application/activity+json'
         end
 
-        it 'returns public Cache-Control header' do
-          expect(response.headers['Cache-Control']).to include 'public'
-        end
+        it_behaves_like 'cachable response'
 
         it 'returns orderedItems with pinned statuses' do
           json = body_as_json
@@ -58,9 +71,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
             expect(response.content_type).to eq 'application/activity+json'
           end
 
-          it 'returns public Cache-Control header' do
-            expect(response.headers['Cache-Control']).to include 'public'
-          end
+          it_behaves_like 'cachable response'
 
           it 'returns orderedItems with pinned statuses' do
             json = body_as_json
diff --git a/spec/controllers/activitypub/outboxes_controller_spec.rb b/spec/controllers/activitypub/outboxes_controller_spec.rb
index 03490533d..1baf5a623 100644
--- a/spec/controllers/activitypub/outboxes_controller_spec.rb
+++ b/spec/controllers/activitypub/outboxes_controller_spec.rb
@@ -3,6 +3,21 @@ require 'rails_helper'
 RSpec.describe ActivityPub::OutboxesController, type: :controller do
   let!(:account) { Fabricate(:account) }
 
+  shared_examples 'cachable response' do
+    it 'does not set cookies' do
+      expect(response.cookies).to be_empty
+      expect(response.headers['Set-Cookies']).to be nil
+    end
+
+    it 'does not set sessions' do
+      expect(session).to be_empty
+    end
+
+    it 'returns public Cache-Control header' do
+      expect(response.headers['Cache-Control']).to include 'public'
+    end
+  end
+
   before do
     Fabricate(:status, account: account, visibility: :public)
     Fabricate(:status, account: account, visibility: :unlisted)
@@ -39,9 +54,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
           expect(json[:totalItems]).to eq 4
         end
 
-        it 'returns public Cache-Control header' do
-          expect(response.headers['Cache-Control']).to include 'public'
-        end
+        it_behaves_like 'cachable response'
       end
 
       context 'with page requested' do
@@ -62,9 +75,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
           expect(json[:orderedItems].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
         end
 
-        it 'returns public Cache-Control header' do
-          expect(response.headers['Cache-Control']).to include 'public'
-        end
+        it_behaves_like 'cachable response'
       end
     end
 
diff --git a/spec/controllers/activitypub/replies_controller_spec.rb b/spec/controllers/activitypub/replies_controller_spec.rb
index d956e1b35..ed383864d 100644
--- a/spec/controllers/activitypub/replies_controller_spec.rb
+++ b/spec/controllers/activitypub/replies_controller_spec.rb
@@ -7,6 +7,21 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
   let(:remote_reply_id) { nil }
   let(:remote_account) { nil }
 
+  shared_examples 'cachable response' do
+    it 'does not set cookies' do
+      expect(response.cookies).to be_empty
+      expect(response.headers['Set-Cookies']).to be nil
+    end
+
+    it 'does not set sessions' do
+      expect(session).to be_empty
+    end
+
+    it 'returns public Cache-Control header' do
+      expect(response.headers['Cache-Control']).to include 'public'
+    end
+  end
+
   before do
     allow(controller).to receive(:signed_request_account).and_return(remote_account)
 
@@ -36,9 +51,7 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
           expect(response.content_type).to eq 'application/activity+json'
         end
 
-        it 'returns public Cache-Control header' do
-          expect(response.headers['Cache-Control']).to include 'public'
-        end
+        it_behaves_like 'cachable response'
 
         it 'returns items with account\'s own replies' do
           json = body_as_json
@@ -87,9 +100,7 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
             expect(response.content_type).to eq 'application/activity+json'
           end
 
-          it 'returns public Cache-Control header' do
-            expect(response.headers['Cache-Control']).to include 'public'
-          end
+          it_behaves_like 'cachable response'
 
           context 'without only_other_accounts' do
             it 'returns items with account\'s own replies' do
diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb
index ba1f1370a..cd6e1e607 100644
--- a/spec/controllers/statuses_controller_spec.rb
+++ b/spec/controllers/statuses_controller_spec.rb
@@ -5,6 +5,21 @@ require 'rails_helper'
 describe StatusesController do
   render_views
 
+  shared_examples 'cachable response' do
+    it 'does not set cookies' do
+      expect(response.cookies).to be_empty
+      expect(response.headers['Set-Cookies']).to be nil
+    end
+
+    it 'does not set sessions' do
+      expect(session).to be_empty
+    end
+
+    it 'returns public Cache-Control header' do
+      expect(response.headers['Cache-Control']).to include 'public'
+    end
+  end
+
   describe 'GET #show' do
     let(:account) { Fabricate(:account) }
     let(:status)  { Fabricate(:status, account: account) }
@@ -80,9 +95,7 @@ describe StatusesController do
           expect(response.headers['Vary']).to eq 'Accept'
         end
 
-        it 'returns public Cache-Control header' do
-          expect(response.headers['Cache-Control']).to include 'public'
-        end
+        it_behaves_like 'cachable response'
 
         it 'returns Content-Type header' do
           expect(response.headers['Content-Type']).to include 'application/activity+json'
@@ -470,9 +483,7 @@ describe StatusesController do
             expect(response.headers['Vary']).to eq 'Accept'
           end
 
-          it 'returns public Cache-Control header' do
-            expect(response.headers['Cache-Control']).to include 'public'
-          end
+          it_behaves_like 'cachable response'
 
           it 'returns Content-Type header' do
             expect(response.headers['Content-Type']).to include 'application/activity+json'
diff --git a/spec/lib/activitypub/activity/block_spec.rb b/spec/lib/activitypub/activity/block_spec.rb
index 94d37356d..42bdfdc81 100644
--- a/spec/lib/activitypub/activity/block_spec.rb
+++ b/spec/lib/activitypub/activity/block_spec.rb
@@ -28,6 +28,28 @@ RSpec.describe ActivityPub::Activity::Block do
     end
   end
 
+  context 'when the recipient is already blocked' do
+    before do
+      sender.block!(recipient, uri: 'old')
+    end
+
+    describe '#perform' do
+      subject { described_class.new(json, sender) }
+
+      before do
+        subject.perform
+      end
+
+      it 'creates a block from sender to recipient' do
+        expect(sender.blocking?(recipient)).to be true
+      end
+
+      it 'sets the uri to that of last received block activity' do
+        expect(sender.block_relationships.find_by(target_account: recipient).uri).to eq 'foo'
+      end
+    end
+  end
+
   context 'when the recipient follows the sender' do
     before do
       recipient.follow!(sender)
diff --git a/spec/lib/activitypub/activity/undo_spec.rb b/spec/lib/activitypub/activity/undo_spec.rb
index 9545e1f46..c0309e49d 100644
--- a/spec/lib/activitypub/activity/undo_spec.rb
+++ b/spec/lib/activitypub/activity/undo_spec.rb
@@ -50,6 +50,19 @@ RSpec.describe ActivityPub::Activity::Undo do
           expect(sender.reblogged?(status)).to be false
         end
       end
+
+      context 'with only object uri' do
+        let(:object_json) { 'bar' }
+
+        before do
+          Fabricate(:status, reblog: status, account: sender, uri: 'bar')
+        end
+
+        it 'deletes the reblog by uri' do
+          subject.perform
+          expect(sender.reblogged?(status)).to be false
+        end
+      end
     end
 
     context 'with Accept' do
@@ -91,13 +104,22 @@ RSpec.describe ActivityPub::Activity::Undo do
       end
 
       before do
-        sender.block!(recipient)
+        sender.block!(recipient, uri: 'bar')
       end
 
       it 'deletes block from sender to recipient' do
         subject.perform
         expect(sender.blocking?(recipient)).to be false
       end
+
+      context 'with only object uri' do
+        let(:object_json) { 'bar' }
+
+        it 'deletes block from sender to recipient' do
+          subject.perform
+          expect(sender.blocking?(recipient)).to be false
+        end
+      end
     end
 
     context 'with Follow' do
@@ -113,13 +135,22 @@ RSpec.describe ActivityPub::Activity::Undo do
       end
 
       before do
-        sender.follow!(recipient)
+        sender.follow!(recipient, uri: 'bar')
       end
 
       it 'deletes follow from sender to recipient' do
         subject.perform
         expect(sender.following?(recipient)).to be false
       end
+
+      context 'with only object uri' do
+        let(:object_json) { 'bar' }
+
+        it 'deletes follow from sender to recipient' do
+          subject.perform
+          expect(sender.following?(recipient)).to be false
+        end
+      end
     end
 
     context 'with Like' do
diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb
index 705e577a6..40e8214b6 100644
--- a/spec/lib/feed_manager_spec.rb
+++ b/spec/lib/feed_manager_spec.rb
@@ -531,4 +531,29 @@ RSpec.describe FeedManager do
       expect(Redis.current).to have_received(:publish).with("timeline:#{receiver.id}", deletion)
     end
   end
+
+  describe '#clear_from_timeline' do
+    let(:account)          { Fabricate(:account) }
+    let(:followed_account) { Fabricate(:account) }
+    let(:target_account)   { Fabricate(:account) }
+    let(:status_1)         { Fabricate(:status, account: followed_account) }
+    let(:status_2)         { Fabricate(:status, account: target_account) }
+    let(:status_3)         { Fabricate(:status, account: followed_account, mentions: [Fabricate(:mention, account: target_account)]) }
+    let(:status_4)         { Fabricate(:status, mentions: [Fabricate(:mention, account: target_account)]) }
+    let(:status_5)         { Fabricate(:status, account: followed_account, reblog: status_4) }
+    let(:status_6)         { Fabricate(:status, account: followed_account, reblog: status_2) }
+    let(:status_7)         { Fabricate(:status, account: followed_account) }
+
+    before do
+      [status_1, status_3, status_5, status_6, status_7].each do |status|
+        Redis.current.zadd("feed:home:#{account.id}", status.id, status.id)
+      end
+    end
+
+    it 'correctly cleans the timeline' do
+      FeedManager.instance.clear_from_timeline(account, target_account)
+
+      expect(Redis.current.zrange("feed:home:#{account.id}", 0, -1)).to eq [status_1.id.to_s, status_7.id.to_s]
+    end
+  end
 end
diff --git a/yarn.lock b/yarn.lock
index 89f53f62c..2c51161d0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6979,10 +6979,10 @@ 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.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.3.0, lodash@~4.17.12:
-  version "4.17.15"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
-  integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.3.0, lodash@~4.17.12:
+  version "4.17.19"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
+  integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
 
 loglevel@^1.6.8:
   version "1.6.8"