about summary refs log tree commit diff
diff options
context:
space:
mode:
authorKaito Sinclaire <MattWCSTRFAN@gmail.com>2018-04-18 04:09:06 -0700
committerEugen Rochko <eugen@zeonfederated.com>2018-04-18 13:09:06 +0200
commit156b916caf4ec902a8db525843e95c1a42350207 (patch)
tree3f80c7da4842fc87bb56e62f5258072693fe415f
parentaedfea3554dc5cd60f4bebd828109d1ef4327814 (diff)
Direct messages column (#4514)
* Added a timeline for Direct statuses
* Lists all Direct statuses you've sent and received
* Displayed in Getting Started
* Streaming server support for direct TL

* Changes to match other timelines in 2.0
-rw-r--r--app/controllers/api/v1/timelines/direct_controller.rb60
-rw-r--r--app/javascript/mastodon/actions/compose.js2
-rw-r--r--app/javascript/mastodon/actions/streaming.js1
-rw-r--r--app/javascript/mastodon/actions/timelines.js1
-rw-r--r--app/javascript/mastodon/features/direct_timeline/containers/column_settings_container.js17
-rw-r--r--app/javascript/mastodon/features/direct_timeline/index.js104
-rw-r--r--app/javascript/mastodon/features/getting_started/index.js15
-rw-r--r--app/javascript/mastodon/features/ui/components/columns_area.js3
-rw-r--r--app/javascript/mastodon/features/ui/index.js8
-rw-r--r--app/javascript/mastodon/features/ui/util/async-components.js4
-rw-r--r--app/javascript/mastodon/locales/ar.json5
-rw-r--r--app/javascript/mastodon/locales/bg.json5
-rw-r--r--app/javascript/mastodon/locales/ca.json5
-rw-r--r--app/javascript/mastodon/locales/de.json5
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json33
-rw-r--r--app/javascript/mastodon/locales/en.json5
-rw-r--r--app/javascript/mastodon/locales/eo.json5
-rw-r--r--app/javascript/mastodon/locales/es.json5
-rw-r--r--app/javascript/mastodon/locales/fa.json5
-rw-r--r--app/javascript/mastodon/locales/fi.json5
-rw-r--r--app/javascript/mastodon/locales/fr.json5
-rw-r--r--app/javascript/mastodon/locales/gl.json5
-rw-r--r--app/javascript/mastodon/locales/he.json5
-rw-r--r--app/javascript/mastodon/locales/hr.json5
-rw-r--r--app/javascript/mastodon/locales/hu.json5
-rw-r--r--app/javascript/mastodon/locales/hy.json5
-rw-r--r--app/javascript/mastodon/locales/id.json5
-rw-r--r--app/javascript/mastodon/locales/io.json5
-rw-r--r--app/javascript/mastodon/locales/it.json5
-rw-r--r--app/javascript/mastodon/locales/ja.json5
-rw-r--r--app/javascript/mastodon/locales/ko.json5
-rw-r--r--app/javascript/mastodon/locales/nl.json5
-rw-r--r--app/javascript/mastodon/locales/no.json5
-rw-r--r--app/javascript/mastodon/locales/oc.json5
-rw-r--r--app/javascript/mastodon/locales/pl.json5
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json5
-rw-r--r--app/javascript/mastodon/locales/pt.json5
-rw-r--r--app/javascript/mastodon/locales/ru.json5
-rw-r--r--app/javascript/mastodon/locales/sk.json5
-rw-r--r--app/javascript/mastodon/locales/sr-Latn.json5
-rw-r--r--app/javascript/mastodon/locales/sr.json5
-rw-r--r--app/javascript/mastodon/locales/sv.json5
-rw-r--r--app/javascript/mastodon/locales/th.json5
-rw-r--r--app/javascript/mastodon/locales/tr.json5
-rw-r--r--app/javascript/mastodon/locales/uk.json5
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json5
-rw-r--r--app/javascript/mastodon/locales/zh-HK.json5
-rw-r--r--app/javascript/mastodon/locales/zh-TW.json5
-rw-r--r--app/javascript/mastodon/reducers/contexts.js5
-rw-r--r--app/javascript/mastodon/reducers/settings.js6
-rw-r--r--app/models/status.rb8
-rw-r--r--app/services/batched_remove_status_service.rb11
-rw-r--r--app/services/fan_out_on_write_service.rb13
-rw-r--r--app/services/remove_status_service.rb8
-rw-r--r--config/routes.rb1
-rw-r--r--spec/models/status_spec.rb49
-rw-r--r--streaming/index.js7
-rw-r--r--yarn.lock12
58 files changed, 538 insertions, 15 deletions
diff --git a/app/controllers/api/v1/timelines/direct_controller.rb b/app/controllers/api/v1/timelines/direct_controller.rb
new file mode 100644
index 000000000..d455227eb
--- /dev/null
+++ b/app/controllers/api/v1/timelines/direct_controller.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+class Api::V1::Timelines::DirectController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read }, 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.paginate_by_max_id(
+      limit_param(DEFAULT_STATUSES_LIMIT),
+      params[:max_id],
+      params[:since_id]
+    )
+  end
+
+  def direct_timeline_statuses
+    Status.as_direct_timeline(current_account)
+  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/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index ea9d9f359..eee9c6928 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -145,6 +145,8 @@ export function submitCompose() {
       if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
         insertIfOnline('community');
         insertIfOnline('public');
+      } else if (response.data.visibility === 'direct') {
+        insertIfOnline('direct');
       }
     }).catch(function (error) {
       dispatch(submitComposeFail(error));
diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js
index f76510cdb..14215ab6d 100644
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@ -46,4 +46,5 @@ export const connectCommunityStream = () => connectTimelineStream('community', '
 export const connectMediaStream = () => connectTimelineStream('community', 'public:local');
 export const connectPublicStream = () => connectTimelineStream('public', 'public');
 export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`);
+export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
 export const connectListStream = (id) => connectTimelineStream(`list:${id}`, `list&list=${id}`);
diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js
index 5be07126d..eca847ee7 100644
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@ -87,6 +87,7 @@ export function expandTimeline(timelineId, path, params = {}) {
 export const expandHomeTimeline         = ({ maxId } = {}) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId });
 export const expandPublicTimeline       = ({ maxId } = {}) => expandTimeline('public', '/api/v1/timelines/public', { max_id: maxId });
 export const expandCommunityTimeline    = ({ maxId } = {}) => expandTimeline('community', '/api/v1/timelines/public', { local: true, max_id: maxId });
+export const expandDirectTimeline       = ({ maxId } = {}) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId });
 export const expandAccountTimeline      = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
 export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
 export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true });
diff --git a/app/javascript/mastodon/features/direct_timeline/containers/column_settings_container.js b/app/javascript/mastodon/features/direct_timeline/containers/column_settings_container.js
new file mode 100644
index 000000000..1833f69e5
--- /dev/null
+++ b/app/javascript/mastodon/features/direct_timeline/containers/column_settings_container.js
@@ -0,0 +1,17 @@
+import { connect } from 'react-redux';
+import ColumnSettings from '../../community_timeline/components/column_settings';
+import { changeSetting } from '../../../actions/settings';
+
+const mapStateToProps = state => ({
+  settings: state.getIn(['settings', 'direct']),
+});
+
+const mapDispatchToProps = dispatch => ({
+
+  onChange (key, checked) {
+    dispatch(changeSetting(['direct', ...key], checked));
+  },
+
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.js
new file mode 100644
index 000000000..fda57f69a
--- /dev/null
+++ b/app/javascript/mastodon/features/direct_timeline/index.js
@@ -0,0 +1,104 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import StatusListContainer from '../ui/containers/status_list_container';
+import Column from '../../components/column';
+import ColumnHeader from '../../components/column_header';
+import { expandDirectTimeline } from '../../actions/timelines';
+import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import ColumnSettingsContainer from './containers/column_settings_container';
+import { connectDirectStream } from '../../actions/streaming';
+
+const messages = defineMessages({
+  title: { id: 'column.direct', defaultMessage: 'Direct messages' },
+});
+
+const mapStateToProps = state => ({
+  hasUnread: state.getIn(['timelines', 'direct', 'unread']) > 0,
+});
+
+@connect(mapStateToProps)
+@injectIntl
+export default class DirectTimeline extends React.PureComponent {
+
+  static propTypes = {
+    dispatch: PropTypes.func.isRequired,
+    columnId: PropTypes.string,
+    intl: PropTypes.object.isRequired,
+    hasUnread: PropTypes.bool,
+    multiColumn: PropTypes.bool,
+  };
+
+  handlePin = () => {
+    const { columnId, dispatch } = this.props;
+
+    if (columnId) {
+      dispatch(removeColumn(columnId));
+    } else {
+      dispatch(addColumn('DIRECT', {}));
+    }
+  }
+
+  handleMove = (dir) => {
+    const { columnId, dispatch } = this.props;
+    dispatch(moveColumn(columnId, dir));
+  }
+
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  }
+
+  componentDidMount () {
+    const { dispatch } = this.props;
+
+    dispatch(expandDirectTimeline());
+    this.disconnect = dispatch(connectDirectStream());
+  }
+
+  componentWillUnmount () {
+    if (this.disconnect) {
+      this.disconnect();
+      this.disconnect = null;
+    }
+  }
+
+  setRef = c => {
+    this.column = c;
+  }
+
+  handleLoadMore = maxId => {
+    this.props.dispatch(expandDirectTimeline({ maxId }));
+  }
+
+  render () {
+    const { intl, hasUnread, columnId, multiColumn } = this.props;
+    const pinned = !!columnId;
+
+    return (
+      <Column ref={this.setRef}>
+        <ColumnHeader
+          icon='envelope'
+          active={hasUnread}
+          title={intl.formatMessage(messages.title)}
+          onPin={this.handlePin}
+          onMove={this.handleMove}
+          onClick={this.handleHeaderClick}
+          pinned={pinned}
+          multiColumn={multiColumn}
+        >
+          <ColumnSettingsContainer />
+        </ColumnHeader>
+
+        <StatusListContainer
+          trackScroll={!pinned}
+          scrollKey={`direct_timeline-${columnId}`}
+          timelineId='direct'
+          onLoadMore={this.handleLoadMore}
+          emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
+        />
+      </Column>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
index 053a1ca17..4a249f301 100644
--- a/app/javascript/mastodon/features/getting_started/index.js
+++ b/app/javascript/mastodon/features/getting_started/index.js
@@ -19,6 +19,7 @@ const messages = defineMessages({
   navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation' },
   settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' },
   community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
+  direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' },
   preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
   follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
   sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
@@ -98,20 +99,24 @@ export default class GettingStarted extends ImmutablePureComponent {
       }
     }
 
+    if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) {
+      navItems.push(<ColumnLink key='4' icon='envelope' text={intl.formatMessage(messages.direct)} to='/timelines/direct' />);
+    }
+
     navItems.push(
-      <ColumnLink key='4' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
-      <ColumnLink key='5' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
+      <ColumnLink key='5' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
+      <ColumnLink key='6' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
     );
 
     if (myAccount.get('locked')) {
-      navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
+      navItems.push(<ColumnLink key='7' icon='users' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
     }
 
     if (multiColumn) {
-      navItems.push(<ColumnLink key='7' icon='question' text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' />);
+      navItems.push(<ColumnLink key='8' icon='question' text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' />);
     }
 
-    navItems.push(<ColumnLink key='8' icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />);
+    navItems.push(<ColumnLink key='9' icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />);
 
     return (
       <Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile>
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js
index 05cdb4e3b..0a62cbbeb 100644
--- a/app/javascript/mastodon/features/ui/components/columns_area.js
+++ b/app/javascript/mastodon/features/ui/components/columns_area.js
@@ -12,7 +12,7 @@ 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, FavouritedStatuses, ListTimeline } from '../../ui/util/async-components';
+import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, ListTimeline } from '../../ui/util/async-components';
 
 import detectPassiveEvents from 'detect-passive-events';
 import { scrollRight } from '../../../scroll';
@@ -24,6 +24,7 @@ const componentMap = {
   'PUBLIC': PublicTimeline,
   'COMMUNITY': CommunityTimeline,
   'HASHTAG': HashtagTimeline,
+  'DIRECT': DirectTimeline,
   'FAVOURITES': FavouritedStatuses,
   'LIST': ListTimeline,
 };
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index d2ef19e47..adca0d617 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -30,6 +30,7 @@ import {
   Following,
   Reblogs,
   Favourites,
+  DirectTimeline,
   HashtagTimeline,
   Notifications,
   FollowRequests,
@@ -79,6 +80,7 @@ const keyMap = {
   goToNotifications: 'g n',
   goToLocal: 'g l',
   goToFederated: 'g t',
+  goToDirect: 'g d',
   goToStart: 'g s',
   goToFavourites: 'g f',
   goToPinned: 'g p',
@@ -140,6 +142,7 @@ class SwitchingColumnsArea extends React.PureComponent {
           <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
           <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
           <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
+          <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} />
           <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
           <WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
 
@@ -386,6 +389,10 @@ export default class UI extends React.PureComponent {
     this.context.router.history.push('/timelines/public');
   }
 
+  handleHotkeyGoToDirect = () => {
+    this.context.router.history.push('/timelines/direct');
+  }
+
   handleHotkeyGoToStart = () => {
     this.context.router.history.push('/getting-started');
   }
@@ -425,6 +432,7 @@ export default class UI extends React.PureComponent {
       goToNotifications: this.handleHotkeyGoToNotifications,
       goToLocal: this.handleHotkeyGoToLocal,
       goToFederated: this.handleHotkeyGoToFederated,
+      goToDirect: this.handleHotkeyGoToDirect,
       goToStart: this.handleHotkeyGoToStart,
       goToFavourites: this.handleHotkeyGoToFavourites,
       goToPinned: this.handleHotkeyGoToPinned,
diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js
index 19957208f..8cf2a6e7d 100644
--- a/app/javascript/mastodon/features/ui/util/async-components.js
+++ b/app/javascript/mastodon/features/ui/util/async-components.js
@@ -26,6 +26,10 @@ export function HashtagTimeline () {
   return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline');
 }
 
+export function DirectTimeline() {
+  return import(/* webpackChunkName: "features/direct_timeline" */'../../direct_timeline');
+}
+
 export function ListTimeline () {
   return import(/* webpackChunkName: "features/list_timeline" */'../../list_timeline');
 }
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index c13ff773d..24c8a5b54 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "إعادة المحاولة",
   "column.blocks": "الحسابات المحجوبة",
   "column.community": "الخيط العام المحلي",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "المفضلة",
   "column.follow_requests": "طلبات المتابعة",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "رموز",
   "emoji_button.travel": "أماكن و أسفار",
   "empty_column.community": "الخط الزمني المحلي فارغ. أكتب شيئا ما للعامة كبداية !",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.",
   "empty_column.home": "إنك لا تتبع بعد أي شخص إلى حد الآن. زر {public} أو استخدام حقل البحث لكي تبدأ على التعرف على مستخدمين آخرين.",
   "empty_column.home.public_timeline": "الخيط العام",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "هل تود إخفاء الإخطارات القادمة من هذا المستخدم ؟",
   "navigation_bar.blocks": "الحسابات المحجوبة",
   "navigation_bar.community_timeline": "الخيط العام المحلي",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "تعديل الملف الشخصي",
   "navigation_bar.favourites": "المفضلة",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {result} و {results}}",
   "standalone.public_title": "نظرة على ...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "تعذرت ترقية هذا المنشور",
   "status.delete": "إحذف",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "تدبيس على الملف الشخصي",
   "status.pinned": "تبويق مثبَّت",
   "status.reblog": "رَقِّي",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} رقى",
   "status.reply": "ردّ",
   "status.replyAll": "رُد على الخيط",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index 981aced0b..25ef6db65 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Blocked users",
   "column.community": "Local timeline",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Symbols",
   "emoji_button.travel": "Travel & Places",
   "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.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",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Редактирай профил",
   "navigation_bar.favourites": "Favourites",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.delete": "Изтриване",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
   "status.reblog": "Споделяне",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} сподели",
   "status.reply": "Отговор",
   "status.replyAll": "Reply to thread",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index d9270e0ce..6a44808e0 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Torna-ho a provar",
   "column.blocks": "Usuaris blocats",
   "column.community": "Línia de temps local",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favorits",
   "column.follow_requests": "Peticions per seguir-te",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Símbols",
   "emoji_button.travel": "Viatges i Llocs",
   "empty_column.community": "La línia de temps local és buida. Escriu alguna cosa públicament per fer rodar la pilota!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Encara no hi ha res amb aquesta etiqueta.",
   "empty_column.home": "Encara no segueixes ningú. Visita {public} o fes cerca per començar i conèixer altres usuaris.",
   "empty_column.home.public_timeline": "la línia de temps pública",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Amagar notificacions d'aquest usuari?",
   "navigation_bar.blocks": "Usuaris bloquejats",
   "navigation_bar.community_timeline": "Línia de temps Local",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favorits",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, un {result} altres {results}}",
   "standalone.public_title": "Una mirada a l'interior ...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Aquesta publicació no pot ser retootejada",
   "status.delete": "Esborrar",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Fixat en el perfil",
   "status.pinned": "Toot fixat",
   "status.reblog": "Impuls",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} ha retootejat",
   "status.reply": "Respondre",
   "status.replyAll": "Respondre al tema",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index 6eb5e6840..69c2ae8d8 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Erneut versuchen",
   "column.blocks": "Blockierte Profile",
   "column.community": "Lokale Zeitleiste",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoriten",
   "column.follow_requests": "Folgeanfragen",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Symbole",
   "emoji_button.travel": "Reisen und Orte",
   "empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe einen öffentlichen Beitrag, um den Ball ins Rollen zu bringen!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Unter diesem Hashtag gibt es noch nichts.",
   "empty_column.home": "Deine Startseite ist leer! Besuche {public} oder nutze die Suche, um loszulegen und andere Leute zu finden.",
   "empty_column.home.public_timeline": "die öffentliche Zeitleiste",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Benachrichtigungen von diesem Account verbergen?",
   "navigation_bar.blocks": "Blockierte Profile",
   "navigation_bar.community_timeline": "Lokale Zeitleiste",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Profil bearbeiten",
   "navigation_bar.favourites": "Favoriten",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}",
   "standalone.public_title": "Ein kleiner Einblick …",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden",
   "status.delete": "Löschen",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Im Profil anheften",
   "status.pinned": "Pinned toot",
   "status.reblog": "Teilen",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} teilte",
   "status.reply": "Antworten",
   "status.replyAll": "Auf Thread antworten",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index a3c4f775c..5a02e0c72 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -243,6 +243,14 @@
         "id": "status.reblog"
       },
       {
+        "defaultMessage": "Boost to original audience",
+        "id": "status.reblog_private"
+      },
+      {
+        "defaultMessage": "Unboost",
+        "id": "status.cancel_reblog_private"
+      },
+      {
         "defaultMessage": "This post cannot be boosted",
         "id": "status.cannot_reblog"
       },
@@ -900,6 +908,19 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Direct messages",
+        "id": "column.direct"
+      },
+      {
+        "defaultMessage": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+        "id": "empty_column.direct"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/direct_timeline/index.json"
+  },
+  {
+    "descriptors": [
+      {
         "defaultMessage": "Hidden domains",
         "id": "column.domain_blocks"
       },
@@ -972,6 +993,10 @@
         "id": "navigation_bar.community_timeline"
       },
       {
+        "defaultMessage": "Direct messages",
+        "id": "navigation_bar.direct"
+      },
+      {
         "defaultMessage": "Preferences",
         "id": "navigation_bar.preferences"
       },
@@ -1400,6 +1425,14 @@
         "id": "status.reblog"
       },
       {
+        "defaultMessage": "Boost to original audience",
+        "id": "status.reblog_private"
+      },
+      {
+        "defaultMessage": "Unboost",
+        "id": "status.cancel_reblog_private"
+      },
+      {
         "defaultMessage": "This post cannot be boosted",
         "id": "status.cannot_reblog"
       },
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index a389735c1..d8bd7e3e0 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Blocked users",
   "column.community": "Local timeline",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Symbols",
   "emoji_button.travel": "Travel & Places",
   "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.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",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Edit profile",
   "navigation_bar.favourites": "Favourites",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.delete": "Delete",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
   "status.reblog": "Boost",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} boosted",
   "status.reply": "Reply",
   "status.replyAll": "Reply to thread",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index 19f3c5907..e51163971 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Bonvolu reprovi",
   "column.blocks": "Blokitaj uzantoj",
   "column.community": "Loka tempolinio",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Stelumoj",
   "column.follow_requests": "Petoj de sekvado",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Simboloj",
   "emoji_button.travel": "Vojaĝoj kaj lokoj",
   "empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
   "empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.",
   "empty_column.home.public_timeline": "la publikan tempolinion",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Ĉu vi volas kaŝi la sciigojn el ĉi tiu uzanto?",
   "navigation_bar.blocks": "Blokitaj uzantoj",
   "navigation_bar.community_timeline": "Loka tempolinio",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Redakti profilon",
   "navigation_bar.favourites": "Stelumoj",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezultoj}}",
   "standalone.public_title": "Enrigardo…",
   "status.block": "Bloki @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Ĉi tiu mesaĝo ne diskonigeblas",
   "status.delete": "Forigi",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Alpingli profile",
   "status.pinned": "Alpinglita mesaĝo",
   "status.reblog": "Diskonigi",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} diskonigis",
   "status.reply": "Respondi",
   "status.replyAll": "Respondi al la fadeno",
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index e765cc078..61ea0588d 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Inténtalo de nuevo",
   "column.blocks": "Usuarios bloqueados",
   "column.community": "Línea de tiempo local",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoritos",
   "column.follow_requests": "Solicitudes de seguimiento",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viajes y lugares",
   "empty_column.community": "La línea de tiempo local está vacía. ¡Escribe algo para empezar la fiesta!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "No hay nada en este hashtag aún.",
   "empty_column.home": "No estás siguiendo a nadie aún. Visita {public} o haz búsquedas para empezar y conocer gente nueva.",
   "empty_column.home.public_timeline": "la línea de tiempo pública",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Ocultar notificaciones de este usuario?",
   "navigation_bar.blocks": "Usuarios bloqueados",
   "navigation_bar.community_timeline": "Historia local",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favoritos",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "standalone.public_title": "Un pequeño vistazo...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Este toot no puede retootearse",
   "status.delete": "Borrar",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Fijar",
   "status.pinned": "Toot fijado",
   "status.reblog": "Retootear",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "Retooteado por {name}",
   "status.reply": "Responder",
   "status.replyAll": "Responder al hilo",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index 822c998ce..cfe93007d 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "تلاش دوباره",
   "column.blocks": "کاربران مسدودشده",
   "column.community": "نوشته‌های محلی",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "پسندیده‌ها",
   "column.follow_requests": "درخواست‌های پیگیری",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "نمادها",
   "emoji_button.travel": "سفر و مکان",
   "empty_column.community": "فهرست نوشته‌های محلی خالی است. چیزی بنویسید تا چرخش بچرخد!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "هنوز هیچ چیزی با این هشتگ نیست.",
   "empty_column.home": "شما هنوز پیگیر کسی نیستید. {public} را ببینید یا چیزی را جستجو کنید تا کاربران دیگر را ببینید.",
   "empty_column.home.public_timeline": "فهرست نوشته‌های همه‌جا",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "اعلان‌های این کاربر پنهان شود؟",
   "navigation_bar.blocks": "کاربران مسدودشده",
   "navigation_bar.community_timeline": "نوشته‌های محلی",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "ویرایش نمایه",
   "navigation_bar.favourites": "پسندیده‌ها",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
   "standalone.public_title": "نگاهی به کاربران این سرور...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "این نوشته را نمی‌شود بازبوقید",
   "status.delete": "پاک‌کردن",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "نوشتهٔ ثابت نمایه",
   "status.pinned": "Pinned toot",
   "status.reblog": "بازبوقیدن",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "‫{name}‬ بازبوقید",
   "status.reply": "پاسخ",
   "status.replyAll": "به نوشته پاسخ دهید",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index 5763ac442..1677c3c6c 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Yritä uudestaan",
   "column.blocks": "Estetyt käyttäjät",
   "column.community": "Paikallinen aikajana",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Suosikit",
   "column.follow_requests": "Seuraamispyynnöt",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Symbolit",
   "emoji_button.travel": "Matkailu",
   "empty_column.community": "Paikallinen aikajana on tyhjä. Homma lähtee käyntiin, kun kirjoitat jotain julkista!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Tällä hashtagilla ei ole vielä mitään.",
   "empty_column.home": "Kotiaikajanasi on tyhjä! {public} ja hakutoiminto auttavat alkuun ja kohtaamaan muita käyttäjiä.",
   "empty_column.home.public_timeline": "yleinen aikajana",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Piilota tältä käyttäjältä tulevat ilmoitukset?",
   "navigation_bar.blocks": "Estetyt käyttäjät",
   "navigation_bar.community_timeline": "Paikallinen aikajana",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Muokkaa profiilia",
   "navigation_bar.favourites": "Suosikit",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "Kurkistus sisälle...",
   "status.block": "Estä @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Tätä julkaisua ei voi buustata",
   "status.delete": "Poista",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Kiinnitä profiiliin",
   "status.pinned": "Kiinnitetty tuuttaus",
   "status.reblog": "Buustaa",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} buustasi",
   "status.reply": "Vastaa",
   "status.replyAll": "Vastaa ketjuun",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index ef1c115e3..98c1c43d2 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Réessayer",
   "column.blocks": "Comptes bloqués",
   "column.community": "Fil public local",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoris",
   "column.follow_requests": "Demandes de suivi",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Symboles",
   "emoji_button.travel": "Lieux & Voyages",
   "empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir !",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag.",
   "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres personnes.",
   "empty_column.home.public_timeline": "le fil public",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Masquer les notifications de cette personne ?",
   "navigation_bar.blocks": "Comptes bloqués",
   "navigation_bar.community_timeline": "Fil public local",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Modifier le profil",
   "navigation_bar.favourites": "Favoris",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
   "standalone.public_title": "Un aperçu …",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Cette publication ne peut être boostée",
   "status.delete": "Effacer",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Épingler sur le profil",
   "status.pinned": "Pouet épinglé",
   "status.reblog": "Partager",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} a partagé :",
   "status.reply": "Répondre",
   "status.replyAll": "Répondre au fil",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index 5cbb7d31d..fca42374d 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Inténteo de novo",
   "column.blocks": "Usuarias bloqueadas",
   "column.community": "Liña temporal local",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoritas",
   "column.follow_requests": "Peticións de seguimento",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viaxes e Lugares",
   "empty_column.community": "A liña temporal local está baldeira. Escriba algo de xeito público para que rule!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Aínda non hai nada con esta etiqueta.",
   "empty_column.home": "A súa liña temporal de inicio está baldeira! Visite {public} ou utilice a busca para atopar outras usuarias.",
   "empty_column.home.public_timeline": "a liña temporal pública",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Esconder notificacións deste usuario?",
   "navigation_bar.blocks": "Usuarias bloqueadas",
   "navigation_bar.community_timeline": "Liña temporal local",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favoritas",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count,plural,one {result} outros {results}}",
   "standalone.public_title": "Ollada dentro...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Esta mensaxe non pode ser promocionada",
   "status.delete": "Eliminar",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Fixar no perfil",
   "status.pinned": "Pinned toot",
   "status.reblog": "Promover",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} promoveu",
   "status.reply": "Resposta",
   "status.replyAll": "Resposta a conversa",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index 656d93cdf..e3e87f1d0 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "לנסות שוב",
   "column.blocks": "חסימות",
   "column.community": "ציר זמן מקומי",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "חיבובים",
   "column.follow_requests": "בקשות מעקב",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "סמלים",
   "emoji_button.travel": "טיולים ואתרים",
   "empty_column.community": "טור הסביבה ריק. יש לפרסם משהו כדי שדברים יתרחילו להתגלגל!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "אין כלום בהאשתג הזה עדיין.",
   "empty_column.home": "אף אחד לא במעקב עדיין. אפשר לבקר ב{public} או להשתמש בחיפוש כדי להתחיל ולהכיר חצוצרנים אחרים.",
   "empty_column.home.public_timeline": "ציר זמן בין-קהילתי",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "להסתיר הודעות מחשבון זה?",
   "navigation_bar.blocks": "חסימות",
   "navigation_bar.community_timeline": "ציר זמן מקומי",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "עריכת פרופיל",
   "navigation_bar.favourites": "חיבובים",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}",
   "standalone.public_title": "הצצה פנימה...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "לא ניתן להדהד הודעה זו",
   "status.delete": "מחיקה",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "לקבע באודות",
   "status.pinned": "Pinned toot",
   "status.reblog": "הדהוד",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "הודהד על ידי {name}",
   "status.reply": "תגובה",
   "status.replyAll": "תגובה לכולם",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index 2d7d0a5a4..b41c98394 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Blokirani korisnici",
   "column.community": "Lokalni timeline",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoriti",
   "column.follow_requests": "Zahtjevi za slijeđenje",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Simboli",
   "emoji_button.travel": "Putovanja & Mjesta",
   "empty_column.community": "Lokalni timeline je prazan. Napiši nešto javno kako bi pokrenuo stvari!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Još ne postoji ništa s ovim hashtagom.",
   "empty_column.home": "Još ne slijediš nikoga. Posjeti {public} ili koristi tražilicu kako bi počeo i upoznao druge korisnike.",
   "empty_column.home.public_timeline": "javni timeline",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blokirani korisnici",
   "navigation_bar.community_timeline": "Lokalni timeline",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Uredi profil",
   "navigation_bar.favourites": "Favoriti",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Ovaj post ne može biti boostan",
   "status.delete": "Obriši",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
   "status.reblog": "Podigni",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} je podigao",
   "status.reply": "Odgovori",
   "status.replyAll": "Odgovori na temu",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index 24f3a7816..956accc67 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Próbálja újra",
   "column.blocks": "Letiltott felhasználók",
   "column.community": "Helyi idővonal",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Kedvencek",
   "column.follow_requests": "Követési kérések",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Szimbólumok",
   "emoji_button.travel": "Utazás és Helyek",
   "empty_column.community": "A helyi idővonal üres. Írj egy publikus stástuszt, hogy elindítsd a labdát!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Jelenleg nem található semmi ezen hashtaggel.",
   "empty_column.home": "A hazai idővonala üres! Látogasd meg a {public} vagy használd a keresőt, hogy ismerj meg más felhasználókat.",
   "empty_column.home.public_timeline": "publikus idővonal",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Értesítések elrejtése ezen felhasználótól?",
   "navigation_bar.blocks": "Tiltott felhasználók",
   "navigation_bar.community_timeline": "Helyi idővonal",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Profil szerkesztése",
   "navigation_bar.favourites": "Kedvencek",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "Betekintés...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Ezen státusz nem rebloggolható",
   "status.delete": "Törlés",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Kitűzés a profilra",
   "status.pinned": "Pinned toot",
   "status.reblog": "Reblog",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} reblogolta",
   "status.reply": "Válasz",
   "status.replyAll": "Válaszolj a beszélgetésre",
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index 2ba52c5c0..33e079201 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Կրկին փորձել",
   "column.blocks": "Արգելափակված օգտատերեր",
   "column.community": "Տեղական հոսք",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Հավանածներ",
   "column.follow_requests": "Հետեւելու հայցեր",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Նշաններ",
   "emoji_button.travel": "Ուղեւորություն եւ տեղանքներ",
   "empty_column.community": "Տեղական հոսքը դատա՛րկ է։ Հրապարակային մի բան գրիր շարժիչը խոդ տալու համար։",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Այս պիտակով դեռ ոչինչ չկա։",
   "empty_column.home": "Քո հիմնական հոսքը դատա՛րկ է։ Այցելի՛ր {public}ը կամ օգտվիր որոնումից՝ այլ մարդկանց հանդիպելու համար։",
   "empty_column.home.public_timeline": "հրապարակային հոսք",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Թաքցնե՞լ ցանուցումներն այս օգտատիրոջից։",
   "navigation_bar.blocks": "Արգելափակված օգտատերեր",
   "navigation_bar.community_timeline": "Տեղական հոսք",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Խմբագրել անձնական էջը",
   "navigation_bar.favourites": "Հավանածներ",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "Այս պահին…",
   "status.block": "Արգելափակել @{name}֊ին",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Այս թութը չի կարող տարածվել",
   "status.delete": "Ջնջել",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Ամրացնել անձնական էջում",
   "status.pinned": "Pinned toot",
   "status.reblog": "Տարածել",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} տարածել է",
   "status.reply": "Պատասխանել",
   "status.replyAll": "Պատասխանել թելին",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index e1518c1aa..412ffd3a0 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Coba lagi",
   "column.blocks": "Pengguna diblokir",
   "column.community": "Linimasa Lokal",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favorit",
   "column.follow_requests": "Permintaan mengikuti",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Simbol",
   "emoji_button.travel": "Tempat Wisata",
   "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.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",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Pengguna diblokir",
   "navigation_bar.community_timeline": "Linimasa lokal",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Ubah profil",
   "navigation_bar.favourites": "Favorit",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {hasil} other {hasil}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.delete": "Hapus",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
   "status.reblog": "Boost",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "di-boost {name}",
   "status.reply": "Balas",
   "status.replyAll": "Balas ke semua",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index c79d4a634..9730bf934 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Blokusita uzeri",
   "column.community": "Lokala tempolineo",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favorati",
   "column.follow_requests": "Demandi di sequado",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Symbols",
   "emoji_button.travel": "Travel & Places",
   "empty_column.community": "La lokala tempolineo esas vakua. Skribez ulo publike por iniciar la agiveso!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Esas ankore nulo en ta gretovorto.",
   "empty_column.home": "Tu sequas ankore nulu. Vizitez {public} od uzez la serchilo por komencar e renkontrar altra uzeri.",
   "empty_column.home.public_timeline": "la publika tempolineo",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blokusita uzeri",
   "navigation_bar.community_timeline": "Lokala tempolineo",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Modifikar profilo",
   "navigation_bar.favourites": "Favorati",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezulti}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.delete": "Efacar",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
   "status.reblog": "Repetar",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} repetita",
   "status.reply": "Respondar",
   "status.replyAll": "Respondar a filo",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index 3c85a3e20..5146d7ca2 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Utenti bloccati",
   "column.community": "Timeline locale",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Apprezzati",
   "column.follow_requests": "Richieste di amicizia",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Symbols",
   "emoji_button.travel": "Travel & Places",
   "empty_column.community": "La timeline locale è vuota. Condividi qualcosa pubblicamente per dare inizio alla festa!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Non c'è ancora nessun post con questo hashtag.",
   "empty_column.home": "Non stai ancora seguendo nessuno. Visita {public} o usa la ricerca per incontrare nuove persone.",
   "empty_column.home.public_timeline": "la timeline pubblica",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Utenti bloccati",
   "navigation_bar.community_timeline": "Timeline locale",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Modifica profilo",
   "navigation_bar.favourites": "Apprezzati",
@@ -238,6 +241,7 @@
   "search_results.total": "{count} {count, plural, one {risultato} other {risultati}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.delete": "Elimina",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
   "status.reblog": "Condividi",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} ha condiviso",
   "status.reply": "Rispondi",
   "status.replyAll": "Reply to thread",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 2da9192ca..a4aa06a06 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "再試行",
   "column.blocks": "ブロックしたユーザー",
   "column.community": "ローカルタイムライン",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "非表示にしたドメイン",
   "column.favourites": "お気に入り",
   "column.follow_requests": "フォローリクエスト",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "記号",
   "emoji_button.travel": "旅行と場所",
   "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "このハッシュタグはまだ使われていません。",
   "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
   "empty_column.home.public_timeline": "連合タイムライン",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "このユーザーからの通知を隠しますか?",
   "navigation_bar.blocks": "ブロックしたユーザー",
   "navigation_bar.community_timeline": "ローカルタイムライン",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "非表示にしたドメイン",
   "navigation_bar.edit_profile": "プロフィールを編集",
   "navigation_bar.favourites": "お気に入り",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number}件の結果",
   "standalone.public_title": "今こんな話をしています...",
   "status.block": "@{name}さんをブロック",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "この投稿はブーストできません",
   "status.delete": "削除",
   "status.direct": "@{name}さんにダイレクトメッセージ",
@@ -253,6 +257,7 @@
   "status.pin": "プロフィールに固定表示",
   "status.pinned": "固定されたトゥート",
   "status.reblog": "ブースト",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name}さんがブースト",
   "status.reply": "返信",
   "status.replyAll": "全員に返信",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index e2fadff3c..92367dc95 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "다시 시도",
   "column.blocks": "차단 중인 사용자",
   "column.community": "로컬 타임라인",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "즐겨찾기",
   "column.follow_requests": "팔로우 요청",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "기호",
   "emoji_button.travel": "여행과 장소",
   "empty_column.community": "로컬 타임라인에 아무 것도 없습니다. 아무거나 적어 보세요!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.",
   "empty_column.home": "아직 아무도 팔로우 하고 있지 않습니다. {public}를 보러 가거나, 검색하여 다른 사용자를 찾아 보세요.",
   "empty_column.home.public_timeline": "연합 타임라인",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "이 사용자로부터의 알림을 뮤트하시겠습니까?",
   "navigation_bar.blocks": "차단한 사용자",
   "navigation_bar.community_timeline": "로컬 타임라인",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "프로필 편집",
   "navigation_bar.favourites": "즐겨찾기",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number}건의 결과",
   "standalone.public_title": "지금 이런 이야기를 하고 있습니다…",
   "status.block": "@{name} 차단",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "이 포스트는 부스트 할 수 없습니다",
   "status.delete": "삭제",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "고정",
   "status.pinned": "고정 된 툿",
   "status.reblog": "부스트",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name}님이 부스트 했습니다",
   "status.reply": "답장",
   "status.replyAll": "전원에게 답장",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index 0222432a0..c18ddbd01 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Opnieuw proberen",
   "column.blocks": "Geblokkeerde gebruikers",
   "column.community": "Lokale tijdlijn",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favorieten",
   "column.follow_requests": "Volgverzoeken",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Symbolen",
   "emoji_button.travel": "Reizen en plekken",
   "empty_column.community": "De lokale tijdlijn is nog leeg. Toot iets in het openbaar om de bal aan het rollen te krijgen!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.",
   "empty_column.home": "Jij volgt nog niemand. Bezoek {public} of gebruik het zoekvenster om andere mensen te ontmoeten.",
   "empty_column.home.public_timeline": "de globale tijdlijn",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Verberg meldingen van deze persoon?",
   "navigation_bar.blocks": "Geblokkeerde gebruikers",
   "navigation_bar.community_timeline": "Lokale tijdlijn",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Profiel bewerken",
   "navigation_bar.favourites": "Favorieten",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
   "standalone.public_title": "Een kijkje binnenin...",
   "status.block": "Blokkeer @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Deze toot kan niet geboost worden",
   "status.delete": "Verwijderen",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Aan profielpagina vastmaken",
   "status.pinned": "Vastgemaakte toot",
   "status.reblog": "Boost",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} boostte",
   "status.reply": "Reageren",
   "status.replyAll": "Reageer op iedereen",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index 20b2cbb26..282a72acb 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Prøv igjen",
   "column.blocks": "Blokkerte brukere",
   "column.community": "Lokal tidslinje",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Likt",
   "column.follow_requests": "Følgeforespørsler",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Symboler",
   "emoji_button.travel": "Reise & steder",
   "empty_column.community": "Den lokale tidslinjen er tom. Skriv noe offentlig for å få snøballen til å rulle!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Det er ingenting i denne hashtagen ennå.",
   "empty_column.home": "Du har ikke fulgt noen ennå. Besøk {publlic} eller bruk søk for å komme i gang og møte andre brukere.",
   "empty_column.home.public_timeline": "en offentlig tidslinje",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Skjul varslinger fra denne brukeren?",
   "navigation_bar.blocks": "Blokkerte brukere",
   "navigation_bar.community_timeline": "Lokal tidslinje",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Rediger profil",
   "navigation_bar.favourites": "Favoritter",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}",
   "standalone.public_title": "En titt inni...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Denne posten kan ikke fremheves",
   "status.delete": "Slett",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Fest på profilen",
   "status.pinned": "Pinned toot",
   "status.reblog": "Fremhev",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "Fremhevd av {name}",
   "status.reply": "Svar",
   "status.replyAll": "Svar til samtale",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 32133c1f4..7170aefb8 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Tornar ensajar",
   "column.blocks": "Personas blocadas",
   "column.community": "Flux public local",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favorits",
   "column.follow_requests": "Demandas d’abonament",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Simbòls",
   "emoji_button.travel": "Viatges & lòcs",
   "empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "I a pas encara de contengut ligat a aquesta etiqueta.",
   "empty_column.home": "Vòstre flux d’acuèlh es void. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
   "empty_column.home.public_timeline": "lo flux public",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Rescondre las notificacions d’aquesta persona ?",
   "navigation_bar.blocks": "Personas blocadas",
   "navigation_bar.community_timeline": "Flux public local",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Modificar lo perfil",
   "navigation_bar.favourites": "Favorits",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
   "standalone.public_title": "Una ulhada dedins…",
   "status.block": "Blocar @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat",
   "status.delete": "Escafar",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Penjar al perfil",
   "status.pinned": "Tut penjat",
   "status.reblog": "Partejar",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} a partejat",
   "status.reply": "Respondre",
   "status.replyAll": "Respondre a la conversacion",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index 5360b6f33..c55603a99 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Spróbuj ponownie",
   "column.blocks": "Zablokowani użytkownicy",
   "column.community": "Lokalna oś czasu",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Ukryte domeny",
   "column.favourites": "Ulubione",
   "column.follow_requests": "Prośby o śledzenie",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Symbole",
   "emoji_button.travel": "Podróże i miejsca",
   "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy!",
   "empty_column.home": "Nie śledzisz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.",
   "empty_column.home.public_timeline": "publiczna oś czasu",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?",
   "navigation_bar.blocks": "Zablokowani użytkownicy",
   "navigation_bar.community_timeline": "Lokalna oś czasu",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Ukryte domeny",
   "navigation_bar.edit_profile": "Edytuj profil",
   "navigation_bar.favourites": "Ulubione",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {wynik} few {wyniki} many {wyników} more {wyników}}",
   "standalone.public_title": "Spojrzenie w głąb…",
   "status.block": "Zablokuj @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Ten wpis nie może zostać podbity",
   "status.delete": "Usuń",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Przypnij do profilu",
   "status.pinned": "Przypięty wpis",
   "status.reblog": "Podbij",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} podbił",
   "status.reply": "Odpowiedz",
   "status.replyAll": "Odpowiedz na wątek",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index b4be0bbc7..c604476c7 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Tente novamente",
   "column.blocks": "Usuários bloqueados",
   "column.community": "Local",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoritos",
   "column.follow_requests": "Seguidores pendentes",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viagens & Lugares",
   "empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Ainda não há qualquer conteúdo com essa hashtag.",
   "empty_column.home": "Você ainda não segue usuário algum. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.",
   "empty_column.home.public_timeline": "global",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Esconder notificações deste usuário?",
   "navigation_bar.blocks": "Usuários bloqueados",
   "navigation_bar.community_timeline": "Local",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favoritos",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "standalone.public_title": "Dê uma espiada...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Esta postagem não pode ser compartilhada",
   "status.delete": "Excluir",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Fixar no perfil",
   "status.pinned": "Toot fixado",
   "status.reblog": "Compartilhar",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} compartilhou",
   "status.reply": "Responder",
   "status.replyAll": "Responder à sequência",
diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json
index 132de5293..826785aad 100644
--- a/app/javascript/mastodon/locales/pt.json
+++ b/app/javascript/mastodon/locales/pt.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Tente de novo",
   "column.blocks": "Utilizadores Bloqueados",
   "column.community": "Local",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoritos",
   "column.follow_requests": "Seguidores Pendentes",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Símbolos",
   "emoji_button.travel": "Viagens & Lugares",
   "empty_column.community": "Ainda não existe conteúdo local para mostrar!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "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": "global",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Esconder notificações deste utilizador?",
   "navigation_bar.blocks": "Utilizadores bloqueados",
   "navigation_bar.community_timeline": "Local",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favoritos",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "standalone.public_title": "Espreitar lá dentro...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Este post não pode ser partilhado",
   "status.delete": "Eliminar",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Fixar no perfil",
   "status.pinned": "Pinned toot",
   "status.reblog": "Partilhar",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} partilhou",
   "status.reply": "Responder",
   "status.replyAll": "Responder à conversa",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index b56ccf1df..bb3cc1794 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Попробовать снова",
   "column.blocks": "Список блокировки",
   "column.community": "Локальная лента",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Скрытые домены",
   "column.favourites": "Понравившееся",
   "column.follow_requests": "Запросы на подписку",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Символы",
   "emoji_button.travel": "Путешествия",
   "empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Статусов с таким хэштегом еще не существует.",
   "empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.",
   "empty_column.home.public_timeline": "публичные ленты",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Убрать уведомления от этого пользователя?",
   "navigation_bar.blocks": "Список блокировки",
   "navigation_bar.community_timeline": "Локальная лента",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Скрытые домены",
   "navigation_bar.edit_profile": "Изменить профиль",
   "navigation_bar.favourites": "Понравившееся",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {результат} few {результата} many {результатов} other {результатов}}",
   "standalone.public_title": "Прямо сейчас",
   "status.block": "Заблокировать @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Этот статус не может быть продвинут",
   "status.delete": "Удалить",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Закрепить в профиле",
   "status.pinned": "Pinned toot",
   "status.reblog": "Продвинуть",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} продвинул(а)",
   "status.reply": "Ответить",
   "status.replyAll": "Ответить на тред",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 159315137..58274fd2d 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Skúsiť znova",
   "column.blocks": "Blokovaní užívatelia",
   "column.community": "Lokálna časová os",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Obľúbené",
   "column.follow_requests": "Žiadosti o sledovanie",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Symboly",
   "emoji_button.travel": "Cestovanie a miesta",
   "empty_column.community": "Lokálna časová os je prázdna. Napíšte niečo, aby sa to tu začalo hýbať!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Pod týmto hashtagom sa ešte nič nenachádza.",
   "empty_column.home": "Vaša lokálna osa je zatiaľ prázdna! Pre začiatok pozrite {public} alebo použite vyhľadávanie a nájdite tak ostatných používateľov.",
   "empty_column.home.public_timeline": "verejná časová os",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Skryť notifikácie od tohoto užívateľa?",
   "navigation_bar.blocks": "Blokovaní užívatelia",
   "navigation_bar.community_timeline": "Lokálna časová os",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Upraviť profil",
   "navigation_bar.favourites": "Obľúbené",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, jeden {výsledok} ostatné {výsledky}}",
   "standalone.public_title": "Náhľad dovnútra...",
   "status.block": "Blokovať @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Tento príspevok nemôže byť re-tootnutý",
   "status.delete": "Zmazať",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Pripni na profil",
   "status.pinned": "Pripnutý príspevok",
   "status.reblog": "Povýšiť",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} povýšil/a",
   "status.reply": "Odpovedať",
   "status.replyAll": "Odpovedať na diskusiu",
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index 69c7aa62b..e4d07edd1 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Pokušajte ponovo",
   "column.blocks": "Blokirani korisnici",
   "column.community": "Lokalna lajna",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Omiljeni",
   "column.follow_requests": "Zahtevi za praćenje",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Simboli",
   "emoji_button.travel": "Putovanja & mesta",
   "empty_column.community": "Lokalna lajna je prazna. Napišite nešto javno da lajna produva!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Trenutno nema ništa na ovom heštegu.",
   "empty_column.home": "Vaša lajna je prazna! Posetite {public} ili koristite pretragu da počnete i upoznajete nove ljude.",
   "empty_column.home.public_timeline": "javna lajna",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Sakrij obaveštenja od ovog korisnika?",
   "navigation_bar.blocks": "Blokirani korisnici",
   "navigation_bar.community_timeline": "Lokalna lajna",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Izmeni profil",
   "navigation_bar.favourites": "Omiljeni",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {rezultat} few {rezultata} other {rezultata}}",
   "standalone.public_title": "Pogled iznutra...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Ovaj status ne može da se podrži",
   "status.delete": "Obriši",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Prikači na profil",
   "status.pinned": "Pinned toot",
   "status.reblog": "Podrži",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} podržao(la)",
   "status.reply": "Odgovori",
   "status.replyAll": "Odgovori na diskusiju",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index e9739451e..60c781e9d 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Покушајте поново",
   "column.blocks": "Блокирани корисници",
   "column.community": "Локална лајна",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Омиљени",
   "column.follow_requests": "Захтеви за праћење",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Симболи",
   "emoji_button.travel": "Путовања & места",
   "empty_column.community": "Локална лајна је празна. Напишите нешто јавно да лајна продува!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Тренутно нема ништа на овом хештегу.",
   "empty_column.home": "Ваша лајна је празна! Посетите {public} или користите претрагу да почнете и упознајете нове људе.",
   "empty_column.home.public_timeline": "јавна лајна",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Сакриј обавештења од овог корисника?",
   "navigation_bar.blocks": "Блокирани корисници",
   "navigation_bar.community_timeline": "Локална лајна",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Измени профил",
   "navigation_bar.favourites": "Омиљени",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {резултат} few {резултата} other {резултата}}",
   "standalone.public_title": "Поглед изнутра...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Овај статус не може да се подржи",
   "status.delete": "Обриши",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Прикачи на профил",
   "status.pinned": "Pinned toot",
   "status.reblog": "Подржи",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} подржао(ла)",
   "status.reply": "Одговори",
   "status.replyAll": "Одговори на дискусију",
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index b063adb0f..8fa6992f1 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Försök igen",
   "column.blocks": "Blockerade användare",
   "column.community": "Lokal tidslinje",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoriter",
   "column.follow_requests": "Följ förfrågningar",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Symboler",
   "emoji_button.travel": "Resor & Platser",
   "empty_column.community": "Den lokala tidslinjen är tom. Skriv något offentligt för att få bollen att rulla!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "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",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Dölj notifikationer från denna användare?",
   "navigation_bar.blocks": "Blockerade användare",
   "navigation_bar.community_timeline": "Lokal tidslinje",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Redigera profil",
   "navigation_bar.favourites": "Favoriter",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, ett {result} andra {results}}",
   "standalone.public_title": "En titt inuti...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Detta inlägg kan inte knuffas",
   "status.delete": "Ta bort",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Fäst i profil",
   "status.pinned": "Fäst toot",
   "status.reblog": "Knuff",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} knuffade",
   "status.reply": "Svara",
   "status.replyAll": "Svara på tråden",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 22a75c237..3b91c0d2c 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Blocked users",
   "column.community": "Local timeline",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Symbols",
   "emoji_button.travel": "Travel & Places",
   "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.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",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Edit profile",
   "navigation_bar.favourites": "Favourites",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.delete": "Delete",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
   "status.reblog": "Boost",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} boosted",
   "status.reply": "Reply",
   "status.replyAll": "Reply to thread",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 8e36c512f..cdf6f46a3 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Engellenen kullanıcılar",
   "column.community": "Yerel zaman tüneli",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoriler",
   "column.follow_requests": "Takip istekleri",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Semboller",
   "emoji_button.travel": "Seyahat ve Yerler",
   "empty_column.community": "Yerel zaman tüneliniz boş. Daha fazla eğlence için herkese açık bir gönderi paylaşın.",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Henüz bu hashtag’e sahip hiçbir gönderi yok.",
   "empty_column.home": "Henüz kimseyi takip etmiyorsunuz. {public} ziyaret edebilir veya arama kısmını kullanarak diğer kullanıcılarla iletişime geçebilirsiniz.",
   "empty_column.home.public_timeline": "herkese açık zaman tüneli",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Engellenen kullanıcılar",
   "navigation_bar.community_timeline": "Yerel zaman tüneli",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Profili düzenle",
   "navigation_bar.favourites": "Favoriler",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {sonuç} other {sonuçlar}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Bu gönderi boost edilemez",
   "status.delete": "Sil",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
   "status.reblog": "Boost'la",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} boost etti",
   "status.reply": "Cevapla",
   "status.replyAll": "Konuşmayı cevapla",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 09210a325..261e5795e 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Заблоковані користувачі",
   "column.community": "Локальна стрічка",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Вподобане",
   "column.follow_requests": "Запити на підписку",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "Символи",
   "emoji_button.travel": "Подорожі",
   "empty_column.community": "Локальна стрічка пуста. Напишіть щось, щоб розігріти народ!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "Дописів з цим хештегом поки не існує.",
   "empty_column.home": "Ви поки ні на кого не підписані. Погортайте {public}, або скористуйтесь пошуком, щоб освоїтися та познайомитися з іншими користувачами.",
   "empty_column.home.public_timeline": "публічні стрічки",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Заблоковані користувачі",
   "navigation_bar.community_timeline": "Локальна стрічка",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Редагувати профіль",
   "navigation_bar.favourites": "Вподобане",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} {count, plural, one {результат} few {результати} many {результатів} other {результатів}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "Цей допис не може бути передмухнутий",
   "status.delete": "Видалити",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
   "status.reblog": "Передмухнути",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} передмухнув(-ла)",
   "status.reply": "Відповісти",
   "status.replyAll": "Відповісти на тред",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index f0772ffaa..aba0bde83 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "重试",
   "column.blocks": "屏蔽用户",
   "column.community": "本站时间轴",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "收藏过的嘟文",
   "column.follow_requests": "关注请求",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "符号",
   "emoji_button.travel": "旅行和地点",
   "empty_column.community": "本站时间轴暂时没有内容,快嘟几个来抢头香啊!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "这个话题标签下暂时没有内容。",
   "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
   "empty_column.home.public_timeline": "公共时间轴",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "同时隐藏来自这个用户的通知",
   "navigation_bar.blocks": "被屏蔽的用户",
   "navigation_bar.community_timeline": "本站时间轴",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "修改个人资料",
   "navigation_bar.favourites": "收藏的内容",
@@ -238,6 +241,7 @@
   "search_results.total": "共 {count, number} 个结果",
   "standalone.public_title": "大家都在干啥?",
   "status.block": "屏蔽 @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "无法转嘟这条嘟文",
   "status.delete": "删除",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "在个人资料页面置顶",
   "status.pinned": "Pinned toot",
   "status.reblog": "转嘟",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} 转嘟了",
   "status.reply": "回复",
   "status.replyAll": "回复所有人",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index 28685f4d8..b5ebd20fc 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "重試",
   "column.blocks": "封鎖用戶",
   "column.community": "本站時間軸",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "隱藏的服務站",
   "column.favourites": "最愛的文章",
   "column.follow_requests": "關注請求",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "符號",
   "emoji_button.travel": "旅遊景物",
   "empty_column.community": "本站時間軸暫時未有內容,快寫一點東西來搶頭香啊!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "這個標籤暫時未有內容。",
   "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。",
   "empty_column.home.public_timeline": "公共時間軸",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "隱藏來自這用戶的通知嗎?",
   "navigation_bar.blocks": "被你封鎖的用戶",
   "navigation_bar.community_timeline": "本站時間軸",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "隱藏的服務站",
   "navigation_bar.edit_profile": "修改個人資料",
   "navigation_bar.favourites": "最愛的內容",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} 項結果",
   "standalone.public_title": "站點一瞥…",
   "status.block": "封鎖 @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "這篇文章無法被轉推",
   "status.delete": "刪除",
   "status.direct": "私訊 @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "置頂到資料頁",
   "status.pinned": "置頂文章",
   "status.reblog": "轉推",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} 轉推",
   "status.reply": "回應",
   "status.replyAll": "回應所有人",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index efed9cd4d..28d634600 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "重試",
   "column.blocks": "封鎖的使用者",
   "column.community": "本地時間軸",
+  "column.direct": "Direct messages",
   "column.domain_blocks": "隱藏域名",
   "column.favourites": "最愛",
   "column.follow_requests": "關注請求",
@@ -100,6 +101,7 @@
   "emoji_button.symbols": "符號",
   "emoji_button.travel": "旅遊與地點",
   "empty_column.community": "本地時間軸是空的。公開寫點什麼吧!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
   "empty_column.hashtag": "這個主題標籤下什麼都沒有。",
   "empty_column.home": "你還沒關注任何人。造訪{public}或利用搜尋功能找到其他用者。",
   "empty_column.home.public_timeline": "公開時間軸",
@@ -154,6 +156,7 @@
   "mute_modal.hide_notifications": "隱藏來自這個使用者的通知?",
   "navigation_bar.blocks": "封鎖的使用者",
   "navigation_bar.community_timeline": "本地時間軸",
+  "navigation_bar.direct": "Direct messages",
   "navigation_bar.domain_blocks": "隱藏的域名",
   "navigation_bar.edit_profile": "編輯用者資訊",
   "navigation_bar.favourites": "最愛",
@@ -238,6 +241,7 @@
   "search_results.total": "{count, number} 項結果",
   "standalone.public_title": "站點一瞥…",
   "status.block": "封鎖 @{name}",
+  "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "此貼文無法轉推",
   "status.delete": "刪除",
   "status.direct": "Direct message @{name}",
@@ -253,6 +257,7 @@
   "status.pin": "置頂到個人資訊頁",
   "status.pinned": "置頂的推文",
   "status.reblog": "轉推",
+  "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} 轉推了",
   "status.reply": "回應",
   "status.replyAll": "回應這串",
diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js
index c1ecf7e8a..ebd01e532 100644
--- a/app/javascript/mastodon/reducers/contexts.js
+++ b/app/javascript/mastodon/reducers/contexts.js
@@ -41,8 +41,9 @@ const deleteFromContexts = (immutableState, ids) => immutableState.withMutations
 });
 
 const filterContexts = (state, relationship, statuses) => {
-  const ownedStatusIds = statuses.filter(status => status.get('account') === relationship.id)
-                                 .map(status => status.get('id'));
+  const ownedStatusIds = statuses
+    .filter(status => status.get('account') === relationship.id)
+    .map(status => status.get('id'));
 
   return deleteFromContexts(state, ownedStatusIds);
 };
diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js
index 390b2a13a..9ec52a7fa 100644
--- a/app/javascript/mastodon/reducers/settings.js
+++ b/app/javascript/mastodon/reducers/settings.js
@@ -58,6 +58,12 @@ const initialState = ImmutableMap({
       body: '',
     }),
   }),
+
+  direct: ImmutableMap({
+    regex: ImmutableMap({
+      body: '',
+    }),
+  }),
 });
 
 const defaultColumns = fromJS([
diff --git a/app/models/status.rb b/app/models/status.rb
index f924be47b..62857dd6b 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -183,6 +183,14 @@ class Status < ApplicationRecord
       where(account: [account] + account.following).where(visibility: [:public, :unlisted, :private])
     end
 
+    def as_direct_timeline(account)
+      query = joins("LEFT OUTER JOIN mentions ON statuses.id = mentions.status_id AND mentions.account_id = #{account.id}")
+              .where("mentions.account_id = #{account.id} OR statuses.account_id = #{account.id}")
+              .where(visibility: [:direct])
+
+      apply_timeline_filters(query, account, false)
+    end
+
     def as_public_timeline(account = nil, local_only = false)
       query = timeline_scope(local_only).without_replies
 
diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb
index e2763c2b9..cb65a2256 100644
--- a/app/services/batched_remove_status_service.rb
+++ b/app/services/batched_remove_status_service.rb
@@ -36,6 +36,7 @@ class BatchedRemoveStatusService < BaseService
     # Cannot be batched
     statuses.each do |status|
       unpush_from_public_timelines(status)
+      unpush_from_direct_timelines(status) if status.direct_visibility?
       batch_salmon_slaps(status) if status.local?
     end
 
@@ -87,6 +88,16 @@ class BatchedRemoveStatusService < BaseService
     end
   end
 
+  def unpush_from_direct_timelines(status)
+    payload = @json_payloads[status.id]
+    redis.pipelined do
+      @mentions[status.id].each do |mention|
+        redis.publish("timeline:direct:#{mention.account.id}", payload) if mention.account.local?
+      end
+      redis.publish("timeline:direct:#{status.account.id}", payload) if status.account.local?
+    end
+  end
+
   def batch_salmon_slaps(status)
     return if @mentions[status.id].empty?
 
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index bbaf3094b..0f77556dc 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -10,8 +10,11 @@ class FanOutOnWriteService < BaseService
 
     deliver_to_self(status) if status.account.local?
 
+    render_anonymous_payload(status)
+
     if status.direct_visibility?
       deliver_to_mentioned_followers(status)
+      deliver_to_direct_timelines(status)
     else
       deliver_to_followers(status)
       deliver_to_lists(status)
@@ -19,7 +22,6 @@ class FanOutOnWriteService < BaseService
 
     return if status.account.silenced? || !status.public_visibility? || status.reblog?
 
-    render_anonymous_payload(status)
     deliver_to_hashtags(status)
 
     return if status.reply? && status.in_reply_to_account_id != status.account_id
@@ -84,4 +86,13 @@ class FanOutOnWriteService < BaseService
     Redis.current.publish('timeline:public', @payload)
     Redis.current.publish('timeline:public:local', @payload) if status.local?
   end
+
+  def deliver_to_direct_timelines(status)
+    Rails.logger.debug "Delivering status #{status.id} to direct timelines"
+
+    status.mentions.includes(:account).each do |mention|
+      Redis.current.publish("timeline:direct:#{mention.account.id}", @payload) if mention.account.local?
+    end
+    Redis.current.publish("timeline:direct:#{status.account.id}", @payload) if status.account.local?
+  end
 end
diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb
index a100f73ce..e164c03ab 100644
--- a/app/services/remove_status_service.rb
+++ b/app/services/remove_status_service.rb
@@ -20,6 +20,7 @@ class RemoveStatusService < BaseService
     remove_reblogs
     remove_from_hashtags
     remove_from_public
+    remove_from_direct if status.direct_visibility?
 
     @status.destroy!
 
@@ -130,6 +131,13 @@ class RemoveStatusService < BaseService
     Redis.current.publish('timeline:public:local', @payload) if @status.local?
   end
 
+  def remove_from_direct
+    @mentions.each do |mention|
+      Redis.current.publish("timeline:direct:#{mention.account.id}", @payload) if mention.account.local?
+    end
+    Redis.current.publish("timeline:direct:#{@account.id}", @payload) if @account.local?
+  end
+
   def redis
     Redis.current
   end
diff --git a/config/routes.rb b/config/routes.rb
index 2776898ac..d959301e9 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -229,6 +229,7 @@ Rails.application.routes.draw do
       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
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index 4b5c20871..c6701018e 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -304,6 +304,55 @@ RSpec.describe Status, type: :model do
     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 'includes direct statuses mentioning recipient from followed' do
+      Fabricate(:mention, account: account, status: @followed_direct_status)
+      expect(@results).to include(@followed_direct_status)
+    end
+
+    it 'does not include direct statuses not mentioning recipient from followed' do
+      expect(@results).to_not include(@followed_direct_status)
+    end
+
+    it 'includes direct statuses mentioning recipient from non-followed' do
+      Fabricate(:mention, account: account, status: @not_followed_direct_status)
+      expect(@results).to include(@not_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
+
+  end
+
   describe '.as_public_timeline' do
     it 'only includes statuses with public visibility' do
       public_status = Fabricate(:status, visibility: :public)
diff --git a/streaming/index.js b/streaming/index.js
index 1b4f8596c..48bab8078 100644
--- a/streaming/index.js
+++ b/streaming/index.js
@@ -466,6 +466,10 @@ const startWorker = (workerId) => {
     streamFrom('timeline:public:local', req, streamToHttp(req, res), streamHttpEnd(req), true);
   });
 
+  app.get('/api/v1/streaming/direct', (req, res) => {
+    streamFrom(`timeline:direct:${req.accountId}`, req, streamToHttp(req, res), streamHttpEnd(req), true);
+  });
+
   app.get('/api/v1/streaming/hashtag', (req, res) => {
     streamFrom(`timeline:hashtag:${req.query.tag.toLowerCase()}`, req, streamToHttp(req, res), streamHttpEnd(req), true);
   });
@@ -517,6 +521,9 @@ const startWorker = (workerId) => {
     case 'public:local':
       streamFrom('timeline:public:local', req, streamToWs(req, ws), streamWsEnd(req, ws), true);
       break;
+    case 'direct':
+      streamFrom(`timeline:direct:${req.accountId}`, req, streamToWs(req, ws), streamWsEnd(req, ws), true);
+      break;
     case 'hashtag':
       streamFrom(`timeline:hashtag:${location.query.tag.toLowerCase()}`, req, streamToWs(req, ws), streamWsEnd(req, ws), true);
       break;
diff --git a/yarn.lock b/yarn.lock
index fba2cb9a7..c5a49a497 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5994,9 +5994,9 @@ rc@^1.1.7:
     minimist "^1.2.0"
     strip-json-comments "~2.0.1"
 
-react-dom@^16.2.0:
-  version "16.2.0"
-  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044"
+react-dom@^16.3.0:
+  version "16.3.2"
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df"
   dependencies:
     fbjs "^0.8.16"
     loose-envify "^1.1.0"
@@ -6180,9 +6180,9 @@ react-transition-group@^2.2.0:
     prop-types "^15.5.8"
     warning "^3.0.0"
 
-react@^16.2.0:
-  version "16.2.0"
-  resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
+react@^16.3.0:
+  version "16.3.2"
+  resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9"
   dependencies:
     fbjs "^0.8.16"
     loose-envify "^1.1.0"