about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorClaire <claire.github-309c@sitedethib.com>2023-02-03 19:07:58 +0100
committerClaire <claire.github-309c@sitedethib.com>2023-02-03 19:23:27 +0100
commitaeacebb3d75112d0d22b9829813c388eef6ce5af (patch)
tree98fced042da7c37db7b401eb197cc44c7aa1a41c /app
parentec26f7c1b16ca1429991212292e35e520c617485 (diff)
parent79ca19e9b2e701d98c80afd939a98c2a3ef74830 (diff)
Merge branch 'main' into glitch-soc/merge-upstream
Conflicts:
- `.github/workflows/build-image.yml`:
  Upstream updated `docker/build-push-action`, and we a different config
  for `docker/metadata-action` so the lines directly above were different,
  but it's not a real conflict.
  Upgraded `docker/build-push-action` as upstream did.
- `app/javascript/mastodon/features/compose/components/compose_form.js`:
  Upstream changed the codestyle near a line we had modified to accommodate
  configurable character count.
  Kept our change.
Diffstat (limited to 'app')
-rw-r--r--app/controllers/api/v1/notifications_controller.rb2
-rw-r--r--app/controllers/settings/applications_controller.rb8
-rw-r--r--app/helpers/languages_helper.rb4
-rw-r--r--app/javascript/mastodon/actions/tags.js14
-rw-r--r--app/javascript/mastodon/components/account.js12
-rw-r--r--app/javascript/mastodon/components/animated_number.js4
-rw-r--r--app/javascript/mastodon/components/autosuggest_input.js14
-rw-r--r--app/javascript/mastodon/components/autosuggest_textarea.js16
-rw-r--r--app/javascript/mastodon/components/avatar.js4
-rw-r--r--app/javascript/mastodon/components/avatar_overlay.js4
-rw-r--r--app/javascript/mastodon/components/button.js4
-rw-r--r--app/javascript/mastodon/components/column.js4
-rw-r--r--app/javascript/mastodon/components/column_back_button.js2
-rw-r--r--app/javascript/mastodon/components/column_header.js16
-rw-r--r--app/javascript/mastodon/components/dismissable_banner.js2
-rw-r--r--app/javascript/mastodon/components/display_name.js4
-rw-r--r--app/javascript/mastodon/components/domain.js2
-rw-r--r--app/javascript/mastodon/components/dropdown_menu.js34
-rw-r--r--app/javascript/mastodon/components/edited_timestamp/index.js4
-rw-r--r--app/javascript/mastodon/components/error_boundary.js2
-rw-r--r--app/javascript/mastodon/components/gifv.js4
-rw-r--r--app/javascript/mastodon/components/icon_button.js10
-rw-r--r--app/javascript/mastodon/components/intersection_observer_article.js12
-rw-r--r--app/javascript/mastodon/components/load_gap.js2
-rw-r--r--app/javascript/mastodon/components/load_more.js4
-rw-r--r--app/javascript/mastodon/components/load_pending.js2
-rw-r--r--app/javascript/mastodon/components/media_attachments.js6
-rw-r--r--app/javascript/mastodon/components/media_gallery.js14
-rw-r--r--app/javascript/mastodon/components/modal_root.js8
-rw-r--r--app/javascript/mastodon/components/picture_in_picture_placeholder.js4
-rw-r--r--app/javascript/mastodon/components/poll.js4
-rw-r--r--app/javascript/mastodon/components/scrollable_list.js24
-rw-r--r--app/javascript/mastodon/components/status.js50
-rw-r--r--app/javascript/mastodon/components/status_action_bar.js46
-rw-r--r--app/javascript/mastodon/components/status_content.js18
-rw-r--r--app/javascript/mastodon/components/status_list.js12
-rw-r--r--app/javascript/mastodon/containers/media_container.js8
-rw-r--r--app/javascript/mastodon/extra_polyfills.js1
-rw-r--r--app/javascript/mastodon/features/about/index.js4
-rw-r--r--app/javascript/mastodon/features/account/components/account_note.js6
-rw-r--r--app/javascript/mastodon/features/account/components/header.js18
-rw-r--r--app/javascript/mastodon/features/account_gallery/components/media_item.js8
-rw-r--r--app/javascript/mastodon/features/account_gallery/index.js12
-rw-r--r--app/javascript/mastodon/features/account_timeline/components/header.js32
-rw-r--r--app/javascript/mastodon/features/account_timeline/components/limited_account_hint.js2
-rw-r--r--app/javascript/mastodon/features/account_timeline/index.js2
-rw-r--r--app/javascript/mastodon/features/audio/index.js44
-rw-r--r--app/javascript/mastodon/features/bookmarked_statuses/index.js10
-rw-r--r--app/javascript/mastodon/features/community_timeline/index.js10
-rw-r--r--app/javascript/mastodon/features/compose/components/action_bar.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/compose_form.js30
-rw-r--r--app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js38
-rw-r--r--app/javascript/mastodon/features/compose/components/language_dropdown.js30
-rw-r--r--app/javascript/mastodon/features/compose/components/poll_button.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/poll_form.js17
-rw-r--r--app/javascript/mastodon/features/compose/components/privacy_dropdown.js30
-rw-r--r--app/javascript/mastodon/features/compose/components/reply_indicator.js4
-rw-r--r--app/javascript/mastodon/features/compose/components/search.js14
-rw-r--r--app/javascript/mastodon/features/compose/components/upload.js4
-rw-r--r--app/javascript/mastodon/features/compose/components/upload_button.js6
-rw-r--r--app/javascript/mastodon/features/compose/containers/poll_form_container.js1
-rw-r--r--app/javascript/mastodon/features/compose/index.js6
-rw-r--r--app/javascript/mastodon/features/direct_timeline/components/conversation.js20
-rw-r--r--app/javascript/mastodon/features/direct_timeline/components/conversations_list.js10
-rw-r--r--app/javascript/mastodon/features/direct_timeline/index.js10
-rw-r--r--app/javascript/mastodon/features/directory/components/account_card.js8
-rw-r--r--app/javascript/mastodon/features/directory/index.js14
-rw-r--r--app/javascript/mastodon/features/emoji/emoji_utils.js10
-rw-r--r--app/javascript/mastodon/features/explore/index.js4
-rw-r--r--app/javascript/mastodon/features/explore/statuses.js2
-rw-r--r--app/javascript/mastodon/features/favourited_statuses/index.js10
-rw-r--r--app/javascript/mastodon/features/favourites/index.js2
-rw-r--r--app/javascript/mastodon/features/filters/select_filter.js14
-rw-r--r--app/javascript/mastodon/features/follow_recommendations/components/account.js2
-rw-r--r--app/javascript/mastodon/features/follow_recommendations/index.js2
-rw-r--r--app/javascript/mastodon/features/followed_tags/index.js2
-rw-r--r--app/javascript/mastodon/features/getting_started/components/announcements.js26
-rw-r--r--app/javascript/mastodon/features/hashtag_timeline/index.js16
-rw-r--r--app/javascript/mastodon/features/home_timeline/index.js12
-rw-r--r--app/javascript/mastodon/features/interaction_modal/index.js8
-rw-r--r--app/javascript/mastodon/features/list_editor/components/edit_list_form.js6
-rw-r--r--app/javascript/mastodon/features/list_editor/components/search.js6
-rw-r--r--app/javascript/mastodon/features/list_timeline/index.js16
-rw-r--r--app/javascript/mastodon/features/lists/components/new_list_form.js6
-rw-r--r--app/javascript/mastodon/features/notifications/components/column_settings.js2
-rw-r--r--app/javascript/mastodon/features/notifications/components/notification.js16
-rw-r--r--app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js4
-rw-r--r--app/javascript/mastodon/features/notifications/components/setting_toggle.js4
-rw-r--r--app/javascript/mastodon/features/notifications/index.js12
-rw-r--r--app/javascript/mastodon/features/picture_in_picture/components/footer.js4
-rw-r--r--app/javascript/mastodon/features/picture_in_picture/index.js2
-rw-r--r--app/javascript/mastodon/features/pinned_statuses/index.js4
-rw-r--r--app/javascript/mastodon/features/public_timeline/index.js10
-rw-r--r--app/javascript/mastodon/features/reblogs/index.js2
-rw-r--r--app/javascript/mastodon/features/report/components/option.js4
-rw-r--r--app/javascript/mastodon/features/standalone/compose/index.js2
-rw-r--r--app/javascript/mastodon/features/status/components/action_bar.js38
-rw-r--r--app/javascript/mastodon/features/status/components/card.js8
-rw-r--r--app/javascript/mastodon/features/status/components/detailed_status.js12
-rw-r--r--app/javascript/mastodon/features/status/index.js78
-rw-r--r--app/javascript/mastodon/features/subscribed_languages_modal/index.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/actions_modal.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/block_modal.js8
-rw-r--r--app/javascript/mastodon/features/ui/components/boost_modal.js6
-rw-r--r--app/javascript/mastodon/features/ui/components/bundle.js10
-rw-r--r--app/javascript/mastodon/features/ui/components/bundle_column_error.js8
-rw-r--r--app/javascript/mastodon/features/ui/components/bundle_modal_error.js4
-rw-r--r--app/javascript/mastodon/features/ui/components/column.js6
-rw-r--r--app/javascript/mastodon/features/ui/components/column_header.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/columns_area.js12
-rw-r--r--app/javascript/mastodon/features/ui/components/compose_panel.js4
-rw-r--r--app/javascript/mastodon/features/ui/components/confirmation_modal.js8
-rw-r--r--app/javascript/mastodon/features/ui/components/disabled_account_banner.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/embed_modal.js6
-rw-r--r--app/javascript/mastodon/features/ui/components/focal_point_modal.js36
-rw-r--r--app/javascript/mastodon/features/ui/components/image_loader.js8
-rw-r--r--app/javascript/mastodon/features/ui/components/link_footer.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/media_modal.js12
-rw-r--r--app/javascript/mastodon/features/ui/components/modal_root.js10
-rw-r--r--app/javascript/mastodon/features/ui/components/mute_modal.js10
-rw-r--r--app/javascript/mastodon/features/ui/components/report_modal.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/upload_area.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/zoomable_image.js28
-rw-r--r--app/javascript/mastodon/features/ui/index.js66
-rw-r--r--app/javascript/mastodon/features/ui/util/react_router_helpers.js6
-rw-r--r--app/javascript/mastodon/features/ui/util/reduced_motion.js2
-rw-r--r--app/javascript/mastodon/features/video/index.js52
-rw-r--r--app/javascript/mastodon/locales/ar.json6
-rw-r--r--app/javascript/mastodon/locales/be.json8
-rw-r--r--app/javascript/mastodon/locales/bg.json4
-rw-r--r--app/javascript/mastodon/locales/ca.json48
-rw-r--r--app/javascript/mastodon/locales/cs.json6
-rw-r--r--app/javascript/mastodon/locales/csb.json658
-rw-r--r--app/javascript/mastodon/locales/cy.json4
-rw-r--r--app/javascript/mastodon/locales/en-GB.json6
-rw-r--r--app/javascript/mastodon/locales/fi.json100
-rw-r--r--app/javascript/mastodon/locales/hy.json134
-rw-r--r--app/javascript/mastodon/locales/id.json26
-rw-r--r--app/javascript/mastodon/locales/it.json2
-rw-r--r--app/javascript/mastodon/locales/kk.json2
-rw-r--r--app/javascript/mastodon/locales/ko.json4
-rw-r--r--app/javascript/mastodon/locales/nl.json10
-rw-r--r--app/javascript/mastodon/locales/sk.json4
-rw-r--r--app/javascript/mastodon/locales/sq.json8
-rw-r--r--app/javascript/mastodon/locales/th.json4
-rw-r--r--app/javascript/mastodon/locales/uz.json658
-rw-r--r--app/javascript/mastodon/locales/vi.json4
-rw-r--r--app/javascript/mastodon/locales/whitelist_csb.json2
-rw-r--r--app/javascript/mastodon/locales/whitelist_uz.json2
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json10
-rw-r--r--app/javascript/mastodon/locales/zh-TW.json4
-rw-r--r--app/javascript/mastodon/reducers/compose.js4
-rw-r--r--app/javascript/mastodon/reducers/followed_tags.js2
-rw-r--r--app/javascript/mastodon/service_worker/web_push_notifications.js10
-rw-r--r--app/javascript/packs/public-path.js2
-rw-r--r--app/lib/feed_manager.rb2
-rw-r--r--app/serializers/rest/admin/account_serializer.rb3
-rw-r--r--app/views/statuses/_og_image.html.haml3
158 files changed, 2260 insertions, 921 deletions
diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb
index a6ed359c9..93785b14a 100644
--- a/app/controllers/api/v1/notifications_controller.rb
+++ b/app/controllers/api/v1/notifications_controller.rb
@@ -6,7 +6,7 @@ class Api::V1::NotificationsController < Api::BaseController
   before_action :require_user!
   after_action :insert_pagination_headers, only: :index
 
-  DEFAULT_NOTIFICATIONS_LIMIT = 15
+  DEFAULT_NOTIFICATIONS_LIMIT = 40
 
   def index
     @notifications = load_notifications
diff --git a/app/controllers/settings/applications_controller.rb b/app/controllers/settings/applications_controller.rb
index d3ac268d8..e6e137c2b 100644
--- a/app/controllers/settings/applications_controller.rb
+++ b/app/controllers/settings/applications_controller.rb
@@ -29,7 +29,13 @@ class Settings::ApplicationsController < Settings::BaseController
 
   def update
     if @application.update(application_params)
-      redirect_to settings_applications_path, notice: I18n.t('generic.changes_saved_msg')
+      if @application.scopes_previously_changed?
+        @access_token = current_user.token_for_app(@application)
+        @access_token.destroy
+        redirect_to settings_application_path(@application), notice: I18n.t('applications.token_regenerated')
+      else
+        redirect_to settings_application_path(@application), notice: I18n.t('generic.changes_saved_msg')
+      end
     else
       render :show
     end
diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb
index bb87dd596..bb35ce08c 100644
--- a/app/helpers/languages_helper.rb
+++ b/app/helpers/languages_helper.rb
@@ -199,6 +199,8 @@ module LanguagesHelper
     sco: ['Scots', 'Scots'].freeze,
     sma: ['Southern Sami', 'Åarjelsaemien Gïele'].freeze,
     smj: ['Lule Sami', 'Julevsámegiella'].freeze,
+    szl: ['Silesian', 'ślůnsko godka'].freeze,
+    tai: ['Tai', 'ภาษาไท or ภาษาไต'].freeze,
     tok: ['Toki Pona', 'toki pona'].freeze,
     zba: ['Balaibalan', 'باليبلن'].freeze,
     zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
@@ -210,8 +212,10 @@ module LanguagesHelper
   # names, but for some translations, we need the names of the
   # regional variants specifically
   REGIONAL_LOCALE_NAMES = {
+    'en-GB': 'English (British)',
     'es-AR': 'Español (Argentina)',
     'es-MX': 'Español (México)',
+    'fr-QC': 'Français (Canadien)',
     'pt-BR': 'Português (Brasil)',
     'pt-PT': 'Português (Portugal)',
     'sr-Latn': 'Srpski (latinica)',
diff --git a/app/javascript/mastodon/actions/tags.js b/app/javascript/mastodon/actions/tags.js
index 08a08cda3..dda8c924b 100644
--- a/app/javascript/mastodon/actions/tags.js
+++ b/app/javascript/mastodon/actions/tags.js
@@ -60,7 +60,7 @@ export function fetchFollowedHashtagsRequest() {
   return {
     type: FOLLOWED_HASHTAGS_FETCH_REQUEST,
   };
-};
+}
 
 export function fetchFollowedHashtagsSuccess(followed_tags, next) {
   return {
@@ -68,14 +68,14 @@ export function fetchFollowedHashtagsSuccess(followed_tags, next) {
     followed_tags,
     next,
   };
-};
+}
 
 export function fetchFollowedHashtagsFail(error) {
   return {
     type: FOLLOWED_HASHTAGS_FETCH_FAIL,
     error,
   };
-};
+}
 
 export function expandFollowedHashtags() {
   return (dispatch, getState) => {
@@ -94,13 +94,13 @@ export function expandFollowedHashtags() {
       dispatch(expandFollowedHashtagsFail(error));
     });
   };
-};
+}
 
 export function expandFollowedHashtagsRequest() {
   return {
     type: FOLLOWED_HASHTAGS_EXPAND_REQUEST,
   };
-};
+}
 
 export function expandFollowedHashtagsSuccess(followed_tags, next) {
   return {
@@ -108,14 +108,14 @@ export function expandFollowedHashtagsSuccess(followed_tags, next) {
     followed_tags,
     next,
   };
-};
+}
 
 export function expandFollowedHashtagsFail(error) {
   return {
     type: FOLLOWED_HASHTAGS_EXPAND_FAIL,
     error,
   };
-};
+}
 
 export const followHashtag = name => (dispatch, getState) => {
   dispatch(followHashtagRequest(name));
diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js
index 7aebb124c..7706c3f88 100644
--- a/app/javascript/mastodon/components/account.js
+++ b/app/javascript/mastodon/components/account.js
@@ -47,27 +47,27 @@ class Account extends ImmutablePureComponent {
 
   handleFollow = () => {
     this.props.onFollow(this.props.account);
-  }
+  };
 
   handleBlock = () => {
     this.props.onBlock(this.props.account);
-  }
+  };
 
   handleMute = () => {
     this.props.onMute(this.props.account);
-  }
+  };
 
   handleMuteNotifications = () => {
     this.props.onMuteNotifications(this.props.account, true);
-  }
+  };
 
   handleUnmuteNotifications = () => {
     this.props.onMuteNotifications(this.props.account, false);
-  }
+  };
 
   handleAction = () => {
     this.props.onActionClick(this.props.account);
-  }
+  };
 
   render () {
     const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction, size } = this.props;
diff --git a/app/javascript/mastodon/components/animated_number.js b/app/javascript/mastodon/components/animated_number.js
index b1aebc73e..ce688f04f 100644
--- a/app/javascript/mastodon/components/animated_number.js
+++ b/app/javascript/mastodon/components/animated_number.js
@@ -38,13 +38,13 @@ export default class AnimatedNumber extends React.PureComponent {
     const { direction } = this.state;
 
     return { y: -1 * direction };
-  }
+  };
 
   willLeave = () => {
     const { direction } = this.state;
 
     return { y: spring(1 * direction, { damping: 35, stiffness: 400 }) };
-  }
+  };
 
   render () {
     const { value, obfuscate } = this.props;
diff --git a/app/javascript/mastodon/components/autosuggest_input.js b/app/javascript/mastodon/components/autosuggest_input.js
index b8f8c6f45..817a41b93 100644
--- a/app/javascript/mastodon/components/autosuggest_input.js
+++ b/app/javascript/mastodon/components/autosuggest_input.js
@@ -78,7 +78,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
     }
 
     this.props.onChange(e);
-  }
+  };
 
   onKeyDown = (e) => {
     const { suggestions, disabled } = this.props;
@@ -136,22 +136,22 @@ export default class AutosuggestInput extends ImmutablePureComponent {
     }
 
     this.props.onKeyDown(e);
-  }
+  };
 
   onBlur = () => {
     this.setState({ suggestionsHidden: true, focused: false });
-  }
+  };
 
   onFocus = () => {
     this.setState({ focused: true });
-  }
+  };
 
   onSuggestionClick = (e) => {
     const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
     e.preventDefault();
     this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
     this.input.focus();
-  }
+  };
 
   componentWillReceiveProps (nextProps) {
     if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
@@ -161,7 +161,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
 
   setInput = (c) => {
     this.input = c;
-  }
+  };
 
   renderSuggestion = (suggestion, i) => {
     const { selectedSuggestion } = this.state;
@@ -183,7 +183,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
         {inner}
       </div>
     );
-  }
+  };
 
   render () {
     const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength, lang } = this.props;
diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js
index b6c590916..c04491298 100644
--- a/app/javascript/mastodon/components/autosuggest_textarea.js
+++ b/app/javascript/mastodon/components/autosuggest_textarea.js
@@ -75,7 +75,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
     }
 
     this.props.onChange(e);
-  }
+  };
 
   onKeyDown = (e) => {
     const { suggestions, disabled } = this.props;
@@ -133,25 +133,25 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
     }
 
     this.props.onKeyDown(e);
-  }
+  };
 
   onBlur = () => {
     this.setState({ suggestionsHidden: true, focused: false });
-  }
+  };
 
   onFocus = (e) => {
     this.setState({ focused: true });
     if (this.props.onFocus) {
       this.props.onFocus(e);
     }
-  }
+  };
 
   onSuggestionClick = (e) => {
     const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
     e.preventDefault();
     this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
     this.textarea.focus();
-  }
+  };
 
   componentWillReceiveProps (nextProps) {
     if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
@@ -161,14 +161,14 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
 
   setTextarea = (c) => {
     this.textarea = c;
-  }
+  };
 
   onPaste = (e) => {
     if (e.clipboardData && e.clipboardData.files.length === 1) {
       this.props.onPaste(e.clipboardData.files);
       e.preventDefault();
     }
-  }
+  };
 
   renderSuggestion = (suggestion, i) => {
     const { selectedSuggestion } = this.state;
@@ -190,7 +190,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
         {inner}
       </div>
     );
-  }
+  };
 
   render () {
     const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, lang, children } = this.props;
diff --git a/app/javascript/mastodon/components/avatar.js b/app/javascript/mastodon/components/avatar.js
index e617c2889..013454ccf 100644
--- a/app/javascript/mastodon/components/avatar.js
+++ b/app/javascript/mastodon/components/avatar.js
@@ -27,12 +27,12 @@ export default class Avatar extends React.PureComponent {
   handleMouseEnter = () => {
     if (this.props.animate) return;
     this.setState({ hovering: true });
-  }
+  };
 
   handleMouseLeave = () => {
     if (this.props.animate) return;
     this.setState({ hovering: false });
-  }
+  };
 
   render () {
     const { account, size, animate, inline } = this.props;
diff --git a/app/javascript/mastodon/components/avatar_overlay.js b/app/javascript/mastodon/components/avatar_overlay.js
index 8d5d44ea5..034e8ba56 100644
--- a/app/javascript/mastodon/components/avatar_overlay.js
+++ b/app/javascript/mastodon/components/avatar_overlay.js
@@ -29,12 +29,12 @@ export default class AvatarOverlay extends React.PureComponent {
   handleMouseEnter = () => {
     if (this.props.animate) return;
     this.setState({ hovering: true });
-  }
+  };
 
   handleMouseLeave = () => {
     if (this.props.animate) return;
     this.setState({ hovering: false });
-  }
+  };
 
   render() {
     const { account, friend, animate, size, baseSize, overlaySize } = this.props;
diff --git a/app/javascript/mastodon/components/button.js b/app/javascript/mastodon/components/button.js
index 42ce01f38..a05a75e89 100644
--- a/app/javascript/mastodon/components/button.js
+++ b/app/javascript/mastodon/components/button.js
@@ -24,11 +24,11 @@ export default class Button extends React.PureComponent {
     if (!this.props.disabled && this.props.onClick) {
       this.props.onClick(e);
     }
-  }
+  };
 
   setRef = (c) => {
     this.node = c;
-  }
+  };
 
   focus() {
     this.node.focus();
diff --git a/app/javascript/mastodon/components/column.js b/app/javascript/mastodon/components/column.js
index 239824a4f..5780a1397 100644
--- a/app/javascript/mastodon/components/column.js
+++ b/app/javascript/mastodon/components/column.js
@@ -27,11 +27,11 @@ export default class Column extends React.PureComponent {
     }
 
     this._interruptScrollAnimation();
-  }
+  };
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   componentDidMount () {
     if (this.props.bindToDocument) {
diff --git a/app/javascript/mastodon/components/column_back_button.js b/app/javascript/mastodon/components/column_back_button.js
index d97622705..5bbf11652 100644
--- a/app/javascript/mastodon/components/column_back_button.js
+++ b/app/javascript/mastodon/components/column_back_button.js
@@ -20,7 +20,7 @@ export default class ColumnBackButton extends React.PureComponent {
     } else {
       this.context.router.history.goBack();
     }
-  }
+  };
 
   render () {
     const { multiColumn } = this.props;
diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js
index 7850a93ec..38f6ad60f 100644
--- a/app/javascript/mastodon/components/column_header.js
+++ b/app/javascript/mastodon/components/column_header.js
@@ -49,32 +49,32 @@ class ColumnHeader extends React.PureComponent {
     } else {
       this.context.router.history.goBack();
     }
-  }
+  };
 
   handleToggleClick = (e) => {
     e.stopPropagation();
     this.setState({ collapsed: !this.state.collapsed, animating: true });
-  }
+  };
 
   handleTitleClick = () => {
     this.props.onClick?.();
-  }
+  };
 
   handleMoveLeft = () => {
     this.props.onMove(-1);
-  }
+  };
 
   handleMoveRight = () => {
     this.props.onMove(1);
-  }
+  };
 
   handleBackClick = () => {
     this.historyBack();
-  }
+  };
 
   handleTransitionEnd = () => {
     this.setState({ animating: false });
-  }
+  };
 
   handlePin = () => {
     if (!this.props.pinned) {
@@ -82,7 +82,7 @@ class ColumnHeader extends React.PureComponent {
     }
 
     this.props.onPin();
-  }
+  };
 
   render () {
     const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
diff --git a/app/javascript/mastodon/components/dismissable_banner.js b/app/javascript/mastodon/components/dismissable_banner.js
index 1ee032056..47ca7e4bc 100644
--- a/app/javascript/mastodon/components/dismissable_banner.js
+++ b/app/javascript/mastodon/components/dismissable_banner.js
@@ -24,7 +24,7 @@ class DismissableBanner extends React.PureComponent {
   handleDismiss = () => {
     const { id } = this.props;
     this.setState({ visible: false }, () => bannerSettings.set(id, true));
-  }
+  };
 
   render () {
     const { visible } = this.state;
diff --git a/app/javascript/mastodon/components/display_name.js b/app/javascript/mastodon/components/display_name.js
index e9139ab0f..1dd9fb1d6 100644
--- a/app/javascript/mastodon/components/display_name.js
+++ b/app/javascript/mastodon/components/display_name.js
@@ -23,7 +23,7 @@ export default class DisplayName extends React.PureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -36,7 +36,7 @@ export default class DisplayName extends React.PureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   render () {
     const { others, localDomain } = this.props;
diff --git a/app/javascript/mastodon/components/domain.js b/app/javascript/mastodon/components/domain.js
index 697065d87..e09fa4591 100644
--- a/app/javascript/mastodon/components/domain.js
+++ b/app/javascript/mastodon/components/domain.js
@@ -19,7 +19,7 @@ class Account extends ImmutablePureComponent {
 
   handleDomainUnblock = () => {
     this.props.onUnblockDomain(this.props.domain);
-  }
+  };
 
   render () {
     const { domain, intl } = this.props;
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index 5897aada8..c04c513fb 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/app/javascript/mastodon/components/dropdown_menu.js
@@ -36,7 +36,7 @@ class DropdownMenu extends React.PureComponent {
     if (this.node && !this.node.contains(e.target)) {
       this.props.onClose();
     }
-  }
+  };
 
   componentDidMount () {
     document.addEventListener('click', this.handleDocumentClick, false);
@@ -56,11 +56,11 @@ class DropdownMenu extends React.PureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   setFocusRef = c => {
     this.focusedItem = c;
-  }
+  };
 
   handleKeyDown = e => {
     const items = Array.from(this.node.querySelectorAll('a, button'));
@@ -97,18 +97,18 @@ class DropdownMenu extends React.PureComponent {
       e.preventDefault();
       e.stopPropagation();
     }
-  }
+  };
 
   handleItemKeyPress = e => {
     if (e.key === 'Enter' || e.key === ' ') {
       this.handleClick(e);
     }
-  }
+  };
 
   handleClick = e => {
     const { onItemClick } = this.props;
     onItemClick(e);
-  }
+  };
 
   renderItem = (option, i) => {
     if (option === null) {
@@ -124,7 +124,7 @@ class DropdownMenu extends React.PureComponent {
         </a>
       </li>
     );
-  }
+  };
 
   render () {
     const { items, scrollable, renderHeader, loading } = this.props;
@@ -194,7 +194,7 @@ export default class Dropdown extends React.PureComponent {
     } else {
       this.props.onOpen(this.state.id, this.handleItemClick, type !== 'click');
     }
-  }
+  };
 
   handleClose = () => {
     if (this.activeElement) {
@@ -202,13 +202,13 @@ export default class Dropdown extends React.PureComponent {
       this.activeElement = null;
     }
     this.props.onClose(this.state.id);
-  }
+  };
 
   handleMouseDown = () => {
     if (!this.state.open) {
       this.activeElement = document.activeElement;
     }
-  }
+  };
 
   handleButtonKeyDown = (e) => {
     switch(e.key) {
@@ -217,7 +217,7 @@ export default class Dropdown extends React.PureComponent {
       this.handleMouseDown();
       break;
     }
-  }
+  };
 
   handleKeyPress = (e) => {
     switch(e.key) {
@@ -228,7 +228,7 @@ export default class Dropdown extends React.PureComponent {
       e.preventDefault();
       break;
     }
-  }
+  };
 
   handleItemClick = e => {
     const { onItemClick } = this.props;
@@ -247,25 +247,25 @@ export default class Dropdown extends React.PureComponent {
       e.preventDefault();
       this.context.router.history.push(item.to);
     }
-  }
+  };
 
   setTargetRef = c => {
     this.target = c;
-  }
+  };
 
   findTarget = () => {
     return this.target;
-  }
+  };
 
   componentWillUnmount = () => {
     if (this.state.id === this.props.openDropdownId) {
       this.handleClose();
     }
-  }
+  };
 
   close = () => {
     this.handleClose();
-  }
+  };
 
   render () {
     const {
diff --git a/app/javascript/mastodon/components/edited_timestamp/index.js b/app/javascript/mastodon/components/edited_timestamp/index.js
index bebf93886..b30d88572 100644
--- a/app/javascript/mastodon/components/edited_timestamp/index.js
+++ b/app/javascript/mastodon/components/edited_timestamp/index.js
@@ -36,7 +36,7 @@ class EditedTimestamp extends React.PureComponent {
     return (
       <FormattedMessage id='status.edited_x_times' defaultMessage='Edited {count, plural, one {{count} time} other {{count} times}}' values={{ count: items.size - 1 }} />
     );
-  }
+  };
 
   renderItem = (item, index, { onClick, onKeyPress }) => {
     const formattedDate = <RelativeTimestamp timestamp={item.get('created_at')} short={false} />;
@@ -53,7 +53,7 @@ class EditedTimestamp extends React.PureComponent {
         <button data-index={index} onClick={onClick} onKeyPress={onKeyPress}>{label}</button>
       </li>
     );
-  }
+  };
 
   render () {
     const { timestamp, intl, statusId } = this.props;
diff --git a/app/javascript/mastodon/components/error_boundary.js b/app/javascript/mastodon/components/error_boundary.js
index 02d5616d6..b711f1e46 100644
--- a/app/javascript/mastodon/components/error_boundary.js
+++ b/app/javascript/mastodon/components/error_boundary.js
@@ -64,7 +64,7 @@ export default class ErrorBoundary extends React.PureComponent {
 
     this.setState({ copied: true });
     setTimeout(() => this.setState({ copied: false }), 700);
-  }
+  };
 
   render() {
     const { hasError, copied, errorMessage } = this.state;
diff --git a/app/javascript/mastodon/components/gifv.js b/app/javascript/mastodon/components/gifv.js
index b775e5200..1f0f99b46 100644
--- a/app/javascript/mastodon/components/gifv.js
+++ b/app/javascript/mastodon/components/gifv.js
@@ -17,7 +17,7 @@ export default class GIFV extends React.PureComponent {
 
   handleLoadedData = () => {
     this.setState({ loading: false });
-  }
+  };
 
   componentWillReceiveProps (nextProps) {
     if (nextProps.src !== this.props.src) {
@@ -32,7 +32,7 @@ export default class GIFV extends React.PureComponent {
       e.stopPropagation();
       onClick();
     }
-  }
+  };
 
   render () {
     const { src, width, height, alt } = this.props;
diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js
index b7daf82a4..003692373 100644
--- a/app/javascript/mastodon/components/icon_button.js
+++ b/app/javascript/mastodon/components/icon_button.js
@@ -43,7 +43,7 @@ export default class IconButton extends React.PureComponent {
   state = {
     activate: false,
     deactivate: false,
-  }
+  };
 
   componentWillReceiveProps (nextProps) {
     if (!nextProps.animate) return;
@@ -61,25 +61,25 @@ export default class IconButton extends React.PureComponent {
     if (!this.props.disabled) {
       this.props.onClick(e);
     }
-  }
+  };
 
   handleKeyPress = (e) => {
     if (this.props.onKeyPress && !this.props.disabled) {
       this.props.onKeyPress(e);
     }
-  }
+  };
 
   handleMouseDown = (e) => {
     if (!this.props.disabled && this.props.onMouseDown) {
       this.props.onMouseDown(e);
     }
-  }
+  };
 
   handleKeyDown = (e) => {
     if (!this.props.disabled && this.props.onKeyDown) {
       this.props.onKeyDown(e);
     }
-  }
+  };
 
   render () {
     const style = {
diff --git a/app/javascript/mastodon/components/intersection_observer_article.js b/app/javascript/mastodon/components/intersection_observer_article.js
index 26f85fa40..c2feb003a 100644
--- a/app/javascript/mastodon/components/intersection_observer_article.js
+++ b/app/javascript/mastodon/components/intersection_observer_article.js
@@ -21,7 +21,7 @@ export default class IntersectionObserverArticle extends React.Component {
 
   state = {
     isHidden: false, // set to true in requestIdleCallback to trigger un-render
-  }
+  };
 
   shouldComponentUpdate (nextProps, nextState) {
     const isUnrendered = !this.state.isIntersecting && (this.state.isHidden || this.props.cachedHeight);
@@ -62,7 +62,7 @@ export default class IntersectionObserverArticle extends React.Component {
 
     scheduleIdleTask(this.calculateHeight);
     this.setState(this.updateStateAfterIntersection);
-  }
+  };
 
   updateStateAfterIntersection = (prevState) => {
     if (prevState.isIntersecting !== false && !this.entry.isIntersecting) {
@@ -72,7 +72,7 @@ export default class IntersectionObserverArticle extends React.Component {
       isIntersecting: this.entry.isIntersecting,
       isHidden: false,
     };
-  }
+  };
 
   calculateHeight = () => {
     const { onHeightChange, saveHeightKey, id } = this.props;
@@ -83,7 +83,7 @@ export default class IntersectionObserverArticle extends React.Component {
     if (onHeightChange && saveHeightKey) {
       onHeightChange(saveHeightKey, id, this.height);
     }
-  }
+  };
 
   hideIfNotIntersecting = () => {
     if (!this.componentMounted) {
@@ -95,11 +95,11 @@ export default class IntersectionObserverArticle extends React.Component {
     // this is to save DOM nodes and avoid using up too much memory.
     // See: https://github.com/mastodon/mastodon/issues/2900
     this.setState((prevState) => ({ isHidden: !prevState.isIntersecting }));
-  }
+  };
 
   handleRef = (node) => {
     this.node = node;
-  }
+  };
 
   render () {
     const { children, id, index, listLength, cachedHeight } = this.props;
diff --git a/app/javascript/mastodon/components/load_gap.js b/app/javascript/mastodon/components/load_gap.js
index a44d55d09..c50b245fc 100644
--- a/app/javascript/mastodon/components/load_gap.js
+++ b/app/javascript/mastodon/components/load_gap.js
@@ -19,7 +19,7 @@ class LoadGap extends React.PureComponent {
 
   handleClick = () => {
     this.props.onClick(this.props.maxId);
-  }
+  };
 
   render () {
     const { disabled, intl } = this.props;
diff --git a/app/javascript/mastodon/components/load_more.js b/app/javascript/mastodon/components/load_more.js
index 00e023ca2..150525214 100644
--- a/app/javascript/mastodon/components/load_more.js
+++ b/app/javascript/mastodon/components/load_more.js
@@ -8,11 +8,11 @@ export default class LoadMore extends React.PureComponent {
     onClick: PropTypes.func,
     disabled: PropTypes.bool,
     visible: PropTypes.bool,
-  }
+  };
 
   static defaultProps = {
     visible: true,
-  }
+  };
 
   render() {
     const { disabled, visible } = this.props;
diff --git a/app/javascript/mastodon/components/load_pending.js b/app/javascript/mastodon/components/load_pending.js
index 7e2702403..a75259146 100644
--- a/app/javascript/mastodon/components/load_pending.js
+++ b/app/javascript/mastodon/components/load_pending.js
@@ -7,7 +7,7 @@ export default class LoadPending extends React.PureComponent {
   static propTypes = {
     onClick: PropTypes.func,
     count: PropTypes.number,
-  }
+  };
 
   render() {
     const { count } = this.props;
diff --git a/app/javascript/mastodon/components/media_attachments.js b/app/javascript/mastodon/components/media_attachments.js
index d27720de4..565a30330 100644
--- a/app/javascript/mastodon/components/media_attachments.js
+++ b/app/javascript/mastodon/components/media_attachments.js
@@ -29,7 +29,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
     return (
       <div className='media-gallery' style={{ height, width }} />
     );
-  }
+  };
 
   renderLoadingVideoPlayer = () => {
     const { height, width } = this.props;
@@ -37,7 +37,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
     return (
       <div className='video-player' style={{ height, width }} />
     );
-  }
+  };
 
   renderLoadingAudioPlayer = () => {
     const { height, width } = this.props;
@@ -45,7 +45,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
     return (
       <div className='audio-player' style={{ height, width }} />
     );
-  }
+  };
 
   render () {
     const { status, width, height } = this.props;
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index e4a8be338..659a83375 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -40,14 +40,14 @@ class Item extends React.PureComponent {
     if (this.hoverToPlay()) {
       e.target.play();
     }
-  }
+  };
 
   handleMouseLeave = (e) => {
     if (this.hoverToPlay()) {
       e.target.pause();
       e.target.currentTime = 0;
     }
-  }
+  };
 
   getAutoPlay() {
     return this.props.autoplay || autoPlayGif;
@@ -71,11 +71,11 @@ class Item extends React.PureComponent {
     }
 
     e.stopPropagation();
-  }
+  };
 
   handleImageLoad = () => {
     this.setState({ loaded: true });
-  }
+  };
 
   render () {
     const { attachment, index, size, standalone, displayWidth, visible } = this.props;
@@ -277,11 +277,11 @@ class MediaGallery extends React.PureComponent {
     } else {
       this.setState({ visible: !this.state.visible });
     }
-  }
+  };
 
   handleClick = (index) => {
     this.props.onOpenMedia(this.props.media, index);
-  }
+  };
 
   handleRef = c => {
     this.node = c;
@@ -289,7 +289,7 @@ class MediaGallery extends React.PureComponent {
     if (this.node) {
       this._setDimensions();
     }
-  }
+  };
 
   _setDimensions () {
     const width = this.node.offsetWidth;
diff --git a/app/javascript/mastodon/components/modal_root.js b/app/javascript/mastodon/components/modal_root.js
index b894aeaf9..c0525c221 100644
--- a/app/javascript/mastodon/components/modal_root.js
+++ b/app/javascript/mastodon/components/modal_root.js
@@ -28,7 +28,7 @@ export default class ModalRoot extends React.PureComponent {
          && !!this.props.children) {
       this.props.onClose();
     }
-  }
+  };
 
   handleKeyDown = (e) => {
     if (e.key === 'Tab') {
@@ -49,7 +49,7 @@ export default class ModalRoot extends React.PureComponent {
         e.preventDefault();
       }
     }
-  }
+  };
 
   componentDidMount () {
     window.addEventListener('keyup', this.handleKeyUp, false);
@@ -122,11 +122,11 @@ export default class ModalRoot extends React.PureComponent {
 
   getSiblings = () => {
     return Array(...this.node.parentElement.childNodes).filter(node => node !== this.node);
-  }
+  };
 
   setRef = ref => {
     this.node = ref;
-  }
+  };
 
   render () {
     const { children, onClose } = this.props;
diff --git a/app/javascript/mastodon/components/picture_in_picture_placeholder.js b/app/javascript/mastodon/components/picture_in_picture_placeholder.js
index 19d15c18b..0effddef9 100644
--- a/app/javascript/mastodon/components/picture_in_picture_placeholder.js
+++ b/app/javascript/mastodon/components/picture_in_picture_placeholder.js
@@ -22,7 +22,7 @@ class PictureInPicturePlaceholder extends React.PureComponent {
   handleClick = () => {
     const { dispatch } = this.props;
     dispatch(removePictureInPicture());
-  }
+  };
 
   setRef = c => {
     this.node = c;
@@ -30,7 +30,7 @@ class PictureInPicturePlaceholder extends React.PureComponent {
     if (this.node) {
       this._setDimensions();
     }
-  }
+  };
 
   _setDimensions () {
     const width  = this.node.offsetWidth;
diff --git a/app/javascript/mastodon/components/poll.js b/app/javascript/mastodon/components/poll.js
index 3e643168e..95a900c49 100644
--- a/app/javascript/mastodon/components/poll.js
+++ b/app/javascript/mastodon/components/poll.js
@@ -95,7 +95,7 @@ class Poll extends ImmutablePureComponent {
       tmp[value] = true;
       this.setState({ selected: tmp });
     }
-  }
+  };
 
   handleOptionChange = ({ target: { value } }) => {
     this._toggleOption(value);
@@ -107,7 +107,7 @@ class Poll extends ImmutablePureComponent {
       e.stopPropagation();
       e.preventDefault();
     }
-  }
+  };
 
   handleVote = () => {
     if (this.props.disabled) {
diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js
index 91d04bf4d..4a6ffb149 100644
--- a/app/javascript/mastodon/components/scrollable_list.js
+++ b/app/javascript/mastodon/components/scrollable_list.js
@@ -97,7 +97,7 @@ class ScrollableList extends PureComponent {
     } else {
       return this.node;
     }
-  }
+  };
 
   setScrollTop = newScrollTop => {
     if (this.getScrollTop() !== newScrollTop) {
@@ -143,7 +143,7 @@ class ScrollableList extends PureComponent {
 
     this.mouseMovedRecently = false;
     this.scrollToTopOnMouseIdle = false;
-  }
+  };
 
   componentDidMount () {
     this.attachScrollListener();
@@ -161,25 +161,25 @@ class ScrollableList extends PureComponent {
     } else {
       return null;
     }
-  }
+  };
 
   getScrollTop = () => {
     return this._getScrollingElement().scrollTop;
-  }
+  };
 
   getScrollHeight = () => {
     return this._getScrollingElement().scrollHeight;
-  }
+  };
 
   getClientHeight = () => {
     return this._getScrollingElement().clientHeight;
-  }
+  };
 
   updateScrollBottom = (snapshot) => {
     const newScrollTop = this.getScrollHeight() - snapshot;
 
     this.setScrollTop(newScrollTop);
-  }
+  };
 
   getSnapshotBeforeUpdate (prevProps) {
     const someItemInserted = React.Children.count(prevProps.children) > 0 &&
@@ -206,7 +206,7 @@ class ScrollableList extends PureComponent {
     if (width && this.state.cachedMediaWidth !== width) {
       this.setState({ cachedMediaWidth: width });
     }
-  }
+  };
 
   componentWillUnmount () {
     this.clearMouseIdleTimer();
@@ -218,7 +218,7 @@ class ScrollableList extends PureComponent {
 
   onFullScreenChange = () => {
     this.setState({ fullscreen: isFullscreen() });
-  }
+  };
 
   attachIntersectionObserver () {
     let nodeOptions = {
@@ -269,12 +269,12 @@ class ScrollableList extends PureComponent {
 
   setRef = (c) => {
     this.node = c;
-  }
+  };
 
   handleLoadMore = e => {
     e.preventDefault();
     this.props.onLoadMore();
-  }
+  };
 
   handleLoadPending = e => {
     e.preventDefault();
@@ -286,7 +286,7 @@ class ScrollableList extends PureComponent {
     this.clearMouseIdleTimer();
     this.mouseIdleTimer = setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY);
     this.mouseMovedRecently = true;
-  }
+  };
 
   render () {
     const { children, scrollKey, trackScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, append, emptyMessage, onLoadMore } = this.props;
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index a1384ba58..6b8922608 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -135,7 +135,7 @@ class Status extends ImmutablePureComponent {
 
   handleToggleMediaVisibility = () => {
     this.setState({ showMedia: !this.state.showMedia });
-  }
+  };
 
   handleClick = e => {
     if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
@@ -147,11 +147,11 @@ class Status extends ImmutablePureComponent {
     }
 
     this.handleHotkeyOpen();
-  }
+  };
 
   handlePrependAccountClick = e => {
     this.handleAccountClick(e, false);
-  }
+  };
 
   handleAccountClick = (e, proper = true) => {
     if (e && (e.button !== 0 || e.ctrlKey || e.metaKey))  {
@@ -163,19 +163,19 @@ class Status extends ImmutablePureComponent {
     }
 
     this._openProfile(proper);
-  }
+  };
 
   handleExpandedToggle = () => {
     this.props.onToggleHidden(this._properStatus());
-  }
+  };
 
   handleCollapsedToggle = isCollapsed => {
     this.props.onToggleCollapsed(this._properStatus(), isCollapsed);
-  }
+  };
 
   handleTranslate = () => {
     this.props.onTranslate(this._properStatus());
-  }
+  };
 
   renderLoadingMediaGallery () {
     return <div className='media-gallery' style={{ height: '110px' }} />;
@@ -192,11 +192,11 @@ class Status extends ImmutablePureComponent {
   handleOpenVideo = (options) => {
     const status = this._properStatus();
     this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options);
-  }
+  };
 
   handleOpenMedia = (media, index) => {
     this.props.onOpenMedia(this._properStatus().get('id'), media, index);
-  }
+  };
 
   handleHotkeyOpenMedia = e => {
     const { onOpenMedia, onOpenVideo } = this.props;
@@ -211,32 +211,32 @@ class Status extends ImmutablePureComponent {
         onOpenMedia(status.get('id'), status.get('media_attachments'), 0);
       }
     }
-  }
+  };
 
   handleDeployPictureInPicture = (type, mediaProps) => {
     const { deployPictureInPicture } = this.props;
     const status = this._properStatus();
 
     deployPictureInPicture(status, type, mediaProps);
-  }
+  };
 
   handleHotkeyReply = e => {
     e.preventDefault();
     this.props.onReply(this._properStatus(), this.context.router.history);
-  }
+  };
 
   handleHotkeyFavourite = () => {
     this.props.onFavourite(this._properStatus());
-  }
+  };
 
   handleHotkeyBoost = e => {
     this.props.onReblog(this._properStatus(), e);
-  }
+  };
 
   handleHotkeyMention = e => {
     e.preventDefault();
     this.props.onMention(this._properStatus().get('account'), this.context.router.history);
-  }
+  };
 
   handleHotkeyOpen = () => {
     if (this.props.onClick) {
@@ -252,11 +252,11 @@ class Status extends ImmutablePureComponent {
     }
 
     router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
-  }
+  };
 
   handleHotkeyOpenProfile = () => {
     this._openProfile();
-  }
+  };
 
   _openProfile = (proper = true) => {
     const { router } = this.context;
@@ -267,32 +267,32 @@ class Status extends ImmutablePureComponent {
     }
 
     router.history.push(`/@${status.getIn(['account', 'acct'])}`);
-  }
+  };
 
   handleHotkeyMoveUp = e => {
     this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured'));
-  }
+  };
 
   handleHotkeyMoveDown = e => {
     this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured'));
-  }
+  };
 
   handleHotkeyToggleHidden = () => {
     this.props.onToggleHidden(this._properStatus());
-  }
+  };
 
   handleHotkeyToggleSensitive = () => {
     this.handleToggleMediaVisibility();
-  }
+  };
 
   handleUnfilterClick = e => {
     this.setState({ forceFilter: false });
     e.preventDefault();
-  }
+  };
 
   handleFilterClick = () => {
     this.setState({ forceFilter: true });
-  }
+  };
 
   _properStatus () {
     const { status } = this.props;
@@ -306,7 +306,7 @@ class Status extends ImmutablePureComponent {
 
   handleRef = c => {
     this.node = c;
-  }
+  };
 
   render () {
     let media = null;
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index 00fc94358..eeb376561 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -97,7 +97,7 @@ class StatusActionBar extends ImmutablePureComponent {
     'status',
     'relationship',
     'withDismiss',
-  ]
+  ];
 
   handleReplyClick = () => {
     const { signedIn } = this.context.identity;
@@ -107,7 +107,7 @@ class StatusActionBar extends ImmutablePureComponent {
     } else {
       this.props.onInteractionModal('reply', this.props.status);
     }
-  }
+  };
 
   handleShareClick = () => {
     navigator.share({
@@ -116,7 +116,7 @@ class StatusActionBar extends ImmutablePureComponent {
     }).catch((e) => {
       if (e.name !== 'AbortError') console.error(e);
     });
-  }
+  };
 
   handleFavouriteClick = () => {
     const { signedIn } = this.context.identity;
@@ -126,7 +126,7 @@ class StatusActionBar extends ImmutablePureComponent {
     } else {
       this.props.onInteractionModal('favourite', this.props.status);
     }
-  }
+  };
 
   handleReblogClick = e => {
     const { signedIn } = this.context.identity;
@@ -136,35 +136,35 @@ class StatusActionBar extends ImmutablePureComponent {
     } else {
       this.props.onInteractionModal('reblog', this.props.status);
     }
-  }
+  };
 
   handleBookmarkClick = () => {
     this.props.onBookmark(this.props.status);
-  }
+  };
 
   handleDeleteClick = () => {
     this.props.onDelete(this.props.status, this.context.router.history);
-  }
+  };
 
   handleRedraftClick = () => {
     this.props.onDelete(this.props.status, this.context.router.history, true);
-  }
+  };
 
   handleEditClick = () => {
     this.props.onEdit(this.props.status, this.context.router.history);
-  }
+  };
 
   handlePinClick = () => {
     this.props.onPin(this.props.status);
-  }
+  };
 
   handleMentionClick = () => {
     this.props.onMention(this.props.status.get('account'), this.context.router.history);
-  }
+  };
 
   handleDirectClick = () => {
     this.props.onDirect(this.props.status.get('account'), this.context.router.history);
-  }
+  };
 
   handleMuteClick = () => {
     const { status, relationship, onMute, onUnmute } = this.props;
@@ -175,7 +175,7 @@ class StatusActionBar extends ImmutablePureComponent {
     } else {
       onMute(account);
     }
-  }
+  };
 
   handleBlockClick = () => {
     const { status, relationship, onBlock, onUnblock } = this.props;
@@ -186,50 +186,50 @@ class StatusActionBar extends ImmutablePureComponent {
     } else {
       onBlock(status);
     }
-  }
+  };
 
   handleBlockDomain = () => {
     const { status, onBlockDomain } = this.props;
     const account = status.get('account');
 
     onBlockDomain(account.get('acct').split('@')[1]);
-  }
+  };
 
   handleUnblockDomain = () => {
     const { status, onUnblockDomain } = this.props;
     const account = status.get('account');
 
     onUnblockDomain(account.get('acct').split('@')[1]);
-  }
+  };
 
   handleOpen = () => {
     this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
-  }
+  };
 
   handleEmbed = () => {
     this.props.onEmbed(this.props.status);
-  }
+  };
 
   handleReport = () => {
     this.props.onReport(this.props.status);
-  }
+  };
 
   handleConversationMuteClick = () => {
     this.props.onMuteConversation(this.props.status);
-  }
+  };
 
   handleFilterClick = () => {
     this.props.onAddFilter(this.props.status);
-  }
+  };
 
   handleCopy = () => {
     const url = this.props.status.get('url');
     navigator.clipboard.writeText(url);
-  }
+  };
 
   handleHideClick = () => {
     this.props.onFilter();
-  }
+  };
 
   render () {
     const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index 6f3093d63..ece54621f 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -130,7 +130,7 @@ class StatusContent extends React.PureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -143,7 +143,7 @@ class StatusContent extends React.PureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   componentDidMount () {
     this._updateStatusLinks();
@@ -158,7 +158,7 @@ class StatusContent extends React.PureComponent {
       e.preventDefault();
       this.context.router.history.push(`/@${mention.get('acct')}`);
     }
-  }
+  };
 
   onHashtagClick = (hashtag, e) => {
     hashtag = hashtag.replace(/^#/, '');
@@ -167,11 +167,11 @@ class StatusContent extends React.PureComponent {
       e.preventDefault();
       this.context.router.history.push(`/tags/${hashtag}`);
     }
-  }
+  };
 
   handleMouseDown = (e) => {
     this.startXY = [e.clientX, e.clientY];
-  }
+  };
 
   handleMouseUp = (e) => {
     if (!this.startXY) {
@@ -194,7 +194,7 @@ class StatusContent extends React.PureComponent {
     }
 
     this.startXY = null;
-  }
+  };
 
   handleSpoilerClick = (e) => {
     e.preventDefault();
@@ -205,15 +205,15 @@ class StatusContent extends React.PureComponent {
     } else {
       this.setState({ hidden: !this.state.hidden });
     }
-  }
+  };
 
   handleTranslate = () => {
     this.props.onTranslate();
-  }
+  };
 
   setRef = (c) => {
     this.node = c;
-  }
+  };
 
   render () {
     const { status, intl } = this.props;
diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js
index 35e5749a3..3d513bbf8 100644
--- a/app/javascript/mastodon/components/status_list.js
+++ b/app/javascript/mastodon/components/status_list.js
@@ -34,7 +34,7 @@ export default class StatusList extends ImmutablePureComponent {
 
   getFeaturedStatusCount = () => {
     return this.props.featuredStatusIds ? this.props.featuredStatusIds.size : 0;
-  }
+  };
 
   getCurrentStatusIndex = (id, featured) => {
     if (featured) {
@@ -42,21 +42,21 @@ export default class StatusList extends ImmutablePureComponent {
     } else {
       return this.props.statusIds.indexOf(id) + this.getFeaturedStatusCount();
     }
-  }
+  };
 
   handleMoveUp = (id, featured) => {
     const elementIndex = this.getCurrentStatusIndex(id, featured) - 1;
     this._selectChild(elementIndex, true);
-  }
+  };
 
   handleMoveDown = (id, featured) => {
     const elementIndex = this.getCurrentStatusIndex(id, featured) + 1;
     this._selectChild(elementIndex, false);
-  }
+  };
 
   handleLoadOlder = debounce(() => {
     this.props.onLoadMore(this.props.statusIds.size > 0 ? this.props.statusIds.last() : undefined);
-  }, 300, { leading: true })
+  }, 300, { leading: true });
 
   _selectChild (index, align_top) {
     const container = this.node.node;
@@ -74,7 +74,7 @@ export default class StatusList extends ImmutablePureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   render () {
     const { statusIds, featuredStatusIds, onLoadMore, timelineId, ...other }  = this.props;
diff --git a/app/javascript/mastodon/containers/media_container.js b/app/javascript/mastodon/containers/media_container.js
index 6ee1f0bd8..25dc17444 100644
--- a/app/javascript/mastodon/containers/media_container.js
+++ b/app/javascript/mastodon/containers/media_container.js
@@ -39,7 +39,7 @@ export default class MediaContainer extends PureComponent {
     document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
 
     this.setState({ media, index });
-  }
+  };
 
   handleOpenVideo = (options) => {
     const { components } = this.props;
@@ -50,7 +50,7 @@ export default class MediaContainer extends PureComponent {
     document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
 
     this.setState({ media: mediaList, options });
-  }
+  };
 
   handleCloseMedia = () => {
     document.body.classList.remove('with-modals--active');
@@ -63,11 +63,11 @@ export default class MediaContainer extends PureComponent {
       backgroundColor: null,
       options: null,
     });
-  }
+  };
 
   setBackgroundColor = color => {
     this.setState({ backgroundColor: color });
-  }
+  };
 
   render () {
     const { locale, components } = this.props;
diff --git a/app/javascript/mastodon/extra_polyfills.js b/app/javascript/mastodon/extra_polyfills.js
index 6e8004f07..e6c69de8b 100644
--- a/app/javascript/mastodon/extra_polyfills.js
+++ b/app/javascript/mastodon/extra_polyfills.js
@@ -1,3 +1,2 @@
 import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only';
-import 'intersection-observer';
 import 'requestidlecallback';
diff --git a/app/javascript/mastodon/features/about/index.js b/app/javascript/mastodon/features/about/index.js
index e59f73738..dc1942c63 100644
--- a/app/javascript/mastodon/features/about/index.js
+++ b/app/javascript/mastodon/features/about/index.js
@@ -59,7 +59,7 @@ class Section extends React.PureComponent {
     const { collapsed } = this.state;
 
     this.setState({ collapsed: !collapsed }, () => onOpen && onOpen());
-  }
+  };
 
   render () {
     const { title, children } = this.props;
@@ -106,7 +106,7 @@ class About extends React.PureComponent {
   handleDomainBlocksOpen = () => {
     const { dispatch } = this.props;
     dispatch(fetchDomainBlocks());
-  }
+  };
 
   render () {
     const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props;
diff --git a/app/javascript/mastodon/features/account/components/account_note.js b/app/javascript/mastodon/features/account/components/account_note.js
index 1787ce1ab..fdacc7583 100644
--- a/app/javascript/mastodon/features/account/components/account_note.js
+++ b/app/javascript/mastodon/features/account/components/account_note.js
@@ -90,7 +90,7 @@ class AccountNote extends ImmutablePureComponent {
 
   setTextareaRef = c => {
     this.textarea = c;
-  }
+  };
 
   handleChange = e => {
     this.setState({ value: e.target.value, saving: false });
@@ -114,13 +114,13 @@ class AccountNote extends ImmutablePureComponent {
         }
       });
     }
-  }
+  };
 
   handleBlur = () => {
     if (this._isDirty()) {
       this._save();
     }
-  }
+  };
 
   _save (showMessage = true) {
     this.setState({ saving: true }, () => this.props.onSave(this.state.value));
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 46fb89f2f..539d72574 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -109,7 +109,7 @@ class Header extends ImmutablePureComponent {
 
   openEditProfile = () => {
     window.open('/settings/profile', '_blank');
-  }
+  };
 
   isStatusesPageActive = (match, location) => {
     if (!match) {
@@ -117,7 +117,7 @@ class Header extends ImmutablePureComponent {
     }
 
     return !location.pathname.match(/\/(followers|following)\/?$/);
-  }
+  };
 
   handleMouseEnter = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -130,7 +130,7 @@ class Header extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -143,14 +143,14 @@ class Header extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   handleAvatarClick = e => {
     if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       e.preventDefault();
       this.props.onOpenAvatar();
     }
-  }
+  };
 
   handleShare = () => {
     const { account } = this.props;
@@ -161,7 +161,7 @@ class Header extends ImmutablePureComponent {
     }).catch((e) => {
       if (e.name !== 'AbortError') console.error(e);
     });
-  }
+  };
 
   render () {
     const { account, hidden, intl, domain } = this.props;
@@ -296,9 +296,9 @@ class Header extends ImmutablePureComponent {
       if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
         menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` });
       }
-        if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
-          menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: remoteDomain }), href: `/admin/instances/${remoteDomain}` });
-        }
+      if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
+        menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: remoteDomain }), href: `/admin/instances/${remoteDomain}` });
+      }
     }
 
     const content         = { __html: account.get('note_emojified') };
diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.js
index 13fd7fe03..80e164af8 100644
--- a/app/javascript/mastodon/features/account_gallery/components/media_item.js
+++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js
@@ -22,20 +22,20 @@ export default class MediaItem extends ImmutablePureComponent {
 
   handleImageLoad = () => {
     this.setState({ loaded: true });
-  }
+  };
 
   handleMouseEnter = e => {
     if (this.hoverToPlay()) {
       e.target.play();
     }
-  }
+  };
 
   handleMouseLeave = e => {
     if (this.hoverToPlay()) {
       e.target.pause();
       e.target.currentTime = 0;
     }
-  }
+  };
 
   hoverToPlay () {
     return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1;
@@ -51,7 +51,7 @@ export default class MediaItem extends ImmutablePureComponent {
         this.setState({ visible: true });
       }
     }
-  }
+  };
 
   render () {
     const { attachment, displayWidth } = this.props;
diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js
index dc7556a9a..3942b57cb 100644
--- a/app/javascript/mastodon/features/account_gallery/index.js
+++ b/app/javascript/mastodon/features/account_gallery/index.js
@@ -47,7 +47,7 @@ class LoadMoreMedia extends ImmutablePureComponent {
 
   handleLoadMore = () => {
     this.props.onLoadMore(this.props.maxId);
-  }
+  };
 
   render () {
     return (
@@ -114,7 +114,7 @@ class AccountGallery extends ImmutablePureComponent {
     if (this.props.hasMore) {
       this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined);
     }
-  }
+  };
 
   handleScroll = e => {
     const { scrollTop, scrollHeight, clientHeight } = e.target;
@@ -123,7 +123,7 @@ class AccountGallery extends ImmutablePureComponent {
     if (150 > offset && !this.props.isLoading) {
       this.handleScrollToBottom();
     }
-  }
+  };
 
   handleLoadMore = maxId => {
     this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, { maxId }));
@@ -132,7 +132,7 @@ class AccountGallery extends ImmutablePureComponent {
   handleLoadOlder = e => {
     e.preventDefault();
     this.handleScrollToBottom();
-  }
+  };
 
   handleOpenMedia = attachment => {
     const { dispatch } = this.props;
@@ -148,13 +148,13 @@ class AccountGallery extends ImmutablePureComponent {
 
       dispatch(openModal('MEDIA', { media, index, statusId }));
     }
-  }
+  };
 
   handleRef = c => {
     if (c) {
       this.setState({ width: c.offsetWidth });
     }
-  }
+  };
 
   render () {
     const { attachments, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended } = this.props;
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js
index d67e307ff..bffa5554b 100644
--- a/app/javascript/mastodon/features/account_timeline/components/header.js
+++ b/app/javascript/mastodon/features/account_timeline/components/header.js
@@ -36,35 +36,35 @@ export default class Header extends ImmutablePureComponent {
 
   handleFollow = () => {
     this.props.onFollow(this.props.account);
-  }
+  };
 
   handleBlock = () => {
     this.props.onBlock(this.props.account);
-  }
+  };
 
   handleMention = () => {
     this.props.onMention(this.props.account, this.context.router.history);
-  }
+  };
 
   handleDirect = () => {
     this.props.onDirect(this.props.account, this.context.router.history);
-  }
+  };
 
   handleReport = () => {
     this.props.onReport(this.props.account);
-  }
+  };
 
   handleReblogToggle = () => {
     this.props.onReblogToggle(this.props.account);
-  }
+  };
 
   handleNotifyToggle = () => {
     this.props.onNotifyToggle(this.props.account);
-  }
+  };
 
   handleMute = () => {
     this.props.onMute(this.props.account);
-  }
+  };
 
   handleBlockDomain = () => {
     const domain = this.props.account.get('acct').split('@')[1];
@@ -72,7 +72,7 @@ export default class Header extends ImmutablePureComponent {
     if (!domain) return;
 
     this.props.onBlockDomain(domain);
-  }
+  };
 
   handleUnblockDomain = () => {
     const domain = this.props.account.get('acct').split('@')[1];
@@ -80,31 +80,31 @@ export default class Header extends ImmutablePureComponent {
     if (!domain) return;
 
     this.props.onUnblockDomain(domain);
-  }
+  };
 
   handleEndorseToggle = () => {
     this.props.onEndorseToggle(this.props.account);
-  }
+  };
 
   handleAddToList = () => {
     this.props.onAddToList(this.props.account);
-  }
+  };
 
   handleEditAccountNote = () => {
     this.props.onEditAccountNote(this.props.account);
-  }
+  };
 
   handleChangeLanguages = () => {
     this.props.onChangeLanguages(this.props.account);
-  }
+  };
 
   handleInteractionModal = () => {
     this.props.onInteractionModal(this.props.account);
-  }
+  };
 
   handleOpenAvatar = () => {
     this.props.onOpenAvatar(this.props.account);
-  }
+  };
 
   render () {
     const { account, hidden, hideTabs } = this.props;
diff --git a/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.js b/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.js
index 80b967642..9ee347bb5 100644
--- a/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.js
+++ b/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.js
@@ -20,7 +20,7 @@ class LimitedAccountHint extends React.PureComponent {
   static propTypes = {
     accountId: PropTypes.string.isRequired,
     reveal: PropTypes.func,
-  }
+  };
 
   render () {
     const { reveal } = this.props;
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js
index bdb430a33..337977fde 100644
--- a/app/javascript/mastodon/features/account_timeline/index.js
+++ b/app/javascript/mastodon/features/account_timeline/index.js
@@ -145,7 +145,7 @@ class AccountTimeline extends ImmutablePureComponent {
 
   handleLoadMore = maxId => {
     this.props.dispatch(expandAccountTimeline(this.props.accountId, { maxId, withReplies: this.props.withReplies, tagged: this.props.params.tagged }));
-  }
+  };
 
   render () {
     const { accountId, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, hidden, multiColumn, remote, remoteUrl } = this.props;
diff --git a/app/javascript/mastodon/features/audio/index.js b/app/javascript/mastodon/features/audio/index.js
index 21a453d2c..a55658360 100644
--- a/app/javascript/mastodon/features/audio/index.js
+++ b/app/javascript/mastodon/features/audio/index.js
@@ -75,7 +75,7 @@ class Audio extends React.PureComponent {
     if (this.player) {
       this._setDimensions();
     }
-  }
+  };
 
   _pack() {
     return {
@@ -105,11 +105,11 @@ class Audio extends React.PureComponent {
 
   setSeekRef = c => {
     this.seek = c;
-  }
+  };
 
   setVolumeRef = c => {
     this.volume = c;
-  }
+  };
 
   setAudioRef = c => {
     this.audio = c;
@@ -118,13 +118,13 @@ class Audio extends React.PureComponent {
       this.audio.volume = 1;
       this.audio.muted = false;
     }
-  }
+  };
 
   setCanvasRef = c => {
     this.canvas = c;
 
     this.visualizer.setCanvas(c);
-  }
+  };
 
   componentDidMount () {
     window.addEventListener('scroll', this.handleScroll);
@@ -163,7 +163,7 @@ class Audio extends React.PureComponent {
     } else {
       this.setState({ paused: true }, () => this.audio.pause());
     }
-  }
+  };
 
   handleResize = debounce(() => {
     if (this.player) {
@@ -181,7 +181,7 @@ class Audio extends React.PureComponent {
     }
 
     this._renderCanvas();
-  }
+  };
 
   handlePause = () => {
     this.setState({ paused: true });
@@ -189,7 +189,7 @@ class Audio extends React.PureComponent {
     if (this.audioContext) {
       this.audioContext.suspend();
     }
-  }
+  };
 
   handleProgress = () => {
     const lastTimeRange = this.audio.buffered.length - 1;
@@ -197,7 +197,7 @@ class Audio extends React.PureComponent {
     if (lastTimeRange > -1) {
       this.setState({ buffer: Math.ceil(this.audio.buffered.end(lastTimeRange) / this.audio.duration * 100) });
     }
-  }
+  };
 
   toggleMute = () => {
     const muted = !this.state.muted;
@@ -207,7 +207,7 @@ class Audio extends React.PureComponent {
         this.gainNode.gain.value = muted ? 0 : this.state.volume;
       }
     });
-  }
+  };
 
   toggleReveal = () => {
     if (this.props.onToggleVisibility) {
@@ -215,7 +215,7 @@ class Audio extends React.PureComponent {
     } else {
       this.setState({ revealed: !this.state.revealed });
     }
-  }
+  };
 
   handleVolumeMouseDown = e => {
     document.addEventListener('mousemove', this.handleMouseVolSlide, true);
@@ -227,14 +227,14 @@ class Audio extends React.PureComponent {
 
     e.preventDefault();
     e.stopPropagation();
-  }
+  };
 
   handleVolumeMouseUp = () => {
     document.removeEventListener('mousemove', this.handleMouseVolSlide, true);
     document.removeEventListener('mouseup', this.handleVolumeMouseUp, true);
     document.removeEventListener('touchmove', this.handleMouseVolSlide, true);
     document.removeEventListener('touchend', this.handleVolumeMouseUp, true);
-  }
+  };
 
   handleMouseDown = e => {
     document.addEventListener('mousemove', this.handleMouseMove, true);
@@ -248,7 +248,7 @@ class Audio extends React.PureComponent {
 
     e.preventDefault();
     e.stopPropagation();
-  }
+  };
 
   handleMouseUp = () => {
     document.removeEventListener('mousemove', this.handleMouseMove, true);
@@ -258,7 +258,7 @@ class Audio extends React.PureComponent {
 
     this.setState({ dragging: false });
     this.audio.play();
-  }
+  };
 
   handleMouseMove = throttle(e => {
     const { x } = getPointerPosition(this.seek, e);
@@ -276,7 +276,7 @@ class Audio extends React.PureComponent {
       currentTime: this.audio.currentTime,
       duration: this.audio.duration,
     });
-  }
+  };
 
   handleMouseVolSlide = throttle(e => {
     const { x } = getPointerPosition(this.volume, e);
@@ -311,11 +311,11 @@ class Audio extends React.PureComponent {
 
   handleMouseEnter = () => {
     this.setState({ hovered: true });
-  }
+  };
 
   handleMouseLeave = () => {
     this.setState({ hovered: false });
-  }
+  };
 
   handleLoadedData = () => {
     const { autoPlay, currentTime } = this.props;
@@ -327,7 +327,7 @@ class Audio extends React.PureComponent {
     if (autoPlay) {
       this.togglePlay();
     }
-  }
+  };
 
   _initAudioContext () {
     const AudioContext = window.AudioContext || window.webkitAudioContext;
@@ -361,7 +361,7 @@ class Audio extends React.PureComponent {
     }).catch(err => {
       console.error(err);
     });
-  }
+  };
 
   _renderCanvas () {
     requestAnimationFrame(() => {
@@ -432,7 +432,7 @@ class Audio extends React.PureComponent {
       e.stopPropagation();
       this.togglePlay();
     }
-  }
+  };
 
   handleKeyDown = e => {
     switch(e.key) {
@@ -457,7 +457,7 @@ class Audio extends React.PureComponent {
       this.seekBy(10);
       break;
     }
-  }
+  };
 
   render () {
     const { src, intl, alt, editable, autoPlay, sensitive, blurhash } = this.props;
diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.js b/app/javascript/mastodon/features/bookmarked_statuses/index.js
index 097be17c9..8ef7855c1 100644
--- a/app/javascript/mastodon/features/bookmarked_statuses/index.js
+++ b/app/javascript/mastodon/features/bookmarked_statuses/index.js
@@ -48,24 +48,24 @@ class Bookmarks extends ImmutablePureComponent {
     } else {
       dispatch(addColumn('BOOKMARKS', {}));
     }
-  }
+  };
 
   handleMove = (dir) => {
     const { columnId, dispatch } = this.props;
     dispatch(moveColumn(columnId, dir));
-  }
+  };
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleLoadMore = debounce(() => {
     this.props.dispatch(expandBookmarkedStatuses());
-  }, 300, { leading: true })
+  }, 300, { leading: true });
 
   render () {
     const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js
index 7b3f8845f..4dbd55cf2 100644
--- a/app/javascript/mastodon/features/community_timeline/index.js
+++ b/app/javascript/mastodon/features/community_timeline/index.js
@@ -60,16 +60,16 @@ class CommunityTimeline extends React.PureComponent {
     } else {
       dispatch(addColumn('COMMUNITY', { other: { onlyMedia } }));
     }
-  }
+  };
 
   handleMove = (dir) => {
     const { columnId, dispatch } = this.props;
     dispatch(moveColumn(columnId, dir));
-  }
+  };
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   componentDidMount () {
     const { dispatch, onlyMedia } = this.props;
@@ -109,13 +109,13 @@ class CommunityTimeline extends React.PureComponent {
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleLoadMore = maxId => {
     const { dispatch, onlyMedia } = this.props;
 
     dispatch(expandCommunityTimeline({ maxId, onlyMedia }));
-  }
+  };
 
   render () {
     const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props;
diff --git a/app/javascript/mastodon/features/compose/components/action_bar.js b/app/javascript/mastodon/features/compose/components/action_bar.js
index 90c85321e..ee584cb1b 100644
--- a/app/javascript/mastodon/features/compose/components/action_bar.js
+++ b/app/javascript/mastodon/features/compose/components/action_bar.js
@@ -31,7 +31,7 @@ class ActionBar extends React.PureComponent {
 
   handleLogout = () => {
     this.props.onLogout();
-  }
+  };
 
   render () {
     const { intl } = this.props;
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index 91d21f12d..a8379c59c 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -74,17 +74,17 @@ class ComposeForm extends ImmutablePureComponent {
 
   handleChange = (e) => {
     this.props.onChange(e.target.value);
-  }
+  };
 
   handleKeyDown = (e) => {
     if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
       this.handleSubmit();
     }
-  }
+  };
 
   getFulltextForCharacterCounting = () => {
     return [this.props.spoiler? this.props.spoilerText: '', countableText(this.props.text)].join('');
-  }
+  };
 
   canSubmit = () => {
     const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
@@ -92,7 +92,7 @@ class ComposeForm extends ImmutablePureComponent {
     const isOnlyWhitespace = fulltext.length !== 0 && fulltext.trim().length === 0;
 
     return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > maxChars || (isOnlyWhitespace && !anyMedia));
-  }
+  };
 
   handleSubmit = (e) => {
     if (this.props.text !== this.autosuggestTextarea.textarea.value) {
@@ -110,27 +110,27 @@ class ComposeForm extends ImmutablePureComponent {
     if (e) {
       e.preventDefault();
     }
-  }
+  };
 
   onSuggestionsClearRequested = () => {
     this.props.onClearSuggestions();
-  }
+  };
 
   onSuggestionsFetchRequested = (token) => {
     this.props.onFetchSuggestions(token);
-  }
+  };
 
   onSuggestionSelected = (tokenStart, token, value) => {
     this.props.onSuggestionSelected(tokenStart, token, value, ['text']);
-  }
+  };
 
   onSpoilerSuggestionSelected = (tokenStart, token, value) => {
     this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']);
-  }
+  };
 
   handleChangeSpoilerText = (e) => {
     this.props.onChangeSpoilerText(e.target.value);
-  }
+  };
 
   handleFocus = () => {
     if (this.composeForm && !this.props.singleColumn) {
@@ -139,7 +139,7 @@ class ComposeForm extends ImmutablePureComponent {
         this.composeForm.scrollIntoView();
       }
     }
-  }
+  };
 
   componentDidMount () {
     this._updateFocusAndSelection({ });
@@ -185,15 +185,15 @@ class ComposeForm extends ImmutablePureComponent {
         this.autosuggestTextarea.textarea.focus();
       }
     }
-  }
+  };
 
   setAutosuggestTextarea = (c) => {
     this.autosuggestTextarea = c;
-  }
+  };
 
   setSpoilerText = (c) => {
     this.spoilerText = c;
-  }
+  };
 
   setRef = c => {
     this.composeForm = c;
@@ -205,7 +205,7 @@ class ComposeForm extends ImmutablePureComponent {
     const needsSpace   = data.custom && position > 0 && !allowedAroundShortCode.includes(text[position - 1]);
 
     this.props.onPickEmoji(position, data, needsSpace);
-  }
+  };
 
   render () {
     const { intl, onPaste, autoFocus } = this.props;
diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
index 76c9cda81..79378454d 100644
--- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
@@ -57,7 +57,7 @@ class ModifierPickerMenu extends React.PureComponent {
 
   handleClick = e => {
     this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1);
-  }
+  };
 
   componentWillReceiveProps (nextProps) {
     if (nextProps.active) {
@@ -75,7 +75,7 @@ class ModifierPickerMenu extends React.PureComponent {
     if (this.node && !this.node.contains(e.target)) {
       this.props.onClose();
     }
-  }
+  };
 
   attachListeners () {
     document.addEventListener('click', this.handleDocumentClick, false);
@@ -89,7 +89,7 @@ class ModifierPickerMenu extends React.PureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   render () {
     const { active } = this.props;
@@ -124,12 +124,12 @@ class ModifierPicker extends React.PureComponent {
     } else {
       this.props.onOpen();
     }
-  }
+  };
 
   handleSelect = modifier => {
     this.props.onChange(modifier);
     this.props.onClose();
-  }
+  };
 
   render () {
     const { active, modifier } = this.props;
@@ -174,7 +174,7 @@ class EmojiPickerMenu extends React.PureComponent {
     if (this.node && !this.node.contains(e.target)) {
       this.props.onClose();
     }
-  }
+  };
 
   componentDidMount () {
     document.addEventListener('click', this.handleDocumentClick, false);
@@ -198,7 +198,7 @@ class EmojiPickerMenu extends React.PureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   getI18n = () => {
     const { intl } = this.props;
@@ -219,7 +219,7 @@ class EmojiPickerMenu extends React.PureComponent {
         custom: intl.formatMessage(messages.custom),
       },
     };
-  }
+  };
 
   handleClick = (emoji, event) => {
     if (!emoji.native) {
@@ -229,19 +229,19 @@ class EmojiPickerMenu extends React.PureComponent {
       this.props.onClose();
     }
     this.props.onPick(emoji);
-  }
+  };
 
   handleModifierOpen = () => {
     this.setState({ modifierOpen: true });
-  }
+  };
 
   handleModifierClose = () => {
     this.setState({ modifierOpen: false });
-  }
+  };
 
   handleModifierChange = modifier => {
     this.props.onSkinTone(modifier);
-  }
+  };
 
   render () {
     const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props;
@@ -325,7 +325,7 @@ class EmojiPickerDropdown extends React.PureComponent {
 
   setRef = (c) => {
     this.dropdown = c;
-  }
+  };
 
   onShowDropdown = () => {
     this.setState({ active: true });
@@ -342,11 +342,11 @@ class EmojiPickerDropdown extends React.PureComponent {
         this.setState({ loading: false, active: false });
       });
     }
-  }
+  };
 
   onHideDropdown = () => {
     this.setState({ active: false });
-  }
+  };
 
   onToggle = (e) => {
     if (!this.state.loading && (!e.key || e.key === 'Enter')) {
@@ -356,21 +356,21 @@ class EmojiPickerDropdown extends React.PureComponent {
         this.onShowDropdown(e);
       }
     }
-  }
+  };
 
   handleKeyDown = e => {
     if (e.key === 'Escape') {
       this.onHideDropdown();
     }
-  }
+  };
 
   setTargetRef = c => {
     this.target = c;
-  }
+  };
 
   findTarget = () => {
     return this.target;
-  }
+  };
 
   render () {
     const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, button } = this.props;
diff --git a/app/javascript/mastodon/features/compose/components/language_dropdown.js b/app/javascript/mastodon/features/compose/components/language_dropdown.js
index 2dd406b4b..d96d39f23 100644
--- a/app/javascript/mastodon/features/compose/components/language_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/language_dropdown.js
@@ -40,7 +40,7 @@ class LanguageDropdownMenu extends React.PureComponent {
     if (this.node && !this.node.contains(e.target)) {
       this.props.onClose();
     }
-  }
+  };
 
   componentDidMount () {
     document.addEventListener('click', this.handleDocumentClick, false);
@@ -63,15 +63,15 @@ class LanguageDropdownMenu extends React.PureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   setListRef = c => {
     this.listNode = c;
-  }
+  };
 
   handleSearchChange = ({ target }) => {
     this.setState({ searchValue: target.value });
-  }
+  };
 
   search () {
     const { languages, value, frequentlyUsedLanguages } = this.props;
@@ -122,7 +122,7 @@ class LanguageDropdownMenu extends React.PureComponent {
 
     this.props.onClose();
     this.props.onChange(value);
-  }
+  };
 
   handleKeyDown = e => {
     const { onClose } = this.props;
@@ -163,7 +163,7 @@ class LanguageDropdownMenu extends React.PureComponent {
       e.preventDefault();
       e.stopPropagation();
     }
-  }
+  };
 
   handleSearchKeyDown = e => {
     const { onChange, onClose } = this.props;
@@ -199,11 +199,11 @@ class LanguageDropdownMenu extends React.PureComponent {
 
       break;
     }
-  }
+  };
 
   handleClear = () => {
     this.setState({ searchValue: '' });
-  }
+  };
 
   renderItem = lang => {
     const { value } = this.props;
@@ -213,7 +213,7 @@ class LanguageDropdownMenu extends React.PureComponent {
         <span className='language-dropdown__dropdown__results__item__native-name'>{lang[2]}</span> <span className='language-dropdown__dropdown__results__item__common-name'>({lang[1]})</span>
       </div>
     );
-  }
+  };
 
   render () {
     const { intl } = this.props;
@@ -259,7 +259,7 @@ class LanguageDropdown extends React.PureComponent {
     }
 
     this.setState({ open: !this.state.open });
-  }
+  };
 
   handleClose = () => {
     const { value, onClose } = this.props;
@@ -270,24 +270,24 @@ class LanguageDropdown extends React.PureComponent {
 
     this.setState({ open: false });
     onClose(value);
-  }
+  };
 
   handleChange = value => {
     const { onChange } = this.props;
     onChange(value);
-  }
+  };
 
   setTargetRef = c => {
     this.target = c;
-  }
+  };
 
   findTarget = () => {
     return this.target;
-  }
+  };
 
   handleOverlayEnter = (state) => {
     this.setState({ placement: state.placement });
-  }
+  };
 
   render () {
     const { value, intl, frequentlyUsedLanguages } = this.props;
diff --git a/app/javascript/mastodon/features/compose/components/poll_button.js b/app/javascript/mastodon/features/compose/components/poll_button.js
index 76f96bfa4..ff7a104aa 100644
--- a/app/javascript/mastodon/features/compose/components/poll_button.js
+++ b/app/javascript/mastodon/features/compose/components/poll_button.js
@@ -27,7 +27,7 @@ class PollButton extends React.PureComponent {
 
   handleClick = () => {
     this.props.onClick();
-  }
+  };
 
   render () {
     const { intl, active, unavailable, disabled } = this.props;
diff --git a/app/javascript/mastodon/features/compose/components/poll_form.js b/app/javascript/mastodon/features/compose/components/poll_form.js
index 3aa527161..a8bc7d0ac 100644
--- a/app/javascript/mastodon/features/compose/components/poll_form.js
+++ b/app/javascript/mastodon/features/compose/components/poll_form.js
@@ -25,6 +25,7 @@ class Option extends React.PureComponent {
 
   static propTypes = {
     title: PropTypes.string.isRequired,
+    lang: PropTypes.string,
     index: PropTypes.number.isRequired,
     isPollMultiple: PropTypes.bool,
     autoFocus: PropTypes.bool,
@@ -57,22 +58,22 @@ class Option extends React.PureComponent {
     if (e.key === 'Enter' || e.key === ' ') {
       this.handleToggleMultiple(e);
     }
-  }
+  };
 
   onSuggestionsClearRequested = () => {
     this.props.onClearSuggestions();
-  }
+  };
 
   onSuggestionsFetchRequested = (token) => {
     this.props.onFetchSuggestions(token);
-  }
+  };
 
   onSuggestionSelected = (tokenStart, token, value) => {
     this.props.onSuggestionSelected(tokenStart, token, value, ['poll', 'options', this.props.index]);
-  }
+  };
 
   render () {
-    const { isPollMultiple, title, index, autoFocus, intl } = this.props;
+    const { isPollMultiple, title, lang, index, autoFocus, intl } = this.props;
 
     return (
       <li>
@@ -91,6 +92,7 @@ class Option extends React.PureComponent {
             placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
             maxLength={100}
             value={title}
+            lang={lang}
             onChange={this.handleOptionTitleChange}
             suggestions={this.props.suggestions}
             onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
@@ -116,6 +118,7 @@ class PollForm extends ImmutablePureComponent {
 
   static propTypes = {
     options: ImmutablePropTypes.list,
+    lang: PropTypes.string,
     expiresIn: PropTypes.number,
     isMultiple: PropTypes.bool,
     onChangeOption: PropTypes.func.isRequired,
@@ -142,7 +145,7 @@ class PollForm extends ImmutablePureComponent {
   };
 
   render () {
-    const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl, ...other } = this.props;
+    const { options, lang, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl, ...other } = this.props;
 
     if (!options) {
       return null;
@@ -153,7 +156,7 @@ class PollForm extends ImmutablePureComponent {
     return (
       <div className='compose-form__poll-wrapper'>
         <ul>
-          {options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} onToggleMultiple={this.handleToggleMultiple} autoFocus={i === autoFocusIndex} {...other} />)}
+          {options.map((title, i) => <Option title={title} lang={lang} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} onToggleMultiple={this.handleToggleMultiple} autoFocus={i === autoFocusIndex} {...other} />)}
         </ul>
 
         <div className='poll__footer'>
diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
index 545b67eda..ffd1094cd 100644
--- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
@@ -35,7 +35,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
     if (this.node && !this.node.contains(e.target)) {
       this.props.onClose();
     }
-  }
+  };
 
   handleKeyDown = e => {
     const { items } = this.props;
@@ -79,7 +79,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
       e.preventDefault();
       e.stopPropagation();
     }
-  }
+  };
 
   handleClick = e => {
     const value = e.currentTarget.getAttribute('data-index');
@@ -88,7 +88,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
 
     this.props.onClose();
     this.props.onChange(value);
-  }
+  };
 
   componentDidMount () {
     document.addEventListener('click', this.handleDocumentClick, false);
@@ -103,11 +103,11 @@ class PrivacyDropdownMenu extends React.PureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   setFocusRef = c => {
     this.focusedItem = c;
-  }
+  };
 
   render () {
     const { style, items, value } = this.props;
@@ -168,7 +168,7 @@ class PrivacyDropdown extends React.PureComponent {
       }
       this.setState({ open: !this.state.open });
     }
-  }
+  };
 
   handleModalActionClick = (e) => {
     e.preventDefault();
@@ -177,7 +177,7 @@ class PrivacyDropdown extends React.PureComponent {
 
     this.props.onModalClose();
     this.props.onChange(value);
-  }
+  };
 
   handleKeyDown = e => {
     switch(e.key) {
@@ -185,13 +185,13 @@ class PrivacyDropdown extends React.PureComponent {
       this.handleClose();
       break;
     }
-  }
+  };
 
   handleMouseDown = () => {
     if (!this.state.open) {
       this.activeElement = document.activeElement;
     }
-  }
+  };
 
   handleButtonKeyDown = (e) => {
     switch(e.key) {
@@ -200,18 +200,18 @@ class PrivacyDropdown extends React.PureComponent {
       this.handleMouseDown();
       break;
     }
-  }
+  };
 
   handleClose = () => {
     if (this.state.open && this.activeElement) {
       this.activeElement.focus({ preventScroll: true });
     }
     this.setState({ open: false });
-  }
+  };
 
   handleChange = value => {
     this.props.onChange(value);
-  }
+  };
 
   componentWillMount () {
     const { intl: { formatMessage } } = this.props;
@@ -231,15 +231,15 @@ class PrivacyDropdown extends React.PureComponent {
 
   setTargetRef = c => {
     this.target = c;
-  }
+  };
 
   findTarget = () => {
     return this.target;
-  }
+  };
 
   handleOverlayEnter = (state) => {
     this.setState({ placement: state.placement });
-  }
+  };
 
   render () {
     const { value, container, disabled, intl } = this.props;
diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.js b/app/javascript/mastodon/features/compose/components/reply_indicator.js
index fc236882a..98b142ab8 100644
--- a/app/javascript/mastodon/features/compose/components/reply_indicator.js
+++ b/app/javascript/mastodon/features/compose/components/reply_indicator.js
@@ -27,14 +27,14 @@ class ReplyIndicator extends ImmutablePureComponent {
 
   handleClick = () => {
     this.props.onCancel();
-  }
+  };
 
   handleAccountClick = (e) => {
     if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       e.preventDefault();
       this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
     }
-  }
+  };
 
   render () {
     const { status, intl } = this.props;
diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js
index 5820f8ca2..0539c6b80 100644
--- a/app/javascript/mastodon/features/compose/components/search.js
+++ b/app/javascript/mastodon/features/compose/components/search.js
@@ -58,11 +58,11 @@ class Search extends React.PureComponent {
 
   setRef = c => {
     this.searchForm = c;
-  }
+  };
 
   handleChange = (e) => {
     this.props.onChange(e.target.value);
-  }
+  };
 
   handleClear = (e) => {
     e.preventDefault();
@@ -70,7 +70,7 @@ class Search extends React.PureComponent {
     if (this.props.value.length > 0 || this.props.submitted) {
       this.props.onClear();
     }
-  }
+  };
 
   handleKeyUp = (e) => {
     if (e.key === 'Enter') {
@@ -84,7 +84,7 @@ class Search extends React.PureComponent {
     } else if (e.key === 'Escape') {
       document.querySelector('.ui').parentElement.focus();
     }
-  }
+  };
 
   handleFocus = () => {
     this.setState({ expanded: true });
@@ -96,15 +96,15 @@ class Search extends React.PureComponent {
         this.searchForm.scrollIntoView();
       }
     }
-  }
+  };
 
   handleBlur = () => {
     this.setState({ expanded: false });
-  }
+  };
 
   findTarget = () => {
     return this.searchForm;
-  }
+  };
 
   render () {
     const { intl, value, submitted } = this.props;
diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js
index af06ce1bf..20f58ee75 100644
--- a/app/javascript/mastodon/features/compose/components/upload.js
+++ b/app/javascript/mastodon/features/compose/components/upload.js
@@ -22,12 +22,12 @@ export default class Upload extends ImmutablePureComponent {
   handleUndoClick = e => {
     e.stopPropagation();
     this.props.onUndo(this.props.media.get('id'));
-  }
+  };
 
   handleFocalPointClick = e => {
     e.stopPropagation();
     this.props.onOpenFocalPoint(this.props.media.get('id'));
-  }
+  };
 
   render () {
     const { media } = this.props;
diff --git a/app/javascript/mastodon/features/compose/components/upload_button.js b/app/javascript/mastodon/features/compose/components/upload_button.js
index 9cb36167a..964340d82 100644
--- a/app/javascript/mastodon/features/compose/components/upload_button.js
+++ b/app/javascript/mastodon/features/compose/components/upload_button.js
@@ -41,15 +41,15 @@ class UploadButton extends ImmutablePureComponent {
     if (e.target.files.length > 0) {
       this.props.onSelectFile(e.target.files);
     }
-  }
+  };
 
   handleClick = () => {
     this.fileElement.click();
-  }
+  };
 
   setRef = (c) => {
     this.fileElement = c;
-  }
+  };
 
   render () {
     const { intl, resetFileKey, unavailable, disabled, acceptContentTypes } = this.props;
diff --git a/app/javascript/mastodon/features/compose/containers/poll_form_container.js b/app/javascript/mastodon/features/compose/containers/poll_form_container.js
index 1401371d0..c47fc7500 100644
--- a/app/javascript/mastodon/features/compose/containers/poll_form_container.js
+++ b/app/javascript/mastodon/features/compose/containers/poll_form_container.js
@@ -10,6 +10,7 @@ import {
 const mapStateToProps = state => ({
   suggestions: state.getIn(['compose', 'suggestions']),
   options: state.getIn(['compose', 'poll', 'options']),
+  lang: state.getIn(['compose', 'language']),
   expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
   isMultiple: state.getIn(['compose', 'poll', 'multiple']),
 });
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index aead7776a..4b30d09ae 100644
--- a/app/javascript/mastodon/features/compose/index.js
+++ b/app/javascript/mastodon/features/compose/index.js
@@ -74,15 +74,15 @@ class Compose extends React.PureComponent {
     }));
 
     return false;
-  }
+  };
 
   onFocus = () => {
     this.props.dispatch(changeComposing(true));
-  }
+  };
 
   onBlur = () => {
     this.props.dispatch(changeComposing(false));
-  }
+  };
 
   render () {
     const { multiColumn, showSearch, intl } = this.props;
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.js b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
index 4a770970d..fbdff1bdd 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversation.js
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
@@ -55,7 +55,7 @@ class Conversation extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -68,7 +68,7 @@ class Conversation extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   handleClick = () => {
     if (!this.context.router) {
@@ -82,35 +82,35 @@ class Conversation extends ImmutablePureComponent {
     }
 
     this.context.router.history.push(`/@${lastStatus.getIn(['account', 'acct'])}/${lastStatus.get('id')}`);
-  }
+  };
 
   handleMarkAsRead = () => {
     this.props.markRead();
-  }
+  };
 
   handleReply = () => {
     this.props.reply(this.props.lastStatus, this.context.router.history);
-  }
+  };
 
   handleDelete = () => {
     this.props.delete();
-  }
+  };
 
   handleHotkeyMoveUp = () => {
     this.props.onMoveUp(this.props.conversationId);
-  }
+  };
 
   handleHotkeyMoveDown = () => {
     this.props.onMoveDown(this.props.conversationId);
-  }
+  };
 
   handleConversationMute = () => {
     this.props.onMute(this.props.lastStatus);
-  }
+  };
 
   handleShowMore = () => {
     this.props.onToggleHidden(this.props.lastStatus);
-  }
+  };
 
   render () {
     const { accounts, lastStatus, unread, scrollKey, intl } = this.props;
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
index fd1df7256..27e9a593f 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
@@ -16,17 +16,17 @@ export default class ConversationsList extends ImmutablePureComponent {
     onLoadMore: PropTypes.func,
   };
 
-  getCurrentIndex = id => this.props.conversations.findIndex(x => x.get('id') === id)
+  getCurrentIndex = id => this.props.conversations.findIndex(x => x.get('id') === id);
 
   handleMoveUp = id => {
     const elementIndex = this.getCurrentIndex(id) - 1;
     this._selectChild(elementIndex, true);
-  }
+  };
 
   handleMoveDown = id => {
     const elementIndex = this.getCurrentIndex(id) + 1;
     this._selectChild(elementIndex, false);
-  }
+  };
 
   _selectChild (index, align_top) {
     const container = this.node.node;
@@ -44,7 +44,7 @@ export default class ConversationsList extends ImmutablePureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   handleLoadOlder = debounce(() => {
     const last = this.props.conversations.last();
@@ -52,7 +52,7 @@ export default class ConversationsList extends ImmutablePureComponent {
     if (last && last.get('last_status')) {
       this.props.onLoadMore(last.get('last_status'));
     }
-  }, 300, { leading: true })
+  }, 300, { leading: true });
 
   render () {
     const { conversations, onLoadMore, ...other } = this.props;
diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.js
index 8dcc43e28..a45965bb2 100644
--- a/app/javascript/mastodon/features/direct_timeline/index.js
+++ b/app/javascript/mastodon/features/direct_timeline/index.js
@@ -34,16 +34,16 @@ class DirectTimeline extends React.PureComponent {
     } else {
       dispatch(addColumn('DIRECT', {}));
     }
-  }
+  };
 
   handleMove = (dir) => {
     const { columnId, dispatch } = this.props;
     dispatch(moveColumn(columnId, dir));
-  }
+  };
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   componentDidMount () {
     const { dispatch } = this.props;
@@ -64,11 +64,11 @@ class DirectTimeline extends React.PureComponent {
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleLoadMore = maxId => {
     this.props.dispatch(expandConversations({ maxId }));
-  }
+  };
 
   render () {
     const { intl, hasUnread, columnId, multiColumn } = this.props;
diff --git a/app/javascript/mastodon/features/directory/components/account_card.js b/app/javascript/mastodon/features/directory/components/account_card.js
index 977f0c32c..15c8ad303 100644
--- a/app/javascript/mastodon/features/directory/components/account_card.js
+++ b/app/javascript/mastodon/features/directory/components/account_card.js
@@ -115,7 +115,7 @@ class AccountCard extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -128,7 +128,7 @@ class AccountCard extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   handleFollow = () => {
     this.props.onFollow(this.props.account);
@@ -140,11 +140,11 @@ class AccountCard extends ImmutablePureComponent {
 
   handleMute = () => {
     this.props.onMute(this.props.account);
-  }
+  };
 
   handleEditProfile = () => {
     window.open('/settings/profile', '_blank');
-  }
+  };
 
   render() {
     const { account, intl } = this.props;
diff --git a/app/javascript/mastodon/features/directory/index.js b/app/javascript/mastodon/features/directory/index.js
index b45faa049..bb5e021cc 100644
--- a/app/javascript/mastodon/features/directory/index.js
+++ b/app/javascript/mastodon/features/directory/index.js
@@ -64,7 +64,7 @@ class Directory extends React.PureComponent {
     } else {
       dispatch(addColumn('DIRECTORY', this.getParams(this.props, this.state)));
     }
-  }
+  };
 
   getParams = (props, state) => ({
     order: state.order === null ? (props.params.order || 'active') : state.order,
@@ -74,11 +74,11 @@ class Directory extends React.PureComponent {
   handleMove = dir => {
     const { columnId, dispatch } = this.props;
     dispatch(moveColumn(columnId, dir));
-  }
+  };
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   componentDidMount () {
     const { dispatch } = this.props;
@@ -97,7 +97,7 @@ class Directory extends React.PureComponent {
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleChangeOrder = e => {
     const { dispatch, columnId } = this.props;
@@ -107,7 +107,7 @@ class Directory extends React.PureComponent {
     } else {
       this.setState({ order: e.target.value });
     }
-  }
+  };
 
   handleChangeLocal = e => {
     const { dispatch, columnId } = this.props;
@@ -117,12 +117,12 @@ class Directory extends React.PureComponent {
     } else {
       this.setState({ local: e.target.value === '1' });
     }
-  }
+  };
 
   handleLoadMore = () => {
     const { dispatch } = this.props;
     dispatch(expandDirectory(this.getParams(this.props, this.state)));
-  }
+  };
 
   render () {
     const { isLoading, accountIds, intl, columnId, multiColumn, domain } = this.props;
diff --git a/app/javascript/mastodon/features/emoji/emoji_utils.js b/app/javascript/mastodon/features/emoji/emoji_utils.js
index dbf725c1f..571907a50 100644
--- a/app/javascript/mastodon/features/emoji/emoji_utils.js
+++ b/app/javascript/mastodon/features/emoji/emoji_utils.js
@@ -135,19 +135,19 @@ function getData(emoji, skin, set) {
       }
     }
 
-    if (data.short_names.hasOwnProperty(emoji)) {
+    if (Object.prototype.hasOwnProperty.call(data.short_names, emoji)) {
       emoji = data.short_names[emoji];
     }
 
-    if (data.emojis.hasOwnProperty(emoji)) {
+    if (Object.prototype.hasOwnProperty.call(data.emojis, emoji)) {
       emojiData = data.emojis[emoji];
     }
   } else if (emoji.id) {
-    if (data.short_names.hasOwnProperty(emoji.id)) {
+    if (Object.prototype.hasOwnProperty.call(data.short_names, emoji.id)) {
       emoji.id = data.short_names[emoji.id];
     }
 
-    if (data.emojis.hasOwnProperty(emoji.id)) {
+    if (Object.prototype.hasOwnProperty.call(data.emojis, emoji.id)) {
       emojiData = data.emojis[emoji.id];
       skin = skin || emoji.skin;
     }
@@ -216,7 +216,7 @@ function deepMerge(a, b) {
     let originalValue = a[key],
       value = originalValue;
 
-    if (b.hasOwnProperty(key)) {
+    if (Object.prototype.hasOwnProperty.call(b, key)) {
       value = b[key];
     }
 
diff --git a/app/javascript/mastodon/features/explore/index.js b/app/javascript/mastodon/features/explore/index.js
index 1ae249f45..d91755ff6 100644
--- a/app/javascript/mastodon/features/explore/index.js
+++ b/app/javascript/mastodon/features/explore/index.js
@@ -41,11 +41,11 @@ class Explore extends React.PureComponent {
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   render() {
     const { intl, multiColumn, isSearching } = this.props;
diff --git a/app/javascript/mastodon/features/explore/statuses.js b/app/javascript/mastodon/features/explore/statuses.js
index 791f11b9f..b027487d5 100644
--- a/app/javascript/mastodon/features/explore/statuses.js
+++ b/app/javascript/mastodon/features/explore/statuses.js
@@ -33,7 +33,7 @@ class Statuses extends React.PureComponent {
   handleLoadMore = debounce(() => {
     const { dispatch } = this.props;
     dispatch(expandTrendingStatuses());
-  }, 300, { leading: true })
+  }, 300, { leading: true });
 
   render () {
     const { isLoading, hasMore, statusIds, multiColumn } = this.props;
diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js
index 3741f68f6..89093f682 100644
--- a/app/javascript/mastodon/features/favourited_statuses/index.js
+++ b/app/javascript/mastodon/features/favourited_statuses/index.js
@@ -48,24 +48,24 @@ class Favourites extends ImmutablePureComponent {
     } else {
       dispatch(addColumn('FAVOURITES', {}));
     }
-  }
+  };
 
   handleMove = (dir) => {
     const { columnId, dispatch } = this.props;
     dispatch(moveColumn(columnId, dir));
-  }
+  };
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleLoadMore = debounce(() => {
     this.props.dispatch(expandFavouritedStatuses());
-  }, 300, { leading: true })
+  }, 300, { leading: true });
 
   render () {
     const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.js
index 66ec6a31b..7179e6470 100644
--- a/app/javascript/mastodon/features/favourites/index.js
+++ b/app/javascript/mastodon/features/favourites/index.js
@@ -47,7 +47,7 @@ class Favourites extends ImmutablePureComponent {
 
   handleRefresh = () => {
     this.props.dispatch(fetchFavourites(this.props.params.statusId));
-  }
+  };
 
   render () {
     const { intl, accountIds, multiColumn } = this.props;
diff --git a/app/javascript/mastodon/features/filters/select_filter.js b/app/javascript/mastodon/features/filters/select_filter.js
index b68a5de6c..8a21905d7 100644
--- a/app/javascript/mastodon/features/filters/select_filter.js
+++ b/app/javascript/mastodon/features/filters/select_filter.js
@@ -71,7 +71,7 @@ class SelectFilter extends React.PureComponent {
         <span className='language-dropdown__dropdown__results__item__native-name'>{filter[1]}</span> {warning}
       </div>
     );
-  }
+  };
 
   renderCreateNew (name) {
     return (
@@ -83,11 +83,11 @@ class SelectFilter extends React.PureComponent {
 
   handleSearchChange = ({ target }) => {
     this.setState({ searchValue: target.value });
-  }
+  };
 
   setListRef = c => {
     this.listNode = c;
-  }
+  };
 
   handleKeyDown = e => {
     const index = Array.from(this.listNode.childNodes).findIndex(node => node === e.currentTarget);
@@ -125,7 +125,7 @@ class SelectFilter extends React.PureComponent {
       e.preventDefault();
       e.stopPropagation();
     }
-  }
+  };
 
   handleSearchKeyDown = e => {
     let element = null;
@@ -143,11 +143,11 @@ class SelectFilter extends React.PureComponent {
 
       break;
     }
-  }
+  };
 
   handleClear = () => {
     this.setState({ searchValue: '' });
-  }
+  };
 
   handleItemClick = e => {
     const value = e.currentTarget.getAttribute('data-index');
@@ -155,7 +155,7 @@ class SelectFilter extends React.PureComponent {
     e.preventDefault();
 
     this.props.onSelectFilter(value);
-  }
+  };
 
   handleNewFilterClick = e => {
     e.preventDefault();
diff --git a/app/javascript/mastodon/features/follow_recommendations/components/account.js b/app/javascript/mastodon/features/follow_recommendations/components/account.js
index 14f4e7e1b..daaa2f99e 100644
--- a/app/javascript/mastodon/features/follow_recommendations/components/account.js
+++ b/app/javascript/mastodon/features/follow_recommendations/components/account.js
@@ -50,7 +50,7 @@ class Account extends ImmutablePureComponent {
     } else {
       dispatch(followAccount(account.get('id')));
     }
-  }
+  };
 
   render () {
     const { account, intl } = this.props;
diff --git a/app/javascript/mastodon/features/follow_recommendations/index.js b/app/javascript/mastodon/features/follow_recommendations/index.js
index 5f7baa64c..436cc582b 100644
--- a/app/javascript/mastodon/features/follow_recommendations/index.js
+++ b/app/javascript/mastodon/features/follow_recommendations/index.js
@@ -69,7 +69,7 @@ class FollowRecommendations extends ImmutablePureComponent {
     }));
 
     router.history.push('/home');
-  }
+  };
 
   render () {
     const { suggestions, isLoading } = this.props;
diff --git a/app/javascript/mastodon/features/followed_tags/index.js b/app/javascript/mastodon/features/followed_tags/index.js
index 0a62ca76d..c2d0e4731 100644
--- a/app/javascript/mastodon/features/followed_tags/index.js
+++ b/app/javascript/mastodon/features/followed_tags/index.js
@@ -38,7 +38,7 @@ class FollowedTags extends ImmutablePureComponent {
 
   componentDidMount() {
     this.props.dispatch(fetchFollowedHashtags());
-  };
+  }
 
   handleLoadMore = debounce(() => {
     this.props.dispatch(expandFollowedHashtags());
diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.js b/app/javascript/mastodon/features/getting_started/components/announcements.js
index 24db8cede..d4afbabe3 100644
--- a/app/javascript/mastodon/features/getting_started/components/announcements.js
+++ b/app/javascript/mastodon/features/getting_started/components/announcements.js
@@ -35,7 +35,7 @@ class Content extends ImmutablePureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   componentDidMount () {
     this._updateLinks();
@@ -89,7 +89,7 @@ class Content extends ImmutablePureComponent {
       e.preventDefault();
       this.context.router.history.push(`/@${mention.get('acct')}`);
     }
-  }
+  };
 
   onHashtagClick = (hashtag, e) => {
     hashtag = hashtag.replace(/^#/, '');
@@ -98,14 +98,14 @@ class Content extends ImmutablePureComponent {
       e.preventDefault();
       this.context.router.history.push(`/tags/${hashtag}`);
     }
-  }
+  };
 
   onStatusClick = (status, e) => {
     if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       e.preventDefault();
       this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
     }
-  }
+  };
 
   handleMouseEnter = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -118,7 +118,7 @@ class Content extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -131,7 +131,7 @@ class Content extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   render () {
     const { announcement } = this.props;
@@ -216,11 +216,11 @@ class Reaction extends ImmutablePureComponent {
     } else {
       addReaction(announcementId, reaction.get('name'));
     }
-  }
+  };
 
-  handleMouseEnter = () => this.setState({ hovered: true })
+  handleMouseEnter = () => this.setState({ hovered: true });
 
-  handleMouseLeave = () => this.setState({ hovered: false })
+  handleMouseLeave = () => this.setState({ hovered: false });
 
   render () {
     const { reaction } = this.props;
@@ -254,7 +254,7 @@ class ReactionsBar extends ImmutablePureComponent {
   handleEmojiPick = data => {
     const { addReaction, announcementId } = this.props;
     addReaction(announcementId, data.native.replace(/:/g, ''));
-  }
+  };
 
   willEnter () {
     return { scale: reduceMotion ? 1 : 0 };
@@ -397,15 +397,15 @@ class Announcements extends ImmutablePureComponent {
 
   handleChangeIndex = index => {
     this.setState({ index: index % this.props.announcements.size });
-  }
+  };
 
   handleNextClick = () => {
     this.setState({ index: (this.state.index + 1) % this.props.announcements.size });
-  }
+  };
 
   handlePrevClick = () => {
     this.setState({ index: (this.props.announcements.size + this.state.index - 1) % this.props.announcements.size });
-  }
+  };
 
   render () {
     const { announcements, intl } = this.props;
diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js
index 733f54ff3..e5262d70d 100644
--- a/app/javascript/mastodon/features/hashtag_timeline/index.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/index.js
@@ -54,7 +54,7 @@ class HashtagTimeline extends React.PureComponent {
     } else {
       dispatch(addColumn('HASHTAG', { id: this.props.params.id }));
     }
-  }
+  };
 
   title = () => {
     const { id } = this.props.params;
@@ -73,7 +73,7 @@ class HashtagTimeline extends React.PureComponent {
     }
 
     return title;
-  }
+  };
 
   additionalFor = (mode) => {
     const { tags } = this.props.params;
@@ -83,16 +83,16 @@ class HashtagTimeline extends React.PureComponent {
     } else {
       return '';
     }
-  }
+  };
 
   handleMove = (dir) => {
     const { columnId, dispatch } = this.props;
     dispatch(moveColumn(columnId, dir));
-  }
+  };
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   _subscribe (dispatch, id, tags = {}, local) {
     const { signedIn } = this.context.identity;
@@ -157,14 +157,14 @@ class HashtagTimeline extends React.PureComponent {
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleLoadMore = maxId => {
     const { dispatch, params } = this.props;
     const { id, tags, local }  = params;
 
     dispatch(expandHashtagTimeline(id, { maxId, tags, local }));
-  }
+  };
 
   handleFollow = () => {
     const { dispatch, params, tag } = this.props;
@@ -180,7 +180,7 @@ class HashtagTimeline extends React.PureComponent {
     } else {
       dispatch(followHashtag(id));
     }
-  }
+  };
 
   render () {
     const { hasUnread, columnId, multiColumn, tag, intl } = this.props;
diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js
index ae11ccbe4..001de15d1 100644
--- a/app/javascript/mastodon/features/home_timeline/index.js
+++ b/app/javascript/mastodon/features/home_timeline/index.js
@@ -58,24 +58,24 @@ class HomeTimeline extends React.PureComponent {
     } else {
       dispatch(addColumn('HOME', {}));
     }
-  }
+  };
 
   handleMove = (dir) => {
     const { columnId, dispatch } = this.props;
     dispatch(moveColumn(columnId, dir));
-  }
+  };
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleLoadMore = maxId => {
     this.props.dispatch(expandHomeTimeline({ maxId }));
-  }
+  };
 
   componentDidMount () {
     setTimeout(() => this.props.dispatch(fetchAnnouncements()), 700);
@@ -114,7 +114,7 @@ class HomeTimeline extends React.PureComponent {
   handleToggleAnnouncementsClick = (e) => {
     e.stopPropagation();
     this.props.dispatch(toggleShowAnnouncements());
-  }
+  };
 
   render () {
     const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
diff --git a/app/javascript/mastodon/features/interaction_modal/index.js b/app/javascript/mastodon/features/interaction_modal/index.js
index d4535378f..c1d346fed 100644
--- a/app/javascript/mastodon/features/interaction_modal/index.js
+++ b/app/javascript/mastodon/features/interaction_modal/index.js
@@ -30,14 +30,14 @@ class Copypaste extends React.PureComponent {
 
   setRef = c => {
     this.input = c;
-  }
+  };
 
   handleInputClick = () => {
     this.setState({ copied: false });
     this.input.focus();
     this.input.select();
     this.input.setSelectionRange(0, this.input.value.length);
-  }
+  };
 
   handleButtonClick = () => {
     const { value } = this.props;
@@ -45,7 +45,7 @@ class Copypaste extends React.PureComponent {
     this.input.blur();
     this.setState({ copied: true });
     this.timeout = setTimeout(() => this.setState({ copied: false }), 700);
-  }
+  };
 
   componentWillUnmount () {
     if (this.timeout) clearTimeout(this.timeout);
@@ -86,7 +86,7 @@ class InteractionModal extends React.PureComponent {
 
   handleSignupClick = () => {
     this.props.onSignupClick();
-  }
+  };
 
   render () {
     const { url, type, displayNameHtml } = this.props;
diff --git a/app/javascript/mastodon/features/list_editor/components/edit_list_form.js b/app/javascript/mastodon/features/list_editor/components/edit_list_form.js
index 3ccab12a8..4d7e49ec0 100644
--- a/app/javascript/mastodon/features/list_editor/components/edit_list_form.js
+++ b/app/javascript/mastodon/features/list_editor/components/edit_list_form.js
@@ -33,16 +33,16 @@ class ListForm extends React.PureComponent {
 
   handleChange = e => {
     this.props.onChange(e.target.value);
-  }
+  };
 
   handleSubmit = e => {
     e.preventDefault();
     this.props.onSubmit();
-  }
+  };
 
   handleClick = () => {
     this.props.onSubmit();
-  }
+  };
 
   render () {
     const { value, disabled, intl } = this.props;
diff --git a/app/javascript/mastodon/features/list_editor/components/search.js b/app/javascript/mastodon/features/list_editor/components/search.js
index e3f069bb8..3ee26c8eb 100644
--- a/app/javascript/mastodon/features/list_editor/components/search.js
+++ b/app/javascript/mastodon/features/list_editor/components/search.js
@@ -34,17 +34,17 @@ class Search extends React.PureComponent {
 
   handleChange = e => {
     this.props.onChange(e.target.value);
-  }
+  };
 
   handleKeyUp = e => {
     if (e.keyCode === 13) {
       this.props.onSubmit(this.props.value);
     }
-  }
+  };
 
   handleClear = () => {
     this.props.onClear();
-  }
+  };
 
   render () {
     const { value, intl } = this.props;
diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js
index c2e72e2e9..25dbe311a 100644
--- a/app/javascript/mastodon/features/list_timeline/index.js
+++ b/app/javascript/mastodon/features/list_timeline/index.js
@@ -58,16 +58,16 @@ class ListTimeline extends React.PureComponent {
       dispatch(addColumn('LIST', { id: this.props.params.id }));
       this.context.router.history.push('/');
     }
-  }
+  };
 
   handleMove = (dir) => {
     const { columnId, dispatch } = this.props;
     dispatch(moveColumn(columnId, dir));
-  }
+  };
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   componentDidMount () {
     const { dispatch } = this.props;
@@ -105,16 +105,16 @@ class ListTimeline extends React.PureComponent {
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleLoadMore = maxId => {
     const { id } = this.props.params;
     this.props.dispatch(expandListTimeline(id, { maxId }));
-  }
+  };
 
   handleEditClick = () => {
     this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id }));
-  }
+  };
 
   handleDeleteClick = () => {
     const { dispatch, columnId, intl } = this.props;
@@ -133,13 +133,13 @@ class ListTimeline extends React.PureComponent {
         }
       },
     }));
-  }
+  };
 
   handleRepliesPolicyChange = ({ target }) => {
     const { dispatch } = this.props;
     const { id } = this.props.params;
     dispatch(updateList(id, undefined, false, target.value));
-  }
+  };
 
   render () {
     const { hasUnread, columnId, multiColumn, list, intl } = this.props;
diff --git a/app/javascript/mastodon/features/lists/components/new_list_form.js b/app/javascript/mastodon/features/lists/components/new_list_form.js
index f790ccbe6..4e00e5200 100644
--- a/app/javascript/mastodon/features/lists/components/new_list_form.js
+++ b/app/javascript/mastodon/features/lists/components/new_list_form.js
@@ -34,16 +34,16 @@ class NewListForm extends React.PureComponent {
 
   handleChange = e => {
     this.props.onChange(e.target.value);
-  }
+  };
 
   handleSubmit = e => {
     e.preventDefault();
     this.props.onSubmit();
-  }
+  };
 
   handleClick = () => {
     this.props.onSubmit();
-  }
+  };
 
   render () {
     const { value, disabled, intl } = this.props;
diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js
index a38f8d3c2..9251847ba 100644
--- a/app/javascript/mastodon/features/notifications/components/column_settings.js
+++ b/app/javascript/mastodon/features/notifications/components/column_settings.js
@@ -26,7 +26,7 @@ export default class ColumnSettings extends React.PureComponent {
 
   onPushChange = (path, checked) => {
     this.props.onChange(['push', ...path], checked);
-  }
+  };
 
   render () {
     const { settings, pushSettings, onChange, onClear, alertsEnabled, browserSupport, browserPermission, onRequestNotificationPermission } = this.props;
diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js
index 746d085c6..9e2517f08 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.js
+++ b/app/javascript/mastodon/features/notifications/components/notification.js
@@ -61,12 +61,12 @@ class Notification extends ImmutablePureComponent {
   handleMoveUp = () => {
     const { notification, onMoveUp } = this.props;
     onMoveUp(notification.get('id'));
-  }
+  };
 
   handleMoveDown = () => {
     const { notification, onMoveDown } = this.props;
     onMoveDown(notification.get('id'));
-  }
+  };
 
   handleOpen = () => {
     const { notification } = this.props;
@@ -76,34 +76,34 @@ class Notification extends ImmutablePureComponent {
     } else {
       this.handleOpenProfile();
     }
-  }
+  };
 
   handleOpenProfile = () => {
     const { notification } = this.props;
     this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`);
-  }
+  };
 
   handleMention = e => {
     e.preventDefault();
 
     const { notification, onMention } = this.props;
     onMention(notification.get('account'), this.context.router.history);
-  }
+  };
 
   handleHotkeyFavourite = () => {
     const { status } = this.props;
     if (status) this.props.onFavourite(status);
-  }
+  };
 
   handleHotkeyBoost = e => {
     const { status } = this.props;
     if (status) this.props.onReblog(status, e);
-  }
+  };
 
   handleHotkeyToggleHidden = () => {
     const { status } = this.props;
     if (status) this.props.onToggleHidden(status);
-  }
+  };
 
   getHandlers () {
     return {
diff --git a/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js
index df9b7fb1b..3a7556c1d 100644
--- a/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js
+++ b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js
@@ -23,11 +23,11 @@ class NotificationsPermissionBanner extends React.PureComponent {
 
   handleClick = () => {
     this.props.dispatch(requestBrowserPermission());
-  }
+  };
 
   handleClose = () => {
     this.props.dispatch(changeSetting(['notifications', 'dismissPermissionBanner'], true));
-  }
+  };
 
   render () {
     const { intl } = this.props;
diff --git a/app/javascript/mastodon/features/notifications/components/setting_toggle.js b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
index c4c8bffbe..c979e4383 100644
--- a/app/javascript/mastodon/features/notifications/components/setting_toggle.js
+++ b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
@@ -13,11 +13,11 @@ export default class SettingToggle extends React.PureComponent {
     onChange: PropTypes.func.isRequired,
     defaultValue: PropTypes.bool,
     disabled: PropTypes.bool,
-  }
+  };
 
   onChange = ({ target }) => {
     this.props.onChange(this.props.settingPath, target.checked);
-  }
+  };
 
   render () {
     const { prefix, settings, settingPath, label, defaultValue, disabled } = this.props;
diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js
index 826a7e9ad..fee016a02 100644
--- a/app/javascript/mastodon/features/notifications/index.js
+++ b/app/javascript/mastodon/features/notifications/index.js
@@ -136,30 +136,30 @@ class Notifications extends React.PureComponent {
     } else {
       dispatch(addColumn('NOTIFICATIONS', {}));
     }
-  }
+  };
 
   handleMove = (dir) => {
     const { columnId, dispatch } = this.props;
     dispatch(moveColumn(columnId, dir));
-  }
+  };
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   setColumnRef = c => {
     this.column = c;
-  }
+  };
 
   handleMoveUp = id => {
     const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1;
     this._selectChild(elementIndex, true);
-  }
+  };
 
   handleMoveDown = id => {
     const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1;
     this._selectChild(elementIndex, false);
-  }
+  };
 
   _selectChild (index, align_top) {
     const container = this.column.node;
diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.js b/app/javascript/mastodon/features/picture_in_picture/components/footer.js
index 0dff834c3..3f59b891b 100644
--- a/app/javascript/mastodon/features/picture_in_picture/components/footer.js
+++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.js
@@ -112,7 +112,7 @@ class Footer extends ImmutablePureComponent {
   _performReblog = (status, privacy) => {
     const { dispatch } = this.props;
     dispatch(reblog(status, privacy));
-  }
+  };
 
   handleReblogClick = e => {
     const { dispatch, status } = this.props;
@@ -149,7 +149,7 @@ class Footer extends ImmutablePureComponent {
     }
 
     router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
-  }
+  };
 
   render () {
     const { status, intl, withOpenButton } = this.props;
diff --git a/app/javascript/mastodon/features/picture_in_picture/index.js b/app/javascript/mastodon/features/picture_in_picture/index.js
index 1e59fbcd3..01a7d43f2 100644
--- a/app/javascript/mastodon/features/picture_in_picture/index.js
+++ b/app/javascript/mastodon/features/picture_in_picture/index.js
@@ -32,7 +32,7 @@ class PictureInPicture extends React.Component {
   handleClose = () => {
     const { dispatch } = this.props;
     dispatch(removePictureInPicture());
-  }
+  };
 
   render () {
     const { type, src, currentTime, accountId, statusId } = this.props;
diff --git a/app/javascript/mastodon/features/pinned_statuses/index.js b/app/javascript/mastodon/features/pinned_statuses/index.js
index c6790ea06..504fda415 100644
--- a/app/javascript/mastodon/features/pinned_statuses/index.js
+++ b/app/javascript/mastodon/features/pinned_statuses/index.js
@@ -37,11 +37,11 @@ class PinnedStatuses extends ImmutablePureComponent {
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   render () {
     const { intl, statusIds, hasMore, multiColumn } = this.props;
diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js
index a41be07e1..aaef45c86 100644
--- a/app/javascript/mastodon/features/public_timeline/index.js
+++ b/app/javascript/mastodon/features/public_timeline/index.js
@@ -62,16 +62,16 @@ class PublicTimeline extends React.PureComponent {
     } else {
       dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, onlyRemote } }));
     }
-  }
+  };
 
   handleMove = (dir) => {
     const { columnId, dispatch } = this.props;
     dispatch(moveColumn(columnId, dir));
-  }
+  };
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   componentDidMount () {
     const { dispatch, onlyMedia, onlyRemote } = this.props;
@@ -111,13 +111,13 @@ class PublicTimeline extends React.PureComponent {
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleLoadMore = maxId => {
     const { dispatch, onlyMedia, onlyRemote } = this.props;
 
     dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote }));
-  }
+  };
 
   render () {
     const { intl, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props;
diff --git a/app/javascript/mastodon/features/reblogs/index.js b/app/javascript/mastodon/features/reblogs/index.js
index 36ca11d1a..31e5dc1d4 100644
--- a/app/javascript/mastodon/features/reblogs/index.js
+++ b/app/javascript/mastodon/features/reblogs/index.js
@@ -47,7 +47,7 @@ class Reblogs extends ImmutablePureComponent {
 
   handleRefresh = () => {
     this.props.dispatch(fetchReblogs(this.props.params.statusId));
-  }
+  };
 
   render () {
     const { intl, accountIds, multiColumn } = this.props;
diff --git a/app/javascript/mastodon/features/report/components/option.js b/app/javascript/mastodon/features/report/components/option.js
index 744d85268..42c04b018 100644
--- a/app/javascript/mastodon/features/report/components/option.js
+++ b/app/javascript/mastodon/features/report/components/option.js
@@ -24,12 +24,12 @@ export default class Option extends React.PureComponent {
       e.preventDefault();
       onToggle(value, !checked);
     }
-  }
+  };
 
   handleChange = e => {
     const { value, onToggle } = this.props;
     onToggle(value, e.target.checked);
-  }
+  };
 
   render () {
     const { name, value, checked, label, labelComponent, description, multiple } = this.props;
diff --git a/app/javascript/mastodon/features/standalone/compose/index.js b/app/javascript/mastodon/features/standalone/compose/index.js
index 0d764575f..fbadef6f4 100644
--- a/app/javascript/mastodon/features/standalone/compose/index.js
+++ b/app/javascript/mastodon/features/standalone/compose/index.js
@@ -9,7 +9,7 @@ export default class Compose extends React.PureComponent {
   render () {
     return (
       <div>
-        <ComposeFormContainer />
+        <ComposeFormContainer autoFocus />
         <NotificationsContainer />
         <ModalContainer />
         <LoadingBarContainer className='loading-bar' />
diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js
index 46ee9f6c1..0d4767331 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.js
+++ b/app/javascript/mastodon/features/status/components/action_bar.js
@@ -82,39 +82,39 @@ class ActionBar extends React.PureComponent {
 
   handleReplyClick = () => {
     this.props.onReply(this.props.status);
-  }
+  };
 
   handleReblogClick = (e) => {
     this.props.onReblog(this.props.status, e);
-  }
+  };
 
   handleFavouriteClick = () => {
     this.props.onFavourite(this.props.status);
-  }
+  };
 
   handleBookmarkClick = (e) => {
     this.props.onBookmark(this.props.status, e);
-  }
+  };
 
   handleDeleteClick = () => {
     this.props.onDelete(this.props.status, this.context.router.history);
-  }
+  };
 
   handleRedraftClick = () => {
     this.props.onDelete(this.props.status, this.context.router.history, true);
-  }
+  };
 
   handleEditClick = () => {
     this.props.onEdit(this.props.status, this.context.router.history);
-  }
+  };
 
   handleDirectClick = () => {
     this.props.onDirect(this.props.status.get('account'), this.context.router.history);
-  }
+  };
 
   handleMentionClick = () => {
     this.props.onMention(this.props.status.get('account'), this.context.router.history);
-  }
+  };
 
   handleMuteClick = () => {
     const { status, relationship, onMute, onUnmute } = this.props;
@@ -125,7 +125,7 @@ class ActionBar extends React.PureComponent {
     } else {
       onMute(account);
     }
-  }
+  };
 
   handleBlockClick = () => {
     const { status, relationship, onBlock, onUnblock } = this.props;
@@ -136,49 +136,49 @@ class ActionBar extends React.PureComponent {
     } else {
       onBlock(status);
     }
-  }
+  };
 
   handleBlockDomain = () => {
     const { status, onBlockDomain } = this.props;
     const account = status.get('account');
 
     onBlockDomain(account.get('acct').split('@')[1]);
-  }
+  };
 
   handleUnblockDomain = () => {
     const { status, onUnblockDomain } = this.props;
     const account = status.get('account');
 
     onUnblockDomain(account.get('acct').split('@')[1]);
-  }
+  };
 
   handleConversationMuteClick = () => {
     this.props.onMuteConversation(this.props.status);
-  }
+  };
 
   handleReport = () => {
     this.props.onReport(this.props.status);
-  }
+  };
 
   handlePinClick = () => {
     this.props.onPin(this.props.status);
-  }
+  };
 
   handleShare = () => {
     navigator.share({
       text: this.props.status.get('search_index'),
       url: this.props.status.get('url'),
     });
-  }
+  };
 
   handleEmbed = () => {
     this.props.onEmbed(this.props.status);
-  }
+  };
 
   handleCopy = () => {
     const url = this.props.status.get('url');
     navigator.clipboard.writeText(url);
-  }
+  };
 
   render () {
     const { status, relationship, intl } = this.props;
diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js
index 82537dd5d..34fac1010 100644
--- a/app/javascript/mastodon/features/status/components/card.js
+++ b/app/javascript/mastodon/features/status/components/card.js
@@ -146,7 +146,7 @@ export default class Card extends React.PureComponent {
     } else {
       this.setState({ embedded: true });
     }
-  }
+  };
 
   setRef = c => {
     this.node = c;
@@ -154,17 +154,17 @@ export default class Card extends React.PureComponent {
     if (this.node) {
       this._setDimensions();
     }
-  }
+  };
 
   handleImageLoad = () => {
     this.setState({ previewLoaded: true });
-  }
+  };
 
   handleReveal = e => {
     e.preventDefault();
     e.stopPropagation();
     this.setState({ revealed: true });
-  }
+  };
 
   renderVideo () {
     const { card }  = this.props;
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index c62910e0e..116d9f6b2 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -61,15 +61,15 @@ class DetailedStatus extends ImmutablePureComponent {
     }
 
     e.stopPropagation();
-  }
+  };
 
   handleOpenVideo = (options) => {
     this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), options);
-  }
+  };
 
   handleExpandedToggle = () => {
     this.props.onToggleHidden(this.props.status);
-  }
+  };
 
   _measureHeight (heightJustChanged) {
     if (this.props.measureHeight && this.node) {
@@ -84,7 +84,7 @@ class DetailedStatus extends ImmutablePureComponent {
   setRef = c => {
     this.node = c;
     this._measureHeight();
-  }
+  };
 
   componentDidUpdate (prevProps, prevState) {
     this._measureHeight(prevState.height !== this.state.height);
@@ -102,12 +102,12 @@ class DetailedStatus extends ImmutablePureComponent {
     }
 
     window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
-  }
+  };
 
   handleTranslate = () => {
     const { onTranslate, status } = this.props;
     onTranslate(status);
-  }
+  };
 
   render () {
     const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index 8a63cced2..38bbc6895 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -233,7 +233,7 @@ class Status extends ImmutablePureComponent {
 
   handleToggleMediaVisibility = () => {
     this.setState({ showMedia: !this.state.showMedia });
-  }
+  };
 
   handleFavouriteClick = (status) => {
     const { dispatch } = this.props;
@@ -252,7 +252,7 @@ class Status extends ImmutablePureComponent {
         url: status.get('url'),
       }));
     }
-  }
+  };
 
   handlePin = (status) => {
     if (status.get('pinned')) {
@@ -260,7 +260,7 @@ class Status extends ImmutablePureComponent {
     } else {
       this.props.dispatch(pin(status));
     }
-  }
+  };
 
   handleReplyClick = (status) => {
     const { askReplyConfirmation, dispatch, intl } = this.props;
@@ -283,11 +283,11 @@ class Status extends ImmutablePureComponent {
         url: status.get('url'),
       }));
     }
-  }
+  };
 
   handleModalReblog = (status, privacy) => {
     this.props.dispatch(reblog(status, privacy));
-  }
+  };
 
   handleReblogClick = (status, e) => {
     const { dispatch } = this.props;
@@ -310,7 +310,7 @@ class Status extends ImmutablePureComponent {
         url: status.get('url'),
       }));
     }
-  }
+  };
 
   handleBookmarkClick = (status) => {
     if (status.get('bookmarked')) {
@@ -318,7 +318,7 @@ class Status extends ImmutablePureComponent {
     } else {
       this.props.dispatch(bookmark(status));
     }
-  }
+  };
 
   handleDeleteClick = (status, history, withRedraft = false) => {
     const { dispatch, intl } = this.props;
@@ -332,27 +332,27 @@ class Status extends ImmutablePureComponent {
         onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
       }));
     }
-  }
+  };
 
   handleEditClick = (status, history) => {
     this.props.dispatch(editStatus(status.get('id'), history));
-  }
+  };
 
   handleDirectClick = (account, router) => {
     this.props.dispatch(directCompose(account, router));
-  }
+  };
 
   handleMentionClick = (account, router) => {
     this.props.dispatch(mentionCompose(account, router));
-  }
+  };
 
   handleOpenMedia = (media, index) => {
     this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index }));
-  }
+  };
 
   handleOpenVideo = (media, options) => {
     this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options }));
-  }
+  };
 
   handleHotkeyOpenMedia = e => {
     const { status } = this.props;
@@ -366,11 +366,11 @@ class Status extends ImmutablePureComponent {
         this.handleOpenMedia(status.get('media_attachments'), 0);
       }
     }
-  }
+  };
 
   handleMuteClick = (account) => {
     this.props.dispatch(initMuteModal(account));
-  }
+  };
 
   handleConversationMuteClick = (status) => {
     if (status.get('muted')) {
@@ -378,7 +378,7 @@ class Status extends ImmutablePureComponent {
     } else {
       this.props.dispatch(muteStatus(status.get('id')));
     }
-  }
+  };
 
   handleToggleHidden = (status) => {
     if (status.get('hidden')) {
@@ -386,7 +386,7 @@ class Status extends ImmutablePureComponent {
     } else {
       this.props.dispatch(hideStatus(status.get('id')));
     }
-  }
+  };
 
   handleToggleAll = () => {
     const { status, ancestorsIds, descendantsIds } = this.props;
@@ -397,7 +397,7 @@ class Status extends ImmutablePureComponent {
     } else {
       this.props.dispatch(hideStatus(statusIds));
     }
-  }
+  };
 
   handleTranslate = status => {
     const { dispatch } = this.props;
@@ -407,29 +407,29 @@ class Status extends ImmutablePureComponent {
     } else {
       dispatch(translateStatus(status.get('id')));
     }
-  }
+  };
 
   handleBlockClick = (status) => {
     const { dispatch } = this.props;
     const account = status.get('account');
     dispatch(initBlockModal(account));
-  }
+  };
 
   handleReport = (status) => {
     this.props.dispatch(initReport(status.get('account'), status));
-  }
+  };
 
   handleEmbed = (status) => {
     this.props.dispatch(openModal('EMBED', { url: status.get('url') }));
-  }
+  };
 
   handleUnmuteClick = account => {
     this.props.dispatch(unmuteAccount(account.get('id')));
-  }
+  };
 
   handleUnblockClick = account => {
     this.props.dispatch(unblockAccount(account.get('id')));
-  }
+  };
 
   handleBlockDomainClick = domain => {
     this.props.dispatch(openModal('CONFIRM', {
@@ -437,50 +437,50 @@ class Status extends ImmutablePureComponent {
       confirm: this.props.intl.formatMessage(messages.blockDomainConfirm),
       onConfirm: () => this.props.dispatch(blockDomain(domain)),
     }));
-  }
+  };
 
   handleUnblockDomainClick = domain => {
     this.props.dispatch(unblockDomain(domain));
-  }
+  };
 
 
   handleHotkeyMoveUp = () => {
     this.handleMoveUp(this.props.status.get('id'));
-  }
+  };
 
   handleHotkeyMoveDown = () => {
     this.handleMoveDown(this.props.status.get('id'));
-  }
+  };
 
   handleHotkeyReply = e => {
     e.preventDefault();
     this.handleReplyClick(this.props.status);
-  }
+  };
 
   handleHotkeyFavourite = () => {
     this.handleFavouriteClick(this.props.status);
-  }
+  };
 
   handleHotkeyBoost = () => {
     this.handleReblogClick(this.props.status);
-  }
+  };
 
   handleHotkeyMention = e => {
     e.preventDefault();
     this.handleMentionClick(this.props.status.get('account'));
-  }
+  };
 
   handleHotkeyOpenProfile = () => {
     this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
-  }
+  };
 
   handleHotkeyToggleHidden = () => {
     this.handleToggleHidden(this.props.status);
-  }
+  };
 
   handleHotkeyToggleSensitive = () => {
     this.handleToggleMediaVisibility();
-  }
+  };
 
   handleMoveUp = id => {
     const { status, ancestorsIds, descendantsIds } = this.props;
@@ -497,7 +497,7 @@ class Status extends ImmutablePureComponent {
         this._selectChild(index - 1, true);
       }
     }
-  }
+  };
 
   handleMoveDown = id => {
     const { status, ancestorsIds, descendantsIds } = this.props;
@@ -514,7 +514,7 @@ class Status extends ImmutablePureComponent {
         this._selectChild(index + 1, false);
       }
     }
-  }
+  };
 
   _selectChild (index, align_top) {
     const container = this.node;
@@ -544,7 +544,7 @@ class Status extends ImmutablePureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   componentDidUpdate () {
     if (this._scrolledIntoView) {
@@ -569,7 +569,7 @@ class Status extends ImmutablePureComponent {
 
   onFullScreenChange = () => {
     this.setState({ fullscreen: isFullscreen() });
-  }
+  };
 
   render () {
     let ancestors, descendants;
diff --git a/app/javascript/mastodon/features/subscribed_languages_modal/index.js b/app/javascript/mastodon/features/subscribed_languages_modal/index.js
index a519ceabc..f1360613e 100644
--- a/app/javascript/mastodon/features/subscribed_languages_modal/index.js
+++ b/app/javascript/mastodon/features/subscribed_languages_modal/index.js
@@ -72,7 +72,7 @@ class SubscribedLanguagesModal extends ImmutablePureComponent {
   handleSubmit = () => {
     this.props.onSubmit(this.state.selectedLanguages.toArray());
     this.props.onClose();
-  }
+  };
 
   renderItem (value) {
     const language = this.props.languages.find(language => language[0] === value);
diff --git a/app/javascript/mastodon/features/ui/components/actions_modal.js b/app/javascript/mastodon/features/ui/components/actions_modal.js
index 67be69d43..fd59c1e20 100644
--- a/app/javascript/mastodon/features/ui/components/actions_modal.js
+++ b/app/javascript/mastodon/features/ui/components/actions_modal.js
@@ -31,7 +31,7 @@ export default class ActionsModal extends ImmutablePureComponent {
         </a>
       </li>
     );
-  }
+  };
 
   render () {
     return (
diff --git a/app/javascript/mastodon/features/ui/components/block_modal.js b/app/javascript/mastodon/features/ui/components/block_modal.js
index a07baeaa6..6c9d2043c 100644
--- a/app/javascript/mastodon/features/ui/components/block_modal.js
+++ b/app/javascript/mastodon/features/ui/components/block_modal.js
@@ -55,20 +55,20 @@ class BlockModal extends React.PureComponent {
   handleClick = () => {
     this.props.onClose();
     this.props.onConfirm(this.props.account);
-  }
+  };
 
   handleSecondary = () => {
     this.props.onClose();
     this.props.onBlockAndReport(this.props.account);
-  }
+  };
 
   handleCancel = () => {
     this.props.onClose();
-  }
+  };
 
   setRef = (c) => {
     this.button = c;
-  }
+  };
 
   render () {
     const { account } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.js b/app/javascript/mastodon/features/ui/components/boost_modal.js
index 077ce7b35..087eadba2 100644
--- a/app/javascript/mastodon/features/ui/components/boost_modal.js
+++ b/app/javascript/mastodon/features/ui/components/boost_modal.js
@@ -62,7 +62,7 @@ class BoostModal extends ImmutablePureComponent {
   handleReblog = () => {
     this.props.onReblog(this.props.status, this.props.privacy);
     this.props.onClose();
-  }
+  };
 
   handleAccountClick = (e) => {
     if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
@@ -70,7 +70,7 @@ class BoostModal extends ImmutablePureComponent {
       this.props.onClose();
       this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
     }
-  }
+  };
 
   _findContainer = () => {
     return document.getElementsByClassName('modal-root__container')[0];
@@ -78,7 +78,7 @@ class BoostModal extends ImmutablePureComponent {
 
   setRef = (c) => {
     this.button = c;
-  }
+  };
 
   render () {
     const { status, privacy, intl } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/bundle.js b/app/javascript/mastodon/features/ui/components/bundle.js
index a60ace35b..1b10a218b 100644
--- a/app/javascript/mastodon/features/ui/components/bundle.js
+++ b/app/javascript/mastodon/features/ui/components/bundle.js
@@ -15,7 +15,7 @@ class Bundle extends React.PureComponent {
     onFetch: PropTypes.func,
     onFetchSuccess: PropTypes.func,
     onFetchFail: PropTypes.func,
-  }
+  };
 
   static defaultProps = {
     loading: emptyComponent,
@@ -24,14 +24,14 @@ class Bundle extends React.PureComponent {
     onFetch: noop,
     onFetchSuccess: noop,
     onFetchFail: noop,
-  }
+  };
 
-  static cache = new Map
+  static cache = new Map;
 
   state = {
     mod: undefined,
     forceRender: false,
-  }
+  };
 
   componentWillMount() {
     this.load(this.props);
@@ -83,7 +83,7 @@ class Bundle extends React.PureComponent {
         this.setState({ mod: null });
         onFetchFail(error);
       });
-  }
+  };
 
   render() {
     const { loading: Loading, error: Error, children, renderDelay } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/bundle_column_error.js b/app/javascript/mastodon/features/ui/components/bundle_column_error.js
index dfe970ad0..9955173eb 100644
--- a/app/javascript/mastodon/features/ui/components/bundle_column_error.js
+++ b/app/javascript/mastodon/features/ui/components/bundle_column_error.js
@@ -31,7 +31,7 @@ class GIF extends React.PureComponent {
     if (!animate) {
       this.setState({ hovering: true });
     }
-  }
+  };
 
   handleMouseLeave = () => {
     const { animate } = this.props;
@@ -39,7 +39,7 @@ class GIF extends React.PureComponent {
     if (!animate) {
       this.setState({ hovering: false });
     }
-  }
+  };
 
   render () {
     const { src, staticSrc, className, animate } = this.props;
@@ -75,7 +75,7 @@ class CopyButton extends React.PureComponent {
     navigator.clipboard.writeText(value);
     this.setState({ copied: true });
     this.timeout = setTimeout(() => this.setState({ copied: false }), 700);
-  }
+  };
 
   componentWillUnmount () {
     if (this.timeout) clearTimeout(this.timeout);
@@ -113,7 +113,7 @@ class BundleColumnError extends React.PureComponent {
     if (onRetry) {
       onRetry();
     }
-  }
+  };
 
   render () {
     const { errorType, multiColumn, stacktrace } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/bundle_modal_error.js b/app/javascript/mastodon/features/ui/components/bundle_modal_error.js
index f9365b95b..d79d0ca4a 100644
--- a/app/javascript/mastodon/features/ui/components/bundle_modal_error.js
+++ b/app/javascript/mastodon/features/ui/components/bundle_modal_error.js
@@ -16,11 +16,11 @@ class BundleModalError extends React.PureComponent {
     onRetry: PropTypes.func.isRequired,
     onClose: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
-  }
+  };
 
   handleRetry = () => {
     this.props.onRetry();
-  }
+  };
 
   render () {
     const { onClose, intl: { formatMessage } } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/column.js b/app/javascript/mastodon/features/ui/components/column.js
index 15538ea38..7bc2f7e00 100644
--- a/app/javascript/mastodon/features/ui/components/column.js
+++ b/app/javascript/mastodon/features/ui/components/column.js
@@ -23,7 +23,7 @@ export default class Column extends React.PureComponent {
     }
 
     this._interruptScrollAnimation = scrollTop(scrollable);
-  }
+  };
 
   scrollTop () {
     const scrollable = this.node.querySelector('.scrollable');
@@ -40,11 +40,11 @@ export default class Column extends React.PureComponent {
     if (typeof this._interruptScrollAnimation !== 'undefined') {
       this._interruptScrollAnimation();
     }
-  }, 200)
+  }, 200);
 
   setRef = (c) => {
     this.node = c;
-  }
+  };
 
   render () {
     const { heading, icon, children, active, hideHeadingOnMobile } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/column_header.js b/app/javascript/mastodon/features/ui/components/column_header.js
index b1a36e173..4ceef5957 100644
--- a/app/javascript/mastodon/features/ui/components/column_header.js
+++ b/app/javascript/mastodon/features/ui/components/column_header.js
@@ -15,7 +15,7 @@ export default class ColumnHeader extends React.PureComponent {
 
   handleClick = () => {
     this.props.onClick();
-  }
+  };
 
   render () {
     const { icon, type, active, columnHeaderId } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js
index e7def800e..1dd6e34e8 100644
--- a/app/javascript/mastodon/features/ui/components/columns_area.js
+++ b/app/javascript/mastodon/features/ui/components/columns_area.js
@@ -57,7 +57,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
 
   state = {
     renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches),
-  }
+  };
 
   componentDidMount() {
     if (!this.props.singleColumn) {
@@ -111,7 +111,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
 
   handleLayoutChange = (e) => {
     this.setState({ renderComposePanel: !e.matches });
-  }
+  };
 
   handleWheel = () => {
     if (typeof this._interruptScrollAnimation !== 'function') {
@@ -119,19 +119,19 @@ export default class ColumnsArea extends ImmutablePureComponent {
     }
 
     this._interruptScrollAnimation();
-  }
+  };
 
   setRef = (node) => {
     this.node = node;
-  }
+  };
 
   renderLoading = columnId => () => {
     return columnId === 'COMPOSE' ? <DrawerLoading /> : <ColumnLoading multiColumn />;
-  }
+  };
 
   renderError = (props) => {
     return <BundleColumnError multiColumn errorType='network' {...props} />;
-  }
+  };
 
   render () {
     const { columns, children, singleColumn, isModalOpen } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/compose_panel.js b/app/javascript/mastodon/features/ui/components/compose_panel.js
index 92d16b5b3..6cb352322 100644
--- a/app/javascript/mastodon/features/ui/components/compose_panel.js
+++ b/app/javascript/mastodon/features/ui/components/compose_panel.js
@@ -22,12 +22,12 @@ class ComposePanel extends React.PureComponent {
   onFocus = () => {
     const { dispatch } = this.props;
     dispatch(changeComposing(true));
-  }
+  };
 
   onBlur = () => {
     const { dispatch } = this.props;
     dispatch(changeComposing(false));
-  }
+  };
 
   componentDidMount () {
     const { dispatch } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modal.js b/app/javascript/mastodon/features/ui/components/confirmation_modal.js
index 65d97ca16..b023b00b2 100644
--- a/app/javascript/mastodon/features/ui/components/confirmation_modal.js
+++ b/app/javascript/mastodon/features/ui/components/confirmation_modal.js
@@ -30,20 +30,20 @@ class ConfirmationModal extends React.PureComponent {
       this.props.onClose();
     }
     this.props.onConfirm();
-  }
+  };
 
   handleSecondary = () => {
     this.props.onClose();
     this.props.onSecondary();
-  }
+  };
 
   handleCancel = () => {
     this.props.onClose();
-  }
+  };
 
   setRef = (c) => {
     this.button = c;
-  }
+  };
 
   render () {
     const { message, confirm, secondary } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/disabled_account_banner.js b/app/javascript/mastodon/features/ui/components/disabled_account_banner.js
index 038cc3553..35520478b 100644
--- a/app/javascript/mastodon/features/ui/components/disabled_account_banner.js
+++ b/app/javascript/mastodon/features/ui/components/disabled_account_banner.js
@@ -46,7 +46,7 @@ class DisabledAccountBanner extends React.PureComponent {
     this.props.onLogout();
 
     return false;
-  }
+  };
 
   render () {
     const { disabledAcct, movedToAcct } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/embed_modal.js b/app/javascript/mastodon/features/ui/components/embed_modal.js
index 4679c9650..a054dd3cf 100644
--- a/app/javascript/mastodon/features/ui/components/embed_modal.js
+++ b/app/javascript/mastodon/features/ui/components/embed_modal.js
@@ -17,7 +17,7 @@ class EmbedModal extends ImmutablePureComponent {
     onClose: PropTypes.func.isRequired,
     onError: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
-  }
+  };
 
   state = {
     loading: false,
@@ -48,11 +48,11 @@ class EmbedModal extends ImmutablePureComponent {
 
   setIframeRef = c =>  {
     this.iframe = c;
-  }
+  };
 
   handleTextareaClick = (e) => {
     e.target.select();
-  }
+  };
 
   render () {
     const { intl, onClose } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.js b/app/javascript/mastodon/features/ui/components/focal_point_modal.js
index b9dbd9390..6e8d017ee 100644
--- a/app/javascript/mastodon/features/ui/components/focal_point_modal.js
+++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.js
@@ -39,6 +39,7 @@ const mapStateToProps = (state, { id }) => ({
   account: state.getIn(['accounts', me]),
   isUploadingThumbnail: state.getIn(['compose', 'isUploadingThumbnail']),
   description: state.getIn(['compose', 'media_modal', 'description']),
+  lang: state.getIn(['compose', 'language']),
   focusX: state.getIn(['compose', 'media_modal', 'focusX']),
   focusY: state.getIn(['compose', 'media_modal', 'focusY']),
   dirty: state.getIn(['compose', 'media_modal', 'dirty']),
@@ -134,7 +135,7 @@ class FocalPointModal extends ImmutablePureComponent {
 
     this.updatePosition(e);
     this.setState({ dragging: true });
-  }
+  };
 
   handleTouchStart = e => {
     document.addEventListener('touchmove', this.handleMouseMove);
@@ -142,25 +143,25 @@ class FocalPointModal extends ImmutablePureComponent {
 
     this.updatePosition(e);
     this.setState({ dragging: true });
-  }
+  };
 
   handleMouseMove = e => {
     this.updatePosition(e);
-  }
+  };
 
   handleMouseUp = () => {
     document.removeEventListener('mousemove', this.handleMouseMove);
     document.removeEventListener('mouseup', this.handleMouseUp);
 
     this.setState({ dragging: false });
-  }
+  };
 
   handleTouchEnd = () => {
     document.removeEventListener('touchmove', this.handleMouseMove);
     document.removeEventListener('touchend', this.handleTouchEnd);
 
     this.setState({ dragging: false });
-  }
+  };
 
   updatePosition = e => {
     const { x, y } = getPointerPosition(this.node, e);
@@ -168,24 +169,24 @@ class FocalPointModal extends ImmutablePureComponent {
     const focusY   = (y - .5) * -2;
 
     this.props.onChangeFocus(focusX, focusY);
-  }
+  };
 
   handleChange = e => {
     this.props.onChangeDescription(e.target.value);
-  }
+  };
 
   handleKeyDown = (e) => {
     if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
       this.props.onChangeDescription(e.target.value);
       this.handleSubmit(e);
     }
-  }
+  };
 
   handleSubmit = (e) => {
     e.preventDefault();
     e.stopPropagation();
     this.props.onSave(this.props.description, this.props.focusX, this.props.focusY);
-  }
+  };
 
   getCloseConfirmationMessage = () => {
     const { intl, dirty } = this.props;
@@ -198,15 +199,15 @@ class FocalPointModal extends ImmutablePureComponent {
     } else {
       return null;
     }
-  }
+  };
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   handleTextDetection = () => {
     this._detectText();
-  }
+  };
 
   _detectText = (refreshCache = false) => {
     const { media } = this.props;
@@ -257,24 +258,24 @@ class FocalPointModal extends ImmutablePureComponent {
       console.error(e);
       this.setState({ detecting: false });
     });
-  }
+  };
 
   handleThumbnailChange = e => {
     if (e.target.files.length > 0) {
       this.props.onSelectThumbnail(e.target.files);
     }
-  }
+  };
 
   setFileInputRef = c => {
     this.fileInput = c;
-  }
+  };
 
   handleFileInputClick = () => {
     this.fileInput.click();
-  }
+  };
 
   render () {
-    const { media, intl, account, onClose, isUploadingThumbnail, description, focusX, focusY, dirty, is_changing_upload } = this.props;
+    const { media, intl, account, onClose, isUploadingThumbnail, description, lang, focusX, focusY, dirty, is_changing_upload } = this.props;
     const { dragging, detecting, progress, ocrStatus } = this.state;
     const x = (focusX /  2) + .5;
     const y = (focusY / -2) + .5;
@@ -349,6 +350,7 @@ class FocalPointModal extends ImmutablePureComponent {
                 id='upload-modal__description'
                 className='setting-text light'
                 value={detecting ? '…' : description}
+                lang={lang}
                 onChange={this.handleChange}
                 onKeyDown={this.handleKeyDown}
                 disabled={detecting || is_changing_upload}
diff --git a/app/javascript/mastodon/features/ui/components/image_loader.js b/app/javascript/mastodon/features/ui/components/image_loader.js
index dfa0efe49..92aeef5c4 100644
--- a/app/javascript/mastodon/features/ui/components/image_loader.js
+++ b/app/javascript/mastodon/features/ui/components/image_loader.js
@@ -14,7 +14,7 @@ export default class ImageLoader extends PureComponent {
     height: PropTypes.number,
     onClick: PropTypes.func,
     zoomButtonHidden: PropTypes.bool,
-  }
+  };
 
   static defaultProps = {
     alt: '',
@@ -26,7 +26,7 @@ export default class ImageLoader extends PureComponent {
     loading: true,
     error: false,
     width: null,
-  }
+  };
 
   removers = [];
   canvas = null;
@@ -86,7 +86,7 @@ export default class ImageLoader extends PureComponent {
     image.addEventListener('load', handleLoad);
     image.src = previewSrc;
     this.removers.push(removeEventListeners);
-  })
+  });
 
   clearPreviewCanvas () {
     const { width, height } = this.canvas;
@@ -126,7 +126,7 @@ export default class ImageLoader extends PureComponent {
   setCanvasRef = c => {
     this.canvas = c;
     if (c) this.setState({ width: c.offsetWidth });
-  }
+  };
 
   render () {
     const { alt, src, width, height, onClick } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/link_footer.js b/app/javascript/mastodon/features/ui/components/link_footer.js
index 3664a05bf..db5945d6a 100644
--- a/app/javascript/mastodon/features/ui/components/link_footer.js
+++ b/app/javascript/mastodon/features/ui/components/link_footer.js
@@ -44,7 +44,7 @@ class LinkFooter extends React.PureComponent {
     this.props.onLogout();
 
     return false;
-  }
+  };
 
   render () {
     const { signedIn, permissions } = this.context.identity;
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js
index ae937d1cd..1cda8de04 100644
--- a/app/javascript/mastodon/features/ui/components/media_modal.js
+++ b/app/javascript/mastodon/features/ui/components/media_modal.js
@@ -43,27 +43,27 @@ class MediaModal extends ImmutablePureComponent {
 
   handleSwipe = (index) => {
     this.setState({ index: index % this.props.media.size });
-  }
+  };
 
   handleTransitionEnd = () => {
     this.setState({
       zoomButtonHidden: false,
     });
-  }
+  };
 
   handleNextClick = () => {
     this.setState({
       index: (this.getIndex() + 1) % this.props.media.size,
       zoomButtonHidden: true,
     });
-  }
+  };
 
   handlePrevClick = () => {
     this.setState({
       index: (this.props.media.size + this.getIndex() - 1) % this.props.media.size,
       zoomButtonHidden: true,
     });
-  }
+  };
 
   handleChangeIndex = (e) => {
     const index = Number(e.currentTarget.getAttribute('data-index'));
@@ -72,7 +72,7 @@ class MediaModal extends ImmutablePureComponent {
       index: index % this.props.media.size,
       zoomButtonHidden: true,
     });
-  }
+  };
 
   handleKeyDown = (e) => {
     switch(e.key) {
@@ -87,7 +87,7 @@ class MediaModal extends ImmutablePureComponent {
       e.stopPropagation();
       break;
     }
-  }
+  };
 
   componentDidMount () {
     window.addEventListener('keydown', this.handleKeyDown, false);
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js
index 6c4aabae5..5a1734977 100644
--- a/app/javascript/mastodon/features/ui/components/modal_root.js
+++ b/app/javascript/mastodon/features/ui/components/modal_root.js
@@ -79,17 +79,17 @@ export default class ModalRoot extends React.PureComponent {
 
   setBackgroundColor = color => {
     this.setState({ backgroundColor: color });
-  }
+  };
 
   renderLoading = modalId => () => {
     return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
-  }
+  };
 
   renderError = (props) => {
     const { onClose } = this.props;
 
     return <BundleModalError {...props} onClose={onClose} />;
-  }
+  };
 
   handleClose = (ignoreFocus = false) => {
     const { onClose } = this.props;
@@ -102,11 +102,11 @@ export default class ModalRoot extends React.PureComponent {
       // This would be much smoother with react-intl 3+ and `forwardRef`.
     }
     onClose(message, ignoreFocus);
-  }
+  };
 
   setModalRef = (c) => {
     this._modal = c;
-  }
+  };
 
   render () {
     const { type, props, ignoreFocus } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/mute_modal.js b/app/javascript/mastodon/features/ui/components/mute_modal.js
index d8d8e68c3..b3e0ef56b 100644
--- a/app/javascript/mastodon/features/ui/components/mute_modal.js
+++ b/app/javascript/mastodon/features/ui/components/mute_modal.js
@@ -65,23 +65,23 @@ class MuteModal extends React.PureComponent {
   handleClick = () => {
     this.props.onClose();
     this.props.onConfirm(this.props.account, this.props.notifications, this.props.muteDuration);
-  }
+  };
 
   handleCancel = () => {
     this.props.onClose();
-  }
+  };
 
   setRef = (c) => {
     this.button = c;
-  }
+  };
 
   toggleNotifications = () => {
     this.props.onToggleNotifications();
-  }
+  };
 
   changeMuteDuration = (e) => {
     this.props.onChangeMuteDuration(e);
-  }
+  };
 
   render () {
     const { account, notifications, muteDuration, intl } = this.props;
diff --git a/app/javascript/mastodon/features/ui/components/report_modal.js b/app/javascript/mastodon/features/ui/components/report_modal.js
index 264da07ce..22c31eb52 100644
--- a/app/javascript/mastodon/features/ui/components/report_modal.js
+++ b/app/javascript/mastodon/features/ui/components/report_modal.js
@@ -95,7 +95,7 @@ class ReportModal extends ImmutablePureComponent {
     } else {
       this.setState({ selectedRuleIds: selectedRuleIds.remove(ruleId) });
     }
-  }
+  };
 
   handleChangeCategory = category => {
     this.setState({ category });
diff --git a/app/javascript/mastodon/features/ui/components/upload_area.js b/app/javascript/mastodon/features/ui/components/upload_area.js
index 6c423b2c1..035fe7a26 100644
--- a/app/javascript/mastodon/features/ui/components/upload_area.js
+++ b/app/javascript/mastodon/features/ui/components/upload_area.js
@@ -22,7 +22,7 @@ export default class UploadArea extends React.PureComponent {
         break;
       }
     }
-  }
+  };
 
   componentDidMount () {
     window.addEventListener('keyup', this.handleKeyUp, false);
diff --git a/app/javascript/mastodon/features/ui/components/zoomable_image.js b/app/javascript/mastodon/features/ui/components/zoomable_image.js
index 1cf263cb9..3b2bb0286 100644
--- a/app/javascript/mastodon/features/ui/components/zoomable_image.js
+++ b/app/javascript/mastodon/features/ui/components/zoomable_image.js
@@ -102,7 +102,7 @@ class ZoomableImage extends React.PureComponent {
     onClick: PropTypes.func,
     zoomButtonHidden: PropTypes.bool,
     intl: PropTypes.object.isRequired,
-  }
+  };
 
   static defaultProps = {
     alt: '',
@@ -132,7 +132,7 @@ class ZoomableImage extends React.PureComponent {
     dragged: false,
     lockScroll: { x: 0, y: 0 },
     lockTranslate: { x: 0, y: 0 },
-  }
+  };
 
   removers = [];
   container = null;
@@ -212,7 +212,7 @@ class ZoomableImage extends React.PureComponent {
 
     // lock horizontal scroll
     this.container.scrollLeft = Math.max(this.container.scrollLeft + event.pixelX, this.state.lockScroll.x);
-  }
+  };
 
   mouseDownHandler = e => {
     this.container.style.cursor = 'grabbing';
@@ -228,7 +228,7 @@ class ZoomableImage extends React.PureComponent {
 
     this.image.addEventListener('mousemove', this.mouseMoveHandler);
     this.image.addEventListener('mouseup', this.mouseUpHandler);
-  }
+  };
 
   mouseMoveHandler = e => {
     const dx = e.clientX - this.state.dragPosition.x;
@@ -238,7 +238,7 @@ class ZoomableImage extends React.PureComponent {
     this.container.scrollTop = Math.max(this.state.dragPosition.top - dy, this.state.lockScroll.y);
 
     this.setState({ dragged: true });
-  }
+  };
 
   mouseUpHandler = () => {
     this.container.style.cursor = 'grab';
@@ -246,13 +246,13 @@ class ZoomableImage extends React.PureComponent {
 
     this.image.removeEventListener('mousemove', this.mouseMoveHandler);
     this.image.removeEventListener('mouseup', this.mouseUpHandler);
-  }
+  };
 
   handleTouchStart = e => {
     if (e.touches.length !== 2) return;
 
     this.lastDistance = getDistance(...e.touches);
-  }
+  };
 
   handleTouchMove = e => {
     const { scrollTop, scrollHeight, clientHeight } = this.container;
@@ -275,7 +275,7 @@ class ZoomableImage extends React.PureComponent {
 
     this.lastMidpoint = midpoint;
     this.lastDistance = distance;
-  }
+  };
 
   zoom(nextScale, midpoint) {
     const { scale, zoomMatrix } = this.state;
@@ -314,11 +314,11 @@ class ZoomableImage extends React.PureComponent {
     const handler = this.props.onClick;
     if (handler) handler();
     this.setState({ navigationHidden: !this.state.navigationHidden });
-  }
+  };
 
   handleMouseDown = e => {
     e.preventDefault();
-  }
+  };
 
   initZoomMatrix = () => {
     const { width, height } = this.props;
@@ -350,7 +350,7 @@ class ZoomableImage extends React.PureComponent {
         translateY: translateY,
       },
     });
-  }
+  };
 
   handleZoomClick = e => {
     e.preventDefault();
@@ -392,15 +392,15 @@ class ZoomableImage extends React.PureComponent {
 
     this.container.style.cursor = 'grab';
     this.container.style.removeProperty('user-select');
-  }
+  };
 
   setContainerRef = c => {
     this.container = c;
-  }
+  };
 
   setImageRef = c => {
     this.image = c;
-  }
+  };
 
   render () {
     const { alt, src, width, height, intl } = this.props;
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 78dc9ea40..4f0ea0450 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -148,7 +148,7 @@ class SwitchingColumnsArea extends React.PureComponent {
     if (c) {
       this.node = c;
     }
-  }
+  };
 
   render () {
     const { children, mobile } = this.props;
@@ -270,16 +270,16 @@ class UI extends React.PureComponent {
       // but we set user-friendly message for other browsers, e.g. Edge.
       e.returnValue = intl.formatMessage(messages.beforeUnload);
     }
-  }
+  };
 
   handleWindowFocus = () => {
     this.props.dispatch(focusApp());
     this.props.dispatch(submitMarkers({ immediate: true }));
-  }
+  };
 
   handleWindowBlur = () => {
     this.props.dispatch(unfocusApp());
-  }
+  };
 
   handleDragEnter = (e) => {
     e.preventDefault();
@@ -295,7 +295,7 @@ class UI extends React.PureComponent {
     if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.context.identity.signedIn) {
       this.setState({ draggingOver: true });
     }
-  }
+  };
 
   handleDragOver = (e) => {
     if (this.dataTransferIsText(e.dataTransfer)) return false;
@@ -310,7 +310,7 @@ class UI extends React.PureComponent {
     }
 
     return false;
-  }
+  };
 
   handleDrop = (e) => {
     if (this.dataTransferIsText(e.dataTransfer)) return;
@@ -323,7 +323,7 @@ class UI extends React.PureComponent {
     if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.context.identity.signedIn) {
       this.props.dispatch(uploadCompose(e.dataTransfer.files));
     }
-  }
+  };
 
   handleDragLeave = (e) => {
     e.preventDefault();
@@ -336,15 +336,15 @@ class UI extends React.PureComponent {
     }
 
     this.setState({ draggingOver: false });
-  }
+  };
 
   dataTransferIsText = (dataTransfer) => {
     return (dataTransfer && Array.from(dataTransfer.types).filter((type) => type === 'text/plain').length === 1);
-  }
+  };
 
   closeUploadModal = () => {
     this.setState({ draggingOver: false });
-  }
+  };
 
   handleServiceWorkerPostMessage = ({ data }) => {
     if (data.type === 'navigate') {
@@ -352,7 +352,7 @@ class UI extends React.PureComponent {
     } else {
       console.warn('Unknown message type:', data.type);
     }
-  }
+  };
 
   handleLayoutChange = debounce(() => {
     this.props.dispatch(clearHeight()); // The cached heights are no longer accurate, invalidate
@@ -369,7 +369,7 @@ class UI extends React.PureComponent {
     } else {
       this.handleLayoutChange();
     }
-  }
+  };
 
   componentDidMount () {
     const { signedIn } = this.context.identity;
@@ -423,7 +423,7 @@ class UI extends React.PureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   handleHotkeyNew = e => {
     e.preventDefault();
@@ -433,7 +433,7 @@ class UI extends React.PureComponent {
     if (element) {
       element.focus();
     }
-  }
+  };
 
   handleHotkeySearch = e => {
     e.preventDefault();
@@ -443,17 +443,17 @@ class UI extends React.PureComponent {
     if (element) {
       element.focus();
     }
-  }
+  };
 
   handleHotkeyForceNew = e => {
     this.handleHotkeyNew(e);
     this.props.dispatch(resetCompose());
-  }
+  };
 
   handleHotkeyToggleComposeSpoilers = e => {
     e.preventDefault();
     this.props.dispatch(changeComposeSpoilerness());
-  }
+  };
 
   handleHotkeyFocusColumn = e => {
     const index  = (e.key * 1) + 1; // First child is drawer, skip that
@@ -471,7 +471,7 @@ class UI extends React.PureComponent {
         status.focus();
       }
     }
-  }
+  };
 
   handleHotkeyBack = () => {
     if (window.history && window.history.length === 1) {
@@ -479,11 +479,11 @@ class UI extends React.PureComponent {
     } else {
       this.context.router.history.goBack();
     }
-  }
+  };
 
   setHotkeysRef = c => {
     this.hotkeys = c;
-  }
+  };
 
   handleHotkeyToggleHelp = () => {
     if (this.props.location.pathname === '/keyboard-shortcuts') {
@@ -491,55 +491,55 @@ class UI extends React.PureComponent {
     } else {
       this.context.router.history.push('/keyboard-shortcuts');
     }
-  }
+  };
 
   handleHotkeyGoToHome = () => {
     this.context.router.history.push('/home');
-  }
+  };
 
   handleHotkeyGoToNotifications = () => {
     this.context.router.history.push('/notifications');
-  }
+  };
 
   handleHotkeyGoToLocal = () => {
     this.context.router.history.push('/public/local');
-  }
+  };
 
   handleHotkeyGoToFederated = () => {
     this.context.router.history.push('/public');
-  }
+  };
 
   handleHotkeyGoToDirect = () => {
     this.context.router.history.push('/conversations');
-  }
+  };
 
   handleHotkeyGoToStart = () => {
     this.context.router.history.push('/getting-started');
-  }
+  };
 
   handleHotkeyGoToFavourites = () => {
     this.context.router.history.push('/favourites');
-  }
+  };
 
   handleHotkeyGoToPinned = () => {
     this.context.router.history.push('/pinned');
-  }
+  };
 
   handleHotkeyGoToProfile = () => {
     this.context.router.history.push(`/@${this.props.username}`);
-  }
+  };
 
   handleHotkeyGoToBlocked = () => {
     this.context.router.history.push('/blocks');
-  }
+  };
 
   handleHotkeyGoToMuted = () => {
     this.context.router.history.push('/mutes');
-  }
+  };
 
   handleHotkeyGoToRequests = () => {
     this.context.router.history.push('/follow_requests');
-  }
+  };
 
   render () {
     const { draggingOver } = this.state;
diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.js b/app/javascript/mastodon/features/ui/util/react_router_helpers.js
index 205dd6f10..21b352878 100644
--- a/app/javascript/mastodon/features/ui/util/react_router_helpers.js
+++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.js
@@ -80,17 +80,17 @@ export class WrappedRoute extends React.Component {
         {Component => <Component params={match.params} multiColumn={multiColumn} {...componentParams}>{content}</Component>}
       </BundleContainer>
     );
-  }
+  };
 
   renderLoading = () => {
     const { multiColumn } = this.props;
 
     return <ColumnLoading multiColumn={multiColumn} />;
-  }
+  };
 
   renderError = (props) => {
     return <BundleColumnError {...props} errorType='network' />;
-  }
+  };
 
   render () {
     const { component: Component, content, ...rest } = this.props;
diff --git a/app/javascript/mastodon/features/ui/util/reduced_motion.js b/app/javascript/mastodon/features/ui/util/reduced_motion.js
index 95519042b..1123b80ed 100644
--- a/app/javascript/mastodon/features/ui/util/reduced_motion.js
+++ b/app/javascript/mastodon/features/ui/util/reduced_motion.js
@@ -17,7 +17,7 @@ class ReducedMotion extends React.Component {
     defaultStyle: PropTypes.object,
     style: PropTypes.object,
     children: PropTypes.func,
-  }
+  };
 
   render() {
 
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index 1c35ca9d1..8d63394aa 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/features/video/index.js
@@ -148,7 +148,7 @@ class Video extends React.PureComponent {
     if (this.player) {
       this._setDimensions();
     }
-  }
+  };
 
   _setDimensions () {
     const width = this.player.offsetWidth;
@@ -168,26 +168,26 @@ class Video extends React.PureComponent {
     if (this.video) {
       this.setState({ volume: this.video.volume, muted: this.video.muted });
     }
-  }
+  };
 
   setSeekRef = c => {
     this.seek = c;
-  }
+  };
 
   setVolumeRef = c => {
     this.volume = c;
-  }
+  };
 
   handleClickRoot = e => e.stopPropagation();
 
   handlePlay = () => {
     this.setState({ paused: false });
     this._updateTime();
-  }
+  };
 
   handlePause = () => {
     this.setState({ paused: true });
-  }
+  };
 
   _updateTime () {
     requestAnimationFrame(() => {
@@ -206,7 +206,7 @@ class Video extends React.PureComponent {
       currentTime: this.video.currentTime,
       duration:this.video.duration,
     });
-  }
+  };
 
   handleVolumeMouseDown = e => {
     document.addEventListener('mousemove', this.handleMouseVolSlide, true);
@@ -218,14 +218,14 @@ class Video extends React.PureComponent {
 
     e.preventDefault();
     e.stopPropagation();
-  }
+  };
 
   handleVolumeMouseUp = () => {
     document.removeEventListener('mousemove', this.handleMouseVolSlide, true);
     document.removeEventListener('mouseup', this.handleVolumeMouseUp, true);
     document.removeEventListener('touchmove', this.handleMouseVolSlide, true);
     document.removeEventListener('touchend', this.handleVolumeMouseUp, true);
-  }
+  };
 
   handleMouseVolSlide = throttle(e => {
     const { x } = getPointerPosition(this.volume, e);
@@ -249,7 +249,7 @@ class Video extends React.PureComponent {
 
     e.preventDefault();
     e.stopPropagation();
-  }
+  };
 
   handleMouseUp = () => {
     document.removeEventListener('mousemove', this.handleMouseMove, true);
@@ -259,7 +259,7 @@ class Video extends React.PureComponent {
 
     this.setState({ dragging: false });
     this.video.play();
-  }
+  };
 
   handleMouseMove = throttle(e => {
     const { x } = getPointerPosition(this.seek, e);
@@ -291,7 +291,7 @@ class Video extends React.PureComponent {
       e.stopPropagation();
       this.togglePlay();
     }
-  }
+  };
 
   handleKeyDown = e => {
     const frameTime = 1 / this.getFrameRate();
@@ -345,7 +345,7 @@ class Video extends React.PureComponent {
         exitFullscreen();
       }
     }
-  }
+  };
 
   togglePlay = () => {
     if (this.state.paused) {
@@ -353,7 +353,7 @@ class Video extends React.PureComponent {
     } else {
       this.setState({ paused: true }, () => this.video.pause());
     }
-  }
+  };
 
   toggleFullscreen = () => {
     if (isFullscreen()) {
@@ -361,7 +361,7 @@ class Video extends React.PureComponent {
     } else {
       requestFullscreen(this.player);
     }
-  }
+  };
 
   componentDidMount () {
     document.addEventListener('fullscreenchange', this.handleFullscreenChange, true);
@@ -434,19 +434,19 @@ class Video extends React.PureComponent {
 
       this.setState({ paused: true });
     }
-  }, 150, { trailing: true })
+  }, 150, { trailing: true });
 
   handleFullscreenChange = () => {
     this.setState({ fullscreen: isFullscreen() });
-  }
+  };
 
   handleMouseEnter = () => {
     this.setState({ hovered: true });
-  }
+  };
 
   handleMouseLeave = () => {
     this.setState({ hovered: false });
-  }
+  };
 
   toggleMute = () => {
     const muted = !this.video.muted;
@@ -454,7 +454,7 @@ class Video extends React.PureComponent {
     this.setState({ muted }, () => {
       this.video.muted = muted;
     });
-  }
+  };
 
   toggleReveal = () => {
     if (this.props.onToggleVisibility) {
@@ -462,7 +462,7 @@ class Video extends React.PureComponent {
     } else {
       this.setState({ revealed: !this.state.revealed });
     }
-  }
+  };
 
   handleLoadedData = () => {
     const { currentTime, volume, muted, autoPlay } = this.props;
@@ -482,7 +482,7 @@ class Video extends React.PureComponent {
     if (autoPlay) {
       this.video.play();
     }
-  }
+  };
 
   handleProgress = () => {
     const lastTimeRange = this.video.buffered.length - 1;
@@ -490,11 +490,11 @@ class Video extends React.PureComponent {
     if (lastTimeRange > -1) {
       this.setState({ buffer: Math.ceil(this.video.buffered.end(lastTimeRange) / this.video.duration * 100) });
     }
-  }
+  };
 
   handleVolumeChange = () => {
     this.setState({ volume: this.video.volume, muted: this.video.muted });
-  }
+  };
 
   handleOpenVideo = () => {
     this.video.pause();
@@ -505,12 +505,12 @@ class Video extends React.PureComponent {
       defaultVolume: this.state.volume,
       componentIndex: this.props.componentIndex,
     });
-  }
+  };
 
   handleCloseVideo = () => {
     this.video.pause();
     this.props.onCloseVideo();
-  }
+  };
 
   getFrameRate () {
     if (this.props.frameRate && isNaN(this.props.frameRate)) {
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index 835312a36..746af3f1e 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -128,7 +128,7 @@
   "compose.language.search": "البحث عن لغة…",
   "compose_form.direct_message_warning_learn_more": "تَعَلَّم المَزيد",
   "compose_form.encryption_warning": "إنّ المنشورات على ماستدون ليست مشفرة من النهاية إلى النهاية. لا تشارك أي معلومات حساسة عبر ماستدون.",
-  "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
+  "compose_form.hashtag_warning": "لن يُدرَج هذا المنشور تحت أي وسم بما أنَّه غير منشور للعامة. إلّا الرسائل المنشورة للعامة يُمكن البحث عنها بواسطة وسم.",
   "compose_form.lock_disclaimer": "حسابُك غير {locked}. يُمكن لأي شخص مُتابعتك لرؤية (منشورات المتابعين فقط).",
   "compose_form.lock_disclaimer.lock": "مُقفَل",
   "compose_form.placeholder": "فِيمَ تُفكِّر؟",
@@ -221,7 +221,7 @@
   "empty_column.favourites": "لم يقم أي أحد بالإعجاب بهذا المنشور بعد. عندما يقوم أحدهم بذلك سوف يظهر هنا.",
   "empty_column.follow_recommendations": "يبدو أنه لا يمكن إنشاء أي اقتراحات لك. يمكنك البحث عن أشخاص قد تعرفهم أو استكشاف الوسوم الرائجة.",
   "empty_column.follow_requests": "ليس عندك أي طلب للمتابعة بعد. سوف تظهر طلباتك هنا إن قمت بتلقي البعض منها.",
-  "empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
+  "empty_column.followed_tags": "لم تُتابع أي وسم بعدُ. ستظهر الوسوم هنا حينما تفعل ذلك.",
   "empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.",
   "empty_column.home": "إنّ الخيط الزمني لصفحتك الرئيسية فارغ. قم بزيارة {public} أو استخدم حقل البحث لكي تكتشف مستخدمين آخرين.",
   "empty_column.home.suggestions": "شاهد بعض الاقتراحات",
@@ -543,7 +543,7 @@
   "server_banner.server_stats": "إحصائيات الخادم:",
   "sign_in_banner.create_account": "أنشئ حسابًا",
   "sign_in_banner.sign_in": "تسجيل الدخول",
-  "sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
+  "sign_in_banner.text": "قم بالولوج بحسابك لمتابعة الصفحات الشخصية أو الوسوم، أو لإضافة الرسائل إلى المفضلة ومشاركتها والرد عليها أو التفاعل بواسطة حسابك المتواجد على خادم مختلف.",
   "status.admin_account": "افتح الواجهة الإدارية لـ @{name}",
   "status.admin_domain": "فتح واجهة الإشراف لـ {domain}",
   "status.admin_status": "افتح هذا المنشور على واجهة الإشراف",
diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json
index 688113bc9..7b422dc94 100644
--- a/app/javascript/mastodon/locales/be.json
+++ b/app/javascript/mastodon/locales/be.json
@@ -221,7 +221,7 @@
   "empty_column.favourites": "Ніхто яшчэ не ўпадабаў гэты допіс. Калі гэта адбудзецца, вы ўбачыце гэтых людзей тут.",
   "empty_column.follow_recommendations": "Здаецца, прапаноў для вас няма. Вы можаце паспрабаваць выкарыстаць пошук, каб знайсці людзей, якіх вы можаце ведаць, ці даследаваць папулярныя хэштэгі.",
   "empty_column.follow_requests": "У вас яшчэ няма запытаў на падпіскуі. Калі вы атрымаеце запыт, ён з'явяцца тут.",
-  "empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
+  "empty_column.followed_tags": "Вы пакуль не падпісаны ні на адзін хэштэг. Калі падпішацеся, яны з'явяцца тут.",
   "empty_column.hashtag": "Па гэтаму хэштэгу пакуль што нічога няма.",
   "empty_column.home": "Галоўная стужка пустая! Падпішыцеся на іншых людзей, каб запоўніць яе. {suggestions}",
   "empty_column.home.suggestions": "Глядзіце прапановы",
@@ -264,7 +264,7 @@
   "follow_request.authorize": "Аўтарызацыя",
   "follow_request.reject": "Адхіліць",
   "follow_requests.unlocked_explanation": "Ваш акаўнт не схаваны, аднак прадстаўнікі {domain} палічылі, што вы можаце захацець праглядзець запыты на падпіску з гэтых профіляў уручную.",
-  "followed_tags": "Followed hashtags",
+  "followed_tags": "Падпіскі",
   "footer.about": "Пра нас",
   "footer.directory": "Дырэкторыя профіляў",
   "footer.get_app": "Спампаваць праграму",
@@ -381,7 +381,7 @@
   "navigation_bar.favourites": "Упадабаныя",
   "navigation_bar.filters": "Ігнараваныя словы",
   "navigation_bar.follow_requests": "Запыты на падпіску",
-  "navigation_bar.followed_tags": "Followed hashtags",
+  "navigation_bar.followed_tags": "Падпіскі",
   "navigation_bar.follows_and_followers": "Падпіскі і падпісчыкі",
   "navigation_bar.lists": "Спісы",
   "navigation_bar.logout": "Выйсці",
@@ -543,7 +543,7 @@
   "server_banner.server_stats": "Статыстыка сервера:",
   "sign_in_banner.create_account": "Стварыць уліковы запіс",
   "sign_in_banner.sign_in": "Увайсці",
-  "sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
+  "sign_in_banner.text": "Увайдзіце, каб падпісацца на людзей і тэгі, каб адказваць на допісы, дзяліцца імі і падабаць іх, альбо кантактаваць з вашага ўліковага запісу на іншым серверы.",
   "status.admin_account": "Адкрыць інтэрфейс мадэратара для @{name}",
   "status.admin_domain": "Адкрыць інтэрфейс мадэратара для {domain}",
   "status.admin_status": "Адкрыць гэты допіс у інтэрфейсе мадэрацыі",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index f4d3a6ab7..5ddeedeba 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -264,7 +264,7 @@
   "follow_request.authorize": "Упълномощаване",
   "follow_request.reject": "Отхвърляне",
   "follow_requests.unlocked_explanation": "Въпреки че акаунтът ви не е заключен, служителите на {domain} помислиха, че може да искате да преглеждате ръчно заявките за последване на тези профили.",
-  "followed_tags": "Последвани хештагове",
+  "followed_tags": "Последвани хаштагове",
   "footer.about": "Относно",
   "footer.directory": "Директория на профилите",
   "footer.get_app": "Вземане на приложението",
@@ -579,7 +579,7 @@
   "status.reblog_private": "Подсилване с оригиналната видимост",
   "status.reblogged_by": "{name} подсили",
   "status.reblogs.empty": "Още никого не е подсилвал публикацията. Подсилващият ще се покаже тук.",
-  "status.redraft": "Изтриване и преработване",
+  "status.redraft": "Изтриване и преначертаване",
   "status.remove_bookmark": "Премахване на отметката",
   "status.replied_to": "В отговор до {name}",
   "status.reply": "Отговор",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index d83f15537..5adec87f7 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -18,7 +18,7 @@
   "account.block": "Bloca @{name}",
   "account.block_domain": "Bloca el domini {domain}",
   "account.blocked": "Blocat",
-  "account.browse_more_on_origin_server": "Navega més en el perfil original",
+  "account.browse_more_on_origin_server": "Explora'n més al perfil original",
   "account.cancel_follow_request": "Retira la sol·licitud de seguiment",
   "account.direct": "Missatge directe a @{name}",
   "account.disable_notifications": "Deixa de notificar-me els tuts de @{name}",
@@ -26,8 +26,8 @@
   "account.edit_profile": "Edita el perfil",
   "account.enable_notifications": "Notifica'm els tuts de @{name}",
   "account.endorse": "Recomana en el perfil",
-  "account.featured_tags.last_status_at": "Darrera publicació el {date}",
-  "account.featured_tags.last_status_never": "No hi ha tuts",
+  "account.featured_tags.last_status_at": "Darrer tut el {date}",
+  "account.featured_tags.last_status_never": "No hi ha publicacions",
   "account.featured_tags.title": "etiquetes destacades de {name}",
   "account.follow": "Segueix",
   "account.followers": "Seguidors",
@@ -128,7 +128,7 @@
   "compose.language.search": "Cerca idiomes...",
   "compose_form.direct_message_warning_learn_more": "Més informació",
   "compose_form.encryption_warning": "Els tuts a Mastodon no estant xifrats punt a punt. No comparteixis informació sensible mitjançant Mastodon.",
-  "compose_form.hashtag_warning": "Aquest tut no es mostrarà en cap etiqueta, ja que no és públic. Només els tuts públics es poden cercar per etiqueta.",
+  "compose_form.hashtag_warning": "Aquest tut no apareixerà a les llistes d'etiquetes perquè no és públic. Només els tuts públics apareixen a les cerques per etiqueta.",
   "compose_form.lock_disclaimer": "El teu compte no està {locked}. Tothom pot seguir-te i veure els tuts de només per a seguidors.",
   "compose_form.lock_disclaimer.lock": "blocat",
   "compose_form.placeholder": "Què et passa pel cap?",
@@ -149,7 +149,7 @@
   "compose_form.spoiler.unmarked": "Afegeix avís de contingut",
   "compose_form.spoiler_placeholder": "Escriu l'avís aquí",
   "confirmation_modal.cancel": "Cancel·la",
-  "confirmations.block.block_and_report": "Bloca i informa",
+  "confirmations.block.block_and_report": "Bloca i denuncia",
   "confirmations.block.confirm": "Bloca",
   "confirmations.block.message": "Segur que vols blocar a {name}?",
   "confirmations.cancel_follow_request.confirm": "Retirar la sol·licitud",
@@ -290,30 +290,30 @@
   "home.column_settings.show_replies": "Mostra les respostes",
   "home.hide_announcements": "Amaga els anuncis",
   "home.show_announcements": "Mostra els anuncis",
-  "interaction_modal.description.favourite": "Amb un compte a Mastodon pots afavorir aquesta publicació, que l'autor sàpiga que t'ha agradat i desar-la per a més endavant.",
+  "interaction_modal.description.favourite": "Amb un compte a Mastodon pots afavorir aquest tut perquè l'autor sàpiga que t'ha agradat i desar-lo per a més endavant.",
   "interaction_modal.description.follow": "Amb un compte a Mastodon, pots seguir a {name} per a rebre els seus tuts en la teva línia de temps d'Inici.",
   "interaction_modal.description.reblog": "Amb un compte a Mastodon, pots impulsar aquesta publicació per a compartir-la amb els teus seguidors.",
-  "interaction_modal.description.reply": "Amb un compte a Mastodon, pots respondre aquest tut.",
-  "interaction_modal.on_another_server": "A un altre servidor",
+  "interaction_modal.description.reply": "Amb un compte a Mastodon, pots respondre aquesta publicació.",
+  "interaction_modal.on_another_server": "En un servidor diferent",
   "interaction_modal.on_this_server": "En aquest servidor",
   "interaction_modal.other_server_instructions": "Copia i enganxa aquest URL en el camp de cerca de la teva aplicació Mastodon preferida o a la interfície web del teu servidor Mastodon.",
   "interaction_modal.preamble": "Com que Mastodon és descentralitzat, pots fer servir el teu compte existent en un altre servidor Mastodon o plataforma compatible si no tens compte en aquest.",
-  "interaction_modal.title.favourite": "Marca el tut de {name}",
+  "interaction_modal.title.favourite": "Marca la publicació de {name}",
   "interaction_modal.title.follow": "Segueix {name}",
-  "interaction_modal.title.reblog": "Impulsa la publicació de {name}",
+  "interaction_modal.title.reblog": "Impulsa el tut de {name}",
   "interaction_modal.title.reply": "Respon a la publicació de {name}",
   "intervals.full.days": "{number, plural, one {# dia} other {# dies}}",
   "intervals.full.hours": "{number, plural, one {# hora} other {# hores}}",
   "intervals.full.minutes": "{number, plural, one {# minut} other {# minuts}}",
   "keyboard_shortcuts.back": "Vés enrere",
   "keyboard_shortcuts.blocked": "Obre la llista d'usuaris blocats",
-  "keyboard_shortcuts.boost": "Impulsa el tut",
+  "keyboard_shortcuts.boost": "Impulsa la publicació",
   "keyboard_shortcuts.column": "Centra la columna",
   "keyboard_shortcuts.compose": "Centra l'àrea de composició de text",
   "keyboard_shortcuts.description": "Descripció",
   "keyboard_shortcuts.direct": "Obre la columna de missatges directes",
   "keyboard_shortcuts.down": "Abaixa a la llista",
-  "keyboard_shortcuts.enter": "Obre la publicació",
+  "keyboard_shortcuts.enter": "Obre el tut",
   "keyboard_shortcuts.favourite": "Afavoreix la publicació",
   "keyboard_shortcuts.favourites": "Obre la llista de preferits",
   "keyboard_shortcuts.federated": "Obre la línia de temps federada",
@@ -329,7 +329,7 @@
   "keyboard_shortcuts.open_media": "Obre mèdia",
   "keyboard_shortcuts.pinned": "Obre la llista de tuts fixats",
   "keyboard_shortcuts.profile": "Obre el perfil de l'autor",
-  "keyboard_shortcuts.reply": "Respon al tut",
+  "keyboard_shortcuts.reply": "Respon a la publicació",
   "keyboard_shortcuts.requests": "Obre la llista de sol·licituds de seguiment",
   "keyboard_shortcuts.search": "Centra la barra de cerca",
   "keyboard_shortcuts.spoilers": "Mostra/amaga el camp CW",
@@ -395,7 +395,7 @@
   "not_signed_in_indicator.not_signed_in": "Necessites iniciar la sessió per a accedir aquest recurs.",
   "notification.admin.report": "{name} ha reportat {target}",
   "notification.admin.sign_up": "{name} s'ha registrat",
-  "notification.favourite": "a {name} li ha agradat el teu tut",
+  "notification.favourite": "a {name} li ha agradat la teva publicació",
   "notification.follow": "{name} et segueix",
   "notification.follow_request": "{name} ha sol·licitat seguir-te",
   "notification.mention": "{name} t'ha mencionat",
@@ -485,7 +485,7 @@
   "report.category.subtitle": "Tria la millor coincidència",
   "report.category.title": "Explica'ns què passa amb això ({type})",
   "report.category.title_account": "perfil",
-  "report.category.title_status": "tut",
+  "report.category.title_status": "publicació",
   "report.close": "Fet",
   "report.comment.title": "Hi ha res més que creguis que hauríem de saber?",
   "report.forward": "Reenvia a {target}",
@@ -505,9 +505,9 @@
   "report.rules.subtitle": "Selecciona totes les aplicables",
   "report.rules.title": "Quines regles s'han violat?",
   "report.statuses.subtitle": "Selecciona totes les aplicables",
-  "report.statuses.title": "Hi ha cap tut que doni suport a aquest informe?",
+  "report.statuses.title": "Hi ha cap publicació que doni suport a aquest informe?",
   "report.submit": "Envia",
-  "report.target": "Es reporta {target}",
+  "report.target": "Es denuncia {target}",
   "report.thanks.take_action": "Aquestes són les teves opcions per a controlar el que veus a Mastodon:",
   "report.thanks.take_action_actionable": "Mentre ho revisem, pots prendre mesures contra @{name}:",
   "report.thanks.title": "No ho vols veure?",
@@ -524,7 +524,7 @@
   "search_popout.search_format": "Format de cerca avançada",
   "search_popout.tips.full_text": "Text simple recupera tuts que has escrit, els marcats com a favorits, els impulsats o en els que has estat esmentat, així com usuaris, noms d'usuari i etiquetes.",
   "search_popout.tips.hashtag": "etiqueta",
-  "search_popout.tips.status": "tut",
+  "search_popout.tips.status": "publicació",
   "search_popout.tips.text": "El text simple recupera coincidències amb els usuaris, els noms d'usuari i les etiquetes",
   "search_popout.tips.user": "usuari",
   "search_results.accounts": "Gent",
@@ -546,12 +546,12 @@
   "sign_in_banner.text": "Inicia la sessió per a seguir perfils o etiquetes, afavorir, compartir i respondre publicacions. També pots interactuar des del teu compte a un servidor diferent.",
   "status.admin_account": "Obre la interfície de moderació per a @{name}",
   "status.admin_domain": "Obre la interfície de moderació per a @{domain}",
-  "status.admin_status": "Obrir aquest tut a la interfície de moderació",
+  "status.admin_status": "Obre aquest tut a la interfície de moderació",
   "status.block": "Bloca @{name}",
   "status.bookmark": "Marca",
   "status.cancel_reblog_private": "Desfés l'impuls",
   "status.cannot_reblog": "No es pot impulsar aquest tut",
-  "status.copy": "Copia l'enllaç al tut",
+  "status.copy": "Copia l'enllaç a la publicació",
   "status.delete": "Elimina",
   "status.detailed_status": "Vista detallada de la conversa",
   "status.direct": "Missatge directe a @{name}",
@@ -560,9 +560,9 @@
   "status.edited_x_times": "Editat {count, plural, one {{count} vegada} other {{count} vegades}}",
   "status.embed": "Incrusta",
   "status.favourite": "Favorit",
-  "status.filter": "Filtra aquest tut",
+  "status.filter": "Filtra aquesta publicació",
   "status.filtered": "Filtrada",
-  "status.hide": "Amaga el tut",
+  "status.hide": "Amaga la publicació",
   "status.history.created": "creat per {name} {date}",
   "status.history.edited": "editat per {name} {date}",
   "status.load_more": "Carrega'n més",
@@ -571,9 +571,9 @@
   "status.more": "Més",
   "status.mute": "Silencia @{name}",
   "status.mute_conversation": "Silencia la conversa",
-  "status.open": "Amplia el tut",
+  "status.open": "Amplia la publicació",
   "status.pin": "Fixa en el perfil",
-  "status.pinned": "Tut fixat",
+  "status.pinned": "Publicació fixada",
   "status.read_more": "Més informació",
   "status.reblog": "Impulsa",
   "status.reblog_private": "Impulsa amb la visibilitat original",
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index 8588501d2..7ff5aa221 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -221,7 +221,7 @@
   "empty_column.favourites": "Tento příspěvek si zatím nikdo neoblíbil. Až to někdo udělá, zobrazí se zde.",
   "empty_column.follow_recommendations": "Zdá se, že pro vás nelze vygenerovat žádné návrhy. Můžete zkusit přes vyhledávání nalézt lidi, které znáte, nebo prozkoumat populární hashtagy.",
   "empty_column.follow_requests": "Zatím nemáte žádné žádosti o sledování. Až nějakou obdržíte, zobrazí se zde.",
-  "empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
+  "empty_column.followed_tags": "Zatím jste nesledovali žádné hashtagy. Až to uděláte, objeví se zde.",
   "empty_column.hashtag": "Pod tímto hashtagem zde zatím nic není.",
   "empty_column.home": "Vaše domovská časová osa je prázdná! Naplňte ji sledováním dalších lidí. {suggestions}",
   "empty_column.home.suggestions": "Prohlédněte si návrhy",
@@ -264,7 +264,7 @@
   "follow_request.authorize": "Autorizovat",
   "follow_request.reject": "Zamítnout",
   "follow_requests.unlocked_explanation": "Přestože váš účet není zamčený, administrátor {domain} usoudil, že byste mohli chtít tyto žádosti o sledování zkontrolovat ručně.",
-  "followed_tags": "Followed hashtags",
+  "followed_tags": "Sledované hashtagy",
   "footer.about": "O aplikaci",
   "footer.directory": "Adresář profilů",
   "footer.get_app": "Získat aplikaci",
@@ -381,7 +381,7 @@
   "navigation_bar.favourites": "Oblíbené",
   "navigation_bar.filters": "Skrytá slova",
   "navigation_bar.follow_requests": "Žádosti o sledování",
-  "navigation_bar.followed_tags": "Followed hashtags",
+  "navigation_bar.followed_tags": "Sledované hashtagy",
   "navigation_bar.follows_and_followers": "Sledovaní a sledující",
   "navigation_bar.lists": "Seznamy",
   "navigation_bar.logout": "Odhlásit se",
diff --git a/app/javascript/mastodon/locales/csb.json b/app/javascript/mastodon/locales/csb.json
new file mode 100644
index 000000000..fbb103d2c
--- /dev/null
+++ b/app/javascript/mastodon/locales/csb.json
@@ -0,0 +1,658 @@
+{
+  "about.blocks": "Moderated servers",
+  "about.contact": "Contact:",
+  "about.disclaimer": "Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.",
+  "about.domain_blocks.no_reason_available": "Reason not available",
+  "about.domain_blocks.preamble": "Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server.",
+  "about.domain_blocks.silenced.explanation": "You will generally not see profiles and content from this server, unless you explicitly look it up or opt into it by following.",
+  "about.domain_blocks.silenced.title": "Limited",
+  "about.domain_blocks.suspended.explanation": "No data from this server will be processed, stored or exchanged, making any interaction or communication with users from this server impossible.",
+  "about.domain_blocks.suspended.title": "Suspended",
+  "about.not_available": "This information has not been made available on this server.",
+  "about.powered_by": "Decentralized social media powered by {mastodon}",
+  "about.rules": "Server rules",
+  "account.account_note_header": "Note",
+  "account.add_or_remove_from_list": "Add or Remove from lists",
+  "account.badges.bot": "Bot",
+  "account.badges.group": "Group",
+  "account.block": "Block @{name}",
+  "account.block_domain": "Block domain {domain}",
+  "account.blocked": "Blocked",
+  "account.browse_more_on_origin_server": "Browse more on the original profile",
+  "account.cancel_follow_request": "Withdraw follow request",
+  "account.direct": "Direct message @{name}",
+  "account.disable_notifications": "Stop notifying me when @{name} posts",
+  "account.domain_blocked": "Domain blocked",
+  "account.edit_profile": "Edit profile",
+  "account.enable_notifications": "Notify me when @{name} posts",
+  "account.endorse": "Feature on profile",
+  "account.featured_tags.last_status_at": "Last post on {date}",
+  "account.featured_tags.last_status_never": "No posts",
+  "account.featured_tags.title": "{name}'s featured hashtags",
+  "account.follow": "Follow",
+  "account.followers": "Followers",
+  "account.followers.empty": "No one follows this user yet.",
+  "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
+  "account.following": "Following",
+  "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
+  "account.follows.empty": "This user doesn't follow anyone yet.",
+  "account.follows_you": "Follows you",
+  "account.go_to_profile": "Go to profile",
+  "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.joined_short": "Joined",
+  "account.languages": "Change subscribed languages",
+  "account.link_verified_on": "Ownership of this link was checked on {date}",
+  "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
+  "account.media": "Media",
+  "account.mention": "Mention @{name}",
+  "account.moved_to": "{name} has indicated that their new account is now:",
+  "account.mute": "Mute @{name}",
+  "account.mute_notifications": "Mute notifications from @{name}",
+  "account.muted": "Muted",
+  "account.open_original_page": "Open original page",
+  "account.posts": "Posts",
+  "account.posts_with_replies": "Posts and replies",
+  "account.report": "Report @{name}",
+  "account.requested": "Awaiting approval. Click to cancel follow request",
+  "account.requested_follow": "{name} has requested to follow you",
+  "account.share": "Share @{name}'s profile",
+  "account.show_reblogs": "Show boosts from @{name}",
+  "account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Posts}}",
+  "account.unblock": "Unblock @{name}",
+  "account.unblock_domain": "Unblock domain {domain}",
+  "account.unblock_short": "Unblock",
+  "account.unendorse": "Don't feature on profile",
+  "account.unfollow": "Unfollow",
+  "account.unmute": "Unmute @{name}",
+  "account.unmute_notifications": "Unmute notifications from @{name}",
+  "account.unmute_short": "Unmute",
+  "account_note.placeholder": "Click to add a note",
+  "admin.dashboard.daily_retention": "User retention rate by day after sign-up",
+  "admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
+  "admin.dashboard.retention.average": "Average",
+  "admin.dashboard.retention.cohort": "Sign-up month",
+  "admin.dashboard.retention.cohort_size": "New users",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
+  "alert.unexpected.message": "An unexpected error occurred.",
+  "alert.unexpected.title": "Oops!",
+  "announcement.announcement": "Announcement",
+  "attachments_list.unprocessed": "(unprocessed)",
+  "audio.hide": "Hide audio",
+  "autosuggest_hashtag.per_week": "{count} per week",
+  "boost_modal.combo": "You can press {combo} to skip this next time",
+  "bundle_column_error.copy_stacktrace": "Copy error report",
+  "bundle_column_error.error.body": "The requested page could not be rendered. It could be due to a bug in our code, or a browser compatibility issue.",
+  "bundle_column_error.error.title": "Oh, no!",
+  "bundle_column_error.network.body": "There was an error when trying to load this page. This could be due to a temporary problem with your internet connection or this server.",
+  "bundle_column_error.network.title": "Network error",
+  "bundle_column_error.retry": "Try again",
+  "bundle_column_error.return": "Go back home",
+  "bundle_column_error.routing.body": "The requested page could not be found. Are you sure the URL in the address bar is correct?",
+  "bundle_column_error.routing.title": "404",
+  "bundle_modal_error.close": "Close",
+  "bundle_modal_error.message": "Something went wrong while loading this component.",
+  "bundle_modal_error.retry": "Try again",
+  "closed_registrations.other_server_instructions": "Since Mastodon is decentralized, you can create an account on another server and still interact with this one.",
+  "closed_registrations_modal.description": "Creating an account on {domain} is currently not possible, but please keep in mind that you do not need an account specifically on {domain} to use Mastodon.",
+  "closed_registrations_modal.find_another_server": "Find another server",
+  "closed_registrations_modal.preamble": "Mastodon is decentralized, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!",
+  "closed_registrations_modal.title": "Signing up on Mastodon",
+  "column.about": "About",
+  "column.blocks": "Blocked users",
+  "column.bookmarks": "Bookmarks",
+  "column.community": "Local timeline",
+  "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
+  "column.domain_blocks": "Blocked domains",
+  "column.favourites": "Favourites",
+  "column.follow_requests": "Follow requests",
+  "column.home": "Home",
+  "column.lists": "Lists",
+  "column.mutes": "Muted users",
+  "column.notifications": "Notifications",
+  "column.pins": "Pinned post",
+  "column.public": "Federated timeline",
+  "column_back_button.label": "Back",
+  "column_header.hide_settings": "Hide settings",
+  "column_header.moveLeft_settings": "Move column to the left",
+  "column_header.moveRight_settings": "Move column to the right",
+  "column_header.pin": "Pin",
+  "column_header.show_settings": "Show settings",
+  "column_header.unpin": "Unpin",
+  "column_subheading.settings": "Settings",
+  "community.column_settings.local_only": "Local only",
+  "community.column_settings.media_only": "Media only",
+  "community.column_settings.remote_only": "Remote only",
+  "compose.language.change": "Change language",
+  "compose.language.search": "Search languages...",
+  "compose_form.direct_message_warning_learn_more": "Learn more",
+  "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
+  "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
+  "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
+  "compose_form.lock_disclaimer.lock": "locked",
+  "compose_form.placeholder": "What is on your mind?",
+  "compose_form.poll.add_option": "Add a choice",
+  "compose_form.poll.duration": "Poll duration",
+  "compose_form.poll.option_placeholder": "Choice {number}",
+  "compose_form.poll.remove_option": "Remove this choice",
+  "compose_form.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.publish": "Publish",
+  "compose_form.publish_form": "Publish",
+  "compose_form.publish_loud": "{publish}!",
+  "compose_form.save_changes": "Save changes",
+  "compose_form.sensitive.hide": "{count, plural, one {Mark media as sensitive} other {Mark media as sensitive}}",
+  "compose_form.sensitive.marked": "{count, plural, one {Media is marked as sensitive} other {Media is marked as sensitive}}",
+  "compose_form.sensitive.unmarked": "{count, plural, one {Media is not marked as sensitive} other {Media is not marked as sensitive}}",
+  "compose_form.spoiler.marked": "Text is hidden behind warning",
+  "compose_form.spoiler.unmarked": "Text is not hidden",
+  "compose_form.spoiler_placeholder": "Write your warning here",
+  "confirmation_modal.cancel": "Cancel",
+  "confirmations.block.block_and_report": "Block & Report",
+  "confirmations.block.confirm": "Block",
+  "confirmations.block.message": "Are you sure you want to block {name}?",
+  "confirmations.cancel_follow_request.confirm": "Withdraw request",
+  "confirmations.cancel_follow_request.message": "Are you sure you want to withdraw your request to follow {name}?",
+  "confirmations.delete.confirm": "Delete",
+  "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.delete_list.confirm": "Delete",
+  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
+  "confirmations.discard_edit_media.confirm": "Discard",
+  "confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
+  "confirmations.domain_block.confirm": "Hide entire domain",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
+  "confirmations.mute.confirm": "Mute",
+  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
+  "confirmations.mute.message": "Are you sure you want to mute {name}?",
+  "confirmations.redraft.confirm": "Delete & redraft",
+  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
+  "confirmations.reply.confirm": "Reply",
+  "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+  "confirmations.unfollow.confirm": "Unfollow",
+  "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "conversation.delete": "Delete conversation",
+  "conversation.mark_as_read": "Mark as read",
+  "conversation.open": "View conversation",
+  "conversation.with": "With {names}",
+  "copypaste.copied": "Copied",
+  "copypaste.copy": "Copy",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
+  "disabled_account_banner.account_settings": "Account settings",
+  "disabled_account_banner.text": "Your account {disabledAccount} is currently disabled.",
+  "dismissable_banner.community_timeline": "These are the most recent public posts from people whose accounts are hosted by {domain}.",
+  "dismissable_banner.dismiss": "Dismiss",
+  "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.explore_statuses": "These posts from this and other servers in the decentralized network are gaining traction on this server right now.",
+  "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
+  "dismissable_banner.public_timeline": "These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.",
+  "embed.instructions": "Embed this status on your website by copying the code below.",
+  "embed.preview": "Here is what it will look like:",
+  "emoji_button.activity": "Activity",
+  "emoji_button.clear": "Clear",
+  "emoji_button.custom": "Custom",
+  "emoji_button.flags": "Flags",
+  "emoji_button.food": "Food & Drink",
+  "emoji_button.label": "Insert emoji",
+  "emoji_button.nature": "Nature",
+  "emoji_button.not_found": "No matching emojis found",
+  "emoji_button.objects": "Objects",
+  "emoji_button.people": "People",
+  "emoji_button.recent": "Frequently used",
+  "emoji_button.search": "Search...",
+  "emoji_button.search_results": "Search results",
+  "emoji_button.symbols": "Symbols",
+  "emoji_button.travel": "Travel & Places",
+  "empty_column.account_suspended": "Account suspended",
+  "empty_column.account_timeline": "No posts found",
+  "empty_column.account_unavailable": "Profile unavailable",
+  "empty_column.blocks": "You haven't blocked any users yet.",
+  "empty_column.bookmarked_statuses": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.",
+  "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+  "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
+  "empty_column.domain_blocks": "There are no blocked domains yet.",
+  "empty_column.explore_statuses": "Nothing is trending right now. Check back later!",
+  "empty_column.favourited_statuses": "You don't have any favourite posts yet. When you favourite one, it will show up here.",
+  "empty_column.favourites": "No one has favourited this post yet. When someone does, they will show up here.",
+  "empty_column.follow_recommendations": "Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.",
+  "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
+  "empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
+  "empty_column.hashtag": "There is nothing in this hashtag yet.",
+  "empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
+  "empty_column.home.suggestions": "See some suggestions",
+  "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
+  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
+  "empty_column.mutes": "You haven't muted any users yet.",
+  "empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.",
+  "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
+  "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+  "error.unexpected_crash.explanation_addons": "This page could not be displayed correctly. This error is likely caused by a browser add-on or automatic translation tools.",
+  "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "error.unexpected_crash.next_steps_addons": "Try disabling them and refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+  "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
+  "errors.unexpected_crash.report_issue": "Report issue",
+  "explore.search_results": "Search results",
+  "explore.suggested_follows": "For you",
+  "explore.title": "Explore",
+  "explore.trending_links": "News",
+  "explore.trending_statuses": "Posts",
+  "explore.trending_tags": "Hashtags",
+  "filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
+  "filter_modal.added.context_mismatch_title": "Context mismatch!",
+  "filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
+  "filter_modal.added.expired_title": "Expired filter!",
+  "filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
+  "filter_modal.added.review_and_configure_title": "Filter settings",
+  "filter_modal.added.settings_link": "settings page",
+  "filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
+  "filter_modal.added.title": "Filter added!",
+  "filter_modal.select_filter.context_mismatch": "does not apply to this context",
+  "filter_modal.select_filter.expired": "expired",
+  "filter_modal.select_filter.prompt_new": "New category: {name}",
+  "filter_modal.select_filter.search": "Search or create",
+  "filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
+  "filter_modal.select_filter.title": "Filter this post",
+  "filter_modal.title.status": "Filter a post",
+  "follow_recommendations.done": "Done",
+  "follow_recommendations.heading": "Follow people you'd like to see posts from! Here are some suggestions.",
+  "follow_recommendations.lead": "Posts from people you follow will show up in chronological order on your home feed. Don't be afraid to make mistakes, you can unfollow people just as easily any time!",
+  "follow_request.authorize": "Authorize",
+  "follow_request.reject": "Reject",
+  "follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.",
+  "followed_tags": "Followed hashtags",
+  "footer.about": "About",
+  "footer.directory": "Profiles directory",
+  "footer.get_app": "Get the app",
+  "footer.invite": "Invite people",
+  "footer.keyboard_shortcuts": "Keyboard shortcuts",
+  "footer.privacy_policy": "Privacy policy",
+  "footer.source_code": "View source code",
+  "generic.saved": "Saved",
+  "getting_started.heading": "Getting started",
+  "hashtag.column_header.tag_mode.all": "and {additional}",
+  "hashtag.column_header.tag_mode.any": "or {additional}",
+  "hashtag.column_header.tag_mode.none": "without {additional}",
+  "hashtag.column_settings.select.no_options_message": "No suggestions found",
+  "hashtag.column_settings.select.placeholder": "Enter hashtags…",
+  "hashtag.column_settings.tag_mode.all": "All of these",
+  "hashtag.column_settings.tag_mode.any": "Any of these",
+  "hashtag.column_settings.tag_mode.none": "None of these",
+  "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
+  "hashtag.follow": "Follow hashtag",
+  "hashtag.unfollow": "Unfollow hashtag",
+  "home.column_settings.basic": "Basic",
+  "home.column_settings.show_reblogs": "Show boosts",
+  "home.column_settings.show_replies": "Show replies",
+  "home.hide_announcements": "Hide announcements",
+  "home.show_announcements": "Show announcements",
+  "interaction_modal.description.favourite": "With an account on Mastodon, you can favourite this post to let the author know you appreciate it and save it for later.",
+  "interaction_modal.description.follow": "With an account on Mastodon, you can follow {name} to receive their posts in your home feed.",
+  "interaction_modal.description.reblog": "With an account on Mastodon, you can boost this post to share it with your own followers.",
+  "interaction_modal.description.reply": "With an account on Mastodon, you can respond to this post.",
+  "interaction_modal.on_another_server": "On a different server",
+  "interaction_modal.on_this_server": "On this server",
+  "interaction_modal.other_server_instructions": "Copy and paste this URL into the search field of your favourite Mastodon app or the web interface of your Mastodon server.",
+  "interaction_modal.preamble": "Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one.",
+  "interaction_modal.title.favourite": "Favourite {name}'s post",
+  "interaction_modal.title.follow": "Follow {name}",
+  "interaction_modal.title.reblog": "Boost {name}'s post",
+  "interaction_modal.title.reply": "Reply to {name}'s post",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "keyboard_shortcuts.back": "to navigate back",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
+  "keyboard_shortcuts.boost": "to boost",
+  "keyboard_shortcuts.column": "to focus a status in one of the columns",
+  "keyboard_shortcuts.compose": "to focus the compose textarea",
+  "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.direct": "to open direct messages column",
+  "keyboard_shortcuts.down": "to move down in the list",
+  "keyboard_shortcuts.enter": "to open status",
+  "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
+  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
+  "keyboard_shortcuts.hotkey": "Hotkey",
+  "keyboard_shortcuts.legend": "to display this legend",
+  "keyboard_shortcuts.local": "to open local timeline",
+  "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "to open your profile",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.open_media": "to open media",
+  "keyboard_shortcuts.pinned": "to open pinned posts list",
+  "keyboard_shortcuts.profile": "to open author's profile",
+  "keyboard_shortcuts.reply": "to reply",
+  "keyboard_shortcuts.requests": "to open follow requests list",
+  "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.spoilers": "to show/hide CW field",
+  "keyboard_shortcuts.start": "to open \"get started\" column",
+  "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
+  "keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
+  "keyboard_shortcuts.toot": "to start a brand new post",
+  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
+  "keyboard_shortcuts.up": "to move up in the list",
+  "lightbox.close": "Close",
+  "lightbox.compress": "Compress image view box",
+  "lightbox.expand": "Expand image view box",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
+  "limited_account_hint.action": "Show profile anyway",
+  "limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.",
+  "lists.account.add": "Add to list",
+  "lists.account.remove": "Remove from list",
+  "lists.delete": "Delete list",
+  "lists.edit": "Edit list",
+  "lists.edit.submit": "Change title",
+  "lists.new.create": "Add list",
+  "lists.new.title_placeholder": "New list title",
+  "lists.replies_policy.followed": "Any followed user",
+  "lists.replies_policy.list": "Members of the list",
+  "lists.replies_policy.none": "No one",
+  "lists.replies_policy.title": "Show replies to:",
+  "lists.search": "Search among people you follow",
+  "lists.subheading": "Your lists",
+  "load_pending": "{count, plural, one {# new item} other {# new items}}",
+  "loading_indicator.label": "Loading...",
+  "media_gallery.toggle_visible": "{number, plural, one {Hide image} other {Hide images}}",
+  "missing_indicator.label": "Not found",
+  "missing_indicator.sublabel": "This resource could not be found",
+  "moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.",
+  "mute_modal.duration": "Duration",
+  "mute_modal.hide_notifications": "Hide notifications from this user?",
+  "mute_modal.indefinite": "Indefinite",
+  "navigation_bar.about": "About",
+  "navigation_bar.blocks": "Blocked users",
+  "navigation_bar.bookmarks": "Bookmarks",
+  "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.compose": "Compose new post",
+  "navigation_bar.direct": "Direct messages",
+  "navigation_bar.discover": "Discover",
+  "navigation_bar.domain_blocks": "Hidden domains",
+  "navigation_bar.edit_profile": "Edit profile",
+  "navigation_bar.explore": "Explore",
+  "navigation_bar.favourites": "Favourites",
+  "navigation_bar.filters": "Muted words",
+  "navigation_bar.follow_requests": "Follow requests",
+  "navigation_bar.followed_tags": "Followed hashtags",
+  "navigation_bar.follows_and_followers": "Follows and followers",
+  "navigation_bar.lists": "Lists",
+  "navigation_bar.logout": "Logout",
+  "navigation_bar.mutes": "Muted users",
+  "navigation_bar.personal": "Personal",
+  "navigation_bar.pins": "Pinned posts",
+  "navigation_bar.preferences": "Preferences",
+  "navigation_bar.public_timeline": "Federated timeline",
+  "navigation_bar.search": "Search",
+  "navigation_bar.security": "Security",
+  "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
+  "notification.admin.report": "{name} reported {target}",
+  "notification.admin.sign_up": "{name} signed up",
+  "notification.favourite": "{name} favourited your status",
+  "notification.follow": "{name} followed you",
+  "notification.follow_request": "{name} has requested to follow you",
+  "notification.mention": "{name} mentioned you",
+  "notification.own_poll": "Your poll has ended",
+  "notification.poll": "A poll you have voted in has ended",
+  "notification.reblog": "{name} boosted your status",
+  "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
+  "notifications.clear": "Clear notifications",
+  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+  "notifications.column_settings.admin.report": "New reports:",
+  "notifications.column_settings.admin.sign_up": "New sign-ups:",
+  "notifications.column_settings.alert": "Desktop notifications",
+  "notifications.column_settings.favourite": "Favourites:",
+  "notifications.column_settings.filter_bar.advanced": "Display all categories",
+  "notifications.column_settings.filter_bar.category": "Quick filter bar",
+  "notifications.column_settings.filter_bar.show_bar": "Show filter bar",
+  "notifications.column_settings.follow": "New followers:",
+  "notifications.column_settings.follow_request": "New follow requests:",
+  "notifications.column_settings.mention": "Mentions:",
+  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.push": "Push notifications",
+  "notifications.column_settings.reblog": "Boosts:",
+  "notifications.column_settings.show": "Show in column",
+  "notifications.column_settings.sound": "Play sound",
+  "notifications.column_settings.status": "New posts:",
+  "notifications.column_settings.unread_notifications.category": "Unread notifications",
+  "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
+  "notifications.filter.all": "All",
+  "notifications.filter.boosts": "Boosts",
+  "notifications.filter.favourites": "Favourites",
+  "notifications.filter.follows": "Follows",
+  "notifications.filter.mentions": "Mentions",
+  "notifications.filter.polls": "Poll results",
+  "notifications.filter.statuses": "Updates from people you follow",
+  "notifications.grant_permission": "Grant permission.",
+  "notifications.group": "{count} notifications",
+  "notifications.mark_as_read": "Mark every notification as read",
+  "notifications.permission_denied": "Desktop notifications are unavailable due to previously denied browser permissions request",
+  "notifications.permission_denied_alert": "Desktop notifications can't be enabled, as browser permission has been denied before",
+  "notifications.permission_required": "Desktop notifications are unavailable because the required permission has not been granted.",
+  "notifications_permission_banner.enable": "Enable desktop notifications",
+  "notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.",
+  "notifications_permission_banner.title": "Never miss a thing",
+  "picture_in_picture.restore": "Put it back",
+  "poll.closed": "Closed",
+  "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
+  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
+  "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
+  "poll.votes": "{votes, plural, one {# vote} other {# votes}}",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
+  "privacy.change": "Adjust status privacy",
+  "privacy.direct.long": "Visible for mentioned users only",
+  "privacy.direct.short": "Direct",
+  "privacy.private.long": "Visible for followers only",
+  "privacy.private.short": "Followers-only",
+  "privacy.public.long": "Visible for all",
+  "privacy.public.short": "Public",
+  "privacy.unlisted.long": "Visible for all, but opted-out of discovery features",
+  "privacy.unlisted.short": "Unlisted",
+  "privacy_policy.last_updated": "Last updated {date}",
+  "privacy_policy.title": "Privacy Policy",
+  "refresh": "Refresh",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
+  "relative_time.days": "{number}d",
+  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
+  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
+  "relative_time.full.just_now": "just now",
+  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
+  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.hours": "{number}h",
+  "relative_time.just_now": "now",
+  "relative_time.minutes": "{number}m",
+  "relative_time.seconds": "{number}s",
+  "relative_time.today": "today",
+  "reply_indicator.cancel": "Cancel",
+  "report.block": "Block",
+  "report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.",
+  "report.categories.other": "Other",
+  "report.categories.spam": "Spam",
+  "report.categories.violation": "Content violates one or more server rules",
+  "report.category.subtitle": "Choose the best match",
+  "report.category.title": "Tell us what's going on with this {type}",
+  "report.category.title_account": "profile",
+  "report.category.title_status": "post",
+  "report.close": "Done",
+  "report.comment.title": "Is there anything else you think we should know?",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.mute": "Mute",
+  "report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
+  "report.next": "Next",
+  "report.placeholder": "Type or paste additional comments",
+  "report.reasons.dislike": "I don't like it",
+  "report.reasons.dislike_description": "It is not something you want to see",
+  "report.reasons.other": "It's something else",
+  "report.reasons.other_description": "The issue does not fit into other categories",
+  "report.reasons.spam": "It's spam",
+  "report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies",
+  "report.reasons.violation": "It violates server rules",
+  "report.reasons.violation_description": "You are aware that it breaks specific rules",
+  "report.rules.subtitle": "Select all that apply",
+  "report.rules.title": "Which rules are being violated?",
+  "report.statuses.subtitle": "Select all that apply",
+  "report.statuses.title": "Are there any posts that back up this report?",
+  "report.submit": "Submit report",
+  "report.target": "Report {target}",
+  "report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:",
+  "report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:",
+  "report.thanks.title": "Don't want to see this?",
+  "report.thanks.title_actionable": "Thanks for reporting, we'll look into this.",
+  "report.unfollow": "Unfollow @{name}",
+  "report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
+  "report_notification.attached_statuses": "{count, plural, one {{count} post} other {{count} posts}} attached",
+  "report_notification.categories.other": "Other",
+  "report_notification.categories.spam": "Spam",
+  "report_notification.categories.violation": "Rule violation",
+  "report_notification.open": "Open report",
+  "search.placeholder": "Search",
+  "search.search_or_paste": "Search or paste URL",
+  "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+  "search_popout.tips.hashtag": "hashtag",
+  "search_popout.tips.status": "status",
+  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
+  "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.all": "All",
+  "search_results.hashtags": "Hashtags",
+  "search_results.nothing_found": "Could not find anything for these search terms",
+  "search_results.statuses": "Posts",
+  "search_results.statuses_fts_disabled": "Searching posts by their content is not enabled on this Mastodon server.",
+  "search_results.title": "Search for {q}",
+  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+  "server_banner.about_active_users": "People using this server during the last 30 days (Monthly Active Users)",
+  "server_banner.active_users": "active users",
+  "server_banner.administered_by": "Administered by:",
+  "server_banner.introduction": "{domain} is part of the decentralized social network powered by {mastodon}.",
+  "server_banner.learn_more": "Learn more",
+  "server_banner.server_stats": "Server stats:",
+  "sign_in_banner.create_account": "Create account",
+  "sign_in_banner.sign_in": "Sign in",
+  "sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
+  "status.admin_account": "Open moderation interface for @{name}",
+  "status.admin_domain": "Open moderation interface for {domain}",
+  "status.admin_status": "Open this status in the moderation interface",
+  "status.block": "Block @{name}",
+  "status.bookmark": "Bookmark",
+  "status.cancel_reblog_private": "Unboost",
+  "status.cannot_reblog": "This post cannot be boosted",
+  "status.copy": "Copy link to status",
+  "status.delete": "Delete",
+  "status.detailed_status": "Detailed conversation view",
+  "status.direct": "Direct message @{name}",
+  "status.edit": "Edit",
+  "status.edited": "Edited {date}",
+  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.embed": "Embed",
+  "status.favourite": "Favourite",
+  "status.filter": "Filter this post",
+  "status.filtered": "Filtered",
+  "status.hide": "Hide post",
+  "status.history.created": "{name} created {date}",
+  "status.history.edited": "{name} edited {date}",
+  "status.load_more": "Load more",
+  "status.media_hidden": "Media hidden",
+  "status.mention": "Mention @{name}",
+  "status.more": "More",
+  "status.mute": "Mute @{name}",
+  "status.mute_conversation": "Mute conversation",
+  "status.open": "Expand this status",
+  "status.pin": "Pin on profile",
+  "status.pinned": "Pinned post",
+  "status.read_more": "Read more",
+  "status.reblog": "Boost",
+  "status.reblog_private": "Boost with original visibility",
+  "status.reblogged_by": "{name} boosted",
+  "status.reblogs.empty": "No one has boosted this post yet. When someone does, they will show up here.",
+  "status.redraft": "Delete & re-draft",
+  "status.remove_bookmark": "Remove bookmark",
+  "status.replied_to": "Replied to {name}",
+  "status.reply": "Reply",
+  "status.replyAll": "Reply to thread",
+  "status.report": "Report @{name}",
+  "status.sensitive_warning": "Sensitive content",
+  "status.share": "Share",
+  "status.show_filter_reason": "Show anyway",
+  "status.show_less": "Show less",
+  "status.show_less_all": "Show less for all",
+  "status.show_more": "Show more",
+  "status.show_more_all": "Show more for all",
+  "status.show_original": "Show original",
+  "status.translate": "Translate",
+  "status.translated_from_with": "Translated from {lang} using {provider}",
+  "status.uncached_media_warning": "Not available",
+  "status.unmute_conversation": "Unmute conversation",
+  "status.unpin": "Unpin from profile",
+  "subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
+  "subscribed_languages.save": "Save changes",
+  "subscribed_languages.target": "Change subscribed languages for {target}",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
+  "tabs_bar.federated_timeline": "Federated",
+  "tabs_bar.home": "Home",
+  "tabs_bar.local_timeline": "Local",
+  "tabs_bar.notifications": "Notifications",
+  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
+  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
+  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
+  "time_remaining.moments": "Moments remaining",
+  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "timeline_hint.remote_resource_not_displayed": "{resource} from other servers are not displayed.",
+  "timeline_hint.resources.followers": "Followers",
+  "timeline_hint.resources.follows": "Follows",
+  "timeline_hint.resources.statuses": "Older posts",
+  "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {{days} days}}",
+  "trends.trending_now": "Trending now",
+  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
+  "units.short.billion": "{count}B",
+  "units.short.million": "{count}M",
+  "units.short.thousand": "{count}K",
+  "upload_area.title": "Drag & drop to upload",
+  "upload_button.label": "Add images, a video or an audio file",
+  "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
+  "upload_form.audio_description": "Describe for people who are hard of hearing",
+  "upload_form.description": "Describe for people who are blind or have low vision",
+  "upload_form.description_missing": "No description added",
+  "upload_form.edit": "Edit",
+  "upload_form.thumbnail": "Change thumbnail",
+  "upload_form.undo": "Delete",
+  "upload_form.video_description": "Describe for people who are deaf, hard of hearing, blind or have low vision",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.applying": "Applying…",
+  "upload_modal.choose_image": "Choose image",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preparing_ocr": "Preparing OCR…",
+  "upload_modal.preview_label": "Preview ({ratio})",
+  "upload_progress.label": "Uploading…",
+  "upload_progress.processing": "Processing…",
+  "video.close": "Close video",
+  "video.download": "Download file",
+  "video.exit_fullscreen": "Exit full screen",
+  "video.expand": "Expand video",
+  "video.fullscreen": "Full screen",
+  "video.hide": "Hide video",
+  "video.mute": "Mute sound",
+  "video.pause": "Pause",
+  "video.play": "Play",
+  "video.unmute": "Unmute sound"
+}
diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json
index 204a54241..407f5dd92 100644
--- a/app/javascript/mastodon/locales/cy.json
+++ b/app/javascript/mastodon/locales/cy.json
@@ -221,7 +221,7 @@
   "empty_column.favourites": "Does neb wedi hoffi'r post hwn eto. Pan bydd rhywun yn ei hoffi, byddent yn ymddangos yma.",
   "empty_column.follow_recommendations": "Does dim awgrymiadau yma i chi. Gallwch geisio chwilio am bobl rydych yn eu hadnabod neu edrych drwy hashnodau sy'n trendio.",
   "empty_column.follow_requests": "Nid oes gennych unrhyw geisiadau dilyn eto. Pan fyddwch yn derbyn un, byddan nhw'n ymddangos yma.",
-  "empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
+  "empty_column.followed_tags": "Nid ydych wedi dilyn unrhyw hashnodau eto. Pan fyddwch chi'n gwneud hynny, byddan nhw'n ymddangos yma.",
   "empty_column.hashtag": "Nid oes dim ar yr hashnod hwn eto.",
   "empty_column.home": "Mae eich ffrwd gartref yn wag! Ymwelwch â {public} neu defnyddiwch y chwilotwr i ddechrau arni ac i gwrdd â defnyddwyr eraill.",
   "empty_column.home.suggestions": "Dyma rai awgrymiadau",
@@ -264,7 +264,7 @@
   "follow_request.authorize": "Awdurdodi",
   "follow_request.reject": "Gwrthod",
   "follow_requests.unlocked_explanation": "Er nid yw eich cyfrif wedi'i gloi, roedd y staff {domain} yn meddwl efallai hoffech adolygu ceisiadau dilyn o'r cyfrifau rhain wrth law.",
-  "followed_tags": "Followed hashtags",
+  "followed_tags": "Hashnodau rydych yn eu dilyn",
   "footer.about": "Ynghylch",
   "footer.directory": "Cyfeiriadur proffiliau",
   "footer.get_app": "Lawrlwytho'r ap",
diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json
index 0e5b589c1..a787a747b 100644
--- a/app/javascript/mastodon/locales/en-GB.json
+++ b/app/javascript/mastodon/locales/en-GB.json
@@ -450,7 +450,7 @@
   "poll.voted": "You voted for this answer",
   "poll.votes": "{votes, plural, one {# vote} other {# votes}}",
   "poll_button.add_poll": "Add a poll",
-  "poll_button.remove_poll": "Remove poll",
+  "poll_button.remove_poll": "Add a poll",
   "privacy.change": "Adjust status privacy",
   "privacy.direct.long": "Visible for mentioned users only",
   "privacy.direct.short": "Direct",
@@ -489,7 +489,7 @@
   "report.close": "Done",
   "report.comment.title": "Is there anything else you think we should know?",
   "report.forward": "Forward to {target}",
-  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.forward_hint": "The account is from another server. Send an anonymised copy of the report there as well?",
   "report.mute": "Mute",
   "report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
   "report.next": "Next",
@@ -538,7 +538,7 @@
   "server_banner.about_active_users": "People using this server during the last 30 days (Monthly Active Users)",
   "server_banner.active_users": "active users",
   "server_banner.administered_by": "Administered by:",
-  "server_banner.introduction": "{domain} is part of the decentralized social network powered by {mastodon}.",
+  "server_banner.introduction": "{domain} is part of the decentralised social network powered by {mastodon}.",
   "server_banner.learn_more": "Learn more",
   "server_banner.server_stats": "Server stats:",
   "sign_in_banner.create_account": "Create account",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index bb8a1a68b..daaab3857 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -24,7 +24,7 @@
   "account.disable_notifications": "Lopeta @{name}:n julkaisuista ilmoittaminen",
   "account.domain_blocked": "Verkko-osoite estetty",
   "account.edit_profile": "Muokkaa profiilia",
-  "account.enable_notifications": "Ilmoita @{name}:n julkaisuista",
+  "account.enable_notifications": "Ilmoita kun käyttäjä @{name} julkaisee viestin",
   "account.endorse": "Suosittele profiilissasi",
   "account.featured_tags.last_status_at": "Viimeisin viesti {date}",
   "account.featured_tags.last_status_never": "Ei viestejä",
@@ -38,7 +38,7 @@
   "account.follows.empty": "Tämä käyttäjä ei vielä seuraa ketään.",
   "account.follows_you": "Seuraa sinua",
   "account.go_to_profile": "Avaa profiili",
-  "account.hide_reblogs": "Piilota käyttäjän @{name} buustaukset",
+  "account.hide_reblogs": "Piilota käyttäjän @{name} tehostukset",
   "account.joined_short": "Liittynyt",
   "account.languages": "Vaihda tilattuja kieliä",
   "account.link_verified_on": "Linkin omistus tarkistettiin {date}",
@@ -56,8 +56,8 @@
   "account.requested": "Odottaa hyväksyntää. Peruuta seuraamispyyntö klikkaamalla",
   "account.requested_follow": "{name} on pyytänyt lupaa seurata sinua",
   "account.share": "Jaa käyttäjän @{name} profiili",
-  "account.show_reblogs": "Näytä buustaukset käyttäjältä @{name}",
-  "account.statuses_counter": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}",
+  "account.show_reblogs": "Näytä tehostukset käyttäjältä @{name}",
+  "account.statuses_counter": "{count, plural, one {{counter} viesti} other {{counter} viestiä}}",
   "account.unblock": "Salli @{name}",
   "account.unblock_domain": "Salli palvelu {domain}",
   "account.unblock_short": "Poista esto",
@@ -155,20 +155,20 @@
   "confirmations.cancel_follow_request.confirm": "Peruuta pyyntö",
   "confirmations.cancel_follow_request.message": "Haluatko varmasti peruuttaa pyyntösi seurata käyttäjää {name}?",
   "confirmations.delete.confirm": "Poista",
-  "confirmations.delete.message": "Haluatko varmasti poistaa tämän julkaisun?",
+  "confirmations.delete.message": "Haluatko varmasti poistaa tämän viestin?",
   "confirmations.delete_list.confirm": "Poista",
   "confirmations.delete_list.message": "Haluatko varmasti poistaa tämän listan kokonaan?",
   "confirmations.discard_edit_media.confirm": "Hylkää",
-  "confirmations.discard_edit_media.message": "Onko sinulla tallentamattomia muutoksia kuvaukseen tai esikatseluun, hylätäänkö ne silti?",
+  "confirmations.discard_edit_media.message": "Sinulla on tallentamattomia muutoksia median kuvaukseen tai esikatseluun, hylätäänkö ne silti?",
   "confirmations.domain_block.confirm": "Estä koko palvelu",
   "confirmations.domain_block.message": "Haluatko aivan varmasti estää palvelun {domain} täysin? Useimmiten muutama kohdistettu esto tai mykistys on riittävä ja suositeltava toimenpide. Et näe kyseisen sisältöä kyseiseltä verkkoalueelta missään julkisissa aikajanoissa tai ilmoituksissa. Tälle verkkoalueelle kuuluvat seuraajasi poistetaan.",
   "confirmations.logout.confirm": "Kirjaudu ulos",
-  "confirmations.logout.message": "Oletko varma, että haluat kirjautua ulos?",
+  "confirmations.logout.message": "Haluatko varmasti kirjautua ulos?",
   "confirmations.mute.confirm": "Mykistä",
   "confirmations.mute.explanation": "Tämä toiminto piilottaa heidän julkaisunsa sinulta – mukaan lukien ne, joissa heidät mainitaan – sallien heidän yhä nähdä julkaisusi ja seurata sinua.",
   "confirmations.mute.message": "Haluatko varmasti mykistää käyttäjän {name}?",
   "confirmations.redraft.confirm": "Poista & palauta muokattavaksi",
-  "confirmations.redraft.message": "Oletko varma että haluat poistaa tämän julkaisun ja tehdä siitä uuden luonnoksen? Suosikit ja buustaukset menetään, alkuperäisen julkaisusi vastaukset jäävät orvoiksi.",
+  "confirmations.redraft.message": "Oletko varma että haluat poistaa tämän julkaisun ja tehdä siitä uuden luonnoksen? Suosikit ja tehostukset menetään, alkuperäisen julkaisusi vastaukset jäävät orvoiksi.",
   "confirmations.reply.confirm": "Vastaa",
   "confirmations.reply.message": "Jos vastaat nyt, vastaus korvaa tällä hetkellä työstämäsi viestin. Oletko varma, että haluat jatkaa?",
   "confirmations.unfollow.confirm": "Lopeta seuraaminen",
@@ -208,7 +208,7 @@
   "emoji_button.search_results": "Hakutulokset",
   "emoji_button.symbols": "Symbolit",
   "emoji_button.travel": "Matkailu ja paikat",
-  "empty_column.account_suspended": "Tilin käyttäminen keskeytetty",
+  "empty_column.account_suspended": "Tilin käyttäminen jäädytetty",
   "empty_column.account_timeline": "Ei viestejä täällä.",
   "empty_column.account_unavailable": "Profiilia ei löydy",
   "empty_column.blocks": "Et ole vielä estänyt käyttäjiä.",
@@ -240,7 +240,7 @@
   "explore.suggested_follows": "Sinulle",
   "explore.title": "Selaa",
   "explore.trending_links": "Uutiset",
-  "explore.trending_statuses": "Julkaisut",
+  "explore.trending_statuses": "Viestit",
   "explore.trending_tags": "Aihetunnisteet",
   "filter_modal.added.context_mismatch_explanation": "Tämä suodatinluokka ei koske asiayhteyttä, jossa olet käyttänyt tätä viestiä. Jos haluat, että viesti suodatetaan myös tässä yhteydessä, sinun on muokattava suodatinta.",
   "filter_modal.added.context_mismatch_title": "Asiayhteys ei täsmää!",
@@ -248,7 +248,7 @@
   "filter_modal.added.expired_title": "Vanhentunut suodatin!",
   "filter_modal.added.review_and_configure": "Voit tarkastella tätä suodatinluokkaa ja määrittää sen tarkemmin siirtymällä {settings_link}.",
   "filter_modal.added.review_and_configure_title": "Suodattimen asetukset",
-  "filter_modal.added.settings_link": "asetukset sivu",
+  "filter_modal.added.settings_link": "asetukset-sivulle",
   "filter_modal.added.short_explanation": "Tämä viesti on lisätty seuraavaan suodatinluokkaan: {title}.",
   "filter_modal.added.title": "Suodatin lisätty!",
   "filter_modal.select_filter.context_mismatch": "ei sovellu tähän asiayhteyteen",
@@ -259,7 +259,7 @@
   "filter_modal.select_filter.title": "Suodata tämä viesti",
   "filter_modal.title.status": "Suodata viesti",
   "follow_recommendations.done": "Valmis",
-  "follow_recommendations.heading": "Seuraa ihmisiä, joilta haluaisit nähdä julkaisuja! Tässä on muutamia ehdotuksia.",
+  "follow_recommendations.heading": "Seuraa ihmisiä, joilta haluat nähdä viestejä! Tässä on muutamia ehdotuksia.",
   "follow_recommendations.lead": "Seuraamiesi julkaisut näkyvät aikajärjestyksessä kotisyötteessä. Älä pelkää seurata vahingossa, voit lopettaa seuraamisen yhtä helposti!",
   "follow_request.authorize": "Valtuuta",
   "follow_request.reject": "Hylkää",
@@ -286,34 +286,34 @@
   "hashtag.follow": "Seuraa aihetunnistetta",
   "hashtag.unfollow": "Lopeta aihetunnisteen seuraaminen",
   "home.column_settings.basic": "Perusasetukset",
-  "home.column_settings.show_reblogs": "Näytä buustaukset",
+  "home.column_settings.show_reblogs": "Näytä tehostukset",
   "home.column_settings.show_replies": "Näytä vastaukset",
   "home.hide_announcements": "Piilota ilmoitukset",
   "home.show_announcements": "Näytä ilmoitukset",
   "interaction_modal.description.favourite": "Kun sinulla on tili Mastodonissa, voit lisätä tämän viestin suosikkeihin ja tallentaa sen myöhempää käyttöä varten.",
-  "interaction_modal.description.follow": "Kun sinulla on tili Mastodonissa, voit seurata {name} saadaksesi hänen viestejä sinun kotisyötteeseen.",
+  "interaction_modal.description.follow": "Kun sinulla on tili Mastodonissa, voit seurata käyttäjää {name} saadaksesi hänen viestit kotisyötteeseesi.",
   "interaction_modal.description.reblog": "Kun sinulla on tili Mastodonissa, voit tehostaa viestiä ja jakaa sen omien seuraajiesi kanssa.",
   "interaction_modal.description.reply": "Kun sinulla on tili Mastodonissa, voit vastata tähän viestiin.",
   "interaction_modal.on_another_server": "Toisella palvelimella",
   "interaction_modal.on_this_server": "Tällä palvelimella",
   "interaction_modal.other_server_instructions": "Kopioi ja liitä tämä URL-osoite käyttämäsi Mastodon-sovelluksen hakukenttään tai Mastodon-palvelimen web-käyttöliittymään.",
   "interaction_modal.preamble": "Koska Mastodon on hajautettu, voit käyttää toisen Mastodon-palvelimen tai yhteensopivan alustan ylläpitämää tiliäsi, jos sinulla ei ole tiliä tällä palvelimella.",
-  "interaction_modal.title.favourite": "Suosikin {name} viesti",
+  "interaction_modal.title.favourite": "Aseta käyttäjän {name} viesti suosikiksi",
   "interaction_modal.title.follow": "Seuraa {name}",
-  "interaction_modal.title.reblog": "Tehosta {name} viestiä",
+  "interaction_modal.title.reblog": "Tehosta käyttäjän {name} viestiä",
   "interaction_modal.title.reply": "Vastaa käyttäjän {name} viestiin",
   "intervals.full.days": "{number, plural, one {# päivä} other {# päivää}}",
   "intervals.full.hours": "{number, plural, one {# tunti} other {# tuntia}}",
   "intervals.full.minutes": "{number, plural, one {# minuutti} other {# minuuttia}}",
   "keyboard_shortcuts.back": "Siirry takaisin",
   "keyboard_shortcuts.blocked": "Avaa estettyjen käyttäjien luettelo",
-  "keyboard_shortcuts.boost": "Buustaa viestiä",
+  "keyboard_shortcuts.boost": "Tehosta viestiä",
   "keyboard_shortcuts.column": "Kohdista sarakkeeseen",
   "keyboard_shortcuts.compose": "siirry tekstinsyöttöön",
   "keyboard_shortcuts.description": "Kuvaus",
-  "keyboard_shortcuts.direct": "avaa yksityisviesti sarake",
+  "keyboard_shortcuts.direct": "avataksesi yksityisviestisarakkeen",
   "keyboard_shortcuts.down": "Siirry listassa alaspäin",
-  "keyboard_shortcuts.enter": "Avaa julkaisu",
+  "keyboard_shortcuts.enter": "Avaa viesti",
   "keyboard_shortcuts.favourite": "Lisää suosikkeihin",
   "keyboard_shortcuts.favourites": "Avaa lista suosikeista",
   "keyboard_shortcuts.federated": "Avaa yleinen aikajana",
@@ -332,8 +332,8 @@
   "keyboard_shortcuts.reply": "Vastaa viestiin",
   "keyboard_shortcuts.requests": "Avaa lista seurauspyynnöistä",
   "keyboard_shortcuts.search": "siirry hakukenttään",
-  "keyboard_shortcuts.spoilers": "näyttääksesi/piilottaaksesi CW kentän",
-  "keyboard_shortcuts.start": "avaa \"Aloitus\" -sarake",
+  "keyboard_shortcuts.spoilers": "Näytä/piilota sisältövaroituskenttä",
+  "keyboard_shortcuts.start": "avaa \"Aloitus\"-sarake",
   "keyboard_shortcuts.toggle_hidden": "näytä/piilota sisältövaroituksella merkitty teksti",
   "keyboard_shortcuts.toggle_sensitivity": "näytä/piilota media",
   "keyboard_shortcuts.toot": "Aloita uusi viesti",
@@ -354,7 +354,7 @@
   "lists.new.create": "Lisää lista",
   "lists.new.title_placeholder": "Uuden listan nimi",
   "lists.replies_policy.followed": "Jokainen seurattu käyttäjä",
-  "lists.replies_policy.list": "Luettelon jäsenet",
+  "lists.replies_policy.list": "Listan jäsenet",
   "lists.replies_policy.none": "Ei kukaan",
   "lists.replies_policy.title": "Näytä vastaukset:",
   "lists.search": "Etsi seuraamistasi henkilöistä",
@@ -394,15 +394,15 @@
   "navigation_bar.security": "Turvallisuus",
   "not_signed_in_indicator.not_signed_in": "Sinun täytyy kirjautua sisään päästäksesi käsiksi tähän resurssiin.",
   "notification.admin.report": "{name} ilmoitti {target}",
-  "notification.admin.sign_up": "{name} rekisteröitynyt",
+  "notification.admin.sign_up": "{name} rekisteröityi",
   "notification.favourite": "{name} tykkäsi viestistäsi",
   "notification.follow": "{name} seurasi sinua",
   "notification.follow_request": "{name} haluaa seurata sinua",
   "notification.mention": "{name} mainitsi sinut",
   "notification.own_poll": "Kyselysi on päättynyt",
   "notification.poll": "Kysely, johon osallistuit, on päättynyt",
-  "notification.reblog": "{name} buustasi julkaisusi",
-  "notification.status": "{name} julkaisi juuri",
+  "notification.reblog": "{name} tehosti viestiäsi",
+  "notification.status": "{name} julkaisi juuri viestin",
   "notification.update": "{name} muokkasi viestiä",
   "notifications.clear": "Tyhjennä ilmoitukset",
   "notifications.clear_confirmation": "Haluatko varmasti poistaa kaikki ilmoitukset pysyvästi?",
@@ -418,15 +418,15 @@
   "notifications.column_settings.mention": "Maininnat:",
   "notifications.column_settings.poll": "Kyselyn tulokset:",
   "notifications.column_settings.push": "Push-ilmoitukset",
-  "notifications.column_settings.reblog": "Buustit:",
+  "notifications.column_settings.reblog": "Tehostukset:",
   "notifications.column_settings.show": "Näytä sarakkeessa",
   "notifications.column_settings.sound": "Äänimerkki",
-  "notifications.column_settings.status": "Uudet julkaisut:",
+  "notifications.column_settings.status": "Uudet viestit:",
   "notifications.column_settings.unread_notifications.category": "Lukemattomat ilmoitukset",
   "notifications.column_settings.unread_notifications.highlight": "Korosta lukemattomat ilmoitukset",
   "notifications.column_settings.update": "Muokkaukset:",
   "notifications.filter.all": "Kaikki",
-  "notifications.filter.boosts": "Buustit",
+  "notifications.filter.boosts": "Tehostukset",
   "notifications.filter.favourites": "Suosikit",
   "notifications.filter.follows": "Seuraa",
   "notifications.filter.mentions": "Maininnat",
@@ -451,10 +451,10 @@
   "poll.votes": "{votes, plural, one {# ääni} other {# ääntä}}",
   "poll_button.add_poll": "Lisää kysely",
   "poll_button.remove_poll": "Poista kysely",
-  "privacy.change": "Muuta julkaisun näkyvyyttä",
-  "privacy.direct.long": "Julkaise vain mainituille käyttäjille",
+  "privacy.change": "Muuta viestin näkyvyyttä",
+  "privacy.direct.long": "Näkyvissä vain mainituille käyttäjille",
   "privacy.direct.short": "Vain mainitut henkilöt",
-  "privacy.private.long": "Julkaise vain seuraajille",
+  "privacy.private.long": "Näkyvissä vain seuraajille",
   "privacy.private.short": "Vain seuraajat",
   "privacy.public.long": "Näkyvissä kaikille",
   "privacy.public.short": "Julkinen",
@@ -481,9 +481,9 @@
   "report.block_explanation": "Et näe heidän viestejään, eivätkä he voi nähdä viestejäsi tai seurata sinua. He näkevät, että heidät on estetty.",
   "report.categories.other": "Muu",
   "report.categories.spam": "Roskaposti",
-  "report.categories.violation": "Sisältö rikkoo yhden tai useamman palvelimen sääntöjä",
-  "report.category.subtitle": "Valitse paras osuma",
-  "report.category.title": "Kerro meille mitä tämän {type} kanssa tapahtuu",
+  "report.categories.violation": "Sisältö rikkoo yhtä tai useampaa palvelimen sääntöä",
+  "report.category.subtitle": "Valitse paras vastaavuus",
+  "report.category.title": "Kerro meille miksi tämä {type} pitää raportoida",
   "report.category.title_account": "profiili",
   "report.category.title_status": "viesti",
   "report.close": "Valmis",
@@ -512,8 +512,8 @@
   "report.thanks.take_action_actionable": "Sillä välin kun tarkistamme tätä, voit ryhtyä toimenpiteisiin käyttäjää @{name} vastaan:",
   "report.thanks.title": "Etkö halua nähdä tätä?",
   "report.thanks.title_actionable": "Kiitos raportista, tutkimme asiaa.",
-  "report.unfollow": "Lopeta seuraaminen @{name}",
-  "report.unfollow_explanation": "Seuraat tätä tiliä. Jotta et enää näkisi heidän kirjoituksiaan, lopeta niiden seuraaminen.",
+  "report.unfollow": "Lopeta käyttäjän @{name} seuraaminen",
+  "report.unfollow_explanation": "Seuraat tätä tiliä. Jotta et enää näe tilin viestejä, lopeta tilin seuraaminen.",
   "report_notification.attached_statuses": "{count, plural, one {{count} viesti} other {{count} viestiä}} liitteenä",
   "report_notification.categories.other": "Muu",
   "report_notification.categories.spam": "Roskaposti",
@@ -522,16 +522,16 @@
   "search.placeholder": "Hae",
   "search.search_or_paste": "Etsi tai kirjoita URL-osoite",
   "search_popout.search_format": "Tarkennettu haku",
-  "search_popout.tips.full_text": "Tekstihaku listaa tilapäivitykset, jotka olet kirjoittanut, lisännyt suosikkeihisi, boostannut tai joissa sinut mainitaan, sekä tekstin sisältävät käyttäjänimet, nimimerkit ja aihetunnisteet.",
+  "search_popout.tips.full_text": "Tekstihaku listaa tilapäivitykset, jotka olet kirjoittanut, lisännyt suosikkeihisi, tehostanut tai joissa sinut mainitaan, sekä tekstin sisältävät käyttäjänimet, nimimerkit ja aihetunnisteet.",
   "search_popout.tips.hashtag": "aihetunnisteet",
-  "search_popout.tips.status": "julkaisu",
+  "search_popout.tips.status": "viesti",
   "search_popout.tips.text": "Tekstihaku listaa hakua vastaavat nimimerkit, käyttäjänimet ja aihetunnisteet",
   "search_popout.tips.user": "käyttäjä",
   "search_results.accounts": "Ihmiset",
   "search_results.all": "Kaikki",
   "search_results.hashtags": "Aihetunnisteet",
   "search_results.nothing_found": "Näille hakusanoille ei löytynyt mitään",
-  "search_results.statuses": "Julkaisut",
+  "search_results.statuses": "Viestit",
   "search_results.statuses_fts_disabled": "Viestien haku sisällön perusteella ei ole käytössä tällä Mastodon-palvelimella.",
   "search_results.title": "Etsi {q}",
   "search_results.total": "{count, number} {count, plural, one {tulos} other {tulosta}}",
@@ -546,12 +546,12 @@
   "sign_in_banner.text": "Kirjaudu sisään seurataksesi profiileja tai aihetunnisteita, lisätäksesi suosikkeihin, jakaaksesi julkaisuja ja vastataksesi niihin. Voit myös olla vuorovaikutuksessa tililtäsi toisella palvelimella.",
   "status.admin_account": "Avaa moderaattorinäkymä tilistä @{name}",
   "status.admin_domain": "Avaa palvelimen {domain} moderointitoiminnot",
-  "status.admin_status": "Avaa julkaisu moderointinäkymässä",
+  "status.admin_status": "Avaa viesti moderointinäkymässä",
   "status.block": "Estä @{name}",
   "status.bookmark": "Tallenna kirjanmerkki",
-  "status.cancel_reblog_private": "Peru buustaus",
-  "status.cannot_reblog": "Tätä viestiä ei voi buustata",
-  "status.copy": "Kopioi linkki julkaisuun",
+  "status.cancel_reblog_private": "Peru tehostus",
+  "status.cannot_reblog": "Tätä viestiä ei voi tehostaa",
+  "status.copy": "Kopioi linkki viestiin",
   "status.delete": "Poista",
   "status.detailed_status": "Yksityiskohtainen keskustelunäkymä",
   "status.direct": "Yksityisviesti käyttäjälle @{name}",
@@ -575,10 +575,10 @@
   "status.pin": "Kiinnitä profiiliin",
   "status.pinned": "Kiinnitetty viesti",
   "status.read_more": "Näytä enemmän",
-  "status.reblog": "Buustaa",
-  "status.reblog_private": "Buustaa alkuperäiselle yleisölle",
-  "status.reblogged_by": "{name} buustasi",
-  "status.reblogs.empty": "Kukaan ei ole vielä buustannut tätä viestiä. Kun joku tekee niin, näkyy kyseinen henkilö tässä.",
+  "status.reblog": "Tehosta",
+  "status.reblog_private": "Tehosta alkuperäiselle yleisölle",
+  "status.reblogged_by": "{name} tehosti",
+  "status.reblogs.empty": "Kukaan ei ole vielä tehostanut tätä viestiä. Kun joku tekee niin, näkyy kyseinen henkilö tässä.",
   "status.redraft": "Poista ja palauta muokattavaksi",
   "status.remove_bookmark": "Poista kirjanmerkki",
   "status.replied_to": "Vastasit käyttäjälle {name}",
@@ -615,7 +615,7 @@
   "timeline_hint.remote_resource_not_displayed": "{resource} muilta palvelimilta ei näytetä.",
   "timeline_hint.resources.followers": "Seuraajia",
   "timeline_hint.resources.follows": "Seurattuja",
-  "timeline_hint.resources.statuses": "Vanhemmat julkaisut",
+  "timeline_hint.resources.statuses": "Vanhemmat viestit",
   "trends.counter_by_accounts": "{count, plural, one {{counter} henkilö} other {{counter} henkilöä}} viimeisten {days, plural, one {päivän} other {{days} päivän}}",
   "trends.trending_now": "Suosittua nyt",
   "ui.beforeunload": "Luonnos häviää, jos poistut Mastodonista.",
@@ -623,7 +623,7 @@
   "units.short.million": "{count} milj.",
   "units.short.thousand": "{count} t.",
   "upload_area.title": "Lataa raahaamalla ja pudottamalla tähän",
-  "upload_button.label": "Lisää mediaa",
+  "upload_button.label": "Lisää kuvia, video tai äänitiedosto",
   "upload_error.limit": "Tiedostolatauksien raja ylitetty.",
   "upload_error.poll": "Tiedon lataaminen ei ole sallittua kyselyissä.",
   "upload_form.audio_description": "Kuvaile sisältöä kuuroille ja kuulorajoitteisille",
@@ -631,7 +631,7 @@
   "upload_form.description_missing": "Kuvausta ei ole lisätty",
   "upload_form.edit": "Muokkaa",
   "upload_form.thumbnail": "Vaihda pikkukuva",
-  "upload_form.undo": "Peru",
+  "upload_form.undo": "Poista",
   "upload_form.video_description": "Kuvaile sisältöä kuuroille, kuulorajoitteisille, sokeille tai näkörajoitteisille",
   "upload_modal.analyzing_picture": "Analysoidaan kuvaa…",
   "upload_modal.apply": "Käytä",
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index 2c1c82707..253dea6f4 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -1,16 +1,16 @@
 {
-  "about.blocks": "Moderated servers",
-  "about.contact": "Contact:",
-  "about.disclaimer": "Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.",
+  "about.blocks": "Մոդերացուող սպասարկիչներ",
+  "about.contact": "Կապ՝",
+  "about.disclaimer": "Մաստոդոնը ազատ, բաց ելակոդով ծրագրակազմ է, յայտնի Mastodon gGmbH ապրանքանշանով։",
   "about.domain_blocks.no_reason_available": "Reason not available",
   "about.domain_blocks.preamble": "Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server.",
   "about.domain_blocks.silenced.explanation": "You will generally not see profiles and content from this server, unless you explicitly look it up or opt into it by following.",
-  "about.domain_blocks.silenced.title": "Limited",
+  "about.domain_blocks.silenced.title": "Սահմանափակ",
   "about.domain_blocks.suspended.explanation": "No data from this server will be processed, stored or exchanged, making any interaction or communication with users from this server impossible.",
-  "about.domain_blocks.suspended.title": "Suspended",
-  "about.not_available": "This information has not been made available on this server.",
-  "about.powered_by": "Decentralized social media powered by {mastodon}",
-  "about.rules": "Server rules",
+  "about.domain_blocks.suspended.title": "Սպասող",
+  "about.not_available": "Այս տեղեկութիւնը տեսանելի չի այս սերուերում։",
+  "about.powered_by": "Ապակենտրոն սոց. ցանց սեղծուած {mastodon}-ի կողմից",
+  "about.rules": "Սերուերի կանոնները",
   "account.account_note_header": "Նշում",
   "account.add_or_remove_from_list": "Աւելացնել կամ հեռացնել ցանկերից",
   "account.badges.bot": "Բոտ",
@@ -26,8 +26,8 @@
   "account.edit_profile": "Խմբագրել անձնական էջը",
   "account.enable_notifications": "Ծանուցել ինձ @{name} գրառումների մասին",
   "account.endorse": "Ցուցադրել անձնական էջում",
-  "account.featured_tags.last_status_at": "Last post on {date}",
-  "account.featured_tags.last_status_never": "No posts",
+  "account.featured_tags.last_status_at": "Վերջին գրառումը եղել է՝ {date}",
+  "account.featured_tags.last_status_never": "Գրառումներ չկան",
   "account.featured_tags.title": "{name}'s featured hashtags",
   "account.follow": "Հետեւել",
   "account.followers": "Հետեւողներ",
@@ -37,9 +37,9 @@
   "account.following_counter": "{count, plural, one {{counter} Հետեւած} other {{counter} Հետեւած}}",
   "account.follows.empty": "Այս օգտատէրը դեռ ոչ մէկի չի հետեւում։",
   "account.follows_you": "Հետեւում է քեզ",
-  "account.go_to_profile": "Go to profile",
+  "account.go_to_profile": "Գնալ անձնական հաշիւ",
   "account.hide_reblogs": "Թաքցնել @{name}֊ի տարածածները",
-  "account.joined_short": "Joined",
+  "account.joined_short": "Միացել է",
   "account.languages": "Change subscribed languages",
   "account.link_verified_on": "Սոյն յղման տիրապետումը ստուգուած է՝ {date}֊ին",
   "account.locked_info": "Սոյն հաշուի գաղտնիութեան մակարդակը նշուած է որպէս՝ փակ։ Հաշուի տէրն ընտրում է, թէ ով կարող է հետեւել իրեն։",
@@ -49,12 +49,12 @@
   "account.mute": "Լռեցնել @{name}֊ին",
   "account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից",
   "account.muted": "Լռեցուած",
-  "account.open_original_page": "Open original page",
+  "account.open_original_page": "Բացել իրական էջը",
   "account.posts": "Գրառումներ",
   "account.posts_with_replies": "Գրառումներ եւ պատասխաններ",
   "account.report": "Բողոքել @{name}֊ի մասին",
   "account.requested": "Հաստատման կարիք ունի։ Սեղմիր՝ հետեւելու հայցը չեղարկելու համար։",
-  "account.requested_follow": "{name} has requested to follow you",
+  "account.requested_follow": "{name}-ը ցանկանում է հետեւել քեզ",
   "account.share": "Կիսուել @{name}֊ի էջով",
   "account.show_reblogs": "Ցուցադրել @{name}֊ի տարածածները",
   "account.statuses_counter": "{count, plural, one {{counter} Գրառում} other {{counter} Գրառումներ}}",
@@ -78,16 +78,16 @@
   "alert.unexpected.title": "Վա՜յ",
   "announcement.announcement": "Յայտարարութիւններ",
   "attachments_list.unprocessed": "(unprocessed)",
-  "audio.hide": "Hide audio",
+  "audio.hide": "Թաքցնել աուդիոն",
   "autosuggest_hashtag.per_week": "շաբաթը՝ {count}",
   "boost_modal.combo": "Կարող ես սեղմել {combo}՝ սա յաջորդ անգամ բաց թողնելու համար",
   "bundle_column_error.copy_stacktrace": "Copy error report",
   "bundle_column_error.error.body": "The requested page could not be rendered. It could be due to a bug in our code, or a browser compatibility issue.",
-  "bundle_column_error.error.title": "Oh, no!",
+  "bundle_column_error.error.title": "Օ՜, ոչ։",
   "bundle_column_error.network.body": "There was an error when trying to load this page. This could be due to a temporary problem with your internet connection or this server.",
-  "bundle_column_error.network.title": "Network error",
+  "bundle_column_error.network.title": "Ցանցի սխալ",
   "bundle_column_error.retry": "Կրկին փորձել",
-  "bundle_column_error.return": "Go back home",
+  "bundle_column_error.return": "Վերադառնալ տուն",
   "bundle_column_error.routing.body": "The requested page could not be found. Are you sure the URL in the address bar is correct?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Փակել",
@@ -95,10 +95,10 @@
   "bundle_modal_error.retry": "Կրկին փորձել",
   "closed_registrations.other_server_instructions": "Since Mastodon is decentralized, you can create an account on another server and still interact with this one.",
   "closed_registrations_modal.description": "Creating an account on {domain} is currently not possible, but please keep in mind that you do not need an account specifically on {domain} to use Mastodon.",
-  "closed_registrations_modal.find_another_server": "Find another server",
+  "closed_registrations_modal.find_another_server": "Գտնել այլ սերուերում",
   "closed_registrations_modal.preamble": "Mastodon is decentralized, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!",
   "closed_registrations_modal.title": "Signing up on Mastodon",
-  "column.about": "About",
+  "column.about": "Մասին",
   "column.blocks": "Արգելափակուած օգտատէրեր",
   "column.bookmarks": "Էջանիշեր",
   "column.community": "Տեղական հոսք",
@@ -124,8 +124,8 @@
   "community.column_settings.local_only": "Միայն տեղական",
   "community.column_settings.media_only": "Միայն մեդիա",
   "community.column_settings.remote_only": "Միայն հեռակայ",
-  "compose.language.change": "Change language",
-  "compose.language.search": "Search languages...",
+  "compose.language.change": "Փոխել լեզուն",
+  "compose.language.search": "Որոնել լեզուներ",
   "compose_form.direct_message_warning_learn_more": "Իմանալ աւելին",
   "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
   "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
@@ -138,8 +138,8 @@
   "compose_form.poll.remove_option": "Հեռացնել այս տարբերակը",
   "compose_form.poll.switch_to_multiple": "Հարցումը դարձնել բազմակի ընտրութեամբ",
   "compose_form.poll.switch_to_single": "Հարցումը դարձնել եզակի ընտրութեամբ",
-  "compose_form.publish": "Publish",
-  "compose_form.publish_form": "Publish",
+  "compose_form.publish": "Հրապարակել",
+  "compose_form.publish_form": "Հրապարակել",
   "compose_form.publish_loud": "Հրապարակե՜լ",
   "compose_form.save_changes": "Պահպանել փոփոխութիւնները",
   "compose_form.sensitive.hide": "Նշել մեդիան որպէս դիւրազգաց",
@@ -177,8 +177,8 @@
   "conversation.mark_as_read": "Նշել որպէս ընթերցուած",
   "conversation.open": "Դիտել խօսակցութիւնը",
   "conversation.with": "{names}-ի հետ",
-  "copypaste.copied": "Copied",
-  "copypaste.copy": "Copy",
+  "copypaste.copied": "Պատճէնուած է",
+  "copypaste.copy": "Պատճէնել",
   "directory.federated": "Յայտնի դաշնեզերքից",
   "directory.local": "{domain} տիրոյթից միայն",
   "directory.new_arrivals": "Նորեկներ",
@@ -194,7 +194,7 @@
   "embed.instructions": "Այս գրառումը քո կայքում ներդնելու համար կարող ես պատճէնել ներքեւի կոդը։",
   "embed.preview": "Ահա, թէ ինչ տեսք կունենայ այն՝",
   "emoji_button.activity": "Զբաղմունքներ",
-  "emoji_button.clear": "Clear",
+  "emoji_button.clear": "Մաքրել",
   "emoji_button.custom": "Յատուկ",
   "emoji_button.flags": "Դրօշներ",
   "emoji_button.food": "Կերուխում",
@@ -237,24 +237,24 @@
   "errors.unexpected_crash.copy_stacktrace": "Պատճենել սթաքթրեյսը սեղմատախտակին",
   "errors.unexpected_crash.report_issue": "Զեկուցել խնդրի մասին",
   "explore.search_results": "Որոնման արդիւնքներ",
-  "explore.suggested_follows": "For you",
+  "explore.suggested_follows": "Քեզ համար",
   "explore.title": "Բացայայտել",
-  "explore.trending_links": "News",
-  "explore.trending_statuses": "Posts",
-  "explore.trending_tags": "Hashtags",
+  "explore.trending_links": "Նորութիւններ",
+  "explore.trending_statuses": "Գրառումներ",
+  "explore.trending_tags": "Պիտակներ",
   "filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
   "filter_modal.added.context_mismatch_title": "Context mismatch!",
   "filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
   "filter_modal.added.expired_title": "Expired filter!",
   "filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
   "filter_modal.added.review_and_configure_title": "Filter settings",
-  "filter_modal.added.settings_link": "settings page",
+  "filter_modal.added.settings_link": "կարգաւորումների էջ",
   "filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
   "filter_modal.added.title": "Filter added!",
   "filter_modal.select_filter.context_mismatch": "does not apply to this context",
   "filter_modal.select_filter.expired": "expired",
   "filter_modal.select_filter.prompt_new": "New category: {name}",
-  "filter_modal.select_filter.search": "Search or create",
+  "filter_modal.select_filter.search": "Որոնել կամ ստեղծել",
   "filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
   "filter_modal.select_filter.title": "Filter this post",
   "filter_modal.title.status": "Filter a post",
@@ -265,11 +265,11 @@
   "follow_request.reject": "Մերժել",
   "follow_requests.unlocked_explanation": "Այս հարցումը ուղարկուած է հաշուից, որի համար {domain}-ի անձնակազմը միացրել է ձեռքով ստուգում։",
   "followed_tags": "Followed hashtags",
-  "footer.about": "About",
+  "footer.about": "Մասին",
   "footer.directory": "Profiles directory",
   "footer.get_app": "Get the app",
-  "footer.invite": "Invite people",
-  "footer.keyboard_shortcuts": "Keyboard shortcuts",
+  "footer.invite": "Հրաւիրել մարդկանց",
+  "footer.keyboard_shortcuts": "Ստեղնաշարի կարճատներ",
   "footer.privacy_policy": "Privacy policy",
   "footer.source_code": "View source code",
   "generic.saved": "Պահպանուած է",
@@ -283,8 +283,8 @@
   "hashtag.column_settings.tag_mode.any": "Ցանկացածը",
   "hashtag.column_settings.tag_mode.none": "Ոչ մեկը",
   "hashtag.column_settings.tag_toggle": "Ներառել լրացուցիչ պիտակները այս սիւնակում ",
-  "hashtag.follow": "Follow hashtag",
-  "hashtag.unfollow": "Unfollow hashtag",
+  "hashtag.follow": "Հետեւել պիտակին",
+  "hashtag.unfollow": "Չհետեւել պիտակին",
   "home.column_settings.basic": "Հիմնական",
   "home.column_settings.show_reblogs": "Ցուցադրել տարածածները",
   "home.column_settings.show_replies": "Ցուցադրել պատասխանները",
@@ -299,9 +299,9 @@
   "interaction_modal.other_server_instructions": "Copy and paste this URL into the search field of your favourite Mastodon app or the web interface of your Mastodon server.",
   "interaction_modal.preamble": "Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one.",
   "interaction_modal.title.favourite": "Favourite {name}'s post",
-  "interaction_modal.title.follow": "Follow {name}",
-  "interaction_modal.title.reblog": "Boost {name}'s post",
-  "interaction_modal.title.reply": "Reply to {name}'s post",
+  "interaction_modal.title.follow": "Հետեւել {name}-ին",
+  "interaction_modal.title.reblog": "Տարածել {name}-ի գրառումը",
+  "interaction_modal.title.reply": "Պատասխանել {name}-ի գրառմանը",
   "intervals.full.days": "{number, plural, one {# օր} other {# օր}}",
   "intervals.full.hours": "{number, plural, one {# ժամ} other {# ժամ}}",
   "intervals.full.minutes": "{number, plural, one {# րոպէ} other {# րոպէ}}",
@@ -368,7 +368,7 @@
   "mute_modal.duration": "Տեւողութիւն",
   "mute_modal.hide_notifications": "Թաքցնե՞լ ծանուցումներն այս օգտատիրոջից։",
   "mute_modal.indefinite": "Անժամկէտ",
-  "navigation_bar.about": "About",
+  "navigation_bar.about": "Մասին",
   "navigation_bar.blocks": "Արգելափակուած օգտատէրեր",
   "navigation_bar.bookmarks": "Էջանիշեր",
   "navigation_bar.community_timeline": "Տեղական հոսք",
@@ -390,7 +390,7 @@
   "navigation_bar.pins": "Ամրացուած գրառումներ",
   "navigation_bar.preferences": "Նախապատուութիւններ",
   "navigation_bar.public_timeline": "Դաշնային հոսք",
-  "navigation_bar.search": "Search",
+  "navigation_bar.search": "Որոնել",
   "navigation_bar.security": "Անվտանգութիւն",
   "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
   "notification.admin.report": "{name} reported {target}",
@@ -512,15 +512,15 @@
   "report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:",
   "report.thanks.title": "Don't want to see this?",
   "report.thanks.title_actionable": "Thanks for reporting, we'll look into this.",
-  "report.unfollow": "Unfollow @{name}",
+  "report.unfollow": "Չհետեւել {name}-ին",
   "report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
   "report_notification.attached_statuses": "{count, plural, one {{count} post} other {{count} posts}} attached",
-  "report_notification.categories.other": "Other",
-  "report_notification.categories.spam": "Spam",
+  "report_notification.categories.other": "Այլ",
+  "report_notification.categories.spam": "Սպամ",
   "report_notification.categories.violation": "Rule violation",
   "report_notification.open": "Open report",
   "search.placeholder": "Փնտրել",
-  "search.search_or_paste": "Search or paste URL",
+  "search.search_or_paste": "Որոնել կամ դնել URL",
   "search_popout.search_format": "Փնտրելու առաջադէմ ձեւ",
   "search_popout.tips.full_text": "Պարզ տեքստը վերադարձնում է գրառումներդ, հաւանածներդ, տարածածներդ, որտեղ ես նշուած եղել, ինչպէս նաեւ նման օգտանուններ, անուններ եւ պիտակներ։",
   "search_popout.tips.hashtag": "պիտակ",
@@ -528,22 +528,22 @@
   "search_popout.tips.text": "Հասարակ տեքստը կը վերադարձնի համընկնող անուններ, օգտանուններ ու պիտակներ",
   "search_popout.tips.user": "օգտատէր",
   "search_results.accounts": "Մարդիկ",
-  "search_results.all": "All",
+  "search_results.all": "Բոլորը",
   "search_results.hashtags": "Պիտակներ",
   "search_results.nothing_found": "Could not find anything for these search terms",
   "search_results.statuses": "Գրառումներ",
   "search_results.statuses_fts_disabled": "Այս հանգոյցում միացուած չէ ըստ բովանդակութեան գրառում փնտրելու հնարաւորութիւնը։",
-  "search_results.title": "Search for {q}",
+  "search_results.title": "Որոնել {q}-ն",
   "search_results.total": "{count, number} {count, plural, one {արդիւնք} other {արդիւնք}}",
   "server_banner.about_active_users": "People using this server during the last 30 days (Monthly Active Users)",
-  "server_banner.active_users": "active users",
-  "server_banner.administered_by": "Administered by:",
+  "server_banner.active_users": "ակտիւ մարդիկ",
+  "server_banner.administered_by": "Կառաւարող",
   "server_banner.introduction": "{domain} is part of the decentralized social network powered by {mastodon}.",
-  "server_banner.learn_more": "Learn more",
-  "server_banner.server_stats": "Server stats:",
-  "sign_in_banner.create_account": "Create account",
-  "sign_in_banner.sign_in": "Sign in",
-  "sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
+  "server_banner.learn_more": "Իմանալ աւելին",
+  "server_banner.server_stats": "Սերուերի վիճակը",
+  "sign_in_banner.create_account": "Ստեղծել հաշիւ",
+  "sign_in_banner.sign_in": "Մուտք",
+  "sign_in_banner.text": "Մտէք, որ կարողանաք հետեւել հաշիւներին կամ պիտակներին, հաւանել, տարածել կամ պատասխանել գրառումներին։ Նաեւ շփուել այլ հանգոյցների հետ։",
   "status.admin_account": "Բացել @{name} օգտատիրոջ մոդերացիայի դիմերէսը։",
   "status.admin_domain": "Open moderation interface for {domain}",
   "status.admin_status": "Բացել այս գրառումը մոդերատորի դիմերէսի մէջ",
@@ -555,16 +555,16 @@
   "status.delete": "Ջնջել",
   "status.detailed_status": "Շղթայի ընդլայնուած դիտում",
   "status.direct": "Նամակ գրել {name} -ին",
-  "status.edit": "Edit",
-  "status.edited": "Edited {date}",
+  "status.edit": "Խմբագրել",
+  "status.edited": "Խմբագրուել է՝ {date}",
   "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
   "status.embed": "Ներդնել",
   "status.favourite": "Հաւանել",
   "status.filter": "Filter this post",
   "status.filtered": "Զտուած",
-  "status.hide": "Hide post",
-  "status.history.created": "{name} created {date}",
-  "status.history.edited": "{name} edited {date}",
+  "status.hide": "Թաքցնել գրառումը",
+  "status.history.created": "{name}-ը ստեղծել է՝ {date}",
+  "status.history.edited": "{name}-ը խմբագրել է՝ {date}",
   "status.load_more": "Բեռնել աւելին",
   "status.media_hidden": "մեդիաբովանդակութիւնը թաքցուած է",
   "status.mention": "Նշել @{name}֊ին",
@@ -581,25 +581,25 @@
   "status.reblogs.empty": "Այս գրառումը ոչ մէկ դեռ չի տարածել։ Տարածողները կերեւան այստեղ, երբ տարածեն։",
   "status.redraft": "Ջնջել եւ վերակազմել",
   "status.remove_bookmark": "Հեռացնել էջանիշերից",
-  "status.replied_to": "Replied to {name}",
+  "status.replied_to": "Պատասխանել է {name}-ին",
   "status.reply": "Պատասխանել",
   "status.replyAll": "Պատասխանել շղթային",
   "status.report": "Բողոքել @{name}֊ից",
   "status.sensitive_warning": "Կասկածելի բովանդակութիւն",
   "status.share": "Կիսուել",
-  "status.show_filter_reason": "Show anyway",
+  "status.show_filter_reason": "Ցոյց տալ բոլոր դէպքերում",
   "status.show_less": "Պակաս",
   "status.show_less_all": "Թաքցնել բոլոր նախազգուշացնումները",
   "status.show_more": "Աւելին",
   "status.show_more_all": "Ցուցադրել բոլոր նախազգուշացնումները",
-  "status.show_original": "Show original",
-  "status.translate": "Translate",
+  "status.show_original": "Ցոյց տալ բնօրինակը",
+  "status.translate": "Թարգմանել",
   "status.translated_from_with": "Translated from {lang} using {provider}",
   "status.uncached_media_warning": "Անհասանելի",
   "status.unmute_conversation": "Ապալռեցնել խօսակցութիւնը",
   "status.unpin": "Հանել անձնական էջից",
   "subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
-  "subscribed_languages.save": "Save changes",
+  "subscribed_languages.save": "Պահպանել փոփոխութիւնները",
   "subscribed_languages.target": "Change subscribed languages for {target}",
   "suggestions.dismiss": "Անտեսել առաջարկը",
   "suggestions.header": "Միգուցէ քեզ հետաքրքրի…",
@@ -644,7 +644,7 @@
   "upload_modal.preparing_ocr": "Գրաճանաչման նախապատրաստում…",
   "upload_modal.preview_label": "Նախադիտում ({ratio})",
   "upload_progress.label": "Վերբեռնվում է…",
-  "upload_progress.processing": "Processing…",
+  "upload_progress.processing": "Մշակուում է...",
   "video.close": "Փակել  տեսագրութիւնը",
   "video.download": "Ներբեռնել նիշքը",
   "video.exit_fullscreen": "Անջատել լիաէկրան դիտումը",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index ac1247503..584ce4c4c 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -1,7 +1,7 @@
 {
   "about.blocks": "Server yang dimoderasi",
   "about.contact": "Hubungi:",
-  "about.disclaimer": "Mastodon addalah perangkat lunak bebas dan sumber terbuka, dan adalah merek dagang dari Mastodon gGmbH.",
+  "about.disclaimer": "Mastodon adalah perangkat lunak bebas dan sumber terbuka, dan adalah merek dagang dari Mastodon gGmbH.",
   "about.domain_blocks.no_reason_available": "Alasan tidak tersedia",
   "about.domain_blocks.preamble": "Mastodon umumnya mengizinkan Anda untuk melihat konten dan berinteraksi dengan pengguna dari server lain di fediverse. Ini adalah pengecualian yang dibuat untuk beberapa server.",
   "about.domain_blocks.silenced.explanation": "Anda secara umum tidak melihat profil dan konten dari server ini, kecuali jika Anda mencarinya atau memilihnya dengan mengikuti secara eksplisit.",
@@ -111,7 +111,7 @@
   "column.lists": "List",
   "column.mutes": "Pengguna yang dibisukan",
   "column.notifications": "Notifikasi",
-  "column.pins": "Pinned toot",
+  "column.pins": "Kiriman tersemat",
   "column.public": "Linimasa gabungan",
   "column_back_button.label": "Kembali",
   "column_header.hide_settings": "Sembunyikan pengaturan",
@@ -126,14 +126,14 @@
   "community.column_settings.remote_only": "Hanya jarak jauh",
   "compose.language.change": "Ganti bahasa",
   "compose.language.search": "Telusuri bahasa...",
-  "compose_form.direct_message_warning_learn_more": "Pelajari selengkapnya",
-  "compose_form.encryption_warning": "Kiriman di Mastodon tidak dienkripsi end-to-end. Jangan bagikan informasi sensitif melalui Mastodon.",
-  "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
+  "compose_form.direct_message_warning_learn_more": "Pelajari lebih lanjut",
+  "compose_form.encryption_warning": "Kiriman di Mastodon tidak dienkripsi secara end-to-end. Jangan bagikan informasi sensitif melalui Mastodon.",
+  "compose_form.hashtag_warning": "Kiriman ini tidak akan didaftarkan di bawah tagar apapun selama tidak diatur ke publik. Hanya kiriman publik yang dapat dicari dengan tagar.",
   "compose_form.lock_disclaimer": "Akun Anda tidak {locked}. Semua orang dapat mengikuti Anda untuk melihat kiriman khusus untuk pengikut Anda.",
   "compose_form.lock_disclaimer.lock": "terkunci",
   "compose_form.placeholder": "Apa yang ada di pikiran Anda?",
   "compose_form.poll.add_option": "Tambahkan pilihan",
-  "compose_form.poll.duration": "Durasi polling",
+  "compose_form.poll.duration": "Durasi japat",
   "compose_form.poll.option_placeholder": "Pilihan {number}",
   "compose_form.poll.remove_option": "Hapus opsi ini",
   "compose_form.poll.switch_to_multiple": "Ubah japat menjadi pilihan ganda",
@@ -145,8 +145,8 @@
   "compose_form.sensitive.hide": "{count, plural, other {Tandai media sebagai sensitif}}",
   "compose_form.sensitive.marked": "{count, plural, other {Media ini ditandai sebagai sensitif}}",
   "compose_form.sensitive.unmarked": "{count, plural, other {Media ini tidak ditandai sebagai sensitif}}",
-  "compose_form.spoiler.marked": "Teks disembunyikan dibalik peringatan",
-  "compose_form.spoiler.unmarked": "Teks tidak tersembunyi",
+  "compose_form.spoiler.marked": "Hapus peringatan tentang isi konten",
+  "compose_form.spoiler.unmarked": "Tambahkan peringatan tentang isi konten",
   "compose_form.spoiler_placeholder": "Peringatan konten",
   "confirmation_modal.cancel": "Batal",
   "confirmations.block.block_and_report": "Blokir & Laporkan",
@@ -221,7 +221,7 @@
   "empty_column.favourites": "Belum ada yang memfavoritkan toot ini. Ketika seseorang melakukannya, mereka akan muncul di sini.",
   "empty_column.follow_recommendations": "Sepertinya tak ada saran yang dibuat untuk Anda. Anda dapat mencoba menggunakan pencarian untuk menemukan orang yang Anda ketahui atau menjelajahi tagar yang sedang tren.",
   "empty_column.follow_requests": "Anda belum memiliki permintaan mengikuti. Ketika Anda menerimanya, maka itu akan muncul di sini.",
-  "empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
+  "empty_column.followed_tags": "Anda belum mengikuti tagar apapun. Saat Anda mulai melakukannya, mereka akan muncul di sini.",
   "empty_column.hashtag": "Tidak ada apa pun dalam hashtag ini.",
   "empty_column.home": "Linimasa anda kosong! Kunjungi {public} atau gunakan pencarian untuk memulai dan bertemu pengguna lain.",
   "empty_column.home.suggestions": "Lihat beberapa saran",
@@ -264,7 +264,7 @@
   "follow_request.authorize": "Izinkan",
   "follow_request.reject": "Tolak",
   "follow_requests.unlocked_explanation": "Meskipun akun Anda tidak dikunci, staf {domain} menyarankan Anda untuk meninjau permintaan mengikuti dari akun-akun ini secara manual.",
-  "followed_tags": "Followed hashtags",
+  "followed_tags": "Tagar yang diikuti",
   "footer.about": "Tentang",
   "footer.directory": "Direktori profil",
   "footer.get_app": "Dapatkan aplikasi",
@@ -381,7 +381,7 @@
   "navigation_bar.favourites": "Favorit",
   "navigation_bar.filters": "Kata yang dibisukan",
   "navigation_bar.follow_requests": "Permintaan mengikuti",
-  "navigation_bar.followed_tags": "Followed hashtags",
+  "navigation_bar.followed_tags": "Tagar yang diikuti",
   "navigation_bar.follows_and_followers": "Ikuti dan pengikut",
   "navigation_bar.lists": "Daftar",
   "navigation_bar.logout": "Keluar",
@@ -543,9 +543,9 @@
   "server_banner.server_stats": "Statistik server:",
   "sign_in_banner.create_account": "Buat akun",
   "sign_in_banner.sign_in": "Masuk",
-  "sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
+  "sign_in_banner.text": "Masuk untuk mengikuti profil atau tagar, memfavoritkan, membagi, dan membalas kiriman. Anda juga dapat berinteraksi dari akun Anda di server yang berbeda.",
   "status.admin_account": "Buka antarmuka moderasi untuk @{name}",
-  "status.admin_domain": "Open moderation interface for {domain}",
+  "status.admin_domain": "Buka antarmuka moderasi untuk {domain}",
   "status.admin_status": "Buka kiriman ini dalam antar muka moderasi",
   "status.block": "Blokir @{name}",
   "status.bookmark": "Markah",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index 56406da8f..41c404c14 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -5,7 +5,7 @@
   "about.domain_blocks.no_reason_available": "Motivo non disponibile",
   "about.domain_blocks.preamble": "Mastodon, generalmente, ti consente di visualizzare i contenuti e interagire con gli utenti da qualsiasi altro server nel fediverso. Queste sono le eccezioni che sono state fatte su questo particolare server.",
   "about.domain_blocks.silenced.explanation": "Generalmente non vedrai i profili e i contenuti di questo server, a meno che tu non lo cerchi esplicitamente o che tu scelga di seguirlo.",
-  "about.domain_blocks.silenced.title": "Silenziato",
+  "about.domain_blocks.silenced.title": "Limitato",
   "about.domain_blocks.suspended.explanation": "Nessun dato proveniente da questo server verrà elaborato, conservato o scambiato, rendendo impossibile qualsiasi interazione o comunicazione con gli utenti da questo server.",
   "about.domain_blocks.suspended.title": "Sospeso",
   "about.not_available": "Queste informazioni non sono state rese disponibili su questo server.",
diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json
index ffc716dd7..47f4f104d 100644
--- a/app/javascript/mastodon/locales/kk.json
+++ b/app/javascript/mastodon/locales/kk.json
@@ -178,7 +178,7 @@
   "conversation.open": "Пікірталасты қарау",
   "conversation.with": "{names} атты",
   "copypaste.copied": "Copied",
-  "copypaste.copy": "Copy",
+  "copypaste.copy": "Көшіру",
   "directory.federated": "Танымал желіден",
   "directory.local": "Тек {domain} доменінен",
   "directory.new_arrivals": "Жаңадан келгендер",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 164353b6d..7332dca16 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -219,7 +219,7 @@
   "empty_column.explore_statuses": "아직 유행하는 것이 없습니다. 나중에 다시 확인하세요!",
   "empty_column.favourited_statuses": "아직 마음에 들어한 게시물이 없습니다. 게시물을 좋아요 하면 여기에 나타납니다.",
   "empty_column.favourites": "아직 아무도 이 게시물을 마음에 들어하지 않았습니다. 누군가 좋아요를 하면 여기에 나타납니다.",
-  "empty_column.follow_recommendations": "나를 위한 추천을 만들 수 없는 것 같습니다. 알 수도 있는 사람을 검색하거나 유행하는 해시태그를 둘러볼 수 있습니다.",
+  "empty_column.follow_recommendations": "제안을 만들 수 없었습니다. 알 수 있는 사람을 찾아보거나 유행하는 해시태그를 둘러보세요.",
   "empty_column.follow_requests": "아직 팔로우 요청이 없습니다. 요청을 받았을 때 여기에 나타납니다.",
   "empty_column.followed_tags": "아직 아무 해시태그도 팔로우하고 있지 않습니다. 해시태그를 팔로우하면, 여기에 표시됩니다.",
   "empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.",
@@ -585,7 +585,7 @@
   "status.reply": "답장",
   "status.replyAll": "글타래에 답장",
   "status.report": "{name} 님을 신고하기",
-  "status.sensitive_warning": "민감한 미디어",
+  "status.sensitive_warning": "민감한 내용",
   "status.share": "공유",
   "status.show_filter_reason": "그냥 표시하기",
   "status.show_less": "숨기기",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index 4e31b9917..5b753cf2a 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -221,7 +221,7 @@
   "empty_column.favourites": "Niemand heeft dit bericht nog als favoriet gemarkeerd. Wanneer iemand dit doet, valt dat hier te zien.",
   "empty_column.follow_recommendations": "Het lijkt er op dat er geen aanbevelingen voor jou aangemaakt kunnen worden. Je kunt proberen te zoeken naar mensen die je wellicht kent, zoeken op hashtags, de lokale en globale tijdlijnen bekijken of de gebruikersgids doorbladeren.",
   "empty_column.follow_requests": "Jij hebt nog enkel volgverzoek ontvangen. Wanneer je er eentje ontvangt, valt dat hier te zien.",
-  "empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
+  "empty_column.followed_tags": "Je hebt nog geen hashtags gevolgd. Wanneer je dit doet, zullen ze hier verschijnen.",
   "empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.",
   "empty_column.home": "Deze tijdlijn is leeg! Volg meer mensen om het te vullen. {suggestions}",
   "empty_column.home.suggestions": "Enkele aanbevelingen bekijken",
@@ -264,7 +264,7 @@
   "follow_request.authorize": "Goedkeuren",
   "follow_request.reject": "Afwijzen",
   "follow_requests.unlocked_explanation": "Ook al is jouw account niet besloten, de medewerkers van {domain} denken dat jij misschien de volgende volgverzoeken handmatig wil controleren.",
-  "followed_tags": "Followed hashtags",
+  "followed_tags": "Gevolgde hashtags",
   "footer.about": "Over",
   "footer.directory": "Gebruikersgids",
   "footer.get_app": "App downloaden",
@@ -381,8 +381,8 @@
   "navigation_bar.favourites": "Favorieten",
   "navigation_bar.filters": "Filters",
   "navigation_bar.follow_requests": "Volgverzoeken",
-  "navigation_bar.followed_tags": "Followed hashtags",
-  "navigation_bar.follows_and_followers": "Volgers en gevolgden",
+  "navigation_bar.followed_tags": "Gevolgde hashtags",
+  "navigation_bar.follows_and_followers": "Volgers en gevolgde accounts",
   "navigation_bar.lists": "Lijsten",
   "navigation_bar.logout": "Uitloggen",
   "navigation_bar.mutes": "Genegeerde gebruikers",
@@ -543,7 +543,7 @@
   "server_banner.server_stats": "Serverstats:",
   "sign_in_banner.create_account": "Registreren",
   "sign_in_banner.sign_in": "Inloggen",
-  "sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
+  "sign_in_banner.text": "Wanneer je een account op deze server hebt, kun je inloggen om mensen of hashtags te volgen, op berichten te reageren of om deze te delen. Wanneer je een account op een andere server hebt, kun je daar inloggen en daar ook interactie met mensen op deze server hebben.",
   "status.admin_account": "Moderatie-omgeving van @{name} openen",
   "status.admin_domain": "Moderatie-omgeving van {domain} openen",
   "status.admin_status": "Dit bericht in de moderatie-omgeving tonen",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index daa28fd4b..a341c4291 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -505,7 +505,7 @@
   "report.rules.subtitle": "Vyberte všetky, ktoré sa vzťahujú",
   "report.rules.title": "Ktoré pravidlá sa porušujú?",
   "report.statuses.subtitle": "Vyberte všetky, ktoré sa vzťahujú",
-  "report.statuses.title": "Are there any posts that back up this report?",
+  "report.statuses.title": "Sú k dispozícii príspevky podporujúce toto hlásenie?",
   "report.submit": "Odošli",
   "report.target": "Nahlás {target}",
   "report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:",
@@ -557,7 +557,7 @@
   "status.direct": "Priama správa pre @{name}",
   "status.edit": "Uprav",
   "status.edited": "Upravené {date}",
-  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.edited_x_times": "Upravený {count, plural, one {{count} krát} other {{count} krát}}",
   "status.embed": "Vložiť",
   "status.favourite": "Páči sa mi",
   "status.filter": "Filtrovanie tohto príspevku",
diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json
index dad5fab60..f1ac38fdf 100644
--- a/app/javascript/mastodon/locales/sq.json
+++ b/app/javascript/mastodon/locales/sq.json
@@ -221,7 +221,7 @@
   "empty_column.favourites": "Askush s’e ka parapëlqyer ende këtë mesazh. Kur e bën dikush, ai do të shfaqet këtu.",
   "empty_column.follow_recommendations": "Duket se s’u prodhuan dot sugjerime për ju. Mund të provoni të kërkoni për persona që mund të njihni, ose të eksploroni hashtag-ë që janë në modë.",
   "empty_column.follow_requests": "Ende s’keni ndonjë kërkesë ndjekjeje. Kur të merrni një të tillë, do të shfaqet këtu.",
-  "empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
+  "empty_column.followed_tags": "S’keni ndjekur ende nodnjë hashtag. Kur të ndiqni të tillë, do të shfaqen këtu.",
   "empty_column.hashtag": "Ende s’ka gjë nën këtë hashtag.",
   "empty_column.home": "Rrjedha juaj kohore është e zbrazët! Vizitoni {public} ose përdorni kërkimin që t’ia filloni dhe të takoni përdorues të tjerë.",
   "empty_column.home.suggestions": "Shihni disa sugjerime",
@@ -264,7 +264,7 @@
   "follow_request.authorize": "Autorizoje",
   "follow_request.reject": "Hidhe tej",
   "follow_requests.unlocked_explanation": "Edhe pse llogaria juaj s’është e kyçur, ekipi i {domain} mendoi se mund të donit të shqyrtonit dorazi kërkesa ndjekjeje prej këtyre llogarive.",
-  "followed_tags": "Followed hashtags",
+  "followed_tags": "Hashtag-ë të ndjekur",
   "footer.about": "Mbi",
   "footer.directory": "Drejtori profilesh",
   "footer.get_app": "Merreni aplikacionin",
@@ -381,7 +381,7 @@
   "navigation_bar.favourites": "Të parapëlqyer",
   "navigation_bar.filters": "Fjalë të heshtuara",
   "navigation_bar.follow_requests": "Kërkesa për ndjekje",
-  "navigation_bar.followed_tags": "Followed hashtags",
+  "navigation_bar.followed_tags": "Hashtag-ë të ndjekur",
   "navigation_bar.follows_and_followers": "Ndjekje dhe ndjekës",
   "navigation_bar.lists": "Lista",
   "navigation_bar.logout": "Dalje",
@@ -543,7 +543,7 @@
   "server_banner.server_stats": "Statistika shërbyesi:",
   "sign_in_banner.create_account": "Krijoni llogari",
   "sign_in_banner.sign_in": "Hyni",
-  "sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
+  "sign_in_banner.text": "Që të ndiqni profile ose hashtagë, t’u vini shenjë si të parapëlqyer, të ndani me të tjerë dhe t’i ripostoni në postime, bëni hyrjen në llogari. Mundeni edhe të ndërveproni që nga llogaria juaj në një shërbyes tjetër.",
   "status.admin_account": "Hap ndërfaqe moderimi për @{name}",
   "status.admin_domain": "Hap ndërfaqe moderimi për {domain}",
   "status.admin_status": "Hape këtë mesazh te ndërfaqja e moderimit",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index c2d634a48..9598c6a19 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -481,7 +481,7 @@
   "report.block_explanation": "คุณจะไม่เห็นโพสต์ของเขา เขาจะไม่สามารถเห็นโพสต์ของคุณหรือติดตามคุณ เขาจะสามารถบอกได้ว่ามีการปิดกั้นเขา",
   "report.categories.other": "อื่น ๆ",
   "report.categories.spam": "สแปม",
-  "report.categories.violation": "เนื้อหาละเมิดหนึ่งกฎของเซิร์ฟเวอร์หรือมากกว่า",
+  "report.categories.violation": "เนื้อหาละเมิดกฎของเซิร์ฟเวอร์จำนวนหนึ่งหรือมากกว่า",
   "report.category.subtitle": "เลือกที่ตรงกันที่สุด",
   "report.category.title": "บอกเราถึงสิ่งที่กำลังเกิดขึ้นกับ {type} นี้",
   "report.category.title_account": "โปรไฟล์",
@@ -509,7 +509,7 @@
   "report.submit": "ส่ง",
   "report.target": "กำลังรายงาน {target}",
   "report.thanks.take_action": "นี่คือตัวเลือกของคุณสำหรับการควบคุมสิ่งที่คุณเห็นใน Mastodon:",
-  "report.thanks.take_action_actionable": "ขณะที่เราตรวจทานสิ่งนี้ คุณสามารถใช้การกระทำกับ @{name}:",
+  "report.thanks.take_action_actionable": "ขณะที่เราตรวจทานสิ่งนี้ คุณสามารถใช้การกระทำต่อ @{name}:",
   "report.thanks.title": "ไม่ต้องการเห็นสิ่งนี้?",
   "report.thanks.title_actionable": "ขอบคุณสำหรับการรายงาน เราจะตรวจสอบสิ่งนี้",
   "report.unfollow": "เลิกติดตาม @{name}",
diff --git a/app/javascript/mastodon/locales/uz.json b/app/javascript/mastodon/locales/uz.json
new file mode 100644
index 000000000..118d8be0c
--- /dev/null
+++ b/app/javascript/mastodon/locales/uz.json
@@ -0,0 +1,658 @@
+{
+  "about.blocks": "Moderatsiya qilingan serverlar",
+  "about.contact": "Ulanish:",
+  "about.disclaimer": "Mastodon bepul, ochiq kodli dastur va Mastodon gGmbH kompaniyasining savdo belgisidir.",
+  "about.domain_blocks.no_reason_available": "Sabab mavjud emas",
+  "about.domain_blocks.preamble": "Mastodon odatda fediversedagi istalgan boshqa serverdagi foydalanuvchilar tarkibini ko'rish va ular bilan muloqot qilish imkonini beradi. Bu alohida serverda qilingan istisnolar.",
+  "about.domain_blocks.silenced.explanation": "Bu serverdagi profillar va kontentni koʻrmaysiz, agar siz uni aniq koʻrib chiqmasangiz yoki unga amal qilish orqali kirishni xohlamasangiz.",
+  "about.domain_blocks.silenced.title": "Cheklangan",
+  "about.domain_blocks.suspended.explanation": "Ushbu serverdan hech qanday ma'lumot qayta ishlanmaydi, saqlanmaydi yoki almashtirilmaydi, bu esa ushbu serverdagi foydalanuvchilar bilan hech qanday o'zaro aloqa yoki aloqani imkonsiz qiladi.",
+  "about.domain_blocks.suspended.title": "To‘xtatildi",
+  "about.not_available": "Ushbu ma'lumot ushbu serverda mavjud emas.",
+  "about.powered_by": "{mastodon} tomonidan boshqariladigan markazlashtirilmagan ijtimoiy media",
+  "about.rules": "Server qoidalari",
+  "account.account_note_header": "Eslatma",
+  "account.add_or_remove_from_list": "Roʻyxatlarga qoʻshish yoki oʻchirish",
+  "account.badges.bot": "Bo't",
+  "account.badges.group": "Guruhlar",
+  "account.block": "Blok @{name}",
+  "account.block_domain": "{domain} domenini bloklash",
+  "account.blocked": "Bloklangan",
+  "account.browse_more_on_origin_server": "Asl profilda ko'proq ko'rish",
+  "account.cancel_follow_request": "Kuzatuv so‘rovini bekor qilish",
+  "account.direct": "To'g'ridan-to'g'ri xabar @{name}",
+  "account.disable_notifications": "@{name} post qo‘yganida menga xabar berishni to‘xtating",
+  "account.domain_blocked": "Domen bloklangan",
+  "account.edit_profile": "Profilni tahrirlash",
+  "account.enable_notifications": "@{name} post qo‘yganida menga xabar olish",
+  "account.endorse": "Profildagi xususiyat",
+  "account.featured_tags.last_status_at": "Oxirgi post: {date}",
+  "account.featured_tags.last_status_never": "Habarlar yo'q",
+  "account.featured_tags.title": "{name} ning taniqli hashtaglari",
+  "account.follow": "Obuna bo‘lish",
+  "account.followers": "Obunachilar",
+  "account.followers.empty": "Bu foydalanuvchini hali hech kim kuzatmaydi.",
+  "account.followers_counter": "{count, plural, one {{counter} Muxlis} other {{counter} Muxlislar}}",
+  "account.following": "Kuzatish",
+  "account.following_counter": "{count, plural, one {{counter} ga Muxlis} other {{counter} larga muxlis}}",
+  "account.follows.empty": "Bu foydalanuvchi hali hech kimni kuzatmagan.",
+  "account.follows_you": "Sizga obuna",
+  "account.go_to_profile": "Profilga o'tish",
+  "account.hide_reblogs": "@{name} dan boostlarni yashirish",
+  "account.joined_short": "Qo'shilgan",
+  "account.languages": "Obuna boʻlgan tillarni oʻzgartirish",
+  "account.link_verified_on": "Bu havolaning egaligi {date} kuni tekshirilgan",
+  "account.locked_info": "Bu hisobning maxfiylik holati qulflangan qilib sozlangan. Egasi ularni kim kuzatishi mumkinligini qo'lda ko'rib chiqadi.",
+  "account.media": "Media",
+  "account.mention": "@{name} ni zikr qiling",
+  "account.moved_to": "{name} oʻzining yangi hisobi endi ekanligini aytdi:",
+  "account.mute": "@{name} ovozini o‘chirish",
+  "account.mute_notifications": "@{name} bildirishnomalarini o‘chirish",
+  "account.muted": "Ovozsiz",
+  "account.open_original_page": "Original post sahifasi",
+  "account.posts": "Postlar",
+  "account.posts_with_replies": "Xabarlar va javoblar",
+  "account.report": "@{name} xabar berish",
+  "account.requested": "Tasdiqlash kutilmoqda. Kuzatuv soʻrovini bekor qilish uchun bosing",
+  "account.requested_follow": "{name} sizni kuzatishni soʻradi",
+  "account.share": "@{name} profilini ulashing",
+  "account.show_reblogs": "@{name} dan bootlarni ko'rsatish",
+  "account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Postlar}}",
+  "account.unblock": "@{name} ni blokdan chiqarish",
+  "account.unblock_domain": "{domain} domenini blokdan chiqarish",
+  "account.unblock_short": "Blokdan chiqarish",
+  "account.unendorse": "Profilda ko'rsatilmasin",
+  "account.unfollow": "Kuzatishni To'xtatish",
+  "account.unmute": "@{name} ovozini yoqish",
+  "account.unmute_notifications": "@{name} bildirishnomalarining ovozini yoqish",
+  "account.unmute_short": "Ovozni yoqish",
+  "account_note.placeholder": "Eslatma qo'shish uchun bosing",
+  "admin.dashboard.daily_retention": "Ro'yxatdan o'tgandan keyingi kun bo'yicha foydalanuvchini ushlab turish darajasi",
+  "admin.dashboard.monthly_retention": "Ro'yxatdan o'tgandan keyin oy bo'yicha foydalanuvchini ushlab turish darajasi",
+  "admin.dashboard.retention.average": "O‘rtacha",
+  "admin.dashboard.retention.cohort": "Ro'yxatdan o'tish oyi",
+  "admin.dashboard.retention.cohort_size": "Yangi foydalanuvchilar",
+  "alert.rate_limited.message": "{retry_time, time, media} keyin qayta urinib koʻring.",
+  "alert.rate_limited.title": "Rate cheklangan",
+  "alert.unexpected.message": "Kutilmagan xatolik yuz berdi.",
+  "alert.unexpected.title": "Voy!",
+  "announcement.announcement": "E'lonlar",
+  "attachments_list.unprocessed": "(qayta ishlanmagan)",
+  "audio.hide": "Audioni yashirish",
+  "autosuggest_hashtag.per_week": "{count} haftasiga",
+  "boost_modal.combo": "Keyingi safar buni oʻtkazib yuborish uchun {combo} tugmasini bosishingiz mumkin",
+  "bundle_column_error.copy_stacktrace": "Xato hisobotini nusxalash",
+  "bundle_column_error.error.body": "Soʻralgan sahifani koʻrsatib boʻlmadi. Buning sababi bizning kodimizdagi xato yoki brauzer mosligi muammosi bo'lishi mumkin.",
+  "bundle_column_error.error.title": "Voy yo'q!",
+  "bundle_column_error.network.body": "Ushbu sahifani yuklashda xatolik yuz berdi. Buning sababi internet ulanishingiz yoki ushbu serverdagi vaqtinchalik muammo bo'lishi mumkin.",
+  "bundle_column_error.network.title": "Tarmoq xatosi",
+  "bundle_column_error.retry": "Qayta urinib ko'rish",
+  "bundle_column_error.return": "Bosh sahifaga qaytish",
+  "bundle_column_error.routing.body": "Soʻralgan sahifani topib boʻlmadi. Manzil satridagi URL to'g'ri ekanligiga ishonchingiz komilmi?",
+  "bundle_column_error.routing.title": "404",
+  "bundle_modal_error.close": "Yopish",
+  "bundle_modal_error.message": "Ushbu mahsulotni qayta belgilashda xatolik yuz berdi.",
+  "bundle_modal_error.retry": "Qayta urinib ko'rish",
+  "closed_registrations.other_server_instructions": "Mastodon markazlashtirilmaganligi sababli, siz boshqa serverda hisob yaratishingiz va u bilan o'zaro aloqada bo'lishingiz mumkin.",
+  "closed_registrations_modal.description": "{domain} da hisob yaratish hozircha imkonsiz, lekin Mastodondan foydalanish uchun maxsus {domain} hisob qaydnomasi kerak emasligini yodda tuting.",
+  "closed_registrations_modal.find_another_server": "Boshqa server topish",
+  "closed_registrations_modal.preamble": "Mastodon markazlashtirilmagan, shuning uchun hisob qaydnomangizni qayerda yaratmasligingizdan qat'iy nazar, siz ushbu serverdagi har kimni kuzatishingiz va ular bilan muloqot qilishingiz mumkin bo'ladi. Siz hatto uni o'zingiz ham joylashtirishingiz mumkin!",
+  "closed_registrations_modal.title": "Mastodonda ro'yxatdan o'tish",
+  "column.about": "Haqida",
+  "column.blocks": "Bloklangan foydalanuvchilar",
+  "column.bookmarks": "Xatcho‘plar",
+  "column.community": "Mahalliy",
+  "column.direct": "To'g'ridan-to'g'ri xabarlar",
+  "column.directory": "Profillarni ko'rish",
+  "column.domain_blocks": "Bloklangan domenlar",
+  "column.favourites": "Sevimlilar",
+  "column.follow_requests": "So'rovlarni kuzatib boring",
+  "column.home": "Bosh sahifa",
+  "column.lists": "Ro‘yxat",
+  "column.mutes": "Ovozsiz foydalanuvchilar",
+  "column.notifications": "Bildirishnomalar",
+  "column.pins": "Belgilangan post",
+  "column.public": "Federatsiyalangan vaqt jadvali",
+  "column_back_button.label": "Orqaga",
+  "column_header.hide_settings": "Sozlamalarni yashirish",
+  "column_header.moveLeft_settings": "Ustunni chapga siljiting",
+  "column_header.moveRight_settings": "Ustunni o'nga siljiting",
+  "column_header.pin": "Yopishtirish",
+  "column_header.show_settings": "Sozlamalarni ko'rsatish",
+  "column_header.unpin": "Olib qo‘yish",
+  "column_subheading.settings": "Sozlamalar",
+  "community.column_settings.local_only": "Faqat mahalliy",
+  "community.column_settings.media_only": "Faqat media",
+  "community.column_settings.remote_only": "Faqat masofaviy",
+  "compose.language.change": "Tilni o‘zgartirish",
+  "compose.language.search": "Tillarni izlash...",
+  "compose_form.direct_message_warning_learn_more": "Batafsil ma’lumot",
+  "compose_form.encryption_warning": "Mastodondagi xabarlar uchdan uchgacha shifrlanmagan. Mastodon orqali hech qanday nozik ma'lumotni baham ko'rmang.",
+  "compose_form.hashtag_warning": "Bu post hech qanday xeshteg ostida ko‘rsatilmaydi, chunki u ochiq emas. Faqat ochiq xabarlarni heshteg orqali qidirish mumkin.",
+  "compose_form.lock_disclaimer": "Hisobingiz {locked}. Faqat obunachilarga moʻljallangan postlaringizni koʻrish uchun har kim sizni kuzatishi mumkin.",
+  "compose_form.lock_disclaimer.lock": "yopilgan",
+  "compose_form.placeholder": "Xalolizda nima?",
+  "compose_form.poll.add_option": "Tanlov qo'shing",
+  "compose_form.poll.duration": "So‘rov muddati",
+  "compose_form.poll.option_placeholder": "Tanlov {number}",
+  "compose_form.poll.remove_option": "Olib tashlash",
+  "compose_form.poll.switch_to_multiple": "Bir nechta tanlovga ruxsat berish uchun so'rovnomani o'zgartirish",
+  "compose_form.poll.switch_to_single": "Yagona tanlovga ruxsat berish uchun so‘rovnomani o‘zgartirish",
+  "compose_form.publish": "Nashr qilish",
+  "compose_form.publish_form": "Nashr qilish",
+  "compose_form.publish_loud": "{publish}!",
+  "compose_form.save_changes": "O‘zgarishlarni saqlash",
+  "compose_form.sensitive.hide": "{count, plural, one {Mediani sezgir deb belgilang} other {Medialarni sezgir deb belgilang}}",
+  "compose_form.sensitive.marked": "{count, plural, one {Mediani sezgir deb belgilang} other {Medialarni sezgir deb belgilang}}",
+  "compose_form.sensitive.unmarked": "{count, plural, one {Mediani sezgir deb belgilang} other {Medialarni sezgir deb belgilang}}",
+  "compose_form.spoiler.marked": "Kontent ogohlantirishini olib tashlang",
+  "compose_form.spoiler.unmarked": "Kontent haqida ogohlantirish qo'shing",
+  "compose_form.spoiler_placeholder": "Sharhingizni bu erga yozing",
+  "confirmation_modal.cancel": "Bekor qilish",
+  "confirmations.block.block_and_report": "Bloklash va hisobot berish",
+  "confirmations.block.confirm": "Bloklash",
+  "confirmations.block.message": "Haqiqatan ham {name}ni bloklamoqchimisiz?",
+  "confirmations.cancel_follow_request.confirm": "Bekor qilish",
+  "confirmations.cancel_follow_request.message": "Haqiqatan ham {name}ga obuna boʻlish soʻrovingizni qaytarib olmoqchimisiz?",
+  "confirmations.delete.confirm": "Oʻchirish",
+  "confirmations.delete.message": "Haqiqatan ham bu postni oʻchirib tashlamoqchimisiz?",
+  "confirmations.delete_list.confirm": "Oʻchirish",
+  "confirmations.delete_list.message": "Haqiqatan ham bu roʻyxatni butunlay oʻchirib tashlamoqchimisiz?",
+  "confirmations.discard_edit_media.confirm": "Bekor qilish",
+  "confirmations.discard_edit_media.message": "Sizda media tavsifi yoki oldindan ko‘rishda saqlanmagan o‘zgarishlar bor, ular baribir bekor qilinsinmi?",
+  "confirmations.domain_block.confirm": "Butun domenni bloklash",
+  "confirmations.domain_block.message": "Haqiqatan ham, {domain} ni butunlay bloklamoqchimisiz? Ko'pgina hollarda bir nechta maqsadli bloklar yoki ovozni o'chirish etarli va afzaldir. Siz oʻsha domendagi kontentni hech qanday umumiy vaqt jadvallarida yoki bildirishnomalaringizda koʻrmaysiz. Bu domendagi obunachilaringiz olib tashlanadi.",
+  "confirmations.logout.confirm": "Chiqish",
+  "confirmations.logout.message": "Chiqishingizga aminmisiz?",
+  "confirmations.mute.confirm": "Ovozsiz",
+  "confirmations.mute.explanation": "Bu ulardagi postlar va ular haqida eslatib o'tilgan postlarni yashiradi, ammo bu ularga sizning postlaringizni ko'rish va sizni kuzatish imkonini beradi.",
+  "confirmations.mute.message": "Haqiqatan ham {name} ovozini o‘chirib qo‘ymoqchimisiz?",
+  "confirmations.redraft.confirm": "O'chirish va qayta loyihalash",
+  "confirmations.redraft.message": "Haqiqatan ham bu postni o‘chirib tashlab, uni qayta loyihalashni xohlaysizmi? Sevimlilar va yuksalishlar yo'qoladi va asl postga javoblar yetim qoladi.",
+  "confirmations.reply.confirm": "Javob berish",
+  "confirmations.reply.message": "Hozir javob bersangiz, hozir yozayotgan xabaringiz ustidan yoziladi. Davom etishni xohlaysizmi?",
+  "confirmations.unfollow.confirm": "Kuzatishni To'xtatish",
+  "confirmations.unfollow.message": "Haqiqatan ham {name} obunasini bekor qilmoqchimisiz?",
+  "conversation.delete": "Suhbatni o'chirish",
+  "conversation.mark_as_read": "O'qilgan deb belgilang",
+  "conversation.open": "Suhbatni ko'rish",
+  "conversation.with": "{names} bilan",
+  "copypaste.copied": "Ko‘chirildi",
+  "copypaste.copy": "Nusxa olish",
+  "directory.federated": "Faqat bilingan fediversdan",
+  "directory.local": "Faqat {domain}dan",
+  "directory.new_arrivals": "Yangi kelganlar",
+  "directory.recently_active": "Yaqinda faol",
+  "disabled_account_banner.account_settings": "Profil sozlamalari",
+  "disabled_account_banner.text": "{disabledAccount} hisobingiz hozirda oʻchirib qoʻyilgan.",
+  "dismissable_banner.community_timeline": "Bular akkauntlari {domain} tomonidan joylashtirilgan odamlarning eng soʻnggi ochiq postlari.",
+  "dismissable_banner.dismiss": "Bekor qilish",
+  "dismissable_banner.explore_links": "Ushbu yangiliklar haqida hozirda markazlashtirilmagan tarmoqning ushbu va boshqa serverlarida odamlar gaplashmoqda.",
+  "dismissable_banner.explore_statuses": "Markazlashtirilmagan tarmoqdagi ushbu va boshqa serverlarning ushbu xabarlari hozirda ushbu serverda qiziqish uyg'otmoqda.",
+  "dismissable_banner.explore_tags": "Ushbu hashtaglar hozirda markazlashtirilmagan tarmoqning ushbu va boshqa serverlarida odamlar orasida qiziqish uyg'otmoqda.",
+  "dismissable_banner.public_timeline": "Bular ushbu va markazlashtirilmagan tarmoqning boshqa serverlaridagi odamlarning ushbu serverga ma'lum bo'lgan eng so'nggi ommaviy xabarlari.",
+  "embed.instructions": "Quyidagi kodni nusxalash orqali ushbu postni veb-saytingizga joylashtiring.",
+  "embed.preview": "Bu qanday ko'rinishda bo'ladi:",
+  "emoji_button.activity": "Faoliyat",
+  "emoji_button.clear": "Tozalash",
+  "emoji_button.custom": "Boshqa",
+  "emoji_button.flags": "Belgilar",
+  "emoji_button.food": "Ovqat va Ichimlik",
+  "emoji_button.label": "Emoji kiritish",
+  "emoji_button.nature": "Tabiat",
+  "emoji_button.not_found": "Mos emoji topilmadi",
+  "emoji_button.objects": "Ob'ektlar",
+  "emoji_button.people": "Odamlar",
+  "emoji_button.recent": "Ko'p ishlatilgan",
+  "emoji_button.search": "Izlash...",
+  "emoji_button.search_results": "Qidiruv natijalari",
+  "emoji_button.symbols": "Belgilar",
+  "emoji_button.travel": "Sayohat va Joylar",
+  "empty_column.account_suspended": "Hisob to‘xtatildi",
+  "empty_column.account_timeline": "Bu yerda post yo'q!",
+  "empty_column.account_unavailable": "Profil mavjud emas",
+  "empty_column.blocks": "Siz hali hech qanday foydalanuvchini bloklamagansiz.",
+  "empty_column.bookmarked_statuses": "Sizda hali xatcho‘p qo‘yilgan postlar yo‘q. Biriga xatcho‘p qo‘ysangiz, u shu yerda ko‘rinadi.",
+  "empty_column.community": "Mahalliy vaqt jadvali boʻsh. To'pni aylantirish uchun hammaga ochiq narsa yozing!",
+  "empty_column.direct": "Sizda hali hech qanday to‘g‘ridan-to‘g‘ri xabar yo‘q. Siz yuborganingizda yoki qabul qilganingizda, u shu yerda ko'rinadi.",
+  "empty_column.domain_blocks": "Hali bloklangan domenlar mavjud emas.",
+  "empty_column.explore_statuses": "Hozir hech narsa trendda emas. Keyinroq tekshiring!",
+  "empty_column.favourited_statuses": "Sizda hali sevimli postlar yoʻq. Sizga yoqqanida, u shu yerda chiqadi.",
+  "empty_column.favourites": "Bu postni hali hech kim yoqtirmagan. Kimdir buni qilsa, ular shu yerda paydo bo'ladi.",
+  "empty_column.follow_recommendations": "Siz uchun hech qanday taklif yaratilmaganga o‘xshaydi. Siz tanish bo'lgan odamlarni qidirish yoki trendli hashtaglarni o'rganish uchun qidiruvdan foydalanib ko'rishingiz mumkin.",
+  "empty_column.follow_requests": "Sizda hali kuzatuv soʻrovlari yoʻq. Bittasini olganingizda, u shu yerda paydo bo'ladi.",
+  "empty_column.followed_tags": "Siz hali hech qanday hashtagga amal qilmagansiz. Qachonki, ular shu yerda paydo bo'ladi.",
+  "empty_column.hashtag": "Ushbu hashtagda hali hech narsa yo'q.",
+  "empty_column.home": "Bosh sahifa yilnomangiz boʻsh! Uni to'ldirish uchun ko'proq odamlarni kuzatib boring. {suggestions}",
+  "empty_column.home.suggestions": "Ba'zi takliflarni ko'ring",
+  "empty_column.list": "Bu ro'yxatda hali hech narsa yo'q. Ushbu roʻyxat aʼzolari yangi xabarlarni nashr qilganda, ular shu yerda paydo boʻladi.",
+  "empty_column.lists": "Sizda hali hech qanday roʻyxat yoʻq. Uni yaratganingizda, u shu yerda paydo bo'ladi.",
+  "empty_column.mutes": "Siz hali hech bir foydalanuvchining ovozini o‘chirmagansiz.",
+  "empty_column.notifications": "Sizda hali hech qanday bildirishnoma yo‘q. Boshqa odamlar siz bilan muloqot qilganda, buni shu yerda ko'rasiz.",
+  "empty_column.public": "Bu erda hech narsa yo'q! Biror narsani ochiq yozing yoki uni toʻldirish uchun boshqa serverlardagi foydalanuvchilarni qoʻlda kuzatib boring",
+  "error.unexpected_crash.explanation": "Kodimizdagi xatolik yoki brauzer mosligi muammosi tufayli bu sahifani toʻgʻri koʻrsatib boʻlmadi.",
+  "error.unexpected_crash.explanation_addons": "Bu sahifani toʻgʻri koʻrsatib boʻlmadi. Bu xato brauzer qoʻshimchasi yoki avtomatik tarjima vositalaridan kelib chiqqan boʻlishi mumkin.",
+  "error.unexpected_crash.next_steps": "Sahifani yangilab ko‘ring. Agar bu yordam bermasa, siz boshqa brauzer yoki mahalliy ilova orqali Mastodondan foydalanishingiz mumkin.",
+  "error.unexpected_crash.next_steps_addons": "Ularni o'chirib ko'ring va sahifani yangilang. Agar bu yordam bermasa, siz boshqa brauzer yoki mahalliy ilova orqali Mastodondan foydalanishingiz mumkin.",
+  "errors.unexpected_crash.copy_stacktrace": "Stacktrace-ni vaqtinchalik xotiraga nusxalash",
+  "errors.unexpected_crash.report_issue": "Muammo haqida xabar berish",
+  "explore.search_results": "Qidiruv natijalari",
+  "explore.suggested_follows": "Siz uchun",
+  "explore.title": "Ko'rib chiqish",
+  "explore.trending_links": "Yangiliklar",
+  "explore.trending_statuses": "Postlar",
+  "explore.trending_tags": "Hashteglar",
+  "filter_modal.added.context_mismatch_explanation": "Ushbu filtr toifasi siz ushbu postga kirgan kontekstga taalluqli emas. Agar siz post ham shu kontekstda filtrlanishini istasangiz, filtrni tahrirlashingiz kerak bo'ladi.",
+  "filter_modal.added.context_mismatch_title": "Kontekst mos kelmadi!",
+  "filter_modal.added.expired_explanation": "Ushbu filtr toifasi muddati tugagan, uni qo‘llash uchun amal qilish muddatini o‘zgartirishingiz kerak bo‘ladi.",
+  "filter_modal.added.expired_title": "Muddati tugagan filtr!",
+  "filter_modal.added.review_and_configure": "Ushbu filtr toifasini koʻrib chiqish va sozlash uchun {settings_link} sahifasiga oʻting.",
+  "filter_modal.added.review_and_configure_title": "Filtr sozlamalari",
+  "filter_modal.added.settings_link": "sozlamalar sahifasi",
+  "filter_modal.added.short_explanation": "Bu post quyidagi filtr turkumiga qo‘shildi: {title}.",
+  "filter_modal.added.title": "Filter qo'shildi!",
+  "filter_modal.select_filter.context_mismatch": "ushbu kontekstga taalluqli emas",
+  "filter_modal.select_filter.expired": "muddati tugagan",
+  "filter_modal.select_filter.prompt_new": "Yangi turkum: {name}",
+  "filter_modal.select_filter.search": "Qidiring yoki yarating",
+  "filter_modal.select_filter.subtitle": "Mavjud toifadan foydalaning yoki yangisini yarating",
+  "filter_modal.select_filter.title": "Ushbu postni filtrlang",
+  "filter_modal.title.status": "Postni filtrlash",
+  "follow_recommendations.done": "Tayyor",
+  "follow_recommendations.heading": "Xabarlarni ko'rishni istagan odamlarni kuzatib boring! Bu erda ba'zi takliflar mavjud.",
+  "follow_recommendations.lead": "Siz kuzatayotgan odamlarning postlari uy tasmangizda xronologik tartibda ko‘rsatiladi. Xato qilishdan qo'rqmang, istalgan vaqtda odamlarni kuzatishdan osongina voz kechishingiz mumkin!",
+  "follow_request.authorize": "Ruxsat berish",
+  "follow_request.reject": "Rad etish",
+  "follow_requests.unlocked_explanation": "Hisobingiz bloklanmagan boʻlsa ham, {domain} xodimlari ushbu hisoblardan kuzatuv soʻrovlarini qoʻlda koʻrib chiqishingiz mumkin deb oʻylashdi.",
+  "followed_tags": "Kuzatilgan hashtaglar",
+  "footer.about": "Haqida",
+  "footer.directory": "Profillar katalogi",
+  "footer.get_app": "Ilovani yuklab oling",
+  "footer.invite": "Odamlarni taklif qilish",
+  "footer.keyboard_shortcuts": "Klaviatura yorliqlari",
+  "footer.privacy_policy": "Maxfiylik siyosati",
+  "footer.source_code": "Kodini ko'rish",
+  "generic.saved": "Saqlandi",
+  "getting_started.heading": "Ishni boshlash",
+  "hashtag.column_header.tag_mode.all": "va {additional}",
+  "hashtag.column_header.tag_mode.any": "yoki {additional}",
+  "hashtag.column_header.tag_mode.none": "{additional}siz",
+  "hashtag.column_settings.select.no_options_message": "Hech qanday taklif topilmadi",
+  "hashtag.column_settings.select.placeholder": "Hashtaglarni kiriting…",
+  "hashtag.column_settings.tag_mode.all": "Bularning hammasi",
+  "hashtag.column_settings.tag_mode.any": "Bularning har biri",
+  "hashtag.column_settings.tag_mode.none": "Bularning hech biri",
+  "hashtag.column_settings.tag_toggle": "Ushbu ustun uchun qo'shimcha teglarni qo'shing",
+  "hashtag.follow": "Hashtagni kuzatish",
+  "hashtag.unfollow": "Hashtagni kuzatishni to'xtatish",
+  "home.column_settings.basic": "Asos",
+  "home.column_settings.show_reblogs": "Boostlarni ko'rish",
+  "home.column_settings.show_replies": "Javoblarni ko'rish",
+  "home.hide_announcements": "E'lonlarni yashirish",
+  "home.show_announcements": "E'lonlarni ko'rsatish",
+  "interaction_modal.description.favourite": "Mastodonda akkaunt bilan siz muallifga buni qadrlayotganingizni bildirish uchun ushbu postni yoqtirishingiz va keyinroq saqlashingiz mumkin.",
+  "interaction_modal.description.follow": "Mastodon’da akkauntga ega bo‘lgan holda, siz {name} ga obuna bo‘lib, ularning postlarini bosh sahifangizga olishingiz mumkin.",
+  "interaction_modal.description.reblog": "Mastodon-dagi akkaunt yordamida siz ushbu postni o'z izdoshlaringiz bilan baham ko'rish uchun oshirishingiz mumkin.",
+  "interaction_modal.description.reply": "Mastodondagi akkaunt bilan siz ushbu xabarga javob berishingiz mumkin.",
+  "interaction_modal.on_another_server": "Boshqa serverda",
+  "interaction_modal.on_this_server": "Shu serverda",
+  "interaction_modal.other_server_instructions": "Ushbu URL manzilidan nusxa ko‘chiring va sevimli Mastodon ilovangizning qidirish maydoniga yoki Mastodon serveringiz veb-interfeysiga joylashtiring.",
+  "interaction_modal.preamble": "Mastodon markazlashtirilmaganligi sababli, boshqa Mastodon serverida joylashgan mavjud hisob qaydnomangizdan yoki bu serverda akkauntingiz bo'lmasa, unga mos platformadan foydalanishingiz mumkin.",
+  "interaction_modal.title.favourite": "{name} posti yoqdi",
+  "interaction_modal.title.follow": "{name} ga ergashing",
+  "interaction_modal.title.reblog": "{name}ning postini boost qilish",
+  "interaction_modal.title.reply": "{name} postiga javob bering",
+  "intervals.full.days": "{number, plural, one {# day} other {# days}}",
+  "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
+  "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
+  "keyboard_shortcuts.back": "Orqaga qaytish",
+  "keyboard_shortcuts.blocked": "to open blocked users list",
+  "keyboard_shortcuts.boost": "Postni boost qilish",
+  "keyboard_shortcuts.column": "Fokus ustuni",
+  "keyboard_shortcuts.compose": "to focus the compose textarea",
+  "keyboard_shortcuts.description": "Tavsif",
+  "keyboard_shortcuts.direct": "to open direct messages column",
+  "keyboard_shortcuts.down": "to move down in the list",
+  "keyboard_shortcuts.enter": "Yozuvni ochish",
+  "keyboard_shortcuts.favourite": "Yozuv yoqdi",
+  "keyboard_shortcuts.favourites": "to open favourites list",
+  "keyboard_shortcuts.federated": "to open federated timeline",
+  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.home": "to open home timeline",
+  "keyboard_shortcuts.hotkey": "Qaynoq klavishlar",
+  "keyboard_shortcuts.legend": "Ushbu afsonani ko'rsatish",
+  "keyboard_shortcuts.local": "to open local timeline",
+  "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.muted": "to open muted users list",
+  "keyboard_shortcuts.my_profile": "Profilingizni ochish",
+  "keyboard_shortcuts.notifications": "to open notifications column",
+  "keyboard_shortcuts.open_media": "Mediani ochish",
+  "keyboard_shortcuts.pinned": "Qattiqlangan postlar roʻyxatini oching",
+  "keyboard_shortcuts.profile": "Muallif profilini ochish",
+  "keyboard_shortcuts.reply": "Postga javob berish",
+  "keyboard_shortcuts.requests": "Kuzatuv soʻrovlari roʻyxatini ochish",
+  "keyboard_shortcuts.search": "Qidiruv paneliga fokus",
+  "keyboard_shortcuts.spoilers": "CW maydonini ko'rsatish/yashirish",
+  "keyboard_shortcuts.start": "\"Boshlash\" ustunini oching",
+  "keyboard_shortcuts.toggle_hidden": "CW orqasida matnni ko'rsatish/yashirish",
+  "keyboard_shortcuts.toggle_sensitivity": "Mediani ko'rsatish/yashirish",
+  "keyboard_shortcuts.toot": "Yangi post boshlang",
+  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
+  "keyboard_shortcuts.up": "Roʻyxatda yuqoriga koʻtarish",
+  "lightbox.close": "Yopish",
+  "lightbox.compress": "Compress image view box",
+  "lightbox.expand": "Expand image view box",
+  "lightbox.next": "Next",
+  "lightbox.previous": "Previous",
+  "limited_account_hint.action": "Baribir profilni ko'rsatish",
+  "limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.",
+  "lists.account.add": "Ro‘yxatga qo‘shish",
+  "lists.account.remove": "Roʻyxatdan o'chirish",
+  "lists.delete": "Roʻyxatni o'chirish",
+  "lists.edit": "Roʻyxatni tahrirlash",
+  "lists.edit.submit": "Change title",
+  "lists.new.create": "Ro‘yxatga qo‘shish",
+  "lists.new.title_placeholder": "New list title",
+  "lists.replies_policy.followed": "Any followed user",
+  "lists.replies_policy.list": "Ro'yxat a'zolari",
+  "lists.replies_policy.none": "Hech kim",
+  "lists.replies_policy.title": "Javoblarni ko'rsatish:",
+  "lists.search": "Siz kuzatadigan odamlar orasidan qidiring",
+  "lists.subheading": "Sizning ro'yxatlaringiz",
+  "load_pending": "{count, plural, one {# yangi element} other {# yangi elementlar}}",
+  "loading_indicator.label": "Yuklanmoqda...",
+  "media_gallery.toggle_visible": "{number, plural, one {Rasmni yashirish} other {Rasmlarni yashirish}}",
+  "missing_indicator.label": "Topilmadi",
+  "missing_indicator.sublabel": "Bu resurs topilmadi",
+  "moved_to_account_banner.text": "{movedToAccount} hisobiga koʻchganingiz uchun {disabledAccount} hisobingiz hozirda oʻchirib qoʻyilgan.",
+  "mute_modal.duration": "Davomiyligi",
+  "mute_modal.hide_notifications": "Bu foydalanuvchidan bildirishnomalar berkitilsinmi?",
+  "mute_modal.indefinite": "Cheksiz",
+  "navigation_bar.about": "Haqida",
+  "navigation_bar.blocks": "Bloklangan foydalanuvchilar",
+  "navigation_bar.bookmarks": "Xatcho‘plar",
+  "navigation_bar.community_timeline": "Mahalliy",
+  "navigation_bar.compose": "Yangi post yozing",
+  "navigation_bar.direct": "To'g'ridan-to'g'ri xabarlar",
+  "navigation_bar.discover": "Kashf qilish",
+  "navigation_bar.domain_blocks": "Bloklangan domenlar",
+  "navigation_bar.edit_profile": "Profilni tahrirlash",
+  "navigation_bar.explore": "O‘rganish",
+  "navigation_bar.favourites": "Sevimlilar",
+  "navigation_bar.filters": "E'tiborga olinmagan so'zlar",
+  "navigation_bar.follow_requests": "Follow requests",
+  "navigation_bar.followed_tags": "Kuzatilgan hashtaglar",
+  "navigation_bar.follows_and_followers": "Kuzatuvchilar va izdoshlar",
+  "navigation_bar.lists": "Ro‘yxat",
+  "navigation_bar.logout": "Chiqish",
+  "navigation_bar.mutes": "Ovozsiz foydalanuvchilar",
+  "navigation_bar.personal": "Shaxsiy",
+  "navigation_bar.pins": "Belgilangan postlar",
+  "navigation_bar.preferences": "Sozlamalar",
+  "navigation_bar.public_timeline": "Federatsiyalangan vaqt jadvali",
+  "navigation_bar.search": "Izlash",
+  "navigation_bar.security": "Xavfsizlik",
+  "not_signed_in_indicator.not_signed_in": "Ushbu manbaga kirish uchun tizimga kirishingiz kerak.",
+  "notification.admin.report": "{name} reported {target}",
+  "notification.admin.sign_up": "{name} signed up",
+  "notification.favourite": "{name} favourited your status",
+  "notification.follow": "{name} followed you",
+  "notification.follow_request": "{name} has requested to follow you",
+  "notification.mention": "{name} mentioned you",
+  "notification.own_poll": "So‘rovingiz tugadi",
+  "notification.poll": "Siz ovoz bergan soʻrovnoma yakunlandi",
+  "notification.reblog": "{name} boosted your status",
+  "notification.status": "{name} just posted",
+  "notification.update": "{name} edited a post",
+  "notifications.clear": "Clear notifications",
+  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+  "notifications.column_settings.admin.report": "New reports:",
+  "notifications.column_settings.admin.sign_up": "New sign-ups:",
+  "notifications.column_settings.alert": "Desktop notifications",
+  "notifications.column_settings.favourite": "Favourites:",
+  "notifications.column_settings.filter_bar.advanced": "Display all categories",
+  "notifications.column_settings.filter_bar.category": "Quick filter bar",
+  "notifications.column_settings.filter_bar.show_bar": "Show filter bar",
+  "notifications.column_settings.follow": "New followers:",
+  "notifications.column_settings.follow_request": "New follow requests:",
+  "notifications.column_settings.mention": "Mentions:",
+  "notifications.column_settings.poll": "Poll results:",
+  "notifications.column_settings.push": "Push notifications",
+  "notifications.column_settings.reblog": "Boosts:",
+  "notifications.column_settings.show": "Show in column",
+  "notifications.column_settings.sound": "Play sound",
+  "notifications.column_settings.status": "New posts:",
+  "notifications.column_settings.unread_notifications.category": "Unread notifications",
+  "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
+  "notifications.column_settings.update": "Edits:",
+  "notifications.filter.all": "All",
+  "notifications.filter.boosts": "Boosts",
+  "notifications.filter.favourites": "Favourites",
+  "notifications.filter.follows": "Follows",
+  "notifications.filter.mentions": "Mentions",
+  "notifications.filter.polls": "Poll results",
+  "notifications.filter.statuses": "Updates from people you follow",
+  "notifications.grant_permission": "Grant permission.",
+  "notifications.group": "{count} notifications",
+  "notifications.mark_as_read": "Mark every notification as read",
+  "notifications.permission_denied": "Desktop notifications are unavailable due to previously denied browser permissions request",
+  "notifications.permission_denied_alert": "Desktop notifications can't be enabled, as browser permission has been denied before",
+  "notifications.permission_required": "Desktop notifications are unavailable because the required permission has not been granted.",
+  "notifications_permission_banner.enable": "Enable desktop notifications",
+  "notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.",
+  "notifications_permission_banner.title": "Never miss a thing",
+  "picture_in_picture.restore": "Put it back",
+  "poll.closed": "Closed",
+  "poll.refresh": "Refresh",
+  "poll.total_people": "{count, plural, one {# person} other {# people}}",
+  "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
+  "poll.vote": "Vote",
+  "poll.voted": "You voted for this answer",
+  "poll.votes": "{votes, plural, one {# vote} other {# votes}}",
+  "poll_button.add_poll": "Add a poll",
+  "poll_button.remove_poll": "Remove poll",
+  "privacy.change": "Adjust status privacy",
+  "privacy.direct.long": "Visible for mentioned users only",
+  "privacy.direct.short": "Direct",
+  "privacy.private.long": "Visible for followers only",
+  "privacy.private.short": "Followers-only",
+  "privacy.public.long": "Visible for all",
+  "privacy.public.short": "Public",
+  "privacy.unlisted.long": "Visible for all, but opted-out of discovery features",
+  "privacy.unlisted.short": "Unlisted",
+  "privacy_policy.last_updated": "Last updated {date}",
+  "privacy_policy.title": "Privacy Policy",
+  "refresh": "Refresh",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
+  "relative_time.days": "{number}d",
+  "relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
+  "relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
+  "relative_time.full.just_now": "just now",
+  "relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
+  "relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
+  "relative_time.hours": "{number}h",
+  "relative_time.just_now": "now",
+  "relative_time.minutes": "{number}m",
+  "relative_time.seconds": "{number}s",
+  "relative_time.today": "today",
+  "reply_indicator.cancel": "Cancel",
+  "report.block": "Block",
+  "report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.",
+  "report.categories.other": "Other",
+  "report.categories.spam": "Spam",
+  "report.categories.violation": "Content violates one or more server rules",
+  "report.category.subtitle": "Choose the best match",
+  "report.category.title": "Tell us what's going on with this {type}",
+  "report.category.title_account": "profile",
+  "report.category.title_status": "post",
+  "report.close": "Done",
+  "report.comment.title": "Is there anything else you think we should know?",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.mute": "Mute",
+  "report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
+  "report.next": "Next",
+  "report.placeholder": "Type or paste additional comments",
+  "report.reasons.dislike": "I don't like it",
+  "report.reasons.dislike_description": "It is not something you want to see",
+  "report.reasons.other": "It's something else",
+  "report.reasons.other_description": "The issue does not fit into other categories",
+  "report.reasons.spam": "It's spam",
+  "report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies",
+  "report.reasons.violation": "It violates server rules",
+  "report.reasons.violation_description": "You are aware that it breaks specific rules",
+  "report.rules.subtitle": "Select all that apply",
+  "report.rules.title": "Which rules are being violated?",
+  "report.statuses.subtitle": "Select all that apply",
+  "report.statuses.title": "Are there any posts that back up this report?",
+  "report.submit": "Submit report",
+  "report.target": "Report {target}",
+  "report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:",
+  "report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:",
+  "report.thanks.title": "Don't want to see this?",
+  "report.thanks.title_actionable": "Thanks for reporting, we'll look into this.",
+  "report.unfollow": "Unfollow @{name}",
+  "report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
+  "report_notification.attached_statuses": "{count, plural, one {{count} post} other {{count} posts}} attached",
+  "report_notification.categories.other": "Other",
+  "report_notification.categories.spam": "Spam",
+  "report_notification.categories.violation": "Rule violation",
+  "report_notification.open": "Open report",
+  "search.placeholder": "Search",
+  "search.search_or_paste": "Search or paste URL",
+  "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+  "search_popout.tips.hashtag": "hashtag",
+  "search_popout.tips.status": "status",
+  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
+  "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.all": "All",
+  "search_results.hashtags": "Hashtags",
+  "search_results.nothing_found": "Could not find anything for these search terms",
+  "search_results.statuses": "Posts",
+  "search_results.statuses_fts_disabled": "Searching posts by their content is not enabled on this Mastodon server.",
+  "search_results.title": "Search for {q}",
+  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+  "server_banner.about_active_users": "People using this server during the last 30 days (Monthly Active Users)",
+  "server_banner.active_users": "active users",
+  "server_banner.administered_by": "Administered by:",
+  "server_banner.introduction": "{domain} is part of the decentralized social network powered by {mastodon}.",
+  "server_banner.learn_more": "Learn more",
+  "server_banner.server_stats": "Server stats:",
+  "sign_in_banner.create_account": "Create account",
+  "sign_in_banner.sign_in": "Sign in",
+  "sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
+  "status.admin_account": "Open moderation interface for @{name}",
+  "status.admin_domain": "Open moderation interface for {domain}",
+  "status.admin_status": "Open this status in the moderation interface",
+  "status.block": "Block @{name}",
+  "status.bookmark": "Bookmark",
+  "status.cancel_reblog_private": "Unboost",
+  "status.cannot_reblog": "This post cannot be boosted",
+  "status.copy": "Copy link to status",
+  "status.delete": "Delete",
+  "status.detailed_status": "Detailed conversation view",
+  "status.direct": "Direct message @{name}",
+  "status.edit": "Edit",
+  "status.edited": "Edited {date}",
+  "status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
+  "status.embed": "Embed",
+  "status.favourite": "Favourite",
+  "status.filter": "Filter this post",
+  "status.filtered": "Filtered",
+  "status.hide": "Hide post",
+  "status.history.created": "{name} created {date}",
+  "status.history.edited": "{name} edited {date}",
+  "status.load_more": "Load more",
+  "status.media_hidden": "Media hidden",
+  "status.mention": "Mention @{name}",
+  "status.more": "More",
+  "status.mute": "Mute @{name}",
+  "status.mute_conversation": "Mute conversation",
+  "status.open": "Expand this status",
+  "status.pin": "Pin on profile",
+  "status.pinned": "Pinned post",
+  "status.read_more": "Read more",
+  "status.reblog": "Boost",
+  "status.reblog_private": "Boost with original visibility",
+  "status.reblogged_by": "{name} boosted",
+  "status.reblogs.empty": "No one has boosted this post yet. When someone does, they will show up here.",
+  "status.redraft": "Delete & re-draft",
+  "status.remove_bookmark": "Remove bookmark",
+  "status.replied_to": "Replied to {name}",
+  "status.reply": "Reply",
+  "status.replyAll": "Reply to thread",
+  "status.report": "Report @{name}",
+  "status.sensitive_warning": "Sensitive content",
+  "status.share": "Share",
+  "status.show_filter_reason": "Show anyway",
+  "status.show_less": "Show less",
+  "status.show_less_all": "Show less for all",
+  "status.show_more": "Show more",
+  "status.show_more_all": "Show more for all",
+  "status.show_original": "Show original",
+  "status.translate": "Translate",
+  "status.translated_from_with": "Translated from {lang} using {provider}",
+  "status.uncached_media_warning": "Not available",
+  "status.unmute_conversation": "Unmute conversation",
+  "status.unpin": "Unpin from profile",
+  "subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
+  "subscribed_languages.save": "Save changes",
+  "subscribed_languages.target": "Change subscribed languages for {target}",
+  "suggestions.dismiss": "Dismiss suggestion",
+  "suggestions.header": "You might be interested in…",
+  "tabs_bar.federated_timeline": "Federated",
+  "tabs_bar.home": "Home",
+  "tabs_bar.local_timeline": "Local",
+  "tabs_bar.notifications": "Notifications",
+  "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
+  "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
+  "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
+  "time_remaining.moments": "Moments remaining",
+  "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
+  "timeline_hint.remote_resource_not_displayed": "{resource} from other servers are not displayed.",
+  "timeline_hint.resources.followers": "Followers",
+  "timeline_hint.resources.follows": "Follows",
+  "timeline_hint.resources.statuses": "Older posts",
+  "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {{days} days}}",
+  "trends.trending_now": "Trending now",
+  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
+  "units.short.billion": "{count}B",
+  "units.short.million": "{count}M",
+  "units.short.thousand": "{count}K",
+  "upload_area.title": "Drag & drop to upload",
+  "upload_button.label": "Add images, a video or an audio file",
+  "upload_error.limit": "File upload limit exceeded.",
+  "upload_error.poll": "File upload not allowed with polls.",
+  "upload_form.audio_description": "Describe for people who are hard of hearing",
+  "upload_form.description": "Describe for people who are blind or have low vision",
+  "upload_form.description_missing": "No description added",
+  "upload_form.edit": "Edit",
+  "upload_form.thumbnail": "Change thumbnail",
+  "upload_form.undo": "Delete",
+  "upload_form.video_description": "Describe for people who are deaf, hard of hearing, blind or have low vision",
+  "upload_modal.analyzing_picture": "Analyzing picture…",
+  "upload_modal.apply": "Apply",
+  "upload_modal.applying": "Applying…",
+  "upload_modal.choose_image": "Choose image",
+  "upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
+  "upload_modal.detect_text": "Detect text from picture",
+  "upload_modal.edit_media": "Edit media",
+  "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+  "upload_modal.preparing_ocr": "Preparing OCR…",
+  "upload_modal.preview_label": "Preview ({ratio})",
+  "upload_progress.label": "Uploading…",
+  "upload_progress.processing": "Processing…",
+  "video.close": "Close video",
+  "video.download": "Download file",
+  "video.exit_fullscreen": "Exit full screen",
+  "video.expand": "Expand video",
+  "video.fullscreen": "Full screen",
+  "video.hide": "Hide video",
+  "video.mute": "Mute sound",
+  "video.pause": "Pause",
+  "video.play": "Play",
+  "video.unmute": "Unmute sound"
+}
diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json
index 663011633..0798d7b26 100644
--- a/app/javascript/mastodon/locales/vi.json
+++ b/app/javascript/mastodon/locales/vi.json
@@ -1,7 +1,7 @@
 {
   "about.blocks": "Giới hạn chung",
   "about.contact": "Liên lạc:",
-  "about.disclaimer": "Mastodon là phần mềm tự do mã nguồn mở, một thương hiệu của Mastodon gGmbH.",
+  "about.disclaimer": "Mastodon là phần mềm tự do nguồn mở của Mastodon gGmbH.",
   "about.domain_blocks.no_reason_available": "Lý do không được cung cấp",
   "about.domain_blocks.preamble": "Mastodon cho phép bạn tương tác nội dung và giao tiếp với mọi người từ bất kỳ máy chủ nào khác trong mạng liên hợp. Còn máy chủ này có những ngoại lệ riêng.",
   "about.domain_blocks.silenced.explanation": "Nói chung, bạn sẽ không thấy người và nội dung từ máy chủ này, trừ khi bạn tự tìm kiếm hoặc tự theo dõi.",
@@ -222,7 +222,7 @@
   "empty_column.follow_recommendations": "Bạn chưa có gợi ý theo dõi nào. Hãy thử tìm kiếm những người thú vị hoặc khám phá những hashtag nổi bật.",
   "empty_column.follow_requests": "Bạn chưa có yêu cầu theo dõi nào.",
   "empty_column.followed_tags": "Bạn chưa theo dõi hashtag nào. Khi bạn theo dõi, chúng sẽ hiện lên ở đây.",
-  "empty_column.hashtag": "Chưa có bài đăng nào dùng hashtag này.",
+  "empty_column.hashtag": "Chưa có tút nào dùng hashtag này.",
   "empty_column.home": "Bảng tin của bạn đang trống! Hãy theo dõi nhiều người hơn. {suggestions}",
   "empty_column.home.suggestions": "Gợi ý dành cho bạn",
   "empty_column.list": "Chưa có tút. Khi những người trong danh sách này đăng tút mới, chúng sẽ xuất hiện ở đây.",
diff --git a/app/javascript/mastodon/locales/whitelist_csb.json b/app/javascript/mastodon/locales/whitelist_csb.json
new file mode 100644
index 000000000..0d4f101c7
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_csb.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/app/javascript/mastodon/locales/whitelist_uz.json b/app/javascript/mastodon/locales/whitelist_uz.json
new file mode 100644
index 000000000..0d4f101c7
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_uz.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index 40858cd71..ea1ae3179 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -9,7 +9,7 @@
   "about.domain_blocks.suspended.explanation": "此服务器的数据将不会被处理、存储或者交换,本站也将无法和来自此服务器的用户互动或者交流。",
   "about.domain_blocks.suspended.title": "已封禁",
   "about.not_available": "此信息在当前服务器尚不可用。",
-  "about.powered_by": "由 {mastodon} 驱动的分布式社交媒体",
+  "about.powered_by": "由 {mastodon} 驱动的去中心化社交媒体",
   "about.rules": "站点规则",
   "account.account_note_header": "备注",
   "account.add_or_remove_from_list": "从列表中添加或移除",
@@ -127,9 +127,9 @@
   "compose.language.change": "更改语言",
   "compose.language.search": "搜索语言...",
   "compose_form.direct_message_warning_learn_more": "了解详情",
-  "compose_form.encryption_warning": "Mastodon 上的嘟文并未端到端加密。请不要在 Mastodon 上分享敏感信息。",
+  "compose_form.encryption_warning": "Mastodon 上的嘟文未经端到端加密。请勿在 Mastodon 上分享敏感信息。",
   "compose_form.hashtag_warning": "这条嘟文被设置为“不公开”,因此它不会出现在任何话题标签的列表下。只有公开的嘟文才能通过话题标签进行搜索。",
-  "compose_form.lock_disclaimer": "你的帐户没有{locked}。任何人都可以在关注你后立即查看仅关注者可见的嘟文。",
+  "compose_form.lock_disclaimer": "你的账户没有{locked}。任何人都可以在关注你后立即查看仅关注者可见的嘟文。",
   "compose_form.lock_disclaimer.lock": "开启保护",
   "compose_form.placeholder": "在想些什么?",
   "compose_form.poll.add_option": "添加一个选项",
@@ -221,7 +221,7 @@
   "empty_column.favourites": "没有人喜欢过这条嘟文。如果有人喜欢了,就会显示在这里。",
   "empty_column.follow_recommendations": "似乎无法为你生成任何建议。你可以尝试使用搜索寻找你可能知道的人或探索热门标签。",
   "empty_column.follow_requests": "你没有收到新的关注请求。收到后将显示在此。",
-  "empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
+  "empty_column.followed_tags": "您还没有关注任何话题标签。 当您关注后,它们会出现在这里。",
   "empty_column.hashtag": "这个话题标签下暂时没有内容。",
   "empty_column.home": "你的主页时间线是空的!快去关注更多人吧。 {suggestions}",
   "empty_column.home.suggestions": "查看一些建议",
@@ -264,7 +264,7 @@
   "follow_request.authorize": "授权",
   "follow_request.reject": "拒绝",
   "follow_requests.unlocked_explanation": "尽管你没有锁嘟,但是 {domain} 的工作人员认为你也许会想手动审核审核这些账号的关注请求。",
-  "followed_tags": "Followed hashtags",
+  "followed_tags": "关注的话题标签",
   "footer.about": "关于",
   "footer.directory": "用户目录",
   "footer.get_app": "获取应用程序",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index 3c3248008..63ee6ec6b 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -160,7 +160,7 @@
   "confirmations.delete_list.message": "您確定要永久刪除此列表?",
   "confirmations.discard_edit_media.confirm": "捨棄",
   "confirmations.discard_edit_media.message": "您在媒體描述或預覽區塊有未儲存的變更。是否要捨棄這些變更?",
-  "confirmations.domain_block.confirm": "隱藏整個域名",
+  "confirmations.domain_block.confirm": "封鎖整個域名",
   "confirmations.domain_block.message": "真的非常確定封鎖整個 {domain} 網域嗎?大部分情況下,您只需要封鎖或靜音少數特定的帳號能滿足需求了。您將不能在任何公開的時間軸及通知中看到來自此網域的內容。您來自該網域的跟隨者也將被移除。",
   "confirmations.logout.confirm": "登出",
   "confirmations.logout.message": "您確定要登出嗎?",
@@ -375,7 +375,7 @@
   "navigation_bar.compose": "撰寫新嘟文",
   "navigation_bar.direct": "私訊",
   "navigation_bar.discover": "探索",
-  "navigation_bar.domain_blocks": "隱藏的網域",
+  "navigation_bar.domain_blocks": "已封鎖的網域",
   "navigation_bar.edit_profile": "編輯個人檔案",
   "navigation_bar.explore": "探索",
   "navigation_bar.favourites": "最愛",
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index 1760c7c89..77faa96a4 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -222,8 +222,8 @@ const privacyPreference = (a, b) => {
 const hydrate = (state, hydratedState) => {
   state = clearAll(state.merge(hydratedState));
 
-  if (hydratedState.has('text')) {
-    state = state.set('text', hydratedState.get('text'));
+  if (hydratedState.get('text')) {
+    state = state.set('text', hydratedState.get('text')).set('focusDate', new Date());
   }
 
   return state;
diff --git a/app/javascript/mastodon/reducers/followed_tags.js b/app/javascript/mastodon/reducers/followed_tags.js
index f50ee6aa3..da20b7b12 100644
--- a/app/javascript/mastodon/reducers/followed_tags.js
+++ b/app/javascript/mastodon/reducers/followed_tags.js
@@ -39,4 +39,4 @@ export default function followed_tags(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/mastodon/service_worker/web_push_notifications.js b/app/javascript/mastodon/service_worker/web_push_notifications.js
index f12595777..b9d626694 100644
--- a/app/javascript/mastodon/service_worker/web_push_notifications.js
+++ b/app/javascript/mastodon/service_worker/web_push_notifications.js
@@ -1,6 +1,6 @@
 import IntlMessageFormat from 'intl-messageformat';
-import locales from './web_push_locales';
 import { unescape } from 'lodash';
+import locales from './web_push_locales';
 
 const MAX_NOTIFICATIONS = 5;
 const GROUP_TAG = 'tag';
@@ -90,7 +90,13 @@ export const handlePush = (event) => {
       options.tag       = notification.id;
       options.badge     = '/badge.png';
       options.image     = notification.status && notification.status.media_attachments.length > 0 && notification.status.media_attachments[0].preview_url || undefined;
-      options.data      = { access_token, preferred_locale, id: notification.status ? notification.status.id : notification.account.id, url: notification.status ? `/@${notification.account.acct}/${notification.status.id}` : `/@${notification.account.acct}` };
+      options.data      = { access_token, preferred_locale, id: notification.status ? notification.status.id : notification.account.id };
+
+      if (notification.status) {
+        options.data.url = `/@${notification.status.account.acct}/${notification.status.id}`;
+      } else {
+        options.data.url = `/@${notification.account.acct}`;
+      }
 
       if (notification.status && notification.status.spoiler_text || notification.status.sensitive) {
         options.data.hiddenBody  = htmlToPlainText(notification.status.content);
diff --git a/app/javascript/packs/public-path.js b/app/javascript/packs/public-path.js
index f96109f4f..f4d166a77 100644
--- a/app/javascript/packs/public-path.js
+++ b/app/javascript/packs/public-path.js
@@ -17,5 +17,5 @@ function formatPublicPath(host = '', path = '') {
 
 const cdnHost = document.querySelector('meta[name=cdn-host]');
 
-// eslint-disable-next-line camelcase, no-undef, no-unused-vars
+// eslint-disable-next-line no-undef
 __webpack_public_path__ = formatPublicPath(cdnHost ? cdnHost.content : '', process.env.PUBLIC_OUTPUT_PATH);
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index 14208e557..be5b68b3f 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -7,7 +7,7 @@ class FeedManager
   include Redisable
 
   # Maximum number of items stored in a single feed
-  MAX_ITEMS = 400
+  MAX_ITEMS = 800
 
   # Number of items in the feed since last reblog of status
   # before the new reblog will be inserted. Must be <= MAX_ITEMS
diff --git a/app/serializers/rest/admin/account_serializer.rb b/app/serializers/rest/admin/account_serializer.rb
index ad98a53e8..959884c55 100644
--- a/app/serializers/rest/admin/account_serializer.rb
+++ b/app/serializers/rest/admin/account_serializer.rb
@@ -2,7 +2,7 @@
 
 class REST::Admin::AccountSerializer < ActiveModel::Serializer
   attributes :id, :username, :domain, :created_at,
-             :email, :ip, :role, :confirmed, :suspended,
+             :email, :ip, :confirmed, :suspended,
              :silenced, :sensitized, :disabled, :approved, :locale,
              :invite_request
 
@@ -11,6 +11,7 @@ class REST::Admin::AccountSerializer < ActiveModel::Serializer
 
   has_many :ips, serializer: REST::Admin::IpSerializer
   has_one :account, serializer: REST::AccountSerializer
+  has_one :role, serializer: REST::RoleSerializer
 
   def id
     object.id.to_s
diff --git a/app/views/statuses/_og_image.html.haml b/app/views/statuses/_og_image.html.haml
index 5a647531a..39f390fdf 100644
--- a/app/views/statuses/_og_image.html.haml
+++ b/app/views/statuses/_og_image.html.haml
@@ -45,7 +45,4 @@
   - else
     = opengraph 'twitter:card', 'summary_large_image'
 - else
-  = opengraph 'og:image', full_asset_url(account.avatar.url(:original))
-  = opengraph 'og:image:width', '400'
-  = opengraph 'og:image:height','400'
   = opengraph 'twitter:card', 'summary'