about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorThibG <thib@sitedethib.com>2020-01-25 19:07:32 +0100
committerGitHub <noreply@github.com>2020-01-25 19:07:32 +0100
commit5bd752081fd72c9899780eaa4b8ce2bd277b1ff0 (patch)
treee70256fe3b1c89a88222ff91ae88e12248b4a207 /app
parent340cb4a04c94f9403673d59e552be909fce25ece (diff)
parent6ce72f1fee6ca0752467e4f42ca1bd2dd84b3502 (diff)
Merge pull request #1267 from ThibG/glitch-soc/merge-upstream
Merge upstream changes
Diffstat (limited to 'app')
-rw-r--r--app/javascript/flavours/glitch/actions/announcements.js18
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/components/announcements.js20
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js4
-rw-r--r--app/javascript/flavours/glitch/features/home_timeline/index.js40
-rw-r--r--app/javascript/flavours/glitch/reducers/announcements.js28
-rw-r--r--app/javascript/flavours/glitch/styles/components/announcements.scss8
-rw-r--r--app/javascript/mastodon/actions/announcements.js18
-rw-r--r--app/javascript/mastodon/features/getting_started/components/announcements.js20
-rw-r--r--app/javascript/mastodon/features/getting_started/containers/announcements_container.js4
-rw-r--r--app/javascript/mastodon/features/home_timeline/index.js40
-rw-r--r--app/javascript/mastodon/locales/br.json38
-rw-r--r--app/javascript/mastodon/locales/ca.json14
-rw-r--r--app/javascript/mastodon/locales/co.json2
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json4
-rw-r--r--app/javascript/mastodon/locales/el.json2
-rw-r--r--app/javascript/mastodon/locales/es-AR.json2
-rw-r--r--app/javascript/mastodon/locales/es.json54
-rw-r--r--app/javascript/mastodon/locales/eu.json2
-rw-r--r--app/javascript/mastodon/locales/fa.json290
-rw-r--r--app/javascript/mastodon/locales/gl.json36
-rw-r--r--app/javascript/mastodon/locales/hu.json2
-rw-r--r--app/javascript/mastodon/locales/ja.json6
-rw-r--r--app/javascript/mastodon/locales/ko.json2
-rw-r--r--app/javascript/mastodon/locales/nn.json8
-rw-r--r--app/javascript/mastodon/locales/no.json8
-rw-r--r--app/javascript/mastodon/locales/ru.json2
-rw-r--r--app/javascript/mastodon/locales/sk.json2
-rw-r--r--app/javascript/mastodon/locales/th.json2
-rw-r--r--app/javascript/mastodon/locales/tr.json2
-rw-r--r--app/javascript/mastodon/locales/uk.json10
-rw-r--r--app/javascript/mastodon/reducers/announcements.js28
-rw-r--r--app/javascript/styles/mastodon/components.scss8
-rw-r--r--app/validators/reaction_validator.rb10
33 files changed, 395 insertions, 339 deletions
diff --git a/app/javascript/flavours/glitch/actions/announcements.js b/app/javascript/flavours/glitch/actions/announcements.js
index d0e5ee176..b4e8cee2f 100644
--- a/app/javascript/flavours/glitch/actions/announcements.js
+++ b/app/javascript/flavours/glitch/actions/announcements.js
@@ -5,7 +5,6 @@ export const ANNOUNCEMENTS_FETCH_REQUEST = 'ANNOUNCEMENTS_FETCH_REQUEST';
 export const ANNOUNCEMENTS_FETCH_SUCCESS = 'ANNOUNCEMENTS_FETCH_SUCCESS';
 export const ANNOUNCEMENTS_FETCH_FAIL    = 'ANNOUNCEMENTS_FETCH_FAIL';
 export const ANNOUNCEMENTS_UPDATE        = 'ANNOUNCEMENTS_UPDATE';
-export const ANNOUNCEMENTS_DISMISS       = 'ANNOUNCEMENTS_DISMISS';
 
 export const ANNOUNCEMENTS_REACTION_ADD_REQUEST = 'ANNOUNCEMENTS_REACTION_ADD_REQUEST';
 export const ANNOUNCEMENTS_REACTION_ADD_SUCCESS = 'ANNOUNCEMENTS_REACTION_ADD_SUCCESS';
@@ -17,6 +16,8 @@ export const ANNOUNCEMENTS_REACTION_REMOVE_FAIL    = 'ANNOUNCEMENTS_REACTION_REM
 
 export const ANNOUNCEMENTS_REACTION_UPDATE = 'ANNOUNCEMENTS_REACTION_UPDATE';
 
+export const ANNOUNCEMENTS_TOGGLE_SHOW = 'ANNOUNCEMENTS_TOGGLE_SHOW';
+
 const noOp = () => {};
 
 export const fetchAnnouncements = (done = noOp) => (dispatch, getState) => {
@@ -54,15 +55,6 @@ export const updateAnnouncements = announcement => ({
   announcement: normalizeAnnouncement(announcement),
 });
 
-export const dismissAnnouncement = announcementId => (dispatch, getState) => {
-  dispatch({
-    type: ANNOUNCEMENTS_DISMISS,
-    id: announcementId,
-  });
-
-  api(getState).post(`/api/v1/announcements/${announcementId}/dismiss`);
-};
-
 export const addReaction = (announcementId, name) => (dispatch, getState) => {
   dispatch(addReactionRequest(announcementId, name));
 
@@ -131,3 +123,9 @@ export const updateReaction = reaction => ({
   type: ANNOUNCEMENTS_REACTION_UPDATE,
   reaction,
 });
+
+export function toggleShowAnnouncements() {
+  return dispatch => {
+    dispatch({ type: ANNOUNCEMENTS_TOGGLE_SHOW });
+  };
+}
diff --git a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js
index 71b54b060..9a7d175c4 100644
--- a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js
+++ b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js
@@ -277,19 +277,13 @@ class Announcement extends ImmutablePureComponent {
   static propTypes = {
     announcement: ImmutablePropTypes.map.isRequired,
     emojiMap: ImmutablePropTypes.map.isRequired,
-    dismissAnnouncement: PropTypes.func.isRequired,
     addReaction: PropTypes.func.isRequired,
     removeReaction: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
   };
 
-  handleDismissClick = () => {
-    const { dismissAnnouncement, announcement } = this.props;
-    dismissAnnouncement(announcement.get('id'));
-  }
-
   render () {
-    const { announcement, intl } = this.props;
+    const { announcement } = this.props;
     const startsAt = announcement.get('starts_at') && new Date(announcement.get('starts_at'));
     const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at'));
     const now = new Date();
@@ -314,8 +308,6 @@ class Announcement extends ImmutablePureComponent {
           removeReaction={this.props.removeReaction}
           emojiMap={this.props.emojiMap}
         />
-
-        <IconButton title={intl.formatMessage(messages.close)} icon='times' className='announcements__item__dismiss-icon' onClick={this.handleDismissClick} />
       </div>
     );
   }
@@ -328,8 +320,6 @@ class Announcements extends ImmutablePureComponent {
   static propTypes = {
     announcements: ImmutablePropTypes.list,
     emojiMap: ImmutablePropTypes.map.isRequired,
-    fetchAnnouncements: PropTypes.func.isRequired,
-    dismissAnnouncement: PropTypes.func.isRequired,
     addReaction: PropTypes.func.isRequired,
     removeReaction: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
@@ -339,11 +329,6 @@ class Announcements extends ImmutablePureComponent {
     index: 0,
   };
 
-  componentDidMount () {
-    const { fetchAnnouncements } = this.props;
-    fetchAnnouncements();
-  }
-
   handleChangeIndex = index => {
     this.setState({ index: index % this.props.announcements.size });
   }
@@ -369,13 +354,12 @@ class Announcements extends ImmutablePureComponent {
         <img className='announcements__mastodon' alt='' draggable='false' src={mascot || elephantUIPlane} />
 
         <div className='announcements__container'>
-          <ReactSwipeableViews index={index} onChangeIndex={this.handleChangeIndex}>
+          <ReactSwipeableViews animateHeight index={index} onChangeIndex={this.handleChangeIndex}>
             {announcements.map(announcement => (
               <Announcement
                 key={announcement.get('id')}
                 announcement={announcement}
                 emojiMap={this.props.emojiMap}
-                dismissAnnouncement={this.props.dismissAnnouncement}
                 addReaction={this.props.addReaction}
                 removeReaction={this.props.removeReaction}
                 intl={intl}
diff --git a/app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js b/app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js
index b10d1d4ce..8fa695e34 100644
--- a/app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js
+++ b/app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js
@@ -1,5 +1,5 @@
 import { connect } from 'react-redux';
-import { fetchAnnouncements, dismissAnnouncement, addReaction, removeReaction } from 'mastodon/actions/announcements';
+import { addReaction, removeReaction } from 'flavours/glitch/actions/announcements';
 import Announcements from '../components/announcements';
 import { createSelector } from 'reselect';
 import { Map as ImmutableMap } from 'immutable';
@@ -12,8 +12,6 @@ const mapStateToProps = state => ({
 });
 
 const mapDispatchToProps = dispatch => ({
-  fetchAnnouncements: () => dispatch(fetchAnnouncements()),
-  dismissAnnouncement: id => dispatch(dismissAnnouncement(id)),
   addReaction: (id, name) => dispatch(addReaction(id, name)),
   removeReaction: (id, name) => dispatch(removeReaction(id, name)),
 });
diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.js b/app/javascript/flavours/glitch/features/home_timeline/index.js
index 263371b06..457ac051c 100644
--- a/app/javascript/flavours/glitch/features/home_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/home_timeline/index.js
@@ -9,15 +9,23 @@ import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/col
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ColumnSettingsContainer from './containers/column_settings_container';
 import { Link } from 'react-router-dom';
+import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/actions/announcements';
 import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container';
+import classNames from 'classnames';
+import IconWithBadge from 'flavours/glitch/components/icon_with_badge';
 
 const messages = defineMessages({
   title: { id: 'column.home', defaultMessage: 'Home' },
+  show_announcements: { id: 'home.show_announcements', defaultMessage: 'Show announcements' },
+  hide_announcements: { id: 'home.hide_announcements', defaultMessage: 'Hide announcements' },
 });
 
 const mapStateToProps = state => ({
   hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
   isPartial: state.getIn(['timelines', 'home', 'isPartial']),
+  hasAnnouncements: !state.getIn(['announcements', 'items']).isEmpty(),
+  unreadAnnouncements: state.getIn(['announcements', 'unread']).size,
+  showAnnouncements: state.getIn(['announcements', 'show']),
 });
 
 export default @connect(mapStateToProps)
@@ -31,6 +39,9 @@ class HomeTimeline extends React.PureComponent {
     isPartial: PropTypes.bool,
     columnId: PropTypes.string,
     multiColumn: PropTypes.bool,
+    hasAnnouncements: PropTypes.bool,
+    unreadAnnouncements: PropTypes.number,
+    showAnnouncements: PropTypes.bool,
   };
 
   handlePin = () => {
@@ -61,6 +72,7 @@ class HomeTimeline extends React.PureComponent {
   }
 
   componentDidMount () {
+    this.props.dispatch(fetchAnnouncements());
     this._checkIfReloadNeeded(false, this.props.isPartial);
   }
 
@@ -93,10 +105,31 @@ class HomeTimeline extends React.PureComponent {
     }
   }
 
+  handleToggleAnnouncementsClick = (e) => {
+    e.stopPropagation();
+    this.props.dispatch(toggleShowAnnouncements());
+  }
+
   render () {
-    const { intl, hasUnread, columnId, multiColumn } = this.props;
+    const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
     const pinned = !!columnId;
 
+    let announcementsButton = null;
+
+    if (hasAnnouncements) {
+      announcementsButton = (
+        <button
+          className={classNames('column-header__button', { 'active': showAnnouncements })}
+          title={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
+          aria-label={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
+          aria-pressed={showAnnouncements ? 'true' : 'false'}
+          onClick={this.handleToggleAnnouncementsClick}
+        >
+          <IconWithBadge id='bullhorn' count={unreadAnnouncements} />
+        </button>
+      );
+    }
+
     return (
       <Column bindToDocument={!multiColumn} ref={this.setRef} name='home' label={intl.formatMessage(messages.title)}>
         <ColumnHeader
@@ -108,13 +141,14 @@ class HomeTimeline extends React.PureComponent {
           onClick={this.handleHeaderClick}
           pinned={pinned}
           multiColumn={multiColumn}
+          extraButton={announcementsButton}
         >
           <ColumnSettingsContainer />
         </ColumnHeader>
 
+        {hasAnnouncements && showAnnouncements && <AnnouncementsContainer />}
+
         <StatusListContainer
-          prepend={<AnnouncementsContainer />}
-          alwaysPrepend
           trackScroll={!pinned}
           scrollKey={`home_timeline-${columnId}`}
           onLoadMore={this.handleLoadMore}
diff --git a/app/javascript/flavours/glitch/reducers/announcements.js b/app/javascript/flavours/glitch/reducers/announcements.js
index aa674e516..1cfb598fb 100644
--- a/app/javascript/flavours/glitch/reducers/announcements.js
+++ b/app/javascript/flavours/glitch/reducers/announcements.js
@@ -3,18 +3,20 @@ import {
   ANNOUNCEMENTS_FETCH_SUCCESS,
   ANNOUNCEMENTS_FETCH_FAIL,
   ANNOUNCEMENTS_UPDATE,
-  ANNOUNCEMENTS_DISMISS,
   ANNOUNCEMENTS_REACTION_UPDATE,
   ANNOUNCEMENTS_REACTION_ADD_REQUEST,
   ANNOUNCEMENTS_REACTION_ADD_FAIL,
   ANNOUNCEMENTS_REACTION_REMOVE_REQUEST,
   ANNOUNCEMENTS_REACTION_REMOVE_FAIL,
+  ANNOUNCEMENTS_TOGGLE_SHOW,
 } from '../actions/announcements';
-import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+import { Map as ImmutableMap, List as ImmutableList, Set as ImmutableSet, fromJS } from 'immutable';
 
 const initialState = ImmutableMap({
   items: ImmutableList(),
   isLoading: false,
+  show: true,
+  unread: ImmutableSet(),
 });
 
 const updateReaction = (state, id, name, updater) => state.update('items', list => list.map(announcement => {
@@ -43,21 +45,35 @@ const addReaction = (state, id, name) => updateReaction(state, id, name, x => x.
 
 const removeReaction = (state, id, name) => updateReaction(state, id, name, x => x.set('me', false).update('count', y => y - 1));
 
+const addUnread = (state, items) => {
+  if (state.get('show')) return state;
+
+  const newIds = ImmutableSet(items.map(x => x.get('id')));
+  const oldIds = ImmutableSet(state.get('items').map(x => x.get('id')));
+  return state.update('unread', unread => unread.union(newIds.subtract(oldIds)));
+};
+
 export default function announcementsReducer(state = initialState, action) {
   switch(action.type) {
+  case ANNOUNCEMENTS_TOGGLE_SHOW:
+    return state.withMutations(map => {
+      if (!map.get('show')) map.set('unread', ImmutableSet());
+      map.set('show', !map.get('show'));
+    });
   case ANNOUNCEMENTS_FETCH_REQUEST:
     return state.set('isLoading', true);
   case ANNOUNCEMENTS_FETCH_SUCCESS:
     return state.withMutations(map => {
-      map.set('items', fromJS(action.announcements));
+      const items = fromJS(action.announcements);
+      map.set('unread', ImmutableSet());
+      addUnread(map, items);
+      map.set('items', items);
       map.set('isLoading', false);
     });
   case ANNOUNCEMENTS_FETCH_FAIL:
     return state.set('isLoading', false);
   case ANNOUNCEMENTS_UPDATE:
-    return state.update('items', list => list.unshift(fromJS(action.announcement)).sortBy(announcement => announcement.get('starts_at')));
-  case ANNOUNCEMENTS_DISMISS:
-    return state.update('items', list => list.filterNot(announcement => announcement.get('id') === action.id));
+    return addUnread(state, [fromJS(action.announcement)]).update('items', list => list.unshift(fromJS(action.announcement)).sortBy(announcement => announcement.get('starts_at')));
   case ANNOUNCEMENTS_REACTION_UPDATE:
     return updateReactionCount(state, action.reaction);
   case ANNOUNCEMENTS_REACTION_ADD_REQUEST:
diff --git a/app/javascript/flavours/glitch/styles/components/announcements.scss b/app/javascript/flavours/glitch/styles/components/announcements.scss
index 11d29931d..6bf9e2a0c 100644
--- a/app/javascript/flavours/glitch/styles/components/announcements.scss
+++ b/app/javascript/flavours/glitch/styles/components/announcements.scss
@@ -37,7 +37,7 @@
 }
 
 .announcements {
-  background: lighten($ui-base-color, 4%);
+  background: lighten($ui-base-color, 8%);
   border-top: 1px solid $ui-base-color;
   font-size: 13px;
   display: flex;
@@ -78,12 +78,6 @@
       font-weight: 500;
       margin-bottom: 10px;
     }
-
-    &__dismiss-icon {
-      position: absolute;
-      top: 12px;
-      right: 12px;
-    }
   }
 
   &__pagination {
diff --git a/app/javascript/mastodon/actions/announcements.js b/app/javascript/mastodon/actions/announcements.js
index c65bc052e..64bf5ef91 100644
--- a/app/javascript/mastodon/actions/announcements.js
+++ b/app/javascript/mastodon/actions/announcements.js
@@ -5,7 +5,6 @@ export const ANNOUNCEMENTS_FETCH_REQUEST = 'ANNOUNCEMENTS_FETCH_REQUEST';
 export const ANNOUNCEMENTS_FETCH_SUCCESS = 'ANNOUNCEMENTS_FETCH_SUCCESS';
 export const ANNOUNCEMENTS_FETCH_FAIL    = 'ANNOUNCEMENTS_FETCH_FAIL';
 export const ANNOUNCEMENTS_UPDATE        = 'ANNOUNCEMENTS_UPDATE';
-export const ANNOUNCEMENTS_DISMISS       = 'ANNOUNCEMENTS_DISMISS';
 
 export const ANNOUNCEMENTS_REACTION_ADD_REQUEST = 'ANNOUNCEMENTS_REACTION_ADD_REQUEST';
 export const ANNOUNCEMENTS_REACTION_ADD_SUCCESS = 'ANNOUNCEMENTS_REACTION_ADD_SUCCESS';
@@ -17,6 +16,8 @@ export const ANNOUNCEMENTS_REACTION_REMOVE_FAIL    = 'ANNOUNCEMENTS_REACTION_REM
 
 export const ANNOUNCEMENTS_REACTION_UPDATE = 'ANNOUNCEMENTS_REACTION_UPDATE';
 
+export const ANNOUNCEMENTS_TOGGLE_SHOW = 'ANNOUNCEMENTS_TOGGLE_SHOW';
+
 const noOp = () => {};
 
 export const fetchAnnouncements = (done = noOp) => (dispatch, getState) => {
@@ -54,15 +55,6 @@ export const updateAnnouncements = announcement => ({
   announcement: normalizeAnnouncement(announcement),
 });
 
-export const dismissAnnouncement = announcementId => (dispatch, getState) => {
-  dispatch({
-    type: ANNOUNCEMENTS_DISMISS,
-    id: announcementId,
-  });
-
-  api(getState).post(`/api/v1/announcements/${announcementId}/dismiss`);
-};
-
 export const addReaction = (announcementId, name) => (dispatch, getState) => {
   dispatch(addReactionRequest(announcementId, name));
 
@@ -131,3 +123,9 @@ export const updateReaction = reaction => ({
   type: ANNOUNCEMENTS_REACTION_UPDATE,
   reaction,
 });
+
+export function toggleShowAnnouncements() {
+  return dispatch => {
+    dispatch({ type: ANNOUNCEMENTS_TOGGLE_SHOW });
+  };
+}
diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.js b/app/javascript/mastodon/features/getting_started/components/announcements.js
index 975db0265..8ff1b0b4e 100644
--- a/app/javascript/mastodon/features/getting_started/components/announcements.js
+++ b/app/javascript/mastodon/features/getting_started/components/announcements.js
@@ -277,19 +277,13 @@ class Announcement extends ImmutablePureComponent {
   static propTypes = {
     announcement: ImmutablePropTypes.map.isRequired,
     emojiMap: ImmutablePropTypes.map.isRequired,
-    dismissAnnouncement: PropTypes.func.isRequired,
     addReaction: PropTypes.func.isRequired,
     removeReaction: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
   };
 
-  handleDismissClick = () => {
-    const { dismissAnnouncement, announcement } = this.props;
-    dismissAnnouncement(announcement.get('id'));
-  }
-
   render () {
-    const { announcement, intl } = this.props;
+    const { announcement } = this.props;
     const startsAt = announcement.get('starts_at') && new Date(announcement.get('starts_at'));
     const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at'));
     const now = new Date();
@@ -314,8 +308,6 @@ class Announcement extends ImmutablePureComponent {
           removeReaction={this.props.removeReaction}
           emojiMap={this.props.emojiMap}
         />
-
-        <IconButton title={intl.formatMessage(messages.close)} icon='times' className='announcements__item__dismiss-icon' onClick={this.handleDismissClick} />
       </div>
     );
   }
@@ -328,8 +320,6 @@ class Announcements extends ImmutablePureComponent {
   static propTypes = {
     announcements: ImmutablePropTypes.list,
     emojiMap: ImmutablePropTypes.map.isRequired,
-    fetchAnnouncements: PropTypes.func.isRequired,
-    dismissAnnouncement: PropTypes.func.isRequired,
     addReaction: PropTypes.func.isRequired,
     removeReaction: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
@@ -339,11 +329,6 @@ class Announcements extends ImmutablePureComponent {
     index: 0,
   };
 
-  componentDidMount () {
-    const { fetchAnnouncements } = this.props;
-    fetchAnnouncements();
-  }
-
   handleChangeIndex = index => {
     this.setState({ index: index % this.props.announcements.size });
   }
@@ -369,13 +354,12 @@ class Announcements extends ImmutablePureComponent {
         <img className='announcements__mastodon' alt='' draggable='false' src={mascot || elephantUIPlane} />
 
         <div className='announcements__container'>
-          <ReactSwipeableViews index={index} onChangeIndex={this.handleChangeIndex}>
+          <ReactSwipeableViews animateHeight index={index} onChangeIndex={this.handleChangeIndex}>
             {announcements.map(announcement => (
               <Announcement
                 key={announcement.get('id')}
                 announcement={announcement}
                 emojiMap={this.props.emojiMap}
-                dismissAnnouncement={this.props.dismissAnnouncement}
                 addReaction={this.props.addReaction}
                 removeReaction={this.props.removeReaction}
                 intl={intl}
diff --git a/app/javascript/mastodon/features/getting_started/containers/announcements_container.js b/app/javascript/mastodon/features/getting_started/containers/announcements_container.js
index b10d1d4ce..8c3fc2e6b 100644
--- a/app/javascript/mastodon/features/getting_started/containers/announcements_container.js
+++ b/app/javascript/mastodon/features/getting_started/containers/announcements_container.js
@@ -1,5 +1,5 @@
 import { connect } from 'react-redux';
-import { fetchAnnouncements, dismissAnnouncement, addReaction, removeReaction } from 'mastodon/actions/announcements';
+import { addReaction, removeReaction } from 'mastodon/actions/announcements';
 import Announcements from '../components/announcements';
 import { createSelector } from 'reselect';
 import { Map as ImmutableMap } from 'immutable';
@@ -12,8 +12,6 @@ const mapStateToProps = state => ({
 });
 
 const mapDispatchToProps = dispatch => ({
-  fetchAnnouncements: () => dispatch(fetchAnnouncements()),
-  dismissAnnouncement: id => dispatch(dismissAnnouncement(id)),
   addReaction: (id, name) => dispatch(addReaction(id, name)),
   removeReaction: (id, name) => dispatch(removeReaction(id, name)),
 });
diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js
index b7f9d5095..c7de8c9cb 100644
--- a/app/javascript/mastodon/features/home_timeline/index.js
+++ b/app/javascript/mastodon/features/home_timeline/index.js
@@ -9,15 +9,23 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ColumnSettingsContainer from './containers/column_settings_container';
 import { Link } from 'react-router-dom';
+import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/announcements';
 import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
+import classNames from 'classnames';
+import IconWithBadge from 'mastodon/components/icon_with_badge';
 
 const messages = defineMessages({
   title: { id: 'column.home', defaultMessage: 'Home' },
+  show_announcements: { id: 'home.show_announcements', defaultMessage: 'Show announcements' },
+  hide_announcements: { id: 'home.hide_announcements', defaultMessage: 'Hide announcements' },
 });
 
 const mapStateToProps = state => ({
   hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
   isPartial: state.getIn(['timelines', 'home', 'isPartial']),
+  hasAnnouncements: !state.getIn(['announcements', 'items']).isEmpty(),
+  unreadAnnouncements: state.getIn(['announcements', 'unread']).size,
+  showAnnouncements: state.getIn(['announcements', 'show']),
 });
 
 export default @connect(mapStateToProps)
@@ -32,6 +40,9 @@ class HomeTimeline extends React.PureComponent {
     isPartial: PropTypes.bool,
     columnId: PropTypes.string,
     multiColumn: PropTypes.bool,
+    hasAnnouncements: PropTypes.bool,
+    unreadAnnouncements: PropTypes.number,
+    showAnnouncements: PropTypes.bool,
   };
 
   handlePin = () => {
@@ -62,6 +73,7 @@ class HomeTimeline extends React.PureComponent {
   }
 
   componentDidMount () {
+    this.props.dispatch(fetchAnnouncements());
     this._checkIfReloadNeeded(false, this.props.isPartial);
   }
 
@@ -94,10 +106,31 @@ class HomeTimeline extends React.PureComponent {
     }
   }
 
+  handleToggleAnnouncementsClick = (e) => {
+    e.stopPropagation();
+    this.props.dispatch(toggleShowAnnouncements());
+  }
+
   render () {
-    const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props;
+    const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
     const pinned = !!columnId;
 
+    let announcementsButton = null;
+
+    if (hasAnnouncements) {
+      announcementsButton = (
+        <button
+          className={classNames('column-header__button', { 'active': showAnnouncements })}
+          title={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
+          aria-label={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
+          aria-pressed={showAnnouncements ? 'true' : 'false'}
+          onClick={this.handleToggleAnnouncementsClick}
+        >
+          <IconWithBadge id='bullhorn' count={unreadAnnouncements} />
+        </button>
+      );
+    }
+
     return (
       <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
         <ColumnHeader
@@ -109,13 +142,14 @@ class HomeTimeline extends React.PureComponent {
           onClick={this.handleHeaderClick}
           pinned={pinned}
           multiColumn={multiColumn}
+          extraButton={announcementsButton}
         >
           <ColumnSettingsContainer />
         </ColumnHeader>
 
+        {hasAnnouncements && showAnnouncements && <AnnouncementsContainer />}
+
         <StatusListContainer
-          prepend={<AnnouncementsContainer />}
-          alwaysPrepend
           trackScroll={!pinned}
           scrollKey={`home_timeline-${columnId}`}
           onLoadMore={this.handleLoadMore}
diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json
index 967577f2e..b6bf0eb8a 100644
--- a/app/javascript/mastodon/locales/br.json
+++ b/app/javascript/mastodon/locales/br.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "Ouzhpenn pe lemel ag ar listennadoù",
   "account.badges.bot": "Robot",
-  "account.badges.group": "Group",
+  "account.badges.group": "Strollad",
   "account.block": "Stankañ @{name}",
   "account.block_domain": "Kuzh kement tra a {domain}",
   "account.blocked": "Stanket",
@@ -35,17 +35,17 @@
   "account.show_reblogs": "Diskouez toudoù a @{name}",
   "account.unblock": "Distankañ @{name}",
   "account.unblock_domain": "Diguzh {domain}",
-  "account.unendorse": "Don't feature on profile",
+  "account.unendorse": "Paouez da lakaat war-wel war ar profil",
   "account.unfollow": "Diheuliañ",
   "account.unmute": "Diguzhat @{name}",
   "account.unmute_notifications": "Diguzhat kemennoù a @{name}",
-  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.message": "Klaskit en-dro a-benn {retry_time, time, medium}.",
   "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "Ur fazi dic'hortozet zo degouezhet.",
   "alert.unexpected.title": "C'hem !",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Kemenn",
   "autosuggest_hashtag.per_week": "{count} bep sizhun",
-  "boost_modal.combo": "You can press {combo} to skip this next time",
+  "boost_modal.combo": "Ar wezh kentañ e c'halliot gwaskañ war {combo} evit tremen hebiou",
   "bundle_column_error.body": "Something went wrong while loading this component.",
   "bundle_column_error.retry": "Klask endro",
   "bundle_column_error.title": "Fazi rouedad",
@@ -53,12 +53,12 @@
   "bundle_modal_error.message": "Something went wrong while loading this component.",
   "bundle_modal_error.retry": "Klask endro",
   "column.blocks": "Implijour·ezed·ion stanket",
-  "column.bookmarks": "Bookmarks",
+  "column.bookmarks": "Sinedoù",
   "column.community": "Red-amzer lec'hel",
   "column.direct": "Kemennadoù prevez",
   "column.directory": "Mont a-dreuz ar profiloù",
   "column.domain_blocks": "Domani kuzhet",
-  "column.favourites": "Favourites",
+  "column.favourites": "Ar re vuiañ-karet",
   "column.follow_requests": "Pedadoù heuliañ",
   "column.home": "Degemer",
   "column.lists": "Listennoù",
@@ -93,28 +93,28 @@
   "compose_form.sensitive.marked": "Media is marked as sensitive",
   "compose_form.sensitive.unmarked": "Media is not marked as sensitive",
   "compose_form.spoiler.marked": "Text is hidden behind warning",
-  "compose_form.spoiler.unmarked": "Text is not hidden",
+  "compose_form.spoiler.unmarked": "N'eo ket kuzhet an destenn",
   "compose_form.spoiler_placeholder": "Write your warning here",
-  "confirmation_modal.cancel": "Cancel",
+  "confirmation_modal.cancel": "Nullañ",
   "confirmations.block.block_and_report": "Block & Report",
   "confirmations.block.confirm": "Block",
   "confirmations.block.message": "Are you sure you want to block {name}?",
-  "confirmations.delete.confirm": "Delete",
+  "confirmations.delete.confirm": "Dilemel",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
-  "confirmations.delete_list.confirm": "Delete",
-  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
-  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.delete_list.confirm": "Dilemel",
+  "confirmations.delete_list.message": "Ha sur eo hoc'h eus c'hoant da zilemel ar roll-mañ da vat ?",
+  "confirmations.domain_block.confirm": "Kuzhat an domani a-bezh",
   "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
-  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.confirm": "Digevreañ",
   "confirmations.logout.message": "Are you sure you want to log out?",
-  "confirmations.mute.confirm": "Mute",
+  "confirmations.mute.confirm": "Kuzhat",
   "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
-  "confirmations.mute.message": "Are you sure you want to mute {name}?",
-  "confirmations.redraft.confirm": "Delete & redraft",
+  "confirmations.mute.message": "Ha sur oc'h e fell deoc'h kuzhaat {name} ?",
+  "confirmations.redraft.confirm": "Diverkañ ha skrivañ en-dro",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
-  "confirmations.reply.confirm": "Reply",
+  "confirmations.reply.confirm": "Respont",
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
-  "confirmations.unfollow.confirm": "Unfollow",
+  "confirmations.unfollow.confirm": "Diheuliañ",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
   "conversation.delete": "Delete conversation",
   "conversation.mark_as_read": "Mark as read",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index 6516a4a80..65ec5750b 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -8,8 +8,8 @@
   "account.cancel_follow_request": "Anul·la la sol·licitud de seguiment",
   "account.direct": "Missatge directe @{name}",
   "account.domain_blocked": "Domini ocult",
-  "account.edit_profile": "Editar el perfil",
-  "account.endorse": "Recomanar en el teu perfil",
+  "account.edit_profile": "Edita el perfil",
+  "account.endorse": "Recomana en el teu perfil",
   "account.follow": "Segueix",
   "account.followers": "Seguidors",
   "account.followers.empty": "Encara ningú no segueix aquest usuari.",
@@ -29,13 +29,13 @@
   "account.never_active": "Mai",
   "account.posts": "Tuts",
   "account.posts_with_replies": "Tuts i respostes",
-  "account.report": "Informe @{name}",
+  "account.report": "Informar sobre @{name}",
   "account.requested": "Esperant aprovació. Clic per a cancel·lar la petició de seguiment",
   "account.share": "Comparteix el perfil de @{name}",
   "account.show_reblogs": "Mostra els impulsos de @{name}",
-  "account.unblock": "Desbloca @{name}",
+  "account.unblock": "Desbloqueja @{name}",
   "account.unblock_domain": "Mostra {domain}",
-  "account.unendorse": "No es mostren al perfil",
+  "account.unendorse": "No recomanar en el perfil",
   "account.unfollow": "Deixa de seguir",
   "account.unmute": "Treure silenci de @{name}",
   "account.unmute_notifications": "Activar notificacions de @{name}",
@@ -43,9 +43,9 @@
   "alert.rate_limited.title": "Límit de freqüència",
   "alert.unexpected.message": "S'ha produït un error inesperat.",
   "alert.unexpected.title": "Vaja!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Anunci",
   "autosuggest_hashtag.per_week": "{count} per setmana",
-  "boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop",
+  "boost_modal.combo": "Pots prémer {combo} per saltar-te això el proper cop",
   "bundle_column_error.body": "S'ha produït un error en carregar aquest component.",
   "bundle_column_error.retry": "Torna-ho a provar",
   "bundle_column_error.title": "Error de connexió",
diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json
index 091a73c85..8f3c99f8a 100644
--- a/app/javascript/mastodon/locales/co.json
+++ b/app/javascript/mastodon/locales/co.json
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "Ghjettu limitatu",
   "alert.unexpected.message": "Un prublemu inaspettatu hè accadutu.",
   "alert.unexpected.title": "Uups!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Annunziu",
   "autosuggest_hashtag.per_week": "{count} per settimana",
   "boost_modal.combo": "Pudete appughjà nant'à {combo} per saltà quessa a prussima volta",
   "bundle_column_error.body": "C'hè statu un prublemu caricandu st'elementu.",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 8cd2bb8a3..d6f490fcf 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -2320,6 +2320,10 @@
         "id": "status.bookmark"
       },
       {
+        "defaultMessage": "More",
+        "id": "status.more"
+      },
+      {
         "defaultMessage": "Mute @{name}",
         "id": "status.mute"
       },
diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json
index d9d2ef590..abb6f0d33 100644
--- a/app/javascript/mastodon/locales/el.json
+++ b/app/javascript/mastodon/locales/el.json
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "Περιορισμός συχνότητας",
   "alert.unexpected.message": "Προέκυψε απροσδόκητο σφάλμα.",
   "alert.unexpected.title": "Εεπ!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Ανακοίνωση",
   "autosuggest_hashtag.per_week": "{count} ανα εβδομάδα",
   "boost_modal.combo": "Μπορείς να πατήσεις {combo} για να το προσπεράσεις αυτό την επόμενη φορά",
   "bundle_column_error.body": "Κάτι πήγε στραβά ενώ φορτωνόταν αυτό το στοιχείο.",
diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json
index 70bcef133..f83e5a251 100644
--- a/app/javascript/mastodon/locales/es-AR.json
+++ b/app/javascript/mastodon/locales/es-AR.json
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "Tarifa limitada",
   "alert.unexpected.message": "Ocurrió un error.",
   "alert.unexpected.title": "¡Epa!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Anuncio",
   "autosuggest_hashtag.per_week": "{count} por semana",
   "boost_modal.combo": "Podés hacer clic en {combo} para saltar esto la próxima vez",
   "bundle_column_error.body": "Algo salió mal al cargar este componente.",
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index 31f190616..93484b412 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -40,10 +40,10 @@
   "account.unmute": "Dejar de silenciar a @{name}",
   "account.unmute_notifications": "Dejar de silenciar las notificaciones de @{name}",
   "alert.rate_limited.message": "Por favor reintente después de {retry_time, time, medium}.",
-  "alert.rate_limited.title": "Tasa limitada",
+  "alert.rate_limited.title": "Tarifa limitada",
   "alert.unexpected.message": "Hubo un error inesperado.",
   "alert.unexpected.title": "¡Ups!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Anuncio",
   "autosuggest_hashtag.per_week": "{count} por semana",
   "boost_modal.combo": "Puedes hacer clic en {combo} para saltar este aviso la próxima vez",
   "bundle_column_error.body": "Algo salió mal al cargar este componente.",
@@ -54,7 +54,7 @@
   "bundle_modal_error.retry": "Inténtalo de nuevo",
   "column.blocks": "Usuarios bloqueados",
   "column.bookmarks": "Marcadores",
-  "column.community": "Cronología local",
+  "column.community": "Línea de tiempo local",
   "column.direct": "Mensajes directos",
   "column.directory": "Buscar perfiles",
   "column.domain_blocks": "Dominios ocultados",
@@ -65,7 +65,7 @@
   "column.mutes": "Usuarios silenciados",
   "column.notifications": "Notificaciones",
   "column.pins": "Toots fijados",
-  "column.public": "Cronología federada",
+  "column.public": "Línea de tiempo federada",
   "column_back_button.label": "Atrás",
   "column_header.hide_settings": "Ocultar configuración",
   "column_header.moveLeft_settings": "Mover columna a la izquierda",
@@ -87,7 +87,7 @@
   "compose_form.poll.remove_option": "Eliminar esta opción",
   "compose_form.poll.switch_to_multiple": "Modificar encuesta para permitir múltiples opciones",
   "compose_form.poll.switch_to_single": "Modificar encuesta para permitir una única opción",
-  "compose_form.publish": "Ipotxa",
+  "compose_form.publish": "Tootear",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Marcar multimedia como sensible",
   "compose_form.sensitive.marked": "Material marcado como sensible",
@@ -104,7 +104,7 @@
   "confirmations.delete_list.confirm": "Eliminar",
   "confirmations.delete_list.message": "¿Seguro que quieres borrar esta lista permanentemente?",
   "confirmations.domain_block.confirm": "Ocultar dominio entero",
-  "confirmations.domain_block.message": "¿Seguro de que quieres bloquear al dominio {domain} entero? En general unos cuantos bloqueos y silenciados concretos es suficiente y preferible.\nNo podrás ver contenido de ese dominio en ninguna de las cronologías públicas o tus notificaciones. Tus seguidoras de ese dominio serán borradas.",
+  "confirmations.domain_block.message": "¿Seguro de que quieres bloquear al dominio {domain} entero? En general unos cuantos bloqueos y silenciados concretos es suficiente y preferible.",
   "confirmations.logout.confirm": "Cerrar sesión",
   "confirmations.logout.message": "¿Estás seguro de querer cerrar la sesión?",
   "confirmations.mute.confirm": "Silenciar",
@@ -143,23 +143,23 @@
   "empty_column.account_timeline": "¡No hay toots aquí!",
   "empty_column.account_unavailable": "Perfil no disponible",
   "empty_column.blocks": "Aún no has bloqueado a ningún usuario.",
-  "empty_column.bookmarked_statuses": "Aún no tienes ninguna barritada guardada como marcador. Cuando guardes una, se mostrará aquí.",
-  "empty_column.community": "La cronología local está vacía. ¡Escribe algo para empezar la fiesta!",
+  "empty_column.bookmarked_statuses": "Aún no tienes ningún toot guardado como marcador. Cuando guardes uno, se mostrará aquí.",
+  "empty_column.community": "La línea de tiempo local está vacía. ¡Escribe algo para empezar la fiesta!",
   "empty_column.direct": "Aún no tienes ningún mensaje directo. Cuando envíes o recibas uno, se mostrará aquí.",
   "empty_column.domain_blocks": "Todavía no hay dominios ocultos.",
   "empty_column.favourited_statuses": "Aún no tienes toots preferidos. Cuando marques uno como favorito, aparecerá aquí.",
   "empty_column.favourites": "Nadie ha marcado este toot como preferido. Cuando alguien lo haga, aparecerá aquí.",
   "empty_column.follow_requests": "No tienes ninguna petición de seguidor. Cuando recibas una, se mostrará aquí.",
-  "empty_column.hashtag": "No hay nada en esta etiqueta aún.",
+  "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 cronología pública",
+  "empty_column.home.public_timeline": "la línea de tiempo pública",
   "empty_column.list": "No hay nada en esta lista aún. Cuando miembros de esta lista publiquen nuevos estatus, estos aparecerán qui.",
   "empty_column.lists": "No tienes ninguna lista. cuando crees una, se mostrará aquí.",
   "empty_column.mutes": "Aún no has silenciado a ningún usuario.",
   "empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.",
   "empty_column.public": "¡No hay nada aquí! Escribe algo públicamente, o sigue usuarios de otras instancias manualmente para llenarlo",
   "error.unexpected_crash.explanation": "Debido a un error en nuestro código o a un problema de compatibilidad con el navegador, esta página no se ha podido mostrar correctamente.",
-  "error.unexpected_crash.next_steps": "Intenta actualizar la página. Si eso no ayuda, es posible que puedas usar Mastodonte a través de otro navegador o aplicación nativa.",
+  "error.unexpected_crash.next_steps": "Intenta actualizar la página. Si eso no ayuda, es posible que puedas usar Mastodon a través de otro navegador o aplicación nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Copiar el seguimiento de pila en el portapapeles",
   "errors.unexpected_crash.report_issue": "Informar de un problema/error",
   "follow_request.authorize": "Autorizar",
@@ -169,18 +169,18 @@
   "getting_started.documentation": "Documentación",
   "getting_started.heading": "Primeros pasos",
   "getting_started.invite": "Invitar usuarios",
-  "getting_started.open_source_notice": "Mastodonte es un Programa Libre y de Código Abierto - Plica/Foss -. Puedes contribuir o reportar errores en {github}.",
+  "getting_started.open_source_notice": "Mastodon es software libre. Puedes contribuir o reportar errores en {github}.",
   "getting_started.security": "Seguridad",
   "getting_started.terms": "Términos de servicio",
   "hashtag.column_header.tag_mode.all": "y {additional}",
   "hashtag.column_header.tag_mode.any": "o {additional}",
   "hashtag.column_header.tag_mode.none": "sin {additional}",
   "hashtag.column_settings.select.no_options_message": "No se encontraron sugerencias",
-  "hashtag.column_settings.select.placeholder": "Introduzca etiquetas…",
-  "hashtag.column_settings.tag_mode.all": "Todos estos",
+  "hashtag.column_settings.select.placeholder": "Introduzca hashtags…",
+  "hashtag.column_settings.tag_mode.all": "Cualquiera de estos",
   "hashtag.column_settings.tag_mode.any": "Cualquiera de estos",
   "hashtag.column_settings.tag_mode.none": "Ninguno de estos",
-  "hashtag.column_settings.tag_toggle": "Incluya etiquetas adicionales para esta columna",
+  "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
   "home.column_settings.basic": "Básico",
   "home.column_settings.show_reblogs": "Mostrar retoots",
   "home.column_settings.show_replies": "Mostrar respuestas",
@@ -193,7 +193,7 @@
   "introduction.federation.home.headline": "Inicio",
   "introduction.federation.home.text": "Los posts de personas que sigues aparecerán en tu cronología. ¡Puedes seguir a cualquiera en cualquier servidor!",
   "introduction.federation.local.headline": "Local",
-  "introduction.federation.local.text": "Los mensajes públicos de personas en el mismo servidor que aparecerán en la cronología local.",
+  "introduction.federation.local.text": "Los posts públicos de personas en el mismo servidor que aparecerán en la cronología local.",
   "introduction.interactions.action": "¡Terminar tutorial!",
   "introduction.interactions.favourite.headline": "Favorito",
   "introduction.interactions.favourite.text": "Puedes guardar un toot para más tarde, y hacer saber al autor que te gustó, dándole a favorito.",
@@ -215,12 +215,12 @@
   "keyboard_shortcuts.enter": "abrir estado",
   "keyboard_shortcuts.favourite": "añadir a favoritos",
   "keyboard_shortcuts.favourites": "abrir la lista de favoritos",
-  "keyboard_shortcuts.federated": "abrir la cronología federada",
+  "keyboard_shortcuts.federated": "abrir el timeline federado",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
-  "keyboard_shortcuts.home": "abrir la cronología propia",
+  "keyboard_shortcuts.home": "abrir el timeline propio",
   "keyboard_shortcuts.hotkey": "Tecla caliente",
   "keyboard_shortcuts.legend": "para mostrar esta leyenda",
-  "keyboard_shortcuts.local": "abrir la cronología local",
+  "keyboard_shortcuts.local": "abrir el timeline local",
   "keyboard_shortcuts.mention": "para mencionar al autor",
   "keyboard_shortcuts.muted": "abrir la lista de usuarios silenciados",
   "keyboard_shortcuts.my_profile": "abrir tu perfil",
@@ -259,7 +259,7 @@
   "navigation_bar.apps": "Aplicaciones móviles",
   "navigation_bar.blocks": "Usuarios bloqueados",
   "navigation_bar.bookmarks": "Marcadores",
-  "navigation_bar.community_timeline": "Cronología local",
+  "navigation_bar.community_timeline": "Historia local",
   "navigation_bar.compose": "Escribir un nuevo toot",
   "navigation_bar.direct": "Mensajes directos",
   "navigation_bar.discover": "Descubrir",
@@ -277,7 +277,7 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Toots fijados",
   "navigation_bar.preferences": "Preferencias",
-  "navigation_bar.public_timeline": "Cronología federada",
+  "navigation_bar.public_timeline": "Historia federada",
   "navigation_bar.security": "Seguridad",
   "notification.favourite": "{name} marcó tu estado como favorito",
   "notification.follow": "{name} te empezó a seguir",
@@ -321,9 +321,9 @@
   "privacy.direct.short": "Directo",
   "privacy.private.long": "Sólo mostrar a seguidores",
   "privacy.private.short": "Privado",
-  "privacy.public.long": "Mostrar en la cronología federada",
+  "privacy.public.long": "Mostrar en la historia federada",
   "privacy.public.short": "Público",
-  "privacy.unlisted.long": "No mostrar en la cronología federada",
+  "privacy.unlisted.long": "No mostrar en la historia federada",
   "privacy.unlisted.short": "No listado",
   "refresh": "Actualizar",
   "regeneration_indicator.label": "Cargando…",
@@ -345,12 +345,12 @@
   "search_popout.tips.full_text": "Búsquedas de texto recuperan posts que has escrito, marcado como favoritos, retooteado o en los que has sido mencionado, así como usuarios, nombres y hashtags.",
   "search_popout.tips.hashtag": "etiqueta",
   "search_popout.tips.status": "estado",
-  "search_popout.tips.text": "El texto simple devuelve correspondencias de nombre, persona usuaria y etiqueta",
+  "search_popout.tips.text": "El texto simple devuelve correspondencias de nombre, usuario y hashtag",
   "search_popout.tips.user": "usuario",
   "search_results.accounts": "Gente",
   "search_results.hashtags": "Etiquetas",
   "search_results.statuses": "Toots",
-  "search_results.statuses_fts_disabled": "Buscar bramidos por su contenido no está disponible en este servidor de Mastodonte.",
+  "search_results.statuses_fts_disabled": "Buscar toots por su contenido no está disponible en este servidor de Mastodon.",
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "status.admin_account": "Abrir interfaz de moderación para @{name}",
   "status.admin_status": "Abrir este estado en la interfaz de moderación",
@@ -396,7 +396,7 @@
   "status.unpin": "Dejar de fijar",
   "suggestions.dismiss": "Descartar sugerencia",
   "suggestions.header": "Es posible que te interese…",
-  "tabs_bar.federated_timeline": "Federada",
+  "tabs_bar.federated_timeline": "Federado",
   "tabs_bar.home": "Inicio",
   "tabs_bar.local_timeline": "Local",
   "tabs_bar.notifications": "Notificaciones",
@@ -408,7 +408,7 @@
   "time_remaining.seconds": "{number, plural, one {# segundo restante} other {# segundos restantes}}",
   "trends.count_by_accounts": "{count} {rawCount, plural, one {persona} other {personas}} hablando",
   "trends.trending_now": "Tendencia ahora",
-  "ui.beforeunload": "Tu borrador se perderá si sales de Mastodonte.",
+  "ui.beforeunload": "Tu borrador se perderá si sales de Mastodon.",
   "upload_area.title": "Arrastra y suelta para subir",
   "upload_button.label": "Subir multimedia (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Límite de subida de archivos excedido.",
diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json
index e9fbcedac..c89cb151f 100644
--- a/app/javascript/mastodon/locales/eu.json
+++ b/app/javascript/mastodon/locales/eu.json
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "Abiadura mugatua",
   "alert.unexpected.message": "Ustekabeko errore bat gertatu da.",
   "alert.unexpected.title": "Ene!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Iragarpena",
   "autosuggest_hashtag.per_week": "{count} asteko",
   "boost_modal.combo": "{combo} sakatu dezakezu hurrengoan hau saltatzeko",
   "bundle_column_error.body": "Zerbait okerra gertatu da osagai hau kargatzean.",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index c2a8eb0de..3bb2f0ee8 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -1,48 +1,48 @@
 {
-  "account.add_or_remove_from_list": "افزودن یا برداشتن از فهرست",
+  "account.add_or_remove_from_list": "افزودن یا برداشتن از فهرست‌ها",
   "account.badges.bot": "ربات",
   "account.badges.group": "گروه",
   "account.block": "مسدودسازی @{name}",
-  "account.block_domain": "پنهان‌سازی همه چیز از سرور {domain}",
+  "account.block_domain": "نهفتن همه چیز از {domain}",
   "account.blocked": "مسدود",
   "account.cancel_follow_request": "لغو درخواست پیگیری",
   "account.direct": "پیام خصوصی به @{name}",
-  "account.domain_blocked": "دامنه نهفته",
+  "account.domain_blocked": "دامنهٔ نهفته",
   "account.edit_profile": "ویرایش نمایه",
   "account.endorse": "معرّفی در نمایه",
-  "account.follow": "پیگیری",
+  "account.follow": "پی بگیرید",
   "account.followers": "پی‌گیران",
   "account.followers.empty": "هنوز کسی پیگیر این کاربر نیست.",
   "account.follows": "پی می‌گیرد",
   "account.follows.empty": "این کاربر هنوز پیگیر کسی نیست.",
-  "account.follows_you": "پیگیرتان",
-  "account.hide_reblogs": "نهفتن تقویت‌های @{name}",
+  "account.follows_you": "پیگیر شماست",
+  "account.hide_reblogs": "نهفتن بازبوق‌های @{name}",
   "account.last_status": "آخرین فعالیت",
   "account.link_verified_on": "مالکیت این پیوند در {date} بررسی شد",
   "account.locked_info": "این حساب خصوصی است. صاحبش تصمیم می‌گیرد که چه کسی بتواند پیگیرش باشد.",
   "account.media": "رسانه",
-  "account.mention": "اشاره به @{name}",
+  "account.mention": "نام‌بردن از @{name}",
   "account.moved_to": "{name} منتقل شده به:",
   "account.mute": "خموشی @{name}",
-  "account.mute_notifications": "خموشی آگاهی‌ها از @{name}",
+  "account.mute_notifications": "خموشی اعلان‌ها از @{name}",
   "account.muted": "خموش",
   "account.never_active": "هرگز",
-  "account.posts": "بوق‌ها",
-  "account.posts_with_replies": "بوق‌ها و پاسخ‌ها",
+  "account.posts": "نوشته‌ها",
+  "account.posts_with_replies": "نوشته‌ها و پاسخ‌ها",
   "account.report": "گزارش @{name}",
-  "account.requested": "منتظر پذیرش. کلیک برای لغو درخواست پی‌گیری",
+  "account.requested": "منتظر پذیرش. برای لغو درخواست پی‌گیری کلیک کنید",
   "account.share": "هم‌رسانی نمایهٔ @{name}",
-  "account.show_reblogs": "نمایش تقویت‌های @{name}",
+  "account.show_reblogs": "نمایش بازبوق‌های @{name}",
   "account.unblock": "رفع انسداد @{name}",
   "account.unblock_domain": "رفع نهفتن {domain}",
   "account.unendorse": "معرّفی نکردن در نمایه",
-  "account.unfollow": "ناپی‌گیری",
+  "account.unfollow": "پایان پیگیری",
   "account.unmute": "رفع خموشی @{name}",
-  "account.unmute_notifications": "رفع خموشی آگاهی‌ها از @{name}",
+  "account.unmute_notifications": "رفع خموشی اعلان‌ها از @{name}",
   "alert.rate_limited.message": "لطفاً پس از {retry_time, time, medium} دوباره بیازمایید.",
-  "alert.rate_limited.title": "محدود شده",
+  "alert.rate_limited.title": "محدودیت تعداد",
   "alert.unexpected.message": "خطایی غیرمنتظره رخ داد.",
-  "alert.unexpected.title": "وای!",
+  "alert.unexpected.title": "ای وای!",
   "announcement.announcement": "Announcement",
   "autosuggest_hashtag.per_week": "{count} در هفته",
   "boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید",
@@ -50,37 +50,37 @@
   "bundle_column_error.retry": "تلاش دوباره",
   "bundle_column_error.title": "خطای شبکه",
   "bundle_modal_error.close": "بستن",
-  "bundle_modal_error.message": "هنگام بار کردن این بخش خطایی رخ داد.",
+  "bundle_modal_error.message": "هنگام بازکردن این بخش خطایی رخ داد.",
   "bundle_modal_error.retry": "تلاش دوباره",
   "column.blocks": "کاربران مسدود",
   "column.bookmarks": "نشانک‌ها",
-  "column.community": "خط زمانی محلّی",
+  "column.community": "نوشته‌های محلی",
   "column.direct": "پیام‌های خصوصی",
   "column.directory": "مرور نمایه‌ها",
   "column.domain_blocks": "دامنه‌های نهفته",
-  "column.favourites": "برگزیده‌ها",
+  "column.favourites": "پسندیده‌ها",
   "column.follow_requests": "درخواست‌های پیگیری",
   "column.home": "خانه",
   "column.lists": "فهرست‌ها",
   "column.mutes": "کاربران خموش",
-  "column.notifications": "آگاهی‌ها",
-  "column.pins": "بوق‌های سنجاق‌شده",
-  "column.public": "خط‌زمانی عمومی",
+  "column.notifications": "اعلان‌ها",
+  "column.pins": "بوق‌های ثابت",
+  "column.public": "نوشته‌های همه‌جا",
   "column_back_button.label": "بازگشت",
   "column_header.hide_settings": "نهفتن تنظیمات",
   "column_header.moveLeft_settings": "انتقال ستون به راست",
   "column_header.moveRight_settings": "انتقال ستون به چپ",
-  "column_header.pin": "سنجاق",
+  "column_header.pin": "ثابت‌کردن",
   "column_header.show_settings": "نمایش تنظیمات",
-  "column_header.unpin": "برداشتن سنجاق",
+  "column_header.unpin": "رهاکردن",
   "column_subheading.settings": "تنظیمات",
   "community.column_settings.media_only": "فقط رسانه",
-  "compose_form.direct_message_warning": "این بوق تنها به کاربران اشاره‌شده فرستاده خواهد شد.",
-  "compose_form.direct_message_warning_learn_more": "بیش‌تر بدانید",
+  "compose_form.direct_message_warning": "این بوق تنها به کاربرانی که از آن‌ها نام برده شده فرستاده خواهد شد.",
+  "compose_form.direct_message_warning_learn_more": "بیشتر بدانید",
   "compose_form.hashtag_warning": "از آن‌جا که این بوق فهرست‌نشده است، در نتایج جست‌وجوی هشتگ‌ها پیدا نخواهد شد. تنها بوق‌های عمومی را می‌توان با جست‌وجوی هشتگ یافت.",
   "compose_form.lock_disclaimer": "حسابتان {locked} نیست. هر کسی می‌تواند پیگیرتان شده و فرسته‌های ویژهٔ پیگیرانتان را ببیند.",
   "compose_form.lock_disclaimer.lock": "قفل",
-  "compose_form.placeholder": "چی تو سرته؟",
+  "compose_form.placeholder": "تازه چه خبر؟",
   "compose_form.poll.add_option": "افزودن گزینه",
   "compose_form.poll.duration": "مدت نظرسنجی",
   "compose_form.poll.option_placeholder": "گزینهٔ {number}",
@@ -89,81 +89,81 @@
   "compose_form.poll.switch_to_single": "تبدیل به نظرسنجی تک‌گزینه‌ای",
   "compose_form.publish": "بوق",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.hide": "علامت‌گذاری رسانه به عنوان حساس",
+  "compose_form.sensitive.hide": "علامت‌گذاری به عنوان حساس",
   "compose_form.sensitive.marked": "رسانه به عنوان حساس علامت‌گذاری شده",
   "compose_form.sensitive.unmarked": "رسانه به عنوان حساس علامت‌گذاری نشده",
   "compose_form.spoiler.marked": "نوشته پشت هشدار پنهان است",
   "compose_form.spoiler.unmarked": "نوشته پنهان نیست",
   "compose_form.spoiler_placeholder": "هشدارتان را این‌جا بنویسید",
-  "confirmation_modal.cancel": "لغو",
-  "confirmations.block.block_and_report": "انسداد و گزارش",
-  "confirmations.block.confirm": "انسداد",
+  "confirmation_modal.cancel": "بی‌خیال",
+  "confirmations.block.block_and_report": "مسدودسازی و گزارش",
+  "confirmations.block.confirm": "مسدود کن",
   "confirmations.block.message": "مطمئنید که می‌خواهید {name} را مسدود کنید؟",
-  "confirmations.delete.confirm": "حذف",
-  "confirmations.delete.message": "مطمئنید می‌خواهید این وضعیت را حذف کنید؟",
-  "confirmations.delete_list.confirm": "حذف",
+  "confirmations.delete.confirm": "پاک کن",
+  "confirmations.delete.message": "آیا مطمئنید که می‌خواهید این بوق را پاک کنید؟",
+  "confirmations.delete_list.confirm": "پاک کن",
   "confirmations.delete_list.message": "مطمئنید می‌خواهید این فهرست را برای همیشه پاک کنید؟",
   "confirmations.domain_block.confirm": "نهفتن تمام دامنه",
-  "confirmations.domain_block.message": "آیا جدی جدی می‌خواهید تمام دامنهٔ {domain} را مسدود کنید؟ در بیش‌تر موارد انسداد یا خموش کردن چند حساب خاص کافی بوده و توصیه می‌شود. پس از این کار شما هیچ نوشته‌ای را از این دامنه در خط‌زمانی‌های عمومی یا آگاهی‌هایتان نخواهید دید. پیگیرانتان از این دامنه هم برداشته خواهند شد.",
+  "confirmations.domain_block.message": "آیا جدی جدی می‌خواهید تمام دامنهٔ {domain} را مسدود کنید؟ در بیشتر موارد مسدودسازی یا خموشیدن چند حساب خاص کافی است و توصیه می‌شود. پس از این کار شما هیچ نوشته‌ای را از این دامنه در فهرست نوشته‌های عمومی یا اعلان‌هایتان نخواهید دید. پیگیرانتان از این دامنه هم حذف خواهند شد.",
   "confirmations.logout.confirm": "خروج",
   "confirmations.logout.message": "مطمئنید می‌خواهید خارج شوید؟",
   "confirmations.mute.confirm": "خموشی",
-  "confirmations.mute.explanation": "این کار فرسته‌هایشان و فرسته‌هایی که به آنان اشاره می‌کند را پنهان می‌کند، ولی همچنان اجازه دارند فرسته‌هایتان را دیده و دنبالتان کنند.",
-  "confirmations.mute.message": "مطمئنید می‌خواهید {name} را خموش کنید؟",
-  "confirmations.redraft.confirm": "حذف و بازنویسی",
-  "confirmations.redraft.message": "مطمئنید می‌خواهید این وضعیت را حذف کرده و از نو بنویسید؟ با این کار تقویت‌ها و برگزیدن‌هایش از دست رفته و پاسخ‌ها به فرستهٔ اصلی یتیم می‌شوند.",
+  "confirmations.mute.explanation": "این کار فرسته‌های آن‌ها و فرسته‌هایی را که از آن‌ها نام برده پنهان می‌کند، ولی آن‌ها همچنان اجازه دارند فرسته‌های شما را ببینند و شما را پی بگیرند.",
+  "confirmations.mute.message": "مطمئنید می‌خواهید {name} را بخموشید؟",
+  "confirmations.redraft.confirm": "پاک‌کردن و بازنویسی",
+  "confirmations.redraft.message": "مطمئنید که می‌خواهید این بوق را پاک کنید و از نو بنویسید؟ با این کار بازبوق‌ها و پسندهای آن از دست می‌رود و پاسخ‌ها به آن بی‌مرجع می‌شود.",
   "confirmations.reply.confirm": "پاسخ",
-  "confirmations.reply.message": "اگر الان پاسخ دهید، پیامی که در حال نوشتنش بودید پاک خواهد شد. می‌خواهید ادامه دهید؟",
-  "confirmations.unfollow.confirm": "ناپی‌گیری",
-  "confirmations.unfollow.message": "مطمئنید می‌خواهید پیگیری {name} را پایان دهید؟",
-  "conversation.delete": "حذف گفت‌وگو",
+  "confirmations.reply.message": "اگر الان پاسخ دهید، چیزی که در حال نوشتنش بودید پاک خواهد شد. می‌خواهید ادامه دهید؟",
+  "confirmations.unfollow.confirm": "پایان پیگیری",
+  "confirmations.unfollow.message": "مطمئنید که می‌خواهید به پیگیری از {name} پایان دهید؟",
+  "conversation.delete": "حذف گفتگو",
   "conversation.mark_as_read": "علامت‌گذاری به عنوان خوانده شده",
-  "conversation.open": "دیدن گفت‌وگو",
+  "conversation.open": "دیدن گفتگو",
   "conversation.with": "با {names}",
   "directory.federated": "از کارسازهای شناخته‌شده",
-  "directory.local": "فقط از {domain}",
+  "directory.local": "تنها از {domain}",
   "directory.new_arrivals": "تازه‌واردان",
   "directory.recently_active": "کاربران فعال اخیر",
-  "embed.instructions": "با رونوشت از کد زیر، این وضعیت را روی پایگاهتان جاگذاری کنید.",
+  "embed.instructions": "برای جاگذاری این بوق در سایت خودتان، کد زیر را کپی کنید.",
   "embed.preview": "این گونه دیده خواهد شد:",
   "emoji_button.activity": "فعالیت",
   "emoji_button.custom": "سفارشی",
   "emoji_button.flags": "پرچم‌ها",
   "emoji_button.food": "غذا و نوشیدنی",
-  "emoji_button.label": "درج اموجی",
+  "emoji_button.label": "افزودن شکلک",
   "emoji_button.nature": "طبیعت",
-  "emoji_button.not_found": "اموجی‌ای وجود ندارد!! ‪ (╯°□°)╯︵ ┻━┻‬",
+  "emoji_button.not_found": "این‌جا شکلکی نیست!! (╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "اشیا",
   "emoji_button.people": "مردم",
   "emoji_button.recent": "پراستفاده",
-  "emoji_button.search": "جست‌وجو…",
-  "emoji_button.search_results": "نتایج جست‌وجو",
+  "emoji_button.search": "جستجو...",
+  "emoji_button.search_results": "نتایج جستجو",
   "emoji_button.symbols": "نمادها",
   "emoji_button.travel": "سفر و مکان",
   "empty_column.account_timeline": "هیچ بوقی این‌جا نیست!",
   "empty_column.account_unavailable": "نمایهٔ ناموجود",
   "empty_column.blocks": "هنوز کسی را مسدود نکرده‌اید.",
   "empty_column.bookmarked_statuses": "هنوز هیچ بوق نشان‌شده‌ای ندارید. وقتی بوقی را نشان‌کنید، این‌جا دیده خواهد شد.",
-  "empty_column.community": "خط‌زمانی محلّی خالی است. چیزی به صورت عمومی نوشته تا چرخش بچرخد!",
-  "empty_column.direct": "هنوز هیچ پیغام مستقیمی ندارید. هروقت چنین پیغامی بگیرید یا بفرستید این‌جا نمایش خواهد یافت.",
+  "empty_column.community": "فهرست نوشته‌های محلی خالی است. چیزی بنویسید تا چرخش بچرخد!",
+  "empty_column.direct": "هنوز هیچ پیام مستقیمی ندارید. هروقت چنین پیامی بگیرید یا بفرستید این‌جا نمایش خواهد یافت.",
   "empty_column.domain_blocks": "هنوز هیچ دامنه‌ای پنهان نشده است.",
-  "empty_column.favourited_statuses": "هنوز هیچ بوق برگزیده‌ای ندارید. وقتی بوقی را برگزینید، این‌جا دیده خواهد شد.",
-  "empty_column.favourites": "هنوز کسی این بوق را برنگزیده. وقتی کسی این کار را بکند، این‌جا دیده خواهد شد.",
-  "empty_column.follow_requests": "هنوز هیچ درخواست پیگیری‌ای ندارید. وقتی چنین درخواستی بگیرید، این‌جا دیده خواهد شد.",
+  "empty_column.favourited_statuses": "شما هنوز هیچ بوقی را نپسندیده‌اید. وقتی بوقی را بپسندید، این‌جا نمایش خواهد یافت.",
+  "empty_column.favourites": "هنوز هیچ کسی این بوق را نپسندیده است. وقتی کسی آن را بپسندد، نامش این‌جا نمایش خواهد یافت.",
+  "empty_column.follow_requests": "شما هنوز هیچ درخواست پیگیری‌ای ندارید. وقتی چنین درخواستی بگیرید، این‌جا نمایش خواهد یافت.",
   "empty_column.hashtag": "هنوز هیچ چیزی در این برچسب نیست.",
-  "empty_column.home": "خط‌زمانی خانگیتان خالیست! برای شروع و دیدن دیگر کاربران، از جست‌وجو استفاده کرده یا {public} را ببینید.",
-  "empty_column.home.public_timeline": "خط‌زمانی عمومی",
-  "empty_column.list": "هنوز چیزی در این فهرست وجود ندارد. هنگامی که اعضای این فهرست وضعیت جدیدی بفرستند، این‌جا ظاهر خواهد شد.",
+  "empty_column.home": "فهرست خانگی شما خالی است! {public} را ببینید یا چیزی را جستجو کنید تا کاربران دیگر را ببینید.",
+  "empty_column.home.public_timeline": "فهرست نوشته‌های همه‌جا",
+  "empty_column.list": "در این فهرست هنوز چیزی نیست. وقتی اعضای این فهرست چیزی بفرستند، این‌جا ظاهر خواهد شد.",
   "empty_column.lists": "هنوز هیچ فهرستی ندارید. هنگامی که فهرستی بسازید، این‌جا دیده خواهد شد.",
   "empty_column.mutes": "هنوز هیچ کاربری را خموش نکرده‌اید.",
-  "empty_column.notifications": "هنوز هیچ آگاهی‌ای ندارید. به دیگران واکنش نشان داده تا گفت‌وگو آغاز شود.",
-  "empty_column.public": "هیچ‌چیز این‌جا نیست! پرای پر کردنش چیزی به صورت عمومی نوشته یا کاربران دیگر کارسازها را پی بگیرید",
-  "error.unexpected_crash.explanation": "به خاطر مشکلی در کدمان یا یک ناسازگاری مرورگر، این صفحه نتوانست به درستی نمایش یابد.",
-  "error.unexpected_crash.next_steps": "تازه‌سازی صفحه را بیازمایید. اگر کمکی نکرد، شاید بتوانید از طریق مرورگری دیگر یا کارهٔ بومی از ماستودون استفاده کنید.",
+  "empty_column.notifications": "هنوز هیچ اعلانی ندارید. به دیگران واکنش نشان دهید تا گفتگو آغاز شود.",
+  "empty_column.public": "این‌جا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران کارسازهای دیگر را پی بگیرید تا این‌جا پر شود",
+  "error.unexpected_crash.explanation": "به خاطر اشکالی در کدهای ما یا ناسازگاری با مرورگر شما، این صفحه به درستی نمایش نیافت.",
+  "error.unexpected_crash.next_steps": "لطفاً صفحه را دوباره باز کنید. اگر کمکی نکرد، شاید همچنان بتوانید با ماستدون از راه یک مرورگر دیگر یا با یکی از اپ‌های آن کار کنید.",
   "errors.unexpected_crash.copy_stacktrace": "رونوشت از جزئیات اشکال",
   "errors.unexpected_crash.report_issue": "گزارش مشکل",
-  "follow_request.authorize": "اجازه دادن",
-  "follow_request.reject": "رد کردن",
+  "follow_request.authorize": "اجازه دهید",
+  "follow_request.reject": "رد کنید",
   "getting_started.developers": "توسعه‌دهندگان",
   "getting_started.directory": "فهرست گزیدهٔ کاربران",
   "getting_started.documentation": "مستندات",
@@ -182,65 +182,65 @@
   "hashtag.column_settings.tag_mode.none": "هیچ‌کدام از این‌ها",
   "hashtag.column_settings.tag_toggle": "افزودن برچسب‌هایی بیشتر به این ستون",
   "home.column_settings.basic": "پایه‌ای",
-  "home.column_settings.show_reblogs": "نمایش تقویت‌ها",
+  "home.column_settings.show_reblogs": "نمایش بازبوق‌ها",
   "home.column_settings.show_replies": "نمایش پاسخ‌ها",
   "intervals.full.days": "{number, plural, one {# روز} other {# روز}}",
   "intervals.full.hours": "{number, plural, one {# ساعت} other {# ساعت}}",
   "intervals.full.minutes": "{number, plural, one {# دقیقه} other {# دقیقه}}",
   "introduction.federation.action": "بعدی",
   "introduction.federation.federated.headline": "همگانی",
-  "introduction.federation.federated.text": "نوشته‌های عمومی دیگر کارسازها در خط‌زمانی همگانی طاهر خواهند شد.",
+  "introduction.federation.federated.text": "نوشته‌های عمومی کارسازهای دیگر در این فهرست نمایش می‌یابند.",
   "introduction.federation.home.headline": "خانه",
-  "introduction.federation.home.text": "فرسته‌ها از افرادی که دنبالشان می‌کنید در خوراک خانه‌تان ظاهر خواهند شد. می‌توانید هر کسی را روی هر کارسازی پی‌بگیرید!",
+  "introduction.federation.home.text": "فرسته‌های کسانی که شما آن‌ها را پی می‌گیرید این‌جا نمایش می‌یابند. شما می‌توانید هر کسی را روی هر کارسازی پی بگیرید!",
   "introduction.federation.local.headline": "محلّی",
-  "introduction.federation.local.text": "نوشته‌های عمومی افرادی که روی همان کارساز خودتان هستند در خط زمانی محلّی ظاهر خواهند شد.",
+  "introduction.federation.local.text": "فرسته‌های عمومی کسانی که روی کارساز شما هستند در فهرست نوشته‌های محلی نمایش می‌یابند.",
   "introduction.interactions.action": "پایان خودآموز!",
-  "introduction.interactions.favourite.headline": "برگزیدن",
-  "introduction.interactions.favourite.text": "می‌توانید بوقی را برای بعد ذخیره کنید و با برگزیدنش، بگذارید نویسنده بداند که پسندیدیش.",
-  "introduction.interactions.reblog.headline": "تقویت",
-  "introduction.interactions.reblog.text": "با تقویت بوق‌های دیگران، می‌توانید آن‌ها را با پی‌گیرانتان هم‌رسانی کنید.",
+  "introduction.interactions.favourite.headline": "پسندیدن",
+  "introduction.interactions.favourite.text": "با پسندیدن یک بوق، شما آن را برای آینده ذخیره می‌کنید و به نویسنده می‌گویید که از بوقش خوشتان آمده.",
+  "introduction.interactions.reblog.headline": "بازبوقیدن",
+  "introduction.interactions.reblog.text": "اگر بخواهید نوشته‌ای را با پیگیران خودتان به اشتراک بگذارید، آن را بازمی‌بوقید.",
   "introduction.interactions.reply.headline": "پاسخ",
-  "introduction.interactions.reply.text": "می‌توانید به بوق‌های خودتان و دیگران پاسخ دهید، تا در یک گفت‌وگو به هم زنجیر شوند.",
+  "introduction.interactions.reply.text": "می‌توانید به بوق‌های خودتان و دیگران پاسخ دهید، تا در یک گفتگو به هم زنجیر شوند.",
   "introduction.welcome.action": "بزن بریم!",
   "introduction.welcome.headline": "نخستین گام‌ها",
   "introduction.welcome.text": "به دنیای شبکه‌های اجتماعی غیرمتمرکز خوش آمدید! به زودی می‌توانید نوشته‌هایتان را منتشر کرده و با دوستانتان روی دامنهٔ وسیعی از کارسازها حرف بزنید. ولی این کارساز، {domain}، با بقیه فرق دارد، چرا که میزبان نمایهٔ شماست، پس نامش را به خاطر بسپارید.",
   "keyboard_shortcuts.back": "برای بازگشت",
   "keyboard_shortcuts.blocked": "برای گشودن فهرست کاربران خموش",
-  "keyboard_shortcuts.boost": "برای تقویت",
-  "keyboard_shortcuts.column": "برای تمرکز روی وضعیتی در یکی از ستون‌ها",
+  "keyboard_shortcuts.boost": "برای بازبوقیدن",
+  "keyboard_shortcuts.column": "برای تمرکز روی یک بوق در یکی از ستون‌ها",
   "keyboard_shortcuts.compose": "برای تمرکز روی محیط نوشتن",
   "keyboard_shortcuts.description": "توضیح",
-  "keyboard_shortcuts.direct": "برای گشودن ستون پیام‌های خصوصی",
+  "keyboard_shortcuts.direct": "برای گشودن ستون پیغام‌های مستقیم",
   "keyboard_shortcuts.down": "برای پایین رفتن در فهرست",
-  "keyboard_shortcuts.enter": "برای گشودن وضعیت",
-  "keyboard_shortcuts.favourite": "برای برگزیدن",
-  "keyboard_shortcuts.favourites": "برای گشودن فهرست برگزیده‌ها",
-  "keyboard_shortcuts.federated": "برای گشودن خط‌زمانی همگانی",
+  "keyboard_shortcuts.enter": "برای گشودن نوشته",
+  "keyboard_shortcuts.favourite": "برای پسندیدن",
+  "keyboard_shortcuts.favourites": "برای گشودن فهرست پسندیده‌ها",
+  "keyboard_shortcuts.federated": "برای گشودن فهرست نوشته‌های همه‌جا",
   "keyboard_shortcuts.heading": "میان‌برهای صفحه‌کلید",
-  "keyboard_shortcuts.home": "برای گشودن خط‌زمانی خانه",
+  "keyboard_shortcuts.home": "برای گشودن ستون اصلی پیگیری‌ها",
   "keyboard_shortcuts.hotkey": "میان‌بر",
   "keyboard_shortcuts.legend": "برای نمایش این راهنما",
-  "keyboard_shortcuts.local": "برای گشودن خط‌زمانی محلّی",
-  "keyboard_shortcuts.mention": "برای اشاره به نویسنده",
+  "keyboard_shortcuts.local": "برای گشودن فهرست نوشته‌های محلی",
+  "keyboard_shortcuts.mention": "برای نام‌بردن از نویسنده",
   "keyboard_shortcuts.muted": "برای گشودن فهرست کاربران خموش",
   "keyboard_shortcuts.my_profile": "برای گشودن نمایه‌تان",
-  "keyboard_shortcuts.notifications": "برای گشودن ستون آگاهی‌ها",
+  "keyboard_shortcuts.notifications": "برای گشودن ستون اعلان‌ها",
   "keyboard_shortcuts.open_media": "برای باز کردن رسانه",
-  "keyboard_shortcuts.pinned": "برای گشودن فهرست بوق‌های سنجاق‌شده",
+  "keyboard_shortcuts.pinned": "برای گشودن فهرست بوق‌های ثابت",
   "keyboard_shortcuts.profile": "برای گشودن نمایهٔ نویسنده",
   "keyboard_shortcuts.reply": "برای پاسخ",
   "keyboard_shortcuts.requests": "برای گشودن فهرست درخواست‌های پیگیری",
-  "keyboard_shortcuts.search": "برای تمرکز روی جست‌وجو",
+  "keyboard_shortcuts.search": "برای تمرکز روی جستجو",
   "keyboard_shortcuts.start": "برای گشودن ستون «آغاز کنید»",
   "keyboard_shortcuts.toggle_hidden": "برای نمایش/نهفتن نوشتهٔ پشت هشدار محتوا",
   "keyboard_shortcuts.toggle_sensitivity": "برای نمایش/نهفتن رسانه",
   "keyboard_shortcuts.toot": "برای آغاز یک بوق تازه",
-  "keyboard_shortcuts.unfocus": "برای برداشتن تمرکز از نوشتن/جست‌وجو",
+  "keyboard_shortcuts.unfocus": "برای برداشتن تمرکز از نوشتن/جستجو",
   "keyboard_shortcuts.up": "برای بالا رفتن در فهرست",
   "lightbox.close": "بستن",
   "lightbox.next": "بعدی",
-  "lightbox.previous": "پیشین",
-  "lightbox.view_context": "نمایش متن",
+  "lightbox.previous": "قبلی",
+  "lightbox.view_context": "نمایش گفتگو",
   "lists.account.add": "افزودن به فهرست",
   "lists.account.remove": "برداشتن از فهرست",
   "lists.delete": "حذف فهرست",
@@ -248,100 +248,100 @@
   "lists.edit.submit": "تغییر عنوان",
   "lists.new.create": "افزودن فهرست",
   "lists.new.title_placeholder": "عنوان فهرست تازه",
-  "lists.search": "جست‌وجو بین افرادی که پی می‌گیرید",
-  "lists.subheading": "فهرست‌هایتان",
+  "lists.search": "بین کسانی که پی می‌گیرید بگردید",
+  "lists.subheading": "فهرست‌های شما",
   "load_pending": "{count, plural, one {# مورد تازه} other {# مورد تازه}}",
-  "loading_indicator.label": "در حال بار کردن…",
-  "media_gallery.toggle_visible": "تغییر وضعیت نمایانی",
+  "loading_indicator.label": "بارگیری...",
+  "media_gallery.toggle_visible": "تغییر پیدایی",
   "missing_indicator.label": "پیدا نشد",
   "missing_indicator.sublabel": "این منبع پیدا نشد",
-  "mute_modal.hide_notifications": "نهفتن آگاهی‌ها از این کاربر؟",
-  "navigation_bar.apps": "کاره‌های همراه",
-  "navigation_bar.blocks": "کاربران مسدود",
+  "mute_modal.hide_notifications": "اعلان‌های این کاربر پنهان شود؟",
+  "navigation_bar.apps": "اپ‌های موبایل",
+  "navigation_bar.blocks": "کاربران مسدودشده",
   "navigation_bar.bookmarks": "نشانک‌ها",
-  "navigation_bar.community_timeline": "خط‌زمانی محلّی",
+  "navigation_bar.community_timeline": "نوشته‌های محلی",
   "navigation_bar.compose": "نوشتن بوق تازه",
-  "navigation_bar.direct": "پیام‌های خصوصی",
-  "navigation_bar.discover": "کشف",
+  "navigation_bar.direct": "پیام‌های مستقیم",
+  "navigation_bar.discover": "گشت و گذار",
   "navigation_bar.domain_blocks": "دامنه‌های نهفته",
   "navigation_bar.edit_profile": "ویرایش نمایه",
-  "navigation_bar.favourites": "برگزیده‌ها",
+  "navigation_bar.favourites": "پسندیده‌ها",
   "navigation_bar.filters": "واژگان خموش",
   "navigation_bar.follow_requests": "درخواست‌های پیگیری",
-  "navigation_bar.follows_and_followers": "پی‌گیری‌ها و پی‌گیران",
+  "navigation_bar.follows_and_followers": "پیگیری‌ها و پیگیران",
   "navigation_bar.info": "دربارهٔ این کارساز",
   "navigation_bar.keyboard_shortcuts": "میان‌برها",
   "navigation_bar.lists": "فهرست‌ها",
   "navigation_bar.logout": "خروج",
-  "navigation_bar.mutes": "کاربران خموش",
+  "navigation_bar.mutes": "کاربران خموشیده",
   "navigation_bar.personal": "شخصی",
-  "navigation_bar.pins": "بوق‌های سنجاق‌شده",
+  "navigation_bar.pins": "بوق‌های ثابت",
   "navigation_bar.preferences": "ترجیحات",
-  "navigation_bar.public_timeline": "خط‌زمانی همگانی",
+  "navigation_bar.public_timeline": "نوشته‌های همه‌جا",
   "navigation_bar.security": "امنیت",
-  "notification.favourite": "‫{name}‬ وضعیتتان را برگزید",
-  "notification.follow": "‫{name}‬ پی‌گیرتان شد",
-  "notification.follow_request": "{name} درخواست پی‌گیریتان را داده است",
-  "notification.mention": "‫{name}‬ به شما اشاره کرد",
-  "notification.own_poll": "نظرسنجیتان پایان یافت",
-  "notification.poll": "نظرسنجی‌ای که در آن رأی دادید پایان یافته است",
-  "notification.reblog": "‫{name}‬ وضعیتتان را تقویت کرد",
-  "notifications.clear": "پاک‌سازی آگاهی‌ها",
-  "notifications.clear_confirmation": "مطمئنید می‌خواهید تمام آگاهی‌هایتان را برای همیشه پاک کنید؟",
-  "notifications.column_settings.alert": "آگاهی‌های میزکار",
-  "notifications.column_settings.favourite": "برگزیده‌ها:",
-  "notifications.column_settings.filter_bar.advanced": "نمایش تمام دسته‌ها",
+  "notification.favourite": "‫{name}‬ نوشتهٔ شما را پسندید",
+  "notification.follow": "‫{name}‬ پیگیرتان شد",
+  "notification.follow_request": "{name} می‌خواهد پیگیر شما باشد",
+  "notification.mention": "‫{name}‬ از شما نام برد",
+  "notification.own_poll": "نظرسنجی شما به پایان رسید",
+  "notification.poll": "نظرسنجی‌ای که در آن رأی دادید به پایان رسیده است",
+  "notification.reblog": "‫{name}‬ نوشتهٔ شما را بازبوقید",
+  "notifications.clear": "پاک‌کردن اعلان‌ها",
+  "notifications.clear_confirmation": "مطمئنید می‌خواهید همهٔ اعلان‌هایتان را برای همیشه پاک کنید؟",
+  "notifications.column_settings.alert": "اعلان‌های میزکار",
+  "notifications.column_settings.favourite": "پسندیده‌ها:",
+  "notifications.column_settings.filter_bar.advanced": "نمایش همهٔ دسته‌ها",
   "notifications.column_settings.filter_bar.category": "نوار پالایش سریع",
   "notifications.column_settings.filter_bar.show": "نمایش",
-  "notifications.column_settings.follow": "پی‌گیران تازه:",
+  "notifications.column_settings.follow": "پیگیران تازه:",
   "notifications.column_settings.follow_request": "درخواست‌های جدید پی‌گیری:",
-  "notifications.column_settings.mention": "اشاره‌ها:",
+  "notifications.column_settings.mention": "نام‌بردن‌ها:",
   "notifications.column_settings.poll": "نتایج نظرسنجی:",
-  "notifications.column_settings.push": "ارسال آگاهی‌ها",
-  "notifications.column_settings.reblog": "تقویت‌ها:",
+  "notifications.column_settings.push": "اعلان‌ها از سمت سرور",
+  "notifications.column_settings.reblog": "بازبوق‌ها:",
   "notifications.column_settings.show": "نمایش در ستون",
   "notifications.column_settings.sound": "پخش صدا",
   "notifications.filter.all": "همه",
-  "notifications.filter.boosts": "تقویت‌ها",
-  "notifications.filter.favourites": "برگزیده‌ها",
-  "notifications.filter.follows": "پی‌گیری‌ها",
-  "notifications.filter.mentions": "اشاره‌ها",
+  "notifications.filter.boosts": "بازبوق‌ها",
+  "notifications.filter.favourites": "پسندها",
+  "notifications.filter.follows": "پیگیری‌ها",
+  "notifications.filter.mentions": "نام‌بردن‌ها",
   "notifications.filter.polls": "نتایج نظرسنجی",
-  "notifications.group": "{count} آگاهی",
-  "poll.closed": "بسته‌شده",
-  "poll.refresh": "نوسازی",
+  "notifications.group": "{count} اعلان",
+  "poll.closed": "پایان‌یافته",
+  "poll.refresh": "به‌روزرسانی",
   "poll.total_people": "{count, plural, one {# نفر} other {# نفر}}",
   "poll.total_votes": "{count, plural, one {# رأی} other {# رأی}}",
   "poll.vote": "رأی",
-  "poll.voted": "به این پاسخ رأی دادید",
+  "poll.voted": "شما به این گزینه رأی دادید",
   "poll_button.add_poll": "افزودن نظرسنجی",
-  "poll_button.remove_poll": "برداشتن نظرسنجی",
-  "privacy.change": "تنظیم محرمانگی وضعیت‌ها",
-  "privacy.direct.long": "ارسال فقط به کاربران اشاره‌شده",
+  "poll_button.remove_poll": "حذف نظرسنجی",
+  "privacy.change": "تنظیم محرمانگی نوشته",
+  "privacy.direct.long": "تنها به کاربران نام‌برده‌شده نشان بده",
   "privacy.direct.short": "خصوصی",
-  "privacy.private.long": "ارسال فقط به پی‌گیران",
-  "privacy.private.short": "فقط پی‌گیران",
-  "privacy.public.long": "ارسال به خط‌زمانی عمومی",
+  "privacy.private.long": "تنها به پیگیران نشان بده",
+  "privacy.private.short": "خصوصی",
+  "privacy.public.long": "نمایش در فهرست عمومی",
   "privacy.public.short": "عمومی",
-  "privacy.unlisted.long": "ارسال نکردن به خط‌زمانی عمومی",
+  "privacy.unlisted.long": "عمومی، ولی فهرست نکن",
   "privacy.unlisted.short": "فهرست‌نشده",
-  "refresh": "نوسازی",
-  "regeneration_indicator.label": "در حال بار کردن…",
-  "regeneration_indicator.sublabel": "خوراک خانه‌تان در حال آماده شدن است!",
+  "refresh": "به‌روزرسانی",
+  "regeneration_indicator.label": "در حال باز شدن…",
+  "regeneration_indicator.sublabel": "این فهرست دارد آماده می‌شود!",
   "relative_time.days": "{number} روز",
   "relative_time.hours": "{number} ساعت",
-  "relative_time.just_now": "اکنون",
+  "relative_time.just_now": "الان",
   "relative_time.minutes": "{number} دقیقه",
   "relative_time.seconds": "{number} ثانیه",
   "reply_indicator.cancel": "لغو",
-  "report.forward": "هدایت به {target}",
-  "report.forward_hint": "این حساب از کارسازی دیگر است. رونوشتی ناشناس از گزارش به آن‌جا نیز فرستاده شود؟",
-  "report.hint": "این گزارش به مدیران کارسازتان فرستاده خواهد شد. می‌توانید دلیلی بر چرایی گزارش این حساب را در ادامه بنویسید:",
+  "report.forward": "فرستادن به {target}",
+  "report.forward_hint": "این حساب در کارساز دیگری ثبت شده. آیا می‌خواهید رونوشتی ناشناس از این گزارش به آن‌جا هم فرستاده شود؟",
+  "report.hint": "این گزارش به مدیران کارسازتان فرستاده خواهد شد. می‌توانید دلیل گزارش این حساب را در ادامه بنویسید:",
   "report.placeholder": "توضیح اضافه",
-  "report.submit": "ثبت",
+  "report.submit": "بفرست",
   "report.target": "در حال گزارش {target}",
-  "search.placeholder": "جست‌وجو",
-  "search_popout.search_format": "قالب جست‌وجوی پیشرفته",
+  "search.placeholder": "جستجو",
+  "search_popout.search_format": "راهنمای جستجوی پیشرفته",
   "search_popout.tips.full_text": "جست‌وجوی متنی ساده وضعیت‌هایی که که نوشته، برگزیده، تقویت‌کرده یا در آن‌ها اشاره‌شده‌اید را به اضافهٔ نام‌های کاربری، نام‌های نمایشی و برچسب‌های مطابق برمی‌گرداند.",
   "search_popout.tips.hashtag": "برچسب",
   "search_popout.tips.status": "وضعیت",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index b2715cc4b..81ba22987 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -77,17 +77,17 @@
   "community.column_settings.media_only": "Só multimedia",
   "compose_form.direct_message_warning": "Este toot só será enviado ás usuarias mencionadas.",
   "compose_form.direct_message_warning_learn_more": "Coñecer máis",
-  "compose_form.hashtag_warning": "Este toot non se amosará baixo cancelos (hashtags) porque non é público. Só os toots públicos poden ser procurados por cancelos.",
-  "compose_form.lock_disclaimer": "A túa conta non está {locked}. Todos poden seguirche para ollar os teus toots só para seguidores.",
-  "compose_form.lock_disclaimer.lock": "bloqueado",
-  "compose_form.placeholder": "En que estás a pensar?",
+  "compose_form.hashtag_warning": "Este toot non aparecerá baixo ningún cancelo (hashtag) porque non está listado. Só se poden procurar toots públicos por cancelos.",
+  "compose_form.lock_disclaimer": "A túa conta non está {locked}. Todas poden seguirte para ollar os teus toots só para seguidoras.",
+  "compose_form.lock_disclaimer.lock": "bloqueada",
+  "compose_form.placeholder": "Qué contas?",
   "compose_form.poll.add_option": "Engadir unha opción",
   "compose_form.poll.duration": "Duración da enquisa",
   "compose_form.poll.option_placeholder": "Opción {number}",
   "compose_form.poll.remove_option": "Eliminar esta opción",
   "compose_form.poll.switch_to_multiple": "Mudar a enquisa para permitir múltiples escollas",
   "compose_form.poll.switch_to_single": "Mudar a enquisa para permitir unha soa escolla",
-  "compose_form.publish": "Tootear",
+  "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Marcar coma contido multimedia sensíbel",
   "compose_form.sensitive.marked": "Contido multimedia marcado coma sensíbel",
@@ -104,16 +104,16 @@
   "confirmations.delete_list.confirm": "Eliminar",
   "confirmations.delete_list.message": "Tes a certeza de querer eliminar de xeito permanente esta listaxe?",
   "confirmations.domain_block.confirm": "Agochar dominio enteiro",
-  "confirmations.domain_block.message": "Tes a certeza de querer bloquear todo de {domain}? Na meirande parte dos casos uns bloqueos ou silenciados específicos son suficientes. Non verás máis o contido deste dominio en ningunha cronoloxía pública ou nas túas notificacións. Os teus seguidores deste dominio serán eliminados.",
+  "confirmations.domain_block.message": "Tes a certeza de querer bloquear todo de {domain}? Na meirande parte dos casos uns bloqueos ou silenciados específicos son suficientes. Non verás máis o contido deste dominio en ningunha cronoloxía pública ou nas túas notificacións. As túas seguidoras deste dominio serán eliminadas.",
   "confirmations.logout.confirm": "Pechar sesión",
   "confirmations.logout.message": "Desexas pechar a sesión?",
-  "confirmations.mute.confirm": "Silenciar",
-  "confirmations.mute.explanation": "Isto agochará as publicacións deles ou nas que os mencionen, mais permitirá que vexan as túas publicacións e sexan os teus seguidores.",
-  "confirmations.mute.message": "Tes a certeza de querer silenciar a {name}?",
+  "confirmations.mute.confirm": "Acalar",
+  "confirmations.mute.explanation": "Isto agochará as publicacións delas ou nas que as mencionen, mais permitirá que vexan as túas publicacións e sexan seguidoras túas.",
+  "confirmations.mute.message": "Tes a certeza de querer acalar a {name}?",
   "confirmations.redraft.confirm": "Eliminar e reescribir",
   "confirmations.redraft.message": "Tes a certeza de querer eliminar este estado e reescribilo? Perderás os compartidos e favoritos, e as respostas á publicación orixinal ficarán orfas.",
   "confirmations.reply.confirm": "Responder",
-  "confirmations.reply.message": "Respostar agora sobrescribirá a mensaxe que estás a compor. Tes a certeza de que queres continuar?",
+  "confirmations.reply.message": "Responder agora sobrescribirá a mensaxe que estás a compor. Tes a certeza de que queres continuar?",
   "confirmations.unfollow.confirm": "Deixar de seguir",
   "confirmations.unfollow.message": "Desexas deixar de seguir a {name}?",
   "conversation.delete": "Eliminar conversa",
@@ -122,10 +122,10 @@
   "conversation.with": "Con {names}",
   "directory.federated": "Do fediverso coñecido",
   "directory.local": "Só de {domain}",
-  "directory.new_arrivals": "Recén chegados",
-  "directory.recently_active": "Activos recentemente",
+  "directory.new_arrivals": "Recén chegadas",
+  "directory.recently_active": "Activas recentemente",
   "embed.instructions": "Engade este estado ó teu sitio web copiando o seguinte código.",
-  "embed.preview": "Así será amosado:",
+  "embed.preview": "Así será mostrado:",
   "emoji_button.activity": "Actividade",
   "emoji_button.custom": "Personalizado",
   "emoji_button.flags": "Bandeiras",
@@ -199,8 +199,8 @@
   "introduction.interactions.favourite.text": "Podes gardar un toot para depois e facer saber ó autor que che gostou marcandoo coma favorito.",
   "introduction.interactions.reblog.headline": "Promover",
   "introduction.interactions.reblog.text": "Podes compartir os toots doutras persoas coas túas seguidoras.",
-  "introduction.interactions.reply.headline": "Respostar",
-  "introduction.interactions.reply.text": "Podes respostar ós toots doutras persoas e ós teus propios, así ficarán encadeados nunha conversa.",
+  "introduction.interactions.reply.headline": "Responder",
+  "introduction.interactions.reply.text": "Podes responder ós toots doutras persoas e ós teus propios, así ficarán encadeados nunha conversa.",
   "introduction.welcome.action": "Imos!",
   "introduction.welcome.headline": "Primeiros pasos",
   "introduction.welcome.text": "Benvido ó fediverso! Nun intre poderás difundir mensaxes e falar coas túas amizades nun grande número de servidores. Mais este servidor, {domain}, é especial—hospeda o teu perfil, por iso lémbrate do seu nome.",
@@ -228,7 +228,7 @@
   "keyboard_shortcuts.open_media": "para abrir o contido multimedia",
   "keyboard_shortcuts.pinned": "para abrir a listaxe dos toots fixados",
   "keyboard_shortcuts.profile": "para abrir o perfil do autor",
-  "keyboard_shortcuts.reply": "para respostar",
+  "keyboard_shortcuts.reply": "para responder",
   "keyboard_shortcuts.requests": "para abrir a listaxe das peticións de seguimento",
   "keyboard_shortcuts.search": "para destacar a procura",
   "keyboard_shortcuts.start": "para abrir a columna dos \"primeiros pasos\"",
@@ -381,8 +381,8 @@
   "status.reblogs.empty": "Aínda ninguén promoveu este toot. Cando alguén o faga, amosarase aquí.",
   "status.redraft": "Eliminar e reescribir",
   "status.remove_bookmark": "Eliminar marcador",
-  "status.reply": "Respostar",
-  "status.replyAll": "Respostar ó fío",
+  "status.reply": "Responder",
+  "status.replyAll": "Responder ó fío",
   "status.report": "Denunciar @{name}",
   "status.sensitive_warning": "Contido sensíbel",
   "status.share": "Compartir",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index f4280e740..e8dc573fa 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "Forgalomkorlátozás",
   "alert.unexpected.message": "Váratlan hiba történt.",
   "alert.unexpected.title": "Hoppá!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Közlemény",
   "autosuggest_hashtag.per_week": "{count}/hét",
   "boost_modal.combo": "Hogy átugord ezt következő alkalommal, használd {combo}",
   "bundle_column_error.body": "Hiba történt a komponens betöltése közben.",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 51eeb1eb6..67cee9276 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "制限に達しました",
   "alert.unexpected.message": "不明なエラーが発生しました。",
   "alert.unexpected.title": "エラー!",
-  "announcement.announcement": "告知",
+  "announcement.announcement": "お知らせ",
   "autosuggest_hashtag.per_week": "{count} 回 / 週",
   "boost_modal.combo": "次からは{combo}を押せばスキップできます",
   "bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。",
@@ -89,8 +89,8 @@
   "compose_form.poll.duration": "アンケート期間",
   "compose_form.poll.option_placeholder": "項目 {number}",
   "compose_form.poll.remove_option": "この項目を削除",
-  "compose_form.poll.switch_to_multiple": "複数選択用に変更",
-  "compose_form.poll.switch_to_single": "単一選択用に変更",
+  "compose_form.poll.switch_to_multiple": "複数選択に変更",
+  "compose_form.poll.switch_to_single": "単一選択に変更",
   "compose_form.publish": "トゥート",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "メディアを閲覧注意にする",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index f7be26c09..5a3b409c0 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "빈도 제한",
   "alert.unexpected.message": "예측하지 못한 에러가 발생했습니다.",
   "alert.unexpected.title": "앗!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "공지사항",
   "autosuggest_hashtag.per_week": "주간 {count}회",
   "boost_modal.combo": "{combo}를 누르면 다음부터 이 과정을 건너뛸 수 있습니다",
   "bundle_column_error.body": "컴포넌트를 불러오는 과정에서 문제가 발생했습니다.",
diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json
index d69717292..c04b30e04 100644
--- a/app/javascript/mastodon/locales/nn.json
+++ b/app/javascript/mastodon/locales/nn.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "Legg til eller tak vekk frå listene",
   "account.badges.bot": "Robot",
-  "account.badges.group": "Group",
+  "account.badges.group": "Gruppe",
   "account.block": "Blokker @{name}",
   "account.block_domain": "Skjul alt frå {domain}",
   "account.blocked": "Blokkert",
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "Begrensa rate",
   "alert.unexpected.message": "Eit uventa problem oppstod.",
   "alert.unexpected.title": "Oi sann!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Kunngjøring",
   "autosuggest_hashtag.per_week": "{count} per veke",
   "boost_modal.combo": "Du kan trykkja {combo} for å hoppa over dette neste gong",
   "bundle_column_error.body": "Noko gjekk gale mens denne komponenten vart lasta ned.",
@@ -85,8 +85,8 @@
   "compose_form.poll.duration": "Varigskap for røysting",
   "compose_form.poll.option_placeholder": "Val {number}",
   "compose_form.poll.remove_option": "Ta vekk dette valet",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "Endre avstemning til å tillate flere valg",
+  "compose_form.poll.switch_to_single": "Endre avstemning til å tillate ett valg",
   "compose_form.publish": "Tut",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Merk medium som sensitivt",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index c6dc4ca0e..215cdb150 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "Legg til eller fjern fra lister",
   "account.badges.bot": "Bot",
-  "account.badges.group": "Group",
+  "account.badges.group": "Gruppe",
   "account.block": "Blokkér @{name}",
   "account.block_domain": "Skjul alt fra {domain}",
   "account.blocked": "Blokkert",
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "Hastighetsbegrenset",
   "alert.unexpected.message": "En uventet feil oppstod.",
   "alert.unexpected.title": "Oops!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Kunngjøring",
   "autosuggest_hashtag.per_week": "{count} per uke",
   "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang",
   "bundle_column_error.body": "Noe gikk galt mens denne komponenten lastet.",
@@ -85,8 +85,8 @@
   "compose_form.poll.duration": "Avstemningens varighet",
   "compose_form.poll.option_placeholder": "Valg {number}",
   "compose_form.poll.remove_option": "Fjern dette valget",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "Endre avstemning til å tillate flere valg",
+  "compose_form.poll.switch_to_single": "Endre avstemning til å tillate ett valg",
   "compose_form.publish": "Tut",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Merk media som sensitivt",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 1681da968..6e63a418c 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "Вы выполняете действие слишком часто",
   "alert.unexpected.message": "Что-то пошло не так.",
   "alert.unexpected.title": "Ой!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Объявление",
   "autosuggest_hashtag.per_week": "{count} / неделю",
   "boost_modal.combo": "{combo}, чтобы пропустить это в следующий раз",
   "bundle_column_error.body": "Что-то пошло не так при загрузке этого компонента.",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 2410daf06..976dc572e 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "Tempo obmedzené",
   "alert.unexpected.message": "Vyskytla sa nečakaná chyba.",
   "alert.unexpected.title": "Ups!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Oboznámenie",
   "autosuggest_hashtag.per_week": "{count} týždenne",
   "boost_modal.combo": "Nabudúce môžeš kliknúť {combo} pre preskočenie",
   "bundle_column_error.body": "Pri načítaní tohto prvku nastala nejaká chyba.",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 8a72783a2..2d009b851 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "มีการจำกัดอัตรา",
   "alert.unexpected.message": "เกิดข้อผิดพลาดที่ไม่คาดคิด",
   "alert.unexpected.title": "อุปส์!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "ประกาศ",
   "autosuggest_hashtag.per_week": "{count} ต่อสัปดาห์",
   "boost_modal.combo": "คุณสามารถกด {combo} เพื่อข้ามสิ่งนี้ในครั้งถัดไป",
   "bundle_column_error.body": "มีบางอย่างผิดพลาดขณะโหลดส่วนประกอบนี้",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 3ade92977..11f9ff2ef 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "Oran sınırlıdır",
   "alert.unexpected.message": "Beklenmedik bir hata oluştu.",
   "alert.unexpected.title": "Hay aksi!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Duyuru",
   "autosuggest_hashtag.per_week": "Haftada {count}",
   "boost_modal.combo": "Bir daha ki sefere {combo} tuşuna basabilirsiniz",
   "bundle_column_error.body": "Bu bileşen yüklenirken bir şeyler ters gitti.",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 3dc69d6d6..061fc50cb 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "Додати або видалити зі списків",
   "account.badges.bot": "Бот",
-  "account.badges.group": "Group",
+  "account.badges.group": "Група",
   "account.block": "Заблокувати @{name}",
   "account.block_domain": "Заглушити {domain}",
   "account.blocked": "Заблоковані",
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "Швидкість обмежена",
   "alert.unexpected.message": "Трапилась неочікувана помилка.",
   "alert.unexpected.title": "Ой!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Оголошення",
   "autosuggest_hashtag.per_week": "{count} в тиждень",
   "boost_modal.combo": "Ви можете натиснути {combo}, щоб пропустити це наступного разу",
   "bundle_column_error.body": "Щось пішло не так під час завантаження компоненту.",
@@ -85,8 +85,8 @@
   "compose_form.poll.duration": "Тривалість опитування",
   "compose_form.poll.option_placeholder": "Варіант {number}",
   "compose_form.poll.remove_option": "Видалити цей варіант",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "Перемкнути у режим вибору декількох відповідей",
+  "compose_form.poll.switch_to_single": "Перемкнути у режим вибору однієї відповіді",
   "compose_form.publish": "Дмухнути",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Позначити медіа як дражливе",
@@ -225,7 +225,7 @@
   "keyboard_shortcuts.muted": "відкрити список заглушених користувачів",
   "keyboard_shortcuts.my_profile": "відкрити ваш профіль",
   "keyboard_shortcuts.notifications": "відкрити колонку сповіщень",
-  "keyboard_shortcuts.open_media": "to open media",
+  "keyboard_shortcuts.open_media": "відкрити медіа",
   "keyboard_shortcuts.pinned": "відкрити список закріплених дмухів",
   "keyboard_shortcuts.profile": "відкрити профіль автора",
   "keyboard_shortcuts.reply": "відповісти",
diff --git a/app/javascript/mastodon/reducers/announcements.js b/app/javascript/mastodon/reducers/announcements.js
index aa674e516..1cfb598fb 100644
--- a/app/javascript/mastodon/reducers/announcements.js
+++ b/app/javascript/mastodon/reducers/announcements.js
@@ -3,18 +3,20 @@ import {
   ANNOUNCEMENTS_FETCH_SUCCESS,
   ANNOUNCEMENTS_FETCH_FAIL,
   ANNOUNCEMENTS_UPDATE,
-  ANNOUNCEMENTS_DISMISS,
   ANNOUNCEMENTS_REACTION_UPDATE,
   ANNOUNCEMENTS_REACTION_ADD_REQUEST,
   ANNOUNCEMENTS_REACTION_ADD_FAIL,
   ANNOUNCEMENTS_REACTION_REMOVE_REQUEST,
   ANNOUNCEMENTS_REACTION_REMOVE_FAIL,
+  ANNOUNCEMENTS_TOGGLE_SHOW,
 } from '../actions/announcements';
-import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+import { Map as ImmutableMap, List as ImmutableList, Set as ImmutableSet, fromJS } from 'immutable';
 
 const initialState = ImmutableMap({
   items: ImmutableList(),
   isLoading: false,
+  show: true,
+  unread: ImmutableSet(),
 });
 
 const updateReaction = (state, id, name, updater) => state.update('items', list => list.map(announcement => {
@@ -43,21 +45,35 @@ const addReaction = (state, id, name) => updateReaction(state, id, name, x => x.
 
 const removeReaction = (state, id, name) => updateReaction(state, id, name, x => x.set('me', false).update('count', y => y - 1));
 
+const addUnread = (state, items) => {
+  if (state.get('show')) return state;
+
+  const newIds = ImmutableSet(items.map(x => x.get('id')));
+  const oldIds = ImmutableSet(state.get('items').map(x => x.get('id')));
+  return state.update('unread', unread => unread.union(newIds.subtract(oldIds)));
+};
+
 export default function announcementsReducer(state = initialState, action) {
   switch(action.type) {
+  case ANNOUNCEMENTS_TOGGLE_SHOW:
+    return state.withMutations(map => {
+      if (!map.get('show')) map.set('unread', ImmutableSet());
+      map.set('show', !map.get('show'));
+    });
   case ANNOUNCEMENTS_FETCH_REQUEST:
     return state.set('isLoading', true);
   case ANNOUNCEMENTS_FETCH_SUCCESS:
     return state.withMutations(map => {
-      map.set('items', fromJS(action.announcements));
+      const items = fromJS(action.announcements);
+      map.set('unread', ImmutableSet());
+      addUnread(map, items);
+      map.set('items', items);
       map.set('isLoading', false);
     });
   case ANNOUNCEMENTS_FETCH_FAIL:
     return state.set('isLoading', false);
   case ANNOUNCEMENTS_UPDATE:
-    return state.update('items', list => list.unshift(fromJS(action.announcement)).sortBy(announcement => announcement.get('starts_at')));
-  case ANNOUNCEMENTS_DISMISS:
-    return state.update('items', list => list.filterNot(announcement => announcement.get('id') === action.id));
+    return addUnread(state, [fromJS(action.announcement)]).update('items', list => list.unshift(fromJS(action.announcement)).sortBy(announcement => announcement.get('starts_at')));
   case ANNOUNCEMENTS_REACTION_UPDATE:
     return updateReactionCount(state, action.reaction);
   case ANNOUNCEMENTS_REACTION_ADD_REQUEST:
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 2e6ea3c7e..adaed2826 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -6631,7 +6631,7 @@ noscript {
 }
 
 .announcements {
-  background: lighten($ui-base-color, 4%);
+  background: lighten($ui-base-color, 8%);
   border-top: 1px solid $ui-base-color;
   font-size: 13px;
   display: flex;
@@ -6672,12 +6672,6 @@ noscript {
       font-weight: 500;
       margin-bottom: 10px;
     }
-
-    &__dismiss-icon {
-      position: absolute;
-      top: 12px;
-      right: 12px;
-    }
   }
 
   &__pagination {
diff --git a/app/validators/reaction_validator.rb b/app/validators/reaction_validator.rb
index be899c89d..494b6041b 100644
--- a/app/validators/reaction_validator.rb
+++ b/app/validators/reaction_validator.rb
@@ -6,10 +6,10 @@ class ReactionValidator < ActiveModel::Validator
   LIMIT = 8
 
   def validate(reaction)
-    return if reaction.name.blank? || reaction.custom_emoji_id.present?
+    return if reaction.name.blank?
 
-    reaction.errors.add(:name, I18n.t('reactions.errors.unrecognized_emoji')) unless unicode_emoji?(reaction.name)
-    reaction.errors.add(:base, I18n.t('reactions.errors.limit_reached')) if limit_reached?(reaction)
+    reaction.errors.add(:name, I18n.t('reactions.errors.unrecognized_emoji')) if reaction.custom_emoji_id.blank? && !unicode_emoji?(reaction.name)
+    reaction.errors.add(:base, I18n.t('reactions.errors.limit_reached')) if new_reaction?(reaction) && limit_reached?(reaction)
   end
 
   private
@@ -18,6 +18,10 @@ class ReactionValidator < ActiveModel::Validator
     SUPPORTED_EMOJIS.include?(name)
   end
 
+  def new_reaction?(reaction)
+    !reaction.announcement.announcement_reactions.where(name: reaction.name).exists?
+  end
+
   def limit_reached?(reaction)
     reaction.announcement.announcement_reactions.where.not(name: reaction.name).count('distinct name') >= LIMIT
   end