about summary refs log tree commit diff
diff options
context:
space:
mode:
authorClaire <claire.github-309c@sitedethib.com>2023-02-04 23:18:14 +0100
committerGitHub <noreply@github.com>2023-02-04 23:18:14 +0100
commit76b4e7727b7497c1b68e06133831701f8950ae19 (patch)
treedee110c9c6afd598b202e73283d5c8f8ae6d3999
parentec26f7c1b16ca1429991212292e35e520c617485 (diff)
parentfa433ac5a638b00f5bf77ee52955696d7aa842d6 (diff)
Merge pull request #2101 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes
-rw-r--r--.eslintrc.js87
-rw-r--r--.github/workflows/build-image.yml8
-rw-r--r--.github/workflows/lint-css.yml48
-rw-r--r--.github/workflows/lint-js.yml40
-rw-r--r--.github/workflows/lint-json.yml2
-rw-r--r--.github/workflows/lint-ruby.yml41
-rw-r--r--.github/workflows/lint-yml.yml2
-rw-r--r--.github/workflows/linter.yml83
-rw-r--r--CHANGELOG.md13
-rw-r--r--Gemfile8
-rw-r--r--Gemfile.lock166
-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/core/embed.js2
-rw-r--r--app/javascript/core/settings.js4
-rw-r--r--app/javascript/flavours/glitch/actions/account_notes.js14
-rw-r--r--app/javascript/flavours/glitch/actions/accounts.js166
-rw-r--r--app/javascript/flavours/glitch/actions/alerts.js6
-rw-r--r--app/javascript/flavours/glitch/actions/blocks.js16
-rw-r--r--app/javascript/flavours/glitch/actions/bookmarks.js16
-rw-r--r--app/javascript/flavours/glitch/actions/boosts.js2
-rw-r--r--app/javascript/flavours/glitch/actions/columns.js6
-rw-r--r--app/javascript/flavours/glitch/actions/compose.js92
-rw-r--r--app/javascript/flavours/glitch/actions/custom_emojis.js8
-rw-r--r--app/javascript/flavours/glitch/actions/domain_blocks.js32
-rw-r--r--app/javascript/flavours/glitch/actions/emojis.js2
-rw-r--r--app/javascript/flavours/glitch/actions/favourites.js16
-rw-r--r--app/javascript/flavours/glitch/actions/height_cache.js4
-rw-r--r--app/javascript/flavours/glitch/actions/interactions.js80
-rw-r--r--app/javascript/flavours/glitch/actions/local_settings.js10
-rw-r--r--app/javascript/flavours/glitch/actions/markers.js10
-rw-r--r--app/javascript/flavours/glitch/actions/modal.js4
-rw-r--r--app/javascript/flavours/glitch/actions/mutes.js16
-rw-r--r--app/javascript/flavours/glitch/actions/notifications.js40
-rw-r--r--app/javascript/flavours/glitch/actions/onboarding.js2
-rw-r--r--app/javascript/flavours/glitch/actions/pin_statuses.js8
-rw-r--r--app/javascript/flavours/glitch/actions/search.js12
-rw-r--r--app/javascript/flavours/glitch/actions/settings.js4
-rw-r--r--app/javascript/flavours/glitch/actions/statuses.js48
-rw-r--r--app/javascript/flavours/glitch/actions/store.js4
-rw-r--r--app/javascript/flavours/glitch/actions/suggestions.js8
-rw-r--r--app/javascript/flavours/glitch/actions/tags.js14
-rw-r--r--app/javascript/flavours/glitch/actions/timelines.js20
-rw-r--r--app/javascript/flavours/glitch/compare_id.js2
-rw-r--r--app/javascript/flavours/glitch/components/account.js12
-rw-r--r--app/javascript/flavours/glitch/components/admin/Retention.js2
-rw-r--r--app/javascript/flavours/glitch/components/animated_number.js4
-rw-r--r--app/javascript/flavours/glitch/components/autosuggest_input.js14
-rw-r--r--app/javascript/flavours/glitch/components/autosuggest_textarea.js16
-rw-r--r--app/javascript/flavours/glitch/components/avatar.js4
-rw-r--r--app/javascript/flavours/glitch/components/button.js4
-rw-r--r--app/javascript/flavours/glitch/components/column.js4
-rw-r--r--app/javascript/flavours/glitch/components/column_back_button.js2
-rw-r--r--app/javascript/flavours/glitch/components/column_back_button_slim.js2
-rw-r--r--app/javascript/flavours/glitch/components/column_header.js16
-rw-r--r--app/javascript/flavours/glitch/components/dismissable_banner.js2
-rw-r--r--app/javascript/flavours/glitch/components/display_name.js6
-rw-r--r--app/javascript/flavours/glitch/components/domain.js2
-rw-r--r--app/javascript/flavours/glitch/components/dropdown_menu.js34
-rw-r--r--app/javascript/flavours/glitch/components/edited_timestamp/index.js4
-rw-r--r--app/javascript/flavours/glitch/components/error_boundary.js2
-rw-r--r--app/javascript/flavours/glitch/components/gifv.js4
-rw-r--r--app/javascript/flavours/glitch/components/icon_button.js10
-rw-r--r--app/javascript/flavours/glitch/components/intersection_observer_article.js17
-rw-r--r--app/javascript/flavours/glitch/components/load_gap.js2
-rw-r--r--app/javascript/flavours/glitch/components/load_more.js4
-rw-r--r--app/javascript/flavours/glitch/components/load_pending.js2
-rw-r--r--app/javascript/flavours/glitch/components/media_attachments.js6
-rw-r--r--app/javascript/flavours/glitch/components/media_gallery.js18
-rw-r--r--app/javascript/flavours/glitch/components/modal_root.js9
-rw-r--r--app/javascript/flavours/glitch/components/permalink.js4
-rw-r--r--app/javascript/flavours/glitch/components/picture_in_picture_placeholder.js4
-rw-r--r--app/javascript/flavours/glitch/components/poll.js4
-rw-r--r--app/javascript/flavours/glitch/components/scrollable_list.js22
-rw-r--r--app/javascript/flavours/glitch/components/setting_text.js2
-rw-r--r--app/javascript/flavours/glitch/components/spoilers.js30
-rw-r--r--app/javascript/flavours/glitch/components/status.js64
-rw-r--r--app/javascript/flavours/glitch/components/status_action_bar.js44
-rw-r--r--app/javascript/flavours/glitch/components/status_content.js26
-rw-r--r--app/javascript/flavours/glitch/components/status_header.js4
-rw-r--r--app/javascript/flavours/glitch/components/status_icons.js22
-rw-r--r--app/javascript/flavours/glitch/components/status_list.js12
-rw-r--r--app/javascript/flavours/glitch/components/status_prepend.js6
-rw-r--r--app/javascript/flavours/glitch/containers/media_container.js8
-rw-r--r--app/javascript/flavours/glitch/extra_polyfills.js1
-rw-r--r--app/javascript/flavours/glitch/features/about/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/account/components/account_note.js2
-rw-r--r--app/javascript/flavours/glitch/features/account/components/action_bar.js4
-rw-r--r--app/javascript/flavours/glitch/features/account/components/header.js15
-rw-r--r--app/javascript/flavours/glitch/features/account_gallery/components/media_item.js8
-rw-r--r--app/javascript/flavours/glitch/features/account_gallery/index.js16
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/components/header.js32
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/components/limited_account_hint.js2
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js4
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js2
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/index.js6
-rw-r--r--app/javascript/flavours/glitch/features/audio/index.js46
-rw-r--r--app/javascript/flavours/glitch/features/bookmarked_statuses/index.js10
-rw-r--r--app/javascript/flavours/glitch/features/closed_registrations_modal/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js2
-rw-r--r--app/javascript/flavours/glitch/features/community_timeline/index.js10
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/action_bar.js2
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/compose_form.js44
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/dropdown.js22
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js14
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js38
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/header.js12
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/language_dropdown.js30
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/options.js6
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/poll_form.js15
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/publisher.js3
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/reply_indicator.js2
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/search.js14
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/search_results.js5
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/textarea_icons.js3
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload.js6
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload_form.js1
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js34
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/options_container.js2
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js1
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/warning_container.js4
-rw-r--r--app/javascript/flavours/glitch/features/compose/index.js1
-rw-r--r--app/javascript/flavours/glitch/features/compose/util/counter.js2
-rw-r--r--app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js24
-rw-r--r--app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.js10
-rw-r--r--app/javascript/flavours/glitch/features/direct_timeline/index.js12
-rw-r--r--app/javascript/flavours/glitch/features/directory/components/account_card.js10
-rw-r--r--app/javascript/flavours/glitch/features/directory/index.js14
-rw-r--r--app/javascript/flavours/glitch/features/emoji/emoji_utils.js10
-rw-r--r--app/javascript/flavours/glitch/features/explore/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/explore/statuses.js2
-rw-r--r--app/javascript/flavours/glitch/features/explore/suggestions.js2
-rw-r--r--app/javascript/flavours/glitch/features/favourited_statuses/index.js10
-rw-r--r--app/javascript/flavours/glitch/features/favourites/index.js6
-rw-r--r--app/javascript/flavours/glitch/features/filters/select_filter.js14
-rw-r--r--app/javascript/flavours/glitch/features/follow_recommendations/components/account.js2
-rw-r--r--app/javascript/flavours/glitch/features/follow_recommendations/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/followed_tags/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/followers/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/following/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/components/announcements.js26
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/index.js6
-rw-r--r--app/javascript/flavours/glitch/features/getting_started_misc/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js4
-rw-r--r--app/javascript/flavours/glitch/features/hashtag_timeline/index.js16
-rw-r--r--app/javascript/flavours/glitch/features/home_timeline/index.js12
-rw-r--r--app/javascript/flavours/glitch/features/interaction_modal/index.js8
-rw-r--r--app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.js6
-rw-r--r--app/javascript/flavours/glitch/features/list_editor/components/search.js6
-rw-r--r--app/javascript/flavours/glitch/features/list_timeline/index.js18
-rw-r--r--app/javascript/flavours/glitch/features/lists/components/new_list_form.js6
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/navigation/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/index.js12
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/item/index.js6
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/admin_report.js10
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/admin_signup.js10
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/column_settings.js2
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/follow.js10
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/follow_request.js10
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.js4
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/overlay.js2
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/pill_bar_button.js6
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js4
-rw-r--r--app/javascript/flavours/glitch/features/notifications/index.js18
-rw-r--r--app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js4
-rw-r--r--app/javascript/flavours/glitch/features/picture_in_picture/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/pinned_accounts_editor/containers/search_container.js2
-rw-r--r--app/javascript/flavours/glitch/features/pinned_statuses/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js2
-rw-r--r--app/javascript/flavours/glitch/features/public_timeline/index.js10
-rw-r--r--app/javascript/flavours/glitch/features/reblogs/index.js6
-rw-r--r--app/javascript/flavours/glitch/features/report/components/option.js4
-rw-r--r--app/javascript/flavours/glitch/features/standalone/compose/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/status/components/action_bar.js34
-rw-r--r--app/javascript/flavours/glitch/features/status/components/card.js8
-rw-r--r--app/javascript/flavours/glitch/features/status/components/detailed_status.js18
-rw-r--r--app/javascript/flavours/glitch/features/status/index.js80
-rw-r--r--app/javascript/flavours/glitch/features/subscribed_languages_modal/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/actions_modal.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/block_modal.js8
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/boost_modal.js12
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/bundle.js10
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js8
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.js4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/column.js6
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/column_header.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/column_link.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/columns_area.js12
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/compose_panel.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js10
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.js4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/doodle_modal.js6
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/embed_modal.js6
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/favourite_modal.js8
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js36
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/image_loader.js8
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/link_footer.js4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/media_modal.js12
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/modal_root.js14
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/mute_modal.js10
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js18
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/report_modal.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/upload_area.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/zoomable_image.js28
-rw-r--r--app/javascript/flavours/glitch/features/ui/index.js70
-rw-r--r--app/javascript/flavours/glitch/features/ui/util/react_router_helpers.js10
-rw-r--r--app/javascript/flavours/glitch/features/ui/util/reduced_motion.js2
-rw-r--r--app/javascript/flavours/glitch/features/video/index.js54
-rw-r--r--app/javascript/flavours/glitch/middleware/errors.js2
-rw-r--r--app/javascript/flavours/glitch/middleware/loading_bar.js2
-rw-r--r--app/javascript/flavours/glitch/middleware/sounds.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/accounts.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/accounts_counters.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/accounts_map.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/alerts.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/announcements.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/compose.js30
-rw-r--r--app/javascript/flavours/glitch/reducers/contexts.js4
-rw-r--r--app/javascript/flavours/glitch/reducers/conversations.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/custom_emojis.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/domain_lists.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/filters.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/followed_tags.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/height_cache.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/list_adder.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/list_editor.js10
-rw-r--r--app/javascript/flavours/glitch/reducers/lists.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/local_settings.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/markers.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/media_attachments.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/meta.js10
-rw-r--r--app/javascript/flavours/glitch/reducers/modal.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/notifications.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/picture_in_picture.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/pinned_accounts_editor.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/push_notifications.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/relationships.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/search.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/settings.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/status_lists.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/statuses.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/suggestions.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/tags.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/timelines.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/trends.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/user_lists.js2
-rw-r--r--app/javascript/flavours/glitch/store/configureStore.js2
-rw-r--r--app/javascript/flavours/glitch/utils/hashtag.js8
-rw-r--r--app/javascript/flavours/glitch/utils/privacy_preference.js2
-rw-r--r--app/javascript/flavours/glitch/utils/react_helpers.js2
-rw-r--r--app/javascript/flavours/glitch/uuid.js2
-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
-rw-r--r--config/application.rb15
-rw-r--r--config/initializers/rack_attack.rb10
-rw-r--r--config/locales/activerecord.ca.yml2
-rw-r--r--config/locales/activerecord.csb.yml1
-rw-r--r--config/locales/activerecord.uz.yml55
-rw-r--r--config/locales/activerecord.zh_Hant.yml15
-rw-r--r--config/locales/ar.yml28
-rw-r--r--config/locales/be.yml6
-rw-r--r--config/locales/ca.yml70
-rw-r--r--config/locales/cs.yml3
-rw-r--r--config/locales/csb.yml12
-rw-r--r--config/locales/cy.yml24
-rw-r--r--config/locales/da.yml34
-rw-r--r--config/locales/de.yml30
-rw-r--r--config/locales/devise.csb.yml1
-rw-r--r--config/locales/devise.ko.yml38
-rw-r--r--config/locales/devise.uz.yml13
-rw-r--r--config/locales/devise.zh-CN.yml2
-rw-r--r--config/locales/doorkeeper.csb.yml1
-rw-r--r--config/locales/doorkeeper.es.yml2
-rw-r--r--config/locales/doorkeeper.eu.yml12
-rw-r--r--config/locales/doorkeeper.gl.yml10
-rw-r--r--config/locales/doorkeeper.hy.yml1
-rw-r--r--config/locales/doorkeeper.ko.yml2
-rw-r--r--config/locales/doorkeeper.ru.yml1
-rw-r--r--config/locales/doorkeeper.uz.yml1
-rw-r--r--config/locales/en-GB.yml70
-rw-r--r--config/locales/en_GB.yml1043
-rw-r--r--config/locales/es-AR.yml24
-rw-r--r--config/locales/es-MX.yml24
-rw-r--r--config/locales/es.yml25
-rw-r--r--config/locales/et.yml24
-rw-r--r--config/locales/eu.yml55
-rw-r--r--config/locales/fi.yml131
-rw-r--r--config/locales/fo.yml24
-rw-r--r--config/locales/fr-QC.yml8
-rw-r--r--config/locales/fr.yml32
-rw-r--r--config/locales/fy.yml24
-rw-r--r--config/locales/gl.yml26
-rw-r--r--config/locales/he.yml24
-rw-r--r--config/locales/hu.yml48
-rw-r--r--config/locales/hy.yml22
-rw-r--r--config/locales/id.yml2
-rw-r--r--config/locales/is.yml24
-rw-r--r--config/locales/it.yml30
-rw-r--r--config/locales/ja.yml24
-rw-r--r--config/locales/ko.yml96
-rw-r--r--config/locales/lv.yml24
-rw-r--r--config/locales/nl.yml34
-rw-r--r--config/locales/pl.yml24
-rw-r--r--config/locales/pt-BR.yml17
-rw-r--r--config/locales/pt-PT.yml24
-rw-r--r--config/locales/simple_form.csb.yml1
-rw-r--r--config/locales/simple_form.da.yml2
-rw-r--r--config/locales/simple_form.en-GB.yml131
-rw-r--r--config/locales/simple_form.en_GB.yml131
-rw-r--r--config/locales/simple_form.es.yml2
-rw-r--r--config/locales/simple_form.eu.yml6
-rw-r--r--config/locales/simple_form.fi.yml8
-rw-r--r--config/locales/simple_form.ja.yml22
-rw-r--r--config/locales/simple_form.ko.yml14
-rw-r--r--config/locales/simple_form.nl.yml6
-rw-r--r--config/locales/simple_form.sk.yml2
-rw-r--r--config/locales/simple_form.uz.yml1
-rw-r--r--config/locales/simple_form.zh-TW.yml4
-rw-r--r--config/locales/sk.yml17
-rw-r--r--config/locales/sl.yml24
-rw-r--r--config/locales/sq.yml24
-rw-r--r--config/locales/sv.yml11
-rw-r--r--config/locales/th.yml42
-rw-r--r--config/locales/tr.yml20
-rw-r--r--config/locales/uk.yml24
-rw-r--r--config/locales/uz.yml51
-rw-r--r--config/locales/vi.yml24
-rw-r--r--config/locales/zh-CN.yml48
-rw-r--r--config/locales/zh-TW.yml36
-rw-r--r--lib/mastodon/version.rb2
-rw-r--r--package.json29
-rw-r--r--spec/controllers/settings/applications_controller_spec.rb2
-rw-r--r--yarn.lock1377
489 files changed, 6161 insertions, 4454 deletions
diff --git a/.eslintrc.js b/.eslintrc.js
index 03af2975b..ca7fc83eb 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -3,6 +3,8 @@ module.exports = {
 
   extends: [
     'eslint:recommended',
+    'plugin:react/recommended',
+    'plugin:jsx-a11y/recommended',
   ],
 
   env: {
@@ -80,8 +82,6 @@ module.exports = {
       },
     ],
     'no-empty': 'off',
-    'no-nested-ternary': 'warn',
-    'no-prototype-builtins': 'off',
     'no-restricted-properties': [
       'error',
       { property: 'substring', message: 'Use .slice instead of .substring.' },
@@ -113,55 +113,42 @@ module.exports = {
     'react/jsx-boolean-value': 'error',
     'react/jsx-closing-bracket-location': ['error', 'line-aligned'],
     'react/jsx-curly-spacing': 'error',
+    'react/display-name': 'off',
     'react/jsx-equals-spacing': 'error',
     'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'],
     'react/jsx-indent': ['error', 2],
     'react/jsx-no-bind': 'error',
-    'react/jsx-no-duplicate-props': 'error',
-    'react/jsx-no-undef': 'error',
+    'react/jsx-no-target-blank': 'off',
     'react/jsx-tag-spacing': 'error',
-    'react/jsx-uses-react': 'error',
-    'react/jsx-uses-vars': 'error',
     'react/jsx-wrap-multilines': 'error',
-    'react/no-multi-comp': 'off',
-    'react/no-string-refs': 'error',
-    'react/prop-types': 'error',
+    'react/no-deprecated': 'off',
+    'react/no-unknown-property': 'off',
     'react/self-closing-comp': 'error',
 
+    // recommended values found in https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/src/index.js
     'jsx-a11y/accessible-emoji': 'warn',
-    'jsx-a11y/alt-text': 'warn',
-    'jsx-a11y/anchor-has-content': 'warn',
-    'jsx-a11y/anchor-is-valid': [
-      'warn',
-      {
-        components: [
-          'Link',
-          'NavLink',
-        ],
-        specialLink: [
-          'to',
-        ],
-        aspect: [
-          'noHref',
-          'invalidHref',
-          'preferButton',
-        ],
-      },
-    ],
-    'jsx-a11y/aria-activedescendant-has-tabindex': 'warn',
-    'jsx-a11y/aria-props': 'warn',
-    'jsx-a11y/aria-proptypes': 'warn',
-    'jsx-a11y/aria-role': 'warn',
-    'jsx-a11y/aria-unsupported-elements': 'warn',
-    'jsx-a11y/heading-has-content': 'warn',
-    'jsx-a11y/html-has-lang': 'warn',
-    'jsx-a11y/iframe-has-title': 'warn',
-    'jsx-a11y/img-redundant-alt': 'warn',
-    'jsx-a11y/interactive-supports-focus': 'warn',
-    'jsx-a11y/label-has-for': 'off',
-    'jsx-a11y/mouse-events-have-key-events': 'warn',
-    'jsx-a11y/no-access-key': 'warn',
-    'jsx-a11y/no-distracting-elements': 'warn',
+    'jsx-a11y/click-events-have-key-events': 'off',
+    'jsx-a11y/label-has-associated-control': 'off',
+    'jsx-a11y/media-has-caption': 'off',
+    'jsx-a11y/no-autofocus': 'off',
+    // recommended rule is:
+    // 'jsx-a11y/no-interactive-element-to-noninteractive-role': [
+    //   'error',
+    //   {
+    //     tr: ['none', 'presentation'],
+    //     canvas: ['img'],
+    //   },
+    // ],
+    'jsx-a11y/no-interactive-element-to-noninteractive-role': 'off',
+    // recommended rule is:
+    // 'jsx-a11y/no-noninteractive-element-interactions': [
+    //   'error',
+    //   {
+    //     body: ['onError', 'onLoad'],
+    //     iframe: ['onError', 'onLoad'],
+    //     img: ['onError', 'onLoad'],
+    //   },
+    // ],
     'jsx-a11y/no-noninteractive-element-interactions': [
       'warn',
       {
@@ -170,8 +157,18 @@ module.exports = {
         ],
       },
     ],
+    // recommended rule is:
+    // 'jsx-a11y/no-noninteractive-tabindex': [
+    //   'error',
+    //   {
+    //     tags: [],
+    //     roles: ['tabpanel'],
+    //     allowExpressionValues: true,
+    //   },
+    // ],
+    'jsx-a11y/no-noninteractive-tabindex': 'off',
     'jsx-a11y/no-onchange': 'warn',
-    'jsx-a11y/no-redundant-roles': 'warn',
+    // recommended is full 'error'
     'jsx-a11y/no-static-element-interactions': [
       'warn',
       {
@@ -180,10 +177,6 @@ module.exports = {
         ],
       },
     ],
-    'jsx-a11y/role-has-required-aria-props': 'warn',
-    'jsx-a11y/role-supports-aria-props': 'off',
-    'jsx-a11y/scope': 'warn',
-    'jsx-a11y/tabindex-no-positive': 'warn',
 
     'import/extensions': [
       'error',
diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml
index 51ee8a071..36e9bf370 100644
--- a/.github/workflows/build-image.yml
+++ b/.github/workflows/build-image.yml
@@ -15,6 +15,11 @@ permissions:
 jobs:
   build-image:
     runs-on: ubuntu-latest
+
+    concurrency:
+      group: ${{ github.ref }}
+      cancel-in-progress: true
+
     steps:
       - uses: actions/checkout@v3
       - uses: hadolint/hadolint-action@v3.1.0
@@ -34,10 +39,11 @@ jobs:
             type=raw,value=latest,enable={{is_default_branch}}
             type=edge,branch=main
             type=sha,prefix=,format=long
-      - uses: docker/build-push-action@v3
+      - uses: docker/build-push-action@v4
         with:
           context: .
           platforms: linux/amd64,linux/arm64
+          provenance: false
           builder: ${{ steps.buildx.outputs.name }}
           push: ${{ github.event_name != 'pull_request' }}
           tags: ${{ steps.meta.outputs.tags }}
diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml
new file mode 100644
index 000000000..431b88e8d
--- /dev/null
+++ b/.github/workflows/lint-css.yml
@@ -0,0 +1,48 @@
+name: CSS Linting
+on:
+  push:
+    branches-ignore:
+      - 'dependabot/**'
+    paths:
+      - 'package.json'
+      - 'yarn.lock'
+      - '.prettier*'
+      - 'stylelint.config.js'
+      - '**/*.css'
+      - '**/*.scss'
+      - '.github/workflows/lint-css.yml'
+      - '.github/stylelint-matcher.json'
+
+  pull_request:
+    paths:
+      - 'package.json'
+      - 'yarn.lock'
+      - '.prettier*'
+      - 'stylelint.config.js'
+      - '**/*.css'
+      - '**/*.scss'
+      - '.github/workflows/lint-css.yml'
+      - '.github/stylelint-matcher.json'
+
+jobs:
+  lint:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Clone repository
+        uses: actions/checkout@v3
+
+      - name: Set up Node.js
+        uses: actions/setup-node@v3
+        with:
+          cache: yarn
+
+      - name: Install all yarn packages
+        run: yarn --frozen-lockfile
+
+      - uses: xt0rted/stylelint-problem-matcher@v1
+
+      - run: echo "::add-matcher::.github/stylelint-matcher.json"
+
+      - name: Stylelint
+        run: yarn test:lint:sass
diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml
new file mode 100644
index 000000000..49d989771
--- /dev/null
+++ b/.github/workflows/lint-js.yml
@@ -0,0 +1,40 @@
+name: JavaScript Linting
+on:
+  push:
+    branches-ignore:
+      - 'dependabot/**'
+    paths:
+      - 'package.json'
+      - 'yarn.lock'
+      - '.prettier*'
+      - '.eslint*'
+      - '**/*.js'
+      - '.github/workflows/lint-js.yml'
+
+  pull_request:
+    paths:
+      - 'package.json'
+      - 'yarn.lock'
+      - '.prettier*'
+      - '.eslint*'
+      - '**/*.js'
+      - '.github/workflows/lint-js.yml'
+
+jobs:
+  lint:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Clone repository
+        uses: actions/checkout@v3
+
+      - name: Set up Node.js
+        uses: actions/setup-node@v3
+        with:
+          cache: yarn
+
+      - name: Install all yarn packages
+        run: yarn --frozen-lockfile
+
+      - name: ESLint
+        run: yarn test:lint:js
diff --git a/.github/workflows/lint-json.yml b/.github/workflows/lint-json.yml
index 5bf4349b3..524ed083a 100644
--- a/.github/workflows/lint-json.yml
+++ b/.github/workflows/lint-json.yml
@@ -9,6 +9,7 @@ on:
       - '.prettier*'
       - '**/*.json'
       - '.github/workflows/lint-json.yml'
+      - '!app/javascript/mastodon/locales/*.json'
 
   pull_request:
     paths:
@@ -17,6 +18,7 @@ on:
       - '.prettier*'
       - '**/*.json'
       - '.github/workflows/lint-json.yml'
+      - '!app/javascript/mastodon/locales/*.json'
 
 jobs:
   lint:
diff --git a/.github/workflows/lint-ruby.yml b/.github/workflows/lint-ruby.yml
new file mode 100644
index 000000000..b834e3053
--- /dev/null
+++ b/.github/workflows/lint-ruby.yml
@@ -0,0 +1,41 @@
+name: Ruby Linting
+on:
+  push:
+    branches-ignore:
+      - 'dependabot/**'
+    paths:
+      - 'Gemfile*'
+      - '.rubocop.yml'
+      - '**/*.rb'
+      - '**/*.rake'
+      - '.github/workflows/lint-ruby.yml'
+
+  pull_request:
+    paths:
+      - 'Gemfile*'
+      - '.rubocop.yml'
+      - '**/*.rb'
+      - '**/*.rake'
+      - '.github/workflows/lint-ruby.yml'
+
+jobs:
+  lint:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout Code
+        uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
+
+      - name: Set-up RuboCop Problem Mathcher
+        uses: r7kamura/rubocop-problem-matchers-action@v1
+
+      - name: Run rubocop
+        uses: github/super-linter@v4
+        env:
+          DEFAULT_BRANCH: main
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          LINTER_RULES_PATH: .
+          RUBY_CONFIG_FILE: .rubocop.yml
+          VALIDATE_ALL_CODEBASE: false
+          VALIDATE_RUBY: true
diff --git a/.github/workflows/lint-yml.yml b/.github/workflows/lint-yml.yml
index b939ec8ce..48f8170b3 100644
--- a/.github/workflows/lint-yml.yml
+++ b/.github/workflows/lint-yml.yml
@@ -10,6 +10,7 @@ on:
       - '**/*.yaml'
       - '**/*.yml'
       - '.github/workflows/lint-yml.yml'
+      - '!config/locales/*.yml'
 
   pull_request:
     paths:
@@ -19,6 +20,7 @@ on:
       - '**/*.yaml'
       - '**/*.yml'
       - '.github/workflows/lint-yml.yml'
+      - '!config/locales/*.yml'
 
 jobs:
   lint:
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
deleted file mode 100644
index b6438d665..000000000
--- a/.github/workflows/linter.yml
+++ /dev/null
@@ -1,83 +0,0 @@
----
-#################################
-#################################
-## Super Linter GitHub Actions ##
-#################################
-#################################
-name: Lint Code Base
-
-#
-# Documentation:
-# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
-#
-
-#############################
-# Start the job on all push #
-#############################
-on:
-  push:
-    branches-ignore: [main]
-    # Remove the line above to run when pushing to master
-  pull_request:
-    branches: [main]
-
-###############
-# Set the Job #
-###############
-permissions:
-  checks: write
-  contents: read
-  pull-requests: write
-  statuses: write
-
-jobs:
-  build:
-    # Name the Job
-    name: Lint Code Base
-    # Set the agent to run on
-    runs-on: ubuntu-latest
-
-    ##################
-    # Load all steps #
-    ##################
-    steps:
-      ##########################
-      # Checkout the code base #
-      ##########################
-      - name: Checkout Code
-        uses: actions/checkout@v3
-        with:
-          # Full git history is needed to get a proper list of changed files within `super-linter`
-          fetch-depth: 0
-
-      - name: Set-up Node.js
-        uses: actions/setup-node@v3
-        with:
-          node-version-file: .nvmrc
-          cache: yarn
-      - name: Install dependencies
-        run: yarn install --frozen-lockfile
-      - name: Set-up RuboCop Problem Mathcher
-        uses: r7kamura/rubocop-problem-matchers-action@v1
-      - name: Set-up Stylelint Problem Matcher
-        uses: xt0rted/stylelint-problem-matcher@v1
-      # https://github.com/xt0rted/stylelint-problem-matcher/issues/360
-      - run: echo "::add-matcher::.github/stylelint-matcher.json"
-
-      ################################
-      # Run Linter against code base #
-      ################################
-      - name: Lint Code Base
-        uses: github/super-linter@v4
-        env:
-          CSS_FILE_NAME: stylelint.config.js
-          DEFAULT_BRANCH: main
-          NO_COLOR: 1 # https://github.com/xt0rted/stylelint-problem-matcher/issues/360
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.js
-          LINTER_RULES_PATH: .
-          RUBY_CONFIG_FILE: .rubocop.yml
-          VALIDATE_ALL_CODEBASE: false
-          VALIDATE_CSS: true
-          VALIDATE_JAVASCRIPT_ES: true
-          VALIDATE_RUBY: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a1ad4ffc..578a4f32a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,8 +38,10 @@ All notable changes to this project will be documented in this file.
 - Add brotli compression to `assets:precompile` ([Izorkin](https://github.com/mastodon/mastodon/pull/19025))
 - Add “disabled” account filter to the `/admin/accounts` UI ([tribela](https://github.com/mastodon/mastodon/pull/21282))
 - Add transparency to modal background for accessibility ([edent](https://github.com/mastodon/mastodon/pull/18081))
+- Add `lang` attribute to image description textarea and poll option field ([c960657](https://github.com/mastodon/mastodon/pull/23293))
 - Add `title` attribute to video elements in media attachments ([bramus](https://github.com/mastodon/mastodon/pull/21420))
 - Add left and right margins to emojis ([dsblank](https://github.com/mastodon/mastodon/pull/20464))
+- Add `roles` attribute to `Account` entities in REST API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23255))
 - Add `reading:autoplay:gifs` to `/api/v1/preferences` ([j-f1](https://github.com/mastodon/mastodon/pull/22706))
 - Add `hide_collections` parameter to `/api/v1/accounts/credentials` ([CarlSchwan](https://github.com/mastodon/mastodon/pull/22790))
 - Add `policy` attribute to web push subscription objects in `/api/v1/push/subscriptions` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23210))
@@ -67,8 +69,13 @@ All notable changes to this project will be documented in this file.
 - Change confirm prompts for relationships management ([tribela](https://github.com/mastodon/mastodon/pull/19411))
 - Change language surrounding disability in prompts for media descriptions ([hs4man21](https://github.com/mastodon/mastodon/pull/20923))
 - Change confusing wording in the sign in banner ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22490))
+- Change `POST /settings/applications/:id` to regenerate token on scopes change ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23359))
 - Change account moderation notes to make links clickable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22553))
+- Change link previews for statuses to never use avatar as fallback ([Gargron](https://github.com/mastodon/mastodon/pull/23376))
 - Change email address input to be read-only for logged-in users when requesting a new confirmation e-mail ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23247))
+- Change notifications per page from 15 to 40 in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/23348))
+- Change number of stored items in home feed from 400 to 800 ([Gargron](https://github.com/mastodon/mastodon/pull/23349))
+- Change API rate limits from 300/5min per user to 1500/5min per user, 300/5min per app ([Gargron](https://github.com/mastodon/mastodon/pull/23347))
 - Save avatar or header correctly even if the other one fails ([tribela](https://github.com/mastodon/mastodon/pull/18465))
 - Change `referrer-policy` to `same-origin` application-wide ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23014), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23037))
 - Add 'private' to `Cache-Control`, match Rails expectations ([daxtens](https://github.com/mastodon/mastodon/pull/20608))
@@ -88,7 +95,7 @@ All notable changes to this project will be documented in this file.
 - Allow adding relays in secure mode and limited federation mode ([ineffyble](https://github.com/mastodon/mastodon/pull/22324))
 - Change timestamps to be displayed using the user's timezone throughout the moderation interface ([FrancisMurillo](https://github.com/mastodon/mastodon/pull/21878), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/22555))
 - Change CSP directives on API to be tight and concise ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20960))
-- Change web UI to not autofocus the compose form ([raboof](https://github.com/mastodon/mastodon/pull/16517))
+- Change web UI to not autofocus the compose form ([raboof](https://github.com/mastodon/mastodon/pull/16517), [Akkiesoft](https://github.com/mastodon/mastodon/pull/23094))
 - Change idempotency key handling for posting when database access is slow ([lambda](https://github.com/mastodon/mastodon/pull/21840))
 - Change remote media files to be downloaded outside of transactions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21796))
 - Improve contrast of charts in “poll has ended” notifications ([j-f1](https://github.com/mastodon/mastodon/pull/22575))
@@ -101,6 +108,7 @@ All notable changes to this project will be documented in this file.
 
 - Officially remove support for Ruby 2.6 ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21477))
 - Remove `object-fit` polyfill used for old versions of Microsoft Edge ([shuuji3](https://github.com/mastodon/mastodon/pull/22693))
+- Remove `intersection-observer` polyfill for old Safari support ([shuuji3](https://github.com/mastodon/mastodon/pull/23284))
 - Remove empty `title` tag from mailer layout ([nametoolong](https://github.com/mastodon/mastodon/pull/23078))
 
 ### Fixed
@@ -110,6 +118,7 @@ All notable changes to this project will be documented in this file.
 - Fix possible race conditions when suspending/unsuspending accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22363))
 - Fix being stuck in edit mode when deleting the edited status ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22126))
 - Fix filters not being applied to some notification types ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23211))
+- Fix incorrect link in push notifications for some event types ([elizabeth-dev](https://github.com/mastodon/mastodon/pull/23286))
 - Fix some performance issues with `/admin/instances` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21907))
 - Fix some pre-4.0 admin audit logs ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22091))
 - Fix moderation audit log items for warnings having incorrect links ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23242))
@@ -126,6 +135,7 @@ All notable changes to this project will be documented in this file.
 - Fix wrong padding in RTL layout ([Gargron](https://github.com/mastodon/mastodon/pull/23157))
 - Fix drag & drop upload area display in single-column mode ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23217))
 - Fix being unable to get a single EmailDomainBlock from the admin API ([trwnh](https://github.com/mastodon/mastodon/pull/20846))
+- Fix unserialized `role` on account entities in admin API ([Gargron](https://github.com/mastodon/mastodon/pull/23290))
 - Fix pagination of followed tags ([trwnh](https://github.com/mastodon/mastodon/pull/20861))
 - Fix dropdown menu positions when scrolling ([sidp](https://github.com/mastodon/mastodon/pull/22916), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23062))
 - Fix email with empty domain name labels passing validation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23246))
@@ -150,6 +160,7 @@ All notable changes to this project will be documented in this file.
 - Fix crash when trying to fetch unobtainable avatar of user using external authentication ([lochiiconnectivity](https://github.com/mastodon/mastodon/pull/22462))
 - Fix potential duplicate statuses in Explore tab ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22121))
 - Fix deprecation warning in `tootctl accounts rotate` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22120))
+- Fix styling of featured tags in light theme ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23252))
 - Fix missing style in warning and strike cards ([AtelierSnek](https://github.com/mastodon/mastodon/pull/22177), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/22302))
 - Fix wasteful request to `/api/v1/custom_emojis` when not logged in ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22326))
 - Fix replies sometimes being delivered to user-blocked domains ([tribela](https://github.com/mastodon/mastodon/pull/22117))
diff --git a/Gemfile b/Gemfile
index dd3d59d2d..af2de5e23 100644
--- a/Gemfile
+++ b/Gemfile
@@ -18,7 +18,7 @@ gem 'makara', '~> 0.5'
 gem 'pghero'
 gem 'dotenv-rails', '~> 2.8'
 
-gem 'aws-sdk-s3', '~> 1.117', require: false
+gem 'aws-sdk-s3', '~> 1.119', require: false
 gem 'fog-core', '<= 2.4.0'
 gem 'fog-openstack', '~> 0.3', require: false
 gem 'kt-paperclip', '~> 7.1'
@@ -40,7 +40,7 @@ end
 gem 'net-ldap', '~> 0.17'
 gem 'omniauth-cas', '~> 2.0'
 gem 'omniauth-saml', '~> 1.10'
-gem 'gitlab-omniauth-openid-connect', '~>0.10.0', require: 'omniauth_openid_connect'
+gem 'gitlab-omniauth-openid-connect', '~>0.10.1', require: 'omniauth_openid_connect'
 gem 'omniauth', '~> 1.9'
 gem 'omniauth-rails_csrf_protection', '~> 0.1'
 
@@ -73,7 +73,7 @@ gem 'rack-attack', '~> 6.6'
 gem 'rack-cors', '~> 1.1', require: 'rack/cors'
 gem 'rails-i18n', '~> 6.0'
 gem 'rails-settings-cached', '~> 0.6'
-gem 'redcarpet', '~> 3.5'
+gem 'redcarpet', '~> 3.6'
 gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis']
 gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
 gem 'rqrcode', '~> 2.1'
@@ -85,7 +85,7 @@ gem 'sidekiq-scheduler', '~> 4.0'
 gem 'sidekiq-unique-jobs', '~> 7.1'
 gem 'sidekiq-bulk', '~> 0.2.0'
 gem 'simple-navigation', '~> 4.4'
-gem 'simple_form', '~> 5.1'
+gem 'simple_form', '~> 5.2'
 gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
 gem 'stoplight', '~> 3.0.1'
 gem 'strong_migrations', '~> 0.7'
diff --git a/Gemfile.lock b/Gemfile.lock
index 2160c1579..36659aca3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -10,40 +10,40 @@ GIT
 GEM
   remote: https://rubygems.org/
   specs:
-    actioncable (6.1.7.1)
-      actionpack (= 6.1.7.1)
-      activesupport (= 6.1.7.1)
+    actioncable (6.1.7.2)
+      actionpack (= 6.1.7.2)
+      activesupport (= 6.1.7.2)
       nio4r (~> 2.0)
       websocket-driver (>= 0.6.1)
-    actionmailbox (6.1.7.1)
-      actionpack (= 6.1.7.1)
-      activejob (= 6.1.7.1)
-      activerecord (= 6.1.7.1)
-      activestorage (= 6.1.7.1)
-      activesupport (= 6.1.7.1)
+    actionmailbox (6.1.7.2)
+      actionpack (= 6.1.7.2)
+      activejob (= 6.1.7.2)
+      activerecord (= 6.1.7.2)
+      activestorage (= 6.1.7.2)
+      activesupport (= 6.1.7.2)
       mail (>= 2.7.1)
-    actionmailer (6.1.7.1)
-      actionpack (= 6.1.7.1)
-      actionview (= 6.1.7.1)
-      activejob (= 6.1.7.1)
-      activesupport (= 6.1.7.1)
+    actionmailer (6.1.7.2)
+      actionpack (= 6.1.7.2)
+      actionview (= 6.1.7.2)
+      activejob (= 6.1.7.2)
+      activesupport (= 6.1.7.2)
       mail (~> 2.5, >= 2.5.4)
       rails-dom-testing (~> 2.0)
-    actionpack (6.1.7.1)
-      actionview (= 6.1.7.1)
-      activesupport (= 6.1.7.1)
+    actionpack (6.1.7.2)
+      actionview (= 6.1.7.2)
+      activesupport (= 6.1.7.2)
       rack (~> 2.0, >= 2.0.9)
       rack-test (>= 0.6.3)
       rails-dom-testing (~> 2.0)
       rails-html-sanitizer (~> 1.0, >= 1.2.0)
-    actiontext (6.1.7.1)
-      actionpack (= 6.1.7.1)
-      activerecord (= 6.1.7.1)
-      activestorage (= 6.1.7.1)
-      activesupport (= 6.1.7.1)
+    actiontext (6.1.7.2)
+      actionpack (= 6.1.7.2)
+      activerecord (= 6.1.7.2)
+      activestorage (= 6.1.7.2)
+      activesupport (= 6.1.7.2)
       nokogiri (>= 1.8.5)
-    actionview (6.1.7.1)
-      activesupport (= 6.1.7.1)
+    actionview (6.1.7.2)
+      activesupport (= 6.1.7.2)
       builder (~> 3.1)
       erubi (~> 1.4)
       rails-dom-testing (~> 2.0)
@@ -54,22 +54,22 @@ GEM
       case_transform (>= 0.2)
       jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
     active_record_query_trace (1.8)
-    activejob (6.1.7.1)
-      activesupport (= 6.1.7.1)
+    activejob (6.1.7.2)
+      activesupport (= 6.1.7.2)
       globalid (>= 0.3.6)
-    activemodel (6.1.7.1)
-      activesupport (= 6.1.7.1)
-    activerecord (6.1.7.1)
-      activemodel (= 6.1.7.1)
-      activesupport (= 6.1.7.1)
-    activestorage (6.1.7.1)
-      actionpack (= 6.1.7.1)
-      activejob (= 6.1.7.1)
-      activerecord (= 6.1.7.1)
-      activesupport (= 6.1.7.1)
+    activemodel (6.1.7.2)
+      activesupport (= 6.1.7.2)
+    activerecord (6.1.7.2)
+      activemodel (= 6.1.7.2)
+      activesupport (= 6.1.7.2)
+    activestorage (6.1.7.2)
+      actionpack (= 6.1.7.2)
+      activejob (= 6.1.7.2)
+      activerecord (= 6.1.7.2)
+      activesupport (= 6.1.7.2)
       marcel (~> 1.0)
       mini_mime (>= 1.1.0)
-    activesupport (6.1.7.1)
+    activesupport (6.1.7.2)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       i18n (>= 1.6, < 2)
       minitest (>= 5.1)
@@ -90,16 +90,16 @@ GEM
     attr_required (1.0.1)
     awrence (1.2.1)
     aws-eventstream (1.2.0)
-    aws-partitions (1.670.0)
-    aws-sdk-core (3.168.2)
+    aws-partitions (1.701.0)
+    aws-sdk-core (3.170.0)
       aws-eventstream (~> 1, >= 1.0.2)
       aws-partitions (~> 1, >= 1.651.0)
       aws-sigv4 (~> 1.5)
       jmespath (~> 1, >= 1.6.1)
-    aws-sdk-kms (1.60.0)
+    aws-sdk-kms (1.62.0)
       aws-sdk-core (~> 3, >= 3.165.0)
       aws-sigv4 (~> 1.1)
-    aws-sdk-s3 (1.117.2)
+    aws-sdk-s3 (1.119.0)
       aws-sdk-core (~> 3, >= 3.165.0)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.4)
@@ -117,7 +117,7 @@ GEM
       erubi (~> 1.4)
       parser (>= 2.4)
       smart_properties
-    bindata (2.4.10)
+    bindata (2.4.14)
     binding_of_caller (1.0.0)
       debug_inspector (>= 0.0.1)
     blurhash (0.1.6)
@@ -207,7 +207,7 @@ GEM
     docile (1.4.0)
     domain_name (0.5.20190701)
       unf (>= 0.0.5, < 1.0.0)
-    doorkeeper (5.6.2)
+    doorkeeper (5.6.3)
       railties (>= 5)
     dotenv (2.8.1)
     dotenv-rails (2.8.1)
@@ -279,11 +279,11 @@ GEM
     fuubar (2.5.1)
       rspec-core (~> 3.0)
       ruby-progressbar (~> 1.4)
-    gitlab-omniauth-openid-connect (0.10.0)
+    gitlab-omniauth-openid-connect (0.10.1)
       addressable (~> 2.7)
       omniauth (>= 1.9, < 3)
       openid_connect (~> 1.2)
-    globalid (1.0.1)
+    globalid (1.1.0)
       activesupport (>= 5.0)
     hamlit (2.13.0)
       temple (>= 0.8.2)
@@ -333,10 +333,11 @@ GEM
     jmespath (1.6.2)
     json (2.6.3)
     json-canonicalization (0.3.0)
-    json-jwt (1.14.0)
+    json-jwt (1.15.3)
       activesupport (>= 4.2)
       aes_key_wrap
       bindata
+      httpclient
     json-ld (3.2.3)
       htmlentities (~> 4.3)
       json-canonicalization (~> 0.3)
@@ -418,7 +419,7 @@ GEM
     net-ldap (0.17.1)
     net-pop (0.1.2)
       net-protocol
-    net-protocol (0.1.3)
+    net-protocol (0.2.1)
       timeout
     net-scp (4.0.0.rc1)
       net-ssh (>= 2.6.5, < 8.0.0)
@@ -426,7 +427,7 @@ GEM
       net-protocol
     net-ssh (7.0.1)
     nio4r (2.5.8)
-    nokogiri (1.14.0)
+    nokogiri (1.14.1)
       mini_portile2 (~> 2.8.0)
       racc (~> 1.4)
     nsa (0.2.8)
@@ -448,21 +449,22 @@ GEM
     omniauth-saml (1.10.3)
       omniauth (~> 1.3, >= 1.3.2)
       ruby-saml (~> 1.9)
-    openid_connect (1.3.0)
+    openid_connect (1.4.2)
       activemodel
       attr_required (>= 1.0.0)
-      json-jwt (>= 1.5.0)
-      rack-oauth2 (>= 1.6.1)
-      swd (>= 1.0.0)
+      json-jwt (>= 1.15.0)
+      net-smtp
+      rack-oauth2 (~> 1.21)
+      swd (~> 1.3)
       tzinfo
       validate_email
       validate_url
-      webfinger (>= 1.0.1)
+      webfinger (~> 1.2)
     openssl (3.0.0)
     openssl-signature_algorithm (1.2.1)
       openssl (> 2.0, < 3.1)
     orm_adapter (0.5.0)
-    ox (2.14.13)
+    ox (2.14.14)
     parallel (1.22.1)
     parser (3.2.0.0)
       ast (~> 2.4.1)
@@ -503,7 +505,7 @@ GEM
       rack (>= 1.0, < 3)
     rack-cors (1.1.1)
       rack (>= 2.0.0)
-    rack-oauth2 (1.19.0)
+    rack-oauth2 (1.21.3)
       activesupport
       attr_required
       httpclient
@@ -513,20 +515,20 @@ GEM
       rack
     rack-test (2.0.2)
       rack (>= 1.3)
-    rails (6.1.7.1)
-      actioncable (= 6.1.7.1)
-      actionmailbox (= 6.1.7.1)
-      actionmailer (= 6.1.7.1)
-      actionpack (= 6.1.7.1)
-      actiontext (= 6.1.7.1)
-      actionview (= 6.1.7.1)
-      activejob (= 6.1.7.1)
-      activemodel (= 6.1.7.1)
-      activerecord (= 6.1.7.1)
-      activestorage (= 6.1.7.1)
-      activesupport (= 6.1.7.1)
+    rails (6.1.7.2)
+      actioncable (= 6.1.7.2)
+      actionmailbox (= 6.1.7.2)
+      actionmailer (= 6.1.7.2)
+      actionpack (= 6.1.7.2)
+      actiontext (= 6.1.7.2)
+      actionview (= 6.1.7.2)
+      activejob (= 6.1.7.2)
+      activemodel (= 6.1.7.2)
+      activerecord (= 6.1.7.2)
+      activestorage (= 6.1.7.2)
+      activesupport (= 6.1.7.2)
       bundler (>= 1.15.0)
-      railties (= 6.1.7.1)
+      railties (= 6.1.7.2)
       sprockets-rails (>= 2.0.0)
     rails-controller-testing (1.0.5)
       actionpack (>= 5.0.1.rc1)
@@ -535,16 +537,16 @@ GEM
     rails-dom-testing (2.0.3)
       activesupport (>= 4.2.0)
       nokogiri (>= 1.6)
-    rails-html-sanitizer (1.4.4)
+    rails-html-sanitizer (1.5.0)
       loofah (~> 2.19, >= 2.19.1)
     rails-i18n (6.0.0)
       i18n (>= 0.7, < 2)
       railties (>= 6.0.0, < 7)
     rails-settings-cached (0.6.6)
       rails (>= 4.2.0)
-    railties (6.1.7.1)
-      actionpack (= 6.1.7.1)
-      activesupport (= 6.1.7.1)
+    railties (6.1.7.2)
+      actionpack (= 6.1.7.2)
+      activesupport (= 6.1.7.2)
       method_source
       rake (>= 12.2)
       thor (~> 1.0)
@@ -554,7 +556,7 @@ GEM
       link_header (~> 0.0, >= 0.0.8)
     rdf-normalize (0.5.1)
       rdf (~> 3.2)
-    redcarpet (3.5.1)
+    redcarpet (3.6.0)
     redis (4.5.1)
     redis-namespace (1.10.0)
       redis (>= 4)
@@ -595,7 +597,7 @@ GEM
     rspec-support (3.11.1)
     rspec_junit_formatter (0.6.0)
       rspec-core (>= 2, < 4, != 2.12.0)
-    rubocop (1.44.0)
+    rubocop (1.44.1)
       json (~> 2.3)
       parallel (~> 1.10)
       parser (>= 3.2.0.0)
@@ -628,7 +630,7 @@ GEM
       fugit (~> 1.1, >= 1.1.6)
     safety_net_attestation (0.4.0)
       jwt (~> 2.0)
-    sanitize (6.0.0)
+    sanitize (6.0.1)
       crass (~> 1.0.2)
       nokogiri (>= 1.12.0)
     scenic (1.7.0)
@@ -654,7 +656,7 @@ GEM
       thor (>= 0.20, < 3.0)
     simple-navigation (4.4.0)
       activesupport (>= 2.3.2)
-    simple_form (5.1.0)
+    simple_form (5.2.0)
       actionpack (>= 5.2)
       activemodel (>= 5.2)
     simplecov (0.22.0)
@@ -691,7 +693,7 @@ GEM
       climate_control (>= 0.0.3, < 1.0)
     thor (1.2.1)
     tilt (2.0.11)
-    timeout (0.3.0)
+    timeout (0.3.1)
     tpm-key_attestation (0.11.0)
       bindata (~> 2.4)
       openssl (> 2.0, < 3.1)
@@ -709,7 +711,7 @@ GEM
     twitter-text (3.1.0)
       idn-ruby
       unf (~> 0.1.0)
-    tzinfo (2.0.5)
+    tzinfo (2.0.6)
       concurrent-ruby (~> 1.0)
     tzinfo-data (1.2022.7)
       tzinfo (>= 1.0.0)
@@ -764,7 +766,7 @@ DEPENDENCIES
   active_record_query_trace (~> 1.8)
   addressable (~> 2.8)
   annotate (~> 3.2)
-  aws-sdk-s3 (~> 1.117)
+  aws-sdk-s3 (~> 1.119)
   better_errors (~> 2.9)
   binding_of_caller (~> 1.0)
   blurhash (~> 0.1)
@@ -799,7 +801,7 @@ DEPENDENCIES
   fog-core (<= 2.4.0)
   fog-openstack (~> 0.3)
   fuubar (~> 2.5)
-  gitlab-omniauth-openid-connect (~> 0.10.0)
+  gitlab-omniauth-openid-connect (~> 0.10.1)
   hamlit-rails (~> 0.2)
   hcaptcha (~> 7.1)
   hiredis (~> 0.6)
@@ -852,7 +854,7 @@ DEPENDENCIES
   rails-i18n (~> 6.0)
   rails-settings-cached (~> 0.6)
   rdf-normalize (~> 0.5)
-  redcarpet (~> 3.5)
+  redcarpet (~> 3.6)
   redis (~> 4.5)
   redis-namespace (~> 1.10)
   rexml (~> 3.2)
@@ -872,7 +874,7 @@ DEPENDENCIES
   sidekiq-scheduler (~> 4.0)
   sidekiq-unique-jobs (~> 7.1)
   simple-navigation (~> 4.4)
-  simple_form (~> 5.1)
+  simple_form (~> 5.2)
   simplecov (~> 0.22)
   sprockets (~> 3.7.2)
   sprockets-rails (~> 3.4)
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/core/embed.js b/app/javascript/core/embed.js
index 9083eb7a3..d1e8f6b10 100644
--- a/app/javascript/core/embed.js
+++ b/app/javascript/core/embed.js
@@ -15,7 +15,7 @@ window.addEventListener('message', e => {
       id: data.id,
       height: document.getElementsByTagName('html')[0].scrollHeight,
     }, '*');
-  };
+  }
 
   if (['interactive', 'complete'].includes(document.readyState)) {
     setEmbedHeight();
diff --git a/app/javascript/core/settings.js b/app/javascript/core/settings.js
index d5bb9532c..d578463a3 100644
--- a/app/javascript/core/settings.js
+++ b/app/javascript/core/settings.js
@@ -2,7 +2,9 @@
 
 import 'packs/public-path';
 import escapeTextContentForBrowser from 'escape-html';
+
 const { delegate } = require('@rails/ujs');
+
 import emojify from '../mastodon/features/emoji/emoji';
 
 delegate(document, '#account_display_name', 'input', ({ target }) => {
@@ -65,7 +67,7 @@ delegate(document, '.input-copy button', 'click', ({ target }) => {
       input.blur();
       target.parentNode.classList.add('copied');
 
-    setTimeout(() => {
+      setTimeout(() => {
         target.parentNode.classList.remove('copied');
       }, 700);
     }
diff --git a/app/javascript/flavours/glitch/actions/account_notes.js b/app/javascript/flavours/glitch/actions/account_notes.js
index 059ed9e80..62a6b4cbb 100644
--- a/app/javascript/flavours/glitch/actions/account_notes.js
+++ b/app/javascript/flavours/glitch/actions/account_notes.js
@@ -21,27 +21,27 @@ export function submitAccountNote() {
       dispatch(submitAccountNoteSuccess(response.data));
     }).catch(error => dispatch(submitAccountNoteFail(error)));
   };
-};
+}
 
 export function submitAccountNoteRequest() {
   return {
     type: ACCOUNT_NOTE_SUBMIT_REQUEST,
   };
-};
+}
 
 export function submitAccountNoteSuccess(relationship) {
   return {
     type: ACCOUNT_NOTE_SUBMIT_SUCCESS,
     relationship,
   };
-};
+}
 
 export function submitAccountNoteFail(error) {
   return {
     type: ACCOUNT_NOTE_SUBMIT_FAIL,
     error,
   };
-};
+}
 
 export function initEditAccountNote(account) {
   return (dispatch, getState) => {
@@ -53,17 +53,17 @@ export function initEditAccountNote(account) {
       comment,
     });
   };
-};
+}
 
 export function cancelAccountNote() {
   return {
     type: ACCOUNT_NOTE_CANCEL,
   };
-};
+}
 
 export function changeAccountNoteComment(comment) {
   return {
     type: ACCOUNT_NOTE_CHANGE_COMMENT,
     comment,
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/accounts.js b/app/javascript/flavours/glitch/actions/accounts.js
index dc670e50a..6b5b2ade5 100644
--- a/app/javascript/flavours/glitch/actions/accounts.js
+++ b/app/javascript/flavours/glitch/actions/accounts.js
@@ -108,7 +108,7 @@ export function fetchAccount(id) {
       dispatch(fetchAccountFail(id, error));
     });
   };
-};
+}
 
 export const lookupAccount = acct => (dispatch, getState) => {
   dispatch(lookupAccountRequest(acct));
@@ -143,13 +143,13 @@ export function fetchAccountRequest(id) {
     type: ACCOUNT_FETCH_REQUEST,
     id,
   };
-};
+}
 
 export function fetchAccountSuccess() {
   return {
     type: ACCOUNT_FETCH_SUCCESS,
   };
-};
+}
 
 export function fetchAccountFail(id, error) {
   return {
@@ -158,7 +158,7 @@ export function fetchAccountFail(id, error) {
     error,
     skipAlert: true,
   };
-};
+}
 
 export function followAccount(id, options = { reblogs: true }) {
   return (dispatch, getState) => {
@@ -173,7 +173,7 @@ export function followAccount(id, options = { reblogs: true }) {
       dispatch(followAccountFail(error, locked));
     });
   };
-};
+}
 
 export function unfollowAccount(id) {
   return (dispatch, getState) => {
@@ -185,7 +185,7 @@ export function unfollowAccount(id) {
       dispatch(unfollowAccountFail(error));
     });
   };
-};
+}
 
 export function followAccountRequest(id, locked) {
   return {
@@ -194,7 +194,7 @@ export function followAccountRequest(id, locked) {
     locked,
     skipLoading: true,
   };
-};
+}
 
 export function followAccountSuccess(relationship, alreadyFollowing) {
   return {
@@ -203,7 +203,7 @@ export function followAccountSuccess(relationship, alreadyFollowing) {
     alreadyFollowing,
     skipLoading: true,
   };
-};
+}
 
 export function followAccountFail(error, locked) {
   return {
@@ -212,7 +212,7 @@ export function followAccountFail(error, locked) {
     locked,
     skipLoading: true,
   };
-};
+}
 
 export function unfollowAccountRequest(id) {
   return {
@@ -220,7 +220,7 @@ export function unfollowAccountRequest(id) {
     id,
     skipLoading: true,
   };
-};
+}
 
 export function unfollowAccountSuccess(relationship, statuses) {
   return {
@@ -229,7 +229,7 @@ export function unfollowAccountSuccess(relationship, statuses) {
     statuses,
     skipLoading: true,
   };
-};
+}
 
 export function unfollowAccountFail(error) {
   return {
@@ -237,7 +237,7 @@ export function unfollowAccountFail(error) {
     error,
     skipLoading: true,
   };
-};
+}
 
 export function blockAccount(id) {
   return (dispatch, getState) => {
@@ -250,7 +250,7 @@ export function blockAccount(id) {
       dispatch(blockAccountFail(id, error));
     });
   };
-};
+}
 
 export function unblockAccount(id) {
   return (dispatch, getState) => {
@@ -262,14 +262,14 @@ export function unblockAccount(id) {
       dispatch(unblockAccountFail(id, error));
     });
   };
-};
+}
 
 export function blockAccountRequest(id) {
   return {
     type: ACCOUNT_BLOCK_REQUEST,
     id,
   };
-};
+}
 
 export function blockAccountSuccess(relationship, statuses) {
   return {
@@ -277,35 +277,35 @@ export function blockAccountSuccess(relationship, statuses) {
     relationship,
     statuses,
   };
-};
+}
 
 export function blockAccountFail(error) {
   return {
     type: ACCOUNT_BLOCK_FAIL,
     error,
   };
-};
+}
 
 export function unblockAccountRequest(id) {
   return {
     type: ACCOUNT_UNBLOCK_REQUEST,
     id,
   };
-};
+}
 
 export function unblockAccountSuccess(relationship) {
   return {
     type: ACCOUNT_UNBLOCK_SUCCESS,
     relationship,
   };
-};
+}
 
 export function unblockAccountFail(error) {
   return {
     type: ACCOUNT_UNBLOCK_FAIL,
     error,
   };
-};
+}
 
 
 export function muteAccount(id, notifications, duration=0) {
@@ -319,7 +319,7 @@ export function muteAccount(id, notifications, duration=0) {
       dispatch(muteAccountFail(id, error));
     });
   };
-};
+}
 
 export function unmuteAccount(id) {
   return (dispatch, getState) => {
@@ -331,14 +331,14 @@ export function unmuteAccount(id) {
       dispatch(unmuteAccountFail(id, error));
     });
   };
-};
+}
 
 export function muteAccountRequest(id) {
   return {
     type: ACCOUNT_MUTE_REQUEST,
     id,
   };
-};
+}
 
 export function muteAccountSuccess(relationship, statuses) {
   return {
@@ -346,35 +346,35 @@ export function muteAccountSuccess(relationship, statuses) {
     relationship,
     statuses,
   };
-};
+}
 
 export function muteAccountFail(error) {
   return {
     type: ACCOUNT_MUTE_FAIL,
     error,
   };
-};
+}
 
 export function unmuteAccountRequest(id) {
   return {
     type: ACCOUNT_UNMUTE_REQUEST,
     id,
   };
-};
+}
 
 export function unmuteAccountSuccess(relationship) {
   return {
     type: ACCOUNT_UNMUTE_SUCCESS,
     relationship,
   };
-};
+}
 
 export function unmuteAccountFail(error) {
   return {
     type: ACCOUNT_UNMUTE_FAIL,
     error,
   };
-};
+}
 
 
 export function fetchFollowers(id) {
@@ -391,14 +391,14 @@ export function fetchFollowers(id) {
       dispatch(fetchFollowersFail(id, error));
     });
   };
-};
+}
 
 export function fetchFollowersRequest(id) {
   return {
     type: FOLLOWERS_FETCH_REQUEST,
     id,
   };
-};
+}
 
 export function fetchFollowersSuccess(id, accounts, next) {
   return {
@@ -407,7 +407,7 @@ export function fetchFollowersSuccess(id, accounts, next) {
     accounts,
     next,
   };
-};
+}
 
 export function fetchFollowersFail(id, error) {
   return {
@@ -416,7 +416,7 @@ export function fetchFollowersFail(id, error) {
     error,
     skipNotFound: true,
   };
-};
+}
 
 export function expandFollowers(id) {
   return (dispatch, getState) => {
@@ -438,14 +438,14 @@ export function expandFollowers(id) {
       dispatch(expandFollowersFail(id, error));
     });
   };
-};
+}
 
 export function expandFollowersRequest(id) {
   return {
     type: FOLLOWERS_EXPAND_REQUEST,
     id,
   };
-};
+}
 
 export function expandFollowersSuccess(id, accounts, next) {
   return {
@@ -454,7 +454,7 @@ export function expandFollowersSuccess(id, accounts, next) {
     accounts,
     next,
   };
-};
+}
 
 export function expandFollowersFail(id, error) {
   return {
@@ -462,7 +462,7 @@ export function expandFollowersFail(id, error) {
     id,
     error,
   };
-};
+}
 
 export function fetchFollowing(id) {
   return (dispatch, getState) => {
@@ -478,14 +478,14 @@ export function fetchFollowing(id) {
       dispatch(fetchFollowingFail(id, error));
     });
   };
-};
+}
 
 export function fetchFollowingRequest(id) {
   return {
     type: FOLLOWING_FETCH_REQUEST,
     id,
   };
-};
+}
 
 export function fetchFollowingSuccess(id, accounts, next) {
   return {
@@ -494,7 +494,7 @@ export function fetchFollowingSuccess(id, accounts, next) {
     accounts,
     next,
   };
-};
+}
 
 export function fetchFollowingFail(id, error) {
   return {
@@ -503,7 +503,7 @@ export function fetchFollowingFail(id, error) {
     error,
     skipNotFound: true,
   };
-};
+}
 
 export function expandFollowing(id) {
   return (dispatch, getState) => {
@@ -525,14 +525,14 @@ export function expandFollowing(id) {
       dispatch(expandFollowingFail(id, error));
     });
   };
-};
+}
 
 export function expandFollowingRequest(id) {
   return {
     type: FOLLOWING_EXPAND_REQUEST,
     id,
   };
-};
+}
 
 export function expandFollowingSuccess(id, accounts, next) {
   return {
@@ -541,7 +541,7 @@ export function expandFollowingSuccess(id, accounts, next) {
     accounts,
     next,
   };
-};
+}
 
 export function expandFollowingFail(id, error) {
   return {
@@ -549,7 +549,7 @@ export function expandFollowingFail(id, error) {
     id,
     error,
   };
-};
+}
 
 export function fetchRelationships(accountIds) {
   return (dispatch, getState) => {
@@ -570,7 +570,7 @@ export function fetchRelationships(accountIds) {
       dispatch(fetchRelationshipsFail(error));
     });
   };
-};
+}
 
 export function fetchRelationshipsRequest(ids) {
   return {
@@ -578,7 +578,7 @@ export function fetchRelationshipsRequest(ids) {
     ids,
     skipLoading: true,
   };
-};
+}
 
 export function fetchRelationshipsSuccess(relationships) {
   return {
@@ -586,7 +586,7 @@ export function fetchRelationshipsSuccess(relationships) {
     relationships,
     skipLoading: true,
   };
-};
+}
 
 export function fetchRelationshipsFail(error) {
   return {
@@ -595,7 +595,7 @@ export function fetchRelationshipsFail(error) {
     skipLoading: true,
     skipNotFound: true,
   };
-};
+}
 
 export function fetchFollowRequests() {
   return (dispatch, getState) => {
@@ -607,13 +607,13 @@ export function fetchFollowRequests() {
       dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null));
     }).catch(error => dispatch(fetchFollowRequestsFail(error)));
   };
-};
+}
 
 export function fetchFollowRequestsRequest() {
   return {
     type: FOLLOW_REQUESTS_FETCH_REQUEST,
   };
-};
+}
 
 export function fetchFollowRequestsSuccess(accounts, next) {
   return {
@@ -621,14 +621,14 @@ export function fetchFollowRequestsSuccess(accounts, next) {
     accounts,
     next,
   };
-};
+}
 
 export function fetchFollowRequestsFail(error) {
   return {
     type: FOLLOW_REQUESTS_FETCH_FAIL,
     error,
   };
-};
+}
 
 export function expandFollowRequests() {
   return (dispatch, getState) => {
@@ -646,13 +646,13 @@ export function expandFollowRequests() {
       dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null));
     }).catch(error => dispatch(expandFollowRequestsFail(error)));
   };
-};
+}
 
 export function expandFollowRequestsRequest() {
   return {
     type: FOLLOW_REQUESTS_EXPAND_REQUEST,
   };
-};
+}
 
 export function expandFollowRequestsSuccess(accounts, next) {
   return {
@@ -660,14 +660,14 @@ export function expandFollowRequestsSuccess(accounts, next) {
     accounts,
     next,
   };
-};
+}
 
 export function expandFollowRequestsFail(error) {
   return {
     type: FOLLOW_REQUESTS_EXPAND_FAIL,
     error,
   };
-};
+}
 
 export function authorizeFollowRequest(id) {
   return (dispatch, getState) => {
@@ -678,21 +678,21 @@ export function authorizeFollowRequest(id) {
       .then(() => dispatch(authorizeFollowRequestSuccess(id)))
       .catch(error => dispatch(authorizeFollowRequestFail(id, error)));
   };
-};
+}
 
 export function authorizeFollowRequestRequest(id) {
   return {
     type: FOLLOW_REQUEST_AUTHORIZE_REQUEST,
     id,
   };
-};
+}
 
 export function authorizeFollowRequestSuccess(id) {
   return {
     type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
     id,
   };
-};
+}
 
 export function authorizeFollowRequestFail(id, error) {
   return {
@@ -700,7 +700,7 @@ export function authorizeFollowRequestFail(id, error) {
     id,
     error,
   };
-};
+}
 
 
 export function rejectFollowRequest(id) {
@@ -712,21 +712,21 @@ export function rejectFollowRequest(id) {
       .then(() => dispatch(rejectFollowRequestSuccess(id)))
       .catch(error => dispatch(rejectFollowRequestFail(id, error)));
   };
-};
+}
 
 export function rejectFollowRequestRequest(id) {
   return {
     type: FOLLOW_REQUEST_REJECT_REQUEST,
     id,
   };
-};
+}
 
 export function rejectFollowRequestSuccess(id) {
   return {
     type: FOLLOW_REQUEST_REJECT_SUCCESS,
     id,
   };
-};
+}
 
 export function rejectFollowRequestFail(id, error) {
   return {
@@ -734,7 +734,7 @@ export function rejectFollowRequestFail(id, error) {
     id,
     error,
   };
-};
+}
 
 export function pinAccount(id) {
   return (dispatch, getState) => {
@@ -746,7 +746,7 @@ export function pinAccount(id) {
       dispatch(pinAccountFail(error));
     });
   };
-};
+}
 
 export function unpinAccount(id) {
   return (dispatch, getState) => {
@@ -758,49 +758,49 @@ export function unpinAccount(id) {
       dispatch(unpinAccountFail(error));
     });
   };
-};
+}
 
 export function pinAccountRequest(id) {
   return {
     type: ACCOUNT_PIN_REQUEST,
     id,
   };
-};
+}
 
 export function pinAccountSuccess(relationship) {
   return {
     type: ACCOUNT_PIN_SUCCESS,
     relationship,
   };
-};
+}
 
 export function pinAccountFail(error) {
   return {
     type: ACCOUNT_PIN_FAIL,
     error,
   };
-};
+}
 
 export function unpinAccountRequest(id) {
   return {
     type: ACCOUNT_UNPIN_REQUEST,
     id,
   };
-};
+}
 
 export function unpinAccountSuccess(relationship) {
   return {
     type: ACCOUNT_UNPIN_SUCCESS,
     relationship,
   };
-};
+}
 
 export function unpinAccountFail(error) {
   return {
     type: ACCOUNT_UNPIN_FAIL,
     error,
   };
-};
+}
 
 export const revealAccount = id => ({
   type: ACCOUNT_REVEAL,
@@ -811,18 +811,18 @@ export function fetchPinnedAccounts() {
   return (dispatch, getState) => {
     dispatch(fetchPinnedAccountsRequest());
 
-    api(getState).get(`/api/v1/endorsements`, { params: { limit: 0 } }).then(response => {
+    api(getState).get('/api/v1/endorsements', { params: { limit: 0 } }).then(response => {
       dispatch(importFetchedAccounts(response.data));
       dispatch(fetchPinnedAccountsSuccess(response.data));
     }).catch(err => dispatch(fetchPinnedAccountsFail(err)));
   };
-};
+}
 
 export function fetchPinnedAccountsRequest() {
   return {
     type: PINNED_ACCOUNTS_FETCH_REQUEST,
   };
-};
+}
 
 export function fetchPinnedAccountsSuccess(accounts, next) {
   return {
@@ -830,14 +830,14 @@ export function fetchPinnedAccountsSuccess(accounts, next) {
     accounts,
     next,
   };
-};
+}
 
 export function fetchPinnedAccountsFail(error) {
   return {
     type: PINNED_ACCOUNTS_FETCH_FAIL,
     error,
   };
-};
+}
 
 export function fetchPinnedAccountsSuggestions(q) {
   return (dispatch, getState) => {
@@ -853,7 +853,7 @@ export function fetchPinnedAccountsSuggestions(q) {
       dispatch(fetchPinnedAccountsSuggestionsReady(q, response.data));
     });
   };
-};
+}
 
 export function fetchPinnedAccountsSuggestionsReady(query, accounts) {
   return {
@@ -861,24 +861,24 @@ export function fetchPinnedAccountsSuggestionsReady(query, accounts) {
     query,
     accounts,
   };
-};
+}
 
 export function clearPinnedAccountsSuggestions() {
   return {
     type: PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CLEAR,
   };
-};
+}
 
 export function changePinnedAccountsSuggestions(value) {
   return {
     type: PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CHANGE,
     value,
-  }
-};
+  };
+}
 
 export function resetPinnedAccountsEditor() {
   return {
     type: PINNED_ACCOUNTS_EDITOR_RESET,
   };
-};
+}
 
diff --git a/app/javascript/flavours/glitch/actions/alerts.js b/app/javascript/flavours/glitch/actions/alerts.js
index 1670f9c10..0220b0af5 100644
--- a/app/javascript/flavours/glitch/actions/alerts.js
+++ b/app/javascript/flavours/glitch/actions/alerts.js
@@ -17,13 +17,13 @@ export function dismissAlert(alert) {
     type: ALERT_DISMISS,
     alert,
   };
-};
+}
 
 export function clearAlert() {
   return {
     type: ALERT_CLEAR,
   };
-};
+}
 
 export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage, message_values = undefined) {
   return {
@@ -32,7 +32,7 @@ export function showAlert(title = messages.unexpectedTitle, message = messages.u
     message,
     message_values,
   };
-};
+}
 
 export function showAlertForError(error, skipNotFound = false) {
   if (error.response) {
diff --git a/app/javascript/flavours/glitch/actions/blocks.js b/app/javascript/flavours/glitch/actions/blocks.js
index fd9881302..192aa3ce4 100644
--- a/app/javascript/flavours/glitch/actions/blocks.js
+++ b/app/javascript/flavours/glitch/actions/blocks.js
@@ -24,13 +24,13 @@ export function fetchBlocks() {
       dispatch(fetchRelationships(response.data.map(item => item.id)));
     }).catch(error => dispatch(fetchBlocksFail(error)));
   };
-};
+}
 
 export function fetchBlocksRequest() {
   return {
     type: BLOCKS_FETCH_REQUEST,
   };
-};
+}
 
 export function fetchBlocksSuccess(accounts, next) {
   return {
@@ -38,14 +38,14 @@ export function fetchBlocksSuccess(accounts, next) {
     accounts,
     next,
   };
-};
+}
 
 export function fetchBlocksFail(error) {
   return {
     type: BLOCKS_FETCH_FAIL,
     error,
   };
-};
+}
 
 export function expandBlocks() {
   return (dispatch, getState) => {
@@ -64,13 +64,13 @@ export function expandBlocks() {
       dispatch(fetchRelationships(response.data.map(item => item.id)));
     }).catch(error => dispatch(expandBlocksFail(error)));
   };
-};
+}
 
 export function expandBlocksRequest() {
   return {
     type: BLOCKS_EXPAND_REQUEST,
   };
-};
+}
 
 export function expandBlocksSuccess(accounts, next) {
   return {
@@ -78,14 +78,14 @@ export function expandBlocksSuccess(accounts, next) {
     accounts,
     next,
   };
-};
+}
 
 export function expandBlocksFail(error) {
   return {
     type: BLOCKS_EXPAND_FAIL,
     error,
   };
-};
+}
 
 export function initBlockModal(account) {
   return dispatch => {
diff --git a/app/javascript/flavours/glitch/actions/bookmarks.js b/app/javascript/flavours/glitch/actions/bookmarks.js
index 544ed2ff2..3c8eec546 100644
--- a/app/javascript/flavours/glitch/actions/bookmarks.js
+++ b/app/javascript/flavours/glitch/actions/bookmarks.js
@@ -25,13 +25,13 @@ export function fetchBookmarkedStatuses() {
       dispatch(fetchBookmarkedStatusesFail(error));
     });
   };
-};
+}
 
 export function fetchBookmarkedStatusesRequest() {
   return {
     type: BOOKMARKED_STATUSES_FETCH_REQUEST,
   };
-};
+}
 
 export function fetchBookmarkedStatusesSuccess(statuses, next) {
   return {
@@ -39,14 +39,14 @@ export function fetchBookmarkedStatusesSuccess(statuses, next) {
     statuses,
     next,
   };
-};
+}
 
 export function fetchBookmarkedStatusesFail(error) {
   return {
     type: BOOKMARKED_STATUSES_FETCH_FAIL,
     error,
   };
-};
+}
 
 export function expandBookmarkedStatuses() {
   return (dispatch, getState) => {
@@ -66,13 +66,13 @@ export function expandBookmarkedStatuses() {
       dispatch(expandBookmarkedStatusesFail(error));
     });
   };
-};
+}
 
 export function expandBookmarkedStatusesRequest() {
   return {
     type: BOOKMARKED_STATUSES_EXPAND_REQUEST,
   };
-};
+}
 
 export function expandBookmarkedStatusesSuccess(statuses, next) {
   return {
@@ -80,11 +80,11 @@ export function expandBookmarkedStatusesSuccess(statuses, next) {
     statuses,
     next,
   };
-};
+}
 
 export function expandBookmarkedStatusesFail(error) {
   return {
     type: BOOKMARKED_STATUSES_EXPAND_FAIL,
     error,
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/boosts.js b/app/javascript/flavours/glitch/actions/boosts.js
index 6e14065d6..c0f0f3acc 100644
--- a/app/javascript/flavours/glitch/actions/boosts.js
+++ b/app/javascript/flavours/glitch/actions/boosts.js
@@ -11,7 +11,7 @@ export function initBoostModal(props) {
 
     dispatch({
       type: BOOSTS_INIT_MODAL,
-      privacy
+      privacy,
     });
 
     dispatch(openModal('BOOST', props));
diff --git a/app/javascript/flavours/glitch/actions/columns.js b/app/javascript/flavours/glitch/actions/columns.js
index 9b87415fb..302c3f0f9 100644
--- a/app/javascript/flavours/glitch/actions/columns.js
+++ b/app/javascript/flavours/glitch/actions/columns.js
@@ -15,7 +15,7 @@ export function addColumn(id, params) {
 
     dispatch(saveSettings());
   };
-};
+}
 
 export function removeColumn(uuid) {
   return dispatch => {
@@ -26,7 +26,7 @@ export function removeColumn(uuid) {
 
     dispatch(saveSettings());
   };
-};
+}
 
 export function moveColumn(uuid, direction) {
   return dispatch => {
@@ -38,7 +38,7 @@ export function moveColumn(uuid, direction) {
 
     dispatch(saveSettings());
   };
-};
+}
 
 export function changeColumnParams(uuid, path, value) {
   return dispatch => {
diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js
index 267cff563..01f0f3666 100644
--- a/app/javascript/flavours/glitch/actions/compose.js
+++ b/app/javascript/flavours/glitch/actions/compose.js
@@ -101,20 +101,20 @@ export function setComposeToStatus(status, text, spoiler_text, content_type) {
     spoiler_text,
     content_type,
   };
-};
+}
 
 export function changeCompose(text) {
   return {
     type: COMPOSE_CHANGE,
     text: text,
   };
-};
+}
 
 export function cycleElefriendCompose() {
   return {
     type: COMPOSE_CYCLE_ELEFRIEND,
   };
-};
+}
 
 export function replyCompose(status, routerHistory) {
   return (dispatch, getState) => {
@@ -127,19 +127,19 @@ export function replyCompose(status, routerHistory) {
 
     ensureComposeIsVisible(getState, routerHistory);
   };
-};
+}
 
 export function cancelReplyCompose() {
   return {
     type: COMPOSE_REPLY_CANCEL,
   };
-};
+}
 
 export function resetCompose() {
   return {
     type: COMPOSE_RESET,
   };
-};
+}
 
 export function mentionCompose(account, routerHistory) {
   return (dispatch, getState) => {
@@ -150,7 +150,7 @@ export function mentionCompose(account, routerHistory) {
 
     ensureComposeIsVisible(getState, routerHistory);
   };
-};
+}
 
 export function directCompose(account, routerHistory) {
   return (dispatch, getState) => {
@@ -161,7 +161,7 @@ export function directCompose(account, routerHistory) {
 
     ensureComposeIsVisible(getState, routerHistory);
   };
-};
+}
 
 export function submitCompose(routerHistory) {
   return function (dispatch, getState) {
@@ -257,34 +257,34 @@ export function submitCompose(routerHistory) {
       dispatch(submitComposeFail(error));
     });
   };
-};
+}
 
 export function submitComposeRequest() {
   return {
     type: COMPOSE_SUBMIT_REQUEST,
   };
-};
+}
 
 export function submitComposeSuccess(status) {
   return {
     type: COMPOSE_SUBMIT_SUCCESS,
     status: status,
   };
-};
+}
 
 export function submitComposeFail(error) {
   return {
     type: COMPOSE_SUBMIT_FAIL,
     error: error,
   };
-};
+}
 
 export function doodleSet(options) {
   return {
     type: COMPOSE_DOODLE_SET,
     options: options,
   };
-};
+}
 
 export function uploadCompose(files) {
   return function (dispatch, getState) {
@@ -347,9 +347,9 @@ export function uploadCompose(files) {
           }
         });
       }).catch(error => dispatch(uploadComposeFail(error)));
-    };
+    }
   };
-};
+}
 
 export const uploadComposeProcessing = () => ({
   type: COMPOSE_UPLOAD_PROCESSING,
@@ -407,14 +407,14 @@ export function initMediaEditModal(id) {
 
     dispatch(openModal('FOCAL_POINT', { id }));
   };
-};
+}
 
 export function onChangeMediaDescription(description) {
   return {
     type: COMPOSE_CHANGE_MEDIA_DESCRIPTION,
     description,
   };
-};
+}
 
 export function onChangeMediaFocus(focusX, focusY) {
   return {
@@ -422,7 +422,7 @@ export function onChangeMediaFocus(focusX, focusY) {
     focusX,
     focusY,
   };
-};
+}
 
 export function changeUploadCompose(id, params) {
   return (dispatch, getState) => {
@@ -454,14 +454,14 @@ export function changeUploadCompose(id, params) {
       });
     }
   };
-};
+}
 
 export function changeUploadComposeRequest() {
   return {
     type: COMPOSE_UPLOAD_CHANGE_REQUEST,
     skipLoading: true,
   };
-};
+}
 
 export function changeUploadComposeSuccess(media, attached) {
   return {
@@ -470,7 +470,7 @@ export function changeUploadComposeSuccess(media, attached) {
     attached: attached,
     skipLoading: true,
   };
-};
+}
 
 export function changeUploadComposeFail(error) {
   return {
@@ -478,14 +478,14 @@ export function changeUploadComposeFail(error) {
     error: error,
     skipLoading: true,
   };
-};
+}
 
 export function uploadComposeRequest() {
   return {
     type: COMPOSE_UPLOAD_REQUEST,
     skipLoading: true,
   };
-};
+}
 
 export function uploadComposeProgress(loaded, total) {
   return {
@@ -493,7 +493,7 @@ export function uploadComposeProgress(loaded, total) {
     loaded: loaded,
     total: total,
   };
-};
+}
 
 export function uploadComposeSuccess(media, file) {
   return {
@@ -502,7 +502,7 @@ export function uploadComposeSuccess(media, file) {
     file: file,
     skipLoading: true,
   };
-};
+}
 
 export function uploadComposeFail(error) {
   return {
@@ -510,14 +510,14 @@ export function uploadComposeFail(error) {
     error: error,
     skipLoading: true,
   };
-};
+}
 
 export function undoUploadCompose(media_id) {
   return {
     type: COMPOSE_UPLOAD_UNDO,
     media_id: media_id,
   };
-};
+}
 
 export function clearComposeSuggestions() {
   if (fetchComposeSuggestionsAccountsController) {
@@ -526,7 +526,7 @@ export function clearComposeSuggestions() {
   return {
     type: COMPOSE_SUGGESTIONS_CLEAR,
   };
-};
+}
 
 const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
   if (fetchComposeSuggestionsAccountsController) {
@@ -603,7 +603,7 @@ export function fetchComposeSuggestions(token) {
       break;
     }
   };
-};
+}
 
 export function readyComposeSuggestionsEmojis(token, emojis) {
   return {
@@ -611,7 +611,7 @@ export function readyComposeSuggestionsEmojis(token, emojis) {
     token,
     emojis,
   };
-};
+}
 
 export function readyComposeSuggestionsAccounts(token, accounts) {
   return {
@@ -619,7 +619,7 @@ export function readyComposeSuggestionsAccounts(token, accounts) {
     token,
     accounts,
   };
-};
+}
 
 export const readyComposeSuggestionsTags = (token, tags) => ({
   type: COMPOSE_SUGGESTIONS_READY,
@@ -659,7 +659,7 @@ export function selectComposeSuggestion(position, token, suggestion, path) {
       });
     }
   };
-};
+}
 
 export function updateSuggestionTags(token) {
   return {
@@ -707,13 +707,13 @@ export function mountCompose() {
   return {
     type: COMPOSE_MOUNT,
   };
-};
+}
 
 export function unmountCompose() {
   return {
     type: COMPOSE_UNMOUNT,
   };
-};
+}
 
 export function changeComposeAdvancedOption(option, value) {
   return {
@@ -727,7 +727,7 @@ export function changeComposeSensitivity() {
   return {
     type: COMPOSE_SENSITIVITY_CHANGE,
   };
-};
+}
 
 export const changeComposeLanguage = language => ({
   type: COMPOSE_LANGUAGE_CHANGE,
@@ -738,28 +738,28 @@ export function changeComposeSpoilerness() {
   return {
     type: COMPOSE_SPOILERNESS_CHANGE,
   };
-};
+}
 
 export function changeComposeSpoilerText(text) {
   return {
     type: COMPOSE_SPOILER_TEXT_CHANGE,
     text,
   };
-};
+}
 
 export function changeComposeVisibility(value) {
   return {
     type: COMPOSE_VISIBILITY_CHANGE,
     value,
   };
-};
+}
 
 export function changeComposeContentType(value) {
   return {
     type: COMPOSE_CONTENT_TYPE_CHANGE,
     value,
   };
-};
+}
 
 export function insertEmojiCompose(position, emoji) {
   return {
@@ -767,26 +767,26 @@ export function insertEmojiCompose(position, emoji) {
     position,
     emoji,
   };
-};
+}
 
 export function addPoll() {
   return {
     type: COMPOSE_POLL_ADD,
   };
-};
+}
 
 export function removePoll() {
   return {
     type: COMPOSE_POLL_REMOVE,
   };
-};
+}
 
 export function addPollOption(title) {
   return {
     type: COMPOSE_POLL_OPTION_ADD,
     title,
   };
-};
+}
 
 export function changePollOption(index, title) {
   return {
@@ -794,14 +794,14 @@ export function changePollOption(index, title) {
     index,
     title,
   };
-};
+}
 
 export function removePollOption(index) {
   return {
     type: COMPOSE_POLL_OPTION_REMOVE,
     index,
   };
-};
+}
 
 export function changePollSettings(expiresIn, isMultiple) {
   return {
@@ -809,4 +809,4 @@ export function changePollSettings(expiresIn, isMultiple) {
     expiresIn,
     isMultiple,
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/custom_emojis.js b/app/javascript/flavours/glitch/actions/custom_emojis.js
index 7b7d0091b..9ec8156b1 100644
--- a/app/javascript/flavours/glitch/actions/custom_emojis.js
+++ b/app/javascript/flavours/glitch/actions/custom_emojis.js
@@ -14,14 +14,14 @@ export function fetchCustomEmojis() {
       dispatch(fetchCustomEmojisFail(error));
     });
   };
-};
+}
 
 export function fetchCustomEmojisRequest() {
   return {
     type: CUSTOM_EMOJIS_FETCH_REQUEST,
     skipLoading: true,
   };
-};
+}
 
 export function fetchCustomEmojisSuccess(custom_emojis) {
   return {
@@ -29,7 +29,7 @@ export function fetchCustomEmojisSuccess(custom_emojis) {
     custom_emojis,
     skipLoading: true,
   };
-};
+}
 
 export function fetchCustomEmojisFail(error) {
   return {
@@ -37,4 +37,4 @@ export function fetchCustomEmojisFail(error) {
     error,
     skipLoading: true,
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/domain_blocks.js b/app/javascript/flavours/glitch/actions/domain_blocks.js
index 34a33a654..d06de20a2 100644
--- a/app/javascript/flavours/glitch/actions/domain_blocks.js
+++ b/app/javascript/flavours/glitch/actions/domain_blocks.js
@@ -29,14 +29,14 @@ export function blockDomain(domain) {
       dispatch(blockDomainFail(domain, err));
     });
   };
-};
+}
 
 export function blockDomainRequest(domain) {
   return {
     type: DOMAIN_BLOCK_REQUEST,
     domain,
   };
-};
+}
 
 export function blockDomainSuccess(domain, accounts) {
   return {
@@ -44,7 +44,7 @@ export function blockDomainSuccess(domain, accounts) {
     domain,
     accounts,
   };
-};
+}
 
 export function blockDomainFail(domain, error) {
   return {
@@ -52,7 +52,7 @@ export function blockDomainFail(domain, error) {
     domain,
     error,
   };
-};
+}
 
 export function unblockDomain(domain) {
   return (dispatch, getState) => {
@@ -66,14 +66,14 @@ export function unblockDomain(domain) {
       dispatch(unblockDomainFail(domain, err));
     });
   };
-};
+}
 
 export function unblockDomainRequest(domain) {
   return {
     type: DOMAIN_UNBLOCK_REQUEST,
     domain,
   };
-};
+}
 
 export function unblockDomainSuccess(domain, accounts) {
   return {
@@ -81,7 +81,7 @@ export function unblockDomainSuccess(domain, accounts) {
     domain,
     accounts,
   };
-};
+}
 
 export function unblockDomainFail(domain, error) {
   return {
@@ -89,7 +89,7 @@ export function unblockDomainFail(domain, error) {
     domain,
     error,
   };
-};
+}
 
 export function fetchDomainBlocks() {
   return (dispatch, getState) => {
@@ -102,13 +102,13 @@ export function fetchDomainBlocks() {
       dispatch(fetchDomainBlocksFail(err));
     });
   };
-};
+}
 
 export function fetchDomainBlocksRequest() {
   return {
     type: DOMAIN_BLOCKS_FETCH_REQUEST,
   };
-};
+}
 
 export function fetchDomainBlocksSuccess(domains, next) {
   return {
@@ -116,14 +116,14 @@ export function fetchDomainBlocksSuccess(domains, next) {
     domains,
     next,
   };
-};
+}
 
 export function fetchDomainBlocksFail(error) {
   return {
     type: DOMAIN_BLOCKS_FETCH_FAIL,
     error,
   };
-};
+}
 
 export function expandDomainBlocks() {
   return (dispatch, getState) => {
@@ -142,13 +142,13 @@ export function expandDomainBlocks() {
       dispatch(expandDomainBlocksFail(err));
     });
   };
-};
+}
 
 export function expandDomainBlocksRequest() {
   return {
     type: DOMAIN_BLOCKS_EXPAND_REQUEST,
   };
-};
+}
 
 export function expandDomainBlocksSuccess(domains, next) {
   return {
@@ -156,11 +156,11 @@ export function expandDomainBlocksSuccess(domains, next) {
     domains,
     next,
   };
-};
+}
 
 export function expandDomainBlocksFail(error) {
   return {
     type: DOMAIN_BLOCKS_EXPAND_FAIL,
     error,
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/emojis.js b/app/javascript/flavours/glitch/actions/emojis.js
index 7cd9d4b7b..3b5d53996 100644
--- a/app/javascript/flavours/glitch/actions/emojis.js
+++ b/app/javascript/flavours/glitch/actions/emojis.js
@@ -11,4 +11,4 @@ export function useEmoji(emoji) {
 
     dispatch(saveSettings());
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/favourites.js b/app/javascript/flavours/glitch/actions/favourites.js
index 9448b1efe..7388e0c58 100644
--- a/app/javascript/flavours/glitch/actions/favourites.js
+++ b/app/javascript/flavours/glitch/actions/favourites.js
@@ -25,14 +25,14 @@ export function fetchFavouritedStatuses() {
       dispatch(fetchFavouritedStatusesFail(error));
     });
   };
-};
+}
 
 export function fetchFavouritedStatusesRequest() {
   return {
     type: FAVOURITED_STATUSES_FETCH_REQUEST,
     skipLoading: true,
   };
-};
+}
 
 export function fetchFavouritedStatusesSuccess(statuses, next) {
   return {
@@ -41,7 +41,7 @@ export function fetchFavouritedStatusesSuccess(statuses, next) {
     next,
     skipLoading: true,
   };
-};
+}
 
 export function fetchFavouritedStatusesFail(error) {
   return {
@@ -49,7 +49,7 @@ export function fetchFavouritedStatusesFail(error) {
     error,
     skipLoading: true,
   };
-};
+}
 
 export function expandFavouritedStatuses() {
   return (dispatch, getState) => {
@@ -69,13 +69,13 @@ export function expandFavouritedStatuses() {
       dispatch(expandFavouritedStatusesFail(error));
     });
   };
-};
+}
 
 export function expandFavouritedStatusesRequest() {
   return {
     type: FAVOURITED_STATUSES_EXPAND_REQUEST,
   };
-};
+}
 
 export function expandFavouritedStatusesSuccess(statuses, next) {
   return {
@@ -83,11 +83,11 @@ export function expandFavouritedStatusesSuccess(statuses, next) {
     statuses,
     next,
   };
-};
+}
 
 export function expandFavouritedStatusesFail(error) {
   return {
     type: FAVOURITED_STATUSES_EXPAND_FAIL,
     error,
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/height_cache.js b/app/javascript/flavours/glitch/actions/height_cache.js
index 4c752993f..a8645410c 100644
--- a/app/javascript/flavours/glitch/actions/height_cache.js
+++ b/app/javascript/flavours/glitch/actions/height_cache.js
@@ -8,10 +8,10 @@ export function setHeight (key, id, height) {
     id,
     height,
   };
-};
+}
 
 export function clearHeight () {
   return {
     type: HEIGHT_CACHE_CLEAR,
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/interactions.js b/app/javascript/flavours/glitch/actions/interactions.js
index 225ee7eb2..c7b552a65 100644
--- a/app/javascript/flavours/glitch/actions/interactions.js
+++ b/app/javascript/flavours/glitch/actions/interactions.js
@@ -54,7 +54,7 @@ export function reblog(status, visibility) {
       dispatch(reblogFail(status, error));
     });
   };
-};
+}
 
 export function unreblog(status) {
   return (dispatch, getState) => {
@@ -67,21 +67,21 @@ export function unreblog(status) {
       dispatch(unreblogFail(status, error));
     });
   };
-};
+}
 
 export function reblogRequest(status) {
   return {
     type: REBLOG_REQUEST,
     status: status,
   };
-};
+}
 
 export function reblogSuccess(status) {
   return {
     type: REBLOG_SUCCESS,
     status: status,
   };
-};
+}
 
 export function reblogFail(status, error) {
   return {
@@ -89,21 +89,21 @@ export function reblogFail(status, error) {
     status: status,
     error: error,
   };
-};
+}
 
 export function unreblogRequest(status) {
   return {
     type: UNREBLOG_REQUEST,
     status: status,
   };
-};
+}
 
 export function unreblogSuccess(status) {
   return {
     type: UNREBLOG_SUCCESS,
     status: status,
   };
-};
+}
 
 export function unreblogFail(status, error) {
   return {
@@ -111,7 +111,7 @@ export function unreblogFail(status, error) {
     status: status,
     error: error,
   };
-};
+}
 
 export function favourite(status) {
   return function (dispatch, getState) {
@@ -124,7 +124,7 @@ export function favourite(status) {
       dispatch(favouriteFail(status, error));
     });
   };
-};
+}
 
 export function unfavourite(status) {
   return (dispatch, getState) => {
@@ -137,21 +137,21 @@ export function unfavourite(status) {
       dispatch(unfavouriteFail(status, error));
     });
   };
-};
+}
 
 export function favouriteRequest(status) {
   return {
     type: FAVOURITE_REQUEST,
     status: status,
   };
-};
+}
 
 export function favouriteSuccess(status) {
   return {
     type: FAVOURITE_SUCCESS,
     status: status,
   };
-};
+}
 
 export function favouriteFail(status, error) {
   return {
@@ -159,21 +159,21 @@ export function favouriteFail(status, error) {
     status: status,
     error: error,
   };
-};
+}
 
 export function unfavouriteRequest(status) {
   return {
     type: UNFAVOURITE_REQUEST,
     status: status,
   };
-};
+}
 
 export function unfavouriteSuccess(status) {
   return {
     type: UNFAVOURITE_SUCCESS,
     status: status,
   };
-};
+}
 
 export function unfavouriteFail(status, error) {
   return {
@@ -181,7 +181,7 @@ export function unfavouriteFail(status, error) {
     status: status,
     error: error,
   };
-};
+}
 
 export function bookmark(status) {
   return function (dispatch, getState) {
@@ -194,7 +194,7 @@ export function bookmark(status) {
       dispatch(bookmarkFail(status, error));
     });
   };
-};
+}
 
 export function unbookmark(status) {
   return (dispatch, getState) => {
@@ -207,21 +207,21 @@ export function unbookmark(status) {
       dispatch(unbookmarkFail(status, error));
     });
   };
-};
+}
 
 export function bookmarkRequest(status) {
   return {
     type: BOOKMARK_REQUEST,
     status: status,
   };
-};
+}
 
 export function bookmarkSuccess(status) {
   return {
     type: BOOKMARK_SUCCESS,
     status: status,
   };
-};
+}
 
 export function bookmarkFail(status, error) {
   return {
@@ -229,21 +229,21 @@ export function bookmarkFail(status, error) {
     status: status,
     error: error,
   };
-};
+}
 
 export function unbookmarkRequest(status) {
   return {
     type: UNBOOKMARK_REQUEST,
     status: status,
   };
-};
+}
 
 export function unbookmarkSuccess(status) {
   return {
     type: UNBOOKMARK_SUCCESS,
     status: status,
   };
-};
+}
 
 export function unbookmarkFail(status, error) {
   return {
@@ -251,7 +251,7 @@ export function unbookmarkFail(status, error) {
     status: status,
     error: error,
   };
-};
+}
 
 export function fetchReblogs(id) {
   return (dispatch, getState) => {
@@ -264,14 +264,14 @@ export function fetchReblogs(id) {
       dispatch(fetchReblogsFail(id, error));
     });
   };
-};
+}
 
 export function fetchReblogsRequest(id) {
   return {
     type: REBLOGS_FETCH_REQUEST,
     id,
   };
-};
+}
 
 export function fetchReblogsSuccess(id, accounts) {
   return {
@@ -279,14 +279,14 @@ export function fetchReblogsSuccess(id, accounts) {
     id,
     accounts,
   };
-};
+}
 
 export function fetchReblogsFail(id, error) {
   return {
     type: REBLOGS_FETCH_FAIL,
     error,
   };
-};
+}
 
 export function fetchFavourites(id) {
   return (dispatch, getState) => {
@@ -299,14 +299,14 @@ export function fetchFavourites(id) {
       dispatch(fetchFavouritesFail(id, error));
     });
   };
-};
+}
 
 export function fetchFavouritesRequest(id) {
   return {
     type: FAVOURITES_FETCH_REQUEST,
     id,
   };
-};
+}
 
 export function fetchFavouritesSuccess(id, accounts) {
   return {
@@ -314,14 +314,14 @@ export function fetchFavouritesSuccess(id, accounts) {
     id,
     accounts,
   };
-};
+}
 
 export function fetchFavouritesFail(id, error) {
   return {
     type: FAVOURITES_FETCH_FAIL,
     error,
   };
-};
+}
 
 export function pin(status) {
   return (dispatch, getState) => {
@@ -334,21 +334,21 @@ export function pin(status) {
       dispatch(pinFail(status, error));
     });
   };
-};
+}
 
 export function pinRequest(status) {
   return {
     type: PIN_REQUEST,
     status,
   };
-};
+}
 
 export function pinSuccess(status) {
   return {
     type: PIN_SUCCESS,
     status,
   };
-};
+}
 
 export function pinFail(status, error) {
   return {
@@ -356,7 +356,7 @@ export function pinFail(status, error) {
     status,
     error,
   };
-};
+}
 
 export function unpin (status) {
   return (dispatch, getState) => {
@@ -369,21 +369,21 @@ export function unpin (status) {
       dispatch(unpinFail(status, error));
     });
   };
-};
+}
 
 export function unpinRequest(status) {
   return {
     type: UNPIN_REQUEST,
     status,
   };
-};
+}
 
 export function unpinSuccess(status) {
   return {
     type: UNPIN_SUCCESS,
     status,
   };
-};
+}
 
 export function unpinFail(status, error) {
   return {
@@ -391,4 +391,4 @@ export function unpinFail(status, error) {
     status,
     error,
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/local_settings.js b/app/javascript/flavours/glitch/actions/local_settings.js
index a4a928611..adf7fd2ab 100644
--- a/app/javascript/flavours/glitch/actions/local_settings.js
+++ b/app/javascript/flavours/glitch/actions/local_settings.js
@@ -33,14 +33,14 @@ export function checkDeprecatedLocalSettings() {
       }));
     }
   };
-};
+}
 
 export function clearDeprecatedLocalSettings() {
   return (dispatch) => {
     dispatch(deleteLocalSetting(['content_warnings', 'auto_unfold']));
     dispatch(deleteLocalSetting(['swipe_to_change_columns']));
   };
-};
+}
 
 export function changeLocalSetting(key, value) {
   return dispatch => {
@@ -52,7 +52,7 @@ export function changeLocalSetting(key, value) {
 
     dispatch(saveLocalSettings());
   };
-};
+}
 
 export function deleteLocalSetting(key) {
   return dispatch => {
@@ -63,7 +63,7 @@ export function deleteLocalSetting(key) {
 
     dispatch(saveLocalSettings());
   };
-};
+}
 
 //  __TODO :__
 //  Right now `saveLocalSettings()` doesn't keep track of which user
@@ -74,4 +74,4 @@ export function saveLocalSettings() {
     const localSettings = getState().get('local_settings').toJS();
     localStorage.setItem('mastodon-settings', JSON.stringify(localSettings));
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/markers.js b/app/javascript/flavours/glitch/actions/markers.js
index 3b6a76bc4..dfd701cbb 100644
--- a/app/javascript/flavours/glitch/actions/markers.js
+++ b/app/javascript/flavours/glitch/actions/markers.js
@@ -101,7 +101,7 @@ export function submitMarkersSuccess({ home, notifications }) {
     home: (home || {}).last_read_id,
     notifications: (notifications || {}).last_read_id,
   };
-};
+}
 
 export function submitMarkers(params = {}) {
   const result = (dispatch, getState) => debouncedSubmitMarkers(dispatch, getState);
@@ -111,7 +111,7 @@ export function submitMarkers(params = {}) {
   }
 
   return result;
-};
+}
 
 export const fetchMarkers = () => (dispatch, getState) => {
   const params = { timeline: ['notifications'] };
@@ -130,7 +130,7 @@ export function fetchMarkersRequest() {
     type: MARKERS_FETCH_REQUEST,
     skipLoading: true,
   };
-};
+}
 
 export function fetchMarkersSuccess(markers) {
   return {
@@ -138,7 +138,7 @@ export function fetchMarkersSuccess(markers) {
     markers,
     skipLoading: true,
   };
-};
+}
 
 export function fetchMarkersFail(error) {
   return {
@@ -147,4 +147,4 @@ export function fetchMarkersFail(error) {
     skipLoading: true,
     skipAlert: true,
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/modal.js b/app/javascript/flavours/glitch/actions/modal.js
index 3e576fab8..ef2ae0e4c 100644
--- a/app/javascript/flavours/glitch/actions/modal.js
+++ b/app/javascript/flavours/glitch/actions/modal.js
@@ -7,7 +7,7 @@ export function openModal(type, props) {
     modalType: type,
     modalProps: props,
   };
-};
+}
 
 export function closeModal(type, options = { ignoreFocus: false }) {
   return {
@@ -15,4 +15,4 @@ export function closeModal(type, options = { ignoreFocus: false }) {
     modalType: type,
     ignoreFocus: options.ignoreFocus,
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/mutes.js b/app/javascript/flavours/glitch/actions/mutes.js
index 1ccf9592f..aa47d1464 100644
--- a/app/javascript/flavours/glitch/actions/mutes.js
+++ b/app/javascript/flavours/glitch/actions/mutes.js
@@ -26,13 +26,13 @@ export function fetchMutes() {
       dispatch(fetchRelationships(response.data.map(item => item.id)));
     }).catch(error => dispatch(fetchMutesFail(error)));
   };
-};
+}
 
 export function fetchMutesRequest() {
   return {
     type: MUTES_FETCH_REQUEST,
   };
-};
+}
 
 export function fetchMutesSuccess(accounts, next) {
   return {
@@ -40,14 +40,14 @@ export function fetchMutesSuccess(accounts, next) {
     accounts,
     next,
   };
-};
+}
 
 export function fetchMutesFail(error) {
   return {
     type: MUTES_FETCH_FAIL,
     error,
   };
-};
+}
 
 export function expandMutes() {
   return (dispatch, getState) => {
@@ -66,13 +66,13 @@ export function expandMutes() {
       dispatch(fetchRelationships(response.data.map(item => item.id)));
     }).catch(error => dispatch(expandMutesFail(error)));
   };
-};
+}
 
 export function expandMutesRequest() {
   return {
     type: MUTES_EXPAND_REQUEST,
   };
-};
+}
 
 export function expandMutesSuccess(accounts, next) {
   return {
@@ -80,14 +80,14 @@ export function expandMutesSuccess(accounts, next) {
     accounts,
     next,
   };
-};
+}
 
 export function expandMutesFail(error) {
   return {
     type: MUTES_EXPAND_FAIL,
     error,
   };
-};
+}
 
 export function initMuteModal(account) {
   return dispatch => {
diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js
index 158a5b7e4..989bc4144 100644
--- a/app/javascript/flavours/glitch/actions/notifications.js
+++ b/app/javascript/flavours/glitch/actions/notifications.js
@@ -129,7 +129,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
       });
     }
   };
-};
+}
 
 const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
 
@@ -209,14 +209,14 @@ export function expandNotifications({ maxId, forceLoad } = {}, done = noOp) {
       done();
     });
   };
-};
+}
 
 export function expandNotificationsRequest(isLoadingMore) {
   return {
     type: NOTIFICATIONS_EXPAND_REQUEST,
     skipLoading: !isLoadingMore,
   };
-};
+}
 
 export function expandNotificationsSuccess(notifications, next, isLoadingMore, isLoadingRecent, usePendingItems) {
   return {
@@ -227,7 +227,7 @@ export function expandNotificationsSuccess(notifications, next, isLoadingMore, i
     usePendingItems,
     skipLoading: !isLoadingMore,
   };
-};
+}
 
 export function expandNotificationsFail(error, isLoadingMore) {
   return {
@@ -236,7 +236,7 @@ export function expandNotificationsFail(error, isLoadingMore) {
     skipLoading: !isLoadingMore,
     skipAlert: !isLoadingMore || error.name === 'AbortError',
   };
-};
+}
 
 export function clearNotifications() {
   return (dispatch, getState) => {
@@ -246,14 +246,14 @@ export function clearNotifications() {
 
     api(getState).post('/api/v1/notifications/clear');
   };
-};
+}
 
 export function scrollTopNotifications(top) {
   return {
     type: NOTIFICATIONS_SCROLL_TOP,
     top,
   };
-};
+}
 
 export function deleteMarkedNotifications() {
   return (dispatch, getState) => {
@@ -277,33 +277,33 @@ export function deleteMarkedNotifications() {
       dispatch(deleteMarkedNotificationsFail(error));
     });
   };
-};
+}
 
 export function enterNotificationClearingMode(yes) {
   return {
     type: NOTIFICATIONS_ENTER_CLEARING_MODE,
     yes: yes,
   };
-};
+}
 
 export function markAllNotifications(yes) {
   return {
     type: NOTIFICATIONS_MARK_ALL_FOR_DELETE,
     yes: yes, // true, false or null. null = invert
   };
-};
+}
 
 export function deleteMarkedNotificationsRequest() {
   return {
     type: NOTIFICATIONS_DELETE_MARKED_REQUEST,
   };
-};
+}
 
 export function deleteMarkedNotificationsFail() {
   return {
     type: NOTIFICATIONS_DELETE_MARKED_FAIL,
   };
-};
+}
 
 export function markNotificationForDelete(id, yes) {
   return {
@@ -311,32 +311,32 @@ export function markNotificationForDelete(id, yes) {
     id: id,
     yes: yes,
   };
-};
+}
 
 export function deleteMarkedNotificationsSuccess() {
   return {
     type: NOTIFICATIONS_DELETE_MARKED_SUCCESS,
   };
-};
+}
 
 export function mountNotifications() {
   return {
     type: NOTIFICATIONS_MOUNT,
   };
-};
+}
 
 export function unmountNotifications() {
   return {
     type: NOTIFICATIONS_UNMOUNT,
   };
-};
+}
 
 export function notificationsSetVisibility(visibility) {
   return {
     type: NOTIFICATIONS_SET_VISIBILITY,
     visibility: visibility,
   };
-};
+}
 
 export function setFilter (filterType) {
   return dispatch => {
@@ -348,13 +348,13 @@ export function setFilter (filterType) {
     dispatch(expandNotifications({ forceLoad: true }));
     dispatch(saveSettings());
   };
-};
+}
 
 export function markNotificationsAsRead() {
   return {
     type: NOTIFICATIONS_MARK_AS_READ,
   };
-};
+}
 
 // Browser support
 export function setupBrowserNotifications() {
@@ -379,7 +379,7 @@ export function requestBrowserPermission(callback = noOp) {
       callback(permission);
     });
   };
-};
+}
 
 export function setBrowserSupport (value) {
   return {
diff --git a/app/javascript/flavours/glitch/actions/onboarding.js b/app/javascript/flavours/glitch/actions/onboarding.js
index a161c50ef..5038b7eb6 100644
--- a/app/javascript/flavours/glitch/actions/onboarding.js
+++ b/app/javascript/flavours/glitch/actions/onboarding.js
@@ -11,4 +11,4 @@ export function showOnboardingOnce() {
       dispatch(saveSettings());
     }
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/pin_statuses.js b/app/javascript/flavours/glitch/actions/pin_statuses.js
index 0926978ac..d8c0a1373 100644
--- a/app/javascript/flavours/glitch/actions/pin_statuses.js
+++ b/app/javascript/flavours/glitch/actions/pin_statuses.js
@@ -18,13 +18,13 @@ export function fetchPinnedStatuses() {
       dispatch(fetchPinnedStatusesFail(error));
     });
   };
-};
+}
 
 export function fetchPinnedStatusesRequest() {
   return {
     type: PINNED_STATUSES_FETCH_REQUEST,
   };
-};
+}
 
 export function fetchPinnedStatusesSuccess(statuses, next) {
   return {
@@ -32,11 +32,11 @@ export function fetchPinnedStatusesSuccess(statuses, next) {
     statuses,
     next,
   };
-};
+}
 
 export function fetchPinnedStatusesFail(error) {
   return {
     type: PINNED_STATUSES_FETCH_FAIL,
     error,
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/search.js b/app/javascript/flavours/glitch/actions/search.js
index f21c0058b..0012808e5 100644
--- a/app/javascript/flavours/glitch/actions/search.js
+++ b/app/javascript/flavours/glitch/actions/search.js
@@ -19,13 +19,13 @@ export function changeSearch(value) {
     type: SEARCH_CHANGE,
     value,
   };
-};
+}
 
 export function clearSearch() {
   return {
     type: SEARCH_CLEAR,
   };
-};
+}
 
 export function submitSearch() {
   return (dispatch, getState) => {
@@ -60,13 +60,13 @@ export function submitSearch() {
       dispatch(fetchSearchFail(error));
     });
   };
-};
+}
 
 export function fetchSearchRequest() {
   return {
     type: SEARCH_FETCH_REQUEST,
   };
-};
+}
 
 export function fetchSearchSuccess(results, searchTerm) {
   return {
@@ -74,14 +74,14 @@ export function fetchSearchSuccess(results, searchTerm) {
     results,
     searchTerm,
   };
-};
+}
 
 export function fetchSearchFail(error) {
   return {
     type: SEARCH_FETCH_FAIL,
     error,
   };
-};
+}
 
 export const expandSearch = type => (dispatch, getState) => {
   const value  = getState().getIn(['search', 'value']);
diff --git a/app/javascript/flavours/glitch/actions/settings.js b/app/javascript/flavours/glitch/actions/settings.js
index 5634a11ef..60f0abf95 100644
--- a/app/javascript/flavours/glitch/actions/settings.js
+++ b/app/javascript/flavours/glitch/actions/settings.js
@@ -15,7 +15,7 @@ export function changeSetting(path, value) {
 
     dispatch(saveSettings());
   };
-};
+}
 
 const debouncedSave = debounce((dispatch, getState) => {
   if (getState().getIn(['settings', 'saved'])) {
@@ -31,4 +31,4 @@ const debouncedSave = debounce((dispatch, getState) => {
 
 export function saveSettings() {
   return (dispatch, getState) => debouncedSave(dispatch, getState);
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/statuses.js b/app/javascript/flavours/glitch/actions/statuses.js
index efb4cc33b..487cd6988 100644
--- a/app/javascript/flavours/glitch/actions/statuses.js
+++ b/app/javascript/flavours/glitch/actions/statuses.js
@@ -45,7 +45,7 @@ export function fetchStatusRequest(id, skipLoading) {
     id,
     skipLoading,
   };
-};
+}
 
 export function fetchStatus(id, forceFetch = false) {
   return (dispatch, getState) => {
@@ -66,14 +66,14 @@ export function fetchStatus(id, forceFetch = false) {
       dispatch(fetchStatusFail(id, error, skipLoading));
     });
   };
-};
+}
 
 export function fetchStatusSuccess(skipLoading) {
   return {
     type: STATUS_FETCH_SUCCESS,
     skipLoading,
   };
-};
+}
 
 export function fetchStatusFail(id, error, skipLoading) {
   return {
@@ -83,7 +83,7 @@ export function fetchStatusFail(id, error, skipLoading) {
     skipLoading,
     skipAlert: true,
   };
-};
+}
 
 export function redraft(status, raw_text, content_type) {
   return {
@@ -92,7 +92,7 @@ export function redraft(status, raw_text, content_type) {
     raw_text,
     content_type,
   };
-};
+}
 
 export const editStatus = (id, routerHistory) => (dispatch, getState) => {
   let status = getState().getIn(['statuses', id]);
@@ -148,21 +148,21 @@ export function deleteStatus(id, routerHistory, withRedraft = false) {
       dispatch(deleteStatusFail(id, error));
     });
   };
-};
+}
 
 export function deleteStatusRequest(id) {
   return {
     type: STATUS_DELETE_REQUEST,
     id: id,
   };
-};
+}
 
 export function deleteStatusSuccess(id) {
   return {
     type: STATUS_DELETE_SUCCESS,
     id: id,
   };
-};
+}
 
 export function deleteStatusFail(id, error) {
   return {
@@ -170,7 +170,7 @@ export function deleteStatusFail(id, error) {
     id: id,
     error: error,
   };
-};
+}
 
 export const updateStatus = status => dispatch =>
   dispatch(importFetchedStatus(status));
@@ -191,14 +191,14 @@ export function fetchContext(id) {
       dispatch(fetchContextFail(id, error));
     });
   };
-};
+}
 
 export function fetchContextRequest(id) {
   return {
     type: CONTEXT_FETCH_REQUEST,
     id,
   };
-};
+}
 
 export function fetchContextSuccess(id, ancestors, descendants) {
   return {
@@ -208,7 +208,7 @@ export function fetchContextSuccess(id, ancestors, descendants) {
     descendants,
     statuses: ancestors.concat(descendants),
   };
-};
+}
 
 export function fetchContextFail(id, error) {
   return {
@@ -217,7 +217,7 @@ export function fetchContextFail(id, error) {
     error,
     skipAlert: true,
   };
-};
+}
 
 export function muteStatus(id) {
   return (dispatch, getState) => {
@@ -229,21 +229,21 @@ export function muteStatus(id) {
       dispatch(muteStatusFail(id, error));
     });
   };
-};
+}
 
 export function muteStatusRequest(id) {
   return {
     type: STATUS_MUTE_REQUEST,
     id,
   };
-};
+}
 
 export function muteStatusSuccess(id) {
   return {
     type: STATUS_MUTE_SUCCESS,
     id,
   };
-};
+}
 
 export function muteStatusFail(id, error) {
   return {
@@ -251,7 +251,7 @@ export function muteStatusFail(id, error) {
     id,
     error,
   };
-};
+}
 
 export function unmuteStatus(id) {
   return (dispatch, getState) => {
@@ -263,21 +263,21 @@ export function unmuteStatus(id) {
       dispatch(unmuteStatusFail(id, error));
     });
   };
-};
+}
 
 export function unmuteStatusRequest(id) {
   return {
     type: STATUS_UNMUTE_REQUEST,
     id,
   };
-};
+}
 
 export function unmuteStatusSuccess(id) {
   return {
     type: STATUS_UNMUTE_SUCCESS,
     id,
   };
-};
+}
 
 export function unmuteStatusFail(id, error) {
   return {
@@ -285,7 +285,7 @@ export function unmuteStatusFail(id, error) {
     id,
     error,
   };
-};
+}
 
 export function hideStatus(ids) {
   if (!Array.isArray(ids)) {
@@ -296,7 +296,7 @@ export function hideStatus(ids) {
     type: STATUS_HIDE,
     ids,
   };
-};
+}
 
 export function revealStatus(ids) {
   if (!Array.isArray(ids)) {
@@ -307,7 +307,7 @@ export function revealStatus(ids) {
     type: STATUS_REVEAL,
     ids,
   };
-};
+}
 
 export function toggleStatusCollapse(id, isCollapsed) {
   return {
@@ -315,7 +315,7 @@ export function toggleStatusCollapse(id, isCollapsed) {
     id,
     isCollapsed,
   };
-};
+}
 
 export const translateStatus = id => (dispatch, getState) => {
   dispatch(translateStatusRequest(id));
diff --git a/app/javascript/flavours/glitch/actions/store.js b/app/javascript/flavours/glitch/actions/store.js
index 9dbc0b214..137b68e22 100644
--- a/app/javascript/flavours/glitch/actions/store.js
+++ b/app/javascript/flavours/glitch/actions/store.js
@@ -18,7 +18,7 @@ const applyMigrations = (state) => {
       if (state.getIn(['settings', 'notifications', 'showUnread']) !== false) {
         state.setIn(['settings', 'notifications', 'showUnread'], state.getIn(['local_settings', 'notifications', 'show_unread']));
       }
-      state.removeIn(['local_settings', 'notifications', 'show_unread'])
+      state.removeIn(['local_settings', 'notifications', 'show_unread']);
     }
   });
 };
@@ -36,4 +36,4 @@ export function hydrateStore(rawState) {
     dispatch(importFetchedAccounts(Object.values(rawState.accounts)));
     dispatch(saveSettings());
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/actions/suggestions.js b/app/javascript/flavours/glitch/actions/suggestions.js
index 1f1116e75..9e8cd1ea4 100644
--- a/app/javascript/flavours/glitch/actions/suggestions.js
+++ b/app/javascript/flavours/glitch/actions/suggestions.js
@@ -21,14 +21,14 @@ export function fetchSuggestions(withRelationships = false) {
       }
     }).catch(error => dispatch(fetchSuggestionsFail(error)));
   };
-};
+}
 
 export function fetchSuggestionsRequest() {
   return {
     type: SUGGESTIONS_FETCH_REQUEST,
     skipLoading: true,
   };
-};
+}
 
 export function fetchSuggestionsSuccess(suggestions) {
   return {
@@ -36,7 +36,7 @@ export function fetchSuggestionsSuccess(suggestions) {
     suggestions,
     skipLoading: true,
   };
-};
+}
 
 export function fetchSuggestionsFail(error) {
   return {
@@ -45,7 +45,7 @@ export function fetchSuggestionsFail(error) {
     skipLoading: true,
     skipAlert: true,
   };
-};
+}
 
 export const dismissSuggestion = accountId => (dispatch, getState) => {
   dispatch({
diff --git a/app/javascript/flavours/glitch/actions/tags.js b/app/javascript/flavours/glitch/actions/tags.js
index 08a08cda3..dda8c924b 100644
--- a/app/javascript/flavours/glitch/actions/tags.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js
index a1c4dd43a..eb817daf9 100644
--- a/app/javascript/flavours/glitch/actions/timelines.js
+++ b/app/javascript/flavours/glitch/actions/timelines.js
@@ -55,14 +55,14 @@ export function updateTimeline(timeline, status, accept) {
       timeline,
       status,
       usePendingItems: preferPendingItems,
-      filtered
+      filtered,
     });
 
     if (timeline === 'home') {
       dispatch(submitMarkers());
     }
   };
-};
+}
 
 export function deleteFromTimelines(id) {
   return (dispatch, getState) => {
@@ -78,13 +78,13 @@ export function deleteFromTimelines(id) {
       reblogOf,
     });
   };
-};
+}
 
 export function clearTimeline(timeline) {
   return (dispatch) => {
     dispatch({ type: TIMELINE_CLEAR, timeline });
   };
-};
+}
 
 const noOp = () => {};
 
@@ -134,7 +134,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
       done();
     });
   };
-};
+}
 
 export function fillTimelineGaps(timelineId, path, params = {}, done = noOp) {
   return (dispatch, getState) => {
@@ -181,7 +181,7 @@ export function expandTimelineRequest(timeline, isLoadingMore) {
     timeline,
     skipLoading: !isLoadingMore,
   };
-};
+}
 
 export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore, usePendingItems) {
   return {
@@ -194,7 +194,7 @@ export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadi
     usePendingItems,
     skipLoading: !isLoadingMore,
   };
-};
+}
 
 export function expandTimelineFail(timeline, error, isLoadingMore) {
   return {
@@ -204,7 +204,7 @@ export function expandTimelineFail(timeline, error, isLoadingMore) {
     skipLoading: !isLoadingMore,
     skipNotFound: timeline.startsWith('account:'),
   };
-};
+}
 
 export function scrollTopTimeline(timeline, top) {
   return {
@@ -212,7 +212,7 @@ export function scrollTopTimeline(timeline, top) {
     timeline,
     top,
   };
-};
+}
 
 export function connectTimeline(timeline) {
   return {
@@ -220,7 +220,7 @@ export function connectTimeline(timeline) {
     timeline,
     usePendingItems: preferPendingItems,
   };
-};
+}
 
 export const disconnectTimeline = timeline => ({
   type: TIMELINE_DISCONNECT,
diff --git a/app/javascript/flavours/glitch/compare_id.js b/app/javascript/flavours/glitch/compare_id.js
index 66cf51c4b..d2bd74f44 100644
--- a/app/javascript/flavours/glitch/compare_id.js
+++ b/app/javascript/flavours/glitch/compare_id.js
@@ -8,4 +8,4 @@ export default function compareId (id1, id2) {
   } else {
     return id1.length > id2.length ? 1 : -1;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/components/account.js b/app/javascript/flavours/glitch/components/account.js
index 8e810ce5f..7ce4b65aa 100644
--- a/app/javascript/flavours/glitch/components/account.js
+++ b/app/javascript/flavours/glitch/components/account.js
@@ -48,27 +48,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 {
diff --git a/app/javascript/flavours/glitch/components/admin/Retention.js b/app/javascript/flavours/glitch/components/admin/Retention.js
index 9cc39040b..e1ba3f6c9 100644
--- a/app/javascript/flavours/glitch/components/admin/Retention.js
+++ b/app/javascript/flavours/glitch/components/admin/Retention.js
@@ -137,7 +137,7 @@ export default class Retention extends React.PureComponent {
       break;
     default:
       title = <FormattedMessage id='admin.dashboard.monthly_retention' defaultMessage='User retention rate by month after sign-up' />;
-    };
+    }
 
     return (
       <div className='retention'>
diff --git a/app/javascript/flavours/glitch/components/animated_number.js b/app/javascript/flavours/glitch/components/animated_number.js
index 9431c96f7..dd21d97f0 100644
--- a/app/javascript/flavours/glitch/components/animated_number.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/autosuggest_input.js b/app/javascript/flavours/glitch/components/autosuggest_input.js
index c7b024652..4d751e281 100644
--- a/app/javascript/flavours/glitch/components/autosuggest_input.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/autosuggest_textarea.js b/app/javascript/flavours/glitch/components/autosuggest_textarea.js
index 68c083433..6e6e567b9 100644
--- a/app/javascript/flavours/glitch/components/autosuggest_textarea.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/avatar.js b/app/javascript/flavours/glitch/components/avatar.js
index 38fd99af5..f30b33e70 100644
--- a/app/javascript/flavours/glitch/components/avatar.js
+++ b/app/javascript/flavours/glitch/components/avatar.js
@@ -28,12 +28,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 {
diff --git a/app/javascript/flavours/glitch/components/button.js b/app/javascript/flavours/glitch/components/button.js
index b1815c3e1..40b8f5a15 100644
--- a/app/javascript/flavours/glitch/components/button.js
+++ b/app/javascript/flavours/glitch/components/button.js
@@ -19,11 +19,11 @@ export default class Button extends React.PureComponent {
     if (!this.props.disabled) {
       this.props.onClick(e);
     }
-  }
+  };
 
   setRef = (c) => {
     this.node = c;
-  }
+  };
 
   focus() {
     this.node.focus();
diff --git a/app/javascript/flavours/glitch/components/column.js b/app/javascript/flavours/glitch/components/column.js
index cf0e6d5e4..47293ef18 100644
--- a/app/javascript/flavours/glitch/components/column.js
+++ b/app/javascript/flavours/glitch/components/column.js
@@ -29,11 +29,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/flavours/glitch/components/column_back_button.js b/app/javascript/flavours/glitch/components/column_back_button.js
index 05688f867..e9e2615cb 100644
--- a/app/javascript/flavours/glitch/components/column_back_button.js
+++ b/app/javascript/flavours/glitch/components/column_back_button.js
@@ -26,7 +26,7 @@ export default class ColumnBackButton extends React.PureComponent {
     } else {
       this.context.router.history.push('/');
     }
-  }
+  };
 
   render () {
     const { multiColumn } = this.props;
diff --git a/app/javascript/flavours/glitch/components/column_back_button_slim.js b/app/javascript/flavours/glitch/components/column_back_button_slim.js
index faa0c23a8..b43d85b3b 100644
--- a/app/javascript/flavours/glitch/components/column_back_button_slim.js
+++ b/app/javascript/flavours/glitch/components/column_back_button_slim.js
@@ -21,7 +21,7 @@ export default class ColumnBackButtonSlim extends React.PureComponent {
     } else {
       this.context.router.history.push('/');
     }
-  }
+  };
 
   render () {
     return (
diff --git a/app/javascript/flavours/glitch/components/column_header.js b/app/javascript/flavours/glitch/components/column_header.js
index 0f89b3a97..3790960dd 100644
--- a/app/javascript/flavours/glitch/components/column_header.js
+++ b/app/javascript/flavours/glitch/components/column_header.js
@@ -55,39 +55,39 @@ class ColumnHeader extends React.PureComponent {
     } else {
       this.context.router.history.push('/');
     }
-  }
+  };
 
   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 = (event) => {
     this.historyBack(event.shiftKey);
-  }
+  };
 
   handleTransitionEnd = () => {
     this.setState({ animating: false });
-  }
+  };
 
   handlePin = () => {
     if (!this.props.pinned) {
       this.historyBack();
     }
     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/flavours/glitch/components/dismissable_banner.js b/app/javascript/flavours/glitch/components/dismissable_banner.js
index ff52a619d..c4968ac3c 100644
--- a/app/javascript/flavours/glitch/components/dismissable_banner.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/display_name.js b/app/javascript/flavours/glitch/components/display_name.js
index 1c2297578..19f63ec60 100644
--- a/app/javascript/flavours/glitch/components/display_name.js
+++ b/app/javascript/flavours/glitch/components/display_name.js
@@ -27,7 +27,7 @@ export default class DisplayName extends React.PureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -40,7 +40,7 @@ export default class DisplayName extends React.PureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   render() {
     const { account, className, inline, localDomain, others, onAccountClick } = this.props;
@@ -74,7 +74,7 @@ export default class DisplayName extends React.PureComponent {
       )).reduce((prev, cur) => [prev, ', ', cur]);
 
       if (others.size - 2 > 0) {
-       displayName.push(` +${others.size - 2}`);
+        displayName.push(` +${others.size - 2}`);
       }
 
       suffix = (
diff --git a/app/javascript/flavours/glitch/components/domain.js b/app/javascript/flavours/glitch/components/domain.js
index 697065d87..e09fa4591 100644
--- a/app/javascript/flavours/glitch/components/domain.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/dropdown_menu.js b/app/javascript/flavours/glitch/components/dropdown_menu.js
index 7c70f750f..f4b6e059f 100644
--- a/app/javascript/flavours/glitch/components/dropdown_menu.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/edited_timestamp/index.js b/app/javascript/flavours/glitch/components/edited_timestamp/index.js
index 9648133af..c973bda58 100644
--- a/app/javascript/flavours/glitch/components/edited_timestamp/index.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/error_boundary.js b/app/javascript/flavours/glitch/components/error_boundary.js
index e0ca3e2b0..8518dfc86 100644
--- a/app/javascript/flavours/glitch/components/error_boundary.js
+++ b/app/javascript/flavours/glitch/components/error_boundary.js
@@ -18,7 +18,7 @@ export default class ErrorBoundary extends React.PureComponent {
     stackTrace: undefined,
     mappedStackTrace: undefined,
     componentStack: undefined,
-  }
+  };
 
   componentDidCatch(error, info) {
     this.setState({
diff --git a/app/javascript/flavours/glitch/components/gifv.js b/app/javascript/flavours/glitch/components/gifv.js
index b775e5200..1f0f99b46 100644
--- a/app/javascript/flavours/glitch/components/gifv.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/icon_button.js b/app/javascript/flavours/glitch/components/icon_button.js
index 2485f0f48..10d7926be 100644
--- a/app/javascript/flavours/glitch/components/icon_button.js
+++ b/app/javascript/flavours/glitch/components/icon_button.js
@@ -46,7 +46,7 @@ export default class IconButton extends React.PureComponent {
   state = {
     activate: false,
     deactivate: false,
-  }
+  };
 
   componentWillReceiveProps (nextProps) {
     if (!nextProps.animate) return;
@@ -64,25 +64,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 () {
     // Hack required for some icons which have an overriden size
diff --git a/app/javascript/flavours/glitch/components/intersection_observer_article.js b/app/javascript/flavours/glitch/components/intersection_observer_article.js
index b28e44e4c..77cd66358 100644
--- a/app/javascript/flavours/glitch/components/intersection_observer_article.js
+++ b/app/javascript/flavours/glitch/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);
@@ -63,7 +63,7 @@ export default class IntersectionObserverArticle extends React.Component {
 
     scheduleIdleTask(this.calculateHeight);
     this.setState(this.updateStateAfterIntersection);
-  }
+  };
 
   updateStateAfterIntersection = (prevState) => {
     if (prevState.isIntersecting !== false && !this.entry.isIntersecting) {
@@ -73,7 +73,7 @@ export default class IntersectionObserverArticle extends React.Component {
       isIntersecting: this.entry.isIntersecting,
       isHidden: false,
     };
-  }
+  };
 
   calculateHeight = () => {
     const { onHeightChange, saveHeightKey, id } = this.props;
@@ -84,7 +84,7 @@ export default class IntersectionObserverArticle extends React.Component {
     if (onHeightChange && saveHeightKey) {
       onHeightChange(saveHeightKey, id, this.height);
     }
-  }
+  };
 
   hideIfNotIntersecting = () => {
     if (!this.componentMounted) {
@@ -96,11 +96,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;
@@ -121,8 +121,9 @@ export default class IntersectionObserverArticle extends React.Component {
         aria-setsize={listLength}
         data-id={id}
         tabIndex='0'
-        style={style}>
-          {children && React.cloneElement(children, { hidden: !isIntersecting && (isHidden || !!cachedHeight) })}
+        style={style}
+      >
+        {children && React.cloneElement(children, { hidden: !isIntersecting && (isHidden || !!cachedHeight) })}
       </article>
     );
   }
diff --git a/app/javascript/flavours/glitch/components/load_gap.js b/app/javascript/flavours/glitch/components/load_gap.js
index fe3f60a58..6ed9a38c6 100644
--- a/app/javascript/flavours/glitch/components/load_gap.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/load_more.js b/app/javascript/flavours/glitch/components/load_more.js
index 389c3e1e1..ab9428e35 100644
--- a/app/javascript/flavours/glitch/components/load_more.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/load_pending.js b/app/javascript/flavours/glitch/components/load_pending.js
index 7e2702403..a75259146 100644
--- a/app/javascript/flavours/glitch/components/load_pending.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/media_attachments.js b/app/javascript/flavours/glitch/components/media_attachments.js
index a517fcf30..33f01bb5a 100644
--- a/app/javascript/flavours/glitch/components/media_attachments.js
+++ b/app/javascript/flavours/glitch/components/media_attachments.js
@@ -30,7 +30,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
     return (
       <div className='media-gallery' style={{ height, width }} />
     );
-  }
+  };
 
   renderLoadingVideoPlayer = () => {
     const { height, width } = this.props;
@@ -38,7 +38,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
     return (
       <div className='video-player' style={{ height, width }} />
     );
-  }
+  };
 
   renderLoadingAudioPlayer = () => {
     const { height, width } = this.props;
@@ -46,7 +46,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
     return (
       <div className='audio-player' style={{ height, width }} />
     );
-  }
+  };
 
   render () {
     const { status, width, height, revealed } = this.props;
diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js
index 23e279589..c11ac46c2 100644
--- a/app/javascript/flavours/glitch/components/media_gallery.js
+++ b/app/javascript/flavours/glitch/components/media_gallery.js
@@ -60,14 +60,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;
@@ -91,11 +91,11 @@ class Item extends React.PureComponent {
     }
 
     e.stopPropagation();
-  }
+  };
 
   handleImageLoad = () => {
     this.setState({ loaded: true });
-  }
+  };
 
   render () {
     const { attachment, index, size, standalone, letterbox, displayWidth, visible } = this.props;
@@ -307,11 +307,11 @@ class MediaGallery extends React.PureComponent {
     } else {
       this.setState({ visible: !this.state.visible });
     }
-  }
+  };
 
   handleClick = (index) => {
     this.props.onOpenMedia(this.props.media, index);
-  }
+  };
 
   handleRef = (node) => {
     this.node = node;
@@ -319,11 +319,11 @@ class MediaGallery extends React.PureComponent {
     if (this.node) {
       this._setDimensions();
     }
-  }
+  };
 
   _setDimensions () {
     const width = this.node.offsetWidth;
- 
+
     if (width && width != this.state.width) {
       // offsetWidth triggers a layout, so only calculate when we need to
       if (this.props.cacheWidth) {
@@ -360,7 +360,7 @@ class MediaGallery extends React.PureComponent {
     } else if (width) {
       style.height = width / (16/9);
     } else {
-      return (<div className={computedClass} ref={this.handleRef}></div>);
+      return (<div className={computedClass} ref={this.handleRef} />);
     }
 
     if (this.isStandaloneEligible()) {
diff --git a/app/javascript/flavours/glitch/components/modal_root.js b/app/javascript/flavours/glitch/components/modal_root.js
index 056277447..5a5563e87 100644
--- a/app/javascript/flavours/glitch/components/modal_root.js
+++ b/app/javascript/flavours/glitch/components/modal_root.js
@@ -5,6 +5,7 @@ import { createBrowserHistory } from 'history';
 import { multiply } from 'color-blend';
 
 export default class ModalRoot extends React.PureComponent {
+
   static contextTypes = {
     router: PropTypes.object,
   };
@@ -28,7 +29,7 @@ export default class ModalRoot extends React.PureComponent {
          && !!this.props.children && !this.props.noEsc) {
       this.props.onClose();
     }
-  }
+  };
 
   handleKeyDown = (e) => {
     if (e.key === 'Tab') {
@@ -49,7 +50,7 @@ export default class ModalRoot extends React.PureComponent {
         e.preventDefault();
       }
     }
-  }
+  };
 
   componentDidMount () {
     window.addEventListener('keyup', this.handleKeyUp, false);
@@ -125,11 +126,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/flavours/glitch/components/permalink.js b/app/javascript/flavours/glitch/components/permalink.js
index 718b02115..b09b17eeb 100644
--- a/app/javascript/flavours/glitch/components/permalink.js
+++ b/app/javascript/flavours/glitch/components/permalink.js
@@ -24,12 +24,12 @@ export default class Permalink extends React.PureComponent {
 
       if (this.context.router) {
         e.preventDefault();
-        let state = {...this.context.router.history.location.state};
+        let state = { ...this.context.router.history.location.state };
         state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
         this.context.router.history.push(this.props.to, state);
       }
     }
-  }
+  };
 
   render () {
     const {
diff --git a/app/javascript/flavours/glitch/components/picture_in_picture_placeholder.js b/app/javascript/flavours/glitch/components/picture_in_picture_placeholder.js
index 01dce0a38..8bfdf343c 100644
--- a/app/javascript/flavours/glitch/components/picture_in_picture_placeholder.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/poll.js b/app/javascript/flavours/glitch/components/poll.js
index da65cd241..53ece560e 100644
--- a/app/javascript/flavours/glitch/components/poll.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.js
index 8eb2b66d4..ae1ba3037 100644
--- a/app/javascript/flavours/glitch/components/scrollable_list.js
+++ b/app/javascript/flavours/glitch/components/scrollable_list.js
@@ -137,7 +137,7 @@ class ScrollableList extends PureComponent {
     }
     this.mouseMovedRecently = false;
     this.scrollToTopOnMouseIdle = false;
-  }
+  };
 
   componentDidMount () {
     this.attachScrollListener();
@@ -154,29 +154,29 @@ class ScrollableList extends PureComponent {
     } else {
       return null;
     }
-  }
+  };
 
   getScrollTop = () => {
     return this.props.bindToDocument ? document.scrollingElement.scrollTop : this.node.scrollTop;
-  }
+  };
 
   getScrollHeight = () => {
     return this.props.bindToDocument ? document.scrollingElement.scrollHeight : this.node.scrollHeight;
-  }
+  };
 
   getClientHeight = () => {
     return this.props.bindToDocument ? document.scrollingElement.clientHeight : this.node.clientHeight;
-  }
+  };
 
   updateScrollBottom = (snapshot) => {
     const newScrollTop = this.getScrollHeight() - snapshot;
 
     this.setScrollTop(newScrollTop);
-  }
+  };
 
   cacheMediaWidth = (width) => {
     if (width && this.state.cachedMediaWidth != width) this.setState({ cachedMediaWidth: width });
-  }
+  };
 
   getSnapshotBeforeUpdate (prevProps, prevState) {
     const someItemInserted = React.Children.count(prevProps.children) > 0 &&
@@ -208,7 +208,7 @@ class ScrollableList extends PureComponent {
 
   onFullScreenChange = () => {
     this.setState({ fullscreen: isFullscreen() });
-  }
+  };
 
   attachIntersectionObserver () {
     this.intersectionObserverWrapper.connect({
@@ -256,12 +256,12 @@ class ScrollableList extends PureComponent {
 
   setRef = (c) => {
     this.node = c;
-  }
+  };
 
   handleLoadMore = e => {
     e.preventDefault();
     this.props.onLoadMore();
-  }
+  };
 
   handleLoadPending = e => {
     e.preventDefault();
@@ -273,7 +273,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/flavours/glitch/components/setting_text.js b/app/javascript/flavours/glitch/components/setting_text.js
index 2c1b70bc3..3a21a0601 100644
--- a/app/javascript/flavours/glitch/components/setting_text.js
+++ b/app/javascript/flavours/glitch/components/setting_text.js
@@ -13,7 +13,7 @@ export default class SettingText extends React.PureComponent {
 
   handleChange = (e) => {
     this.props.onChange(this.props.settingPath, e.target.value);
-  }
+  };
 
   render () {
     const { settings, settingPath, label } = this.props;
diff --git a/app/javascript/flavours/glitch/components/spoilers.js b/app/javascript/flavours/glitch/components/spoilers.js
index 8527403c1..75e4ec3a1 100644
--- a/app/javascript/flavours/glitch/components/spoilers.js
+++ b/app/javascript/flavours/glitch/components/spoilers.js
@@ -4,6 +4,7 @@ import { FormattedMessage } from 'react-intl';
 
 export default
 class Spoilers extends React.PureComponent {
+
   static propTypes = {
     spoilerText: PropTypes.string,
     children: PropTypes.node,
@@ -11,27 +12,27 @@ class Spoilers extends React.PureComponent {
 
   state = {
     hidden: true,
-  }
+  };
 
   handleSpoilerClick = () => {
     this.setState({ hidden: !this.state.hidden });
-  }
+  };
 
   render () {
     const { spoilerText, children } = this.props;
     const { hidden } = this.state;
 
-      const toggleText = hidden ?
-        <FormattedMessage
-          id='status.show_more'
-          defaultMessage='Show more'
-          key='0'
-        /> :
-        <FormattedMessage
-          id='status.show_less'
-          defaultMessage='Show less'
-          key='0'
-        />;
+    const toggleText = hidden ?
+      (<FormattedMessage
+        id='status.show_more'
+        defaultMessage='Show more'
+        key='0'
+      />) :
+      (<FormattedMessage
+        id='status.show_less'
+        defaultMessage='Show less'
+        key='0'
+      />);
 
     return ([
       <p className='spoiler__text'>
@@ -43,8 +44,9 @@ class Spoilers extends React.PureComponent {
       </p>,
       <div className={`status__content__spoiler ${!hidden ? 'status__content__spoiler--visible' : ''}`}>
         {children}
-      </div>
+      </div>,
     ]);
   }
+
 }
 
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index cbd8eb31c..34880efe4 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -54,7 +54,7 @@ export const defaultMediaVisibility = (status, settings) => {
   }
 
   return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all');
-}
+};
 
 export default @injectIntl
 class Status extends ImmutablePureComponent {
@@ -117,7 +117,7 @@ class Status extends ImmutablePureComponent {
     revealBehindCW: undefined,
     showCard: false,
     forceFilter: undefined,
-  }
+  };
 
   // Avoid checking props that are functions (and whose equality will always
   // evaluate to false. See react-immutable-pure-component for usage.
@@ -132,14 +132,14 @@ class Status extends ImmutablePureComponent {
     'expanded',
     'unread',
     'pictureInPicture',
-  ]
+  ];
 
   updateOnStates = [
     'isExpanded',
     'isCollapsed',
     'showMedia',
     'forceFilter',
-  ]
+  ];
 
   //  If our settings have changed to disable collapsed statuses, then we
   //  need to make sure that we uncollapse every one. We do that by watching
@@ -302,7 +302,9 @@ class Status extends ImmutablePureComponent {
     if (this.node && this.props.getScrollPosition) {
       const position = this.props.getScrollPosition();
       if (position !== null && this.node.offsetTop < position.top) {
-         requestAnimationFrame(() => { this.props.updateScrollBottom(position.height - position.top); });
+        requestAnimationFrame(() => {
+          this.props.updateScrollBottom(position.height - position.top);
+        });
       }
     }
   }
@@ -321,7 +323,7 @@ class Status extends ImmutablePureComponent {
     } else {
       this.setState({ isCollapsed: false });
     }
-  }
+  };
 
   setExpansion = (value) => {
     if (this.props.settings.getIn(['content_warnings', 'shared_state']) && this.props.status.get('hidden') === value) {
@@ -332,7 +334,7 @@ class Status extends ImmutablePureComponent {
     if (value) {
       this.setCollapsed(false);
     }
-  }
+  };
 
   //  `parseClick()` takes a click event and responds appropriately.
   //  If our status is collapsed, then clicking on it should uncollapse it.
@@ -361,17 +363,17 @@ class Status extends ImmutablePureComponent {
             status.getIn(['reblog', 'id'], status.get('id'))
           }`;
         }
-        let state = {...router.history.location.state};
+        let state = { ...router.history.location.state };
         state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
         router.history.push(destination, state);
       }
       e.preventDefault();
     }
-  }
+  };
 
   handleToggleMediaVisibility = () => {
     this.setState({ showMedia: !this.state.showMedia });
-  }
+  };
 
   handleExpandedToggle = () => {
     if (this.props.settings.getIn(['content_warnings', 'shared_state'])) {
@@ -384,11 +386,11 @@ class Status extends ImmutablePureComponent {
   handleOpenVideo = (options) => {
     const { status } = this.props;
     this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options);
-  }
+  };
 
   handleOpenMedia = (media, index) => {
     this.props.onOpenMedia(this.props.status.get('id'), media, index);
-  }
+  };
 
   handleHotkeyOpenMedia = e => {
     const { status, onOpenMedia, onOpenVideo } = this.props;
@@ -403,84 +405,84 @@ class Status extends ImmutablePureComponent {
         onOpenMedia(statusId, status.get('media_attachments'), 0);
       }
     }
-  }
+  };
 
   handleDeployPictureInPicture = (type, mediaProps) => {
     const { deployPictureInPicture, status } = this.props;
 
     deployPictureInPicture(status, type, mediaProps);
-  }
+  };
 
   handleHotkeyReply = e => {
     e.preventDefault();
     this.props.onReply(this.props.status, this.context.router.history);
-  }
+  };
 
   handleHotkeyFavourite = (e) => {
     this.props.onFavourite(this.props.status, e);
-  }
+  };
 
   handleHotkeyBoost = e => {
     this.props.onReblog(this.props.status, e);
-  }
+  };
 
   handleHotkeyBookmark = e => {
     this.props.onBookmark(this.props.status, e);
-  }
+  };
 
   handleHotkeyMention = e => {
     e.preventDefault();
     this.props.onMention(this.props.status.get('account'), this.context.router.history);
-  }
+  };
 
   handleHotkeyOpen = () => {
-    let state = {...this.context.router.history.location.state};
+    let state = { ...this.context.router.history.location.state };
     state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
     const status = this.props.status;
     this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`, state);
-  }
+  };
 
   handleHotkeyOpenProfile = () => {
-    let state = {...this.context.router.history.location.state};
+    let state = { ...this.context.router.history.location.state };
     state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
     this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`, state);
-  }
+  };
 
   handleHotkeyMoveUp = e => {
     this.props.onMoveUp(this.props.containerId || this.props.id, e.target.getAttribute('data-featured'));
-  }
+  };
 
   handleHotkeyMoveDown = e => {
     this.props.onMoveDown(this.props.containerId || this.props.id, e.target.getAttribute('data-featured'));
-  }
+  };
 
   handleHotkeyCollapse = e => {
     if (!this.props.settings.getIn(['collapsed', 'enabled']))
       return;
 
     this.setCollapsed(!this.state.isCollapsed);
-  }
+  };
 
   handleHotkeyToggleSensitive = () => {
     this.handleToggleMediaVisibility();
-  }
+  };
 
   handleUnfilterClick = e => {
     this.setState({ forceFilter: false });
     e.preventDefault();
-  }
+  };
 
   handleFilterClick = () => {
     this.setState({ forceFilter: true });
-  }
+  };
 
   handleRef = c => {
     this.node = c;
-  }
+  };
 
   handleTranslate = () => {
     this.props.onTranslate(this.props.status);
-  }
+  };
 
   renderLoadingMediaGallery () {
     return <div className='media-gallery' style={{ height: '110px' }} />;
diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js
index baf61000a..02c5442b5 100644
--- a/app/javascript/flavours/glitch/components/status_action_bar.js
+++ b/app/javascript/flavours/glitch/components/status_action_bar.js
@@ -86,7 +86,7 @@ class StatusActionBar extends ImmutablePureComponent {
     'showReplyCount',
     'withCounters',
     'withDismiss',
-  ]
+  ];
 
   handleReplyClick = () => {
     const { signedIn } = this.context.identity;
@@ -96,14 +96,14 @@ class StatusActionBar extends ImmutablePureComponent {
     } else {
       this.props.onInteractionModal('reply', this.props.status);
     }
-  }
+  };
 
   handleShareClick = () => {
     navigator.share({
       text: this.props.status.get('search_index'),
       url: this.props.status.get('url'),
     });
-  }
+  };
 
   handleFavouriteClick = (e) => {
     const { signedIn } = this.context.identity;
@@ -113,7 +113,7 @@ class StatusActionBar extends ImmutablePureComponent {
     } else {
       this.props.onInteractionModal('favourite', this.props.status);
     }
-  }
+  };
 
   handleReblogClick = e => {
     const { signedIn } = this.context.identity;
@@ -123,78 +123,78 @@ class StatusActionBar extends ImmutablePureComponent {
     } else {
       this.props.onInteractionModal('reblog', 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);
-  }
+  };
 
   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 = () => {
     this.props.onMute(this.props.status.get('account'));
-  }
+  };
 
   handleBlockClick = () => {
     this.props.onBlock(this.props.status);
-  }
+  };
 
   handleOpen = () => {
-    let state = {...this.context.router.history.location.state};
+    let state = { ...this.context.router.history.location.state };
     if (state.mastodonModalKey) {
       this.context.router.history.replace(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`, { mastodonBackSteps: (state.mastodonBackSteps || 0) + 1 });
     } else {
       state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
       this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`, state);
     }
-  }
+  };
 
   handleEmbed = () => {
     this.props.onEmbed(this.props.status);
-  }
+  };
 
   handleReport = () => {
     this.props.onReport(this.props.status);
-  }
+  };
 
   handleConversationMuteClick = () => {
     this.props.onMuteConversation(this.props.status);
-  }
+  };
 
   handleCopy = () => {
     const url = this.props.status.get('url');
     navigator.clipboard.writeText(url);
-  }
+  };
 
   handleHideClick = () => {
     this.props.onFilter();
-  }
+  };
 
   handleFilterClick = () => {
     this.props.onAddFilter(this.props.status);
-  }
+  };
 
   render () {
     const { status, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props;
diff --git a/app/javascript/flavours/glitch/components/status_content.js b/app/javascript/flavours/glitch/components/status_content.js
index c59f42220..790a5c659 100644
--- a/app/javascript/flavours/glitch/components/status_content.js
+++ b/app/javascript/flavours/glitch/components/status_content.js
@@ -12,7 +12,7 @@ const textMatchesTarget = (text, origin, host) => {
   return (text === origin || text === host
           || text.startsWith(origin + '/') || text.startsWith(host + '/')
           || 'www.' + text === host || ('www.' + text).startsWith(host + '/'));
-}
+};
 
 const isLinkMisleading = (link) => {
   let linkTextParts = [];
@@ -168,8 +168,8 @@ class StatusContent extends React.PureComponent {
         link.setAttribute('title', link.href);
         link.classList.add('unhandled-link');
 
-      link.setAttribute('target', '_blank');
-      link.setAttribute('rel', 'noopener nofollow noreferrer');
+        link.setAttribute('target', '_blank');
+        link.setAttribute('rel', 'noopener nofollow noreferrer');
 
         try {
           if (tagLinks && isLinkMisleading(link)) {
@@ -210,7 +210,7 @@ class StatusContent extends React.PureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -223,7 +223,7 @@ class StatusContent extends React.PureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   componentDidMount () {
     this._updateStatusLinks();
@@ -238,13 +238,13 @@ class StatusContent extends React.PureComponent {
     if (this.props.collapsed) {
       if (this.props.parseClick) this.props.parseClick(e);
     }
-  }
+  };
 
   onMentionClick = (mention, e) => {
     if (this.props.parseClick) {
       this.props.parseClick(e, `/@${mention.get('acct')}`);
     }
-  }
+  };
 
   onHashtagClick = (hashtag, e) => {
     hashtag = hashtag.replace(/^#/, '');
@@ -252,11 +252,11 @@ class StatusContent extends React.PureComponent {
     if (this.props.parseClick) {
       this.props.parseClick(e, `/tags/${hashtag}`);
     }
-  }
+  };
 
   handleMouseDown = (e) => {
     this.startXY = [e.clientX, e.clientY];
-  }
+  };
 
   handleMouseUp = (e) => {
     const { parseClick, disabled } = this.props;
@@ -281,7 +281,7 @@ class StatusContent extends React.PureComponent {
     }
 
     this.startXY = null;
-  }
+  };
 
   handleSpoilerClick = (e) => {
     e.preventDefault();
@@ -291,15 +291,15 @@ class StatusContent extends React.PureComponent {
     } else {
       this.setState({ hidden: !this.state.hidden });
     }
-  }
+  };
 
   handleTranslate = () => {
     this.props.onTranslate();
-  }
+  };
 
   setContentsRef = (c) => {
     this.contentsNode = c;
-  }
+  };
 
   render () {
     const {
diff --git a/app/javascript/flavours/glitch/components/status_header.js b/app/javascript/flavours/glitch/components/status_header.js
index 990dea536..21d8b4212 100644
--- a/app/javascript/flavours/glitch/components/status_header.js
+++ b/app/javascript/flavours/glitch/components/status_header.js
@@ -21,12 +21,12 @@ export default class StatusHeader extends React.PureComponent {
   handleClick = (acct, e) => {
     const { parseClick } = this.props;
     parseClick(e, `/@${acct}`);
-  }
+  };
 
   handleAccountClick = (e) => {
     const { status } = this.props;
     this.handleClick(status.getIn(['account', 'acct']), e);
-  }
+  };
 
   //  Rendering.
   render () {
diff --git a/app/javascript/flavours/glitch/components/status_icons.js b/app/javascript/flavours/glitch/components/status_icons.js
index 71ffb2e56..c4cb42741 100644
--- a/app/javascript/flavours/glitch/components/status_icons.js
+++ b/app/javascript/flavours/glitch/components/status_icons.js
@@ -60,22 +60,22 @@ class StatusIcons extends React.PureComponent {
       setCollapsed(!collapsed);
       e.preventDefault();
     }
-  }
+  };
 
   mediaIconTitleText (mediaIcon) {
     const { intl } = this.props;
 
     switch (mediaIcon) {
-      case 'link':
-        return intl.formatMessage(messages.previewCard);
-      case 'picture-o':
-        return intl.formatMessage(messages.pictures);
-      case 'tasks':
-        return intl.formatMessage(messages.poll);
-      case 'video-camera':
-        return intl.formatMessage(messages.video);
-      case 'music':
-        return intl.formatMessage(messages.audio);
+    case 'link':
+      return intl.formatMessage(messages.previewCard);
+    case 'picture-o':
+      return intl.formatMessage(messages.pictures);
+    case 'tasks':
+      return intl.formatMessage(messages.poll);
+    case 'video-camera':
+      return intl.formatMessage(messages.video);
+    case 'music':
+      return intl.formatMessage(messages.audio);
     }
   }
 
diff --git a/app/javascript/flavours/glitch/components/status_list.js b/app/javascript/flavours/glitch/components/status_list.js
index 0d843a27d..a9c06f693 100644
--- a/app/javascript/flavours/glitch/components/status_list.js
+++ b/app/javascript/flavours/glitch/components/status_list.js
@@ -35,7 +35,7 @@ export default class StatusList extends ImmutablePureComponent {
 
   getFeaturedStatusCount = () => {
     return this.props.featuredStatusIds ? this.props.featuredStatusIds.size : 0;
-  }
+  };
 
   getCurrentStatusIndex = (id, featured) => {
     if (featured) {
@@ -43,21 +43,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;
@@ -75,7 +75,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/flavours/glitch/components/status_prepend.js b/app/javascript/flavours/glitch/components/status_prepend.js
index f82533062..8c4343b04 100644
--- a/app/javascript/flavours/glitch/components/status_prepend.js
+++ b/app/javascript/flavours/glitch/components/status_prepend.js
@@ -18,7 +18,7 @@ export default class StatusPrepend extends React.PureComponent {
   handleClick = (e) => {
     const { account, parseClick } = this.props;
     parseClick(e, `/@${account.get('acct')}`);
-  }
+  };
 
   Message = () => {
     const { type, account } = this.props;
@@ -98,7 +98,7 @@ export default class StatusPrepend extends React.PureComponent {
       );
     }
     return null;
-  }
+  };
 
   render () {
     const { Message } = this;
@@ -126,7 +126,7 @@ export default class StatusPrepend extends React.PureComponent {
     case 'update':
       iconId = 'pencil';
       break;
-    };
+    }
 
     return !type ? null : (
       <aside className={type === 'reblogged_by' || type === 'featured' ? 'status__prepend' : 'notification__message'}>
diff --git a/app/javascript/flavours/glitch/containers/media_container.js b/app/javascript/flavours/glitch/containers/media_container.js
index f1e8534aa..37b5484e6 100644
--- a/app/javascript/flavours/glitch/containers/media_container.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/extra_polyfills.js b/app/javascript/flavours/glitch/extra_polyfills.js
index 6e8004f07..e6c69de8b 100644
--- a/app/javascript/flavours/glitch/extra_polyfills.js
+++ b/app/javascript/flavours/glitch/extra_polyfills.js
@@ -1,3 +1,2 @@
 import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only';
-import 'intersection-observer';
 import 'requestidlecallback';
diff --git a/app/javascript/flavours/glitch/features/about/index.js b/app/javascript/flavours/glitch/features/about/index.js
index 57f295ea9..1e0a8666a 100644
--- a/app/javascript/flavours/glitch/features/about/index.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/account/components/account_note.js b/app/javascript/flavours/glitch/features/account/components/account_note.js
index 6e5ed7703..b5c0c9205 100644
--- a/app/javascript/flavours/glitch/features/account/components/account_note.js
+++ b/app/javascript/flavours/glitch/features/account/components/account_note.js
@@ -41,7 +41,7 @@ class Header extends ImmutablePureComponent {
     } else if (e.keyCode === 27) {
       this.props.onCancelAccountNote();
     }
-  }
+  };
 
   render () {
     const { account, accountNote, isEditing, isSubmitting, intl } = this.props;
diff --git a/app/javascript/flavours/glitch/features/account/components/action_bar.js b/app/javascript/flavours/glitch/features/account/components/action_bar.js
index ce0584124..d53080d4f 100644
--- a/app/javascript/flavours/glitch/features/account/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/account/components/action_bar.js
@@ -21,7 +21,7 @@ class ActionBar extends React.PureComponent {
       return false;
     }
     return !location.pathname.match(/\/(followers|following)\/?$/);
-  }
+  };
 
   render () {
     const { account, intl } = this.props;
@@ -32,7 +32,7 @@ class ActionBar extends React.PureComponent {
           <div className='account__disclaimer'>
             <Icon id='info-circle' fixedWidth /> <FormattedMessage
               id='account.suspended_disclaimer_full'
-              defaultMessage="This user has been suspended by a moderator."
+              defaultMessage='This user has been suspended by a moderator.'
             />
           </div>
         </div>
diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js
index 963ce7bf3..c11a472e7 100644
--- a/app/javascript/flavours/glitch/features/account/components/header.js
+++ b/app/javascript/flavours/glitch/features/account/components/header.js
@@ -109,7 +109,7 @@ class Header extends ImmutablePureComponent {
 
   openEditProfile = () => {
     window.open(profileLink, '_blank');
-  }
+  };
 
   handleMouseEnter = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -122,7 +122,7 @@ class Header extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -135,14 +135,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;
@@ -153,7 +153,7 @@ class Header extends ImmutablePureComponent {
     }).catch((e) => {
       if (e.name !== 'AbortError') console.error(e);
     });
-  }
+  };
 
   render () {
     const { account, hidden, intl, domain } = this.props;
@@ -177,8 +177,7 @@ class Header extends ImmutablePureComponent {
 
     if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
       info.push(<span className='relationship-tag'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>);
-    }
-    else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
+    } else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
       info.push(<span className='relationship-tag'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>);
     }
 
@@ -378,7 +377,7 @@ class Header extends ImmutablePureComponent {
                     {fields.map((pair, i) => (
                       <dl key={i}>
                         <dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
-   
+
                         <dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
                           {pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} className='translate' />
                         </dd>
diff --git a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
index f20ee685e..d169875b0 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js
index 638224bc0..afd6e5161 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/index.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/index.js
@@ -45,7 +45,7 @@ class LoadMoreMedia extends ImmutablePureComponent {
 
   handleLoadMore = () => {
     this.props.onLoadMore(this.props.maxId);
-  }
+  };
 
   render () {
     return (
@@ -109,13 +109,13 @@ class AccountGallery extends ImmutablePureComponent {
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   handleScrollToBottom = () => {
     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;
@@ -124,7 +124,7 @@ class AccountGallery extends ImmutablePureComponent {
     if (150 > offset && !this.props.isLoading) {
       this.handleScrollToBottom();
     }
-  }
+  };
 
   handleLoadMore = maxId => {
     this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, { maxId }));
@@ -133,11 +133,11 @@ class AccountGallery extends ImmutablePureComponent {
   handleLoadOlder = e => {
     e.preventDefault();
     this.handleScrollToBottom();
-  }
+  };
 
   setColumnRef = c => {
     this.column = c;
-  }
+  };
 
   handleOpenMedia = attachment => {
     const { dispatch } = this.props;
@@ -153,13 +153,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, suspended } = this.props;
diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js
index 90c4c9d51..eec065b43 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js
@@ -37,35 +37,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];
@@ -73,7 +73,7 @@ export default class Header extends ImmutablePureComponent {
     if (!domain) return;
 
     this.props.onBlockDomain(domain);
-  }
+  };
 
   handleUnblockDomain = () => {
     const domain = this.props.account.get('acct').split('@')[1];
@@ -81,31 +81,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/flavours/glitch/features/account_timeline/components/limited_account_hint.js b/app/javascript/flavours/glitch/features/account_timeline/components/limited_account_hint.js
index ca0e60672..dc2b3e3e6 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/components/limited_account_hint.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/account_timeline/components/moved_note.js b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js
index 308407e94..40bdc4034 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js
@@ -21,13 +21,13 @@ export default class MovedNote extends ImmutablePureComponent {
   handleAccountClick = e => {
     if (e.button === 0) {
       e.preventDefault();
-      let state = {...this.context.router.history.location.state};
+      let state = { ...this.context.router.history.location.state };
       state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
       this.context.router.history.push(`/@${this.props.to.get('acct')}`, state);
     }
 
     e.stopPropagation();
-  }
+  };
 
   render () {
     const { from, to } = this.props;
diff --git a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js
index 25bcd0119..3ec47cf2f 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js
@@ -12,7 +12,7 @@ import {
 } from 'flavours/glitch/actions/accounts';
 import {
   mentionCompose,
-  directCompose
+  directCompose,
 } from 'flavours/glitch/actions/compose';
 import { initMuteModal } from 'flavours/glitch/actions/mutes';
 import { initBlockModal } from 'flavours/glitch/actions/blocks';
diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js
index b79082f00..9151c1990 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/index.js
@@ -140,15 +140,15 @@ class AccountTimeline extends ImmutablePureComponent {
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   handleLoadMore = maxId => {
     this.props.dispatch(expandAccountTimeline(this.props.accountId, { maxId, withReplies: this.props.withReplies, tagged: this.props.params.tagged }));
-  }
+  };
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   render () {
     const { accountId, statusIds, featuredStatusIds, isLoading, hasMore, suspended, isAccount, hidden, multiColumn, remote, remoteUrl } = this.props;
diff --git a/app/javascript/flavours/glitch/features/audio/index.js b/app/javascript/flavours/glitch/features/audio/index.js
index 3a0d49100..1a1d438c0 100644
--- a/app/javascript/flavours/glitch/features/audio/index.js
+++ b/app/javascript/flavours/glitch/features/audio/index.js
@@ -75,7 +75,7 @@ class Audio extends React.PureComponent {
     if (this.player) {
       this._setDimensions();
     }
-  }
+  };
 
   _pack() {
     return {
@@ -107,11 +107,11 @@ class Audio extends React.PureComponent {
 
   setSeekRef = c => {
     this.seek = c;
-  }
+  };
 
   setVolumeRef = c => {
     this.volume = c;
-  }
+  };
 
   setAudioRef = c => {
     this.audio = c;
@@ -120,14 +120,14 @@ 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);
     window.addEventListener('resize', this.handleResize, { passive: true });
@@ -169,7 +169,7 @@ class Audio extends React.PureComponent {
     } else {
       this.setState({ paused: true }, () => this.audio.pause());
     }
-  }
+  };
 
   handleResize = debounce(() => {
     if (this.player) {
@@ -187,7 +187,7 @@ class Audio extends React.PureComponent {
     }
 
     this._renderCanvas();
-  }
+  };
 
   handlePause = () => {
     this.setState({ paused: true });
@@ -195,7 +195,7 @@ class Audio extends React.PureComponent {
     if (this.audioContext) {
       this.audioContext.suspend();
     }
-  }
+  };
 
   handleProgress = () => {
     const lastTimeRange = this.audio.buffered.length - 1;
@@ -203,7 +203,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;
@@ -213,7 +213,7 @@ class Audio extends React.PureComponent {
         this.gainNode.gain.value = muted ? 0 : this.state.volume;
       }
     });
-  }
+  };
 
   toggleReveal = () => {
     if (this.props.onToggleVisibility) {
@@ -221,7 +221,7 @@ class Audio extends React.PureComponent {
     } else {
       this.setState({ revealed: !this.state.revealed });
     }
-  }
+  };
 
   handleVolumeMouseDown = e => {
     document.addEventListener('mousemove', this.handleMouseVolSlide, true);
@@ -233,14 +233,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);
@@ -254,7 +254,7 @@ class Audio extends React.PureComponent {
 
     e.preventDefault();
     e.stopPropagation();
-  }
+  };
 
   handleMouseUp = () => {
     document.removeEventListener('mousemove', this.handleMouseMove, true);
@@ -264,7 +264,7 @@ class Audio extends React.PureComponent {
 
     this.setState({ dragging: false });
     this.audio.play();
-  }
+  };
 
   handleMouseMove = throttle(e => {
     const { x } = getPointerPosition(this.seek, e);
@@ -282,7 +282,7 @@ class Audio extends React.PureComponent {
       currentTime: this.audio.currentTime,
       duration: this.audio.duration,
     });
-  }
+  };
 
   handleMouseVolSlide = throttle(e => {
     const { x } = getPointerPosition(this.volume, e);
@@ -317,11 +317,11 @@ class Audio extends React.PureComponent {
 
   handleMouseEnter = () => {
     this.setState({ hovered: true });
-  }
+  };
 
   handleMouseLeave = () => {
     this.setState({ hovered: false });
-  }
+  };
 
   handleLoadedData = () => {
     const { autoPlay, currentTime } = this.props;
@@ -333,7 +333,7 @@ class Audio extends React.PureComponent {
     if (autoPlay) {
       this.togglePlay();
     }
-  }
+  };
 
   _initAudioContext () {
     const AudioContext = window.AudioContext || window.webkitAudioContext;
@@ -367,7 +367,7 @@ class Audio extends React.PureComponent {
     }).catch(err => {
       console.error(err);
     });
-  }
+  };
 
   _renderCanvas () {
     requestAnimationFrame(() => {
@@ -438,7 +438,7 @@ class Audio extends React.PureComponent {
       e.stopPropagation();
       this.togglePlay();
     }
-  }
+  };
 
   handleKeyDown = e => {
     switch(e.key) {
@@ -463,7 +463,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/flavours/glitch/features/bookmarked_statuses/index.js b/app/javascript/flavours/glitch/features/bookmarked_statuses/index.js
index 8978ac5fc..8e25bc6fd 100644
--- a/app/javascript/flavours/glitch/features/bookmarked_statuses/index.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/closed_registrations_modal/index.js b/app/javascript/flavours/glitch/features/closed_registrations_modal/index.js
index cb91636cb..bdaa9885c 100644
--- a/app/javascript/flavours/glitch/features/closed_registrations_modal/index.js
+++ b/app/javascript/flavours/glitch/features/closed_registrations_modal/index.js
@@ -72,4 +72,4 @@ class ClosedRegistrationsModal extends ImmutablePureComponent {
     );
   }
 
-};
+}
diff --git a/app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js
index b892f08ad..eac1c4bba 100644
--- a/app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js
+++ b/app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js
@@ -12,7 +12,7 @@ const mapStateToProps = (state, { columnId }) => {
     settings: (uuid && index >= 0) ? columns.get(index).get('params') : state.getIn(['settings', 'community']),
   };
 };
- 
+
 const mapDispatchToProps = (dispatch, { columnId }) => {
   return {
     onChange (key, checked) {
diff --git a/app/javascript/flavours/glitch/features/community_timeline/index.js b/app/javascript/flavours/glitch/features/community_timeline/index.js
index 67bf54875..b9a59fdc7 100644
--- a/app/javascript/flavours/glitch/features/community_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/community_timeline/index.js
@@ -63,16 +63,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;
@@ -112,13 +112,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/flavours/glitch/features/compose/components/action_bar.js b/app/javascript/flavours/glitch/features/compose/components/action_bar.js
index 838ef09ea..1843fdacb 100644
--- a/app/javascript/flavours/glitch/features/compose/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/compose/components/action_bar.js
@@ -32,7 +32,7 @@ class ActionBar extends React.PureComponent {
 
   handleLogout = () => {
     this.props.onLogout();
-  }
+  };
 
   render () {
     const { intl } = this.props;
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index 2b57cf15d..9f57d7b1b 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -21,10 +21,14 @@ import { length } from 'stringz';
 
 const messages = defineMessages({
   placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
-  missingDescriptionMessage: {  id: 'confirmations.missing_media_description.message',
-                                defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' },
-  missingDescriptionConfirm: {  id: 'confirmations.missing_media_description.confirm',
-                                defaultMessage: 'Send anyway' },
+  missingDescriptionMessage: {
+    id: 'confirmations.missing_media_description.message',
+    defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.',
+  },
+  missingDescriptionConfirm: {
+    id: 'confirmations.missing_media_description.confirm',
+    defaultMessage: 'Send anyway',
+  },
   spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Write your warning here' },
 });
 
@@ -83,22 +87,22 @@ class ComposeForm extends ImmutablePureComponent {
 
   handleChange = (e) => {
     this.props.onChange(e.target.value);
-  }
+  };
 
   getFulltextForCharacterCounting = () => {
     return [
       this.props.spoiler? this.props.spoilerText: '',
       countableText(this.props.text),
-      this.props.advancedOptions && this.props.advancedOptions.get('do_not_federate') ? ' 👁️' : ''
+      this.props.advancedOptions && this.props.advancedOptions.get('do_not_federate') ? ' 👁️' : '',
     ].join('');
-  }
+  };
 
   canSubmit = () => {
     const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
     const fulltext = this.getFulltextForCharacterCounting();
 
     return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > maxChars || (!fulltext.trim().length && !anyMedia));
-  }
+  };
 
   handleSubmit = (overriddenVisibility = null) => {
     const {
@@ -129,7 +133,7 @@ class ComposeForm extends ImmutablePureComponent {
       }
       onSubmit(this.context.router ? this.context.router.history : null);
     }
-  }
+  };
 
   //  Changes the text value of the spoiler.
   handleChangeSpoiler = ({ target: { value } }) => {
@@ -137,7 +141,7 @@ class ComposeForm extends ImmutablePureComponent {
     if (onChangeSpoilerText) {
       onChangeSpoilerText(value);
     }
-  }
+  };
 
   setRef = c => {
     this.composeForm = c;
@@ -150,7 +154,7 @@ class ComposeForm extends ImmutablePureComponent {
     if (onPickEmoji) {
       onPickEmoji(selectionStart, data);
     }
-  }
+  };
 
   //  Handles the secondary submit button.
   handleSecondarySubmit = () => {
@@ -158,16 +162,16 @@ class ComposeForm extends ImmutablePureComponent {
       sideArm,
     } = this.props;
     this.handleSubmit(sideArm === 'none' ? null : sideArm);
-  }
+  };
 
   //  Selects a suggestion from the autofill.
   onSuggestionSelected = (tokenStart, token, value) => {
     this.props.onSuggestionSelected(tokenStart, token, value, ['text']);
-  }
+  };
 
   onSpoilerSuggestionSelected = (tokenStart, token, value) => {
     this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']);
-  }
+  };
 
   handleKeyDown = (e) => {
     if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
@@ -177,21 +181,21 @@ class ComposeForm extends ImmutablePureComponent {
     if (e.keyCode == 13 && e.altKey) {
       this.handleSecondarySubmit();
     }
-  }
+  };
 
   //  Sets a reference to the textarea.
   setAutosuggestTextarea = (textareaComponent) => {
     if (textareaComponent) {
       this.textarea = textareaComponent.textarea;
     }
-  }
+  };
 
   //  Sets a reference to the CW field.
   handleRefSpoilerText = (spoilerComponent) => {
     if (spoilerComponent) {
       this.spoilerText = spoilerComponent.input;
     }
-  }
+  };
 
   handleFocus = () => {
     if (this.composeForm && !this.props.singleColumn) {
@@ -200,7 +204,7 @@ class ComposeForm extends ImmutablePureComponent {
         this.composeForm.scrollIntoView();
       }
     }
-  }
+  };
 
   componentDidMount () {
     this._updateFocusAndSelection({ });
@@ -217,7 +221,7 @@ class ComposeForm extends ImmutablePureComponent {
   //      - Replying to more than one user, selects any usernames past
   //        the first; this provides a convenient shortcut to drop
   //        everyone else from the conversation.
-   _updateFocusAndSelection = (prevProps) => {
+  _updateFocusAndSelection = (prevProps) => {
     const {
       textarea,
       spoilerText,
@@ -271,7 +275,7 @@ class ComposeForm extends ImmutablePureComponent {
         }
       }
     }
-  }
+  };
 
 
   render () {
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js
index d98b311d9..fe4ab36f5 100644
--- a/app/javascript/flavours/glitch/features/compose/components/dropdown.js
+++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.js
@@ -64,7 +64,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
       }
       this.setState({ open: !this.state.open, openedViaKeyboard: type !== 'click' });
     }
-  }
+  };
 
   handleKeyDown = (e) => {
     switch (e.key) {
@@ -72,13 +72,13 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
       this.handleClose();
       break;
     }
-  }
+  };
 
   handleMouseDown = () => {
     if (!this.state.open) {
       this.activeElement = document.activeElement;
     }
-  }
+  };
 
   handleButtonKeyDown = (e) => {
     switch(e.key) {
@@ -87,7 +87,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
       this.handleMouseDown();
       break;
     }
-  }
+  };
 
   handleKeyPress = (e) => {
     switch(e.key) {
@@ -98,14 +98,14 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
       e.preventDefault();
       break;
     }
-  }
+  };
 
   handleClose = () => {
     if (this.state.open && this.activeElement) {
       this.activeElement.focus({ preventScroll: true });
     }
     this.setState({ open: false });
-  }
+  };
 
   handleItemClick = (e) => {
     const {
@@ -151,22 +151,22 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
           ...rest,
           active: value && name === value,
           name,
-        })
+        }),
       ),
     };
-  }
+  };
 
   setTargetRef = c => {
     this.target = c;
-  }
+  };
 
   findTarget = () => {
     return this.target;
-  }
+  };
 
   handleOverlayEnter = (state) => {
     this.setState({ placement: state.placement });
-  }
+  };
 
   //  Rendering.
   render () {
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
index c4895dfd0..1ea0df536 100644
--- a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
+++ b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
@@ -44,12 +44,12 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
     if (this.node && !this.node.contains(e.target)) {
       this.props.onClose();
     }
-  }
+  };
 
   //  Stores our node in `this.node`.
   setRef = (node) => {
     this.node = node;
-  }
+  };
 
   //  On mounting, we add our listeners.
   componentDidMount () {
@@ -84,7 +84,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
       onClose();
     }
     onChange(name);
-  }
+  };
 
   // Handle changes differently whether the dropdown is a list of options or actions
   handleChange = (name) => {
@@ -93,7 +93,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
     } else {
       this.setState({ value: name });
     }
-  }
+  };
 
   handleKeyDown = (e) => {
     const index = Number(e.currentTarget.getAttribute('data-index'));
@@ -135,11 +135,11 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
       e.preventDefault();
       e.stopPropagation();
     }
-  }
+  };
 
   setFocusRef = c => {
     this.focusedItem = c;
-  }
+  };
 
   renderItem = (item, i) => {
     const { name, icon, meta, text } = item;
@@ -177,7 +177,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
         {contents}
       </div>
     );
-  }
+  };
 
   //  Rendering.
   render () {
diff --git a/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js
index 38c735551..66355e088 100644
--- a/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js
+++ b/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js
@@ -58,7 +58,7 @@ class ModifierPickerMenu extends React.PureComponent {
 
   handleClick = e => {
     this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1);
-  }
+  };
 
   componentWillReceiveProps (nextProps) {
     if (nextProps.active) {
@@ -76,7 +76,7 @@ class ModifierPickerMenu extends React.PureComponent {
     if (this.node && !this.node.contains(e.target)) {
       this.props.onClose();
     }
-  }
+  };
 
   attachListeners () {
     document.addEventListener('click', this.handleDocumentClick, false);
@@ -90,7 +90,7 @@ class ModifierPickerMenu extends React.PureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   render () {
     const { active } = this.props;
@@ -125,12 +125,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;
@@ -175,7 +175,7 @@ class EmojiPickerMenu extends React.PureComponent {
     if (this.node && !this.node.contains(e.target)) {
       this.props.onClose();
     }
-  }
+  };
 
   componentDidMount () {
     document.addEventListener('click', this.handleDocumentClick, false);
@@ -199,7 +199,7 @@ class EmojiPickerMenu extends React.PureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   getI18n = () => {
     const { intl } = this.props;
@@ -220,7 +220,7 @@ class EmojiPickerMenu extends React.PureComponent {
         custom: intl.formatMessage(messages.custom),
       },
     };
-  }
+  };
 
   handleClick = (emoji, event) => {
     if (!emoji.native) {
@@ -230,19 +230,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;
@@ -327,7 +327,7 @@ class EmojiPickerDropdown extends React.PureComponent {
 
   setRef = (c) => {
     this.dropdown = c;
-  }
+  };
 
   onShowDropdown = () => {
     this.setState({ active: true });
@@ -344,11 +344,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')) {
@@ -358,21 +358,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/flavours/glitch/features/compose/components/header.js b/app/javascript/flavours/glitch/features/compose/components/header.js
index 7ecb573ab..dcbdafa57 100644
--- a/app/javascript/flavours/glitch/features/compose/components/header.js
+++ b/app/javascript/flavours/glitch/features/compose/components/header.js
@@ -47,6 +47,7 @@ const messages = defineMessages({
 
 export default @injectIntl
 class Header extends ImmutablePureComponent {
+
   static propTypes = {
     columns: ImmutablePropTypes.list,
     unreadNotifications: PropTypes.number,
@@ -63,7 +64,7 @@ class Header extends ImmutablePureComponent {
     this.props.onLogout();
 
     return false;
-  }
+  };
 
   render () {
     const { intl, columns, unreadNotifications, showNotificationsBadge, onSettingsClick } = this.props;
@@ -71,8 +72,8 @@ class Header extends ImmutablePureComponent {
     //  Only renders the component if the column isn't being shown.
     const renderForColumn = conditionalRender.bind(null,
       columnId => !columns || !columns.some(
-        column => column.get('id') === columnId
-      )
+        column => column.get('id') === columnId,
+      ),
     );
 
     //  The result.
@@ -125,10 +126,11 @@ class Header extends ImmutablePureComponent {
         <a
           aria-label={intl.formatMessage(messages.logout)}
           onClick={this.handleLogoutClick}
-          href={ signOutLink }
+          href={signOutLink}
           title={intl.formatMessage(messages.logout)}
         ><Icon id='sign-out' /></a>
       </nav>
     );
-  };
+  }
+
 }
diff --git a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js
index 3a1fa0226..3f8411ab1 100644
--- a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js
index b5276c371..e09e13bcb 100644
--- a/app/javascript/flavours/glitch/features/compose/components/options.js
+++ b/app/javascript/flavours/glitch/features/compose/components/options.js
@@ -144,7 +144,7 @@ class ComposerOptions extends ImmutablePureComponent {
     if (files.length && onUpload) {
       onUpload(files);
     }
-  }
+  };
 
   //  Handles attachment clicks.
   handleClickAttach = (name) => {
@@ -164,12 +164,12 @@ class ComposerOptions extends ImmutablePureComponent {
       }
       return;
     }
-  }
+  };
 
   //  Handles a ref to the file input.
   handleRefFileElement = (fileElement) => {
     this.fileElement = fileElement;
-  }
+  };
 
   renderToggleItemContents = (item) => {
     const { onChangeAdvancedOption } = this.props;
diff --git a/app/javascript/flavours/glitch/features/compose/components/poll_form.js b/app/javascript/flavours/glitch/features/compose/components/poll_form.js
index afb5da097..13b7322d3 100644
--- a/app/javascript/flavours/glitch/features/compose/components/poll_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/poll_form.js
@@ -26,6 +26,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,
@@ -48,18 +49,18 @@ class Option extends React.PureComponent {
 
   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>
@@ -70,6 +71,7 @@ class Option extends React.PureComponent {
             placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
             maxLength={pollLimits.max_option_chars}
             value={title}
+            lang={lang}
             onChange={this.handleOptionTitleChange}
             suggestions={this.props.suggestions}
             onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
@@ -95,6 +97,7 @@ class PollForm extends ImmutablePureComponent {
 
   static propTypes = {
     options: ImmutablePropTypes.list,
+    lang: PropTypes.string,
     expiresIn: PropTypes.number,
     isMultiple: PropTypes.bool,
     onChangeOption: PropTypes.func.isRequired,
@@ -121,7 +124,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;
@@ -132,7 +135,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} autoFocus={i === autoFocusIndex} {...other} />)}
+          {options.map((title, i) => <Option title={title} lang={lang} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} autoFocus={i === autoFocusIndex} {...other} />)}
           {options.size < pollLimits.max_options && (
             <label className='poll__text editable'>
               <span className={classNames('poll__input')} style={{ opacity: 0 }} />
diff --git a/app/javascript/flavours/glitch/features/compose/components/publisher.js b/app/javascript/flavours/glitch/features/compose/components/publisher.js
index 9d53b7ee3..59254990b 100644
--- a/app/javascript/flavours/glitch/features/compose/components/publisher.js
+++ b/app/javascript/flavours/glitch/features/compose/components/publisher.js
@@ -94,5 +94,6 @@ class Publisher extends ImmutablePureComponent {
         </div>
       </div>
     );
-  };
+  }
+
 }
diff --git a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js
index 7ad9e2b64..ca167d114 100644
--- a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js
+++ b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js
@@ -33,7 +33,7 @@ class ReplyIndicator extends ImmutablePureComponent {
     if (onCancel) {
       onCancel();
     }
-  }
+  };
 
   //  Rendering.
   render () {
diff --git a/app/javascript/flavours/glitch/features/compose/components/search.js b/app/javascript/flavours/glitch/features/compose/components/search.js
index e5874de75..6241e2a0a 100644
--- a/app/javascript/flavours/glitch/features/compose/components/search.js
+++ b/app/javascript/flavours/glitch/features/compose/components/search.js
@@ -71,14 +71,14 @@ class Search extends React.PureComponent {
 
   setRef = c => {
     this.searchForm = c;
-  }
+  };
 
   handleChange = (e) => {
     const { onChange } = this.props;
     if (onChange) {
       onChange(e.target.value);
     }
-  }
+  };
 
   handleClear = (e) => {
     const {
@@ -90,11 +90,11 @@ class Search extends React.PureComponent {
     if (onClear && (submitted || value && value.length)) {
       onClear();
     }
-  }
+  };
 
   handleBlur = () => {
     this.setState({ expanded: false });
-  }
+  };
 
   handleFocus = () => {
     this.setState({ expanded: true });
@@ -106,7 +106,7 @@ class Search extends React.PureComponent {
         this.searchForm.scrollIntoView();
       }
     }
-  }
+  };
 
   handleKeyUp = (e) => {
     const { onSubmit } = this.props;
@@ -121,11 +121,11 @@ class Search extends React.PureComponent {
     case 'Escape':
       focusRoot();
     }
-  }
+  };
 
   findTarget = () => {
     return this.searchForm;
-  }
+  };
 
   render () {
     const { intl, value, submitted } = this.props;
diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.js b/app/javascript/flavours/glitch/features/compose/components/search_results.js
index c2178702c..23ff60936 100644
--- a/app/javascript/flavours/glitch/features/compose/components/search_results.js
+++ b/app/javascript/flavours/glitch/features/compose/components/search_results.js
@@ -103,7 +103,7 @@ class SearchResults extends ImmutablePureComponent {
         <section className='search-results__section'>
           <h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></h5>
 
-          {results.get('statuses').map(statusId => <StatusContainer id={statusId} key={statusId}/>)}
+          {results.get('statuses').map(statusId => <StatusContainer id={statusId} key={statusId} />)}
 
           {results.get('statuses').size >= 5 && <LoadMore visible onClick={this.handleLoadMoreStatuses} />}
         </section>
@@ -136,5 +136,6 @@ class SearchResults extends ImmutablePureComponent {
         {hashtags}
       </div>
     );
-  };
+  }
+
 }
diff --git a/app/javascript/flavours/glitch/features/compose/components/textarea_icons.js b/app/javascript/flavours/glitch/features/compose/components/textarea_icons.js
index 25c2443b1..d8ee5c81b 100644
--- a/app/javascript/flavours/glitch/features/compose/components/textarea_icons.js
+++ b/app/javascript/flavours/glitch/features/compose/components/textarea_icons.js
@@ -51,9 +51,10 @@ class TextareaIcons extends ImmutablePureComponent {
                 id={icon}
               />
             </span>
-          ) : null
+          ) : null,
         ) : null}
       </div>
     );
   }
+
 }
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload.js b/app/javascript/flavours/glitch/features/compose/components/upload.js
index cd4524540..c82b8d55a 100644
--- a/app/javascript/flavours/glitch/features/compose/components/upload.js
+++ b/app/javascript/flavours/glitch/features/compose/components/upload.js
@@ -23,12 +23,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;
@@ -39,7 +39,7 @@ export default class Upload extends ImmutablePureComponent {
 
     return (
       <div className='compose-form__upload' tabIndex='0' role='button'>
-        <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12, }) }}>
+        <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
           {({ scale }) => (
             <div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
               <div className='compose-form__upload__actions'>
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_form.js b/app/javascript/flavours/glitch/features/compose/components/upload_form.js
index 7ebbac963..f2e7fe7a2 100644
--- a/app/javascript/flavours/glitch/features/compose/components/upload_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/upload_form.js
@@ -6,6 +6,7 @@ import UploadContainer from '../containers/upload_container';
 import SensitiveButtonContainer from '../containers/sensitive_button_container';
 
 export default class UploadForm extends ImmutablePureComponent {
+
   static propTypes = {
     mediaIds: ImmutablePropTypes.list.isRequired,
   };
diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
index 8f2947672..ddcdb367a 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
@@ -21,12 +21,18 @@ import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
 import { privacyPreference } from 'flavours/glitch/utils/privacy_preference';
 
 const messages = defineMessages({
-  missingDescriptionMessage: {  id: 'confirmations.missing_media_description.message',
-                                defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' },
-  missingDescriptionConfirm: {  id: 'confirmations.missing_media_description.confirm',
-                                defaultMessage: 'Send anyway' },
-  missingDescriptionEdit:    {  id: 'confirmations.missing_media_description.edit',
-                                defaultMessage: 'Edit media' },
+  missingDescriptionMessage: {
+    id: 'confirmations.missing_media_description.message',
+    defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.',
+  },
+  missingDescriptionConfirm: {
+    id: 'confirmations.missing_media_description.confirm',
+    defaultMessage: 'Send anyway',
+  },
+  missingDescriptionEdit: {
+    id: 'confirmations.missing_media_description.edit',
+    defaultMessage: 'Edit media',
+  },
 });
 
 //  State mapping.
@@ -38,12 +44,12 @@ function mapStateToProps (state) {
   const sideArmRestrictedPrivacy = replyPrivacy ? privacyPreference(replyPrivacy, sideArmBasePrivacy) : null;
   let sideArmPrivacy = null;
   switch (state.getIn(['local_settings', 'side_arm_reply_mode'])) {
-    case 'copy':
-      sideArmPrivacy = replyPrivacy;
-      break;
-    case 'restrict':
-      sideArmPrivacy = sideArmRestrictedPrivacy;
-      break;
+  case 'copy':
+    sideArmPrivacy = replyPrivacy;
+    break;
+  case 'restrict':
+    sideArmPrivacy = sideArmRestrictedPrivacy;
+    break;
   }
   sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy;
   return {
@@ -72,7 +78,7 @@ function mapStateToProps (state) {
     isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
     lang: state.getIn(['compose', 'language']),
   };
-};
+}
 
 //  Dispatch mapping.
 const mapDispatchToProps = (dispatch, { intl }) => ({
@@ -124,7 +130,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
       onConfirm: () => {
         if (overriddenVisibility) {
           dispatch(changeComposeVisibility(overriddenVisibility));
-        };
+        }
         dispatch(submitCompose(routerHistory));
       },
       secondary: intl.formatMessage(messages.missingDescriptionEdit),
diff --git a/app/javascript/flavours/glitch/features/compose/containers/options_container.js b/app/javascript/flavours/glitch/features/compose/containers/options_container.js
index 5de9f5419..19a90ac8b 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/options_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/options_container.js
@@ -23,7 +23,7 @@ function mapStateToProps (state) {
     showContentTypeChoice: state.getIn(['local_settings', 'show_content_type_choice']),
     contentType: state.getIn(['compose', 'content_type']),
   };
-};
+}
 
 const mapDispatchToProps = (dispatch) => ({
 
diff --git a/app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js
index e87e58771..1e0058341 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/poll_form_container.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/compose/containers/warning_container.js b/app/javascript/flavours/glitch/features/compose/containers/warning_container.js
index b2ed40b82..9402601f8 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/warning_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/warning_container.js
@@ -8,7 +8,7 @@ import { profileLink, termsLink } from 'flavours/glitch/utils/backend_links';
 
 const buildHashtagRE = () => {
   try {
-    const HASHTAG_SEPARATORS = "_\\u00b7\\u200c";
+    const HASHTAG_SEPARATORS = '_\\u00b7\\u200c';
     const ALPHA = '\\p{L}\\p{M}';
     const WORD = '\\p{L}\\p{M}\\p{N}\\p{Pc}';
     return new RegExp(
@@ -22,7 +22,7 @@ const buildHashtagRE = () => {
       '[' + WORD + '_]*' +
       '[' + ALPHA + ']' +
       '[' + WORD + '_]*' +
-      '))', 'iu'
+      '))', 'iu',
     );
   } catch {
     return /(?:^|[^\/\)\w])#(\w*[a-zA-Z·]\w*)/i;
diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js
index 8ca378672..68eb05e2c 100644
--- a/app/javascript/flavours/glitch/features/compose/index.js
+++ b/app/javascript/flavours/glitch/features/compose/index.js
@@ -43,6 +43,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 export default @connect(mapStateToProps, mapDispatchToProps)
 @injectIntl
 class Compose extends React.PureComponent {
+
   static propTypes = {
     multiColumn: PropTypes.bool,
     showSearch: PropTypes.bool,
diff --git a/app/javascript/flavours/glitch/features/compose/util/counter.js b/app/javascript/flavours/glitch/features/compose/util/counter.js
index 7aa9e87b1..5a68bad99 100644
--- a/app/javascript/flavours/glitch/features/compose/util/counter.js
+++ b/app/javascript/flavours/glitch/features/compose/util/counter.js
@@ -6,4 +6,4 @@ export function countableText(inputText) {
   return inputText
     .replace(urlRegex, urlPlaceholder)
     .replace(/(^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+[a-z0-9]+)/ig, '$1@$3');
-};
+}
diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js
index 00d9fdcd0..ad2a68ebd 100644
--- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js
+++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js
@@ -60,12 +60,12 @@ class Conversation extends ImmutablePureComponent {
         }
         destination = `/statuses/${lastStatus.get('id')}`;
       }
-      let state = {...router.history.location.state};
+      let state = { ...router.history.location.state };
       state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
       router.history.push(destination, state);
       e.preventDefault();
     }
-  }
+  };
 
   handleMouseEnter = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -78,7 +78,7 @@ class Conversation extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -91,7 +91,7 @@ class Conversation extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   handleClick = () => {
     if (!this.context.router) {
@@ -105,31 +105,31 @@ 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);
@@ -141,7 +141,7 @@ class Conversation extends ImmutablePureComponent {
 
   setExpansion = value => {
     this.setState({ isExpanded: value });
-  }
+  };
 
   render () {
     const { accounts, lastStatus, unread, scrollKey, intl } = this.props;
diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.js b/app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.js
index c2aff1b57..ae72179e2 100644
--- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/direct_timeline/index.js b/app/javascript/flavours/glitch/features/direct_timeline/index.js
index d55c63c2b..afd348988 100644
--- a/app/javascript/flavours/glitch/features/direct_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/direct_timeline/index.js
@@ -43,16 +43,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, conversationsMode } = this.props;
@@ -89,15 +89,15 @@ class DirectTimeline extends React.PureComponent {
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleLoadMoreTimeline = maxId => {
     this.props.dispatch(expandDirectTimeline({ maxId }));
-  }
+  };
 
   handleLoadMoreConversations = maxId => {
     this.props.dispatch(expandConversations({ maxId }));
-  }
+  };
 
   render () {
     const { intl, hasUnread, columnId, multiColumn, conversationsMode } = this.props;
diff --git a/app/javascript/flavours/glitch/features/directory/components/account_card.js b/app/javascript/flavours/glitch/features/directory/components/account_card.js
index ccc3dd3d2..3fddb5b81 100644
--- a/app/javascript/flavours/glitch/features/directory/components/account_card.js
+++ b/app/javascript/flavours/glitch/features/directory/components/account_card.js
@@ -118,7 +118,7 @@ class AccountCard extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -131,7 +131,7 @@ class AccountCard extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   handleFollow = () => {
     this.props.onFollow(this.props.account);
@@ -143,11 +143,11 @@ class AccountCard extends ImmutablePureComponent {
 
   handleMute = () => {
     this.props.onMute(this.props.account);
-  }
+  };
 
   handleEditProfile = () => {
     window.open('/settings/profile', '_blank');
-  }
+  };
 
   handleDismiss = (e) => {
     const { account, onDismiss } = this.props;
@@ -155,7 +155,7 @@ class AccountCard extends ImmutablePureComponent {
 
     e.preventDefault();
     e.stopPropagation();
-  }
+  };
 
   render() {
     const { account, intl } = this.props;
diff --git a/app/javascript/flavours/glitch/features/directory/index.js b/app/javascript/flavours/glitch/features/directory/index.js
index 94bcd578c..07875b3e1 100644
--- a/app/javascript/flavours/glitch/features/directory/index.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/emoji/emoji_utils.js b/app/javascript/flavours/glitch/features/emoji/emoji_utils.js
index dbf725c1f..571907a50 100644
--- a/app/javascript/flavours/glitch/features/emoji/emoji_utils.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/explore/index.js b/app/javascript/flavours/glitch/features/explore/index.js
index da0dc7f7c..4cbc5294b 100644
--- a/app/javascript/flavours/glitch/features/explore/index.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/explore/statuses.js b/app/javascript/flavours/glitch/features/explore/statuses.js
index 0a5c9de23..21768dd24 100644
--- a/app/javascript/flavours/glitch/features/explore/statuses.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/explore/suggestions.js b/app/javascript/flavours/glitch/features/explore/suggestions.js
index 52e5ce62b..1c9b99266 100644
--- a/app/javascript/flavours/glitch/features/explore/suggestions.js
+++ b/app/javascript/flavours/glitch/features/explore/suggestions.js
@@ -29,7 +29,7 @@ class Suggestions extends React.PureComponent {
   handleDismiss = (accountId) => {
     const { dispatch } = this.props;
     dispatch(dismissSuggestion(accountId));
-  }
+  };
 
   render () {
     const { isLoading, suggestions } = this.props;
diff --git a/app/javascript/flavours/glitch/features/favourited_statuses/index.js b/app/javascript/flavours/glitch/features/favourited_statuses/index.js
index a03e1a4eb..0667c205b 100644
--- a/app/javascript/flavours/glitch/features/favourited_statuses/index.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/favourites/index.js b/app/javascript/flavours/glitch/features/favourites/index.js
index 47c3279c4..ba58ed43b 100644
--- a/app/javascript/flavours/glitch/features/favourites/index.js
+++ b/app/javascript/flavours/glitch/features/favourites/index.js
@@ -48,15 +48,15 @@ class Favourites extends ImmutablePureComponent {
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleRefresh = () => {
     this.props.dispatch(fetchFavourites(this.props.params.statusId));
-  }
+  };
 
   render () {
     const { intl, accountIds, multiColumn } = this.props;
diff --git a/app/javascript/flavours/glitch/features/filters/select_filter.js b/app/javascript/flavours/glitch/features/filters/select_filter.js
index 5391766c9..57adb59cc 100644
--- a/app/javascript/flavours/glitch/features/filters/select_filter.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/follow_recommendations/components/account.js b/app/javascript/flavours/glitch/features/follow_recommendations/components/account.js
index 2c668da3e..7c8a71879 100644
--- a/app/javascript/flavours/glitch/features/follow_recommendations/components/account.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/follow_recommendations/index.js b/app/javascript/flavours/glitch/features/follow_recommendations/index.js
index d9d962b7c..11396d2a3 100644
--- a/app/javascript/flavours/glitch/features/follow_recommendations/index.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/followed_tags/index.js b/app/javascript/flavours/glitch/features/followed_tags/index.js
index 4a23afc2d..73203636c 100644
--- a/app/javascript/flavours/glitch/features/followed_tags/index.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js
index 7122c1905..10fd76865 100644
--- a/app/javascript/flavours/glitch/features/followers/index.js
+++ b/app/javascript/flavours/glitch/features/followers/index.js
@@ -107,11 +107,11 @@ class Followers extends ImmutablePureComponent {
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   render () {
     const { accountId, accountIds, hasMore, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl } = this.props;
diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js
index 4ad670105..5af4e60b6 100644
--- a/app/javascript/flavours/glitch/features/following/index.js
+++ b/app/javascript/flavours/glitch/features/following/index.js
@@ -107,11 +107,11 @@ class Following extends ImmutablePureComponent {
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   render () {
     const { accountId, accountIds, hasMore, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl } = this.props;
diff --git a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js
index 93f3c9428..5ff4f0523 100644
--- a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js
index f9d79013b..91b33c1dd 100644
--- a/app/javascript/flavours/glitch/features/getting_started/index.js
+++ b/app/javascript/flavours/glitch/features/getting_started/index.js
@@ -79,9 +79,9 @@ const badgeDisplay = (number, limit) => {
 
 const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
 
- export default @connect(makeMapStateToProps, mapDispatchToProps)
+export default @connect(makeMapStateToProps, mapDispatchToProps)
  @injectIntl
- class GettingStarted extends ImmutablePureComponent {
+class GettingStarted extends ImmutablePureComponent {
 
   static contextTypes = {
     router: PropTypes.object.isRequired,
@@ -164,7 +164,7 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
         <div key='9'>
           <ColumnLink key='lists' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
           {lists.filter(list => !columns.find(item => item.get('id') === 'LIST' && item.getIn(['params', 'id']) === list.get('id'))).map(list =>
-            <ColumnLink key={`list-${list.get('id')}`} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')} />
+            <ColumnLink key={`list-${list.get('id')}`} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')} />,
           )}
         </div>,
       ]);
diff --git a/app/javascript/flavours/glitch/features/getting_started_misc/index.js b/app/javascript/flavours/glitch/features/getting_started_misc/index.js
index de354d6b1..613b43df7 100644
--- a/app/javascript/flavours/glitch/features/getting_started_misc/index.js
+++ b/app/javascript/flavours/glitch/features/getting_started_misc/index.js
@@ -35,11 +35,11 @@ class gettingStartedMisc extends ImmutablePureComponent {
 
   openOnboardingModal = (e) => {
     this.props.dispatch(openModal('ONBOARDING'));
-  }
+  };
 
   openFeaturedAccountsModal = (e) => {
     this.props.dispatch(openModal('PINNED_ACCOUNTS_EDITOR'));
-  }
+  };
 
   render () {
     const { intl } = this.props;
diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js b/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js
index ede8907e5..ac7863ed3 100644
--- a/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js
@@ -38,7 +38,7 @@ class ColumnSettings extends React.PureComponent {
     } else {
       return tags;
     }
-  };
+  }
 
   onSelect = mode => value => {
     const oldValue = this.tags(mode);
@@ -98,7 +98,7 @@ class ColumnSettings extends React.PureComponent {
     default:
       return '';
     }
-  };
+  }
 
   render () {
     const { settings, onChange } = this.props;
diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js b/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
index 219dc0ec6..54a67804e 100644
--- a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/home_timeline/index.js b/app/javascript/flavours/glitch/features/home_timeline/index.js
index 5ed108ad2..b2bfd3f17 100644
--- a/app/javascript/flavours/glitch/features/home_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/home_timeline/index.js
@@ -60,24 +60,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);
@@ -116,7 +116,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/flavours/glitch/features/interaction_modal/index.js b/app/javascript/flavours/glitch/features/interaction_modal/index.js
index b71c041c9..3a54105a3 100644
--- a/app/javascript/flavours/glitch/features/interaction_modal/index.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/list_editor/components/edit_list_form.js b/app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.js
index a8cab2762..418c2a3e7 100644
--- a/app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/list_editor/components/search.js b/app/javascript/flavours/glitch/features/list_editor/components/search.js
index 192643f77..94782ba69 100644
--- a/app/javascript/flavours/glitch/features/list_editor/components/search.js
+++ b/app/javascript/flavours/glitch/features/list_editor/components/search.js
@@ -20,17 +20,17 @@ export default 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/flavours/glitch/features/list_timeline/index.js b/app/javascript/flavours/glitch/features/list_timeline/index.js
index a94c05c56..3f1503548 100644
--- a/app/javascript/flavours/glitch/features/list_timeline/index.js
+++ b/app/javascript/flavours/glitch/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;
@@ -126,20 +126,20 @@ class ListTimeline extends React.PureComponent {
       onConfirm: () => {
         dispatch(deleteList(id));
 
-        if (!!columnId) {
+        if (columnId) {
           dispatch(removeColumn(columnId));
         } else {
           this.context.router.history.push('/lists');
         }
       },
     }));
-  }
+  };
 
   handleRepliesPolicyChange = ({ target }) => {
     const { dispatch, list } = this.props;
     const { id } = this.props.params;
     this.props.dispatch(updateList(id, undefined, false, target.value));
-  }
+  };
 
   render () {
     const { hasUnread, columnId, multiColumn, list, intl } = this.props;
diff --git a/app/javascript/flavours/glitch/features/lists/components/new_list_form.js b/app/javascript/flavours/glitch/features/lists/components/new_list_form.js
index cc78d30b7..e78a6a3bc 100644
--- a/app/javascript/flavours/glitch/features/lists/components/new_list_form.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/local_settings/navigation/index.js b/app/javascript/flavours/glitch/features/local_settings/navigation/index.js
index 98dda182f..cc1f3df6d 100644
--- a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/navigation/index.js
@@ -72,7 +72,7 @@ class LocalSettingsNavigation extends React.PureComponent {
         />
         <LocalSettingsNavigationItem
           active={index === 5}
-          href={ preferencesLink }
+          href={preferencesLink}
           index={5}
           icon='cog'
           title={intl.formatMessage(messages.preferences)}
diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js b/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js
index 739c5ebae..a4d1b40fa 100644
--- a/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js
@@ -26,7 +26,7 @@ export default class LocalSettingsPage extends React.PureComponent {
       onNavigate(index);
       e.preventDefault();
     }
-  }
+  };
 
   render () {
     const { handleClick } = this;
diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.js b/app/javascript/flavours/glitch/features/local_settings/page/index.js
index d1573da9c..a8120663c 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js
@@ -60,7 +60,7 @@ class LocalSettingsPage extends React.PureComponent {
           onChange={onChange}
         >
           <FormattedMessage id='settings.hicolor_privacy_icons' defaultMessage='High color privacy icons' />
-          <span className='hint'><FormattedMessage id='settings.hicolor_privacy_icons.hint' defaultMessage="Display privacy icons in bright and easily distinguishable colors" /></span>
+          <span className='hint'><FormattedMessage id='settings.hicolor_privacy_icons.hint' defaultMessage='Display privacy icons in bright and easily distinguishable colors' /></span>
         </LocalSettingsPageItem>
         <LocalSettingsPageItem
           settings={settings}
@@ -77,7 +77,7 @@ class LocalSettingsPage extends React.PureComponent {
           onChange={onChange}
         >
           <FormattedMessage id='settings.tag_misleading_links' defaultMessage='Tag misleading links' />
-          <span className='hint'><FormattedMessage id='settings.tag_misleading_links.hint' defaultMessage="Add a visual indication with the link target host to every link not mentioning it explicitly" /></span>
+          <span className='hint'><FormattedMessage id='settings.tag_misleading_links.hint' defaultMessage='Add a visual indication with the link target host to every link not mentioning it explicitly' /></span>
         </LocalSettingsPageItem>
         <LocalSettingsPageItem
           settings={settings}
@@ -100,7 +100,7 @@ class LocalSettingsPage extends React.PureComponent {
             id='mastodon-settings--notifications-tab_badge'
             onChange={onChange}
           >
-            <FormattedMessage id='settings.notifications.tab_badge' defaultMessage="Unread notifications badge" />
+            <FormattedMessage id='settings.notifications.tab_badge' defaultMessage='Unread notifications badge' />
             <span className='hint'><FormattedMessage id='settings.notifications.tab_badge.hint' defaultMessage="Display a badge for unread notifications in the column icons when the notifications column isn't open" /></span>
           </LocalSettingsPageItem>
           <LocalSettingsPageItem
@@ -110,7 +110,7 @@ class LocalSettingsPage extends React.PureComponent {
             onChange={onChange}
           >
             <FormattedMessage id='settings.notifications.favicon_badge' defaultMessage='Unread notifications favicon badge' />
-            <span className='hint'><FormattedMessage id='settings.notifications.favicon_badge.hint' defaultMessage="Add a badge for unread notifications to the favicon" /></span>
+            <span className='hint'><FormattedMessage id='settings.notifications.favicon_badge.hint' defaultMessage='Add a badge for unread notifications to the favicon' /></span>
           </LocalSettingsPageItem>
         </section>
 
@@ -306,7 +306,7 @@ class LocalSettingsPage extends React.PureComponent {
                         defaultMessage='user preferences'
                       />
                     </a>
-                  )
+                  ),
                 }}
               />
             </span>
@@ -414,7 +414,7 @@ class LocalSettingsPage extends React.PureComponent {
             onChange={onChange}
             dependsOn={[['collapsed', 'enabled']]}
             dependsOnNot={[['collapsed', 'auto', 'all']]}
-            inputProps={{type: 'number', min: '200', max: '999'}}
+            inputProps={{ type: 'number', min: '200', max: '999' }}
           >
             <FormattedMessage id='settings.auto_collapse_height' defaultMessage='Height (in pixels) for a toot to be considered lengthy' />
           </LocalSettingsPageItem>
diff --git a/app/javascript/flavours/glitch/features/local_settings/page/item/index.js b/app/javascript/flavours/glitch/features/local_settings/page/item/index.js
index 86da640ba..41c0676a2 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/item/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/item/index.js
@@ -31,7 +31,7 @@ export default class LocalSettingsPageItem extends React.PureComponent {
     if (options && options.length > 0) onChange(item, target.value);
     else if (placeholder) onChange(item, target.value);
     else onChange(item, target.checked);
-  }
+  };
 
   render () {
     const { handleChange } = this;
@@ -55,7 +55,7 @@ export default class LocalSettingsPageItem extends React.PureComponent {
         let optionId = `${id}--${opt.value}`;
         return (
           <label htmlFor={optionId}>
-            <input 
+            <input
               type='radio'
               name={id}
               id={optionId}
@@ -109,7 +109,7 @@ export default class LocalSettingsPageItem extends React.PureComponent {
             onChange={handleChange}
             disabled={!enabled}
             {...inputProps}
-	    />
+          />
           {children}
         </label>
       </div>
diff --git a/app/javascript/flavours/glitch/features/notifications/components/admin_report.js b/app/javascript/flavours/glitch/features/notifications/components/admin_report.js
index 4662bd953..556df8f66 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/admin_report.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/admin_report.js
@@ -32,28 +32,28 @@ export default class AdminReport extends ImmutablePureComponent {
   handleMoveUp = () => {
     const { notification, onMoveUp } = this.props;
     onMoveUp(notification.get('id'));
-  }
+  };
 
   handleMoveDown = () => {
     const { notification, onMoveDown } = this.props;
     onMoveDown(notification.get('id'));
-  }
+  };
 
   handleOpen = () => {
     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);
-  }
+  };
 
   getHandlers () {
     return {
diff --git a/app/javascript/flavours/glitch/features/notifications/components/admin_signup.js b/app/javascript/flavours/glitch/features/notifications/components/admin_signup.js
index 355ebef94..ead2a9701 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/admin_signup.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/admin_signup.js
@@ -26,28 +26,28 @@ export default class NotificationFollow extends ImmutablePureComponent {
   handleMoveUp = () => {
     const { notification, onMoveUp } = this.props;
     onMoveUp(notification.get('id'));
-  }
+  };
 
   handleMoveDown = () => {
     const { notification, onMoveDown } = this.props;
     onMoveDown(notification.get('id'));
-  }
+  };
 
   handleOpen = () => {
     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);
-  }
+  };
 
   getHandlers () {
     return {
diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
index 64fd98bd9..1c04218ba 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
@@ -27,7 +27,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/flavours/glitch/features/notifications/components/follow.js b/app/javascript/flavours/glitch/features/notifications/components/follow.js
index b8fad19d0..434d6609d 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/follow.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/follow.js
@@ -26,28 +26,28 @@ export default class NotificationFollow extends ImmutablePureComponent {
   handleMoveUp = () => {
     const { notification, onMoveUp } = this.props;
     onMoveUp(notification.get('id'));
-  }
+  };
 
   handleMoveDown = () => {
     const { notification, onMoveDown } = this.props;
     onMoveDown(notification.get('id'));
-  }
+  };
 
   handleOpen = () => {
     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);
-  }
+  };
 
   getHandlers () {
     return {
diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow_request.js b/app/javascript/flavours/glitch/features/notifications/components/follow_request.js
index 69b92a06f..a3fdf8a61 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/follow_request.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/follow_request.js
@@ -32,28 +32,28 @@ class FollowRequest extends ImmutablePureComponent {
   handleMoveUp = () => {
     const { notification, onMoveUp } = this.props;
     onMoveUp(notification.get('id'));
-  }
+  };
 
   handleMoveDown = () => {
     const { notification, onMoveDown } = this.props;
     onMoveDown(notification.get('id'));
-  }
+  };
 
   handleOpen = () => {
     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);
-  }
+  };
 
   getHandlers () {
     return {
diff --git a/app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.js b/app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.js
index dd163225e..7b6ab0c7d 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/notifications/components/overlay.js b/app/javascript/flavours/glitch/features/notifications/components/overlay.js
index f3ccafc06..21d3f8acf 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/overlay.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/overlay.js
@@ -29,7 +29,7 @@ class NotificationOverlay extends ImmutablePureComponent {
     const mark = !this.props.notification.get('markedForDelete');
     const id = this.props.notification.get('id');
     this.props.onMarkForDelete(id, mark);
-  }
+  };
 
   render () {
     const { notification, show, intl } = this.props;
diff --git a/app/javascript/flavours/glitch/features/notifications/components/pill_bar_button.js b/app/javascript/flavours/glitch/features/notifications/components/pill_bar_button.js
index 223b7f75f..2f0b48ef9 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/pill_bar_button.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/pill_bar_button.js
@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import classNames from 'classnames'
+import classNames from 'classnames';
 
 export default class PillBarButton extends React.PureComponent {
 
@@ -12,12 +12,12 @@ export default class PillBarButton extends React.PureComponent {
     label: PropTypes.node.isRequired,
     onChange: PropTypes.func.isRequired,
     disabled: PropTypes.bool,
-  }
+  };
 
   onChange = () => {
     const { settings, settingPath } = this.props;
     this.props.onChange(settingPath, !settings.getIn(settingPath));
-  }
+  };
 
   render () {
     const { prefix, settings, settingPath, label, disabled } = this.props;
diff --git a/app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js b/app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js
index e472f7c4f..dc7b89b7f 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js
@@ -14,11 +14,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, meta, defaultValue, disabled } = this.props;
diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js
index fc42a4de4..fff365617 100644
--- a/app/javascript/flavours/glitch/features/notifications/index.js
+++ b/app/javascript/flavours/glitch/features/notifications/index.js
@@ -158,30 +158,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;
@@ -213,16 +213,16 @@ class Notifications extends React.PureComponent {
 
   handleTransitionEndNCD = () => {
     this.setState({ animatingNCD: false });
-  }
+  };
 
   onEnterCleaningMode = () => {
     this.setState({ animatingNCD: true });
     this.props.onEnterCleaningMode(!this.props.notifCleaningActive);
-  }
+  };
 
   handleMarkAsRead = () => {
     this.props.onMarkAsRead();
-  }
+  };
 
   render () {
     const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
index f05a763e0..bc312d530 100644
--- a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
+++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
@@ -114,7 +114,7 @@ class Footer extends ImmutablePureComponent {
   _performReblog = (privacy) => {
     const { dispatch, status } = this.props;
     dispatch(reblog(status, privacy));
-  }
+  };
 
   handleReblogClick = e => {
     const { dispatch, status } = this.props;
@@ -151,7 +151,7 @@ class Footer extends ImmutablePureComponent {
     }
 
     router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
-  }
+  };
 
   render () {
     const { status, intl, showReplyCount, withOpenButton } = this.props;
diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/index.js b/app/javascript/flavours/glitch/features/picture_in_picture/index.js
index 3e6a20faa..d445b6d58 100644
--- a/app/javascript/flavours/glitch/features/picture_in_picture/index.js
+++ b/app/javascript/flavours/glitch/features/picture_in_picture/index.js
@@ -35,7 +35,7 @@ class PictureInPicture extends React.Component {
   handleClose = () => {
     const { dispatch } = this.props;
     dispatch(removePictureInPicture());
-  }
+  };
 
   render () {
     const { type, src, currentTime, accountId, statusId, left } = this.props;
diff --git a/app/javascript/flavours/glitch/features/pinned_accounts_editor/containers/search_container.js b/app/javascript/flavours/glitch/features/pinned_accounts_editor/containers/search_container.js
index 5a1efce0a..db586ecf7 100644
--- a/app/javascript/flavours/glitch/features/pinned_accounts_editor/containers/search_container.js
+++ b/app/javascript/flavours/glitch/features/pinned_accounts_editor/containers/search_container.js
@@ -4,7 +4,7 @@ import { injectIntl } from 'react-intl';
 import {
   fetchPinnedAccountsSuggestions,
   clearPinnedAccountsSuggestions,
-  changePinnedAccountsSuggestions
+  changePinnedAccountsSuggestions,
 } from '../../../actions/accounts';
 import Search from 'flavours/glitch/features/list_editor/components/search';
 
diff --git a/app/javascript/flavours/glitch/features/pinned_statuses/index.js b/app/javascript/flavours/glitch/features/pinned_statuses/index.js
index eeeab46ab..b7bd46fbe 100644
--- a/app/javascript/flavours/glitch/features/pinned_statuses/index.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/public_timeline/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js
index 5091bfb90..97b756658 100644
--- a/app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js
+++ b/app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js
@@ -2,7 +2,7 @@ import { connect } from 'react-redux';
 import ColumnSettings from '../components/column_settings';
 import { changeSetting } from 'flavours/glitch/actions/settings';
 import { changeColumnParams } from 'flavours/glitch/actions/columns';
- 
+
 const mapStateToProps = (state, { columnId }) => {
   const uuid = columnId;
   const columns = state.getIn(['settings', 'columns']);
diff --git a/app/javascript/flavours/glitch/features/public_timeline/index.js b/app/javascript/flavours/glitch/features/public_timeline/index.js
index a61a47de1..810643f97 100644
--- a/app/javascript/flavours/glitch/features/public_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/public_timeline/index.js
@@ -68,16 +68,16 @@ class PublicTimeline extends React.PureComponent {
     } else {
       dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, onlyRemote, allowLocalOnly } }));
     }
-  }
+  };
 
   handleMove = (dir) => {
     const { columnId, dispatch } = this.props;
     dispatch(moveColumn(columnId, dir));
-  }
+  };
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   componentDidMount () {
     const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props;
@@ -116,13 +116,13 @@ class PublicTimeline extends React.PureComponent {
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleLoadMore = maxId => {
     const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props;
 
     dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote, allowLocalOnly }));
-  }
+  };
 
   render () {
     const { intl, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote, allowLocalOnly } = this.props;
diff --git a/app/javascript/flavours/glitch/features/reblogs/index.js b/app/javascript/flavours/glitch/features/reblogs/index.js
index b097ff9d7..46b1ed4e5 100644
--- a/app/javascript/flavours/glitch/features/reblogs/index.js
+++ b/app/javascript/flavours/glitch/features/reblogs/index.js
@@ -48,15 +48,15 @@ class Reblogs extends ImmutablePureComponent {
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleRefresh = () => {
     this.props.dispatch(fetchReblogs(this.props.params.statusId));
-  }
+  };
 
   render () {
     const { intl, accountIds, multiColumn } = this.props;
diff --git a/app/javascript/flavours/glitch/features/report/components/option.js b/app/javascript/flavours/glitch/features/report/components/option.js
index 7e94f0654..6ecfc7a24 100644
--- a/app/javascript/flavours/glitch/features/report/components/option.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/standalone/compose/index.js b/app/javascript/flavours/glitch/features/standalone/compose/index.js
index b33c21cb5..c53442435 100644
--- a/app/javascript/flavours/glitch/features/standalone/compose/index.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/status/components/action_bar.js b/app/javascript/flavours/glitch/features/status/components/action_bar.js
index 73913dd49..4901fc4cc 100644
--- a/app/javascript/flavours/glitch/features/status/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/status/components/action_bar.js
@@ -68,75 +68,75 @@ class ActionBar extends React.PureComponent {
 
   handleReplyClick = () => {
     this.props.onReply(this.props.status);
-  }
+  };
 
   handleReblogClick = (e) => {
     this.props.onReblog(this.props.status, e);
-  }
+  };
 
   handleFavouriteClick = (e) => {
     this.props.onFavourite(this.props.status, e);
-  }
+  };
 
   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 = () => {
     this.props.onMute(this.props.status.get('account'));
-  }
+  };
 
   handleConversationMuteClick = () => {
     this.props.onMuteConversation(this.props.status);
-  }
+  };
 
   handleBlockClick = () => {
     this.props.onBlock(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, intl } = this.props;
diff --git a/app/javascript/flavours/glitch/features/status/components/card.js b/app/javascript/flavours/glitch/features/status/components/card.js
index 2d2e49eb8..6a306ed14 100644
--- a/app/javascript/flavours/glitch/features/status/components/card.js
+++ b/app/javascript/flavours/glitch/features/status/components/card.js
@@ -138,7 +138,7 @@ export default class Card extends React.PureComponent {
     } else {
       this.setState({ embedded: true });
     }
-  }
+  };
 
   setRef = c => {
     this.node = c;
@@ -146,17 +146,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/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
index 907fc3f1c..644881fa5 100644
--- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js
+++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
@@ -56,28 +56,28 @@ class DetailedStatus extends ImmutablePureComponent {
   handleAccountClick = (e) => {
     if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) {
       e.preventDefault();
-      let state = {...this.context.router.history.location.state};
+      let state = { ...this.context.router.history.location.state };
       state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
       this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`, state);
     }
 
     e.stopPropagation();
-  }
+  };
 
   parseClick = (e, destination) => {
     if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) {
       e.preventDefault();
-      let state = {...this.context.router.history.location.state};
+      let state = { ...this.context.router.history.location.state };
       state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
       this.context.router.history.push(destination, state);
     }
 
     e.stopPropagation();
-  }
+  };
 
   handleOpenVideo = (options) => {
     this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), options);
-  }
+  };
 
   _measureHeight (heightJustChanged) {
     if (this.props.measureHeight && this.node) {
@@ -92,7 +92,7 @@ class DetailedStatus extends ImmutablePureComponent {
   setRef = c => {
     this.node = c;
     this._measureHeight();
-  }
+  };
 
   componentDidUpdate (prevProps, prevState) {
     this._measureHeight(prevState.height !== this.state.height);
@@ -100,7 +100,7 @@ class DetailedStatus extends ImmutablePureComponent {
 
   handleChildUpdate = () => {
     this._measureHeight();
-  }
+  };
 
   handleModalLink = e => {
     e.preventDefault();
@@ -114,12 +114,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/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index c22e7f0bd..9b49d41e4 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -263,15 +263,15 @@ class Status extends ImmutablePureComponent {
     } else if (this.props.status.get('spoiler_text')) {
       this.setExpansion(!this.state.isExpanded);
     }
-  }
+  };
 
   handleToggleMediaVisibility = () => {
     this.setState({ showMedia: !this.state.showMedia });
-  }
+  };
 
   handleModalFavourite = (status) => {
     this.props.dispatch(favourite(status));
-  }
+  };
 
   handleFavouriteClick = (status, e) => {
     const { dispatch } = this.props;
@@ -294,7 +294,7 @@ class Status extends ImmutablePureComponent {
         url: status.get('url'),
       }));
     }
-  }
+  };
 
   handlePin = (status) => {
     if (status.get('pinned')) {
@@ -302,7 +302,7 @@ class Status extends ImmutablePureComponent {
     } else {
       this.props.dispatch(pin(status));
     }
-  }
+  };
 
   handleReplyClick = (status) => {
     const { askReplyConfirmation, dispatch, intl } = this.props;
@@ -326,7 +326,7 @@ class Status extends ImmutablePureComponent {
         url: status.get('url'),
       }));
     }
-  }
+  };
 
   handleModalReblog = (status, privacy) => {
     const { dispatch } = this.props;
@@ -336,7 +336,7 @@ class Status extends ImmutablePureComponent {
     } else {
       dispatch(reblog(status, privacy));
     }
-  }
+  };
 
   handleReblogClick = (status, e) => {
     const { settings, dispatch } = this.props;
@@ -357,7 +357,7 @@ class Status extends ImmutablePureComponent {
         url: status.get('url'),
       }));
     }
-  }
+  };
 
   handleBookmarkClick = (status) => {
     if (status.get('bookmarked')) {
@@ -365,7 +365,7 @@ class Status extends ImmutablePureComponent {
     } else {
       this.props.dispatch(bookmark(status));
     }
-  }
+  };
 
   handleDeleteClick = (status, history, withRedraft = false) => {
     const { dispatch, intl } = this.props;
@@ -379,27 +379,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;
@@ -413,11 +413,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')) {
@@ -425,7 +425,7 @@ class Status extends ImmutablePureComponent {
     } else {
       this.props.dispatch(muteStatus(status.get('id')));
     }
-  }
+  };
 
   handleToggleAll = () => {
     const { status, ancestorsIds, descendantsIds, settings } = this.props;
@@ -442,7 +442,7 @@ class Status extends ImmutablePureComponent {
     }
 
     this.setState({ isExpanded: !isExpanded, threadExpanded: !isExpanded });
-  }
+  };
 
   handleTranslate = status => {
     const { dispatch } = this.props;
@@ -452,61 +452,61 @@ 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') }));
-  }
+  };
 
   handleHotkeyToggleSensitive = () => {
     this.handleToggleMediaVisibility();
-  }
+  };
 
   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);
-  }
+  };
 
   handleHotkeyBookmark = () => {
     this.handleBookmarkClick(this.props.status);
-  }
+  };
 
   handleHotkeyMention = e => {
     e.preventDefault();
     this.handleMentionClick(this.props.status);
-  }
+  };
 
   handleHotkeyOpenProfile = () => {
-    let state = {...this.context.router.history.location.state};
+    let state = { ...this.context.router.history.location.state };
     state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
     this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`, state);
-  }
+  };
 
   handleMoveUp = id => {
     const { status, ancestorsIds, descendantsIds } = this.props;
@@ -523,7 +523,7 @@ class Status extends ImmutablePureComponent {
         this._selectChild(index - 1, true);
       }
     }
-  }
+  };
 
   handleMoveDown = id => {
     const { status, ancestorsIds, descendantsIds } = this.props;
@@ -540,7 +540,7 @@ class Status extends ImmutablePureComponent {
         this._selectChild(index + 1, false);
       }
     }
-  }
+  };
 
   _selectChild (index, align_top) {
     const container = this.node;
@@ -558,7 +558,7 @@ class Status extends ImmutablePureComponent {
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   renderChildren (list) {
     return list.map(id => (
@@ -575,15 +575,15 @@ class Status extends ImmutablePureComponent {
 
   setExpansion = value => {
     this.setState({ isExpanded: value });
-  }
+  };
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   setColumnRef = c => {
     this.column = c;
-  }
+  };
 
   componentDidUpdate (prevProps) {
     if (this.props.params.statusId && (this.props.params.statusId !== prevProps.params.statusId || prevProps.ancestorsIds.size < this.props.ancestorsIds.size)) {
@@ -605,7 +605,7 @@ class Status extends ImmutablePureComponent {
 
   onFullScreenChange = () => {
     this.setState({ fullscreen: isFullscreen() });
-  }
+  };
 
   render () {
     let ancestors, descendants;
diff --git a/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.js b/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.js
index fa69d82a4..35083503c 100644
--- a/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/ui/components/actions_modal.js b/app/javascript/flavours/glitch/features/ui/components/actions_modal.js
index aae2e4426..c6e3ee37c 100644
--- a/app/javascript/flavours/glitch/features/ui/components/actions_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/actions_modal.js
@@ -52,7 +52,7 @@ export default class ActionsModal extends ImmutablePureComponent {
         </a>
       </li>
     );
-  }
+  };
 
   render () {
     const status = this.props.status && (
diff --git a/app/javascript/flavours/glitch/features/ui/components/block_modal.js b/app/javascript/flavours/glitch/features/ui/components/block_modal.js
index a07baeaa6..6c9d2043c 100644
--- a/app/javascript/flavours/glitch/features/ui/components/block_modal.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/ui/components/boost_modal.js b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js
index 8d9496bb9..a65b84e20 100644
--- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js
@@ -58,17 +58,17 @@ class BoostModal extends ImmutablePureComponent {
   handleReblog = () => {
     this.props.onReblog(this.props.status, this.props.privacy);
     this.props.onClose();
-  }
+  };
 
   handleAccountClick = (e) => {
     if (e.button === 0) {
       e.preventDefault();
       this.props.onClose();
-      let state = {...this.context.router.history.location.state};
+      let state = { ...this.context.router.history.location.state };
       state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
       this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`, state);
     }
-  }
+  };
 
   _findContainer = () => {
     return document.getElementsByClassName('modal-root__container')[0];
@@ -76,7 +76,7 @@ class BoostModal extends ImmutablePureComponent {
 
   setRef = (c) => {
     this.button = c;
-  }
+  };
 
   render () {
     const { status, missingMediaDescription, privacy, intl } = this.props;
@@ -116,9 +116,9 @@ class BoostModal extends ImmutablePureComponent {
         <div className='boost-modal__action-bar'>
           <div>
             { missingMediaDescription ?
-                <FormattedMessage id='boost_modal.missing_description' defaultMessage='This toot contains some media without description' />
+              <FormattedMessage id='boost_modal.missing_description' defaultMessage='This toot contains some media without description' />
               :
-                <FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' /></span> }} />
+              <FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' /></span> }} />
             }
           </div>
 
diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle.js b/app/javascript/flavours/glitch/features/ui/components/bundle.js
index 8f0d7b8b1..27b13ecfe 100644
--- a/app/javascript/flavours/glitch/features/ui/components/bundle.js
+++ b/app/javascript/flavours/glitch/features/ui/components/bundle.js
@@ -15,7 +15,7 @@ class Bundle extends React.Component {
     onFetch: PropTypes.func,
     onFetchSuccess: PropTypes.func,
     onFetchFail: PropTypes.func,
-  }
+  };
 
   static defaultProps = {
     loading: emptyComponent,
@@ -24,14 +24,14 @@ class Bundle extends React.Component {
     onFetch: noop,
     onFetchSuccess: noop,
     onFetchFail: noop,
-  }
+  };
 
-  static cache = {}
+  static cache = {};
 
   state = {
     mod: undefined,
     forceRender: false,
-  }
+  };
 
   componentWillMount() {
     this.load(this.props);
@@ -84,7 +84,7 @@ class Bundle extends React.Component {
         this.setState({ mod: null });
         onFetchFail(error);
       });
-  }
+  };
 
   render() {
     const { loading: Loading, error: Error, children, renderDelay } = this.props;
diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js b/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js
index 7cbe1413d..88304dc36 100644
--- a/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/ui/components/bundle_modal_error.js b/app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.js
index 2c14a1e5c..b79105450 100644
--- a/app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.js
+++ b/app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.js
@@ -16,11 +16,11 @@ class BundleModalError extends React.Component {
     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/flavours/glitch/features/ui/components/column.js b/app/javascript/flavours/glitch/features/ui/components/column.js
index e9c1e2f87..cc2abc43a 100644
--- a/app/javascript/flavours/glitch/features/ui/components/column.js
+++ b/app/javascript/flavours/glitch/features/ui/components/column.js
@@ -25,7 +25,7 @@ export default class Column extends React.PureComponent {
     }
 
     this._interruptScrollAnimation = scrollTop(scrollable);
-  }
+  };
 
   scrollTop () {
     const scrollable = this.props.bindToDocument ? document.scrollingElement : this.node.querySelector('.scrollable');
@@ -42,11 +42,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, name } = this.props;
diff --git a/app/javascript/flavours/glitch/features/ui/components/column_header.js b/app/javascript/flavours/glitch/features/ui/components/column_header.js
index 528ff73a6..151476f8b 100644
--- a/app/javascript/flavours/glitch/features/ui/components/column_header.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/ui/components/column_link.js b/app/javascript/flavours/glitch/features/ui/components/column_link.js
index bd1c20b47..dcdac077f 100644
--- a/app/javascript/flavours/glitch/features/ui/components/column_link.js
+++ b/app/javascript/flavours/glitch/features/ui/components/column_link.js
@@ -30,7 +30,7 @@ const ColumnLink = ({ icon, text, to, onClick, href, method, badge, transparent,
       e.preventDefault();
       e.stopPropagation();
       return onClick(e);
-    }
+    };
     return (
       <a href='#' onClick={onClick && handleOnClick} className={className} title={text} {...other} tabIndex='0'>
         {iconElement}
diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js
index 993a50796..3b3b0d58f 100644
--- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js
+++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js
@@ -59,7 +59,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
 
   state = {
     renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches),
-  }
+  };
 
   componentDidMount() {
     if (!this.props.singleColumn) {
@@ -113,7 +113,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
 
   handleLayoutChange = (e) => {
     this.setState({ renderComposePanel: !e.matches });
-  }
+  };
 
   handleWheel = () => {
     if (typeof this._interruptScrollAnimation !== 'function') {
@@ -121,19 +121,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, navbarUnder, openSettings } = this.props;
diff --git a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js
index dde252a61..34c194c99 100644
--- a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js
+++ b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js
@@ -55,4 +55,4 @@ class ComposePanel extends React.PureComponent {
     );
   }
 
-};
+}
diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js b/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js
index a665b9fb1..94935de5d 100644
--- a/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js
@@ -34,24 +34,24 @@ class ConfirmationModal extends React.PureComponent {
     if (this.props.onDoNotAsk && this.doNotAskCheckbox.checked) {
       this.props.onDoNotAsk();
     }
-  }
+  };
 
   handleSecondary = () => {
     this.props.onClose();
     this.props.onSecondary();
-  }
+  };
 
   handleCancel = () => {
     this.props.onClose();
-  }
+  };
 
   setRef = (c) => {
     this.button = c;
-  }
+  };
 
   setDoNotAskRef = (c) => {
     this.doNotAskCheckbox = c;
-  }
+  };
 
   render () {
     const { message, confirm, secondary, onDoNotAsk } = this.props;
diff --git a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js
index 68f04cb21..37f52b014 100644
--- a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js
@@ -30,11 +30,11 @@ class DeprecatedSettingsModal extends React.PureComponent {
   handleClick = () => {
     this.props.onConfirm();
     this.props.onClose();
-  }
+  };
 
   setRef = (c) => {
     this.button = c;
-  }
+  };
 
   render () {
     const { settings, intl } = this.props;
diff --git a/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.js b/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.js
index c861d4d81..35933bedb 100644
--- a/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.js
+++ b/app/javascript/flavours/glitch/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;
@@ -89,4 +89,4 @@ class DisabledAccountBanner extends React.PureComponent {
     );
   }
 
-};
+}
diff --git a/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js b/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js
index 0d10204fc..c8ea33a0e 100644
--- a/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js
@@ -279,7 +279,7 @@ class DoodleModal extends ImmutablePureComponent {
     this.swapped = false;
     window.addEventListener('keyup', this.handleKeyUp, false);
     window.addEventListener('keydown', this.handleKeyDown, false);
-  };
+  }
 
   /**
    * Tear component down
@@ -575,7 +575,7 @@ class DoodleModal extends ImmutablePureComponent {
             <div>
               <select aria-label='Canvas size' onInput={this.changeSize} defaultValue={this.size}>
                 { Object.values(mapValues(DOODLE_SIZES, (val, k) =>
-                  <option key={k} value={k}>{val[2]}</option>
+                  <option key={k} value={k}>{val[2]}</option>,
                 )) }
               </select>
             </div>
@@ -602,7 +602,7 @@ class DoodleModal extends ImmutablePureComponent {
                       'foreground': this.fg === c[0],
                       'background': this.bg === c[0],
                     })}
-                  />
+                  />,
               )
             }
           </div>
diff --git a/app/javascript/flavours/glitch/features/ui/components/embed_modal.js b/app/javascript/flavours/glitch/features/ui/components/embed_modal.js
index 624b68f7e..92bfa79c4 100644
--- a/app/javascript/flavours/glitch/features/ui/components/embed_modal.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/ui/components/favourite_modal.js b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js
index d7f671d58..78cbfeb51 100644
--- a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js
@@ -38,21 +38,21 @@ class FavouriteModal extends ImmutablePureComponent {
   handleFavourite = () => {
     this.props.onFavourite(this.props.status);
     this.props.onClose();
-  }
+  };
 
   handleAccountClick = (e) => {
     if (e.button === 0) {
       e.preventDefault();
       this.props.onClose();
-      let state = {...this.context.router.history.location.state};
+      let state = { ...this.context.router.history.location.state };
       state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
       this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`, state);
     }
-  }
+  };
 
   setRef = (c) => {
     this.button = c;
-  }
+  };
 
   render () {
     const { status, intl } = this.props;
diff --git a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js
index fb432cf9c..e36657fab 100644
--- a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js
+++ b/app/javascript/flavours/glitch/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,11 +169,11 @@ 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)) {
@@ -181,11 +182,11 @@ class FocalPointModal extends ImmutablePureComponent {
       this.props.onChangeDescription(e.target.value);
       this.handleSubmit();
     }
-  }
+  };
 
   handleSubmit = () => {
     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/flavours/glitch/features/ui/components/image_loader.js b/app/javascript/flavours/glitch/features/ui/components/image_loader.js
index dfa0efe49..92aeef5c4 100644
--- a/app/javascript/flavours/glitch/features/ui/components/image_loader.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/ui/components/link_footer.js b/app/javascript/flavours/glitch/features/ui/components/link_footer.js
index ac0c78674..c4bea9f31 100644
--- a/app/javascript/flavours/glitch/features/ui/components/link_footer.js
+++ b/app/javascript/flavours/glitch/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;
@@ -93,4 +93,4 @@ class LinkFooter extends React.PureComponent {
     );
   }
 
-};
+}
diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.js b/app/javascript/flavours/glitch/features/ui/components/media_modal.js
index ec3af857d..24559264e 100644
--- a/app/javascript/flavours/glitch/features/ui/components/media_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.js
@@ -47,27 +47,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'));
@@ -76,7 +76,7 @@ class MediaModal extends ImmutablePureComponent {
       index: index % this.props.media.size,
       zoomButtonHidden: true,
     });
-  }
+  };
 
   handleKeyDown = (e) => {
     switch(e.key) {
@@ -91,7 +91,7 @@ class MediaModal extends ImmutablePureComponent {
       e.stopPropagation();
       break;
     }
-  }
+  };
 
   componentDidMount () {
     window.addEventListener('keydown', this.handleKeyDown, false);
diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.js b/app/javascript/flavours/glitch/features/ui/components/modal_root.js
index 379f57cbb..d04a2d53a 100644
--- a/app/javascript/flavours/glitch/features/ui/components/modal_root.js
+++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.js
@@ -76,7 +76,7 @@ export default class ModalRoot extends React.PureComponent {
   };
 
   componentDidUpdate () {
-    if (!!this.props.type) {
+    if (this.props.type) {
       document.body.classList.add('with-modals--active');
       document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
     } else {
@@ -87,17 +87,17 @@ export default class ModalRoot extends React.PureComponent {
 
   setBackgroundColor = color => {
     this.setState({ backgroundColor: color });
-  }
+  };
 
   renderLoading = modalId => () => {
     return ['MEDIA', 'VIDEO', 'BOOST', 'FAVOURITE', 'DOODLE', '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;
@@ -110,14 +110,14 @@ 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;
-  }
+  };
 
   // prevent closing of modal when clicking the overlay
-  noop = () => {}
+  noop = () => {};
 
   render () {
     const { type, props, ignoreFocus } = this.props;
diff --git a/app/javascript/flavours/glitch/features/ui/components/mute_modal.js b/app/javascript/flavours/glitch/features/ui/components/mute_modal.js
index 7d25db316..f8bb9a364 100644
--- a/app/javascript/flavours/glitch/features/ui/components/mute_modal.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
index 611fae1ce..d972fe3b5 100644
--- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
@@ -49,7 +49,7 @@ const PageTwo = ({ intl, myAccount }) => (
           privacy='public'
           text='Awoo! #introductions'
           spoilerText=''
-          suggestions={ [] }
+          suggestions={[]}
         />
       </div>
     </div>
@@ -195,7 +195,7 @@ class OnboardingModal extends React.PureComponent {
       <PageFour domain={domain} intl={intl} />,
       <PageSix admin={admin} domain={domain} />,
     ];
-  };
+  }
 
   componentDidMount() {
     window.addEventListener('keyup', this.handleKeyUp);
@@ -208,30 +208,30 @@ class OnboardingModal extends React.PureComponent {
   handleSkip = (e) => {
     e.preventDefault();
     this.props.onClose();
-  }
+  };
 
   handleDot = (e) => {
     const i = Number(e.currentTarget.getAttribute('data-index'));
     e.preventDefault();
     this.setState({ currentIndex: i });
-  }
+  };
 
   handlePrev = () => {
     this.setState(({ currentIndex }) => ({
       currentIndex: Math.max(0, currentIndex - 1),
     }));
-  }
+  };
 
   handleNext = () => {
     const { pages } = this;
     this.setState(({ currentIndex }) => ({
       currentIndex: Math.min(currentIndex + 1, pages.length - 1),
     }));
-  }
+  };
 
   handleSwipe = (index) => {
     this.setState({ currentIndex: index });
-  }
+  };
 
   handleKeyUp = ({ key }) => {
     switch (key) {
@@ -242,11 +242,11 @@ class OnboardingModal extends React.PureComponent {
       this.handleNext();
       break;
     }
-  }
+  };
 
   handleClose = () => {
     this.props.onClose();
-  }
+  };
 
   render () {
     const { pages } = this;
diff --git a/app/javascript/flavours/glitch/features/ui/components/report_modal.js b/app/javascript/flavours/glitch/features/ui/components/report_modal.js
index 7b6a4a784..4d7f84bae 100644
--- a/app/javascript/flavours/glitch/features/ui/components/report_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/report_modal.js
@@ -96,7 +96,7 @@ class ReportModal extends ImmutablePureComponent {
     } else {
       this.setState({ selectedRuleIds: selectedRuleIds.remove(ruleId) });
     }
-  }
+  };
 
   handleChangeCategory = category => {
     this.setState({ category });
diff --git a/app/javascript/flavours/glitch/features/ui/components/upload_area.js b/app/javascript/flavours/glitch/features/ui/components/upload_area.js
index 6958ba9df..0e07b67f8 100644
--- a/app/javascript/flavours/glitch/features/ui/components/upload_area.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/ui/components/zoomable_image.js b/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js
index caeeced64..50b36b478 100644
--- a/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index d8889f9f9..9255e346e 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -162,7 +162,7 @@ class SwitchingColumnsArea extends React.PureComponent {
     if (c) {
       this.node = c;
     }
-  }
+  };
 
   render () {
     const { children, mobile, navbarUnder } = this.props;
@@ -240,7 +240,7 @@ class SwitchingColumnsArea extends React.PureComponent {
         </WrappedSwitch>
       </ColumnsAreaContainer>
     );
-  };
+  }
 
 }
 
@@ -292,7 +292,7 @@ class UI extends React.Component {
       // but we set user-friendly message for other browsers, e.g. Edge.
       e.returnValue = intl.formatMessage(messages.beforeUnload);
     }
-  }
+  };
 
   handleDragEnter = (e) => {
     e.preventDefault();
@@ -308,7 +308,7 @@ class UI extends React.Component {
     if (e.dataTransfer && 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;
@@ -322,7 +322,7 @@ class UI extends React.Component {
     }
 
     return false;
-  }
+  };
 
   handleDrop = (e) => {
     if (this.dataTransferIsText(e.dataTransfer)) return;
@@ -335,7 +335,7 @@ class UI extends React.Component {
     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();
@@ -348,15 +348,15 @@ class UI extends React.Component {
     }
 
     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') {
@@ -364,7 +364,7 @@ class UI extends React.Component {
     } else {
       console.warn('Unknown message type:', data.type);
     }
-  }
+  };
 
   handleVisibilityChange = () => {
     const visibility = !document[this.visibilityHiddenProp];
@@ -372,7 +372,7 @@ class UI extends React.Component {
     if (visibility) {
       this.props.dispatch(submitMarkers({ immediate: true }));
     }
-  }
+  };
 
   handleLayoutChange = debounce(() => {
     this.props.dispatch(clearHeight()); // The cached heights are no longer accurate, invalidate
@@ -389,7 +389,7 @@ class UI extends React.Component {
     } else {
       this.handleLayoutChange();
     }
-  }
+  };
 
   componentDidMount () {
     const { signedIn } = this.context.identity;
@@ -407,7 +407,7 @@ class UI extends React.Component {
       navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
     }
 
-    this.favicon = new Favico({ animation:"none" });
+    this.favicon = new Favico({ animation:'none' });
 
     // On first launch, redirect to the follow recommendations page
     if (signedIn && this.props.firstLaunch) {
@@ -487,7 +487,7 @@ class UI extends React.Component {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   handleHotkeyNew = e => {
     e.preventDefault();
@@ -497,7 +497,7 @@ class UI extends React.Component {
     if (element) {
       element.focus();
     }
-  }
+  };
 
   handleHotkeySearch = e => {
     e.preventDefault();
@@ -507,17 +507,17 @@ class UI extends React.Component {
     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
@@ -535,7 +535,7 @@ class UI extends React.Component {
         status.focus();
       }
     }
-  }
+  };
 
   handleHotkeyBack = () => {
     // if history is exhausted, or we would leave mastodon, just go to root.
@@ -544,11 +544,11 @@ class UI extends React.Component {
     } else {
       this.props.history.push('/');
     }
-  }
+  };
 
   setHotkeysRef = c => {
     this.hotkeys = c;
-  }
+  };
 
   handleHotkeyToggleHelp = () => {
     if (this.props.location.pathname === '/keyboard-shortcuts') {
@@ -556,55 +556,55 @@ class UI extends React.Component {
     } else {
       this.props.history.push('/keyboard-shortcuts');
     }
-  }
+  };
 
   handleHotkeyGoToHome = () => {
     this.props.history.push('/home');
-  }
+  };
 
   handleHotkeyGoToNotifications = () => {
     this.props.history.push('/notifications');
-  }
+  };
 
   handleHotkeyGoToLocal = () => {
     this.props.history.push('/public/local');
-  }
+  };
 
   handleHotkeyGoToFederated = () => {
     this.props.history.push('/public');
-  }
+  };
 
   handleHotkeyGoToDirect = () => {
     this.props.history.push('/conversations');
-  }
+  };
 
   handleHotkeyGoToStart = () => {
     this.props.history.push('/getting-started');
-  }
+  };
 
   handleHotkeyGoToFavourites = () => {
     this.props.history.push('/favourites');
-  }
+  };
 
   handleHotkeyGoToPinned = () => {
     this.props.history.push('/pinned');
-  }
+  };
 
   handleHotkeyGoToProfile = () => {
     this.props.history.push(`/@${this.props.username}`);
-  }
+  };
 
   handleHotkeyGoToBlocked = () => {
     this.props.history.push('/blocks');
-  }
+  };
 
   handleHotkeyGoToMuted = () => {
     this.props.history.push('/mutes');
-  }
+  };
 
   handleHotkeyGoToRequests = () => {
     this.props.history.push('/follow_requests');
-  }
+  };
 
   render () {
     const { draggingOver } = this.state;
@@ -661,7 +661,7 @@ class UI extends React.Component {
                 <PermaLink href={moved.get('url')} to={`/@${moved.get('acct')}`}>
                   @{moved.get('acct')}
                 </PermaLink>
-              )}}
+              ) }}
             />
           </div>)}
 
diff --git a/app/javascript/flavours/glitch/features/ui/util/react_router_helpers.js b/app/javascript/flavours/glitch/features/ui/util/react_router_helpers.js
index 8946c8252..b1c952d87 100644
--- a/app/javascript/flavours/glitch/features/ui/util/react_router_helpers.js
+++ b/app/javascript/flavours/glitch/features/ui/util/react_router_helpers.js
@@ -36,7 +36,7 @@ export class WrappedRoute extends React.Component {
     content: PropTypes.node,
     multiColumn: PropTypes.bool,
     componentParams: PropTypes.object,
-  }
+  };
 
   static defaultProps = {
     componentParams: {},
@@ -46,7 +46,7 @@ export class WrappedRoute extends React.Component {
     return {
       hasError: true,
     };
-  };
+  }
 
   state = {
     hasError: false,
@@ -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/flavours/glitch/features/ui/util/reduced_motion.js b/app/javascript/flavours/glitch/features/ui/util/reduced_motion.js
index 95519042b..1123b80ed 100644
--- a/app/javascript/flavours/glitch/features/ui/util/reduced_motion.js
+++ b/app/javascript/flavours/glitch/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/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js
index 0daab747b..cb923bcf7 100644
--- a/app/javascript/flavours/glitch/features/video/index.js
+++ b/app/javascript/flavours/glitch/features/video/index.js
@@ -156,7 +156,7 @@ class Video extends React.PureComponent {
     if (this.player) {
       this._setDimensions();
     }
-  }
+  };
 
   _setDimensions () {
     const width = this.player.offsetWidth;
@@ -178,26 +178,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(() => {
@@ -216,7 +216,7 @@ class Video extends React.PureComponent {
       currentTime: this.video.currentTime,
       duration:this.video.duration,
     });
-  }
+  };
 
   handleVolumeMouseDown = e => {
     document.addEventListener('mousemove', this.handleMouseVolSlide, true);
@@ -228,14 +228,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);
@@ -259,7 +259,7 @@ class Video extends React.PureComponent {
 
     e.preventDefault();
     e.stopPropagation();
-  }
+  };
 
   handleMouseUp = () => {
     document.removeEventListener('mousemove', this.handleMouseMove, true);
@@ -269,7 +269,7 @@ class Video extends React.PureComponent {
 
     this.setState({ dragging: false });
     this.video.play();
-  }
+  };
 
   handleMouseMove = throttle(e => {
     const { x } = getPointerPosition(this.seek, e);
@@ -301,7 +301,7 @@ class Video extends React.PureComponent {
       e.stopPropagation();
       this.togglePlay();
     }
-  }
+  };
 
   handleKeyDown = e => {
     const frameTime = 1 / this.getFrameRate();
@@ -355,7 +355,7 @@ class Video extends React.PureComponent {
         exitFullscreen();
       }
     }
-  }
+  };
 
   togglePlay = () => {
     if (this.state.paused) {
@@ -363,7 +363,7 @@ class Video extends React.PureComponent {
     } else {
       this.setState({ paused: true }, () => this.video.pause());
     }
-  }
+  };
 
   toggleFullscreen = () => {
     if (isFullscreen()) {
@@ -371,7 +371,7 @@ class Video extends React.PureComponent {
     } else {
       requestFullscreen(this.player);
     }
-  }
+  };
 
   componentDidMount () {
     document.addEventListener('fullscreenchange', this.handleFullscreenChange, true);
@@ -444,19 +444,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;
@@ -464,7 +464,7 @@ class Video extends React.PureComponent {
     this.setState({ muted }, () => {
       this.video.muted = muted;
     });
-  }
+  };
 
   toggleReveal = () => {
     if (this.state.revealed) {
@@ -476,7 +476,7 @@ class Video extends React.PureComponent {
     } else {
       this.setState({ revealed: !this.state.revealed });
     }
-  }
+  };
 
   handleLoadedData = () => {
     const { currentTime, volume, muted, autoPlay } = this.props;
@@ -496,7 +496,7 @@ class Video extends React.PureComponent {
     if (autoPlay) {
       this.video.play();
     }
-  }
+  };
 
   handleProgress = () => {
     const lastTimeRange = this.video.buffered.length - 1;
@@ -504,11 +504,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();
@@ -519,12 +519,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)) {
@@ -553,7 +553,7 @@ class Video extends React.PureComponent {
 
       playerStyle.height = height;
     } else if (inline) {
-      return (<div className={computedClass} ref={this.setPlayerRef} tabindex={0}></div>);
+      return (<div className={computedClass} ref={this.setPlayerRef} tabindex={0} />);
     }
 
     let preload;
diff --git a/app/javascript/flavours/glitch/middleware/errors.js b/app/javascript/flavours/glitch/middleware/errors.js
index ade529a4e..3639a5951 100644
--- a/app/javascript/flavours/glitch/middleware/errors.js
+++ b/app/javascript/flavours/glitch/middleware/errors.js
@@ -14,4 +14,4 @@ export default function errorsMiddleware() {
 
     return next(action);
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/middleware/loading_bar.js b/app/javascript/flavours/glitch/middleware/loading_bar.js
index a98f1bb2b..da8cc4c7d 100644
--- a/app/javascript/flavours/glitch/middleware/loading_bar.js
+++ b/app/javascript/flavours/glitch/middleware/loading_bar.js
@@ -22,4 +22,4 @@ export default function loadingBarMiddleware(config = {}) {
 
     return next(action);
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/middleware/sounds.js b/app/javascript/flavours/glitch/middleware/sounds.js
index 9f1bc02b9..7f2388983 100644
--- a/app/javascript/flavours/glitch/middleware/sounds.js
+++ b/app/javascript/flavours/glitch/middleware/sounds.js
@@ -43,4 +43,4 @@ export default function soundsMiddleware() {
 
     return next(action);
   };
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/accounts.js b/app/javascript/flavours/glitch/reducers/accounts.js
index e02a5592e..07f45f98b 100644
--- a/app/javascript/flavours/glitch/reducers/accounts.js
+++ b/app/javascript/flavours/glitch/reducers/accounts.js
@@ -35,4 +35,4 @@ export default function accounts(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/accounts_counters.js b/app/javascript/flavours/glitch/reducers/accounts_counters.js
index 9ebf72af9..4e1256d1b 100644
--- a/app/javascript/flavours/glitch/reducers/accounts_counters.js
+++ b/app/javascript/flavours/glitch/reducers/accounts_counters.js
@@ -35,4 +35,4 @@ export default function accountsCounters(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/accounts_map.js b/app/javascript/flavours/glitch/reducers/accounts_map.js
index 444bbda19..8412ad4d0 100644
--- a/app/javascript/flavours/glitch/reducers/accounts_map.js
+++ b/app/javascript/flavours/glitch/reducers/accounts_map.js
@@ -17,4 +17,4 @@ export default function accountsMap(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/alerts.js b/app/javascript/flavours/glitch/reducers/alerts.js
index ee3d54ab0..f0a696164 100644
--- a/app/javascript/flavours/glitch/reducers/alerts.js
+++ b/app/javascript/flavours/glitch/reducers/alerts.js
@@ -23,4 +23,4 @@ export default function alerts(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/announcements.js b/app/javascript/flavours/glitch/reducers/announcements.js
index 34e08eac8..b53f93a4a 100644
--- a/app/javascript/flavours/glitch/reducers/announcements.js
+++ b/app/javascript/flavours/glitch/reducers/announcements.js
@@ -99,4 +99,4 @@ export default function announcementsReducer(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js
index a69c0f7f2..bb42580d4 100644
--- a/app/javascript/flavours/glitch/reducers/compose.js
+++ b/app/javascript/flavours/glitch/reducers/compose.js
@@ -140,7 +140,7 @@ function statusToTextMentions(state, status) {
   }
 
   return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
-};
+}
 
 function apiStatusToTextMentions (state, status) {
   let set = ImmutableOrderedSet([]);
@@ -150,16 +150,16 @@ function apiStatusToTextMentions (state, status) {
   }
 
   return set.union(status.mentions.filter(
-    mention => mention.id !== me
+    mention => mention.id !== me,
   ).map(
-    mention => `@${mention.acct} `
+    mention => `@${mention.acct} `,
   )).join('');
 }
 
 function apiStatusToTextHashtags (state, status) {
   const text = unescapeHTML(status.content);
   return ImmutableOrderedSet([]).union(recoverHashtags(status.tags, text).map(
-    (name) => `#${name} `
+    (name) => `#${name} `,
   )).join('');
 }
 
@@ -175,7 +175,7 @@ function clearAll(state) {
     map.set('in_reply_to', null);
     map.update(
       'advanced_options',
-      map => map.mergeWith(overwrite, state.get('default_advanced_options'))
+      map => map.mergeWith(overwrite, state.get('default_advanced_options')),
     );
     map.set('privacy', state.get('default_privacy'));
     map.set('sensitive', state.get('default_sensitive'));
@@ -184,7 +184,7 @@ function clearAll(state) {
     map.set('poll', null);
     map.set('idempotencyKey', uuid());
   });
-};
+}
 
 function continueThread (state, status) {
   return state.withMutations(function (map) {
@@ -202,7 +202,7 @@ function continueThread (state, status) {
     map.set('in_reply_to', status.id);
     map.update(
       'advanced_options',
-      map => map.merge(new ImmutableMap({ do_not_federate: status.local_only }))
+      map => map.merge(new ImmutableMap({ do_not_federate: status.local_only })),
     );
     map.set('privacy', status.visibility);
     map.set('sensitive', false);
@@ -233,7 +233,7 @@ function appendMedia(state, media, file) {
       map.set('sensitive', true);
     }
   });
-};
+}
 
 function removeMedia(state, mediaId) {
   const prevSize = state.get('media_attachments').size;
@@ -246,7 +246,7 @@ function removeMedia(state, mediaId) {
       map.set('sensitive', false);
     }
   });
-};
+}
 
 const insertSuggestion = (state, position, token, completion, path) => {
   return state.withMutations(map => {
@@ -303,8 +303,8 @@ const insertEmoji = (state, position, emojiData) => {
 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;
@@ -414,7 +414,7 @@ export default function compose(state = initialState, action) {
       map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
       map.update(
         'advanced_options',
-        map => map.merge(new ImmutableMap({ do_not_federate: !!action.status.get('local_only') }))
+        map => map.merge(new ImmutableMap({ do_not_federate: !!action.status.get('local_only') })),
       );
       map.set('focusDate', new Date());
       map.set('caretPosition', null);
@@ -453,7 +453,7 @@ export default function compose(state = initialState, action) {
       map.set('poll', null);
       map.update(
         'advanced_options',
-        map => map.mergeWith(overwrite, state.get('default_advanced_options'))
+        map => map.mergeWith(overwrite, state.get('default_advanced_options')),
       );
       map.set('idempotencyKey', uuid());
     });
@@ -575,7 +575,7 @@ export default function compose(state = initialState, action) {
       map.set('language', action.status.get('language'));
       map.update(
         'advanced_options',
-        map => map.merge(new ImmutableMap({ do_not_federate }))
+        map => map.merge(new ImmutableMap({ do_not_federate })),
       );
       map.set('id', null);
 
@@ -646,4 +646,4 @@ export default function compose(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/contexts.js b/app/javascript/flavours/glitch/reducers/contexts.js
index a0fcc4158..aea77ae41 100644
--- a/app/javascript/flavours/glitch/reducers/contexts.js
+++ b/app/javascript/flavours/glitch/reducers/contexts.js
@@ -67,7 +67,7 @@ const deleteFromContexts = (immutableState, ids) => immutableState.withMutations
 
 const filterContexts = (state, relationship, statuses) => {
   const ownedStatusIds = statuses.filter(status => status.get('account') === relationship.id)
-                                 .map(status => status.get('id'));
+    .map(status => status.get('id'));
 
   return deleteFromContexts(state, ownedStatusIds);
 };
@@ -102,4 +102,4 @@ export default function replies(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/conversations.js b/app/javascript/flavours/glitch/reducers/conversations.js
index 4407dcf04..48b70cc33 100644
--- a/app/javascript/flavours/glitch/reducers/conversations.js
+++ b/app/javascript/flavours/glitch/reducers/conversations.js
@@ -113,4 +113,4 @@ export default function conversations(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/custom_emojis.js b/app/javascript/flavours/glitch/reducers/custom_emojis.js
index f490d0db1..7f71ab791 100644
--- a/app/javascript/flavours/glitch/reducers/custom_emojis.js
+++ b/app/javascript/flavours/glitch/reducers/custom_emojis.js
@@ -12,4 +12,4 @@ export default function custom_emojis(state = initialState, action) {
   }
 
   return state;
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/domain_lists.js b/app/javascript/flavours/glitch/reducers/domain_lists.js
index eff97fbd6..6bf8cee68 100644
--- a/app/javascript/flavours/glitch/reducers/domain_lists.js
+++ b/app/javascript/flavours/glitch/reducers/domain_lists.js
@@ -22,4 +22,4 @@ export default function domainLists(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/filters.js b/app/javascript/flavours/glitch/reducers/filters.js
index f4f97cd3a..e1f014046 100644
--- a/app/javascript/flavours/glitch/reducers/filters.js
+++ b/app/javascript/flavours/glitch/reducers/filters.js
@@ -41,4 +41,4 @@ export default function filters(state = ImmutableMap(), action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/followed_tags.js b/app/javascript/flavours/glitch/reducers/followed_tags.js
index 4109b0b10..84c744640 100644
--- a/app/javascript/flavours/glitch/reducers/followed_tags.js
+++ b/app/javascript/flavours/glitch/reducers/followed_tags.js
@@ -39,4 +39,4 @@ export default function followed_tags(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/height_cache.js b/app/javascript/flavours/glitch/reducers/height_cache.js
index 8b05e0b19..660a2d1d7 100644
--- a/app/javascript/flavours/glitch/reducers/height_cache.js
+++ b/app/javascript/flavours/glitch/reducers/height_cache.js
@@ -20,4 +20,4 @@ export default function statuses(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/list_adder.js b/app/javascript/flavours/glitch/reducers/list_adder.js
index b8c1b0e26..b144610a5 100644
--- a/app/javascript/flavours/glitch/reducers/list_adder.js
+++ b/app/javascript/flavours/glitch/reducers/list_adder.js
@@ -44,4 +44,4 @@ export default function listAdderReducer(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/list_editor.js b/app/javascript/flavours/glitch/reducers/list_editor.js
index 5427ac098..6e020dbe6 100644
--- a/app/javascript/flavours/glitch/reducers/list_editor.js
+++ b/app/javascript/flavours/glitch/reducers/list_editor.js
@@ -54,10 +54,10 @@ export default function listEditorReducer(state = initialState, action) {
     });
   case LIST_CREATE_REQUEST:
   case LIST_UPDATE_REQUEST:
-      return state.withMutations(map => {
-        map.set('isSubmitting', true);
-        map.set('isChanged', false);
-      });
+    return state.withMutations(map => {
+      map.set('isSubmitting', true);
+      map.set('isChanged', false);
+    });
   case LIST_CREATE_FAIL:
   case LIST_UPDATE_FAIL:
     return state.set('isSubmitting', false);
@@ -93,4 +93,4 @@ export default function listEditorReducer(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/lists.js b/app/javascript/flavours/glitch/reducers/lists.js
index f30ffbcbd..ba3e2b3cb 100644
--- a/app/javascript/flavours/glitch/reducers/lists.js
+++ b/app/javascript/flavours/glitch/reducers/lists.js
@@ -34,4 +34,4 @@ export default function lists(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js
index 9075146f3..887e0e135 100644
--- a/app/javascript/flavours/glitch/reducers/local_settings.js
+++ b/app/javascript/flavours/glitch/reducers/local_settings.js
@@ -78,4 +78,4 @@ export default function localSettings(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/markers.js b/app/javascript/flavours/glitch/reducers/markers.js
index fb1572ff5..e3d1b1936 100644
--- a/app/javascript/flavours/glitch/reducers/markers.js
+++ b/app/javascript/flavours/glitch/reducers/markers.js
@@ -22,4 +22,4 @@ export default function markers(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/media_attachments.js b/app/javascript/flavours/glitch/reducers/media_attachments.js
index 6e6058576..dfd8ea42d 100644
--- a/app/javascript/flavours/glitch/reducers/media_attachments.js
+++ b/app/javascript/flavours/glitch/reducers/media_attachments.js
@@ -12,4 +12,4 @@ export default function meta(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/meta.js b/app/javascript/flavours/glitch/reducers/meta.js
index b1482777a..7a38a9090 100644
--- a/app/javascript/flavours/glitch/reducers/meta.js
+++ b/app/javascript/flavours/glitch/reducers/meta.js
@@ -13,14 +13,12 @@ const initialState = ImmutableMap({
 export default function meta(state = initialState, action) {
   switch(action.type) {
   case STORE_HYDRATE:
-    return state.merge(
-      action.state.get('meta'))
-        .set('permissions', action.state.getIn(['role', 'permissions']))
-        .set('layout', layoutFromWindow(action.state.getIn(['local_settings', 'layout']))
-      );
+    return state.merge(action.state.get('meta'))
+      .set('permissions', action.state.getIn(['role', 'permissions']))
+      .set('layout', layoutFromWindow(action.state.getIn(['local_settings', 'layout'])));
   case APP_LAYOUT_CHANGE:
     return state.set('layout', action.layout);
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/modal.js b/app/javascript/flavours/glitch/reducers/modal.js
index 2ef0aef24..c48117181 100644
--- a/app/javascript/flavours/glitch/reducers/modal.js
+++ b/app/javascript/flavours/glitch/reducers/modal.js
@@ -36,4 +36,4 @@ export default function modal(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js
index 18610e758..d5b1568e9 100644
--- a/app/javascript/flavours/glitch/reducers/notifications.js
+++ b/app/javascript/flavours/glitch/reducers/notifications.js
@@ -371,4 +371,4 @@ export default function notifications(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/picture_in_picture.js b/app/javascript/flavours/glitch/reducers/picture_in_picture.js
index 13a3d1aa2..395c21245 100644
--- a/app/javascript/flavours/glitch/reducers/picture_in_picture.js
+++ b/app/javascript/flavours/glitch/reducers/picture_in_picture.js
@@ -22,4 +22,4 @@ export default function pictureInPicture(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/pinned_accounts_editor.js b/app/javascript/flavours/glitch/reducers/pinned_accounts_editor.js
index 267521bb8..144418d12 100644
--- a/app/javascript/flavours/glitch/reducers/pinned_accounts_editor.js
+++ b/app/javascript/flavours/glitch/reducers/pinned_accounts_editor.js
@@ -54,4 +54,4 @@ export default function listEditorReducer(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/push_notifications.js b/app/javascript/flavours/glitch/reducers/push_notifications.js
index 117fb5167..116c3732f 100644
--- a/app/javascript/flavours/glitch/reducers/push_notifications.js
+++ b/app/javascript/flavours/glitch/reducers/push_notifications.js
@@ -50,4 +50,4 @@ export default function push_subscriptions(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/relationships.js b/app/javascript/flavours/glitch/reducers/relationships.js
index e4b9acea2..b53f0238c 100644
--- a/app/javascript/flavours/glitch/reducers/relationships.js
+++ b/app/javascript/flavours/glitch/reducers/relationships.js
@@ -82,4 +82,4 @@ export default function relationships(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/search.js b/app/javascript/flavours/glitch/reducers/search.js
index 4b8913e96..bc0433d1f 100644
--- a/app/javascript/flavours/glitch/reducers/search.js
+++ b/app/javascript/flavours/glitch/reducers/search.js
@@ -64,4 +64,4 @@ export default function search(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js
index 82927f7cd..e69eee966 100644
--- a/app/javascript/flavours/glitch/reducers/settings.js
+++ b/app/javascript/flavours/glitch/reducers/settings.js
@@ -176,4 +176,4 @@ export default function settings(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/status_lists.js b/app/javascript/flavours/glitch/reducers/status_lists.js
index 7ac0dab47..a279d3d34 100644
--- a/app/javascript/flavours/glitch/reducers/status_lists.js
+++ b/app/javascript/flavours/glitch/reducers/status_lists.js
@@ -145,4 +145,4 @@ export default function statusLists(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/statuses.js b/app/javascript/flavours/glitch/reducers/statuses.js
index f0c4c804b..ca220c54d 100644
--- a/app/javascript/flavours/glitch/reducers/statuses.js
+++ b/app/javascript/flavours/glitch/reducers/statuses.js
@@ -94,4 +94,4 @@ export default function statuses(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/suggestions.js b/app/javascript/flavours/glitch/reducers/suggestions.js
index 2bc30d2c6..3c1ea3fa8 100644
--- a/app/javascript/flavours/glitch/reducers/suggestions.js
+++ b/app/javascript/flavours/glitch/reducers/suggestions.js
@@ -34,4 +34,4 @@ export default function suggestionsReducer(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/tags.js b/app/javascript/flavours/glitch/reducers/tags.js
index 266b21177..b280bc4dd 100644
--- a/app/javascript/flavours/glitch/reducers/tags.js
+++ b/app/javascript/flavours/glitch/reducers/tags.js
@@ -22,4 +22,4 @@ export default function tags(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/timelines.js b/app/javascript/flavours/glitch/reducers/timelines.js
index 407293c62..96a6ca961 100644
--- a/app/javascript/flavours/glitch/reducers/timelines.js
+++ b/app/javascript/flavours/glitch/reducers/timelines.js
@@ -229,4 +229,4 @@ export default function timelines(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/trends.js b/app/javascript/flavours/glitch/reducers/trends.js
index e2bac6199..0b8e0882d 100644
--- a/app/javascript/flavours/glitch/reducers/trends.js
+++ b/app/javascript/flavours/glitch/reducers/trends.js
@@ -43,4 +43,4 @@ export default function trendsReducer(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/reducers/user_lists.js b/app/javascript/flavours/glitch/reducers/user_lists.js
index 0a75e85c1..9e020fd91 100644
--- a/app/javascript/flavours/glitch/reducers/user_lists.js
+++ b/app/javascript/flavours/glitch/reducers/user_lists.js
@@ -187,4 +187,4 @@ export default function userLists(state = initialState, action) {
   default:
     return state;
   }
-};
+}
diff --git a/app/javascript/flavours/glitch/store/configureStore.js b/app/javascript/flavours/glitch/store/configureStore.js
index e18af842f..0e0d45c66 100644
--- a/app/javascript/flavours/glitch/store/configureStore.js
+++ b/app/javascript/flavours/glitch/store/configureStore.js
@@ -12,4 +12,4 @@ export default function configureStore() {
     errorsMiddleware(),
     soundsMiddleware(),
   ), window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f));
-};
+}
diff --git a/app/javascript/flavours/glitch/utils/hashtag.js b/app/javascript/flavours/glitch/utils/hashtag.js
index 9b663487f..8f5665c46 100644
--- a/app/javascript/flavours/glitch/utils/hashtag.js
+++ b/app/javascript/flavours/glitch/utils/hashtag.js
@@ -1,8 +1,8 @@
 export function recoverHashtags (recognizedTags, text) {
   return recognizedTags.map(tag => {
-      const re = new RegExp(`(?:^|[^\/\)\w])#(${tag.name})`, 'i');
-      const matched_hashtag = text.match(re);
-      return matched_hashtag ? matched_hashtag[1] : null;
-    }
+    const re = new RegExp(`(?:^|[^\/)\w])#(${tag.name})`, 'i');
+    const matched_hashtag = text.match(re);
+    return matched_hashtag ? matched_hashtag[1] : null;
+  },
   ).filter(x => x !== null);
 }
diff --git a/app/javascript/flavours/glitch/utils/privacy_preference.js b/app/javascript/flavours/glitch/utils/privacy_preference.js
index 7781ca7fa..51bdf072d 100644
--- a/app/javascript/flavours/glitch/utils/privacy_preference.js
+++ b/app/javascript/flavours/glitch/utils/privacy_preference.js
@@ -2,4 +2,4 @@ export const order = ['public', 'unlisted', 'private', 'direct'];
 
 export function privacyPreference (a, b) {
   return order[Math.max(order.indexOf(a), order.indexOf(b), 0)];
-};
+}
diff --git a/app/javascript/flavours/glitch/utils/react_helpers.js b/app/javascript/flavours/glitch/utils/react_helpers.js
index 082a58e62..ea11acdb6 100644
--- a/app/javascript/flavours/glitch/utils/react_helpers.js
+++ b/app/javascript/flavours/glitch/utils/react_helpers.js
@@ -7,7 +7,7 @@ export function assignHandlers (target, handlers) {
   //  We just bind each handler to the `target`.
   const handle = target.handlers = {};
   Object.keys(handlers).forEach(
-    key => handle[key] = handlers[key].bind(target)
+    key => handle[key] = handlers[key].bind(target),
   );
 }
 
diff --git a/app/javascript/flavours/glitch/uuid.js b/app/javascript/flavours/glitch/uuid.js
index be1899305..0d2cfaa77 100644
--- a/app/javascript/flavours/glitch/uuid.js
+++ b/app/javascript/flavours/glitch/uuid.js
@@ -1,3 +1,3 @@
 export default function uuid(a) {
   return a ? (a^Math.random() * 16 >> a / 4).toString(16) : ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, uuid);
-};
+}
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'
diff --git a/config/application.rb b/config/application.rb
index 929a44948..c51eacd68 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -69,12 +69,14 @@ module Mastodon
     # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
     config.i18n.available_locales = [
       :af,
+      :an,
       :ar,
       :ast,
       :be,
       :bg,
       :bn,
       :br,
+      :bs,
       :ca,
       :ckb,
       :co,
@@ -84,6 +86,7 @@ module Mastodon
       :de,
       :el,
       :en,
+      :'en-GB',
       :eo,
       :es,
       :'es-AR',
@@ -92,7 +95,9 @@ module Mastodon
       :eu,
       :fa,
       :fi,
+      :fo,
       :fr,
+      :'fr-QC',
       :fy,
       :ga,
       :gd,
@@ -103,6 +108,7 @@ module Mastodon
       :hu,
       :hy,
       :id,
+      :ig,
       :io,
       :is,
       :it,
@@ -113,16 +119,20 @@ module Mastodon
       :kn,
       :ko,
       :ku,
+      :kw,
+      :la,
       :lt,
       :lv,
       :mk,
       :ml,
       :mr,
       :ms,
+      :my,
       :nl,
       :nn,
       :no,
       :oc,
+      :pa,
       :pl,
       :'pt-BR',
       :'pt-PT',
@@ -130,6 +140,7 @@ module Mastodon
       :ru,
       :sa,
       :sc,
+      :sco,
       :si,
       :sk,
       :sl,
@@ -137,10 +148,14 @@ module Mastodon
       :sr,
       :'sr-Latn',
       :sv,
+      :szl,
       :ta,
+      :tai,
       :te,
       :th,
       :tr,
+      :tt,
+      :ug,
       :uk,
       :ur,
       :vi,
diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb
index 72ef7ba80..3857e3055 100644
--- a/config/initializers/rack_attack.rb
+++ b/config/initializers/rack_attack.rb
@@ -33,6 +33,10 @@ class Rack::Attack
       authenticated_token&.resource_owner_id
     end
 
+    def authenticated_token_id
+      authenticated_token&.id
+    end
+
     def unauthenticated?
       !authenticated_user_id
     end
@@ -62,10 +66,14 @@ class Rack::Attack
     IpBlock.blocked?(req.remote_ip)
   end
 
-  throttle('throttle_authenticated_api', limit: 300, period: 5.minutes) do |req|
+  throttle('throttle_authenticated_api', limit: 1_500, period: 5.minutes) do |req|
     req.authenticated_user_id if req.api_request?
   end
 
+  throttle('throttle_per_token_api', limit: 300, period: 5.minutes) do |req|
+    req.authenticated_token_id if req.api_request?
+  end
+
   throttle('throttle_unauthenticated_api', limit: 300, period: 5.minutes) do |req|
     req.throttleable_remote_ip if req.api_request? && req.unauthenticated?
   end
diff --git a/config/locales/activerecord.ca.yml b/config/locales/activerecord.ca.yml
index d7243d09a..17d0720d5 100644
--- a/config/locales/activerecord.ca.yml
+++ b/config/locales/activerecord.ca.yml
@@ -36,7 +36,7 @@ ca:
         status:
           attributes:
             reblog:
-              taken: del tut que ja existeix
+              taken: de la publicació ja existeix
         user:
           attributes:
             email:
diff --git a/config/locales/activerecord.csb.yml b/config/locales/activerecord.csb.yml
new file mode 100644
index 000000000..0de706e41
--- /dev/null
+++ b/config/locales/activerecord.csb.yml
@@ -0,0 +1 @@
+csb:
diff --git a/config/locales/activerecord.uz.yml b/config/locales/activerecord.uz.yml
new file mode 100644
index 000000000..ed13813d1
--- /dev/null
+++ b/config/locales/activerecord.uz.yml
@@ -0,0 +1,55 @@
+---
+uz:
+  activerecord:
+    attributes:
+      poll:
+        expires_at: Tugatish muddati
+        options: Tanlovlar
+      user:
+        agreement: Foydalanish shartnomasi
+        email: E-mail
+        locale: Mahalliy
+        password: Parol
+      user/account:
+        username: Foydalanuvchi nomi
+      user/invite_request:
+        text: Sabab
+    errors:
+      models:
+        account:
+          attributes:
+            username:
+              invalid: faqat harflar, raqamlar va pastki chiziqdan iborat bo'lishi kerak
+              reserved: rezervlangan
+        admin/webhook:
+          attributes:
+            url:
+              invalid: haqiqiy URL emas
+        doorkeeper/application:
+          attributes:
+            website:
+              invalid: haqiqiy URL emas
+        import:
+          attributes:
+            data:
+              malformed: noto'g'ri shakllangan
+        status:
+          attributes:
+            reblog:
+              taken: post allaqachon mavjud
+        user:
+          attributes:
+            email:
+              blocked: ruxsat etilmagan elektron pochta provayderidan foydalangan
+              unreachable: mavjud emasga o'xshaydi
+            role_id:
+              elevated: sizning hozirgi rolingizdan yuqori bo'lishi mumkin emas
+        user_role:
+          attributes:
+            permissions_as_keys:
+              dangerous: asosiy rol uchun xavfsiz bo'lmagan ruxsatlarni o'z ichiga oladi
+              elevated: joriy rolingiz ega bo'lmagan ruxsatlarni o'z ichiga olmaydi
+              own_role: sizning hozirgi rolingizdan yuqori bo'lishi mumkin emas
+            position:
+              elevated: sizning hozirgi rolingizdan yuqori bo'lishi mumkin emas
+              own_role: joriy rolingiz bilan o‘zgartirib bo‘lmaydi
diff --git a/config/locales/activerecord.zh_Hant.yml b/config/locales/activerecord.zh_Hant.yml
deleted file mode 100644
index 730ab3a51..000000000
--- a/config/locales/activerecord.zh_Hant.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-zh_Hant:
-  activerecord:
-    attributes:
-      status:
-        owned_poll: 投票
-    errors:
-      models:
-        account:
-          attributes:
-            username:
-              invalid: 只允許使用字母、數字和底線
-        status:
-          attributes:
-            reblog:
-              taken: 的嘟文已經存在
diff --git a/config/locales/ar.yml b/config/locales/ar.yml
index 2d552ae9b..ad2797592 100644
--- a/config/locales/ar.yml
+++ b/config/locales/ar.yml
@@ -438,6 +438,7 @@ ar:
         private_comment_description_html: 'لمساعدتك على تتبع سبب حظر النطاقات المستوردة، سيتم إنشاء الحظر مع التعليق الخاص التالي: <q>%{comment}</q>'
         private_comment_template: تم الاستيراد من %{source} بتاريخ %{date}
         title: استيراد قامة النطاقات المحظورة
+      invalid_domain_block: 'تم تخطي حجب نطاق أو أكثر بسبب الأخطاء التالية: %{error}'
       new:
         title: استيراد قامة النطاقات المحظورة
       no_file: لم يتم تحديد أيّ ملف
@@ -452,6 +453,13 @@ ar:
     instances:
       availability:
         failure_threshold_reached: تم الوصول إلى أقصى حد للفشل بتاريخ %{date}.
+        failures_recorded:
+          few: المحاولات الفاشلة في %{count} يومًا.
+          many: المحاولات الفاشلة في %{count} يوم مختلف.
+          one: محاولة فاشلة في يوم %{count} واحد.
+          other: المحاولات الفاشلة في %{count} أيام مختلفة.
+          two: المحاولات الفاشلة في يَومين %{count}.
+          zero: ليس هناك أية محاولة فاشلة في %{count} يوم.
         no_failures_recorded: لم يتم العثور على أي فشل.
         title: التوفر
         warning: فشلت المحاولة الأخيرة للاتصال بهذا النطاق
@@ -585,6 +593,7 @@ ar:
       comment:
         none: لا شيء
       comment_description_html: 'لتوفير المزيد من المعلومات، كتب %{name}:'
+      confirm_action: تأكيد اتخاذ إجراء إشراف على @%{acct}
       created_at: ذكرت
       delete_and_resolve: احذف المنشورات
       forwarded: أُعيد توجيهه
@@ -601,6 +610,7 @@ ar:
         placeholder: قم بوصف الإجراءات التي تم اتخاذها أو أي تحديثات أخرى ذات علاقة...
         title: الملاحظات
       notes_description_html: عرض الملاحظات وتركها للمشرفين الآخرين ولنفسك في المستقبل
+      processed_msg: 'تمت معالجة التقرير #%{id} بنجاح'
       quick_actions_description_html: 'قم بإجراء سريع أو التمرير للأسفل لرؤية المحتوى المبلغ عنه:'
       remote_user_placeholder: المستخدم البعيد من %{instance}
       reopen: إعادة فتح الشكوى
@@ -613,9 +623,17 @@ ar:
       status: الحالة
       statuses: المحتوى المبلغ عنه
       statuses_description_html: سيشار إلى المحتوى المخالف في الاتصال بالحساب المبلغ عنه
+      summary:
+        actions:
+          delete_html: إزالة المنشورات المُخالِفة
+          mark_as_sensitive_html: تصنيف وسائط المنشورات المُخالفة كحساسة
+        close_report: 'تصنيف التقرير #%{id} كإبلاغ تمت معالجته'
+        send_email_html: إرسال بريد إلكتروني تحذيري إلى <strong>@%{acct}</strong>
+        warning_placeholder: مبررات إضافية اختيارية لإجراء الإشراف.
       target_origin: مصدر الحساب المبلغ عنه
       title: الشكاوى
       unassign: إلغاء تعيين
+      unknown_action_msg: 'إجراء غير معروف: %{action}'
       unresolved: غير معالجة
       updated_at: محدث
       view_profile: اعرض الصفحة التعريفية
@@ -928,6 +946,8 @@ ar:
   auth:
     apply_for_account: اطلُب حسابًا
     change_password: الكلمة السرية
+    confirmations:
+      wrong_email_hint: إذا كان عنوان البريد الإلكتروني هذا غير صحيح، يمكنك تغييره في إعدادات الحساب.
     delete_account: حذف الحساب
     delete_account_html: إن كنت ترغب في حذف حسابك يُمكنك <a href="%{path}">المواصلة هنا</a>. سوف يُطلَبُ منك التأكيد قبل الحذف.
     description:
@@ -1126,6 +1146,13 @@ ar:
       empty: ليست لديك أية عوامل تصفية.
       expires_in: تنتهي مدة صلاحيتها في غضون %{distance}
       expires_on: تنتهي مدة صلاحيتها في %{date}
+      keywords:
+        few: "%{count} كلمة مفتاحية"
+        many: "%{count} كلمة مفتاحية"
+        one: كلمة مفتاحية %{count} واحدة
+        other: "%{count} كلمة مفتاحية"
+        two: كلمتان مفتاحيتان %{count}
+        zero: "%{count} كلمة مفتاحية"
       title: عوامل التصفية
     new:
       save: حفظ عامل التصفية الجديد
@@ -1347,6 +1374,7 @@ ar:
     activity: نشاط الحساب
     confirm_follow_selected_followers: هل أنت متأكد من أنك تريد متابعة المتابِعين المحددين؟
     confirm_remove_selected_followers: هل أنت متأكد من أنك تريد إزالة المتابِعين المحددين؟
+    confirm_remove_selected_follows: هل أنت متيقِّن من أنك تريد إزالة الاشتراكات المحدَّدة؟
     dormant: في سبات
     follow_selected_followers: متابَعة المتابِعين المحددين
     followers: المتابِعون
diff --git a/config/locales/be.yml b/config/locales/be.yml
index 7cae109f5..18ec63376 100644
--- a/config/locales/be.yml
+++ b/config/locales/be.yml
@@ -457,6 +457,7 @@ be:
         private_comment_description_html: 'Каб дапамагчы вам адсочваць, адкуль паходзяць імпартаваныя блокі, імпартаваныя блокі будуць створаны з наступным прыватным каментарыем: <q>%{comment}</q>'
         private_comment_template: Імпартавана з %{source} %{date}
         title: Імпарт блакіровак дамену
+      invalid_domain_block: 'Адзін ці больш даменных блокаў былі прапушчаны праз наступную(-ыя) памылку(-і): %{error}'
       new:
         title: Імпарт блакіровак дамену
       no_file: Файл не выбраны
@@ -629,6 +630,7 @@ be:
         placeholder: Апішыце, якія дзеянні былі зроблены, або любыя іншыя звязаныя абнаўленні...
         title: Нататкі
       notes_description_html: Праглядвайце і пакідайце нататкі іншым мадэратарам і сабе ў будучыні
+      processed_msg: 'Скарга #%{id} паспяхова апрацавана'
       quick_actions_description_html: 'Выканайце хуткае дзеянне або пракруціце ўніз, каб убачыць змесціва, на якое пададзена скарга:'
       remote_user_placeholder: аддалены карыстальнік з %{instance}
       reopen: Пераадкрыць скаргу
@@ -641,6 +643,8 @@ be:
       status: Стан
       statuses: Змесціва, на якое паскардзіліся
       statuses_description_html: Крыўднае змесціва будзе згадвацца ў зносінах з уліковым запісам, на які пададзена скарга
+      summary:
+        close_report: 'Пазначыць скаргу #%{id} як вырашаную'
       target_origin: Крыніца уліковага запісу на які пададзена скарга
       title: Скаргі
       unassign: Скінуць
@@ -979,6 +983,8 @@ be:
   auth:
     apply_for_account: Пакінуць заяўку
     change_password: Пароль
+    confirmations:
+      wrong_email_hint: Калі гэты адрас электроннай пошты памылковы, вы можаце змяніць яго ў наладах уліковага запісу.
     delete_account: Выдаліць уліковы запіс
     delete_account_html: Калі вы жадаеце выдаліць ваш уліковы запіс, можаце <a href="%{path}">працягнуць тут</a>. Ад вас будзе запатрабавана пацвярджэнне.
     description:
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index e0970485d..616f8de17 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -211,11 +211,11 @@ ca:
         reopen_report: Reobre l'informe
         resend_user: Torna a enviar el correu de confirmació
         reset_password_user: Restableix la contrasenya
-        resolve_report: Resolt l'informe
+        resolve_report: Resol l'informe
         sensitive_account: Marcar els mèdia en el teu compte com a sensibles
         silence_account: Silencia el compte
         suspend_account: Suspèn el compte
-        unassigned_report: Des-assigna l'informe
+        unassigned_report: Desassigna l'informe
         unblock_email_account: Desbloqueja l'adreça de correu
         unsensitive_account: Desmarcar els mèdia en el teu compte com a sensibles
         unsilence_account: Desfés el silenci del compte
@@ -229,7 +229,7 @@ ca:
       actions:
         approve_appeal_html: "%{name} ha aprovat l'apel·lació a la decisió de moderació de %{target}"
         approve_user_html: "%{name} ha aprovat el registre de %{target}"
-        assigned_to_self_report_html: "%{name} han assignat l'informe %{target} a ells mateixos"
+        assigned_to_self_report_html: "%{name} s'han assignat l'informe %{target} a ells mateixos"
         change_email_user_html: "%{name} ha canviat l'adreça de correu electrònic del usuari %{target}"
         change_role_user_html: "%{name} ha canviat el rol de %{target}"
         confirm_user_html: "%{name} ha confirmat l'adreça de correu electrònic de l'usuari %{target}"
@@ -441,6 +441,7 @@ ca:
         private_comment_description_html: 'Per a ajudar-te a fer un seguiment d''on provenen els bloquejos importats, es crearan amb el següent comentari privat: <q>%{comment}</q>'
         private_comment_template: Importar des de %{source} el %{date}
         title: Importa dominis bloquejats
+      invalid_domain_block: 'Un o més dominis blocats s''han omés degut al següent error(s): %{error}'
       new:
         title: Importa dominis bloquejats
       no_file: No s'ha seleccionat cap fitxer
@@ -573,23 +574,24 @@ ca:
       actions:
         delete_description_html: Els tuts reportats seran eliminats i un cop serà gravat per a ajudar-te a escalar en futures infraccions des del mateix compte.
         mark_as_sensitive_description_html: Els mèdia dels tuts reportats seran marcats com a sensibles i una acció serà gravada per ajudar a escalar en futures infraccions del mateix compte.
-        other_description_html: Veu més opcions controlant el comportament del compte i personalitza la comunicació al compte reportat.
-        resolve_description_html: No serà presa cap acció contra el compte reportat, cap cop serà gravat i l'informe es tancarà.
+        other_description_html: Veu més opcions controlant el comportament del compte i personalitza la comunicació al compte denunciat.
+        resolve_description_html: No serà presa cap acció contra el compte denunciat, no se'n registrarà res i l'informe es tancarà.
         silence_description_html: El compte només serà visible a qui ja el seguia o l'ha cercat manualment, limitant-ne fortament l'abast. Sempre es pot revertir. Es tancaran tots els informes contra aquest compte.
         suspend_description_html: Aquest compte i tots els seus continguts seran inaccessibles i finalment eliminats, i interaccionar amb ell no serà possible. Reversible en 30 dies. Tanca tots els informes contra aquest compte.
-      actions_description_html: Decideix quina acció a prendre per a resoldre aquest informe. Si prens un acció punitiva contra el compte reportat, se li enviarà una notificació per correu electrònic, excepte quan la categoria <strong>Spam</strong> és seleccionada.
+      actions_description_html: Decideix quina acció a prendre per a resoldre aquest informe. Si prens un acció punitiva contra el compte denunciat, se li enviarà una notificació per correu electrònic, excepte quan se selecciona la categoria <strong>Spam</strong>.
       actions_description_remote_html: Decideix quina acció prendre per a resoldre aquest informe. Això només afectarà com <strong>el teu</strong> servidor es comunica amb aquest compte remot i en gestiona el contingut.
       add_to_report: Afegir més al informe
-      are_you_sure: N'estàs segur?
-      assign_to_self: Assignar-me
+      are_you_sure: Segur?
+      assign_to_self: Assigna'm
       assigned: Moderador assignat
-      by_target_domain: Domini del compte reportat
+      by_target_domain: Domini del compte denunciat
       category: Categoria
-      category_description_html: El motiu pel qual aquest compte o contingut ha estat reportat serà citat en la comunicació amb el compte reportat
+      category_description_html: El motiu pel qual aquest compte o contingut ha estat denunciat se citarà en la comunicació amb el compte denunciat
       comment:
         none: Cap
       comment_description_html: 'Per a donar més informació, %{name} ha escrit:'
-      created_at: Reportat
+      confirm_action: Confirma l'acció de moderació contra @%{acct}
+      created_at: Denunciat
       delete_and_resolve: Elimina les publicacions
       forwarded: Reenviat
       forwarded_to: Reenviat a %{domain}
@@ -598,28 +600,48 @@ ca:
       mark_as_unresolved: Marcar com a sense resoldre
       no_one_assigned: Ningú
       notes:
-        create: Afegir una nota
+        create: Afegeix una nota
         create_and_resolve: Resol amb una nota
         create_and_unresolve: Reobre amb una nota
         delete: Elimina
         placeholder: Descriu les accions que s'han pres o qualsevol altra actualització relacionada…
         title: Notes
-      notes_description_html: Veu i deixa notes als altres moderadors i a tu mateix
-      quick_actions_description_html: 'Pren una acció ràpida o desplaça''t avall per a veure el contingut reportat:'
+      notes_description_html: Mira i deixa notes als altres moderadors i a tu mateix
+      processed_msg: 'L''informe #%{id} ha estat processat amb èxit'
+      quick_actions_description_html: 'Pren una acció ràpida o desplaça''t avall per a veure el contingut denunciat:'
       remote_user_placeholder: l'usuari remot des de %{instance}
       reopen: Reobre l'informe
       report: 'Informe #%{id}'
-      reported_account: Compte reportat
-      reported_by: Reportat per
+      reported_account: Compte denunciat
+      reported_by: Denunciat per
       resolved: Resolt
-      resolved_msg: Informe resolt amb èxit!
+      resolved_msg: Informe resolt correctament!
       skip_to_actions: Salta a les accions
       status: Estat
       statuses: Contingut reportat
-      statuses_description_html: El contingut ofensiu serà citat en comunicació amb el compte reportat
-      target_origin: Origen del compte reportat
+      statuses_description_html: El contingut ofensiu se citarà en comunicació amb el compte denunciat
+      summary:
+        action_preambles:
+          delete_html: 'Estàs a punt d''<strong>esborrar</strong> alguns dels tuts de <strong>@%{acct}</strong>. Això provocarà:'
+          mark_as_sensitive_html: 'Estàs a punt de <strong>marcar</strong> alguns dels tuts de <strong>@%{acct}</strong> com a <strong>sensibles</strong>. Això provocarà:'
+          silence_html: 'Estàs a punt de <strong>limitar</strong> el compte de <strong>@%{acct}</strong>. Això provocarà:'
+          suspend_html: 'Estàs a punt de <strong>suspendre</strong> el compte de <strong>@%{acct}</strong>. Això provocarà:'
+        actions:
+          delete_html: Esborra els tuts ofensius
+          mark_as_sensitive_html: Marca el contingut multimèdia dels tuts com a sensibles
+          silence_html: Limita severament l'abast de <strong>@%{acct}</strong> fent el seus perfils i continguts només visibles per la gent que ja els estan seguint o que cerquin manualment els seus perfils
+          suspend_html: Suspèn <strong>@%{acct}</strong>, fent els seus perfils i continguts inaccessibles i impossibles d'interactuar
+        close_report: 'Marca l''informe #%{id} com a resolt'
+        close_reports_html: Marca <strong>tots</strong> els informes contra <strong>@%{acct}</strong> com a resolts
+        delete_data_html: Esborra el perfil de <strong>@%{acct}</strong> i els seus continguts dins de 30 dies des d'ara a no ser que es desactivi la suspensió abans
+        preview_preamble_html: "<strong>@%{acct}</strong> rebrà un avís amb el contingut següent:"
+        record_strike_html: Registra una acció contra <strong>@%{acct}</strong> per ajudar a escalar-ho en futures violacions des d'aquest compte
+        send_email_html: Envia un avís per correu electrònic a <strong>@%{acct}</strong>
+        warning_placeholder: Opcional raó adicional d'aquesta acció de moderació.
+      target_origin: Origen del compte denunciat
       title: Informes
       unassign: Treu l'assignació
+      unknown_action_msg: 'Acció desconeguda: %{action}'
       unresolved: No resolt
       updated_at: Actualitzat
       view_profile: Veure perfil
@@ -738,10 +760,10 @@ ca:
       account: Autor
       application: Aplicació
       back_to_account: Torna a la pàgina del compte
-      back_to_report: Torna a la pàgina del informe
+      back_to_report: Torna a la pàgina de l'informe
       batch:
-        remove_from_report: Treu del informe
-        report: Informe
+        remove_from_report: Treu de l'informe
+        report: Denuncia
       deleted: Eliminada
       favourites: Favorits
       history: Històric de versions
@@ -894,7 +916,7 @@ ca:
       subject: Nou compte per a revisar a %{instance} (%{username})
     new_report:
       body: "%{reporter} ha informat de %{target}"
-      body_remote: Algú des de el domini %{domain} ha informat sobre %{target}
+      body_remote: Algú des del domini %{domain} ha informat sobre %{target}
       subject: Informe nou per a %{instance} (#%{id})
     new_trends:
       body: 'Els següents elements necessiten una revisió abans de que puguin ser mostrats públicament:'
@@ -943,6 +965,8 @@ ca:
   auth:
     apply_for_account: Sol·licitar un compte
     change_password: Contrasenya
+    confirmations:
+      wrong_email_hint: Si aquesta adreça de correu electrònic no és correcte, pots canviar-la en els ajustos del compte.
     delete_account: Elimina el compte
     delete_account_html: Si vols suprimir el compte pots <a href="%{path}">fer-ho aquí</a>. Se't demanarà confirmació.
     description:
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index 54d74e725..f96eaae15 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -644,6 +644,7 @@ cs:
       target_origin: Původ nahlášeného účtu
       title: Hlášení
       unassign: Odebrat
+      unknown_action_msg: 'Neznámá akce: %{action}'
       unresolved: Nevyřešeno
       updated_at: Aktualizováno
       view_profile: Zobrazit profil
@@ -979,6 +980,8 @@ cs:
   auth:
     apply_for_account: Požádat o účet
     change_password: Heslo
+    confirmations:
+      wrong_email_hint: Pokud není e-mail správný, můžete si ho změnit v nastavení účtu.
     delete_account: Odstranit účet
     delete_account_html: Chcete-li odstranit svůj účet, <a href="%{path}">pokračujte zde</a>. Budete požádáni o potvrzení.
     description:
diff --git a/config/locales/csb.yml b/config/locales/csb.yml
new file mode 100644
index 000000000..0d4e4b36b
--- /dev/null
+++ b/config/locales/csb.yml
@@ -0,0 +1,12 @@
+---
+csb:
+  errors:
+    '400': The request you submitted was invalid or malformed.
+    '403': You don't have permission to view this page.
+    '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
+    '410': The page you were looking for doesn't exist here anymore.
+    '422': 
+    '429': Too many requests
+    '500': 
+    '503': The page could not be served due to a temporary server failure.
diff --git a/config/locales/cy.yml b/config/locales/cy.yml
index 3e9b20a72..52d1aa202 100644
--- a/config/locales/cy.yml
+++ b/config/locales/cy.yml
@@ -473,6 +473,7 @@ cy:
         private_comment_description_html: 'Er mwyn eich helpu i olrhain o ble mae blociau a fewnforwyd yn dod, bydd blociau a fewnforwyd yn cael eu creu gyda''r sylw preifat canlynol: <q>%{comment}</q>'
         private_comment_template: Mewnforiwyd o %{source} ar %{date}
         title: Mewnforio blociau parth
+      invalid_domain_block: 'Cafodd un neu fwy o flociau parth eu hepgor oherwydd y gwall(au) canlynol: %{error}'
       new:
         title: Mewnforio blociau parth
       no_file: Heb ddewis ffeil
@@ -637,6 +638,7 @@ cy:
       comment:
         none: Dim
       comment_description_html: 'I ddarparu rhagor o wybodaeth, ysgrifennodd %{name}:'
+      confirm_action: Cadarnhau gweithred cymedroli yn erbyn @%{acct}
       created_at: Adroddwyd
       delete_and_resolve: Dileu postiadau
       forwarded: Wedi'i anfon ymlaen
@@ -653,6 +655,7 @@ cy:
         placeholder: Disgrifiwch pa weithredoedd sydd wedi eu cymryd, neu unrhyw ddiweddariadau perthnasol eraill...
         title: Nodiadau
       notes_description_html: Gweld a gadael nodiadau i gymedrolwyr eraill a chi eich hun yn y dyfodol
+      processed_msg: 'Adroddiad ar #%{id} wedi''i brosesu''n llwyddiannus'
       quick_actions_description_html: 'Cymerwch gamau cyflym neu sgroliwch i lawr i weld cynnwys yr adroddwyd amdano:'
       remote_user_placeholder: y defnyddiwr pell o %{instance}
       reopen: Ailagor adroddiad
@@ -665,9 +668,28 @@ cy:
       status: Statws
       statuses: Cynnwys wedi'i adrodd
       statuses_description_html: Bydd cynnwys tramgwyddus yn cael ei ddyfynnu wrth gyfathrebu â'r cyfrif a adroddwyd
+      summary:
+        action_preambles:
+          delete_html: 'Rydych ar fin <strong>dileu</strong> rhai o <strong>bostiadau @%{acct}</strong>. Bydd hyn yn:'
+          mark_as_sensitive_html: 'Rydych ar fin <strong>marcio</strong> rhai o <strong>bostiadau @%{acct}</strong> fel rhai <strong>sensitif</strong>. Bydd hyn yn:'
+          silence_html: 'Rydych ar fin <strong>cyfyngu ar</strong> gyfrif <strong>@%{acct}</strong>. Bydd hyn yn:'
+          suspend_html: 'Rydych ar fin <strong>atal</strong> cyfrif <strong>@%{acct}</strong>. Bydd hyn yn:'
+        actions:
+          delete_html: Tynnu'r postiadau tramgwyddus
+          mark_as_sensitive_html: Nodi fod cyfryngau'r postiadau tramgwyddus yn sensitif
+          silence_html: Cyfyngu'n sylweddol ar gyrhaeddiad <strong>@%{acct}</strong> trwy wneud ei ph/broffil a'i gynnwys ond yn weladwy i bobl sydd eisoes yn ei d/ddilyn neu edrych ar ei ph/broffil â llaw
+          suspend_html: Atal <strong>@%{acct}</strong>, gan wneud ei ph/broffil a'i gynnwys yn anhygyrch ac yn amhosibl rhyngweithio ag ef
+        close_report: 'Nodi adroddiad #%{id} fel wedi''i ddatrys'
+        close_reports_html: Nodi bod <strong>pob</strong> adroddiad yn erbyn <strong>@%{acct}</strong> wedi'i ddatrys
+        delete_data_html: Dileu proffil a chynnwys <strong>@%{acct}</strong> 30 diwrnod o nawr oni bai ei b/fod heb ei h/atal yn y cyfamser
+        preview_preamble_html: 'Bydd <strong>@%{acct}</strong> yn derbyn rhybudd gyda''r cynnwys canlynol:'
+        record_strike_html: Recordio rhybudd yn erbyn <strong>@%{acct}</strong> i'ch helpu i ddwysáu ar achosion o dorri rheolau yn y dyfodol o'r cyfrif hwn
+        send_email_html: Anfon e-bost rhybudd at <strong>@%{acct}</strong>
+        warning_placeholder: Rhesymeg ychwanegol dewisol ar gyfer y cam cymedroli.
       target_origin: Tarddiad y cyfrif a adroddwyd
       title: Adroddiadau
       unassign: Dadneilltuo
+      unknown_action_msg: 'Gweithred anhysbys: %{action}'
       unresolved: Heb ei ddatrys
       updated_at: Diweddarwyd
       view_profile: Gweld proffil
@@ -1015,6 +1037,8 @@ cy:
   auth:
     apply_for_account: Gofyn am gyfrif
     change_password: Cyfrinair
+    confirmations:
+      wrong_email_hint: Os nad yw'r cyfeiriad e-bost hwnnw'n gywir, gallwch ei newid yng ngosodiadau'r cyfrif.
     delete_account: Dileu cyfrif
     delete_account_html: Os hoffech chi ddileu eich cyfrif, mae modd <a href="%{path}">parhau yma</a>. Bydd gofyn i chi gadarnhau.
     description:
diff --git a/config/locales/da.yml b/config/locales/da.yml
index 15e3115f8..913f275cc 100644
--- a/config/locales/da.yml
+++ b/config/locales/da.yml
@@ -4,7 +4,7 @@ da:
     about_mastodon_html: 'Fremtidens sociale netværk: Ingen annoncer, ingen virksomhedsovervågning, etisk design og decentralisering! Vær ejer af egne data med Mastodon!'
     contact_missing: Ikke angivet
     contact_unavailable: Utilgængelig
-    hosted_on: Mostodon hostet på %{domain}
+    hosted_on: Mastodon hostet på %{domain}
     title: Om
   accounts:
     follow: Følg
@@ -209,7 +209,7 @@ da:
         reject_user: Afvis bruger
         remove_avatar_user: Fjern profilbillede
         reopen_report: Genåbn anmeldelse
-        resend_user: Gensend bekræftelsese-mail
+        resend_user: Gensend bekræftelses-e-mail
         reset_password_user: Nulstil adgangskode
         resolve_report: Løs anmeldelse
         sensitive_account: Gennemtving sensitiv konto
@@ -441,6 +441,7 @@ da:
         private_comment_description_html: 'For at man lettere kan holde styr på, hvorfra importerede blokeringer kommer, oprettes disse med flg. private kommentar: <q>%{comment}</q>'
         private_comment_template: Importeret fra %{source} d. %{date}
         title: Import af domæneblokeringer
+      invalid_domain_block: 'En eller flere domæneblokke blev oversprunget grundet flg. fejl: %{error}'
       new:
         title: Import af domæneblokeringer
       no_file: Ingen fill udvalgt
@@ -586,6 +587,7 @@ da:
       comment:
         none: Ingen
       comment_description_html: 'For at give mere information, skrev %{name}:'
+      confirm_action: Bekræft moderatorhandling for %{acct}
       created_at: Anmeldt
       delete_and_resolve: Slet indlæg
       forwarded: Videresendt
@@ -602,6 +604,7 @@ da:
         placeholder: Beskriv udførte foranstaltninger eller andre relevante opdateringer...
         title: Notater
       notes_description_html: Se og skriv notater til andre moderatorer og dit fremtid selv
+      processed_msg: 'Anmeldelse #%{id} er blev behandlet'
       quick_actions_description_html: 'Træf en hurtig foranstaltning eller rul ned for at se anmeldt indhold:'
       remote_user_placeholder: fjernbrugeren fra %{instance}
       reopen: Genåbn anmeldelse
@@ -614,9 +617,28 @@ da:
       status: Status
       statuses: Anmeld indhold
       statuses_description_html: Krænkende indhold citeres i kommunikationen med den anmeldte konto
+      summary:
+        action_preambles:
+          delete_html: 'Du er ved at <strong>fjerne</strong> nogle indlæg fra <strong>@%{acct}</strong>. Dette vil:'
+          mark_as_sensitive_html: 'Du er ved at <strong>markere</strong> nogle indlæg fra <strong>@%{acct}</strong> som <strong>sensitive</strong>. Dette vil:'
+          silence_html: 'Du er ved at <strong>begrænse</strong> nogle indlæg fra kontoen <strong>@%{acct}</strong>. Dette vil:'
+          suspend_html: 'Du er ved at <strong>suspendere</strong> kontoen <strong>@%{acct}</strong>. Dette vil:'
+        actions:
+          delete_html: Fjern de overtrædende indlæg
+          mark_as_sensitive_html: Markér medier i overtrædende indlæg som sensitive
+          silence_html: Begræns kraftigt rækkeviden for <strong>@%{acct}</strong> ved at gøre vedkommendes profil og indhold synligt alene for personer, som allerede er følgere eller manuelt slår profilen op
+          suspend_html: Suspendér <strong>@%{acct}</strong>, hvilket gør vedkommendes profil og indhold utilgængeligt og umuligt at interagere med
+        close_report: 'Markér anmeldelsen #%{id} som løst'
+        close_reports_html: Markér <strong>alle</strong> anmeldelser af <strong>@%{acct}</strong> som løst
+        delete_data_html: Slet profil og indhold for <strong>@%{acct}</strong> 30 dage fra nu medmindre suspenderingen af vedkommende i mellemtiden fjernes
+        preview_preamble_html: "<strong>@%{acct}</strong> vil modtage en advarsel med flg. indhold:"
+        record_strike_html: Registrér en advarsel for <strong>@%{acct}</strong> som hjælpe til eskalering af fremtidige overtrædelser fra samme konto
+        send_email_html: Send en advarselsmail til <strong>@%{acct}</strong>
+        warning_placeholder: Valgfri yderligere begrundelse for modereringshandlingen.
       target_origin: Anmeldte kontos oprindelse
       title: Anmeldelser
       unassign: Fjern tildeling
+      unknown_action_msg: 'Ukendt handling: %{action}'
       unresolved: Uløst
       updated_at: Opdateret
       view_profile: Vis profil
@@ -939,6 +961,8 @@ da:
   auth:
     apply_for_account: Anmod om en konto
     change_password: Adgangskode
+    confirmations:
+      wrong_email_hint: Er denne e-mail-adresse ikke korrekt, kan den ændres i kontoindstillinger.
     delete_account: Slet konto
     delete_account_html: Ønsker du at slette din konto, kan du <a href="%{path}">gøre dette hér</a>. Du vil blive bedt om bekræftelse.
     description:
@@ -972,7 +996,7 @@ da:
     set_new_password: Opsæt ny adgangskode
     setup:
       email_below_hint_html: Er nedenstående e-mailadresse forkert, kan du rette den hér og modtage en ny bekræftelses-e-mail.
-      email_settings_hint_html: Bekræftelsese-mailen er sendt til %{email}. Er denne e-mailadresse forkert, kan du rette den via kontoindstillingerne.
+      email_settings_hint_html: Bekræftelses-e-mailen er sendt til %{email}. Er denne e-mailadresse forkert, kan du rette den via kontoindstillingerne.
       title: Opsætning
     sign_in:
       preamble_html: Log ind med dine <strong>%{domain}</strong>-legitimationsoplysninger. Hostes kontoen på en anden server, vil der ikke kunne logges ind her.
@@ -1040,7 +1064,7 @@ da:
       data_removal: Dine indlæg og andre data fjernes permanent
       email_change_html: Du kan <a href="%{path}">skifte e-mailadresse</a> uden at slette din konto
       email_contact_html: Hvis det stadig ikke ankommer, kan du sende en e-mail til <a href="mailto:%{email}">%{email}</a> for hjælp
-      email_reconfirmation_html: Modtager du ikke bekræftelsese-mailen, kan du <a href="%{path}">anmode om en ny</a>
+      email_reconfirmation_html: Modtager du ikke bekræftelses-e-mailen, kan du <a href="%{path}">anmode om en ny</a>
       irreversible: Du vil ikke kunne gendanne/genaktivere din konto
       more_details_html: For yderligere oplysningerer, tjek <a href="%{terms_path}">fortrolighedspolitikken</a>.
       username_available: Dit brugernavn vil blive tilgængeligt igen
@@ -1639,7 +1663,7 @@ da:
       final_action: Begynd at poste
       final_step: 'Begynd at poste! Selv uden følgere vil offentlige indlæg kunne ses af andre f.eks. på den lokale tidslinje og i hashtags. Man kan introducere sig selv via hastagget #introductions.'
       full_handle: Dit fulde brugernavn
-      full_handle_hint: Dette er, hvad du oplyser til dine venner, så de kan sende dig beskeder eller følge dig fra andre server.
+      full_handle_hint: Dette er, hvad du oplyser til dine venner, så de kan sende dig beskeder eller følge dig fra andre servere.
       subject: Velkommen til Mastodon
       title: Velkommen ombord, %{name}!
   users:
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 1bfcf3568..ee5f3c28e 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -441,6 +441,7 @@ de:
         private_comment_description_html: 'Damit du später nachvollziehen kannst, woher die importierten Sperren stammen, kannst du diesem Eintrag eine private Notiz hinzufügen: <q>%{comment}</q>'
         private_comment_template: Importiert von %{source} am %{date}
         title: Domain-Sperren importieren
+      invalid_domain_block: 'Ein oder mehrere Domainsperren wurden wegen folgenden Fehler(n) übersprungen: %{error}'
       new:
         title: Domain-Sperren importieren
       no_file: Keine Datei ausgewählt
@@ -589,6 +590,7 @@ de:
       comment:
         none: Kein
       comment_description_html: 'Um weitere Informationen bereitzustellen, schrieb %{name} Folgendes:'
+      confirm_action: Moderationsaktion gegen @%{acct} bestätigen
       created_at: Gemeldet
       delete_and_resolve: Beiträge löschen
       forwarded: Weitergeleitet
@@ -605,7 +607,8 @@ de:
         placeholder: Bitte beschreibe, welche Maßnahmen ergriffen wurden oder andere damit verbundene Aktualisierungen …
         title: Notizen
       notes_description_html: Notiz an dich und andere Moderator*innen hinterlassen
-      quick_actions_description_html: 'Führe eine schnelle Aktion aus oder scrolle nach unten, um gemeldete Inhalte zu sehen:'
+      processed_msg: 'Meldung #%{id} erfolgreich bearbeitet'
+      quick_actions_description_html: 'Eine schnelle Aktion ausführen oder nach unten rolle, um gemeldete Inhalte zu sehen:'
       remote_user_placeholder: das externe Profil von %{instance}
       reopen: Meldung wieder eröffnen
       report: 'Meldung #%{id}'
@@ -617,9 +620,28 @@ de:
       status: Status
       statuses: Gemeldeter Inhalt
       statuses_description_html: Störende Inhalte werden in der Kommunikation mit dem gemeldeten Konto zitiert
+      summary:
+        action_preambles:
+          delete_html: 'Du bist dabei, einige Beiträge von <strong>@%{acct}</strong> zu <strong>entfernen</strong>. Dies wird:'
+          mark_as_sensitive_html: 'Du bist dabei, einige Beiträge von <strong>@%{acct}</strong> mit einer <strong>Inhaltswarnung</strong> zu <strong>versehen</strong>. Dies wird:'
+          silence_html: 'Du bist dabei, das Konto von <strong>@%{acct}</strong> <strong>einzuschränken</strong>. Dies wird:'
+          suspend_html: 'Du bist dabei, das Konto von <strong>@%{acct}</strong> zu <strong>sperren</strong>. Dies wird:'
+        actions:
+          delete_html: Die anstößigen Beiträge entfernen
+          mark_as_sensitive_html: Medien der anstößigen Beiträge mit einer Inhaltswarnung versehen
+          silence_html: Schränkt die Reichweite von <strong>@%{acct}</strong> stark ein, indem das Profil und dessen Inhalte nur für Personen sichtbar sind, die dem Profil bereits folgen oder es manuell aufrufen
+          suspend_html: "<strong>@%{acct}</strong> sperren, sodass das Profil und dessen Inhalte nicht mehr zugänglich sind und keine Interaktion mehr möglich ist"
+        close_report: 'Meldung #%{id} als erledigt markieren'
+        close_reports_html: "<strong>Alle</strong> Meldungen gegen <strong>@%{acct}</strong> als erledigt markieren"
+        delete_data_html: Das Profil und die Inhalte von <strong>@%{acct}</strong> werden in 30 Tagen gelöscht, es sei denn, sie werden in der Zwischenzeit entsperrt
+        preview_preamble_html: "<strong>@%{acct}</strong> wird eine Warnung mit folgenden Inhalten erhalten:"
+        record_strike_html: Einen Verstoß gegen <strong>@%{acct}</strong> eintragen, um bei zukünftigen Verstößen desselben Kontos besser reagieren zu können
+        send_email_html: "<strong>@%{acct}</strong> eine Warnung per E-Mail senden"
+        warning_placeholder: Optional zusätzliche Begründung für die Moderationsmaßnahme.
       target_origin: Domain des gemeldeten Kontos
       title: Meldungen
       unassign: Zuweisung entfernen
+      unknown_action_msg: 'Unbekannte Aktion: %{action}'
       unresolved: Ungelöst
       updated_at: Aktualisiert
       view_profile: Profil anzeigen
@@ -805,7 +827,7 @@ de:
           other: In der letzten Woche von %{count} Personen geteilt
         title: Angesagte Links
         usage_comparison: Heute %{today} Mal geteilt, gestern %{yesterday} Mal
-      only_allowed: Nur Erlaubte
+      only_allowed: Nur Genehmigte
       pending_review: Überprüfung ausstehend
       preview_card_providers:
         allowed: Links von diesem Herausgeber können angesagt sein
@@ -823,7 +845,7 @@ de:
         not_discoverable: Autor*in hat sich dafür entschieden, nicht entdeckt zu werden
         shared_by:
           one: Einmal geteilt oder favorisiert
-          other: "%{friendly_count} mal geteilt oder favorisiert"
+          other: "%{friendly_count}-mal geteilt oder favorisiert"
         title: Angesagte Beiträge
       tags:
         current_score: Aktuelle Punktzahl %{score}
@@ -943,6 +965,8 @@ de:
   auth:
     apply_for_account: Konto beantragen
     change_password: Passwort
+    confirmations:
+      wrong_email_hint: Wenn diese E-Mail-Adresse nicht korrekt ist, kann sie in den Kontoeinstellungen geändert werden.
     delete_account: Konto löschen
     delete_account_html: Falls du dein Konto endgültig löschen möchtest, kannst du das <a href="%{path}">hier vornehmen</a>. Du musst dies zusätzlich bestätigen.
     description:
diff --git a/config/locales/devise.csb.yml b/config/locales/devise.csb.yml
new file mode 100644
index 000000000..0de706e41
--- /dev/null
+++ b/config/locales/devise.csb.yml
@@ -0,0 +1 @@
+csb:
diff --git a/config/locales/devise.ko.yml b/config/locales/devise.ko.yml
index 45e5e47f8..dd49b6df4 100644
--- a/config/locales/devise.ko.yml
+++ b/config/locales/devise.ko.yml
@@ -8,44 +8,44 @@ ko:
     failure:
       already_authenticated: 이미 로그인 된 상태입니다.
       inactive: 계정이 아직 활성화 되지 않았습니다.
-      invalid: 올바르지 않은 %{authentication_keys} 혹은 패스워드입니다.
+      invalid: 알맞지 않은 %{authentication_keys} 혹은 암호입니다.
       last_attempt: 계정이 잠기기까지 한 번의 시도가 남았습니다.
       locked: 계정이 잠겼습니다.
-      not_found_in_database: 올바르지 않은 %{authentication_keys} 혹은 패스워드입니다.
-      pending: 계정이 아직 심사 중입니다.
+      not_found_in_database: 알맞지 않은 %{authentication_keys} 혹은 암호입니다.
+      pending: 이 계정은 아직 검토 중입니다.
       timeout: 세션이 만료 되었습니다. 다시 로그인 해 주세요.
       unauthenticated: 계속 하려면 로그인을 해야 합니다.
       unconfirmed: 계속 하려면 이메일을 확인 받아야 합니다.
     mailer:
       confirmation_instructions:
-        action: 이메일 확인
+        action: 이메일 주소 검증
         action_with_app: 확인하고 %{app}으로 돌아가기
         explanation: 당신은 %{host}에서 이 이메일로 가입하셨습니다. 클릭만 하시면 계정이 활성화 됩니다. 만약 당신이 가입한 게 아니라면 이 메일을 무시해 주세요.
         explanation_when_pending: 당신은 %{host}에 가입 요청을 하셨습니다. 이 이메일이 확인 되면 우리가 가입 요청을 리뷰하고 승인할 수 있습니다. 그 전까지는 로그인을 할 수 없습니다. 당신의 가입 요청이 거부 될 경우 당신에 대한 정보는 모두 삭제 되며 따로 요청 할 필요는 없습니다. 만약 당신이 가입 요청을 한 게 아니라면 이 메일을 무시해 주세요.
         extra_html: <a href="%{terms_path}">서버의 규칙</a>과 <a href="%{policy_path}">이용 약관</a>도 확인해 주세요.
         subject: '마스토돈: %{instance}에 대한 확인 메일'
-        title: 이메일 주소 확인
+        title: 이메일 주소 검증
       email_changed:
-        explanation: '당신의 계정에 대한 이메일이 다음과 같이 바뀌려고 합니다:'
-        extra: 만약 당신이 메일을 바꾸지 않았다면 누군가가 당신의 계정에 대한 접근 권한을 얻은 것입니다. 즉시 패스워드를 바꾼 후, 계정이 잠겼다면 서버의 관리자에게 연락 하세요.
+        explanation: '귀하의 계정이 다음의 이메일 주소로 변경됩니다:'
+        extra: 만약 이메일을 바꾸지 않았다면 누군가 계정에 대한 접근 권한을 얻은 것입니다. 바로 암호를 바꾸셔야 하며, 만약 계정이 잠겼다면 서버의 운영자에게 연락 바랍니다.
         subject: '마스토돈: 이메일이 변경 되었습니다'
         title: 새 이메일 주소
       password_change:
-        explanation: 당신의 계정 패스워드가 변경되었습니다.
+        explanation: 계정의 암호를 바꾸었습니다.
         extra: 만약 패스워드 변경을 하지 않았다면 누군가가 당신의 계정에 대한 접근 권한을 얻은 것입니다. 즉시 패스워드를 바꾼 후, 계정이 잠겼다면 서버의 관리자에게 연락 하세요.
-        subject: '마스토돈: 패스워드가 변경 되었습니다'
-        title: 패스워드가 변경 되었습니다
+        subject: 'Mastodon: 암호 변경함'
+        title: 암호 변경함
       reconfirmation_instructions:
         explanation: 이메일 주소를 바꾸려면 새 이메일 주소를 확인해야 합니다.
         extra: 당신이 시도한 것이 아니라면 이 메일을 무시해 주세요. 위 링크를 클릭하지 않으면 이메일 변경은 일어나지 않습니다.
         subject: '마스토돈: %{instance}에 대한 이메일 확인'
-        title: 이메일 주소 확인
+        title: 이메일 주소 검증
       reset_password_instructions:
-        action: 패스워드 변경
-        explanation: 계정에 대한 패스워드 변경을 요청하였습니다.
-        extra: 만약 당신이 시도한 것이 아니라면 이 메일을 무시해 주세요. 위 링크를 클릭해 패스워드를 새로 설정하기 전까지는 패스워드가 바뀌지 않습니다.
-        subject: '마스토돈: 패스워드 재설정 방법'
-        title: 패스워드 재설정
+        action: 암호 변경
+        explanation: 계정에 새 암호를 쓰도록 요청받았습니다.
+        extra: 요청하지 않았다면 이 이메일을 무시하셔야 합니다. 상기 링크에 접속하지 않으면 암호는 새것으로 변경되지 않습니다.
+        subject: 'Mastodon: 암호 재설정 설명'
+        title: 암호 재설정
       two_factor_disabled:
         explanation: 당신의 계정에 설정된 이중 인증이 비활성화 되었습니다. 이제 이메일과 암호만으로 로그인이 가능합니다.
         subject: '마스토돈: 이중 인증 비활성화'
@@ -81,11 +81,11 @@ ko:
       failure: '"%{reason}" 때문에 당신을 %{kind}에서 인증할 수 없습니다.'
       success: "%{kind} 계정을 성공적으로 인증했습니다."
     passwords:
-      no_token: 패스워드 재설정 이메일을 거치지 않고는 여기에 올 수 없습니다. 만약 패스워드 재설정 메일에서 온 것이라면 URL이 맞는지 확인해 주세요.
+      no_token: 이 페이지는 암호 재설정 이메일을 거쳐야 접근할 수 있습니다. 만약 암호 재설정 이메일 거치지 않았다면 사용하려는 URL이 맞는지 확인 바랍니다.
       send_instructions: 당신의 이메일 주소가 우리의 DB에 있다면 패스워드 복구 링크가 몇 분 이내에 메일로 발송 됩니다. 만약 메일을 받지 못 하신 경우 스팸 폴더를 확인해 주세요.
       send_paranoid_instructions: 당신의 이메일 주소가 우리의 DB에 있다면 패스워드 복구 링크가 몇 분 이내에 메일로 발송 됩니다. 만약 메일을 받지 못 하신 경우 스팸 폴더를 확인해 주세요.
-      updated: 패스워드가 재설정 되었습니다. 로그인 되었습니다.
-      updated_not_active: 패스워드가 성공적으로 변경 되었습니다.
+      updated: 암호를 잘 바꾸었습니다. 현재 로그인 상태입니다.
+      updated_not_active: 암호를 잘 변경하였습니다.
     registrations:
       destroyed: 안녕히 가세요! 계정이 성공적으로 제거되었습니다. 다시 만나기를 희망합니다.
       signed_up: 안녕하세요! 성공적으로 가입했습니다.
diff --git a/config/locales/devise.uz.yml b/config/locales/devise.uz.yml
new file mode 100644
index 000000000..fab6a0655
--- /dev/null
+++ b/config/locales/devise.uz.yml
@@ -0,0 +1,13 @@
+---
+uz:
+  devise:
+    confirmations:
+      confirmed: Sizning elektron pochta manzilingiz muvaffaqiyatli tasdiqlandi.
+      send_instructions: Bir necha daqiqadan so'ng elektron pochta manzilingizni qanday tasdiqlash bo'yicha ko'rsatmalar bilan elektron pochta xabarini olasiz. Agar siz ushbu xatni olmagan bo'lsangiz, spam jildini tekshiring.
+      send_paranoid_instructions: Agar sizning elektron pochta manzilingiz bizning ma'lumotlar bazamizda mavjud bo'lsa, sizga bir necha daqiqadan so'ng elektron pochta manzilingizni qanday tasdiqlash bo'yicha ko'rsatmalar yozilgan elektron pochta xabari keladi. Agar siz ushbu xatni olmagan bo'lsangiz, spam jildini tekshiring.
+    failure:
+      already_authenticated: Siz allaqachon tizimga kirgansiz.
+    mailer:
+      reset_password_instructions:
+        action: Parolni almashtirish
+        title: Parolni tiklash
diff --git a/config/locales/devise.zh-CN.yml b/config/locales/devise.zh-CN.yml
index c752ceed8..4a47dddec 100644
--- a/config/locales/devise.zh-CN.yml
+++ b/config/locales/devise.zh-CN.yml
@@ -74,7 +74,7 @@ zh-CN:
         subject: Mastodon:安全密钥认证已禁用
         title: 安全密钥已禁用
       webauthn_enabled:
-        explanation: 你的帐户已启用安全密钥身份验证。你的安全密钥现在可以用于登录。
+        explanation: 你的账户已启用安全密钥身份验证。你的安全密钥现在可以用于登录。
         subject: Mastodon:安全密钥认证已启用
         title: 已启用安全密钥
     omniauth_callbacks:
diff --git a/config/locales/doorkeeper.csb.yml b/config/locales/doorkeeper.csb.yml
new file mode 100644
index 000000000..0de706e41
--- /dev/null
+++ b/config/locales/doorkeeper.csb.yml
@@ -0,0 +1 @@
+csb:
diff --git a/config/locales/doorkeeper.es.yml b/config/locales/doorkeeper.es.yml
index d06646e7b..e8d9d8e93 100644
--- a/config/locales/doorkeeper.es.yml
+++ b/config/locales/doorkeeper.es.yml
@@ -122,12 +122,14 @@ es:
         admin/accounts: Administración de cuentas
         admin/all: Todas las funciones administrativas
         admin/reports: Administración de informes
+        all: Acceso completo a tu cuenta de Mastodon
         blocks: Bloqueos
         bookmarks: Marcadores
         conversations: Conversaciones
         crypto: Cifrado de extremo a extremo
         favourites: Favoritos
         filters: Filtros
+        follow: Seguimientos, silenciad@s y bloqueos
         follows: Seguidos
         lists: Listas
         media: Adjuntos multimedia
diff --git a/config/locales/doorkeeper.eu.yml b/config/locales/doorkeeper.eu.yml
index 68706d9d9..8d2a9f3b6 100644
--- a/config/locales/doorkeeper.eu.yml
+++ b/config/locales/doorkeeper.eu.yml
@@ -122,12 +122,14 @@ eu:
         admin/accounts: Kontuen administrazioa
         admin/all: Funtzio administratibo guztiak
         admin/reports: Salaketen administrazioa
+        all: Sarbide osoa zure Mastodon kontura
         blocks: Blokeoak
         bookmarks: Laster-markak
         conversations: Elkarrizketak
         crypto: Muturretik-muturrerako zifraketa
         favourites: Gogokoak
         filters: Iragazkiak
+        follow: Jarraitzeak, mututzeak eta blokeatzeak
         follows: Jarraipenak
         lists: Zerrendak
         media: Multimedia eranskinak
@@ -147,9 +149,19 @@ eu:
     scopes:
       admin:read: zerbitzariko datu guztiak irakurri
       admin:read:accounts: kontu guztien informazio sentsiblea irakurri
+      admin:read:canonical_email_blocks: irakurri eposta kanonikoen blokeatzeari buruzko informazio sentikorra
+      admin:read:domain_allows: irakurri onartutako domeinu guztien informazio sentikorra
+      admin:read:domain_blocks: irakurri blokeatutako domeinu guztien informazio sentikorra
+      admin:read:email_domain_blocks: irakurri blokeatutako eposta domeinu guztien informazio sentikorra
+      admin:read:ip_blocks: irakurri blokeatutako IP guztien informazio sentikorra
       admin:read:reports: salaketa guztietako eta salatutako kontu guztietako informazio sentsiblea irakurri
       admin:write: zerbitzariko datu guztiak aldatu
       admin:write:accounts: kontuetan moderazio ekintzak burutu
+      admin:write:canonical_email_blocks: gauzatu moderazio ekintzak eposta kanonikoen blokeatzean
+      admin:write:domain_allows: gauzatu moderazio ekintzak onartutako domeinuetan
+      admin:write:domain_blocks: gauzatu moderazio ekintzak domeinuen blokeatzeetan
+      admin:write:email_domain_blocks: gauzatu moderazio ekintzak eposta domeinuen blokeatzeetan
+      admin:write:ip_blocks: gauzatu moderazio ekintzak IP blokeatzeetan
       admin:write:reports: salaketetan moderazio ekintzak burutu
       crypto: erabili muturretik muturrerako zifraketa
       follow: aldatu kontuaren erlazioak
diff --git a/config/locales/doorkeeper.gl.yml b/config/locales/doorkeeper.gl.yml
index 959942c1b..622fcfe76 100644
--- a/config/locales/doorkeeper.gl.yml
+++ b/config/locales/doorkeeper.gl.yml
@@ -74,7 +74,7 @@ gl:
         authorized_at: Autorizada o %{date}
         description_html: Estas aplicacións poden acceder á túa conta usando a API. Se ves aplicacións que non recoñeces, ou hai comportamentos non consentidos dalgunha delas, podes revogar o acceso.
         last_used_at: Último acceso o %{date}
-        never_used: Nunca usada
+        never_used: Nunca empregada
         scopes: Permisos
         superapp: Interno
         title: As túas aplicacións autorizadas
@@ -149,18 +149,18 @@ gl:
     scopes:
       admin:read: ler todos os datos no servidor
       admin:read:accounts: ler información sensible de todas as contas
-      admin:read:canonical_email_blocks: ler a información sensible de tódolos bloqueos de email canónicos
+      admin:read:canonical_email_blocks: ler a información sensíbel de tódolos bloqueos de correos electrónicos canónicos
       admin:read:domain_allows: ler a información sensible de tódolos dominios permitidos
       admin:read:domain_blocks: ler a información sensible de tódolos bloqueos de dominio
-      admin:read:email_domain_blocks: ler a información sensible de tódolos dominios de email
+      admin:read:email_domain_blocks: ler a información sensible de tódolos dominios de correo electrónico
       admin:read:ip_blocks: ler a información sensible de tódolos bloqueos de IP
       admin:read:reports: ler información sensible de todos os informes e contas denunciadas
       admin:write: modificar todos os datos no servidor
       admin:write:accounts: executar accións de moderación nas contas
-      admin:write:canonical_email_blocks: realizar accións de moderación en bloqueos de email canónicos
+      admin:write:canonical_email_blocks: realizar accións de moderación en bloqueos de correo electrónico canónicos
       admin:write:domain_allows: realizar accións de moderación en dominios permitidos
       admin:write:domain_blocks: realizar accións de moderación en bloqueos de dominio
-      admin:write:email_domain_blocks: realizar accións de moderación en bloqueos de dominio de email
+      admin:write:email_domain_blocks: realizar accións de moderación en bloqueos de dominio de correo electrónico
       admin:write:ip_blocks: realizar accións de moderación en bloqueos de IPs
       admin:write:reports: executar accións de moderación nas denuncias
       crypto: usar cifrado de extremo-a-extremo
diff --git a/config/locales/doorkeeper.hy.yml b/config/locales/doorkeeper.hy.yml
index ffb56c810..6548d1e5e 100644
--- a/config/locales/doorkeeper.hy.yml
+++ b/config/locales/doorkeeper.hy.yml
@@ -123,6 +123,7 @@ hy:
         mutes: Լռեցուածներ
         notifications: Ծանուցումներ
         push: Հրելու ծանուցումներ
+        search: Որոնել
         statuses: Գրառումներ
     layouts:
       admin:
diff --git a/config/locales/doorkeeper.ko.yml b/config/locales/doorkeeper.ko.yml
index 140f83862..504fefd28 100644
--- a/config/locales/doorkeeper.ko.yml
+++ b/config/locales/doorkeeper.ko.yml
@@ -81,7 +81,7 @@ ko:
     errors:
       messages:
         access_denied: 리소스 소유자 또는 인증 서버가 요청을 거부했습니다.
-        credential_flow_not_configured: Doorkeeper.configure.resource_owner_from_credentials의 설정이 되어있지 않아 리소스 소유자 패스워드 자격증명이 실패하였습니다.
+        credential_flow_not_configured: Doorkeeper.configure.resource_owner_from_credentials의 설정이 되어있지 않아 리소스 소유자 암호 자격증명이 실패하였습니다.
         invalid_client: 알 수 없는 클라이언트이기 때문에 클라이언트 인증이 실패하였습니다, 클라이언트 자격증명이 포함되지 않았거나, 지원 되지 않는 메소드입니다.
         invalid_grant: 제공된 권한 부여가 잘못되거나, 만료되었거나, 취소되었거나, 권한 부여 요청에 사용된 리디렉션 URI가 일치하지 않거나, 다른 클라이언트에 지정되었습니다.
         invalid_redirect_uri: 리디렉션 URI가 올바르지 않습니다
diff --git a/config/locales/doorkeeper.ru.yml b/config/locales/doorkeeper.ru.yml
index e650a963d..6fb149b7e 100644
--- a/config/locales/doorkeeper.ru.yml
+++ b/config/locales/doorkeeper.ru.yml
@@ -151,6 +151,7 @@ ru:
       admin:read:accounts: читать конфиденциальную информацию всех учётных записей
       admin:read:canonical_email_blocks: чтение конфиденциальной информации всех канонических блоков электронной почты
       admin:read:domain_allows: чтение конфиденциальной информации для всего домена позволяет
+      admin:read:domain_blocks: чтение конфиденциальной информации для всего домена позволяет
       admin:read:reports: читать конфиденциальную информацию о всех жалобах и учётных записях с жалобами
       admin:write: модифицировать все данные на сервере
       admin:write:accounts: производить модерацию учётных записей
diff --git a/config/locales/doorkeeper.uz.yml b/config/locales/doorkeeper.uz.yml
new file mode 100644
index 000000000..3ed042df3
--- /dev/null
+++ b/config/locales/doorkeeper.uz.yml
@@ -0,0 +1 @@
+uz:
diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml
index 4b0ee8773..3bda08dcd 100644
--- a/config/locales/en-GB.yml
+++ b/config/locales/en-GB.yml
@@ -69,6 +69,47 @@ en-GB:
       enable: Unfreeze
       enable_sign_in_token_auth: Enable e-mail token authentication
       enabled: Enabled
+      enabled_msg: Successfully unfroze %{username}'s account
+      followers: Followers
+      follows: Follows
+      header: Header
+      inbox_url: Inbox URL
+      invite_request_text: Reasons for joining
+      invited_by: Invited by
+      ip: IP
+      joined: Joined
+      location:
+        all: All
+        local: Local
+        remote: Remote
+        title: Location
+      login_status: Login status
+      media_attachments: Media attachments
+      memorialize: Turn into memoriam
+      memorialized: Memorialised
+      memorialized_msg: Successfully turned %{username} into a memorial account
+      moderation:
+        active: Active
+        all: All
+        pending: Pending
+        silenced: Limited
+        suspended: Suspended
+        title: Moderation
+      moderation_notes: Moderation notes
+      most_recent_activity: Most recent activity
+      most_recent_ip: Most recent IP
+      no_account_selected: No accounts were changed as none were selected
+      no_limits_imposed: No limits imposed
+      no_role_assigned: No role assigned
+      not_subscribed: Not subscribed
+      pending: Pending review
+      perform_full_suspension: Suspend
+      previous_strikes: Previous strikes
+      previous_strikes_description_html:
+        one: This account has <strong>one</strong> strike.
+        other: This account has <strong>%{count}</strong> strikes.
+      promote: Promote
+      protocol: Protocol
     roles:
       categories:
         devops: DevOps
@@ -91,3 +132,32 @@ en-GB:
     platforms:
       blackberry: BlackBerry
       chrome_os: ChromeOS
+  user_mailer:
+    warning:
+      subject:
+        silence: Your account %{acct} has been limited
+        suspend: Your account %{acct} has been suspended
+      title:
+        delete_statuses: Posts removed
+        disable: Account frozen
+        mark_statuses_as_sensitive: Posts marked as sensitive
+        none: Warning
+        sensitive: Account marked as sensitive
+        silence: Account limited
+        suspend: Account suspended
+    welcome:
+      edit_profile_action: Setup profile
+      edit_profile_step: You can customise your profile by uploading a profile picture, changing your display name and more. You can opt-in to review new followers before they’re allowed to follow you.
+      explanation: Here are some tips to get you started
+      final_action: Start posting
+      final_step: 'Start posting! Even without followers, your public posts may be seen by others, for example on the local timeline or in hashtags. You may want to introduce yourself on the #introductions hashtag.'
+      full_handle: Your full handle
+      full_handle_hint: This is what you would tell your friends so they can message or follow you from another server.
+      subject: Welcome to Mastodon
+      title: Welcome aboard, %{name}!
+  users:
+    follow_limit_reached: You cannot follow more than %{limit} people
+    invalid_otp_token: Invalid two-factor code
+    otp_lost_help_html: If you lost access to both, you may get in touch with %{email}
+    seamless_external_login: You are logged in via an external service, so password and e-mail settings are not available.
+    signed_in_as: 'Signed in as:'
diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml
deleted file mode 100644
index 2cba40da0..000000000
--- a/config/locales/en_GB.yml
+++ /dev/null
@@ -1,1043 +0,0 @@
----
-en_GB:
-  about:
-    about_hashtag_html: These are public toots tagged with <strong>#%{hashtag}</strong>. You can interact with them if you have an account anywhere in the fediverse.
-    about_mastodon_html: Mastodon is a social network based on open web protocols and free, open-source software. It is decentralized like e-mail.
-    about_this: About
-    active_count_after: active
-    active_footnote: Monthly Active Users (MAU)
-    administered_by: 'Administered by:'
-    api: API
-    apps: Mobile apps
-    apps_platforms: Use Mastodon from iOS, Android and other platforms
-    browse_directory: Browse a profile directory and filter by interests
-    browse_public_posts: Browse a live stream of public posts on Mastodon
-    contact: Contact
-    contact_missing: Not set
-    contact_unavailable: N/A
-    discover_users: Discover users
-    documentation: Documentation
-    extended_description_html: |
-      <h3>1A good place for rules</h3>2
-      <p>3The extended description has not been set up yet.</p>4
-    federation_hint_html: With an account on %{instance} you'll be able to follow people on any Mastodon server and beyond.
-    generic_description: "%{domain} is one server in the network"
-    get_apps: Try a mobile app
-    hosted_on: Mastodon hosted on %{domain}
-    learn_more: Learn more
-    privacy_policy: Privacy policy
-    see_whats_happening: See what's happening
-    server_stats: 'Server stats:'
-    source_code: Source code
-    status_count_after:
-      one: status
-      other: statuses
-    status_count_before: Who authored
-    tagline: Follow friends and discover new ones
-    terms: Terms of service
-    user_count_after:
-      one: user
-      other: users
-    user_count_before: Home to
-    what_is_mastodon: What is Mastodon?
-  accounts:
-    choices_html: "%{name}'s choices:"
-    follow: Follow
-    followers:
-      one: Follower
-      other: Follower
-    following: Following
-    joined: Joined %{date}
-    last_active: last active
-    link_verified_on: Ownership of this link was checked on %{date}
-    media: Media
-    moved_html: "%{name} has moved to %{new_profile_link}:"
-    network_hidden: This information is not available
-    nothing_here: There is nothing here!
-    people_followed_by: People whom %{name} follows
-    people_who_follow: People who follow %{name}
-    pin_errors:
-      following: You must be already following the person you want to endorse
-    posts:
-      one: Toot
-      other: Toots
-    posts_tab_heading: Toots
-    posts_with_replies: Toots and replies
-    reserved_username: The username is reserved
-    roles:
-      admin: Admin
-      bot: Bot
-      moderator: Mod
-    unfollow: Unfollow
-  admin:
-    account_actions:
-      action: Perform action
-      title: Perform moderation action on %{acct}
-    account_moderation_notes:
-      create: Leave note
-      created_msg: Moderation note successfully created!
-      delete: Delete
-      destroyed_msg: Moderation note successfully destroyed!
-    accounts:
-      approve: Approve
-      are_you_sure: Are you sure?
-      avatar: Avatar
-      by_domain: Domain
-      change_email:
-        changed_msg: Account email successfully changed!
-        current_email: Current email
-        label: Change email
-        new_email: New email
-        submit: Change email
-        title: Change email for %{username}
-      confirm: Confirm
-      confirmed: Confirmed
-      confirming: Confirming
-      deleted: Deleted
-      demote: Demote
-      disable: Disable
-      disable_two_factor_authentication: Disable 2FA
-      disabled: Disabled
-      display_name: Display name
-      domain: Domain
-      edit: Edit
-      email: Email
-      email_status: Email status
-      enable: Enable
-      enabled: Enabled
-      followers: Followers
-      follows: Follows
-      header: Header
-      inbox_url: Inbox URL
-      invited_by: Invited by
-      ip: IP
-      joined: Joined
-      location:
-        all: All
-        local: Local
-        remote: Remote
-        title: Location
-      login_status: Login status
-      media_attachments: Media attachments
-      memorialize: Turn into memoriam
-      moderation:
-        active: Active
-        all: All
-        pending: Pending
-        silenced: Silenced
-        suspended: Suspended
-        title: Moderation
-      moderation_notes: Moderation notes
-      most_recent_activity: Most recent activity
-      most_recent_ip: Most recent IP
-      no_limits_imposed: No limits imposed
-      not_subscribed: Not subscribed
-      pending: Pending review
-      perform_full_suspension: Suspend
-      promote: Promote
-      protocol: Protocol
-      public: Public
-      push_subscription_expires: PuSH subscription expires
-      redownload: Refresh profile
-      reject: Reject
-      remove_avatar: Remove avatar
-      remove_header: Remove header
-      resend_confirmation:
-        already_confirmed: This user is already confirmed
-        send: Resend confirmation email
-        success: Confirmation email successfully sent!
-      reset: Reset
-      reset_password: Reset password
-      resubscribe: Resubscribe
-      role: Permissions
-      roles:
-        admin: Administrator
-        moderator: Moderator
-        staff: Staff
-        user: User
-      search: Search
-      shared_inbox_url: Shared inbox URL
-      show:
-        created_reports: Made reports
-        targeted_reports: Reported by others
-      silence: Silence
-      silenced: Silenced
-      statuses: Statuses
-      subscribe: Subscribe
-      suspended: Suspended
-      title: Accounts
-      unconfirmed_email: Unconfirmed email
-      undo_silenced: Undo silence
-      undo_suspension: Undo suspension
-      unsubscribe: Unsubscribe
-      username: Username
-      warn: Warn
-      web: Web
-    action_logs:
-      actions:
-        assigned_to_self_report: "%{name} assigned report %{target} to themselves"
-        change_email_user: "%{name} changed the e-mail address of user %{target}"
-        confirm_user: "%{name} confirmed e-mail address of user %{target}"
-        create_account_warning: "%{name} sent a warning to %{target}"
-        create_custom_emoji: "%{name} uploaded new emoji %{target}"
-        create_domain_block: "%{name} blocked domain %{target}"
-        create_email_domain_block: "%{name} blacklisted e-mail domain %{target}"
-        demote_user: "%{name} demoted user %{target}"
-        destroy_custom_emoji: "%{name} destroyed emoji %{target}"
-        destroy_domain_block: "%{name} unblocked domain %{target}"
-        destroy_email_domain_block: "%{name} whitelisted e-mail domain %{target}"
-        destroy_status: "%{name} removed status by %{target}"
-        disable_2fa_user: "%{name} disabled two factor requirement for user %{target}"
-        disable_custom_emoji: "%{name} disabled emoji %{target}"
-        disable_user: "%{name} disabled login for user %{target}"
-        enable_custom_emoji: "%{name} enabled emoji %{target}"
-        enable_user: "%{name} enabled login for user %{target}"
-        memorialize_account: "%{name} turned %{target}'s account into a memoriam page"
-        promote_user: "%{name} promoted user %{target}"
-        remove_avatar_user: "%{name} removed %{target}'s avatar"
-        reopen_report: "%{name} reopened report %{target}"
-        reset_password_user: "%{name} reset password of user %{target}"
-        resolve_report: "%{name} resolved report %{target}"
-        silence_account: "%{name} silenced %{target}'s account"
-        suspend_account: "%{name} suspended %{target}'s account"
-        unassigned_report: "%{name} unassigned report %{target}"
-        unsilence_account: "%{name} unsilenced %{target}'s account"
-        unsuspend_account: "%{name} unsuspended %{target}'s account"
-        update_custom_emoji: "%{name} updated emoji %{target}"
-        update_status: "%{name} updated status by %{target}"
-      deleted_status: "(deleted status)"
-      title: Audit log
-    custom_emojis:
-      by_domain: Domain
-      copied_msg: Successfully created local copy of the emoji
-      copy: Copy
-      copy_failed_msg: Could not make a local copy of that emoji
-      created_msg: Emoji successfully created!
-      delete: Delete
-      destroyed_msg: Emojo successfully destroyed!
-      disable: Disable
-      disabled_msg: Successfully disabled that emoji
-      emoji: Emoji
-      enable: Enable
-      enabled_msg: Successfully enabled that emoji
-      listed: Listed
-      new:
-        title: Add new custom emoji
-      overwrite: Overwrite
-      shortcode: Shortcode
-      shortcode_hint: At least 2 characters, only alphanumeric characters and underscores
-      title: Custom emojis
-      unlisted: Unlisted
-      update_failed_msg: Could not update that emoji
-      updated_msg: Emoji successfully updated!
-      upload: Upload
-    dashboard:
-      backlog: backlogged jobs
-      config: Configuration
-      feature_deletions: Account deletions
-      feature_invites: Invite links
-      feature_profile_directory: Profile directory
-      feature_registrations: Registrations
-      feature_relay: Federation relay
-      features: Features
-      hidden_service: Federation with hidden services
-      open_reports: open reports
-      recent_users: Recent users
-      search: Full-text search
-      single_user_mode: Single user mode
-      software: Software
-      space: Space usage
-      title: Dashboard
-      total_users: users in total
-      trends: Trends
-      week_interactions: interactions this week
-      week_users_active: active this week
-      week_users_new: users this week
-    domain_blocks:
-      add_new: Add new domain block
-      created_msg: Domain block is now being processed
-      destroyed_msg: Domain block has been undone
-      domain: Domain
-      new:
-        create: Create block
-        hint: The domain block will not prevent creation of account entries in the database, but will retroactively and automatically apply specific moderation methods on those accounts.
-        severity:
-          desc_html: "<strong>Silence</strong> will make the account's posts invisible to anyone who isn't following them. <strong>Suspend</strong> will remove all of the account's content, media, and profile data. Use <strong>None</strong> if you just want to reject media files."
-          noop: None
-          silence: Silence
-          suspend: Suspend
-        title: New domain block
-      reject_media: Reject media files
-      reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions
-      reject_reports: Reject reports
-      reject_reports_hint: Ignore all reports coming from this domain. Irrelevant for suspensions
-      rejecting_media: rejecting media files
-      rejecting_reports: rejecting reports
-      severity:
-        silence: silenced
-        suspend: suspended
-      show:
-        affected_accounts:
-          one: One account in the database affected
-          other: "%{count} accounts in the database affected"
-        retroactive:
-          silence: Unsilence all existing accounts from this domain
-          suspend: Unsuspend all existing accounts from this domain
-        title: Undo domain block for %{domain}
-        undo: Undo
-      undo: Undo domain block
-    email_domain_blocks:
-      add_new: Add new
-      created_msg: Successfully added e-mail domain to blacklist
-      delete: Delete
-      destroyed_msg: Successfully deleted e-mail domain from blacklist
-      domain: Domain
-      new:
-        create: Add domain
-        title: New e-mail blacklist entry
-      title: E-mail blacklist
-    followers:
-      back_to_account: Back To Account
-      title: "%{acct}'s Followers"
-    instances:
-      by_domain: Domain
-      delivery_available: Delivery is available
-      known_accounts:
-        one: "%{count} known account"
-        other: "%{count} known accounts"
-      moderation:
-        all: All
-        limited: Limited
-        title: Moderation
-      title: Federation
-      total_blocked_by_us: Blocked by us
-      total_followed_by_them: Followed by them
-      total_followed_by_us: Followed by us
-      total_reported: Reports about them
-      total_storage: Media attachments
-    invites:
-      deactivate_all: Deactivate all
-      filter:
-        all: All
-        available: Available
-        expired: Expired
-        title: Filter
-      title: Invites
-    relays:
-      add_new: Add new relay
-      delete: Delete
-      description_html: A <strong>federation relay</strong> is an intermediary server that exchanges large volumes of public toots between servers that subscribe and publish to it. <strong>It can help small and medium servers discover content from the fediverse</strong>, which would otherwise require local users manually following other people on remote servers.
-      disable: Disable
-      disabled: Disabled
-      enable: Enable
-      enable_hint: Once enabled, your server will subscribe to all public toots from this relay, and will begin sending this server's public toots to it.
-      enabled: Enabled
-      inbox_url: Relay URL
-      pending: Waiting for relay's approval
-      save_and_enable: Save and enable
-      setup: Setup a relay connection
-      status: Status
-      title: Relays
-    report_notes:
-      created_msg: Report note successfully created!
-      destroyed_msg: Report note successfully deleted!
-    reports:
-      account:
-        note: note
-        report: report
-      action_taken_by: Action taken by
-      are_you_sure: Are you sure?
-      assign_to_self: Assign to me
-      assigned: Assigned moderator
-      comment:
-        none: None
-      created_at: Reported
-      mark_as_resolved: Mark as resolved
-      mark_as_unresolved: Mark as unresolved
-      notes:
-        create: Add note
-        create_and_resolve: Resolve with note
-        create_and_unresolve: Reopen with note
-        delete: Delete
-        placeholder: Describe what actions have been taken, or any other related updates...
-      reopen: Reopen report
-      report: 'Report #%{id}'
-      reported_account: Reported account
-      reported_by: Reported by
-      resolved: Resolved
-      resolved_msg: Report successfully resolved!
-      status: Status
-      title: Reports
-      unassign: Unassign
-      unresolved: Unresolved
-      updated_at: Updated
-    settings:
-      activity_api_enabled:
-        desc_html: Counts of locally posted statuses, active users, and new registrations in weekly buckets
-        title: Publish aggregate statistics about user activity
-      bootstrap_timeline_accounts:
-        desc_html: Separate multiple usernames by comma. Only local and unlocked accounts will work. Default when empty is all local admins.
-        title: Default follows for new users
-      contact_information:
-        email: Business e-mail
-        username: Contact username
-      custom_css:
-        desc_html: Modify the look with CSS loaded on every page
-        title: Custom CSS
-      hero:
-        desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to server thumbnail
-        title: Hero image
-      mascot:
-        desc_html: Displayed on multiple pages. At least 293×205px recommended. When not set, falls back to default mascot
-        title: Mascot image
-      peers_api_enabled:
-        desc_html: Domain names this server has encountered in the fediverse
-        title: Publish list of discovered servers
-      preview_sensitive_media:
-        desc_html: Link previews on other websites will display a thumbnail even if the media is marked as sensitive
-        title: Show sensitive media in OpenGraph previews
-      profile_directory:
-        desc_html: Allow users to be discoverable
-        title: Enable profile directory
-      registrations:
-        closed_message:
-          desc_html: Displayed on frontpage when registrations are closed. You can use HTML tags
-          title: Closed registration message
-        deletion:
-          desc_html: Allow anyone to delete their account
-          title: Open account deletion
-        min_invite_role:
-          disabled: No one
-          title: Allow invitations by
-      registrations_mode:
-        modes:
-          approved: Approval required for sign up
-          none: Nobody can sign up
-          open: Anyone can sign up
-        title: Registrations mode
-      show_known_fediverse_at_about_page:
-        desc_html: When toggled, it will show toots from all the known fediverse on preview. Otherwise it will only show local toots.
-        title: Show known fediverse on timeline preview
-      show_staff_badge:
-        desc_html: Show a staff badge on a user page
-        title: Show staff badge
-      site_description:
-        desc_html: Introductory paragraph on the frontpage. Describe what makes this Mastodon server special and anything else important. You can use HTML tags, in particular <code>&lt;a&gt;</code> and <code>&lt;em&gt;</code>.
-        title: Server description
-      site_description_extended:
-        desc_html: A good place for your code of conduct, rules, guidelines and other things that set your server apart. You can use HTML tags
-        title: Custom extended information
-      site_short_description:
-        desc_html: Displayed in sidebar and meta tags. Describe what Mastodon is and what makes this server special in a single paragraph. If empty, defaults to server description.
-        title: Short server description
-      site_terms:
-        desc_html: You can write your own privacy policy, terms of service or other legalese. You can use HTML tags
-        title: Custom terms of service
-      site_title: Server name
-      thumbnail:
-        desc_html: Used for previews via OpenGraph and API. 1200x630px recommended
-        title: Server thumbnail
-      timeline_preview:
-        desc_html: Display public timeline on landing page
-        title: Timeline preview
-      title: Site settings
-    statuses:
-      back_to_account: Back to account page
-      batch:
-        delete: Delete
-        nsfw_off: Mark as not sensitive
-        nsfw_on: Mark as sensitive
-      failed_to_execute: Failed to execute
-      media:
-        title: Media
-      no_media: No media
-      no_status_selected: No statuses were changed as none were selected
-      title: Account statuses
-      with_media: With media
-    subscriptions:
-      callback_url: Callback URL
-      confirmed: Confirmed
-      expires_in: Expires in
-      last_delivery: Last delivery
-      title: WebSub
-      topic: Topic
-    tags:
-      accounts: Accounts
-      hidden: Hidden
-      hide: Hide from directory
-      name: Hashtag
-      title: Hashtags
-      unhide: Show in directory
-      visible: Visible
-    title: Administration
-    warning_presets:
-      add_new: Add new
-      delete: Delete
-      edit_preset: Edit warning preset
-      title: Manage warning presets
-  admin_mailer:
-    new_pending_account:
-      body: The details of the new account are below. You can approve or reject this application.
-      subject: New account up for review on %{instance} (%{username})
-    new_report:
-      body: "%{reporter} has reported %{target}"
-      body_remote: Someone from %{domain} has reported %{target}
-      subject: New report for %{instance} (#%{id})
-  application_mailer:
-    notification_preferences: Change e-mail preferences
-    salutation: "%{name},"
-    settings: 'Change e-mail preferences: %{link}'
-    view: 'View:'
-    view_profile: View Profile
-    view_status: View status
-  applications:
-    created: Application successfully created
-    destroyed: Application successfully deleted
-    invalid_url: The provided URL is invalid
-    regenerate_token: Regenerate access token
-    token_regenerated: Access token successfully regenerated
-    warning: Be very careful with this data. Never share it with anyone!
-    your_token: Your access token
-  auth:
-    apply_for_account: Request an invite
-    change_password: Password
-    checkbox_agreement_html: I agree to the <a href="%{rules_path}" target="_blank">server rules</a> and <a href="%{terms_path}" target="_blank">terms of service</a>
-    confirm_email: Confirm email
-    delete_account: Delete account
-    delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation.
-    didnt_get_confirmation: Didn't receive confirmation instructions?
-    forgot_password: Forgot your password?
-    invalid_reset_password_token: Password reset token is invalid or expired. Please request a new one.
-    login: Log in
-    logout: Logout
-    migrate_account: Move to a different account
-    migrate_account_html: If you wish to redirect this account to a different one, you can <a href="%{path}">configure it here</a>.
-    or_log_in_with: Or log in with
-    providers:
-      cas: CAS
-      saml: SAML
-    register: Sign up
-    registration_closed: "%{instance} is not accepting new members"
-    resend_confirmation: Resend confirmation instructions
-    reset_password: Reset password
-    security: Security
-    set_new_password: Set new password
-    trouble_logging_in: Trouble logging in?
-  authorize_follow:
-    already_following: You are already following this account
-    error: Unfortunately, there was an error looking up the remote account
-    follow: Follow
-    follow_request: 'You have sent a follow request to:'
-    following: 'Success! You are now following:'
-    post_follow:
-      close: Or, you can just close this window.
-      return: Show the user's profile
-      web: Go to web
-    title: Follow %{acct}
-  datetime:
-    distance_in_words:
-      about_x_hours: "%{count}h"
-      about_x_months: "%{count}mo"
-      about_x_years: "%{count}y"
-      almost_x_years: "%{count}y"
-      half_a_minute: Just now
-      less_than_x_minutes: "%{count}m"
-      less_than_x_seconds: Just now
-      over_x_years: "%{count}y"
-      x_days: "%{count}d"
-      x_minutes: "%{count}m"
-      x_months: "%{count}mo"
-      x_seconds: "%{count}s"
-  deletes:
-    bad_password_msg: Nice try, hackers! Incorrect password
-    confirm_password: Enter your current password to verify your identity
-    description_html: This will <strong>permanently, irreversibly</strong> remove content from your account and deactivate it. Your username will remain reserved to prevent future impersonations.
-    proceed: Delete account
-    success_msg: Your account was successfully deleted
-    warning_html: Only deletion of content from this particular server is guaranteed. Content that has been widely shared is likely to leave traces. Offline servers and servers that have unsubscribed from your updates will not update their databases.
-    warning_title: Disseminated content availability
-  directories:
-    directory: Profile directory
-    enabled: You are currently listed in the directory.
-    enabled_but_waiting: You have opted-in to be listed in the directory, but you do not have the minimum number of followers (%{min_followers}) to be listed yet.
-    explanation: Discover users based on their interests
-    explore_mastodon: Explore %{title}
-    how_to_enable: You are not currently opted-in to the directory. You can opt-in below. Use hashtags in your bio text to be listed under specific hashtags!
-    people:
-      one: "%{count} person"
-      other: "%{count} people"
-  errors:
-    '403': You don't have permission to view this page.
-    '404': The page you are looking for isn't here.
-    '410': The page you were looking for doesn't exist here anymore.
-    '422':
-      content: Security verification failed. Are you blocking cookies?
-      title: Security verification failed
-    '429': Throttled
-    '500':
-      content: We're sorry, but something went wrong on our end.
-      title: This page is not correct
-    noscript_html: To use the Mastodon web application, please enable JavaScript. Alternatively, try one of the <a href="%{apps_path}">native apps</a> for Mastodon for your platform.
-  exports:
-    archive_takeout:
-      date: Date
-      download: Download your archive
-      hint_html: You can request an archive of your <strong>toots and uploaded media</strong>. The exported data will be in the ActivityPub format, readable by any compliant software. You can request an archive every 7 days.
-      in_progress: Compiling your archive...
-      request: Request your archive
-      size: Size
-    blocks: You block
-    csv: CSV
-    domain_blocks: Domain blocks
-    follows: You follow
-    lists: Lists
-    mutes: You mute
-    storage: Media storage
-  featured_tags:
-    add_new: Add new
-    errors:
-      limit: You have already featured the maximum amount of hashtags
-  filters:
-    contexts:
-      home: Home and lists
-      notifications: Notifications
-      public: Public timelines
-      thread: Conversations
-    edit:
-      title: Edit filter
-    errors:
-      invalid_context: None or invalid context supplied
-      invalid_irreversible: Irreversible filtering only works with home or notifications context
-    index:
-      delete: Delete
-      title: Filters
-    new:
-      title: Add new filter
-  footer:
-    developers: Developers
-    more: More…
-    resources: Resources
-  generic:
-    all: All
-    changes_saved_msg: Changes successfully saved!
-    copy: Copy
-    save_changes: Save changes
-    validation_errors:
-      one: Something isn't quite right yet! Please review the error below
-      other: Something isn't quite right yet! Please review %{count} errors below
-  identity_proofs:
-    active: Active
-    authorize: Yes, authorize
-    authorize_connection_prompt: Authorize this cryptographic connection?
-    errors:
-      failed: The cryptographic connection failed. Please try again from %{provider}.
-      keybase:
-        invalid_token: Keybase tokens are hashes of signatures and must be 66 hex characters
-        verification_failed: Keybase does not recognize this token as a signature of Keybase user %{kb_username}. Please retry from Keybase.
-    explanation_html: Here you can cryptographically connect your other identities, such as a Keybase profile. This lets other people send you encrypted messages and trust content you send them.
-    i_am_html: I am %{username} on %{service}.
-    identity: Identity
-    inactive: Inactive
-    status: Verification status
-    view_proof: View proof
-  imports:
-    modes:
-      merge: Merge
-      merge_long: Keep existing records and add new ones
-      overwrite: Overwrite
-      overwrite_long: Replace current records with the new ones
-    preface: You can import data that you have exported from another server, such as a list of the people you are following or blocking.
-    success: Your data was successfully uploaded and will now be processed in due time
-    types:
-      blocking: Blocking list
-      domain_blocking: Domain blocking list
-      following: Following list
-      muting: Muting list
-    upload: Upload
-  in_memoriam_html: In Memoriam.
-  invites:
-    delete: Deactivate
-    expired: Expired
-    expires_in:
-      '1800': 30 minutes
-      '21600': 6 hours
-      '3600': 1 hour
-      '43200': 12 hours
-      '604800': 1 week
-      '86400': 1 day
-    expires_in_prompt: Never
-    generate: Generate
-    invited_by: 'You were invited by:'
-    max_uses:
-      one: 1 use
-      other: "%{count} uses"
-    max_uses_prompt: No limit
-    prompt: Generate and share links with others to grant access to this server
-    table:
-      expires_at: Expires
-      uses: Uses
-    title: Invite people
-  lists:
-    errors:
-      limit: You have reached the maximum amount of lists
-  media_attachments:
-    validations:
-      images_and_video: Cannot attach a video to a status that already contains images
-      too_many: Cannot attach more than 4 files
-  migrations:
-    acct: username@domain of the new account
-    currently_redirecting: 'Your profile is set to redirect to:'
-    proceed: Save
-    updated_msg: Your account migration setting successfully updated!
-  moderation:
-    title: Moderation
-  notification_mailer:
-    digest:
-      action: View all notifications
-      body: Here is a brief summary of the messages you missed since your last visit on %{since}
-      mention: "%{name} mentioned you in:"
-      new_followers_summary:
-        one: Also, you have acquired one new follower while being away! Yay!
-        other: Also, you have acquired %{count} new followers while being away! Amazing!
-      subject:
-        one: "1 new notification since your last visit 🐘"
-        other: "%{count} new notifications since your last visit 🐘"
-      title: In your absence...
-    favourite:
-      body: 'Your status was favourited by %{name}:'
-      subject: "%{name} favourited your status"
-      title: New favourite
-    follow:
-      body: "%{name} is now following you!"
-      subject: "%{name} is now following you"
-      title: New follower
-    follow_request:
-      action: Manage follow requests
-      body: "%{name} has requested to follow you"
-      subject: 'Pending follower: %{name}'
-      title: New follow request
-    mention:
-      action: Reply
-      body: 'You were mentioned by %{name} in:'
-      subject: You were mentioned by %{name}
-      title: New mention
-    reblog:
-      body: 'Your status was boosted by %{name}:'
-      subject: "%{name} boosted your status"
-      title: New boost
-  number:
-    human:
-      decimal_units:
-        format: "%n%u"
-        units:
-          billion: B
-          million: M
-          quadrillion: Q
-          thousand: K
-          trillion: T
-  pagination:
-    newer: Newer
-    next: Next
-    older: Older
-    prev: Prev
-    truncate: "&hellip;"
-  polls:
-    errors:
-      already_voted: You have already voted on this poll
-      duplicate_options: contain duplicate items
-      duration_too_long: is too far into the future
-      duration_too_short: is too soon
-      expired: The poll has already ended
-      over_character_limit: cannot be longer than %{max} characters each
-      too_few_options: must have more than one item
-      too_many_options: can't contain more than %{max} items
-  preferences:
-    other: Other
-  relationships:
-    activity: Account activity
-    dormant: Dormant
-    moved: Moved
-    mutual: Mutual
-    primary: Primary
-    relationship: Relationship
-    remove_selected_domains: Remove all followers from the selected domains
-    remove_selected_followers: Remove selected followers
-    remove_selected_follows: Unfollow selected users
-    status: Account status
-  remote_follow:
-    acct: Enter your username@domain you want to act from
-    missing_resource: Could not find the required redirect URL for your account
-    no_account_html: Don't have an account? You can <a href='%{sign_up_path}' target='_blank'>sign up here</a>
-    proceed: Proceed to follow
-    prompt: 'You are going to follow:'
-    reason_html: "<strong>Why is this step necessary?</strong> <code>%{instance}</code> might not be the server where you are registered, so we need to redirect you to your home server first."
-  remote_interaction:
-    favourite:
-      proceed: Proceed to favourite
-      prompt: 'You want to favourite this toot:'
-    reblog:
-      proceed: Proceed to boost
-      prompt: 'You want to boost this toot:'
-    reply:
-      proceed: Proceed to reply
-      prompt: 'You want to reply to this toot:'
-  remote_unfollow:
-    error: Error
-    title: Title
-    unfollowed: Unfollowed
-  scheduled_statuses:
-    over_daily_limit: You have exceeded the limit of %{limit} scheduled toots for that day
-    over_total_limit: You have exceeded the limit of %{limit} scheduled toots
-    too_soon: The scheduled date must be in the future
-  sessions:
-    activity: Last activity
-    browser: Browser
-    browsers:
-      alipay: Alipay
-      blackberry: Blackberry
-      chrome: Chrome
-      edge: Microsoft Edge
-      electron: Electron
-      firefox: Firefox
-      generic: Unknown browser
-      ie: Internet Explorer
-      micro_messenger: MicroMessenger
-      nokia: Nokia S40 Ovi Browser
-      opera: Opera
-      otter: Otter
-      phantom_js: PhantomJS
-      qq: QQ Browser
-      safari: Safari
-      uc_browser: UCBrowser
-      weibo: Weibo
-    current_session: Current session
-    description: "%{browser} on %{platform}"
-    explanation: These are the web browsers currently logged in to your Mastodon account.
-    ip: IP
-    platforms:
-      adobe_air: Adobe Air
-      android: Android
-      blackberry: Blackberry
-      chrome_os: ChromeOS
-      firefox_os: Firefox OS
-      ios: iOS
-      linux: Linux
-      mac: Mac
-      other: unknown platform
-      windows: Windows
-      windows_mobile: Windows Mobile
-      windows_phone: Windows Phone
-    revoke: Revoke
-    revoke_success: Session successfully revoked
-    title: Sessions
-  settings:
-    authorized_apps: Authorized apps
-    back: Back to Mastodon
-    delete: Account deletion
-    development: Development
-    edit_profile: Edit profile
-    export: Data export
-    featured_tags: Featured hashtags
-    identity_proofs: Identity proofs
-    import: Import
-    migrate: Account migration
-    notifications: Notifications
-    preferences: Preferences
-    relationships: Follows and followers
-    two_factor_authentication: Two-factor Auth
-  statuses:
-    attached:
-      description: 'Attached: %{attached}'
-      image:
-        one: "%{count} image"
-        other: "%{count} images"
-      video:
-        one: "%{count} video"
-        other: "%{count} videos"
-    boosted_from_html: Boosted from %{acct_link}
-    content_warning: 'Content warning: %{warning}'
-    disallowed_hashtags:
-      one: 'contained a disallowed hashtag: %{tags}'
-      other: 'contained the disallowed hashtags: %{tags}'
-    language_detection: Automatically detect language
-    open_in_web: Open in web
-    over_character_limit: character limit of %{max} exceeded
-    pin_errors:
-      limit: You have already pinned the maximum number of toots
-      ownership: Someone else's toot cannot be pinned
-      private: Non-public toot cannot be pinned
-      reblog: A boost cannot be pinned
-    poll:
-      total_votes:
-        one: "%{count} vote"
-        other: "%{count} votes"
-      vote: Vote
-    show_more: Show more
-    sign_in_to_participate: Sign in to participate in the conversation
-    title: '%{name}: "%{quote}"'
-    visibilities:
-      private: Followers-only
-      private_long: Only show to followers
-      public: Public
-      public_long: Everyone can see
-      unlisted: Unlisted
-      unlisted_long: Everyone can see, but not listed on public timelines
-  stream_entries:
-    pinned: Pinned toot
-    reblogged: boosted
-    sensitive_content: Sensitive content
-  terms:
-    body_html: |
-      <h2>Privacy Policy</h2>
-      <h3 id="collect">What information do we collect?</h3>
-
-      <ul>
-        <li><em>Basic account information</em>: If you register on this server, you may be asked to enter a username, an e-mail address and a password. You may also enter additional profile information such as a display name and biography, and upload a profile picture and header image. The username, display name, biography, profile picture and header image are always listed publicly.</li>
-        <li><em>Posts, following and other public information</em>: The list of people you follow is listed publicly, the same is true for your followers. When you submit a message, the date and time is stored as well as the application you submitted the message from. Messages may contain media attachments, such as pictures and videos. Public and unlisted posts are available publicly. When you feature a post on your profile, that is also publicly available information. Your posts are delivered to your followers, in some cases it means they are delivered to different servers and copies are stored there. When you delete posts, this is likewise delivered to your followers. The action of reblogging or favouriting another post is always public.</li>
-        <li><em>Direct and followers-only posts</em>: All posts are stored and processed on the server. Followers-only posts are delivered to your followers and users who are mentioned in them, and direct posts are delivered only to users mentioned in them. In some cases it means they are delivered to different servers and copies are stored there. We make a good faith effort to limit the access to those posts only to authorized persons, but other servers may fail to do so. Therefore it's important to review servers your followers belong to. You may toggle an option to approve and reject new followers manually in the settings. <em>Please keep in mind that the operators of the server and any receiving server may view such messages</em>, and that recipients may screenshot, copy or otherwise re-share them. <em>Do not share any dangerous information over Mastodon.</em></li>
-        <li><em>IPs and other metadata</em>: When you log in, we record the IP address you log in from, as well as the name of your browser application. All the logged in sessions are available for your review and revocation in the settings. The latest IP address used is stored for up to 12 months. We also may retain server logs which include the IP address of every request to our server.</li>
-      </ul>
-
-      <hr class="spacer" />
-
-      <h3 id="use">What do we use your information for?</h3>
-
-      <p>Any of the information we collect from you may be used in the following ways:</p>
-
-      <ul>
-        <li>To provide the core functionality of Mastodon. You can only interact with other people's content and post your own content when you are logged in. For example, you may follow other people to view their combined posts in your own personalized home timeline.</li>
-        <li>To aid moderation of the community, for example comparing your IP address with other known ones to determine ban evasion or other violations.</li>
-        <li>The email address you provide may be used to send you information, notifications about other people interacting with your content or sending you messages, and to respond to inquiries, and/or other requests or questions.</li>
-      </ul>
-
-      <hr class="spacer" />
-
-      <h3 id="protect">How do we protect your information?</h3>
-
-      <p>We implement a variety of security measures to maintain the safety of your personal information when you enter, submit, or access your personal information. Among other things, your browser session, as well as the traffic between your applications and the API, are secured with SSL, and your password is hashed using a strong one-way algorithm. You may enable two-factor authentication to further secure access to your account.</p>
-
-      <hr class="spacer" />
-
-      <h3 id="data-retention">What is our data retention policy?</h3>
-
-      <p>We will make a good faith effort to:</p>
-
-      <ul>
-        <li>Retain server logs containing the IP address of all requests to this server, in so far as such logs are kept, no more than 90 days.</li>
-        <li>Retain the IP addresses associated with registered users no more than 12 months.</li>
-      </ul>
-
-      <p>You can request and download an archive of your content, including your posts, media attachments, profile picture, and header image.</p>
-
-      <p>You may irreversibly delete your account at any time.</p>
-
-      <hr class="spacer"/>
-
-      <h3 id="cookies">Do we use cookies?</h3>
-
-      <p>Yes. Cookies are small files that a site or its service provider transfers to your computer's hard drive through your Web browser (if you allow). These cookies enable the site to recognize your browser and, if you have a registered account, associate it with your registered account.</p>
-
-      <p>We use cookies to understand and save your preferences for future visits.</p>
-
-      <hr class="spacer" />
-
-      <h3 id="disclose">Do we disclose any information to outside parties?</h3>
-
-      <p>We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our site, conducting our business, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety.</p>
-
-      <p>Your public content may be downloaded by other servers in the network. Your public and followers-only posts are delivered to the servers where your followers reside, and direct messages are delivered to the servers of the recipients, in so far as those followers or recipients reside on a different server than this.</p>
-
-      <p>When you authorize an application to use your account, depending on the scope of permissions you approve, it may access your public profile information, your following list, your followers, your lists, all your posts, and your favourites. Applications can never access your e-mail address or password.</p>
-
-      <hr class="spacer" />
-
-      <h3 id="children">Site usage by children</h3>
-
-      <p>If this server is in the EU or the EEA: Our site, products and services are all directed to people who are at least 16 years old. If you are under the age of 16, per the requirements of the GDPR (<a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">General Data Protection Regulation</a>) do not use this site.</p>
-
-      <p>If this server is in the USA: Our site, products and services are all directed to people who are at least 13 years old. If you are under the age of 13, per the requirements of COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) do not use this site.</p>
-
-      <p>Law requirements can be different if this server is in another jurisdiction.</p>
-
-      <hr class="spacer" />
-
-      <h3 id="changes">Changes to our Privacy Policy</h3>
-
-      <p>If we decide to change our privacy policy, we will post those changes on this page.</p>
-
-      <p>This document is CC-BY-SA. It was last updated March 7, 2018.</p>
-
-      <p>Originally adapted from the <a href="https://github.com/discourse/discourse">Discourse privacy policy</a>.</p>
-    title: "%{instance} Terms of Service and Privacy Policy"
-  themes:
-    contrast: Mastodon (High contrast)
-    default: Mastodon (Dark)
-    mastodon-light: Mastodon (Light)
-  time:
-    formats:
-      default: "%b %d, %Y, %H:%M"
-      month: "%b %Y"
-  two_factor_authentication:
-    code_hint: Enter the code generated by your authenticator app to confirm
-    description_html: If you enable <strong>two-factor authentication</strong>, logging in will require you to be in possession of your phone, which will generate tokens for you to enter.
-    disable: Disable
-    enable: Enable
-    enabled: Two-factor authentication is enabled
-    enabled_success: Two-factor authentication successfully enabled
-    generate_recovery_codes: Generate recovery codes
-    instructions_html: "<strong>Scan this QR code into Google Authenticator or a similar TOTP app on your phone</strong>. From now on, that app will generate tokens that you will have to enter when logging in."
-    lost_recovery_codes: Recovery codes allow you to regain access to your account if you lose your phone. If you've lost your recovery codes, you can regenerate them here. Your old recovery codes will be invalidated.
-    manual_instructions: 'If you can''t scan the QR code and need to enter it manually, here is the plain-text secret:'
-    recovery_codes: Backup recovery codes
-    recovery_codes_regenerated: Recovery codes successfully regenerated
-    recovery_instructions_html: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. <strong>Keep the recovery codes safe</strong>. For example, you may print them and store them with other important documents.
-    setup: Set up
-    wrong_code: The entered code was invalid! Are server time and device time correct?
-  user_mailer:
-    backup_ready:
-      explanation: You requested a full backup of your Mastodon account. It's now ready for download!
-      subject: Your archive is ready for download
-      title: Archive takeout
-    warning:
-      explanation:
-        disable: While your account is frozen, your account data remains intact, but you cannot perform any actions until it is unlocked.
-        silence: While your account is limited, only people who are already following you will see your toots on this server, and you may be excluded from various public listings. However, others may still manually follow you.
-        suspend: Your account has been suspended, and all of your toots and your uploaded media files have been irreversibly removed from this server, and servers where you had followers.
-      review_server_policies: Review server policies
-      subject:
-        disable: Your account %{acct} has been frozen
-        none: Warning for %{acct}
-        silence: Your account %{acct} has been limited
-        suspend: Your account %{acct} has been suspended
-      title:
-        disable: Account frozen
-        none: Warning
-        silence: Account limited
-        suspend: Account suspended
-    welcome:
-      edit_profile_action: Setup profile
-      edit_profile_step: You can customize your profile by uploading an avatar, header, changing your display name and more. If you’d like to review new followers before they’re allowed to follow you, you can lock your account.
-      explanation: Here are some tips to get you started
-      final_action: Start posting
-      final_step: 'Start posting! Even without followers your public messages may be seen by others, for example on the local timeline and in hashtags. You may want to introduce yourself on the #introductions hashtag.'
-      full_handle: Your full handle
-      full_handle_hint: This is what you would tell your friends so they can message or follow you from another server.
-      review_preferences_action: Change preferences
-      review_preferences_step: Make sure to set your preferences, such as which emails you'd like to receive, or what privacy level you’d like your posts to default to. If you don’t have motion sickness, you could choose to enable GIF autoplay.
-      subject: Welcome to Mastodon
-      tip_federated_timeline: The federated timeline is a firehose view of the Mastodon network. But it only includes people your neighbours are subscribed to, so it's not complete.
-      tip_following: You follow your server's admin(s) by default. To find more interesting people, check the local and federated timelines.
-      tip_local_timeline: The local timeline is a firehose view of people on %{instance}. These are your immediate neighbours!
-      tip_mobile_webapp: If your mobile browser offers you to add Mastodon to your homescreen, you can receive push notifications. It acts like a native app in many ways!
-      tips: Tips
-      title: Welcome aboard, %{name}!
-  users:
-    follow_limit_reached: You cannot follow more than %{limit} people
-    invalid_email: The e-mail address is invalid
-    invalid_otp_token: Invalid two-factor code
-    otp_lost_help_html: If you lost access to both, you may get in touch with %{email}
-    seamless_external_login: You are logged in via an external service, so password and e-mail settings are not available.
-    signed_in_as: 'Signed in as:'
-  verification:
-    explanation_html: 'You can <strong>verify yourself as the owner of the links in your profile metadata</strong>. For that, the linked website must contain a link back to your Mastodon profile. The link back <strong>must</strong> have a <code>rel="me"</code> attribute. The text content of the link does not matter. Here is an example:'
-    verification: Verification
diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml
index aef909149..9b0ab12cc 100644
--- a/config/locales/es-AR.yml
+++ b/config/locales/es-AR.yml
@@ -441,6 +441,7 @@ es-AR:
         private_comment_description_html: 'Para ayudarte a rastrear de dónde vienen los bloques importados, se crearán los mismos con el siguiente comentario privado: <q>%{comment}</q>'
         private_comment_template: Importado desde %{source} el %{date}
         title: Importar bloques de dominio
+      invalid_domain_block: 'Uno o más bloqueos de dominio fueron omitidos debido al/los siguiente/s error/es: %{error}'
       new:
         title: Importar bloques de dominio
       no_file: No hay ningún archivo seleccionado
@@ -589,6 +590,7 @@ es-AR:
       comment:
         none: Ninguno
       comment_description_html: 'Para proporcionar más información, %{name} escribió:'
+      confirm_action: Confirmar acción de moderación contra @%{acct}
       created_at: Denunciado
       delete_and_resolve: Eliminar mensajes
       forwarded: Reenviado
@@ -605,6 +607,7 @@ es-AR:
         placeholder: Describí qué acciones se tomaron, o cualquier otra actualización relacionada...
         title: Notas
       notes_description_html: Ver y dejar notas para otros moderadores y como referencia futura
+      processed_msg: 'Denuncia #%{id}, procesada exitosamente'
       quick_actions_description_html: 'Tomá una acción rápida o desplazate hacia abajo para ver el contenido denunciado:'
       remote_user_placeholder: el usuario remoto de %{instance}
       reopen: Reabrir denuncia
@@ -617,9 +620,28 @@ es-AR:
       status: Estado
       statuses: Contenido denunciado
       statuses_description_html: El contenido ofensivo se citará en la comunicación con la cuenta denunciada
+      summary:
+        action_preambles:
+          delete_html: 'Estás a punto de <strong>eliminar</strong> algunos de los mensajes de <strong>@%{acct}</strong>. Esto hará lo siguiente:'
+          mark_as_sensitive_html: 'Estás a punto de <strong>marcar</strong> algunos de los mensajes de <strong>@%{acct}</strong>como <strong>sensibles</strong>. Esto hará lo siguiente:'
+          silence_html: 'Estás a punto de <strong>limitar</strong> la cuenta de <strong>@%{acct}</strong>. Esto hará lo siguiente:'
+          suspend_html: 'Estás a punto de <strong>suspender</strong> la cuenta de <strong>@%{acct}</strong>. Esto hará lo siguiente:'
+        actions:
+          delete_html: Eliminar los mensajes ofensivos
+          mark_as_sensitive_html: Marcar los mensajes ofensivos como sensibles
+          silence_html: Limitar severamente el alcance de <strong>@%{acct}</strong> haciendo que su perfil y contenido sólo sean visibles para las personas que ya lo siguen o que busquen manualmente su perfil
+          suspend_html: Suspender <strong>@%{acct}</strong>, haciendo su perfil y contenido inaccesibles, e imposibilitando la interacción con la cuenta
+        close_report: 'Marcar denuncia #%{id} como resuelta'
+        close_reports_html: Marcar <strong>todas</strong> las denuncias contra <strong>@%{acct}</strong> como resueltas
+        delete_data_html: Eliminar el perfil y contenido de <strong>@%{acct}</strong> en 30 días a partir de ahora, a menos que se revierta la suspensión en ese tiempo
+        preview_preamble_html: "<strong>@%{acct}</strong> recibirá una advertencia con el siguiente contenido:"
+        record_strike_html: Registrá una amonestación contra <strong>@%{acct}</strong> para ayudarte a escalar futuras violaciones de esta cuenta
+        send_email_html: Enviar a <strong>@%{acct}</strong> un correo electrónico de advertencia
+        warning_placeholder: Razones adicionales opcionales para la acción de moderación.
       target_origin: Origen de la cuenta denunciada
       title: Denuncias
       unassign: Desasignar
+      unknown_action_msg: 'Acción desconocida: %{action}'
       unresolved: No resueltas
       updated_at: Actualizadas
       view_profile: Ver perfil
@@ -943,6 +965,8 @@ es-AR:
   auth:
     apply_for_account: Solicitar una cuenta
     change_password: Contraseña
+    confirmations:
+      wrong_email_hint: Si esa dirección de correo electrónico no es correcta, podés cambiarla en la configuración de la cuenta.
     delete_account: Eliminar cuenta
     delete_account_html: Si querés eliminar tu cuenta, podés <a href="%{path}">seguir por acá</a>. Se te va a pedir una confirmación.
     description:
diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml
index c2d0ce216..486a122b1 100644
--- a/config/locales/es-MX.yml
+++ b/config/locales/es-MX.yml
@@ -441,6 +441,7 @@ es-MX:
         private_comment_description_html: 'Para ayudarle a rastrear de dónde proceden los bloqueos importados, los bloqueos importados se crearán con el siguiente comentario privado: <q>%{comment}</q>'
         private_comment_template: Importado desde %{source} el %{date}
         title: Importar bloqueos de dominio
+      invalid_domain_block: 'Uno o más bloques de dominio fueron omitidos debido a el/los siguiente(s) error(es): %{error}'
       new:
         title: Importar bloqueos de dominio
       no_file: No hay archivo seleccionado
@@ -589,6 +590,7 @@ es-MX:
       comment:
         none: Ninguno
       comment_description_html: 'Para proporcionar más información, %{name} escribió:'
+      confirm_action: Confirmar acción de moderación contra @%{acct}
       created_at: Denunciado
       delete_and_resolve: Eliminar publicaciones
       forwarded: Reenviado
@@ -605,6 +607,7 @@ es-MX:
         placeholder: Especificar qué acciones se han tomado o cualquier otra novedad respecto a esta denuncia…
         title: Notas
       notes_description_html: Ver y dejar notas a otros moderadores y a tu yo futuro
+      processed_msg: 'La denuncia #%{id} ha sido procesada con éxito'
       quick_actions_description_html: 'Toma una acción rápida o desplázate hacia abajo para ver el contenido denunciado:'
       remote_user_placeholder: el usuario remoto de %{instance}
       reopen: Reabrir denuncia
@@ -617,9 +620,28 @@ es-MX:
       status: Estado
       statuses: Contenido reportado
       statuses_description_html: El contenido ofensivo se citará en comunicación con la cuenta reportada
+      summary:
+        action_preambles:
+          delete_html: 'Estás a punto de <strong>eliminar</strong> algunas de la publicaciones de <strong>@%{acct}</strong>. Esto hará:'
+          mark_as_sensitive_html: 'Estás a punto de <strong>marcar</strong> algunas de las publicaciones de <strong>@%{acct}</strong>como <strong>sensibles</strong>. Esto hará:'
+          silence_html: 'Estás a punto de <strong>limitar</strong> la cuenta de <strong>@%{acct}</strong>. Esto hará:'
+          suspend_html: 'Estás a punto de <strong>suspender</strong> la cuenta de <strong>@%{acct}</strong>. Esto hará:'
+        actions:
+          delete_html: Eliminar las publicaciones ofensivas
+          mark_as_sensitive_html: Marcar las publicaciones ofensivas como sensibles
+          silence_html: Limitar severamente el alcance de <strong>@%{acct}</strong> haciendo que su perfil y contenido sólo sean visibles para las personas que ya lo siguen o que consulten manualmente su perfil
+          suspend_html: Suspender a <strong>@%{acct}</strong>, haciendo su perfil y contenido inaccesibles e imposible la interacción
+        close_report: 'Marcar denuncia #%{id} como resuelta'
+        close_reports_html: Marcar <strong>todas</strong> las denuncias contra <strong>@%{acct}</strong> como resueltas
+        delete_data_html: Eliminar el perfil y contenido de <strong>@%{acct}</strong> en 30 días a partir de ahora a menos que se revierta la suspensión en ese tiempo
+        preview_preamble_html: "<strong>@%{acct}</strong> recibirá una advertencia con el siguiente contenido:"
+        record_strike_html: Registra una amonestación contra <strong>@%{acct}</strong> para ayudarte a escalar futuras violaciones de esta cuenta
+        send_email_html: Enviar a <strong>@%{acct}</strong> un correo electrónico de advertencia
+        warning_placeholder: Razones adicionales opcionales para la acción de moderación.
       target_origin: Origen de la cuenta reportada
       title: Reportes
       unassign: Desasignar
+      unknown_action_msg: 'Acción desconocida: %{action}'
       unresolved: No resuelto
       updated_at: Actualizado
       view_profile: Ver perfil
@@ -943,6 +965,8 @@ es-MX:
   auth:
     apply_for_account: Solicitar una cuenta
     change_password: Contraseña
+    confirmations:
+      wrong_email_hint: Si esa dirección de correo electrónico no es correcta, puedes cambiarla en la configuración de la cuenta.
     delete_account: Borrar cuenta
     delete_account_html: Si desea eliminar su cuenta, puede <a href="%{path}">proceder aquí</a>. Será pedido de una confirmación.
     description:
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 77ac452f3..38cee2adb 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -441,6 +441,7 @@ es:
         private_comment_description_html: 'Para ayudarle a rastrear de dónde proceden los bloqueos importados, los bloqueos importados se crearán con el siguiente comentario privado: <q>%{comment}</q>'
         private_comment_template: Importado desde %{source} el %{date}
         title: Importar bloqueos de dominio
+      invalid_domain_block: 'Uno o más bloqueos de dominio fueron omitidos debido a los siguientes errores: %{error}'
       new:
         title: Importar bloqueos de dominio
       no_file: Ningún archivo seleccionado
@@ -589,6 +590,7 @@ es:
       comment:
         none: Ninguno
       comment_description_html: 'Para proporcionar más información, %{name} escribió:'
+      confirm_action: Confirmar acción de moderación contra @%{acct}
       created_at: Denunciado
       delete_and_resolve: Eliminar publicaciones
       forwarded: Reenviado
@@ -605,6 +607,7 @@ es:
         placeholder: Especificar qué acciones se han tomado o cualquier otra novedad respecto a esta denuncia…
         title: Notas
       notes_description_html: Ver y dejar notas a otros moderadores y a tu yo futuro
+      processed_msg: 'El informe #%{id} ha sido procesado con éxito'
       quick_actions_description_html: 'Toma una acción rápida o desplázate hacia abajo para ver el contenido denunciado:'
       remote_user_placeholder: el usuario remoto de %{instance}
       reopen: Reabrir denuncia
@@ -617,9 +620,28 @@ es:
       status: Estado
       statuses: Contenido reportado
       statuses_description_html: El contenido ofensivo se citará en la comunicación con la cuenta reportada
+      summary:
+        action_preambles:
+          delete_html: 'Estás a punto de <strong>eliminar</strong> algunos de los mensajes de <strong>@%{acct}</strong>. Esto hará:'
+          mark_as_sensitive_html: 'Estás a punto de <strong>marcar</strong> algunas de las publicaciones de <strong>@%{acct}</strong>como <strong>sensibles</strong>. Esto hará:'
+          silence_html: 'Estás a punto de <strong>limitar</strong> la cuenta de <strong>@%{acct}</strong>. Esto hará:'
+          suspend_html: 'Estás a punto de <strong>suspender</strong> la cuenta de <strong>@%{acct}</strong>. Esto hará:'
+        actions:
+          delete_html: Eliminar los mensajes ofensivos
+          mark_as_sensitive_html: Marcar los mensajes ofensivos como sensibles
+          silence_html: Limitar severamente el alcance de <strong>@%{acct}</strong> haciendo que su perfil y contenido sólo sean visibles para las personas que ya lo siguen o que consulten manualmente su perfil
+          suspend_html: Suspender <strong>@%{acct}</strong>, haciendo su perfil y contenido inaccesibles y la interacción con la cuenta imposible
+        close_report: 'Marcar informe #%{id} como resuelto'
+        close_reports_html: Marcar <strong>todos los</strong> informes contra <strong>@%{acct}</strong> como resueltos
+        delete_data_html: Eliminar el perfil y contenido de <strong>@%{acct}</strong> en 30 días a partir de ahora a menos que se revierta la suspensión en ese tiempo
+        preview_preamble_html: "<strong>@%{acct}</strong> recibirá una advertencia con el siguiente contenido:"
+        record_strike_html: Registra una amonestación contra <strong>@%{acct}</strong> para ayudarte a escalar futuras violaciones de esta cuenta
+        send_email_html: Enviar a <strong>@%{acct}</strong> un correo electrónico de advertencia
+        warning_placeholder: Razones adicionales opcionales para la acción de moderación.
       target_origin: Origen de la cuenta reportada
       title: Reportes
       unassign: Desasignar
+      unknown_action_msg: 'Acción desconocida: %{action}'
       unresolved: No resuelto
       updated_at: Actualizado
       view_profile: Ver perfil
@@ -715,6 +737,7 @@ es:
         profile_directory: Directorio de perfiles
         public_timelines: Lineas de tiempo públicas
         publish_discovered_servers: Publicar servidores descubiertos
+        publish_statistics: Publicar estadísticas
         title: Descubrimiento
         trends: Tendencias
       domain_blocks:
@@ -942,6 +965,8 @@ es:
   auth:
     apply_for_account: Solicitar una cuenta
     change_password: Contraseña
+    confirmations:
+      wrong_email_hint: Si esa dirección de correo electrónico no es correcta, puedes cambiarla en la configuración de la cuenta.
     delete_account: Borrar cuenta
     delete_account_html: Si desea eliminar su cuenta, puede <a href="%{path}">proceder aquí</a>. Será pedido de una confirmación.
     description:
diff --git a/config/locales/et.yml b/config/locales/et.yml
index 713df46dc..12dabbbc2 100644
--- a/config/locales/et.yml
+++ b/config/locales/et.yml
@@ -441,6 +441,7 @@ et:
         private_comment_description_html: 'Pidamaks järge, kust imporditud keelud pärinevad, luuakse need järneva privaatse kommentaariga: <q>%{comment}</q>'
         private_comment_template: Imporditud allikast %{source} kuupäeval %{date}
         title: Domeenikeeldude import
+      invalid_domain_block: 'Üks või mitu domeeniblokeeringut jäeti vahele järgnevate vigade tõttu: %{error}'
       new:
         title: Domeenikeeldude import
       no_file: Faili pole valitud
@@ -589,6 +590,7 @@ et:
       comment:
         none: Pole
       comment_description_html: 'Täiendava infona kirjutas %{name}:'
+      confirm_action: Kinnita @%{acct} modereering
       created_at: Teavitatud
       delete_and_resolve: Kustuta postitused
       forwarded: Edastatud
@@ -605,6 +607,7 @@ et:
         placeholder: Kirjelda, mis on ette võetud või muid seotud uuendusi...
         title: Märkmed
       notes_description_html: Märkmete vaatamine ja jätmine teistele moderaatoritele ja tulevikuks endale
+      processed_msg: 'Raport #%{id} edukalt käsitletud'
       quick_actions_description_html: 'Võimalus teha kiire otsus või näha raporteeritud sisu allpool:'
       remote_user_placeholder: kaugkasutaja domeenist %{instance}
       reopen: Taasava teavitus
@@ -617,9 +620,28 @@ et:
       status: Olek
       statuses: Raporteeritud sisu
       statuses_description_html: Sobimatu sisu kaasatakse suhtlusse raporteeritud kontoga
+      summary:
+        action_preambles:
+          delete_html: 'Oled <strong>eemaldamas</strong> mõnesid <strong>@%{acct}</strong> postitusi. Selle tulemusena:'
+          mark_as_sensitive_html: 'Oled <strong>märkimas</strong> mõnesid <strong>@%{acct}</strong> postitusi <strong>tundlikuks</strong>. Selle tulemusena:'
+          silence_html: 'Oled <strong>määramas piirangut</strong> kontole <strong>@%{acct}</strong>. Selle tulemusena:'
+          suspend_html: 'Oled <strong>peatamas</strong> kontot <strong>@%{acct}</strong>. Selle tulemusena:'
+        actions:
+          delete_html: Solvava postituse eemaldamine
+          mark_as_sensitive_html: Solvava postituse meedia märkimine tundlikuks
+          silence_html: "<strong>@%{acct}</strong> ulatuse tugev piiramine muutes tema profiili ja sisu nähtavaks ainult neile, kes teda juba jälgivad, või neile, kes just tema profiili üles otsivad"
+          suspend_html: "<strong>@%{acct}</strong> peatamine, muutes tema profiili ja sisu mitteligipääsetavaks ning mitteinterakteerutavaks"
+        close_report: 'Raporti #%{id} lahendatuks märkimine'
+        close_reports_html: Märgi <strong>kõik</strong> raportid <strong>@%{acct}</strong> vastu lahendatuks
+        delete_data_html: Kustuta tänasest 30 päeva pärast kasutaja <strong>@%{acct}</strong> profiil ja sisu, kui vahepeal tema kontot ei taastata
+        preview_preamble_html: "<strong>@%{acct}</strong> saab järgmise sisuga hoiatuse:"
+        record_strike_html: Salvesta <strong>@%{acct}</strong> kohta juhtum, et aidata selle konto tulevaste rikkumiste puhul reageerida
+        send_email_html: Saada kasutajale <strong>@%{acct}</strong> hoiatus-e-kiri
+        warning_placeholder: Valikuline täiendav põhjendus modereerimisele.
       target_origin: Raporteeritud konto päritolu
       title: Teavitused
       unassign: Eemalda määramine
+      unknown_action_msg: 'Tundmatu tegevus: %{action}'
       unresolved: Lahendamata
       updated_at: Uuendatud
       view_profile: Vaata profiili
@@ -943,6 +965,8 @@ et:
   auth:
     apply_for_account: Konto taotluse esitamine
     change_password: Salasõna
+    confirmations:
+      wrong_email_hint: Kui see e-postiaadress pole korrektne, saad seda kontosätetes muuta.
     delete_account: Konto kustutamine
     delete_account_html: Kui soovid oma konto kustutada, siis <a href="%{path}">jätka siit</a>. Pead kustutamise eraldi kinnitama.
     description:
diff --git a/config/locales/eu.yml b/config/locales/eu.yml
index c884e35aa..917a70e40 100644
--- a/config/locales/eu.yml
+++ b/config/locales/eu.yml
@@ -117,6 +117,7 @@ eu:
       reject: Ukatu
       rejected_msg: "%{username} erabiltzailearen izen emate eskaera behar bezala ukatu da"
       remote_suspension_irreversible: Kontu honetako datuak betiko ezabatuak izan dira.
+      remote_suspension_reversible_hint_html: Kontu hau kanporatua izan da bere zerbitzarian eta bere datuak %{date}(e)an behin betiko ezabatuko dira. Ordura arte urruneko zerbitzariak kontua kalterik gabe leheneratu dezake. Kontuaren datu guztiak oraintxe bertan ezabatu nahi badituzu, jarraian egin dezakezu.
       remove_avatar: Kendu abatarra
       remove_header: Kendu goiburua
       removed_avatar_msg: "%{username} erabiltzailearen avatarra behar bezala kendu da"
@@ -391,10 +392,15 @@ eu:
         create: Sortu blokeoa
         hint: Domeinuaren blokeoak ez du eragotziko kontuen sarrerak sortzea datu-basean, baina automatikoki ezarriko zaizkie moderazio metodo bereziak iraganeko mezuetan ere.
         severity:
+          desc_html: |-
+            <strong>Mugatu</strong> aukerak, domeinu hontako kontu guztien bidalketak ikusezinak bihurtuko dira jarraitzen ez dituztenentzat.
+            <strong>Egotzi</strong> aukerak, domeinu hontako kontu guztien, edukiak, media eta profilen datuak ezabatuak izango dira zure zerbitzaritik. Erabili <strong>Bat ere ez</strong> aukera soilik media-fitxategiak ukatu nahi badituzu.
           noop: Bat ere ez
           silence: Isilarazi
           suspend: Kanporatu
         title: Domeinuaren blokeo berria
+      no_domain_block_selected: Ez da domeinu berririk blokeatu, bat ere ez delako aukeratu
+      not_permitted: Ekintza hau egiteko baimenik ez duzu
       obfuscate: Lausotu domeinu-izena
       obfuscate_hint: Domeinuaren izena partzialki lausotu zerrendan, domeinuen zerrenda iragartzea mugatzea gaituta badago
       private_comment: Iruzkin pribatua
@@ -427,8 +433,19 @@ eu:
       resolved_through_html: "%{domain} domeinuaren bidez ebatzia"
       title: E-mail zerrenda beltza
     export_domain_allows:
+      new:
+        title: Baimendutako domeinuak inportatu
       no_file: Ez da fitxategirik hautatu
     export_domain_blocks:
+      import:
+        description_html: Blokeatutako domeinuen zerrenda bat inportatzera zoaz. Mesedez, berrikusi zerrenda kontu handiz, batez ere, zuk ez baduzu zerrenda hau egin.
+        existing_relationships_warning: Oraingo jarraipen-loturak
+        private_comment_description_html: 'Inportatutako blokeoak nondik datozen zuri jakiten laguntzeko, hauek hurrengo iruzkin pribatuarekin sortuko dira: <q>%{comment}</q>'
+        private_comment_template: "%{date} %{source}-(e)tik inportatua"
+        title: Domeinu-blokeoak inportatu
+      invalid_domain_block: 'Domeinu-blokeatze bat edo gehiago ekidin dira ondorengo errorearen edo erroreengatik: %{error}'
+      new:
+        title: Domeinu-blokeoak inportatu
       no_file: Ez da fitxategirik hautatu
     follow_recommendations:
       description_html: "<strong>Jarraitzeko gomendioek erabiltzaile berriei eduki interesgarria azkar aurkitzen laguntzen diete</strong>. Erabiltzaile batek jarraitzeko gomendio pertsonalizatuak jasotzeko adina interakzio izan ez duenean, kontu hauek gomendatzen zaizkio. Egunero birkalkulatzen dira hizkuntza bakoitzerako, azken aldian parte-hartze handiena izan duten eta jarraitzaile lokal gehien dituzten kontuak nahasiz."
@@ -561,7 +578,10 @@ eu:
         mark_as_sensitive_description_html: Salatutako bidalketetako multimedia edukia hunkigarri bezala eta neurria gordeko da, etorkizunean kontu honek arau-hausterik egiten badu kontuan izan dezazun.
         other_description_html: Ikusi kontuaren portaera kontrolatzeko eta salatutako kontuarekin komunikazioa pertsonalizatzeko aukera gehiago.
         resolve_description_html: Ez da ekintzarik hartuko salatutako kontuaren aurka, ez da neurria gordeko eta salaketa itxiko da.
+        silence_description_html: Kontua soilik honen jarraitzaile edo espresuki bilatzen dutenentzat izango da ikusgarri, kontuaren irisgarritasuna gogorki mugatzen delarik.
+        suspend_description_html: Kontua bera eta honen edukiak eskuraezinak izango dira, eta azkenean, ezabatuak. Kontu honekin erlazionatzea ezinezkoa izango da. Prozesua 30 egunez itzulgarria izango da. Kontu honen aurkako txosten guztiak baztertuko lirateke.
       actions_description_html: Erabaki txosten hau konpontzeko ze ekintza hartu. Salatutako kontuaren aurka zigor ekintza bat hartzen baduzu, eposta jakinarazpen bat bidaliko zaie, <strong>Spam</strong> kategoria hautatzean ezik.
+      actions_description_remote_html: Txosten honi konponbidea aurkitzeko zein ekintza egin hautatu. Hau soilik <strong>zure</strong> zerbitzaria kontu honekin nola komunikatu eta bere edukia nola maneiatzeko da.
       add_to_report: Gehitu gehiago txostenera
       are_you_sure: Ziur zaude?
       assign_to_self: Esleitu niri
@@ -572,6 +592,7 @@ eu:
       comment:
         none: Bat ere ez
       comment_description_html: 'Informazio gehiago emateko, %{name} idatzi:'
+      confirm_action: "@%{acct} kontuaren aurkako moderazio-ekintza baieztatu"
       created_at: Salatua
       delete_and_resolve: Ezabatu bidalketak
       forwarded: Birbidalia
@@ -588,6 +609,7 @@ eu:
         placeholder: Azaldu hartutako neurriak, edo erlazioa duten bestelako berriak...
         title: Oharrak
       notes_description_html: Ikusi eta idatzi oharrak beste moderatzaileentzat eta zuretzat etorkizunerako
+      processed_msg: "#%{id} txostena ongi prozesatu da"
       quick_actions_description_html: 'Hartu ekintza azkar bat edo korritu behera salatutako edukia ikusteko:'
       remote_user_placeholder: "%{instance} instantziako urruneko erabiltzailea"
       reopen: Berrireki salaketa
@@ -600,9 +622,28 @@ eu:
       status: Mezua
       statuses: Salatutako edukia
       statuses_description_html: Salatutako edukia salatutako kontuarekiko komunikazioan aipatuko da
+      summary:
+        action_preambles:
+          delete_html: "<strong>@%{acct}</strong> kontuaren bidalketa guztiak <strong>ezabatzera</strong> zoaz. Honek hau suposatuko du:"
+          mark_as_sensitive_html: "<strong>%{acct}</strong> kontuaren bidalketa guztiak <strong>hunkigarri</strong> gisa <strong>markatzera</strong> zoaz. Honek hau suposatuko du:"
+          silence_html: "<strong>@%{acct}</strong> kontua <strong>mugatzera</strong> zoaz. Honek hau suposatuko du:"
+          suspend_html: "<strong>@%{acct}</strong> kontua <strong>bertan behera uztera</strong> zoaz. Honek hau suposatuko du:"
+        actions:
+          delete_html: Bidalketa iraingarriak ezabatu
+          mark_as_sensitive_html: Bidalketa iraingarrien media hunkigarri gisa markatu
+          silence_html: "<strong>@%{acct}</strong> kontuaren irismena gogorki mugatu, beraren profila eta edukia soilik bere jarraitzaileentzat eta espresuki profila aurkitzen dutenentzat ikusgarri egiten direlarik"
+          suspend_html: "<strong>@%{acct}</strong> kontua bertan behera utzi, bere profila eta edukia iritsezin eta elkarreragin ezina izango direlarik"
+        close_report: "#%{id} txostena ebatzitako gisa markatu"
+        close_reports_html: "<strong>@%{acct}</strong> kontuaren txosten <strong>guztiak</strong> ebatzitako gisa markatu"
+        delete_data_html: "<strong>@%{acct}</strong> kontuaren profila eta edukia, gaurtik hasita, 30 egunez ezabatu, ez bada bitartean kontua berraktibatzen"
+        preview_preamble_html: "<strong>@%{acct}</strong> kontuak ondorengo edukia duen abisu bat jasoko du:"
+        record_strike_html: "<strong>@%{acct}</strong> kontuak eginiko eraso bat erregistratu, kontu honek etorkizunean egin ditzakeen erasoen aurrean erabakiak hartzen laguntzeko"
+        send_email_html: "<strong>@%{acct}</strong> kontuari abisu bat posta-elektronikoz bidali"
+        warning_placeholder: Moderazio-ekintzarako aukerazkoak diren arrazoiketa gehigarriak.
       target_origin: Salatutako kontuaren jatorria
       title: Salaketak
       unassign: Kendu esleipena
+      unknown_action_msg: 'Ekintza ezezaguna: %{action}'
       unresolved: Konpondu gabea
       updated_at: Eguneratua
       view_profile: Ikusi profila
@@ -689,11 +730,16 @@ eu:
       content_retention:
         preamble: Kontrolatu erabiltzaileek sortutako edukia nola biltegiratzen den Mastodonen.
         title: Edukia atxikitzea
+      default_noindex:
+        desc_html: Ezarpen hau aldatu ez duten erabiltzaile guztiei eragingo die
+        title: Erabiltzaileei bilaketa-motorraren indexaziotik at egoteko aukera ematen die lehenetsitako aukera modura
       discovery:
         follow_recommendations: Jarraitzeko gomendioak
         preamble: Eduki interesgarria aurkitzea garrantzitsua da Mastodoneko erabiltzaile berrientzat, behar bada inor ez dutelako ezagutuko. Kontrolatu zure zerbitzariko aurkikuntza-ezaugarriek nola funtzionatzen duten.
         profile_directory: Profil-direktorioa
         public_timelines: Denbora-lerro publikoak
+        publish_discovered_servers: Deskubritutako zerbitzariak argitaratu
+        publish_statistics: Estatistikak argitaratu
         title: Aurkitzea
         trends: Joerak
       domain_blocks:
@@ -919,7 +965,10 @@ eu:
     warning: Kontuz datu hauekin, ez partekatu inoiz inorekin!
     your_token: Zure sarbide token-a
   auth:
+    apply_for_account: Kontu bat eskatu
     change_password: Pasahitza
+    confirmations:
+      wrong_email_hint: Helbide-elektroniko hori zuzena ez bada, kontuaren ezarpenetan alda dezakezu.
     delete_account: Ezabatu kontua
     delete_account_html: Kontua ezabatu nahi baduzu, <a href="%{path}">jarraitu hemen</a>. Berrestea eskatuko zaizu.
     description:
@@ -955,6 +1004,9 @@ eu:
       email_below_hint_html: Beheko e-mail helbidea okerra bada, hemen aldatu dezakezu eta baieztapen e-mail berria jaso.
       email_settings_hint_html: Baieztamen e-maila %{email} helbidera bidali da. E-mail helbide hori zuzena ez bada, kontuaren ezarpenetan aldatu dezakezu.
       title: Ezarpena
+    sign_in:
+      preamble_html: Zure <strong>%{domain}-(e)ko</strong> egiaztagiriekin saioa hasi. Zure kontua beste zerbitzari batean badago, ezin izango duzu hemen saioa hasi.
+      title: "%{domain}-(e)an saioa hasi"
     sign_up:
       preamble: Mastodon zerbitzari honetako kontu batekin, aukera izango duzu sareko edozein pertsona jarraitzeko, ez dio axola kontua non ostatatua dagoen.
       title: "%{domain} zerbitzariko kontua prestatuko dizugu."
@@ -1344,6 +1396,9 @@ eu:
       unrecognized_emoji: ez da emoji ezaguna
   relationships:
     activity: Kontuaren aktibitatea
+    confirm_follow_selected_followers: Ziur al zaude hautatutako jarraitzaileak jarraitu nahi dituzula?
+    confirm_remove_selected_followers: Ziur al zaude hautatutako jarraitzaileak ezabatu nahi dituzula?
+    confirm_remove_selected_follows: Ziur al zaude hautatutako jarraipenak ezabatu nahi dituzula?
     dormant: Ez aktiboa
     follow_selected_followers: Jarraitu hautatutako jarraitzaileak
     followers: Jarraitzaileak
diff --git a/config/locales/fi.yml b/config/locales/fi.yml
index 9c1831d7f..16a1b6650 100644
--- a/config/locales/fi.yml
+++ b/config/locales/fi.yml
@@ -12,7 +12,7 @@ fi:
       one: Seuraaja
       other: Seuraajat
     following: Seuraaja
-    instance_actor_flash: Tämä on virtuaalitili, jota käytetään edustamaan itse palvelinta eikä yksittäistä käyttäjää. Sitä käytetään yhdistämistarkoituksiin, eikä sitä tule keskeyttää.
+    instance_actor_flash: Tämä on virtuaalitili, jota käytetään edustamaan itse palvelinta eikä yksittäistä käyttäjää. Sitä käytetään yhdistämistarkoituksiin, eikä sitä tule jäädyttää.
     last_active: viimeksi aktiivinen
     link_verified_on: Tämän linkin omistus on tarkastettu %{date}
     nothing_here: Täällä ei ole mitään!
@@ -20,8 +20,8 @@ fi:
       following: Sinun täytyy seurata henkilöä jota haluat tukea
     posts:
       one: Julkaisu
-      other: Julkaisut
-    posts_tab_heading: Julkaisut
+      other: Viestit
+    posts_tab_heading: Viestit
   admin:
     account_actions:
       action: Suorita toimenpide
@@ -121,30 +121,30 @@ fi:
       remove_avatar: Poista profiilikuva
       remove_header: Poista otsakekuva
       removed_avatar_msg: Käyttäjän %{username} avatar-kuva poistettu onnistuneesti
-      removed_header_msg: Käyttäjän %{username} otsikkokuva poistettiin onnistuneesti
+      removed_header_msg: Käyttäjän %{username} otsakekuva poistettiin onnistuneesti
       resend_confirmation:
         already_confirmed: Tämä käyttäjä on jo vahvistettu
-        send: Lähetä varmistusviesti uudelleen
+        send: Lähetä vahvistusviesti uudelleen
         success: Vahvistusviesti onnistuneesti lähetetty!
       reset: Palauta
       reset_password: Palauta salasana
       resubscribe: Tilaa uudelleen
       role: Rooli
       search: Hae
-      search_same_email_domain: Muut käyttäjät, joilla on sama sähköpostiverkkotunnus
-      search_same_ip: Muut käyttäjät samalla IP-osoitteella
+      search_same_email_domain: Muut käyttäjät, joilla on sama sähköpostin verkkotunnus
+      search_same_ip: Muut käyttäjät, joilla on sama IP-osoite
       security_measures:
         only_password: Vain salasana
         password_and_2fa: Salasana ja kaksivaiheinen tunnistautuminen
-      sensitive: Pakotus arkaluontoiseksi
-      sensitized: Merkitty arkaluontoiseksi
+      sensitive: Pakotus arkaluonteiseksi
+      sensitized: Merkitty arkaluonteiseksi
       shared_inbox_url: Jaetun saapuvan postilaatikon osoite
       show:
         created_reports: Tämän tilin luomat raportit
         targeted_reports: Tästä tilistä tehdyt raportit
       silence: Hiljennä
       silenced: Mykistetty
-      statuses: Tilat
+      statuses: Viestit
       strikes: Aiemmat varoitukset
       subscribe: Tilaa
       suspend: Jäädytä
@@ -155,17 +155,17 @@ fi:
       unblock_email: Poista sähköpostiosoitteen esto
       unblocked_email_msg: Käyttäjän %{username} sähköpostiosoitteen esto kumottiin
       unconfirmed_email: Sähköpostia ei vahvistettu
-      undo_sensitized: Kumoa pakotus arkaluontoiseksi tiliksi
+      undo_sensitized: Kumoa pakotus arkaluonteiseksi tiliksi
       undo_silenced: Peru hiljennys
       undo_suspension: Peru jäähy
-      unsilenced_msg: "%{username} -tilin rajoituksen kumoaminen onnistui"
+      unsilenced_msg: Tilin %{username} rajoituksen kumoaminen onnistui
       unsubscribe: Lopeta tilaus
-      unsuspended_msg: "%{username} -tilin keskeytyksen kumoaminen onnistui"
+      unsuspended_msg: Tilin %{username} jäädytyksen kumoaminen onnistui
       username: Käyttäjätunnus
       view_domain: Näytä verkkotunnuksen yhteenveto
       warn: Varoita
       web: Verkko
-      whitelisted: Sallittu liittämiselle
+      whitelisted: Sallittu federaatioon
     action_logs:
       action_types:
         approve_appeal: Hyväksy valitus
@@ -178,9 +178,9 @@ fi:
         create_announcement: Luo ilmoitus
         create_canonical_email_block: Luo sähköpostin esto
         create_custom_emoji: Luo mukautettu emoji
-        create_domain_allow: Salli palvelin
-        create_domain_block: Estä palvelin
-        create_email_domain_block: Estä sähköpostipalvelin
+        create_domain_allow: Luo verkkotunnuksen salliminen
+        create_domain_block: Luo verkkotunnuksen esto
+        create_email_domain_block: Luo sähköpostin verkkotunnuksen esto
         create_ip_block: Luo IP-sääntö
         create_unavailable_domain: Luo ei-saatavilla oleva verkkotunnus
         create_user_role: Luo rooli
@@ -188,21 +188,21 @@ fi:
         destroy_announcement: Poista ilmoitus
         destroy_canonical_email_block: Poista sähköpostin esto
         destroy_custom_emoji: Poista mukautettu emoji
-        destroy_domain_allow: Salli verkkotunnuksen poisto
+        destroy_domain_allow: Poista verkkotunnuksen salliminen
         destroy_domain_block: Poista verkkotunnuksen esto
-        destroy_email_domain_block: Poista sähköpostipalvelimen esto
+        destroy_email_domain_block: Poista sähköpostin verkkotunnuksen esto
         destroy_instance: Tyhjennä verkkotunnus
         destroy_ip_block: Poista IP-sääntö
-        destroy_status: Poista julkaisu
+        destroy_status: Poista viesti
         destroy_unavailable_domain: Poista ei-saatavilla oleva verkkotunnus
         destroy_user_role: Hävitä rooli
         disable_2fa_user: Poista kaksivaiheinen tunnistautuminen käytöstä
-        disable_custom_emoji: Estä mukautettu emoji
+        disable_custom_emoji: Poista mukautettu emoji käytöstä
         disable_sign_in_token_auth_user: Estä käyttäjältä sähköpostitunnuksen todennus
-        disable_user: Tili poistettu käytöstä
+        disable_user: Poista tili käytöstä
         enable_custom_emoji: Käytä mukautettuja emojeita
         enable_sign_in_token_auth_user: Salli käyttäjälle sähköpostitunnuksen todennus
-        enable_user: Tili otettu käyttöön
+        enable_user: Ota tili käyttöön
         memorialize_account: Muuta muistotiliksi
         promote_user: Käyttäjä ylennetty
         reject_appeal: Hylkää valitus
@@ -231,32 +231,32 @@ fi:
         approve_user_html: "%{name} hyväksyi käyttäjän rekisteröitymisen kohteesta %{target}"
         assigned_to_self_report_html: "%{name} otti raportin %{target} tehtäväkseen"
         change_email_user_html: "%{name} vaihtoi käyttäjän %{target} sähköpostiosoitteen"
-        change_role_user_html: "%{name} muutti roolia %{target}"
+        change_role_user_html: "%{name} muutti käyttäjän %{target} roolia"
         confirm_user_html: "%{name} vahvisti käyttäjän %{target} sähköpostiosoitteen"
-        create_account_warning_html: "%{name} lähetti varoituksen henkilölle %{target}"
+        create_account_warning_html: "%{name} lähetti varoituksen käyttäjälle %{target}"
         create_announcement_html: "%{name} loi uuden ilmoituksen %{target}"
         create_canonical_email_block_html: "%{name} esti sähköpostin hashilla %{target}"
         create_custom_emoji_html: "%{name} lähetti uuden emojin %{target}"
-        create_domain_allow_html: "%{name} salli yhdistäminen verkkotunnuksella %{target}"
+        create_domain_allow_html: "%{name} salli federaation verkkotunnuksella %{target}"
         create_domain_block_html: "%{name} esti verkkotunnuksen %{target}"
         create_email_domain_block_html: "%{name} esti sähköpostin %{target}"
-        create_ip_block_html: "%{name} luonut IP-säännön %{target}"
+        create_ip_block_html: "%{name} loi IP-säännön %{target}"
         create_unavailable_domain_html: "%{name} pysäytti toimituksen verkkotunnukseen %{target}"
-        create_user_role_html: "%{name} luonut %{target} roolin"
+        create_user_role_html: "%{name} loi roolin %{target}"
         demote_user_html: "%{name} alensi käyttäjän %{target}"
         destroy_announcement_html: "%{name} poisti ilmoituksen %{target}"
-        destroy_canonical_email_block_html: "%{name} poisti sähköposti eston hashilla %{target}"
+        destroy_canonical_email_block_html: "%{name} poisti sähköpostieston hashilla %{target}"
         destroy_custom_emoji_html: "%{name} poisti emojin %{target}"
-        destroy_domain_allow_html: "%{name} esti yhdistämisen verkkotunnuksella %{target}"
+        destroy_domain_allow_html: "%{name} esti federaation verkkotunnuksella %{target}"
         destroy_domain_block_html: "%{name} poisti verkkotunnuksen %{target} eston"
-        destroy_email_domain_block_html: "%{name} poisti verkkotunnuksen %{target} eston"
+        destroy_email_domain_block_html: "%{name} poisti sähköpostin verkkotunnuksen %{target} eston"
         destroy_instance_html: "%{name} tyhjensi verkkotunnuksen %{target}"
         destroy_ip_block_html: "%{name} poisti IP-säännön %{target}"
-        destroy_status_html: "%{name} poisti viestin %{target}"
+        destroy_status_html: "%{name} poisti käyttäjän %{target} viestin"
         destroy_unavailable_domain_html: "%{name} jatkoi toimitusta verkkotunnukseen %{target}"
-        destroy_user_role_html: "%{name} poisti %{target} roolin"
+        destroy_user_role_html: "%{name} poisti roolin %{target}"
         disable_2fa_user_html: "%{name} poisti käyttäjältä %{target} vaatimuksen kaksivaiheisen todentamiseen"
-        disable_custom_emoji_html: "%{name} poisti emojin %{target}"
+        disable_custom_emoji_html: "%{name} poisti käytöstä emojin %{target}"
         disable_sign_in_token_auth_user_html: "%{name} poisti sähköpostitunnuksen %{target} todennuksen käytöstä"
         disable_user_html: "%{name} poisti kirjautumisen käyttäjältä %{target}"
         enable_custom_emoji_html: "%{name} salli emojin %{target}"
@@ -271,17 +271,17 @@ fi:
         resend_user_html: "%{name} lähetti vahvistusviestin sähköpostitse käyttäjälle %{target}"
         reset_password_user_html: "%{name} palautti käyttäjän %{target} salasanan"
         resolve_report_html: "%{name} ratkaisi raportin %{target}"
-        sensitive_account_html: "%{name} merkitsi %{target} median arkaluonteiseksi"
-        silence_account_html: "%{name} rajoitti %{target} tilin"
+        sensitive_account_html: "%{name} merkitsi käyttäjän %{target} median arkaluonteiseksi"
+        silence_account_html: "%{name} rajoitti käyttäjän %{target} tilin"
         suspend_account_html: "%{name} siirsi käyttäjän %{target} jäähylle"
         unassigned_report_html: "%{name} peruutti raportin määrityksen %{target}"
-        unblock_email_account_html: "%{name} poisti %{target} sähköpostiosoitteen eston"
-        unsensitive_account_html: "%{name} poisti merkinnän %{target} arkaluonteinen media"
+        unblock_email_account_html: "%{name} poisti käyttäjän %{target} sähköpostiosoitteen eston"
+        unsensitive_account_html: "%{name} poisti käyttäjän %{target} median arkaluonteisen merkinnän"
         unsilence_account_html: "%{name} ei tehnyt rajoitusta %{target} tilille"
         unsuspend_account_html: "%{name} perui käyttäjän %{target} jäähyn"
         update_announcement_html: "%{name} päivitti ilmoituksen %{target}"
         update_custom_emoji_html: "%{name} päivitti emojin %{target}"
-        update_domain_block_html: "%{name} päivitti verkkotunnuksen %{target}"
+        update_domain_block_html: "%{name} päivitti verkkotunnuksen %{target} eston"
         update_ip_block_html: "%{name} muutti sääntöä IP-osoitteelle %{target}"
         update_status_html: "%{name} päivitti viestin %{target}"
         update_user_role_html: "%{name} muutti roolia %{target}"
@@ -302,9 +302,9 @@ fi:
       publish: Julkaise
       published_msg: Ilmoitus julkaistu onnistuneesti!
       scheduled_for: Ajastettu %{time}
-      scheduled_msg: Ilmoitus on tarkoitus julkaista!
+      scheduled_msg: Ilmoitus on ajastettu julkaisua varten!
       title: Ilmoitukset
-      unpublish: Julkaisematon
+      unpublish: Lopeta julkaisu
       unpublished_msg: Ilmoituksen julkaisu lopetettu!
       updated_msg: Ilmoitus päivitetty onnistuneesti!
     custom_emojis:
@@ -324,12 +324,12 @@ fi:
       enable: Ota käyttöön
       enabled: Käytössä
       enabled_msg: Emojin käyttöönotto onnistui
-      image_hint: PNG tai GIF enintään %{size}
+      image_hint: PNG tai GIF, enintään %{size}
       list: Listaa
       listed: Listassa
       new:
         title: Lisää uusi mukautettu emoji
-      no_emoji_selected: Hymiöitä ei muutettu, koska yhtään ei valittu
+      no_emoji_selected: Emojeita ei muutettu, koska yhtään ei valittu
       not_permitted: Sinulla ei ole oikeutta suorittaa tätä toimintoa
       overwrite: Kirjoita yli
       shortcode: Lyhennekoodi
@@ -586,6 +586,7 @@ fi:
       comment:
         none: Ei mitään
       comment_description_html: 'Antaakseen lisätietoja %{name} kirjoitti:'
+      confirm_action: Vahvista moderointitoiminto käyttäjää @%{acct} kohtaan
       created_at: Raportoitu
       delete_and_resolve: Poista viestejä
       forwarded: Välitetty
@@ -602,6 +603,7 @@ fi:
         placeholder: Kuvaile mitä toimia on tehty tai muita päivityksiä tähän raporttiin…
         title: Merkinnät
       notes_description_html: Tarkastele ja jätä merkintöjä muille valvojille ja itsellesi tulevaisuuteen
+      processed_msg: 'Raportti #%{id} käsitelty'
       quick_actions_description_html: 'Suorita nopea toiminto tai vieritä alas nähdäksesi raportoitu sisältö:'
       remote_user_placeholder: etäkäyttäjä instanssista %{instance}
       reopen: Avaa raportti uudestaan
@@ -614,9 +616,26 @@ fi:
       status: Tila
       statuses: Raportoitu sisältö
       statuses_description_html: Loukkaava sisältö mainitaan ilmoitetun tilin yhteydessä
+      summary:
+        action_preambles:
+          delete_html: 'Olet aikeissa <strong>poistaa</strong> joitain käyttäjän <strong>@%{acct}</strong> viestejä. Tästä seuraa:'
+          mark_as_sensitive_html: 'Olet aikeissa <strong>merkitä</strong> joitain käyttäjän <strong>@%{acct}</strong> viestejä <strong>arkaluonteisiksi</strong>. Tästä seuraa:'
+          silence_html: 'Olet aikeissa <strong>rajoittaa</strong> käyttäjän <strong>@%{acct}</strong> tiliä. Tästä seuraa:'
+          suspend_html: 'Olet aikeissa <strong>rajoittaa</strong> käyttäjän <strong>@%{acct}</strong> tiliä. Tästä seuraa:'
+        actions:
+          delete_html: Loukkaavat viestit poistetaan
+          mark_as_sensitive_html: Loukkaavien viestien media merkitään arkaluonteiseksi
+          silence_html: Vakavasti rajoittaa käyttäjän <strong>@%{acct}</strong> tavoitettavuutta tekemällä profiilista ja sen sisällöstä näkyviä vain jo häntä seuraaville tai niille, jotka etsivät profiilia manuaalisesti
+          suspend_html: Rajoita <strong>@%{acct}</strong>, jolloin heidän profiilinsa ja sisällönsä ei ole käytettävissä ja on mahdotonta olla vuorovaikutuksessa
+        close_report: 'Merkitse raportti #%{id} selvitetyksi'
+        close_reports_html: Merkitse <strong>kaikki</strong> käyttäjään <strong>@%{acct}</strong> kohdistuvat raportit ratkaistuiksi
+        preview_preamble_html: "<strong>@%{acct}</strong> saa varoituksen, jonka sisältö on seuraava:"
+        send_email_html: Lähetä käyttäjälle <strong>@%{acct}</strong> varoitus sähköpostitse
+        warning_placeholder: Valinnaiset lisäperustelut moderointitoimenpiteelle.
       target_origin: Raportoidun tilin alkuperä
       title: Raportit
       unassign: Määrittämätön
+      unknown_action_msg: 'Tuntematon toiminto: %{action}'
       unresolved: Ratkaisemattomat
       updated_at: Päivitetty
       view_profile: Näytä profiili
@@ -763,7 +782,7 @@ fi:
         none: "%{name} lähetti varoituksen henkilölle %{target}"
         sensitive: "%{name} merkitsi käyttäjän %{target} tilin arkaluonteiseksi"
         silence: "%{name} rajoitti käyttäjän %{target} tilin"
-        suspend: "%{name} keskeytti käyttäjän %{target} tilin"
+        suspend: "%{name} jäädytti käyttäjän %{target} tilin"
       appeal_approved: Valitti
       appeal_pending: Valitus vireillä
     system_checks:
@@ -836,7 +855,7 @@ fi:
         not_trendable: Ei näy trendien alla
         not_usable: Ei voida käyttää
         peaked_on_and_decaying: Saavutti huipun %{date}, nyt hiipuu
-        title: Suositut tunnisteet
+        title: Suositut aihetunnisteet
         trendable: Voi näkyä trendien alla
         trending_rank: 'Nousussa #%{rank}'
         usable: Voidaan käyttää
@@ -881,7 +900,7 @@ fi:
         none: varoitus
         sensitive: merkitä heidän tilinsä arkaluonteiseksi
         silence: rajoittaa heidän tilinsä
-        suspend: keskeyttää heidän tilinsä
+        suspend: jäädyttää heidän tilinsä
       body: "%{target} on valittanut valvojan päätöksestä %{action_taken_by} aika %{date}, joka oli %{type}. He kirjoittivat:"
       next_steps: Voit hyväksyä vetoomuksen ja kumota päätöksen tai jättää sen huomiotta.
       subject: "%{username} valittaa valvojan päätöksestä, joka koskee instanssia %{instance}"
@@ -939,6 +958,8 @@ fi:
   auth:
     apply_for_account: Pyydä tiliä
     change_password: Salasana
+    confirmations:
+      wrong_email_hint: Jos sähköpostiosoite ei ole oikein, voit muuttaa sen tilin asetuksista.
     delete_account: Poista tili
     delete_account_html: Jos haluat poistaa tilisi, <a href="%{path}">paina tästä</a>. Poisto on vahvistettava.
     description:
@@ -1071,7 +1092,7 @@ fi:
         none: Varoitus
         sensitive: Tilin merkitseminen arkaluonteiseksi
         silence: Tilin rajoittaminen
-        suspend: Tilin keskeyttäminen
+        suspend: Tilin jäädyttäminen
       your_appeal_approved: Valituksesi on hyväksytty
       your_appeal_pending: Olet lähettänyt valituksen
       your_appeal_rejected: Valituksesi on hylätty
@@ -1308,9 +1329,9 @@ fi:
     poll:
       subject: Äänestys käyttäjältä %{name} on päättynyt
     reblog:
-      body: "%{name} buustasi tilaasi:"
-      subject: "%{name} boostasi tilaasi"
-      title: Uusi buustaus
+      body: "%{name} tehosti viestiäsi:"
+      subject: "%{name} tehosti viestiäsi"
+      title: Uusi tehostus
     status:
       subject: "%{name} julkaisi juuri"
     update:
@@ -1475,7 +1496,7 @@ fi:
       video:
         one: "%{count} video"
         other: "%{count} videota"
-    boosted_from_html: Tehostettu %{acct_link}
+    boosted_from_html: Tehostus lähteestä %{acct_link}
     content_warning: 'Sisältövaroitus: %{warning}'
     default_language: Sama kuin käyttöliittymän kieli
     disallowed_hashtags:
@@ -1490,7 +1511,7 @@ fi:
       direct: Viestejä, jotka ovat näkyvissä vain mainituille käyttäjille, ei voi kiinnittää
       limit: Olet jo kiinnittänyt suurimman mahdollisen määrän tuuttauksia
       ownership: Muiden tuuttauksia ei voi kiinnittää
-      reblog: Buustausta ei voi kiinnittää
+      reblog: Tehostusta ei voi kiinnittää
     poll:
       total_people:
         one: "%{count} henkilö"
@@ -1550,7 +1571,7 @@ fi:
     min_reblogs_hint: Ei poista yhtään viestiäsi, jota on tehostettu vähintään näin monta kertaa. Jätä tyhjäksi poistaaksesi viestejä riippumatta niiden tehosteiden määrästä
   stream_entries:
     pinned: Kiinnitetty tuuttaus
-    reblogged: buustasi
+    reblogged: tehosti
     sensitive_content: Arkaluontoista sisältöä
   strikes:
     errors:
@@ -1624,7 +1645,7 @@ fi:
         none: Varoitus %{acct}
         sensitive: Sinun viestisi %{acct} merkitään arkaluonteisiksi tästä lähtien
         silence: Tilisi %{acct} on rajoitettu
-        suspend: Tilisi %{acct} on keskeytetty
+        suspend: Tilisi %{acct} on jäädytetty
       title:
         delete_statuses: Viestit poistettu
         disable: Tili jäädytetty
@@ -1632,7 +1653,7 @@ fi:
         none: Varoitus
         sensitive: Tili on merkitty arkaluonteiseksi
         silence: Rajoitettu tili
-        suspend: Tilin käyttäminen keskeytetty
+        suspend: Tilin käyttäminen jäädytetty
     welcome:
       edit_profile_action: Aseta profiili
       edit_profile_step: Voit muokata profiiliasi lataamalla profiilikuvan, vaihtamalla näyttönimeä ja paljon muuta. Voit halutessasi arvioida uudet seuraajat ennen kuin he saavat seurata sinua.
diff --git a/config/locales/fo.yml b/config/locales/fo.yml
index 0d8234be1..874bdc17f 100644
--- a/config/locales/fo.yml
+++ b/config/locales/fo.yml
@@ -441,6 +441,7 @@ fo:
         private_comment_description_html: Fyri at hjálpa tær at fylgja við í, hvaðani innfluttir blokkar koma, verða innfluttu blokkarnir stovnaðir við hesi privatu viðmerkingini:<q>%{comment}</q>
         private_comment_template: Innflutt frá %{source} tann %{date}
         title: Innflyt navnaøkjablokeringar
+      invalid_domain_block: 'Ein ella fleiri navnaøkjablokeringar vóru umlopnar vegna hesar feil(ir): %{error}'
       new:
         title: Innflyt navnaøkjablokeringar
       no_file: Eingin fíla vald
@@ -589,6 +590,7 @@ fo:
       comment:
         none: Eingin
       comment_description_html: 'Fyri at veita fleiri upplýsingar skrivaði %{name}:'
+      confirm_action: Vátta umsjónaratgerð móti @%{acct}
       created_at: Meldað
       delete_and_resolve: Strika postar
       forwarded: Víðarisent
@@ -605,6 +607,7 @@ fo:
         placeholder: Greið frá, hvørjar atgerðir hava verið gjørdar ella aðrar viðkomandi dagføringar...
         title: Viðmerkingar
       notes_description_html: Vís ella skriva viðmerkingar til onnur umsjónarfólk ella teg sjálva/n í framtíðini
+      processed_msg: 'Rapportering #%{id} liðugt viðgjørd'
       quick_actions_description_html: 'Tak eina skjóta avgerð ella skrulla niðureftir fyri at síggja meldaða innihaldið:'
       remote_user_placeholder: fjarbrúkarin frá %{instance}
       reopen: Lat melding uppaftur
@@ -617,9 +620,28 @@ fo:
       status: Støða
       statuses: Meldað innihald
       statuses_description_html: Tilfarið, sum brotið viðvíkur, fer at vera siterað í samskifti við meldaðu kontuni
+      summary:
+        action_preambles:
+          delete_html: 'Tú er í ferð við at <strong>strika</strong> nakrar av postunum hjá <strong>@%{acct}</strong>. Hetta fer at:'
+          mark_as_sensitive_html: 'Tú er í ferð við at <strong>merkja</strong> nakrar av postunum hjá <strong>@%{acct}</strong> sum <strong>viðkvæmar</strong>. Hetta fer at:'
+          silence_html: 'Tú er í ferð við at <strong>avmarka</strong> kontuna hjá <strong>@%{acct}</strong>. Hetta fer at:'
+          suspend_html: 'Tú er í ferð við at gera kontuna hjá <strong>@%{acct}</strong> <strong>óvirkna</strong>. Hetta fer at:'
+        actions:
+          delete_html: Strika postar, ið eru farnir útum mark
+          mark_as_sensitive_html: Merk postar, ið eru farnir um mark, sum viðkvæmar
+          silence_html: Avmarka hvussu langt <strong>@%{acct}</strong> røkkur við einans at lata vangan og innihaldið hjá teimum vera sjónligt hjá fólki, sum longu fylgja teimum ella manuelt sláa vangan upp
+          suspend_html: Ger kontuna <strong>@%{acct}</strong> óvirkna, soleiðis at vangin og innihaldið verður óframkomiligt og ómøguligt at samvirka við
+        close_report: 'Merk kæruna #%{id} sum loysta'
+        close_reports_html: Merk <strong>allar</strong> kærur móti <strong>@%{acct}</strong> sum loystar
+        delete_data_html: Strika vangan og innihaldið hjá <strong>@%{acct}</strong> um 30 dagar uttan so at kontan verður gjørd virkin aftur áðrenn tað
+        preview_preamble_html: "<strong>@%{acct}</strong> ger at móttaka eina ávaring við hesum innihaldi:"
+        record_strike_html: Skráset eitt brot á <strong>@%{acct}</strong>, soleiðis at tað kann havast í huga í samband við møgulig framtíðar mishald
+        send_email_html: Send <strong>@%{acct}</strong> eitt teldubræv við eini ávaring
+        warning_placeholder: Møguligar eyka grundgevingar fyri umsjónaratgerð.
       target_origin: Uppruni hjá meldaðu kontuni
       title: Meldingar
       unassign: Strika tillutan
+      unknown_action_msg: 'Ókend atgerð: %{action}'
       unresolved: Óloyst
       updated_at: Dagført
       view_profile: Vís vangamynd
@@ -943,6 +965,8 @@ fo:
   auth:
     apply_for_account: Bið um eina kontu
     change_password: Loyniorð
+    confirmations:
+      wrong_email_hint: Um hesin teldupoststaðurin ikki er rættur, so kanst tú broyta hann í kontustillingunum.
     delete_account: Strika kontu
     delete_account_html: Ynskir tú at strika kontuna, so kanst tú <a href="%{path}">halda fram her</a>. Tú verður spurd/ur um váttan.
     description:
diff --git a/config/locales/fr-QC.yml b/config/locales/fr-QC.yml
index f63bd65e8..ed4c900f7 100644
--- a/config/locales/fr-QC.yml
+++ b/config/locales/fr-QC.yml
@@ -575,7 +575,10 @@ fr-QC:
         mark_as_sensitive_description_html: Les médias des messages signalés seront marqués comme sensibles et une sanction sera enregistrée pour vous aider à prendre les mesures appropriées en cas d'infractions futures par le même compte.
         other_description_html: Voir plus d'options pour contrôler le comportement du compte et personnaliser la communication vers le compte signalé.
         resolve_description_html: Aucune mesure ne sera prise contre le compte signalé, aucune sanction ne sera enregistrée et le sigalement sera clôturé.
+        silence_description_html: Le compte ne sera visible que par ceux qui le suivent déjà ou qui le recherchent manuellement, ce qui limite fortement sa portée. Cette action peut toujours être annulée. Cloture tous les signalements concernant ce compte.
+        suspend_description_html: Le compte et tous ses contenus seront inaccessibles et finalement supprimés, et il sera impossible d'interagir avec lui. Réversible dans les 30 jours. Cloture tous les signalements concernant ce compte.
       actions_description_html: Décidez des mesures à prendre pour résoudre ce signalement. Si vous prenez des mesures punitives contre le compte signalé, une notification sera envoyée par e-mail, sauf si la catégorie <strong>Spam</strong> est sélectionnée.
+      actions_description_remote_html: Décidez des mesures à prendre pour résoudre ce signalement. Cela n'affectera que la manière dont <strong>votre</strong> serveur communique avec ce compte distant et traite son contenu.
       add_to_report: Ajouter davantage au rapport
       are_you_sure: Voulez-vous vraiment faire ça ?
       assign_to_self: Me l’assigner
@@ -711,6 +714,8 @@ fr-QC:
         preamble: Faire apparaître un contenu intéressant est essentiel pour interagir avec de nouveaux utilisateurs qui ne connaissent peut-être personne sur Mastodonte. Contrôlez le fonctionnement des différentes fonctionnalités de découverte sur votre serveur.
         profile_directory: Annuaire des profils
         public_timelines: Fils publics
+        publish_discovered_servers: Publier les serveurs découverts
+        publish_statistics: Publier les statistiques
         title: Découverte
         trends: Tendances
       domain_blocks:
@@ -1365,6 +1370,9 @@ fr-QC:
       unrecognized_emoji: n’est pas un émoji reconnu
   relationships:
     activity: Activité du compte
+    confirm_follow_selected_followers: Voulez-vous vraiment suivre les abonné⋅e⋅s sélectionné⋅e⋅s ?
+    confirm_remove_selected_followers: Voulez-vous vraiment supprimer les abonné⋅e⋅s sélectionné⋅e⋅s ?
+    confirm_remove_selected_follows: Voulez-vous vraiment suivre les abonnements sélectionnés ?
     dormant: Dormant
     follow_selected_followers: Suivre les abonné·e·s sélectionné·e·s
     followers: Abonné·e
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index a3af8b739..75f975623 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -392,7 +392,7 @@ fr:
         create: Créer le blocage
         hint: Le blocage de domaine n’empêchera pas la création de comptes dans la base de données, mais il appliquera automatiquement et rétrospectivement des méthodes de modération spécifiques sur ces comptes.
         severity:
-          desc_html: "<strong>Limiter</strong> rendra les messages des comptes de ce domaine invisibles à ceux qui ne les suivent pas. <strong>Suspendre</strong> supprimera tout le contenu, les médias, et données de profile pour les comptes de ce domaine de votre serveur. Utilisez <strong>Aucun</strong> si vous voulez simplement rejeter les fichiers multimédia."
+          desc_html: "<strong>Limiter</strong> rendra les messages des comptes de ce domaine invisibles pour tous les comptes qui ne les suivent pas. <strong>Suspendre</strong> supprimera de votre serveur tout le contenu, les médias et données de profil pour les comptes sur ce domaine. Utilisez <strong>Aucun</strong> si vous voulez simplement rejeter les fichiers multimédia."
           noop: Aucune
           silence: Limiter
           suspend: Suspendre
@@ -438,11 +438,12 @@ fr:
       import:
         description_html: Vous êtes sur le point d'importer une liste de blocs de domaine. Veuillez examiner cette liste très attentivement, spécialement si vous n'êtes pas l'auteur de cette liste.
         existing_relationships_warning: Relations de suivi existantes
-        private_comment_description_html: 'Pour vous aider à suivre d''où viennent les blocs importés, des blocs importés seront créés avec le commentaire privé suivant : <q>%{comment}</q>'
+        private_comment_description_html: 'Pour vous aider à savoir d''où proviennent les blocages importés, ceux-ci seront créés avec le commentaire privé suivant : <q>%{comment}</q>'
         private_comment_template: Importé depuis %{source} le %{date}
-        title: Importer des blocs de domaine
+        title: Importer des blocages de domaine
+      invalid_domain_block: 'Un ou plusieurs blocages de domaine ont été ignorés en raison des erreurs suivantes : %{error}'
       new:
-        title: Importer des blocs de domaine
+        title: Importer des blocages de domaine
       no_file: Aucun fichier sélectionné
     follow_recommendations:
       description_html: "<strong>Les recommandations d'abonnement aident les nouvelles personnes à trouver rapidement du contenu intéressant</strong>. Si un·e utilisateur·rice n'a pas assez interagi avec les autres pour avoir des recommandations personnalisées, ces comptes sont alors recommandés. La sélection est mise à jour quotidiennement depuis un mélange de comptes ayant le plus d'interactions récentes et le plus grand nombre d'abonné·e·s locaux pour une langue donnée."
@@ -589,6 +590,7 @@ fr:
       comment:
         none: Aucun
       comment_description_html: 'Pour fournir plus d''informations, %{name} a écrit :'
+      confirm_action: Confirmer l'action de modération contre @%{acct}
       created_at: Signalé
       delete_and_resolve: Supprimer les messages
       forwarded: Transféré
@@ -605,6 +607,7 @@ fr:
         placeholder: Décrivez quelles actions ont été prises, ou toute autre mise à jour…
         title: Remarques
       notes_description_html: Voir et laisser des notes aux autres modérateurs et à votre futur moi-même
+      processed_msg: 'Le signalement #%{id} a été traité avec succès'
       quick_actions_description_html: 'Faites une action rapide ou faites défiler vers le bas pour voir le contenu signalé :'
       remote_user_placeholder: l'utilisateur·rice distant·e de %{instance}
       reopen: Ré-ouvrir le signalement
@@ -617,9 +620,28 @@ fr:
       status: Statut
       statuses: Contenu signalé
       statuses_description_html: Le contenu offensant sera cité dans la communication avec le compte signalé
+      summary:
+        action_preambles:
+          delete_html: 'Vous êtes sur le point de <strong>supprimer</strong> certains messages de <strong>@%{acct}</strong>. Cela va :'
+          mark_as_sensitive_html: 'Vous êtes sur le point de <strong>marquer</strong> certains messages de <strong>@%{acct}</strong> comme <strong>sensibles</strong>. Cela va :'
+          silence_html: 'Vous êtes sur le point de <strong>limiter</strong> le compte de <strong>@%{acct}</strong>. Cela va :'
+          suspend_html: 'Vous êtes sur le point de <strong>suspendre</strong> le compte de <strong>@%{acct}</strong>. Cela va :'
+        actions:
+          delete_html: supprimer les messages incriminés
+          mark_as_sensitive_html: marquer le média des messages incriminés comme sensible
+          silence_html: limiter drastiquement la portée du compte de <strong>@%{acct}</strong> en rendant son profil, ainsi que ses contenus, visibles uniquement des personnes déjà abonnées ou qui le recherchent manuellement
+          suspend_html: suspendre le compte de <strong>@%{acct}</strong>, ce qui empêche toute interaction avec son profil et tout accès à ses contenus
+        close_report: 'marquer le signalement #%{id} comme résolu'
+        close_reports_html: marquer <strong>tous</strong> les signalements de <strong>@%{acct}</strong> comme résolus
+        delete_data_html: effacer le profil de <strong>@%{acct}</strong> et ses contenus dans 30 jours, à moins que la suspension du compte ne soit annulée entre temps
+        preview_preamble_html: "<strong>@%{acct}</strong> recevra un avertissement contenant les éléments suivants :"
+        record_strike_html: enregistrer une sanction contre <strong>@%{acct}</strong> pour vous aider à prendre des mesures supplémentaires en cas d'infractions futures de ce compte
+        send_email_html: envoyer un courriel d'avertissement à <strong>@%{acct}</strong>
+        warning_placeholder: Arguments supplémentaires pour l'action de modération (facultatif).
       target_origin: Origine du compte signalé
       title: Signalements
       unassign: Dés-assigner
+      unknown_action_msg: 'Action inconnue : %{action}'
       unresolved: Non résolus
       updated_at: Mis à jour
       view_profile: Voir le profil
@@ -943,6 +965,8 @@ fr:
   auth:
     apply_for_account: Demander un compte
     change_password: Mot de passe
+    confirmations:
+      wrong_email_hint: Si cette adresse courriel est incorrecte, vous pouvez la modifier dans vos paramètres de compte.
     delete_account: Supprimer le compte
     delete_account_html: Si vous désirez supprimer votre compte, vous pouvez <a href="%{path}">cliquer ici</a>. Il vous sera demandé de confirmer cette action.
     description:
diff --git a/config/locales/fy.yml b/config/locales/fy.yml
index acb3e28e0..74d211e36 100644
--- a/config/locales/fy.yml
+++ b/config/locales/fy.yml
@@ -441,6 +441,7 @@ fy:
         private_comment_description_html: 'Om jo te helpen byhâlden wêr’t de ymportearre blokkaden wei komme, wurde de ymportearre blokkaden mei de folgjende priveeopmerking oanmakke: <q>%{comment}</q>'
         private_comment_template: Ymportearre fan %{source} op %{date}
         title: Domeinblokkaden ymportearje
+      invalid_domain_block: 'Ien of mear domeinblokkaden binne oerslein, fanwegen de folgjende flater(s): %{error}'
       new:
         title: Domeinblokkaden ymportearje
       no_file: Gjin bestân selektearre
@@ -589,6 +590,7 @@ fy:
       comment:
         none: Gjin
       comment_description_html: 'Om mear ynformaasje te jaan, skreau %{name}:'
+      confirm_action: Moderaasjemaatregelen tsjin %{acct} befêstigje
       created_at: Rapportearre op
       delete_and_resolve: Berjocht fuortsmite
       forwarded: Trochstjoerd
@@ -605,6 +607,7 @@ fy:
         placeholder: Beskriuw hokker maatregels nommen binne of oare relatearre opmerkingen…
         title: Opmerkingen
       notes_description_html: Besjoch en lit opmerkingen efter foar oare moderatoaren en foar jo takomstige sels
+      processed_msg: 'Rapprtaazje #%{id} mei sukses ferwurke'
       quick_actions_description_html: 'Nim in flugge maatregel of skow nei ûnder om de rapportearre ynhâld te besjen:'
       remote_user_placeholder: de eksterne brûker fan %{instance}
       reopen: Rapport opnij iepenje
@@ -617,9 +620,28 @@ fy:
       status: Steat
       statuses: Rapportearre ynhâld
       statuses_description_html: De problematyske ynhâld wurdt oan it rapportearre account meidield
+      summary:
+        action_preambles:
+          delete_html: 'Jo stean op it punt om inkelde berjochten fan <strong>@%{acct}</strong> <strong>fuort te smiten</strong>. Dit sil:'
+          mark_as_sensitive_html: 'Jo stean op it punt om inkelde berjochten fan <strong>@%{acct}</strong> as <strong>gefoelich</strong> te <strong>markearjen</strong>. Dit sil:'
+          silence_html: 'Jo stean op it punt de account fan <strong>@%{acct}</strong> te <strong>beheinen</strong>. Dit sil:'
+          suspend_html: 'Jo stean op it punt de account fan <strong>@%{acct}</strong> te <strong>beskoatteljen</strong>. Dit sil:'
+        actions:
+          delete_html: De problematyske berjochten fuortsmite
+          mark_as_sensitive_html: De media fan de problematyske berjochten as gefoelich markearje
+          silence_html: "<strong>@%{acct}</strong> earnstich beheine, troch it profyl ende ynhâld allinnich sichtber te meitsjen foar de minsken dy’t harren al folgje of hantmjittich it profyl opsykje"
+          suspend_html: "<strong>@%{acct}</strong> blokkearje, sadat it profyl en de ynhâld net mear tagonklik is en it net mooglik is om ynteraksje dêr mei te hawwen"
+        close_report: 'Rapportaazje #%{id} as oplost markearje'
+        close_reports_html: "<strong>Alle</strong> meldingen tsjin <strong>@%{acct}</strong> as oplost markearje"
+        delete_data_html: It profyl en de ynhâld fan <strong>@%{acct}</strong> wurde nei 30 dagen fan no ôf fuortsmiten, útsein as de account yn de tuskentiid net mear blokkearre wurdt
+        preview_preamble_html: "<strong>@%{acct}</strong> sil in warskôging ûntfange mei de folgjende ynhâld:"
+        record_strike_html: In ban tsjin <strong>@%{acct}</strong> ynstelle, om jo te helpen by takomstige skeiningen fan dizze acount te eskalearjen
+        send_email_html: Stjoer <strong>@%{acct}</strong> in warskôgings-e-mailberjocht
+        warning_placeholder: Ekstra opsjonele reden foar de moderaasje-aksje.
       target_origin: Orizjineel fan rapportearre account
       title: Rapportaazjes
       unassign: Net langer tawize
+      unknown_action_msg: 'Unbekende aksje: %{action}'
       unresolved: Net oplost
       updated_at: Bywurke
       view_profile: Profyl besjen
@@ -943,6 +965,8 @@ fy:
   auth:
     apply_for_account: Account oanfreegje
     change_password: Wachtwurd
+    confirmations:
+      wrong_email_hint: As it e-mailadres net korrekt is, kinne jo dat wizigje yn de accountynstellingen.
     delete_account: Account fuortsmite
     delete_account_html: Wannear’t jo jo account graach fuortsmite wolle, kinne jo dat <a href="%{path}">hjir dwaan</a>. Wy freegje jo dêr om in befêstiging.
     description:
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index c2b790388..6885000ac 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -441,6 +441,7 @@ gl:
         private_comment_description_html: 'Para axudarche a lembrar de onde veñen os bloqueos importados, imos crealos engadindo o seguinte comentario privado: <q>%{comment}</q>'
         private_comment_template: Importada desde %{source} o %{date}
         title: Importar bloqueos de dominio
+      invalid_domain_block: 'Un ou varios dominios non se bloquearon debido ao seguintes erros: %{error}'
       new:
         title: Importar bloqueos de dominio
       no_file: Ningún ficheiro seleccionado
@@ -589,6 +590,7 @@ gl:
       comment:
         none: Ningún
       comment_description_html: 'Como información engadida, %{name} escribiu:'
+      confirm_action: Confirma a acción de moderación contra @%{acct}
       created_at: Denunciado
       delete_and_resolve: Eliminar publicacións
       forwarded: Reenviado
@@ -605,6 +607,7 @@ gl:
         placeholder: Describir que accións foron tomadas ou calquera outra novidade sobre esta denuncia...
         title: Notas
       notes_description_html: Ver e deixar unha nota para ti no futuro e outras moderadoras
+      processed_msg: 'Procesada correctamente a denuncia #%{id}'
       quick_actions_description_html: 'Toma unha acción rápida ou desprázate abaixo para ver o contido denunciado:'
       remote_user_placeholder: a usuaria remota desde %{instance}
       reopen: Reabrir denuncia
@@ -617,9 +620,28 @@ gl:
       status: Estado
       statuses: Contido denunciado
       statuses_description_html: O contido ofensivo será citado na comunicación coa conta denunciada
+      summary:
+        action_preambles:
+          delete_html: 'Vas <strong>eliminar</strong> algunha das publicacións de <strong>@%{acct}</strong>. Serán:'
+          mark_as_sensitive_html: 'Vas <strong>marcar</strong> algunha das publicacións de <strong>@%{acct}</strong> como <strong>sensibles</strong>. Serán:'
+          silence_html: 'Vas <strong>limitar</strong> a conta <strong>@%{acct}</strong>:'
+          suspend_html: 'Vas <strong>suspender</strong> a conta <strong>@%{acct}</strong>:'
+        actions:
+          delete_html: Eliminar as publicacións ofensivas
+          mark_as_sensitive_html: Marcar as publicacións ofensivas como sensibles
+          silence_html: Limitar moito a visibilidade e alcance da conta <strong>@%{acct}</strong> facendo que o seu perfil e contidos sexan visibles só por persoas que a seguen ou que a busquen de xeito activo
+          suspend_html: Suspender a conta <strong>@%{acct}</strong>, facendo que o seu perfil e contidos non sexan accesibles e imposible interactuar con ela
+        close_report: 'Marcar a denuncia #%{id} como resolta'
+        close_reports_html: Marcar <strong>todas</strong> as denuncias contra <strong>@%{acct}</strong> como resoltas
+        delete_data_html: Eliminar o perfil e contidos de <strong>@%{acct}</strong> para os próximos 30 días a non ser que sexa suspendida nese período
+        preview_preamble_html: "<strong>@%{acct}</strong> vai recibir un aviso co seguinte contido:"
+        record_strike_html: Anotar un aviso contra <strong>@%{acct}</strong> para axudarche a xestionar futuros problemas con esta conta
+        send_email_html: Enviar un email de aviso a <strong>@%{acct}</strong>
+        warning_placeholder: Razóns adicionais optativas para a acción de moderación.
       target_origin: Orixe da conta denunciada
       title: Denuncias
       unassign: Non asignar
+      unknown_action_msg: 'Acción descoñecida: %{action}'
       unresolved: Non resolto
       updated_at: Actualizado
       view_profile: Ver perfil
@@ -943,6 +965,8 @@ gl:
   auth:
     apply_for_account: Solicita unha conta
     change_password: Contrasinal
+    confirmations:
+      wrong_email_hint: Se o enderezo de email non é correcto, podes cambialo nos axustes da conta.
     delete_account: Eliminar conta
     delete_account_html: Se queres eliminar a túa conta, podes <a href="%{path}">facelo aquí</a>. Deberás confirmar a acción.
     description:
@@ -1549,7 +1573,7 @@ gl:
       '7889238': 3 meses
     min_age_label: Límite temporal
     min_favs: Manter as publicacións favoritas máis de
-    min_favs_hint: Non elimina ningunha das túas publicacións que recibiron máis desta cantidade de favoritos. Deixa en branco para eliminar publicacións independentemente do número de favorecementos
+    min_favs_hint: Non elimina ningunha das túas publicacións que recibiron máis desta cantidade de favorecementos. Deixa en branco para eliminar publicacións independentemente do número de favorecementos
     min_reblogs: Manter publicacións promovidas máis de
     min_reblogs_hint: Non elimina ningunha das túas publicacións se foron promovidas máis deste número de veces. Deixa en branco para eliminar publicacións independentemente do seu número de promocións
   stream_entries:
diff --git a/config/locales/he.yml b/config/locales/he.yml
index 17b6ebb9f..bad6fd6fd 100644
--- a/config/locales/he.yml
+++ b/config/locales/he.yml
@@ -457,6 +457,7 @@ he:
         private_comment_description_html: 'כדי לסייע במעקב מאיכן הגיעו חסימות, חסימות מיובאות ילוו בהערה פרטית זו: <q>%{comment}</q>'
         private_comment_template: יובא מתוך %{source} בתאריך %{date}
         title: יבוא רשימת שרתים חסומים
+      invalid_domain_block: 'חסימה של שרת אחד או יותר דולגו בשל השגיאה הבאה: %{error}'
       new:
         title: יבוא רשימת שרתים חסומים
       no_file: לא נבחר קובץ
@@ -613,6 +614,7 @@ he:
       comment:
         none: ללא
       comment_description_html: 'על מנת לספק עוד מידע, %{name} כתב\ה:'
+      confirm_action: נא לאשר פעולת משמעת לגבי חשבון %{acct}
       created_at: מדווח
       delete_and_resolve: מחיקת הודעות
       forwarded: קודם
@@ -629,6 +631,7 @@ he:
         placeholder: תאר/י אילו פעולות ננקטו, או עדכונים קשורים אחרים...
         title: הערות
       notes_description_html: צפייה והשארת הערות למנחים אחרים או לעצמך העתידי
+      processed_msg: דיווח %{id} עוּבָּד בהצלחה
       quick_actions_description_html: 'נקוט/י פעולה מהירה או גלול/י למטה לצפייה בתוכן המדווח:'
       remote_user_placeholder: המשתמש המרוחק מ-%{instance}
       reopen: פתיחת דו"ח מחדש
@@ -641,9 +644,28 @@ he:
       status: מצב
       statuses: התוכן עליו דווח
       statuses_description_html: התוכן הפוגע יצוטט בתקשורת עם החשבון המדווח
+      summary:
+        action_preambles:
+          delete_html: 'הפעולה <strong>תמחק</strong> כמה הודעות של חשבון <strong>@%{acct}</strong>. תוצאות הפעולה יהיו:'
+          mark_as_sensitive_html: 'הפעולה הבאה <strong>תסמן</strong> כמה הודעות של חשבון <strong>@%{acct}</strong> כ<strong>רגישות</strong>. תוצאות הפעולה יהיו:'
+          silence_html: 'הפעולה הבאה <strong>תגביל</strong> את החשבון <strong>@%{acct}</strong>. תוצאות הפעולה יהיו:'
+          suspend_html: 'הפעולה הבאה <strong>תקפיא</strong> את החשבון <strong>@%{acct}</strong>. תוצאות הפעולה יהיו:'
+        actions:
+          delete_html: הסרת ההודעות החורגות
+          mark_as_sensitive_html: סימון המדיה בהודעות החורגות כרגיש לצפיה
+          silence_html: הגבלה חמורה של תפוצת <strong>@%{acct}</strong> על ידי הפיכת החשבון ותכניו לזמינים רק למי שכבר עוקב אחריו או מי שיחפשו את עמוד הפרופיל שלו ישירות
+          suspend_html: הקפאת החשבון <strong>@%{acct}</strong>, הפיכת הפרופיל והתוכן לנסתרים ולא ניתנים לתקשורת
+        close_report: 'סימון דיווח #%{id} בתור פתור'
+        close_reports_html: סימון <strong>כל</strong> הדיווחים נגד <strong>@%{acct}</strong> בתור פתורים
+        delete_data_html: למחוק את הפרופיל והתוכן של <strong>@%{acct}</strong> בעוד 30 יום אלא אם תוסר ההגבלה עליהם לפני כן
+        preview_preamble_html: 'שליחת אזהרה אל <strong>@%{acct}</strong> בזו הלשון:'
+        record_strike_html: ציין נקודה שחורה נגד <strong>@%{acct}</strong> כדי לסייע בשיפוטו בדיווחים עתידיים על חריגות
+        send_email_html: שליחת דואל אזהרה אל <strong>@%{acct}</strong>
+        warning_placeholder: צידוקים אפשריים נוספים לפעולה המשמעתית.
       target_origin: מקור החשבון המדווח
       title: דיווחים
       unassign: ביטול הקצאה
+      unknown_action_msg: 'פעולה לא מוכרת: %{action}'
       unresolved: לא פתור
       updated_at: עודכן
       view_profile: צפה בפרופיל
@@ -979,6 +1001,8 @@ he:
   auth:
     apply_for_account: הגשת בקשה לחשבון
     change_password: סיסמה
+    confirmations:
+      wrong_email_hint: אם כתובת הדואל הזו איננה נכונה, ניתן לשנות אותה בעמוד ההגדרות.
     delete_account: מחיקת חשבון
     delete_account_html: אם ברצונך למחוק את החשבון, ניתן <a href="%{path}">להמשיך כאן</a>. תתבקש/י לספק אישור נוסף.
     description:
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index b6ccd7a26..a9315b1f2 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -104,10 +104,10 @@ hu:
       not_subscribed: Nincs feliratkozás
       pending: Engedélyezés alatt
       perform_full_suspension: Felfüggesztés
-      previous_strikes: Korábbi szankciók
+      previous_strikes: Korábbi felrótt vétségek
       previous_strikes_description_html:
-        one: Ezt a fiókot <strong>egyszer</strong> szankcionálták.
-        other: Ezt a fiókot <strong>%{count}</strong> esetben szankcionálták.
+        one: Ehhez a fiókhoz <strong>egyszer</strong> róttak fel vétséget.
+        other: Ehhez a fiókhoz <strong>%{count}</strong> esetben róttak fel vétséget.
       promote: Előléptetés
       protocol: Protokoll
       public: Nyilvános
@@ -441,6 +441,7 @@ hu:
         private_comment_description_html: 'Az importált tiltások forrásának könnyebb követése érdekében, az importált tiltások a következő privát megjegyzéssel lesznek létrehozva: <q>%{comment}</q>'
         private_comment_template: 'Innen importálva: %{source}, ekkor: %{date}'
         title: Domain tiltások importálása
+      invalid_domain_block: 'Egy vagy több domain letiltást kihagytunk a következő hiba(ák) miatt: %{error}'
       new:
         title: Domain tiltások importálása
       no_file: Nincs fájl kiválasztva
@@ -571,10 +572,10 @@ hu:
       action_log: Audit napló
       action_taken_by: 'Kezelte:'
       actions:
-        delete_description_html: A bejelentett bejegyzéseket törölni fogjuk és feljegyzünk egy szankciót, hogy segítsük az eszkalációt a fiók későbbi kihágásai esetén.
-        mark_as_sensitive_description_html: A bejelentett bejegyzések médaitartalmait érzékenynek jelöljük, és rögzítünk egy szankciót, hogy segítsük az eszkalációt a fiók későbbi kihágásai esetében.
+        delete_description_html: A bejelentett bejegyzéseket törölni fogjuk és feljegyzünk egy vétséget, hogy segítsük az eszkalációt a fiók későbbi kihágásai esetén.
+        mark_as_sensitive_description_html: A bejelentett bejegyzések médaitartalmait érzékenynek jelöljük, és rögzítünk egy vétséget, hogy segítsük az eszkalációt a fiók későbbi kihágásai esetén.
         other_description_html: További lehetőségek megjelenítése a fiók viselkedésének szabályozásához, és a jelentett fiók kommunikációjának testreszabásához.
-        resolve_description_html: Nem csinálunk semmit a bejelentett fiókkal, nem jegyzünk fel szankciót, és bezárjuk a bejelentést.
+        resolve_description_html: Nem csinálunk semmit a bejelentett fiókkal, nem jegyzünk fel vétséget, és bezárjuk a bejelentést.
         silence_description_html: A profil csak azok számára lesz látható, akik már követik, vagy kézzel rákeresnek, jelentősen korlátozva annak elérését. Ez a művelet bármikor visszafordítható. A fiókkal szemben indított minden bejelentést lezárunk.
         suspend_description_html: A fiók és minden tartalma elérhetetlenné válik és végül törlésre kerül. A fiókkal kapcsolatbalépni lehetetlen lesz. Ez a művelet 30 napig visszafordítható. A fiók ellen indított minden bejelentést lezárunk.
       actions_description_html: Döntsd el, mit csináljunk, hogy megoldjuk ezt a bejelentést. Ha valamilyen büntető intézkedést hozol a bejelentett fiók ellen, küldünk neki egy figyelmeztetést e-mail-ben, kivéve ha a <strong>Spam</strong> kategóriát választod.
@@ -589,6 +590,7 @@ hu:
       comment:
         none: Egyik sem
       comment_description_html: 'Hogy további információkat adjon, %{name} ezt írta:'
+      confirm_action: Moderációs művelet jóváhagyása @%{acct} fiókon
       created_at: Jelentve
       delete_and_resolve: Bejegyzések törlése
       forwarded: Továbbítva
@@ -605,6 +607,7 @@ hu:
         placeholder: Jegyezd le, mi tettünk az ügy érdekében, vagy bármilyen változást...
         title: Megjegyzések
       notes_description_html: Megtekintés, és megjegyzések hagyása más moderátoroknak
+      processed_msg: 'Bejelentés #%{id} sikeresen feldolgozva'
       quick_actions_description_html: 'Hozz egy gyors intézkedést, vagy görgess le a bejelentett tartalomhoz:'
       remote_user_placeholder: 'a távoli felhasználó innen: %{instance}'
       reopen: Bejelentés újranyitása
@@ -617,9 +620,28 @@ hu:
       status: Állapot
       statuses: Jelentett tartalom
       statuses_description_html: A sértő tartalmat idézni fogjuk a bejelentett fiókkal való kommunikáció során
+      summary:
+        action_preambles:
+          delete_html: 'Arra készülsz, hogy <strong>eltávolítsd</strong> <strong>@%{acct}</strong> néhány bejegyzését. Ez a következőket okozza:'
+          mark_as_sensitive_html: 'Arra készülsz, hogy <strong>érzékenynek jelöld</strong> <strong>@%{acct}</strong> néhány tartalmát. Ez a következőket okozza:'
+          silence_html: 'Arra készülsz, hogy <strong>korlátozd</strong> <strong>@%{acct}</strong> fiókját. Ez a következőket okozza:'
+          suspend_html: 'Arra készülsz, hogy <strong>felfüggeszd</strong> <strong>@%{acct}</strong> fiókját. Ez a következőket okozza:'
+        actions:
+          delete_html: Sértő bejegyzések eltávolítása
+          mark_as_sensitive_html: Sértő bejegyzések médiatartalmainak érzékenyként történő megjelölése
+          silence_html: "<strong>@%{acct}</strong> fiók elérésének jelentős korlátozása azzal, hogy ennek profilja és tartalmai csak olyanoknak legyen látható, akik követik vagy manuálisan rákeresnek"
+          suspend_html: "<strong>@%{acct}</strong> felfüggesztése a profil és tartalmainak elérhetetlenné tételével, a fiókkal való interakció ellehetetlenítésével"
+        close_report: 'Bejelentés #%{id} megjelölése megoldottként'
+        close_reports_html: "<strong>Minden</strong> <strong>@%{acct}</strong> ellen tett bejelentés megjelölése megoldottként"
+        delete_data_html: "<strong>@%{acct}</strong> profiljának és tartalmainak törlése 30 nap múlva, hacsak addig nem oldják fel a felfüggesztést"
+        preview_preamble_html: "<strong>@%{acct}</strong> a következő tartalommal fog figyelmeztetést kapni:"
+        record_strike_html: Vétség felrovása a <strong>@%{acct}</strong> fiók ellen, hogy segítsük az eszkalációt a fiók jövőbeni kihágásai esetén
+        send_email_html: Figyelmeztető email küldése <strong>@%{acct}</strong> fiók részére
+        warning_placeholder: Opcionális kiegészítő indoklás a moderációs művelethez.
       target_origin: A jelentett fiók eredete
       title: Bejelentések
       unassign: Hozzárendelés törlése
+      unknown_action_msg: 'Ismeretlen művelet: %{action}'
       unresolved: Megoldatlan
       updated_at: Frissítve
       view_profile: Profil megtekintése
@@ -943,6 +965,8 @@ hu:
   auth:
     apply_for_account: Fiók kérése
     change_password: Jelszó
+    confirmations:
+      wrong_email_hint: Ha az emailcím nem helyes, a fiókbeállításokban megváltoztathatod.
     delete_account: Felhasználói fiók törlése
     delete_account_html: Felhasználói fiókod törléséhez <a href="%{path}">kattints ide</a>. A rendszer újbóli megerősítést fog kérni.
     description:
@@ -990,7 +1014,7 @@ hu:
       functional: A fiókod teljesen működőképes.
       pending: A jelentkezésed engedélyezésre vár. Ez eltarthat egy ideig. Kapsz egy e-mailt, ha a kérelmedet jóváhagyták.
       redirecting_to: A fiókod inaktív, mert jelenleg ide %{acct} van átirányítva.
-      view_strikes: Fiókod elleni korábbi szankciók megtekintése
+      view_strikes: Fiókod ellen felrótt korábbi vétségek megtekintése
     too_fast: Túl gyorsan küldted el az űrlapot, próbáld később.
     use_security_key: Biztonsági kulcs használata
   authorize_follow:
@@ -1053,7 +1077,7 @@ hu:
     strikes:
       action_taken: Intézkedés
       appeal: Fellebbezés
-      appeal_approved: Ezt a szankciót eredményesen fellebbezték, így már nem érvényes
+      appeal_approved: Ezt a felrótt vétséget eredményesen fellebbezték, így már nem érvényes
       appeal_rejected: A fellebbezést visszautasították
       appeal_submitted_at: Fellebbezés beküldve
       appealed_msg: A fellebbezésedet beküldtük. Ha jóváhagyták, értesítünk.
@@ -1464,7 +1488,7 @@ hu:
     profile: Profil
     relationships: Követések és követők
     statuses_cleanup: Bejegyzések automatikus törlése
-    strikes: Moderációs szankciók
+    strikes: Moderációs felrótt vétségek
     two_factor_authentication: Kétlépcsős hitelesítés
     webauthn_authentication: Biztonsági kulcsok
   statuses:
@@ -1558,7 +1582,7 @@ hu:
     sensitive_content: Kényes tartalom
   strikes:
     errors:
-      too_late: Túl késő, hogy fellebbezd ezt a szankciót
+      too_late: Túl késő, hogy fellebbezd ezt a felrótt vétséget
   tags:
     does_not_match_previous_name: nem illeszkedik az előző névvel
   themes:
@@ -1588,11 +1612,11 @@ hu:
   user_mailer:
     appeal_approved:
       action: Ugrás a fiókodhoz
-      explanation: A fiókod %{appeal_date}-i fellebbezése, mely a %{strike_date}-i szankcióval kapcsolatos, jóváhagyásra került. A fiókod megint makulátlan.
+      explanation: A fiókod %{appeal_date}-i fellebbezése, mely a %{strike_date}-i vétségeddel kapcsolatos, jóváhagyásra került. A fiókod megint makulátlan.
       subject: A %{date}-i fellebbezésedet jóváhagyták
       title: Fellebbezés jóváhagyva
     appeal_rejected:
-      explanation: A %{appeal_date}-i fellebbezésed, amely a fiókod %{strike_date}-i szankciójával kapcsolatos, elutasításra került.
+      explanation: A %{appeal_date}-i fellebbezésed, amely a fiókod %{strike_date}-i vétségével kapcsolatos, elutasításra került.
       subject: A %{date}-i fellebbezésedet visszautasították
       title: Fellebbezés visszautasítva
     backup_ready:
diff --git a/config/locales/hy.yml b/config/locales/hy.yml
index 5cd6d53a3..df7dd3e2e 100644
--- a/config/locales/hy.yml
+++ b/config/locales/hy.yml
@@ -5,6 +5,7 @@ hy:
     contact_missing: Սահմանված չէ
     contact_unavailable: Ոչինչ չկա
     hosted_on: Մաստոդոնը տեղակայուած է %{domain}ում
+    title: Մասին
   accounts:
     follow: Հետևել
     followers:
@@ -45,6 +46,7 @@ hy:
       confirm: Հաստատել
       confirmed: Հաստատված է
       confirming: Հաստատման սպասող
+      custom: Յատուկ
       delete: Ջնջել տվյալները
       deleted: Ջնջված է
       demote: Աստիճանազրկել
@@ -248,6 +250,8 @@ hy:
     domain_allows:
       add_new: Թոյլատրել ֆեդերացիա տիրոյթի հետ
       created_msg: Տիրոյթը յաջողութեամբ թոյլատրուեց ֆեդերացուելու
+      export: Արտահանել
+      import: Ներմուծել
       undo: Չթոյլատրել ֆեդերացիան տիրոյթի հետ
     domain_blocks:
       add_new: Աւելացնել նոր տիրոյթի արգելափակում
@@ -255,6 +259,8 @@ hy:
       destroyed_msg: Տիրոյթի արգելափակումը ետարկուեց
       domain: Տիրոյթ
       edit: Խմբագրել տիրոյթի արգելափակումը
+      export: Արտահանել
+      import: Ներմուծել
       new:
         create: Ստեղծել արգելափակում
         severity:
@@ -281,10 +287,15 @@ hy:
       status: Կարգավիճակ
       title: Խորհուրդ ենք տալիս հետեւել
     instances:
+      availability:
+        title: Հասանելի
       back_to_all: Բոլորը
       back_to_limited: Սահամանփակ
       back_to_warning: Զգուշացում
       by_domain: Դոմեն
+      content_policies:
+        policies:
+          silence: Սահմանափակ
       delivery:
         all: Բոլորը
         unavailable: Անհասանելի է
@@ -354,6 +365,7 @@ hy:
       notes:
         create: Ավելացնել նշում
         delete: Ջնջել
+        title: Նշում
       reopen: Վերաբացել բողոքը
       report: 'Բողոք #%{id}'
       reported_account: Բողոքարկուած հաշիւ
@@ -365,6 +377,14 @@ hy:
       unresolved: Չլուծուած
       updated_at: Թարմացուած
       view_profile: Նայել անձնական էջը
+    roles:
+      categories:
+        invites: Հրաւէրներ
+        moderation: Մոդերացիա
+        special: Յատուկ
+      delete: Ջնջել
+      privileges:
+        administrator: Ադմինիստրատոր
     rules:
       add_new: Աւելացնել կանոն
       delete: Ջնջել
@@ -372,6 +392,8 @@ hy:
       empty: Սերուերի կանոնները դեռեւս սահմանուած չեն։
       title: Սերուերի կանոնները
     settings:
+      about:
+        title: Մասին
       domain_blocks:
         all: Բոլորին
         disabled: Ոչ մէկին
diff --git a/config/locales/id.yml b/config/locales/id.yml
index 7e3f6e1a8..10f7e6629 100644
--- a/config/locales/id.yml
+++ b/config/locales/id.yml
@@ -114,6 +114,7 @@ id:
       reject: Tolak
       rejected_msg: Berhasil menolak permintaan pendaftaran %{username}
       remote_suspension_irreversible: Data akun ini telah dihapus permanen.
+      remote_suspension_reversible_hint_html: Akun ini telah ditangguhkan di server mereka, dan data akan dihapus total pada %{date}. Sebelum itu, server jarak jauh dapat memulihkan akun ini tanpa efek samping. Jika Anda ingin menghapus semua data akun langsung, Anda dapat mengikuti bawah ini.
       remove_avatar: Hapus avatar
       remove_header: Hapus header
       removed_avatar_msg: Berhasil menghapus gambar avatar %{username}
@@ -432,6 +433,7 @@ id:
         private_comment_description_html: 'Untuk membantu Anda melacak asal blok yang diimpor, blok yang diimpor akan dibuat dengan komentar pribadi berikut: <q>%{comment}</q>'
         private_comment_template: Diimpor dari %{source} pada %{date}
         title: Impor blok domain
+      invalid_domain_block: 'Satu atau lebih blokir domain dilewati karena kesalahan berikut: %{error}'
       new:
         title: Impor blok domain
       no_file: Tidak ada file dipilih
diff --git a/config/locales/is.yml b/config/locales/is.yml
index b9a9c2a18..e68bbb2f3 100644
--- a/config/locales/is.yml
+++ b/config/locales/is.yml
@@ -441,6 +441,7 @@ is:
         private_comment_description_html: 'Tið að aðstoða þig við að rekja hvaðan lokkanir koma, innfluttar lokanir verða búnar til með eftirfarndi athugasemd: <q>%{comment}</q>'
         private_comment_template: Flutt inn frá %{source} þann %{date}
         title: Flytja inn útilokanir léna
+      invalid_domain_block: 'Einni eða fleiri útilokunum léna var sleppt vegna eftirfarandi villu/villna: %{error}'
       new:
         title: Flytja inn útilokanir léna
       no_file: Engin skrá valin
@@ -589,6 +590,7 @@ is:
       comment:
         none: Ekkert
       comment_description_html: 'Til að gefa nánari upplýsingar skrifaði %{name}:'
+      confirm_action: Staðfesta umsjónaraðgerðir gagnvart @%{acct}
       created_at: Tilkynnt
       delete_and_resolve: Eyða færslum
       forwarded: Áframsent
@@ -605,6 +607,7 @@ is:
         placeholder: Lýstu til hvaða aðgerða hefur verið gripið eða uppfærðu inn aðrar tengdar upplýsingar...
         title: Minnispunktar
       notes_description_html: Skoðaðu og skrifaðu minnispunkta til annarra stjórnenda og sjálfs þín
+      processed_msg: 'Tókst að meðhöndla kæruna #%{id}'
       quick_actions_description_html: 'Beittu flýtiaðgerð eða skrunaðu niður til að skoða kært efni:'
       remote_user_placeholder: fjartengda notandann frá %{instance}
       reopen: Enduropna kæru
@@ -617,9 +620,28 @@ is:
       status: Staða
       statuses: Kært efni
       statuses_description_html: Óviðeigandi efni verður tiltekið í samskiptum við kærðan notandaaðgang
+      summary:
+        action_preambles:
+          delete_html: 'Þú er í þann mund að fara að <strong>fjarlægja</strong> sumar af færslunum frá <strong>@%{acct}</strong>. Þetta mun:'
+          mark_as_sensitive_html: 'Þú er í þann mund að fara að <strong>merkja</strong> sumar af færslunum frá <strong>@%{acct}</strong> sem <strong>viðkvæmar</strong>. Þetta mun:'
+          silence_html: 'Þú er í þann mund að fara að <strong>takmarka aðganginn</strong> <strong>@%{acct}</strong>. Þetta mun:'
+          suspend_html: 'Þú er í þann mund að fara að <strong>frysta</strong> aðganginn hjá <strong>@%{acct}</strong>. Þetta mun:'
+        actions:
+          delete_html: Fjarlægja viðkomandi færslur
+          mark_as_sensitive_html: Merkja myndefni í viðkomandi færslum sem viðkvæmt
+          silence_html: Takmarka verulega umfangið hjá <strong>@%{acct}</strong> með því að gera notandasniðið og efni þess einungis sýnilegt fólki sem þegar fylgist með viðkomandi eða þeim sem fletta handvirkt upp upplýsingunum
+          suspend_html: Setja <strong>@%{acct}</strong> í frysti, gera notandasniðið og efni þess óaðgengilegt án mögulegrar gagnvirkni
+        close_report: 'Merkja kæruna #%{id} sem leysta'
+        close_reports_html: Merkja <strong>allar</strong> kærur gagnavart <strong>@%{acct}</strong> sem leystar
+        delete_data_html: Eyða notandasniði <strong>@%{acct}</strong> og efni þess eftir 30 daga nema viðkomandi verði tekinn úr frysti í millitíðinni
+        preview_preamble_html: "<strong>@%{acct}</strong> mun fá aðvörun með eftirfarandi texta:"
+        record_strike_html: Skrá refsingu gagnvart <strong>@%{acct}</strong> til að geta betur átt við brot frá þessum aðgangi í framtíðinni
+        send_email_html: Senda <strong>@%{acct}</strong> aðvörun í tölvupósti
+        warning_placeholder: Valkvæðar aðrar ástæður fyrir umsjónaraðgerðum.
       target_origin: Uppruni kærða notandaaðgangsins
       title: Kærur
       unassign: Aftengja úthlutun
+      unknown_action_msg: 'Óþekkt aðgerð: %{action}'
       unresolved: Óleyst
       updated_at: Uppfært
       view_profile: Skoða notandasnið
@@ -943,6 +965,8 @@ is:
   auth:
     apply_for_account: Biðja um notandaaðgang
     change_password: Lykilorð
+    confirmations:
+      wrong_email_hint: Ef það tölvupóstfang er ekki rétt geturðu breytt því í stillingum notandaaðgangsins.
     delete_account: Eyða notandaaðgangi
     delete_account_html: Ef þú vilt eyða notandaaðgangnum þínum, þá geturðu <a href="%{path}">farið í það hér</a>. Þú verður beðin/n um staðfestingu.
     description:
diff --git a/config/locales/it.yml b/config/locales/it.yml
index 17e1bca24..d0c7168af 100644
--- a/config/locales/it.yml
+++ b/config/locales/it.yml
@@ -142,8 +142,8 @@ it:
       show:
         created_reports: Rapporti creati
         targeted_reports: Segnalato da altri
-      silence: Limita
-      silenced: Limitato
+      silence: Silenzia
+      silenced: Silenziato
       statuses: Toot
       strikes: Provvedimenti precedenti
       subscribe: Iscriviti
@@ -156,7 +156,7 @@ it:
       unblocked_email_msg: Indirizzo email di %{username} sbloccato correttamente
       unconfirmed_email: Email non confermata
       undo_sensitized: Annulla sensibile
-      undo_silenced: Annulla limitazione
+      undo_silenced: Rimuovi silenzia
       undo_suspension: Annulla sospensione
       unsilenced_msg: Limitazione del profilo di %{username} annullata correttamente
       unsubscribe: Disiscriviti
@@ -441,6 +441,7 @@ it:
         private_comment_description_html: 'Per aiutarti a tenere traccia della provenienza dei blocchi importati, i blocchi importati verranno creati con il seguente commento privato: <q>%{comment}</q>'
         private_comment_template: Importato da %{source} il %{date}
         title: Importare i blocchi di dominio
+      invalid_domain_block: 'Uno o più blocchi di dominio sono stati saltati a causa dei seguenti errori: %{error}'
       new:
         title: Importare i blocchi di dominio
       no_file: Nessun file selezionato
@@ -589,6 +590,7 @@ it:
       comment:
         none: Nessuno
       comment_description_html: 'Per fornire ulteriori informazioni, %{name} ha scritto:'
+      confirm_action: Conferma l'azione di moderazione contro @%{acct}
       created_at: Segnalato
       delete_and_resolve: Cancella post
       forwarded: Inoltrato
@@ -605,6 +607,7 @@ it:
         placeholder: Descrivi quali azioni sono state intraprese, o ogni altro aggiornamento rilevante...
         title: Note
       notes_description_html: Visualizza e lascia note ad altri moderatori e al tuo futuro sé
+      processed_msg: 'Segnalazione #%{id} elaborata correttamente'
       quick_actions_description_html: 'Fai un''azione rapida o scorri verso il basso per vedere il contenuto segnalato:'
       remote_user_placeholder: l'utente remoto da %{instance}
       reopen: Riapri rapporto
@@ -617,9 +620,28 @@ it:
       status: Stato
       statuses: Contenuto segnalato
       statuses_description_html: Il contenuto offensivo sarà citato nella comunicazione con l'account segnalato
+      summary:
+        action_preambles:
+          delete_html: 'Stai per <strong>rimuovere</strong> alcuni post di <strong>@%{acct}</strong>. Questo conseguirà:'
+          mark_as_sensitive_html: 'Stai per <strong>contrassegnare</strong> alcuni post di <strong>@%{acct}</strong> come <strong>sensibili</strong>. Questo conseguirà:'
+          silence_html: 'Stai per <strong>limitare</strong> l''account di <strong>@%{acct}</strong>. Questo conseguirà:'
+          suspend_html: 'Stai per <strong>sospendere</strong> l''account di <strong>@%{acct}</strong>. Questo conseguirà:'
+        actions:
+          delete_html: Rimuovi i post offensivi
+          mark_as_sensitive_html: Contrassegna i file multimediali dei post offensivi come sensibili
+          silence_html: Limita severamente la portata di <strong>@%{acct}</strong> rendendo il suo profilo e il suo contenuto visibili solo a persone che già li seguono o che lo guardano manualmente
+          suspend_html: Sospendere <strong>@%{acct}</strong>, rendendo il suo profilo e i suoi contenuti inaccessibili e impossibilitandone l'interazione
+        close_report: 'Contrassegna la segnalazione #%{id} come risolta'
+        close_reports_html: Contrassegna <strong>tutte</strong> le segnalazioni contro <strong>@%{acct}</strong> come risolte
+        delete_data_html: Elimina il profilo e i contenuti di <strong>@%{acct}</strong> tra 30 giorni da ora, a meno che non vengano riattivati nel frattempo
+        preview_preamble_html: "<strong>@%{acct}</strong> riceverà un avvertimento con i seguenti contenuti:"
+        record_strike_html: Registra un provvedimento contro <strong>@%{acct}</strong> per aiutarti a gestire meglio future violazioni da questo account
+        send_email_html: Invia a <strong>@%{acct}</strong> una e-mail di avvertimento
+        warning_placeholder: Motivazione aggiuntiva facoltativa per l'azione di moderazione.
       target_origin: Origine dell'account segnalato
       title: Rapporti
       unassign: Non assegnare
+      unknown_action_msg: 'Azione sconosciuta: %{action}'
       unresolved: Non risolto
       updated_at: Aggiornato
       view_profile: Visualizza profilo
@@ -945,6 +967,8 @@ it:
   auth:
     apply_for_account: Richiedi un account
     change_password: Password
+    confirmations:
+      wrong_email_hint: Se l'indirizzo e-mail non è corretto, puoi modificarlo nelle impostazioni dell'account.
     delete_account: Elimina account
     delete_account_html: Se desideri cancellare il tuo account, puoi <a href="%{path}">farlo qui</a>. Ti sarà chiesta conferma.
     description:
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 684348ce9..4f2f820a7 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -433,6 +433,7 @@ ja:
         private_comment_description_html: 'ブロックのインポート元を判別できるようにするため、ブロックは次のプライベートコメントを追加してインポートされます: <q>%{comment}</q>'
         private_comment_template: "%{source} から %{date} にインポートしました"
         title: ドメインブロックをインポート
+      invalid_domain_block: '次のエラーのため、1つ以上のドメインブロックがスキップされました: %{error}'
       new:
         title: ドメインブロックをインポート
       no_file: ファイルが選択されていません
@@ -577,6 +578,7 @@ ja:
       comment:
         none: なし
       comment_description_html: "%{name}からの詳細情報:"
+      confirm_action: "@%{acct} さんに対するアクション"
       created_at: 通報日時
       delete_and_resolve: 投稿を削除
       forwarded: 転送済み
@@ -593,6 +595,7 @@ ja:
         placeholder: どのような措置が取られたか、または関連する更新を記述してください…
         title: メモ
       notes_description_html: 他のモデレーターと将来の自分にメモを残してください
+      processed_msg: '通報 #%{id} が正常に処理されました'
       quick_actions_description_html: 'クイックアクションを実行するかスクロールして報告された通報を確認してください:'
       remote_user_placeholder: "%{instance}からのリモートユーザー"
       reopen: 未解決に戻す
@@ -605,9 +608,28 @@ ja:
       status: ステータス
       statuses: 通報内容
       statuses_description_html: 問題の投稿は通報されたアカウントへの連絡時に引用されます
+      summary:
+        action_preambles:
+          delete_html: "<strong>@%{acct}</strong>さんの投稿を<strong>削除</strong>します。この操作は:"
+          mark_as_sensitive_html: "<strong>@%{acct}</strong>さんの投稿を<strong>閲覧注意</strong>として<strong>マーク</strong>します。この操作は:"
+          silence_html: "<strong>@%{acct}</strong>さんのアカウントを<strong>制限</strong>します。この操作は:"
+          suspend_html: "<strong>@%{acct}</strong>さんのアカウントを<strong>停止</strong>します。この操作は:"
+        actions:
+          delete_html: 当該の投稿を削除します
+          mark_as_sensitive_html: 当該の投稿に含まれるメディアを閲覧注意にします
+          silence_html: プロフィールとコンテンツを、すでにフォローしている人や、意図的にプロフィールにアクセスする人にのみ表示することで、<strong>@%{acct}</strong>さんのリーチを厳しく制限します
+          suspend_html: "<strong>@%{acct}</strong>さんのアカウントが凍結され、プロフィールとコンテンツへのアクセス、および投稿ができなくなります"
+        close_report: '通報 #%{id} を解決済みにします'
+        close_reports_html: "<strong>@%{acct}</strong>さんに対する<strong>すべての</strong>通報を解決済みにします"
+        delete_data_html: 停止が解除されないまま30日経過すると、<strong>@%{acct}</strong>さんのプロフィールとコンテンツは削除されます
+        preview_preamble_html: "<strong>@%{acct}</strong>さんに次の内容の警告を通知します:"
+        record_strike_html: 今後、<strong>@%{acct}</strong>さんが違反行為をしたときにエスカレーションできるように、このアカウントに対するストライクを記録します
+        send_email_html: "<strong>@%{acct}</strong>さんに警告メールを送信します"
+        warning_placeholder: アクションを行使する追加の理由(オプション)
       target_origin: 報告されたアカウントの起源
       title: 通報
       unassign: 担当を外す
+      unknown_action_msg: '不明なアクションです: %{action}'
       unresolved: 未解決
       updated_at: 更新日時
       view_profile: プロフィールを表示
@@ -925,6 +947,8 @@ ja:
   auth:
     apply_for_account: アカウントのリクエスト
     change_password: パスワード
+    confirmations:
+      wrong_email_hint: メールアドレスが正しくない場合は、アカウント設定で変更できます。
     delete_account: アカウントの削除
     delete_account_html: アカウントを削除したい場合、<a href="%{path}">こちら</a>から手続きが行えます。削除する前に、確認画面があります。
     description:
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index d02227dec..557e499f3 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -25,9 +25,9 @@ ko:
       action: 조치 취하기
       title: "%{acct} 계정에 중재 취하기"
     account_moderation_notes:
-      create: 중재 기록 작성하기
-      created_msg: 중재 기록이 성공적으로 작성되었습니다!
-      destroyed_msg: 중재 기록이 성공적으로 삭제되었습니다!
+      create: 참고사항 남기기
+      created_msg: 중재 참고사항을 만들었습니다!
+      destroyed_msg: 중재 참고사항을 지웠습니다!
     accounts:
       add_email_domain_block: 이 이메일 도메인을 차단하기
       approve: 허가
@@ -69,7 +69,7 @@ ko:
       enabled: 활성
       enabled_msg: "%{username}의 계정을 성공적으로 얼리기 해제하였습니다"
       followers: 팔로워
-      follows: 팔로잉
+      follows: 팔로우
       header: 헤더
       inbox_url: 수신함 URL
       invite_request_text: 가입 하려는 이유
@@ -93,14 +93,14 @@ ko:
         silenced: 제한됨
         suspended: 정지 중
         title: 중재
-      moderation_notes: 중재 기록
+      moderation_notes: 중재 참고사항
       most_recent_activity: 최근 활동
       most_recent_ip: 최근 IP
       no_account_selected: 아무 것도 선택 되지 않아 어떤 계정도 변경 되지 않았습니다
       no_limits_imposed: 제한 없음
       no_role_assigned: 할당된 역할 없음
       not_subscribed: 구독하지 않음
-      pending: 심사 대기
+      pending: 계류된 검토
       perform_full_suspension: 정지
       previous_strikes: 이전의 처벌들
       previous_strikes_description_html:
@@ -139,8 +139,8 @@ ko:
       show:
         created_reports: 이 계정에서 제출된 신고
         targeted_reports: 이 계정에 대한 신고
-      silence: 침묵
-      silenced: 침묵 됨
+      silence: 제한
+      silenced: 제한됨
       statuses: 게시물
       strikes: 이전의 처벌들
       subscribe: 구독하기
@@ -153,7 +153,7 @@ ko:
       unblocked_email_msg: "%{username}의 이메일 주소를 성공적으로 차단 해제했습니다"
       unconfirmed_email: 확인되지 않은 이메일 주소
       undo_sensitized: 민감함으로 설정 취소
-      undo_silenced: 침묵 해제
+      undo_silenced: 제한 해제
       undo_suspension: 정지 해제
       unsilenced_msg: 성공적으로 %{username} 계정을 제한 해제했습니다
       unsubscribe: 구독 해제
@@ -210,12 +210,12 @@ ko:
         reset_password_user: 암호 재설정
         resolve_report: 신고 처리
         sensitive_account: 당신의 계정의 미디어를 민감함으로 표시
-        silence_account: 계정 침묵
+        silence_account: 계정 제한
         suspend_account: 계정 정지
         unassigned_report: 신고 맡기 취소
         unblock_email_account: 이메일 주소 차단 해제
         unsensitive_account: 당신의 계정의 미디어를 민감함으로 표시하지 않음
-        unsilence_account: 계정 침묵 취소
+        unsilence_account: 계정 제한 취소
         unsuspend_account: 계정 정지 취소
         update_announcement: 공지사항 업데이트
         update_custom_emoji: 커스텀 에모지 업데이트
@@ -269,12 +269,12 @@ ko:
         reset_password_user_html: "%{name} 님이 사용자 %{target}의 암호를 초기화했습니다"
         resolve_report_html: "%{name} 님이 신고 %{target}를 처리됨으로 변경하였습니다"
         sensitive_account_html: "%{name} 님이 %{target}의 미디어를 민감함으로 표시했습니다"
-        silence_account_html: "%{name} 님이 %{target}의 계정을 침묵시켰습니다"
+        silence_account_html: "%{name} 님이 %{target}의 계정을 제한시켰습니다"
         suspend_account_html: "%{name} 님이 %{target}의 계정을 정지시켰습니다"
         unassigned_report_html: "%{name} 님이 신고 %{target}을 할당 해제했습니다"
         unblock_email_account_html: "%{name} 님이 %{target}의 이메일 주소를 차단 해제했습니다"
         unsensitive_account_html: "%{name} 님이 %{target}의 미디어를 민감하지 않음으로 표시했습니다"
-        unsilence_account_html: "%{name} 님이 %{target}의 계정에 대한 침묵을 해제했습니다"
+        unsilence_account_html: "%{name} 님이 %{target}의 계정에 대한 제한을 해제했습니다"
         unsuspend_account_html: "%{name} 님이 %{target}의 계정에 대한 정지를 해제했습니다"
         update_announcement_html: "%{name} 님이 공지사항 %{target}을 갱신했습니다"
         update_custom_emoji_html: "%{name} 님이 에모지 %{target}를 업데이트 했습니다"
@@ -297,7 +297,7 @@ ko:
         create: 공지사항 생성
         title: 새 공지사항
       publish: 게시
-      published_msg: 공지가 성공적으로 발행되었습니다!
+      published_msg: 공지사항을 잘 발행했습니다!
       scheduled_for: "%{time}에 예약됨"
       scheduled_msg: 공지의 발행이 예약되었습니다!
       title: 공지사항
@@ -435,6 +435,7 @@ ko:
         private_comment_description_html: '어디서 불러온 것인지 추적을 원활하게 하기 위해서, 불러온 차단들은 다음과 같은 비공개 주석과 함께 생성될 것입니다: <q>%{comment}</q>'
         private_comment_template: "%{date}에 %{source}에서 불러옴"
         title: 도메인 차단 불러오기
+      invalid_domain_block: '한 개 이상의 도메인 차단이 생략되었습니다. 에러는 다음과 같습니다: %{error}'
       new:
         title: 도메인 차단 불러오기
       no_file: 선택된 파일이 없습니다
@@ -466,7 +467,7 @@ ko:
         description_html: 이 도메인과 하위 도메인의 모든 계정에 적용될 콘텐츠 정책을 정의할 수 있습니다.
         policies:
           reject_media: 미디어 거부
-          reject_reports: 신고 거부
+          reject_reports: 신고 반려
           silence: 제한
           suspend: 정지
         policy: 정책
@@ -557,7 +558,7 @@ ko:
     reports:
       account:
         notes:
-          other: "%{count}개의 기록"
+          other: "%{count}개의 참고사항"
       action_log: 감사 기록
       action_taken_by: 신고 처리자
       actions:
@@ -579,7 +580,8 @@ ko:
       comment:
         none: 없음
       comment_description_html: '더 많은 정보를 위해, %{name} 님이 작성했습니다:'
-      created_at: 리포트 시각
+      confirm_action: "@%{acct}에 취할 중재 결정에 대한 확인"
+      created_at: 신고 시각
       delete_and_resolve: 게시물 삭제
       forwarded: 전달됨
       forwarded_to: "%{domain}에게 전달됨"
@@ -589,27 +591,47 @@ ko:
       no_one_assigned: 아무도 없음
       notes:
         create: 기록 추가
-        create_and_resolve: 기록을 작성하고 해결됨으로 표시
-        create_and_unresolve: 기록 작성과 함께 미해결로 표시
+        create_and_resolve: 종결 및 참고사항 기재
+        create_and_unresolve: 재검토 및 참고사항 기재
         delete: 삭제
-        placeholder: 이 리포트에 대한 조치, 기타 관련 된 사항에 대해 설명합니다…
-        title: 기록
+        placeholder: 어떤 대응을 했는지 서설 또는 그 밖의 관련된 갱신 사항들
+        title: 참고사항
       notes_description_html: 확인하고 다른 중재자나 미래의 자신을 위해 기록을 작성합니다
+      processed_msg: '신고 #%{id}가 정상적으로 처리되었습니다'
       quick_actions_description_html: '빠른 조치를 취하거나 아래로 스크롤해서 신고된 콘텐츠를 확인하세요:'
       remote_user_placeholder: "%{instance}의 리모트 사용자"
-      reopen: 리포트 다시 열기
+      reopen: 신고 재검토
       report: '신고 #%{id}'
       reported_account: 신고 대상 계정
       reported_by: 신고자
       resolved: 해결됨
-      resolved_msg: 리포트가 성공적으로 해결되었습니다!
+      resolved_msg: 신고를 잘 해결했습니다!
       skip_to_actions: 작업으로 건너뛰기
       status: 상태
       statuses: 신고된 콘텐츠
       statuses_description_html: 문제가 되는 콘텐츠는 신고된 계정에게 인용되어 전달됩니다
+      summary:
+        action_preambles:
+          delete_html: "<strong>@%{acct}</strong>의 게시물 중 일부를 <strong>지우려고</strong> 합니다. 이것은 아래의 행동이 수반됩니다:"
+          mark_as_sensitive_html: "<strong>@%{acct}</strong>의 게시물 중 일부를 <strong>민감함으로 표시</strong> 합니다. 이것은 아래의 행동이 수반됩니다:"
+          silence_html: "<strong>@%{acct}</strong>의 계정을 <strong>제한</strong>하려고 합니다. 이것은 아래의 행동이 수반됩니다:"
+          suspend_html: "<strong>@%{acct}</strong>의 계정을 <strong>정지</strong>하려고 합니다. 이것은 아래의 행동이 수반됩니다:"
+        actions:
+          delete_html: 문제가 되는 게시물을 지웁니다
+          mark_as_sensitive_html: 문제가 되는 게시물의 미디어를 민감함으로 표시합니다
+          silence_html: 이미 팔로우하고 있는 사람에게만 프로필을 보이게 하고 나머지 사람들에게는 수동으로 확인을 해야만 볼 수 있게 하여 <strong>@%{acct}</strong>의 도달 범위를 엄격하게 제한합니다.
+          suspend_html: "<strong>@%{acct}</strong>의 계정을 정지합니다. 프로필과 콘텐츠가 사용 불가능하게 됩니다."
+        close_report: '신고 #%{id}를 해결됨으로 표시합니다'
+        close_reports_html: "<strong>@%{acct}</strong>에 대한 <strong>모든</strong> 신고를 해결됨으로 처리합니다"
+        delete_data_html: "<strong>@%{acct}</strong>의 프로필과 콘텐츠를 30일의 유예기간 이후에 삭제합니다"
+        preview_preamble_html: "<strong>@%{acct}</strong>는 다음 내용의 경고를 받게 됩니다:"
+        record_strike_html: 향후 규칙위반에 대한 참고사항이 될 수 있도록 <strong>@%{acct}</strong>에 대한 처벌기록을 남깁니다
+        send_email_html: "<strong>@%{acct}</strong>에게 경고 메일을 보냅니다"
+        warning_placeholder: 중재 결정에 대한 추가적인 이유.
       target_origin: 신고된 계정의 소속
       title: 신고
       unassign: 할당 해제
+      unknown_action_msg: '알 수 없는 액션: %{action}'
       unresolved: 미해결
       updated_at: 업데이트 시각
       view_profile: 프로필 보기
@@ -772,7 +794,7 @@ ko:
       sidekiq_process_check:
         message_html: "%{value} 큐에 대한 사이드킥 프로세스가 발견되지 않았습니다. 사이드킥 설정을 검토해주세요"
     tags:
-      review: 심사 상태
+      review: 검토 상태
       updated_msg: 해시태그 설정이 성공적으로 갱신되었습니다
     title: 관리
     trends:
@@ -793,7 +815,7 @@ ko:
         title: 유행하는 링크
         usage_comparison: 오늘은 %{today}회 공유되었고, 어제는 %{yesterday}회 공유되었습니다
       only_allowed: 허용된 것만
-      pending_review: 심사 대기
+      pending_review: 계류된 검토
       preview_card_providers:
         allowed: 이 출처의 링크는 유행 목록에 실릴 수 있습니다
         description_html: 당신의 서버에서 많은 링크가 공유되고 있는 도메인들입니다. 링크의 도메인이 승인되기 전까지는 링크들은 공개적으로 트렌드에 게시되지 않습니다. 당신의 승인(또는 거절)은 서브도메인까지 확장됩니다.
@@ -830,7 +852,7 @@ ko:
         trendable: 유행 목록에 나타날 수 있습니다
         trending_rank: "#%{rank}위로 유행 중"
         usable: 사용 가능
-        usage_comparison: 오늘은 %{today}회 사용되었고, 어제는 %{yesterday}회 사용되었습니다
+        usage_comparison: 오늘은 %{today}회 쓰였고, 어제는 %{yesterday}회 쓰임
         used_by_over_week:
           other: 지난 주 동안 %{count} 명의 사람들이 사용했습니다
       title: 유행
@@ -875,7 +897,7 @@ ko:
       subject: "%{username} 님이 %{instance}에서 발생한 중재 결정에 대해 소명을 제출했습니다"
     new_pending_account:
       body: 아래에 새 계정에 대한 상세정보가 있습니다. 이 가입을 승인하거나 거부할 수 있습니다.
-      subject: "%{instance}의 새 계정(%{username})에 대한 심사가 대기중입니다"
+      subject: "%{instance}의 새 계정(%{username}) 검토"
     new_report:
       body: "%{reporter} 님이 %{target}를 신고했습니다"
       body_remote: "%{domain}의 누군가가 %{target}을 신고했습니다"
@@ -890,7 +912,7 @@ ko:
         no_approved_tags: 현재 승인된 유행 중인 해시태그가 없습니다.
         requirements: '이 후보들 중 어떤 것이라도 #%{rank}위의 승인된 유행 중인 해시태그를 앞지를 수 있으며, 이것은 현재 #%{lowest_tag_name}이고 %{lowest_tag_score}점을 기록하고 있습니다.'
         title: 유행하는 해시태그
-      subject: 새 트렌드가 %{instance}에서 심사 대기 중입니다
+      subject: "%{instance}의 새 유행물 검토"
   aliases:
     add_new: 별칭 만들기
     created_msg: 새 별칭이 성공적으로 만들어졌습니다. 이제 기존 계정에서 이주를 시작할 수 있습니다.
@@ -926,16 +948,18 @@ ko:
     your_token: 액세스 토큰
   auth:
     apply_for_account: 가입 요청하기
-    change_password: 패스워드
+    change_password: 암호
+    confirmations:
+      wrong_email_hint: 만약 이메일 주소가 올바르지 않다면, 계정 설정에서 수정할 수 있습니다.
     delete_account: 계정 삭제
     delete_account_html: 계정을 삭제하고 싶은 경우, <a href="%{path}">여기서</a> 삭제할 수 있습니다. 삭제 전 확인 화면이 표시됩니다.
     description:
-      prefix_invited_by_user: "@%{name} 님이 당신을 이 마스토돈 서버로 초대했습니다!"
+      prefix_invited_by_user: "@%{name}님이 마스토돈 서버에 초대했습니다!"
       prefix_sign_up: 마스토돈에 가입하세요!
       suffix: 계정 하나로 사람들을 팔로우 하고, 게시물을 작성하며 마스토돈을 포함한 다른 어떤 서버의 사용자와도 메시지를 주고 받을 수 있습니다!
     didnt_get_confirmation: 확인 메일을 받지 못하셨습니까?
     dont_have_your_security_key: 보안 키가 없습니까?
-    forgot_password: 비밀번호를 잊어버리셨습니까?
+    forgot_password: 암호를 잊었나요?
     invalid_reset_password_token: 암호 리셋 토큰이 올바르지 못하거나 기간이 만료되었습니다. 다시 요청해주세요.
     link_to_otp: 휴대폰의 2차 코드 혹은 복구 키를 입력해 주세요
     link_to_webauth: 보안 키 장치 사용
@@ -985,7 +1009,7 @@ ko:
     follow_request: '당신은 다음 계정에 팔로우 신청을 했습니다:'
     following: '성공! 당신은 다음 계정을 팔로우 하고 있습니다:'
     post_follow:
-      close: 혹은, 당신은 이 윈도우를 닫을 수 있습니다.
+      close: 또한, 그저 이 창을 닫을 수도 있습니다.
       return: 사용자 프로필 보기
       web: 웹으로 가기
     title: "%{acct} 를 팔로우"
@@ -1023,7 +1047,7 @@ ko:
     proceed: 계정 삭제
     success_msg: 계정이 성공적으로 삭제되었습니다
     warning:
-      before: '진행하기 전, 주의사항을 꼼꼼히 읽어보세요:'
+      before: '진행 전, 참고사항을 주의 깊게 읽기 바랍니다:'
       caches: 다른 서버에 캐싱된 정보들은 남아있을 수 있습니다
       data_removal: 당신의 게시물과 다른 정보들은 영구적으로 삭제 됩니다
       email_change_html: 계정을 지우지 않고도 <a href="%{path}">이메일 주소를 수정할 수 있습니다</a>
@@ -1194,7 +1218,7 @@ ko:
       '86400': 하루
     expires_in_prompt: 영원히
     generate: 생성
-    invited_by: '당신을 초대한 사람:'
+    invited_by: '초대자:'
     max_uses:
       other: "%{count}회"
     max_uses_prompt: 제한 없음
@@ -1246,7 +1270,7 @@ ko:
     set_redirect: 리디렉션 설정
     warning:
       backreference_required: 새 계정은 이 계정으로 역참조를 하도록 설정되어 있어야 합니다
-      before: '진행하기 전, 주의사항을 꼼꼼히 읽어보세요:'
+      before: '진행 전, 참고사항을 주의 깊게 읽기 바랍니다:'
       cooldown: 이주 뒤에는 새로운 이주를 하지 못하는 휴식기간이 존재합니다
       disabled_account: 이 계정은 완전한 사용이 불가능하게 됩니다. 하지만, 데이터 내보내기나 재활성화를 위해 접근할 수 있습니다.
       followers: 이 행동은 현재 계정의 모든 팔로워를 새 계정으로 이동시킵니다
@@ -1258,7 +1282,7 @@ ko:
   move_handler:
     carry_blocks_over_text: 이 사용자는 당신이 차단한 %{acct}로부터 이주 했습니다.
     carry_mutes_over_text: 이 사용자는 당신이 뮤트한 %{acct}로부터 이주 했습니다.
-    copy_account_note_text: '이 사용자는 %{acct}에서 옮겨왔으며 이전의 기록은 다음과 같습니다:'
+    copy_account_note_text: '이 사용자는 %{acct}에서 옮겨왔으며 이전의 참고사항은 다음과 같습니다:'
   navigation:
     toggle_menu: 토글 메뉴
   notification_mailer:
diff --git a/config/locales/lv.yml b/config/locales/lv.yml
index 3ea81f1f9..31e1ba77f 100644
--- a/config/locales/lv.yml
+++ b/config/locales/lv.yml
@@ -449,6 +449,7 @@ lv:
         private_comment_description_html: 'Lai palīdzētu tev izsekot, no kurienes nāk importētie bloki, tiks izveidoti importētie bloki ar šādu privātu komentāru: <q>%{comment}</q>'
         private_comment_template: Importēts no %{source} %{date}
         title: Importēt bloķētos domēnus
+      invalid_domain_block: 'Viens vai vairāki domēna bloķi tika izlaisti šādas kļūdas(-u) dēļ: %{error}'
       new:
         title: Importēt bloķētos domēnus
       no_file: Nav atlasīts neviens fails
@@ -601,6 +602,7 @@ lv:
       comment:
         none: Neviens
       comment_description_html: 'Lai sniegtu vairāk informācijas, %{name} rakstīja:'
+      confirm_action: Apstipriniet regulēšanas darbību pret @%{acct}
       created_at: Ziņoti
       delete_and_resolve: Izdzēst rakstus
       forwarded: Pārsūtīti
@@ -617,6 +619,7 @@ lv:
         placeholder: Apraksti veiktās darbības vai citus saistītus atjauninājumus...
         title: Piezīmes
       notes_description_html: Skati un atstāj piezīmes citiem moderatoriem un sev nākotnei
+      processed_msg: 'Pārskats #%{id} veiksmīgi apstrādāts'
       quick_actions_description_html: 'Veic ātro darbību vai ritini uz leju, lai skatītu saturu, par kuru ziņots:'
       remote_user_placeholder: attālais lietotājs no %{instance}
       reopen: Atkārtoti atvērt ziņojumu
@@ -629,9 +632,28 @@ lv:
       status: Statuss
       statuses: Ziņotais saturs
       statuses_description_html: Pārkāpuma saturs tiks minēts saziņā ar paziņoto kontu
+      summary:
+        action_preambles:
+          delete_html: 'Jūs gatavojaties <strong>noņemt</strong> dažas no lietotāja <strong>@%{acct}</strong> ziņām. Tas:'
+          mark_as_sensitive_html: 'Jūs gatavojaties <strong>atzīmēt</strong> dažas no lietotāja <strong>@%{acct}</strong> ziņām kā <strong>sensitīvas</strong>. Tas:'
+          silence_html: 'Jūs gatavojaties <strong>ierobežot</strong> <strong>@%{acct}</strong> kontu. Tas:'
+          suspend_html: 'Jūs gatavojaties <strong>apturēt</strong> <strong>@%{acct}</strong> kontu. Tas:'
+        actions:
+          delete_html: Noņemt aizskarošās ziņas
+          mark_as_sensitive_html: Atzīmēt aizskarošo ziņu multivides saturu kā sensitīvu
+          silence_html: Ievērojami ierobežojiet <strong>@%{acct}</strong> sasniedzamību, padarot viņa profilu un saturu redzamu tikai personām, kas jau seko viņiem vai manuāli meklē profilu
+          suspend_html: Apturēt <strong>@%{acct}</strong>, padarot viņu profilu un saturu nepieejamu un neiespējamu mijiedarbību ar
+        close_report: 'Atzīmēt ziņojumu #%{id} kā atrisinātu'
+        close_reports_html: Atzīmējiet <strong>visus</strong> pārskatus par <strong>@%{acct}</strong> kā atrisinātus
+        delete_data_html: Dzēsiet lietotāja <strong>@%{acct}</strong> profilu un saturu pēc 30 dienām, ja vien to darbība pa šo laiku netiks atcelta
+        preview_preamble_html: "<strong>@%{acct}</strong> saņems brīdinājumu ar šādu saturu:"
+        record_strike_html: Ierakstiet brīdinājumu pret <strong>@%{acct}</strong>, lai palīdzētu jums izvērst turpmākus pārkāpumus no šī konta
+        send_email_html: Nosūtiet <strong>@%{acct}</strong> brīdinājuma e-pastu
+        warning_placeholder: Izvēles papildu pamatojums regulēšanas darbībai.
       target_origin: Ziņotā konta izcelsme
       title: Ziņojumi
       unassign: Atsaukt
+      unknown_action_msg: 'Nezināms konts: %{action}'
       unresolved: Neatrisinātie
       updated_at: Atjaunināts
       view_profile: Skatīt profilu
@@ -961,6 +983,8 @@ lv:
   auth:
     apply_for_account: Pieprasīt kontu
     change_password: Parole
+    confirmations:
+      wrong_email_hint: Ja šī e-pasta adrese nav pareiza, varat to mainīt konta iestatījumos.
     delete_account: Dzēst kontu
     delete_account_html: Ja vēlies dzēst savu kontu, tu vari <a href="%{path}">turpināt šeit</a>. Tev tiks lūgts apstiprinājums.
     description:
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 4bff5c851..d293f7539 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -441,6 +441,7 @@ nl:
         private_comment_description_html: 'Om je te helpen bijhouden waar de geïmporteerde blokkades vandaan komen, worden de geïmporteerde blokkades met de volgende privé-opmerking aangemaakt: <q>%{comment}</q>'
         private_comment_template: Geïmporteerd van %{source} op %{date}
         title: Domeinblokkades importeren
+      invalid_domain_block: 'Een of meer domeinblokkades zijn overgeslagen vanwege de volgende fout(en): %{error}'
       new:
         title: Domeinblokkades importeren
       no_file: Geen bestand geselecteerd
@@ -575,7 +576,10 @@ nl:
         mark_as_sensitive_description_html: De media in de gerapporteerde berichten worden gemarkeerd als gevoelig en er wordt een overtreding geregistreerd om toekomstige overtredingen van hetzelfde account sneller af te kunnen handelen.
         other_description_html: Bekijk meer opties voor het controleren van het gedrag van en de communicatie met het gerapporteerde account.
         resolve_description_html: Er wordt tegen het gerapporteerde account geen maatregel genomen, geen overtreding geregistreerd en de rapportage wordt gemarkeerd als opgelost.
+        silence_description_html: Het account is alleen zichtbaar voor degenen die het al volgen of handmatig opzoeken, waardoor het bereik ernstig wordt beperkt. Dit kan altijd ongedaan worden gemaakt. Dit sluit alle rapporten tegen dit account af.
+        suspend_description_html: Het account en al zijn inhoud zullen niet toegankelijk zijn en uiteindelijk verwijderd worden en er zal geen interactie met het account mogelijk zijn. Dit is omkeerbaar binnen 30 dagen. Dit sluit alle rapporten tegen dit account af.
       actions_description_html: Beslis welke maatregel moet worden genomen om deze rapportage op te lossen. Wanneer je een (straf)maatregel tegen het gerapporteerde account neemt, krijgt het account een e-mailmelding, behalve wanneer de <strong>spam</strong>-categorie is gekozen.
+      actions_description_remote_html: Beslis welke actie moet worden ondernomen om deze rapportage op te lossen. Dit is alleen van invloed op hoe <strong>jouw</strong> server met dit externe account communiceert en de inhoud ervan beheert.
       add_to_report: Meer aan de rapportage toevoegen
       are_you_sure: Weet je het zeker?
       assign_to_self: Aan mij toewijzen
@@ -586,6 +590,7 @@ nl:
       comment:
         none: Geen
       comment_description_html: 'Om meer informatie te verstrekken, schreef %{name}:'
+      confirm_action: Bevestig moderatiemaatregel tegen @%{acct}
       created_at: Gerapporteerd op
       delete_and_resolve: Bericht verwijderen
       forwarded: Doorgestuurd
@@ -602,6 +607,7 @@ nl:
         placeholder: Beschrijf welke maatregelen zijn genomen of andere gerelateerde opmerkingen...
         title: Opmerkingen
       notes_description_html: Bekijk en laat opmerkingen achter voor andere moderatoren en voor jouw toekomstige zelf
+      processed_msg: 'Rapportage #%{id} succesvol afgehandeld'
       quick_actions_description_html: 'Neem een snelle maatregel of scroll naar beneden om de gerapporteerde inhoud te bekijken:'
       remote_user_placeholder: de externe gebruiker van %{instance}
       reopen: Rapportage heropenen
@@ -614,9 +620,28 @@ nl:
       status: Rapportages
       statuses: Gerapporteerde inhoud
       statuses_description_html: De problematische inhoud wordt aan het gerapporteerde account medegedeeld
+      summary:
+        action_preambles:
+          delete_html: 'Je staat op het punt om enkele berichten van <strong>@%{acct}</strong> te <strong>verwijderen</strong>. Dit zal:'
+          mark_as_sensitive_html: 'Je staat op het punt om enkele berichten van <strong>@%{acct}</strong> als <strong>gevoelig te markeren</strong>. Dit zal:'
+          silence_html: 'Je staat op het punt om het account van <strong>@%{acct}</strong> te <strong>beperken</strong>. Dit zal:'
+          suspend_html: 'Je staat op het punt om het account van <strong>@%{acct}</strong> <strong>op te schorten</strong>. Dit zal:'
+        actions:
+          delete_html: De aanstootgevende berichten verwijderen
+          mark_as_sensitive_html: De media in de aanstootgevende berichten als gevoelig markeren
+          silence_html: Het account van <strong>@%{acct}</strong> ernstig beperken, door diens profiel en inhoud alleen zichtbaar te maken aan mensen die dit account al volgen of aan mensen die het account handmatig opzoeken
+          suspend_html: Het account van <strong>@%{acct}</strong> opschorten, waarmee diens profiel en inhoud niet toegankelijk zijn en het onmogelijk is om interactie te hebben
+        close_report: 'Rapportage #%{id} als opgelost markeren'
+        close_reports_html: "<strong>Alle</strong> rapportages tegen <strong>@%{acct}</strong> als opgelost markeren"
+        delete_data_html: Het account en inhoud van <strong>@%{acct}</strong> over 30 dagen verwijderen, tenzij die in de tussentijd wordt gedeblokkeerd
+        preview_preamble_html: "<strong>@%{acct}</strong> zal een waarschuwing ontvangen met de volgende inhoud:"
+        record_strike_html: Registreer een overtreding van <strong>@%{acct}</strong> om je te helpen met het sneller afhandelen van toekomstige overtredingen van dit account
+        send_email_html: Een waarschuwingsmail naar <strong>@%{acct}</strong> sturen
+        warning_placeholder: Optionele aanvullende redenen voor de moderatie-actie.
       target_origin: Herkomst van de gerapporteerde accounts
       title: Rapportages
       unassign: Niet langer toewijzen
+      unknown_action_msg: 'Onbekende actie: %{action}'
       unresolved: Onopgelost
       updated_at: Bijgewerkt
       view_profile: Profiel bekijken
@@ -711,6 +736,8 @@ nl:
         preamble: Het tonen van interessante inhoud is van essentieel belang voor het aan boord halen van nieuwe gebruikers, die mogelijk niemand van Mastodon kennen. Bepaal hoe verschillende functies voor het ontdekken van inhoud en gebruikers op jouw server werken.
         profile_directory: Gebruikersgids
         public_timelines: Openbare tijdlijnen
+        publish_discovered_servers: Ontdekte servers publiceren
+        publish_statistics: Statistieken publiceren
         title: Ontdekken
         trends: Trends
       domain_blocks:
@@ -938,6 +965,8 @@ nl:
   auth:
     apply_for_account: Account aanvragen
     change_password: Wachtwoord
+    confirmations:
+      wrong_email_hint: Als dat e-mailadres niet correct is, kun je het wijzigen in je accountinstellingen.
     delete_account: Account verwijderen
     delete_account_html: Wanneer je jouw account graag wilt verwijderen, kun je dat <a href="%{path}">hier doen</a>. We vragen jou daar om een bevestiging.
     description:
@@ -1365,6 +1394,9 @@ nl:
       unrecognized_emoji: is geen bestaande emoji-reactie
   relationships:
     activity: Accountactiviteit
+    confirm_follow_selected_followers: Weet je zeker dat je de geselecteerde volgers wilt volgen?
+    confirm_remove_selected_followers: Weet je zeker dat je de geselecteerde volgers wilt verwijderen?
+    confirm_remove_selected_follows: Weet je zeker dat je de geselecteerde gevolgde accounts wilt verwijderen?
     dormant: Sluimerend
     follow_selected_followers: Geselecteerde volgers volgen
     followers: Volgers
@@ -1454,7 +1486,7 @@ nl:
     notifications: Meldingen
     preferences: Voorkeuren
     profile: Profiel
-    relationships: Volgers en gevolgden
+    relationships: Volgers en gevolgde accounts
     statuses_cleanup: Automatisch berichten verwijderen
     strikes: Vastgestelde overtredingen
     two_factor_authentication: Tweestapsverificatie
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index 1c70a33c1..f2c1ccdae 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -457,6 +457,7 @@ pl:
         private_comment_description_html: 'Żebyś wiedział(a) skąd pochodzą zaimportowane bloki, zostaną one utworzone z następującym prywatnym komentarzem: <q>%{comment}</q>'
         private_comment_template: Zaimportowano z %{source} dnia %{date}
         title: Importuj zablokowane domeny
+      invalid_domain_block: 'Jeden lub więcej blokujących domen zostało pominiętych z powodu następującego błędu(ów): %{error}'
       new:
         title: Importuj zablokowane domeny
       no_file: Nie wybrano pliku
@@ -613,6 +614,7 @@ pl:
       comment:
         none: Brak
       comment_description_html: 'Aby dostarczyć więcej informacji, %{name} napisał:'
+      confirm_action: Potwierdzenie działań moderacyjnych wobec @%{acct}
       created_at: Zgłoszono
       delete_and_resolve: Usuń posty
       forwarded: Przekazano
@@ -629,6 +631,7 @@ pl:
         placeholder: Opisz wykonane akcje i inne szczegóły dotyczące tego zgłoszenia…
         title: Notatki
       notes_description_html: Przeglądaj i zostaw notatki innym moderatorom i sobie samemu
+      processed_msg: 'Raport #%{id} został pomyślnie przetworzony'
       quick_actions_description_html: 'Wykonaj szybkie działanie lub przewiń w dół, aby zobaczyć zgłoszoną zawartość:'
       remote_user_placeholder: zdalny użytkownik z %{instance}
       reopen: Otwórz ponownie
@@ -641,9 +644,28 @@ pl:
       status: Stan
       statuses: Zgłoszona treść
       statuses_description_html: Obraźliwe treści będą cytowane w komunikacji ze zgłoszonym kontem
+      summary:
+        action_preambles:
+          delete_html: 'Zamierzasz <strong>usunąć</strong> niektóre posty <strong>@%{acct}</strong>. To spowoduje:'
+          mark_as_sensitive_html: 'Zamierzasz <strong>oznaczyć</strong> niektóre posty <strong>@%{acct}</strong> jako <strong>wrażliwe</strong>. To spowoduje:'
+          silence_html: 'Zamierzasz <strong>ograniczyć</strong> konto <strong>@%{acct}</strong>. To spowoduje:'
+          suspend_html: 'Zamierzasz <strong>zawiesić</strong> konto <strong>@%{acct}</strong>. To spowoduje:'
+        actions:
+          delete_html: Usuń obrażające posty
+          mark_as_sensitive_html: Oznacz obraźliwe media postów jako wrażliwe
+          silence_html: Znacząco ogranicz zasięg <strong>@%{acct}</strong>, aby ich profil i zawartość były widoczne tylko dla osób, które je już obserwują lub ręcznie wyszukują jego profil
+          suspend_html: Zawieś <strong>@%{acct}</strong>, co sprawia, że ich profil i zawartość są niedostępne i niemożliwe do interakcji z
+        close_report: 'Oznacz raport #%{id} jako rozwiązany'
+        close_reports_html: Oznacz <strong>wszystkie</strong> zgłoszenia dotyczące <strong>@%{acct}</strong> jako rozwiązane
+        delete_data_html: Usuń profil i zawartość <strong>@%{acct}</strong> za 30 dni, chyba że w tym czasie zostanie zawieszony
+        preview_preamble_html: "<strong>@%{acct}</strong> otrzyma ostrzeżenie o następującej treści:"
+        record_strike_html: Zarejestruj ostrzeżenie przeciwko <strong>@%{acct}</strong>, aby ułatwić sobie eskalację w przypadku przyszłych naruszeń z tego konta
+        send_email_html: Wyślij do <strong>@%{acct}</strong> wiadomość e-mail z ostrzeżeniem
+        warning_placeholder: Opcjonalne dodatkowe uzasadnienie działania moderacyjnego.
       target_origin: Pochodzenie zgłaszanego konta
       title: Zgłoszenia
       unassign: Cofnij przypisanie
+      unknown_action_msg: 'Nieznane działanie: %{action}'
       unresolved: Nierozwiązane
       updated_at: Zaktualizowano
       view_profile: Wyświetl profil
@@ -979,6 +1001,8 @@ pl:
   auth:
     apply_for_account: Poproś o założenie konta
     change_password: Hasło
+    confirmations:
+      wrong_email_hint: Jeśli ten adres e-mail nie jest poprawny, możesz go zmienić w ustawieniach konta.
     delete_account: Usunięcie konta
     delete_account_html: Jeżeli chcesz usunąć konto, <a href="%{path}">przejdź tutaj</a>. Otrzymasz prośbę o potwierdzenie.
     description:
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 5e3e079a3..a34d77f98 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -573,6 +573,7 @@ pt-BR:
         mark_as_sensitive_description_html: Os conteúdos de mídia em publicações denunciadas serão marcados como sensíveis e um aviso de violação será mantido para te informar sobre o agravamento caso essa mesma conta comenta infrações no futuro.
         other_description_html: Veja mais opções para controlar o comportamento da conta e personalizar a comunicação com a conta denunciada.
         resolve_description_html: Nenhuma ação será tomada contra a conta denunciada, nenhuma violação será guardada e a denúncia será encerrada.
+        silence_description_html: A conta ficará visível apenas para aqueles que já a seguem ou que a procuram manualmente, limitando severamente seu alcance. Pode ser revertido a qualquer momento. Fecha todas as denúncias desta conta.
       actions_description_html: Decida que medidas tomar para resolver esta denúncia. Se você decidir punir a conta denunciada, ela receberá uma notificação por e-mail, exceto quando for selecionada a categoria <strong>spam</strong> for selecionada.
       add_to_report: Adicionar mais à denúncia
       are_you_sure: Você tem certeza?
@@ -612,9 +613,21 @@ pt-BR:
       status: Estado
       statuses: Conteúdo denunciado
       statuses_description_html: Conteúdo ofensivo será citado em comunicação com a conta denunciada
+      summary:
+        action_preambles:
+          delete_html: 'Você está prestes a <strong>remover</strong> algumas das publicações de <strong>@%{acct}</strong>. Isso irá:'
+          mark_as_sensitive_html: 'Você está prestes a <strong>marcar</strong> algumas das publicações de <strong>@%{acct}</strong> como <strong>sensíveis</strong>. Isso irá:'
+          silence_html: 'Você está prestes a <strong>limitar</strong> <strong>a conta de @%{acct}</strong>. Isso irá:'
+          suspend_html: 'Você está prestes a <strong>suspender</strong> <strong>a conta de @%{acct}</strong>. Isso irá:'
+        close_report: 'Marcar denúncia #%{id} como resolvida'
+        close_reports_html: Marcar <strong>todas</strong> as denúncias contra <strong>@%{acct}</strong> como resolvidas
+        delete_data_html: Exclua o perfil e o conteúdo de <strong>@%{acct}</strong> daqui a 30 dias, a menos que a suspensão seja desfeita nesse meio tempo
+        preview_preamble_html: "<strong>@%{acct}</strong> receberá um aviso com o seguinte conteúdo:"
+        send_email_html: Enviar <strong>@%{acct}</strong> um e-mail de aviso
       target_origin: Origem da conta denunciada
       title: Denúncias
       unassign: Desatribuir
+      unknown_action_msg: 'Ação desconhecida: %{action}'
       unresolved: Não resolvido
       updated_at: Atualizado
       view_profile: Ver perfil
@@ -703,6 +716,7 @@ pt-BR:
         title: Retenção de conteúdo
       default_noindex:
         desc_html: Afeta qualquer usuário que não tenha alterado esta configuração manualmente
+        title: Optar por excluir usuários da indexação de mecanismos de pesquisa por padrão
       discovery:
         follow_recommendations: Seguir recomendações
         preamble: Navegar por um conteúdo interessante é fundamental para integrar novos usuários que podem não conhecer ninguém no Mastodon. Controle como várias características de descoberta funcionam no seu servidor.
@@ -937,6 +951,8 @@ pt-BR:
   auth:
     apply_for_account: Solicitar uma conta
     change_password: Senha
+    confirmations:
+      wrong_email_hint: Se esse endereço de e-mail não estiver correto, você pode alterá-lo nas configurações da conta.
     delete_account: Excluir conta
     delete_account_html: Se você deseja excluir sua conta, você pode <a href="%{path}">fazer isso aqui</a>. Uma confirmação será solicitada.
     description:
@@ -964,6 +980,7 @@ pt-BR:
     resend_confirmation: Reenviar instruções de confirmação
     reset_password: Redefinir senha
     rules:
+      preamble: Estes são definidos e aplicados pelos moderadores de %{domain}.
       title: Algumas regras básicas.
     security: Segurança
     set_new_password: Definir uma nova senha
diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml
index 39659a204..fb296988f 100644
--- a/config/locales/pt-PT.yml
+++ b/config/locales/pt-PT.yml
@@ -441,6 +441,7 @@ pt-PT:
         private_comment_description_html: 'Para o ajudar a rastrear a origem dos bloqueios importados, estes serão criados com o seguinte comentário privado: <q>%{comment}</q>'
         private_comment_template: Importado de %{source} em %{date}
         title: Importar bloqueios de domínio
+      invalid_domain_block: 'Um ou mais blocos de domínio foram ignorados devido o(s) seguinte(s) erro(s): %{error}'
       new:
         title: Importar bloqueios de domínio
       no_file: Nenhum ficheiro selecionado
@@ -589,6 +590,7 @@ pt-PT:
       comment:
         none: Nenhum
       comment_description_html: 'Para fornecer mais informações, %{name} escreveu:'
+      confirm_action: Confirmar a ação de moderação contra @%{acct}
       created_at: Denunciado
       delete_and_resolve: Eliminar publicações
       forwarded: Encaminhado
@@ -605,6 +607,7 @@ pt-PT:
         placeholder: Descreve as ações que foram tomadas ou quaisquer outras atualizações relacionadas...
         title: Notas
       notes_description_html: Visualize e deixe anotações para outros moderadores e para si próprio no futuro
+      processed_msg: 'Relatório #%{id} processado com sucesso'
       quick_actions_description_html: 'Tome uma ação rápida ou deslize para baixo para ver o conteúdo denunciado:'
       remote_user_placeholder: o utilizador remoto de %{instance}
       reopen: Reabrir denúncia
@@ -617,9 +620,28 @@ pt-PT:
       status: Estado
       statuses: Conteúdo denunciado
       statuses_description_html: O conteúdo ofensivo será citado na comunicação com a conta denunciada
+      summary:
+        action_preambles:
+          delete_html: 'Você está prestes a <strong>remover</strong> algumas das publicações de <strong>@%{acct}</strong>. Isto irá:'
+          mark_as_sensitive_html: 'Você está prestes a <strong>marcar</strong> alguns dos posts de <strong>@%{acct}</strong>como <strong>sensível</strong>. Isto irá:'
+          silence_html: 'Você está prestes a <strong>limitar a conta do</strong> <strong>@%{acct}</strong>. Isto irá:'
+          suspend_html: 'Você está prestes a <strong>suspender a conta de</strong> <strong>@%{acct}</strong>. Isto irá:'
+        actions:
+          delete_html: Excluir as publicações ofensivas
+          mark_as_sensitive_html: Marcar a mídia dos posts ofensivos como sensível
+          silence_html: Limitar firmemente o alcance de <strong>@%{acct}</strong>, tornando seus perfis e conteúdos apenas visíveis para pessoas que já os estão seguindo ou olhando manualmente no perfil
+          suspend_html: Suspender <strong>@%{acct}</strong>, tornando seu perfil e conteúdo inacessíveis e impossível de interagir com
+        close_report: 'Marcar relatório #%{id} como resolvido'
+        close_reports_html: Marcar <strong>todos os</strong> relatórios contra <strong>@%{acct}</strong> como resolvidos
+        delete_data_html: Excluir <strong>@%{acct}</strong>perfil e conteúdo 30 dias a menos que sejam dessuspensos
+        preview_preamble_html: "<strong>@%{acct}</strong> receberá um aviso com o seguinte conteúdo:"
+        record_strike_html: Registre um ataque contra <strong>@%{acct}</strong> para ajudá-lo a escalar futuras violações desta conta
+        send_email_html: Enviar <strong>@%{acct}</strong> um e-mail de aviso
+        warning_placeholder: Argumentos adicionais opcionais para a acção de moderação.
       target_origin: Origem da conta denunciada
       title: Denúncias
       unassign: Não atribuir
+      unknown_action_msg: 'Ação desconhecida: %{action}'
       unresolved: Por resolver
       updated_at: Atualizado
       view_profile: Ver perfil
@@ -943,6 +965,8 @@ pt-PT:
   auth:
     apply_for_account: Solicitar uma conta
     change_password: Palavra-passe
+    confirmations:
+      wrong_email_hint: Se esse endereço de e-mail não estiver correto, você pode alterá-lo nas configurações da conta.
     delete_account: Eliminar conta
     delete_account_html: Se deseja eliminar a sua conta, pode <a href="%{path}">continuar aqui</a>. Uma confirmação será solicitada.
     description:
diff --git a/config/locales/simple_form.csb.yml b/config/locales/simple_form.csb.yml
new file mode 100644
index 000000000..0de706e41
--- /dev/null
+++ b/config/locales/simple_form.csb.yml
@@ -0,0 +1 @@
+csb:
diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml
index 072030f82..0958426b6 100644
--- a/config/locales/simple_form.da.yml
+++ b/config/locales/simple_form.da.yml
@@ -36,7 +36,7 @@ da:
         current_username: For at bekræfte, angiv brugernavnet for den aktuelle konto
         digest: Sendes kun efter en lang inaktivitetsperiode, og kun hvis du har modtaget personlige beskeder under fraværet
         discoverable: Tillad kontoen at blive fundet af fremmede via anbefalinger og øvrige funktioner
-        email: En bekræftelsese-mail fremsendes
+        email: En bekræftelses-e-mail fremsendes
         fields: Profilen kan have op til 4 elementer vist som en tabel
         header: PNG, GIF eller JPG. Maks. %{size}. Auto-nedskaleres til %{dimensions}px
         inbox_url: Kopiér URL'en fra forsiden af den videreformidler, der skal anvendes
diff --git a/config/locales/simple_form.en-GB.yml b/config/locales/simple_form.en-GB.yml
index 3ad342cb4..d66a708af 100644
--- a/config/locales/simple_form.en-GB.yml
+++ b/config/locales/simple_form.en-GB.yml
@@ -115,8 +115,60 @@ en-GB:
         text: Describe a rule or requirement for users on this server. Try to keep it short and simple
       sessions:
         otp: 'Enter the two-factor code generated by your phone app or use one of your recovery codes:'
+        webauthn: If it's an USB key be sure to insert it and, if necessary, tap it.
+      tag:
+        name: You can only change the casing of the letters, for example, to make it more readable
+      user:
+        chosen_languages: When checked, only posts in selected languages will be displayed in public timelines
+        role: The role controls which permissions the user has
+      user_role:
+        color: Color to be used for the role throughout the UI, as RGB in hex format
+        highlighted: This makes the role publicly visible
+        name: Public name of the role, if role is set to be displayed as a badge
+        permissions_as_keys: Users with this role will have access to...
+        position: Higher role decides conflict resolution in certain situations. Certain actions can only be performed on roles with a lower priority
+      webhook:
+        events: Select events to send
+        url: Where events will be sent to
     labels:
+      account:
+        fields:
+          name: Label
+          value: Content
+      account_alias:
+        acct: Handle of the old account
+      account_migration:
+        acct: Handle of the new account
+      account_warning_preset:
+        text: Preset text
+        title: Title
+      admin_account_action:
+        include_statuses: Include reported posts in the e-mail
+        send_email_notification: Notify the user per e-mail
+        text: Custom warning
+        type: Action
+        types:
+          disable: Freeze
+          none: Send a warning
+          sensitive: Sensitive
+          silence: Limit
+          suspend: Suspend
+        warning_preset_id: Use a warning preset
+      announcement:
+        all_day: All-day event
+        ends_at: End of event
+        scheduled_at: Schedule publication
+        starts_at: Start of event
+        text: Announcement
+      appeal:
+        text: Explain why this decision should be reversed
       defaults:
+        autofollow: Invite to follow your account
+        avatar: Avatar
+        bot: This is a bot account
+        chosen_languages: Filter languages
+        confirm_new_password: Confirm new password
+        confirm_password: Confirm password
         context: Filter contexts
         current_password: Current password
         data: Data
@@ -135,7 +187,85 @@ en-GB:
         new_password: New password
         note: Bio
         otp_attempt: Two-factor code
+        password: Password
+        phrase: Keyword or phrase
+        setting_advanced_layout: Enable advanced web interface
+        setting_aggregate_reblogs: Group boosts in timelines
+        setting_always_send_emails: Always send e-mail notifications
+        setting_auto_play_gif: Auto-play animated GIFs
+        setting_boost_modal: Show confirmation dialogue before boosting
+        setting_crop_images: Crop images in non-expanded posts to 16x9
+        setting_default_language: Posting language
+        setting_default_privacy: Posting privacy
+        setting_default_sensitive: Always mark media as sensitive
+        setting_delete_modal: Show confirmation dialogue before deleting a post
+        setting_disable_swiping: Disable swiping motions
+        setting_display_media: Media display
+        setting_display_media_default: Default
+        setting_display_media_hide_all: Hide all
+        setting_display_media_show_all: Show all
+        setting_expand_spoilers: Always expand posts marked with content warnings
+        setting_hide_network: Hide your social graph
+        setting_noindex: Opt-out of search engine indexing
+        setting_reduce_motion: Reduce motion in animations
+        setting_show_application: Disclose application used to send posts
+        setting_system_font_ui: Use system's default font
+        setting_theme: Site theme
+        setting_trends: Show today's trends
+        setting_unfollow_modal: Show confirmation dialog before unfollowing someone
+        setting_use_blurhash: Show colourful gradients for hidden media
+        setting_use_pending_items: Slow mode
+        severity: Severity
+        sign_in_token_attempt: Security code
+        title: Title
+        type: Import type
+        username: Username
+        username_or_email: Username or Email
+        whole_word: Whole word
+      email_domain_block:
+        with_dns_records: Include MX records and IPs of the domain
+      featured_tag:
+        name: Hashtag
+      filters:
+        actions:
+          hide: Hide completely
+          warn: Hide with a warning
+      form_admin_settings:
+        activity_api_enabled: Publish aggregate statistics about user activity in the API
+        backups_retention_period: User archive retention period
+        bootstrap_timeline_accounts: Always recommend these accounts to new users
+        closed_registrations_message: Custom message when sign-ups are not available
+        content_cache_retention_period: Content cache retention period
+        custom_css: Custom CSS
+        mascot: Custom mascot (legacy)
+        media_cache_retention_period: Media cache retention period
+        peers_api_enabled: Publish list of discovered servers in the API
+        profile_directory: Enable profile directory
+        registrations_mode: Who can sign-up
+        require_invite_text: Require a reason to join
+        show_domain_blocks: Show domain blocks
+        show_domain_blocks_rationale: Show why domains were blocked
+        site_contact_email: Contact e-mail
+        site_contact_username: Contact username
+        site_extended_description: Extended description
+        site_short_description: Server description
+        site_terms: Privacy Policy
+        site_title: Server name
+        theme: Default theme
+        thumbnail: Server thumbnail
+        timeline_preview: Allow unauthenticated access to public timelines
+        trendable_by_default: Allow trends without prior review
+        trends: Enable trends
+      interactions:
+        must_be_follower: Block notifications from non-followers
+        must_be_following: Block notifications from people you don't follow
+        must_be_following_dm: Block direct messages from people you don't follow
+      invite:
+        comment: Comment
+      invite_request:
+        text: Why do you want to join?
       ip_block:
+        comment: Comment
         ip: IP
         severities:
           no_access: Block access
@@ -164,6 +294,7 @@ en-GB:
         role: Role
       user_role:
         color: Badge colour
+        highlighted: Display role as badge on user profiles
         name: Name
         permissions_as_keys: Permissions
         position: Priority
diff --git a/config/locales/simple_form.en_GB.yml b/config/locales/simple_form.en_GB.yml
deleted file mode 100644
index 8752d81bb..000000000
--- a/config/locales/simple_form.en_GB.yml
+++ /dev/null
@@ -1,131 +0,0 @@
----
-en_GB:
-  simple_form:
-    hints:
-      account_warning_preset:
-        text: You can use toot syntax, such as URLs, hashtags and mentions
-      admin_account_action:
-        send_email_notification: The user will receive an explanation of what happened with their account
-        text_html: Optional. You can use toot syntax. You can <a href="%{path}">add warning presets</a> to save time
-        type_html: Choose what to do with <strong>%{acct}</strong>
-        warning_preset_id: Optional. You can still add custom text to end of the preset
-      defaults:
-        autofollow: People who sign up through the invite will automatically follow you
-        avatar: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px
-        bot: This account mainly performs automated actions and might not be monitored
-        context: One or multiple contexts where the filter should apply
-        digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence
-        discoverable_html: The <a href="%{path}" target="_blank">directory</a> lets people find accounts based on interests and activity. Requires at least %{min_followers} followers
-        email: You will be sent a confirmation e-mail
-        fields: You can have up to 4 items displayed as a table on your profile
-        header: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px
-        inbox_url: Copy the URL from the frontpage of the relay you want to use
-        irreversible: Filtered toots will disappear irreversibly, even if filter is later removed
-        locale: The language of the user interface, e-mails and push notifications
-        locked: Requires you to manually approve followers
-        password: Use at least 8 characters
-        phrase: Will be matched regardless of casing in text or content warning of a toot
-        scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones.
-        setting_aggregate_reblogs: Do not show new boosts for toots that have been recently boosted (only affects newly-received boosts)
-        setting_display_media_default: Hide media marked as sensitive
-        setting_display_media_hide_all: Always hide all media
-        setting_display_media_show_all: Always show media marked as sensitive
-        setting_hide_network: Who you follow and who follows you will not be shown on your profile
-        setting_noindex: Affects your public profile and status pages
-        setting_show_application: The application you use to toot will be displayed in the detailed view of your toots
-        username: Your username will be unique on %{domain}
-        whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word
-      featured_tag:
-        name: 'You might want to use one of these:'
-      imports:
-        data: CSV file exported from another Mastodon server
-      sessions:
-        otp: 'Enter the two-factor code generated by your phone app or use one of your recovery codes:'
-      user:
-        chosen_languages: When checked, only toots in selected languages will be displayed in public timelines
-    labels:
-      account:
-        fields:
-          name: Label
-          value: Content
-      account_warning_preset:
-        text: Preset text
-      admin_account_action:
-        send_email_notification: Notify the user per e-mail
-        text: Custom warning
-        type: Action
-        types:
-          disable: Disable
-          none: Do nothing
-          silence: Silence
-          suspend: Suspend and irreversibly delete account data
-        warning_preset_id: Use a warning preset
-      defaults:
-        autofollow: Invite to follow your account
-        avatar: Avatar
-        bot: This is a bot account
-        chosen_languages: Filter languages
-        confirm_new_password: Confirm new password
-        confirm_password: Confirm password
-        context: Filter contexts
-        current_password: Current password
-        data: Data
-        discoverable: List this account on the directory
-        display_name: Display name
-        email: E-mail address
-        expires_in: Expire after
-        fields: Profile metadata
-        header: Header
-        inbox_url: URL of the relay inbox
-        irreversible: Drop instead of hide
-        locale: Interface language
-        locked: Lock account
-        max_uses: Max number of uses
-        new_password: New password
-        note: Bio
-        otp_attempt: Two-factor code
-        password: Password
-        phrase: Keyword or phrase
-        setting_aggregate_reblogs: Group boosts in timelines
-        setting_auto_play_gif: Auto-play animated GIFs
-        setting_boost_modal: Show confirmation dialog before boosting
-        setting_default_language: Posting language
-        setting_default_privacy: Post privacy
-        setting_default_sensitive: Always mark media as sensitive
-        setting_delete_modal: Show confirmation dialog before deleting a toot
-        setting_display_media: Media display
-        setting_display_media_default: Default
-        setting_display_media_hide_all: Hide all
-        setting_display_media_show_all: Show all
-        setting_expand_spoilers: Always expand toots marked with content warnings
-        setting_hide_network: Hide your network
-        setting_noindex: Opt-out of search engine indexing
-        setting_reduce_motion: Reduce motion in animations
-        setting_show_application: Disclose application used to send toots
-        setting_system_font_ui: Use system's default font
-        setting_theme: Site theme
-        setting_unfollow_modal: Show confirmation dialog before unfollowing someone
-        severity: Severity
-        type: Import type
-        username: Username
-        username_or_email: Username or Email
-        whole_word: Whole word
-      featured_tag:
-        name: Hashtag
-      interactions:
-        must_be_follower: Block notifications from non-followers
-        must_be_following: Block notifications from people you don't follow
-        must_be_following_dm: Block direct messages from people you don't follow
-      notification_emails:
-        digest: Send digest e-mails
-        favourite: Send e-mail when someone favourites your status
-        follow: Send e-mail when someone follows you
-        follow_request: Send e-mail when someone requests to follow you
-        mention: Send e-mail when someone mentions you
-        reblog: Send e-mail when someone boosts your status
-        report: Send e-mail when a new report is submitted
-    'no': 'No'
-    required:
-      mark: "*"
-      text: required
-    'yes': 'Yes'
diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml
index ddecb0036..759297930 100644
--- a/config/locales/simple_form.es.yml
+++ b/config/locales/simple_form.es.yml
@@ -74,6 +74,7 @@ es:
           hide: Ocultar completamente el contenido filtrado, comportándose como si no existiera
           warn: Ocultar el contenido filtrado detrás de una advertencia mencionando el título del filtro
       form_admin_settings:
+        activity_api_enabled: Conteo de publicaciones publicadas localmente, usuarios activos y registros nuevos cada semana
         backups_retention_period: Mantener los archivos de usuario generados durante el número de días especificado.
         bootstrap_timeline_accounts: Estas cuentas aparecerán en la parte superior de las recomendaciones de los nuevos usuarios.
         closed_registrations_message: Mostrado cuando los registros están cerrados
@@ -230,6 +231,7 @@ es:
           hide: Ocultar completamente
           warn: Ocultar con una advertencia
       form_admin_settings:
+        activity_api_enabled: Publicar estadísticas agregadas sobre la actividad del usuario con la API
         backups_retention_period: Período de retención del archivo de usuario
         bootstrap_timeline_accounts: Recomendar siempre estas cuentas a nuevos usuarios
         closed_registrations_message: Mensaje personalizado cuando los registros no están disponibles
diff --git a/config/locales/simple_form.eu.yml b/config/locales/simple_form.eu.yml
index 5efc182a4..29042deb5 100644
--- a/config/locales/simple_form.eu.yml
+++ b/config/locales/simple_form.eu.yml
@@ -18,6 +18,8 @@ eu:
           disable: Erabiltzaileari bere kontua erabiltzea eragotzi, baina ez ezabatu edo ezkutatu bere edukiak.
           none: Erabili hau erabiltzaileari abisu bat bidaltzeko, beste ekintzarik abiarazi gabe.
           sensitive: Behartu erabiltzaile honen multimedia eranskin guztiak hunkigarri gisa markatzea.
+          silence: Eragotzi erabiltzaileak ikusgaitasun publikoarekin argitaratzea, ezkutatu bere bidalketa eta jakinarazpenak jarraitzen ez duten pertsonei. Kontu honen aurkako txosten guztiak ixten ditu.
+          suspend: Eragotzi kontu honek inolako interakziorik izatea eta ezabatu bere edukiak. Atzera bota daiteke 30 egun igaro aurretik. Kontu honen aurkako txosten guztiak ixten ditu.
         warning_preset_id: Aukerakoa. Zure testua gehitu dezakezu aurre-ezarpenaren ostean
       announcement:
         all_day: Markatutakoan soilik denbora barrutiko datak erakutsiko dira
@@ -72,6 +74,7 @@ eu:
           hide: Ezkutatu erabat iragazitako edukia, existituko ez balitz bezala
           warn: Ezkutatu iragazitako edukia iragazkiaren izenburua duen abisu batekin
       form_admin_settings:
+        activity_api_enabled: Lokalki argitaratutako bidalketak, erabiltzaile aktiboak, eta izen-emateen kopuruak astero zenbatzen ditu
         backups_retention_period: Mantendu sortutako erabiltzailearen artxiboa zehazturiko egun kopuruan.
         bootstrap_timeline_accounts: Kontu hauek erabiltzaile berrien jarraitzeko gomendioen goiko aldean ainguratuko dira.
         closed_registrations_message: Izen-ematea itxia dagoenean bistaratua
@@ -79,6 +82,7 @@ eu:
         custom_css: Estilo pertsonalizatuak aplikatu ditzakezu Mastodonen web bertsioan.
         mascot: Web interfaze aurreratuko ilustrazioa gainidazten du.
         media_cache_retention_period: Balio positibo bat ezarriz gero, egun kopuru horretara iristean beste zerbitzarietatik deskargatutako multimedia fitxategiak ezabatuko dira. Ondoren, eskatu ahala deskargatuko dira berriz.
+        peers_api_enabled: Zerbitzari honek fedibertsoan ikusi dituen zerbitzarien domeinu-izenen zerrenda. Ez da daturik ematen zerbitzari jakin batekin federatzearen ala ez federatzearen inguruan, zerbitzariak haien berri duela soilik. Federazioari buruzko estatistika orokorrak biltzen dituzten zerbitzuek erabiltzen dute hau.
         profile_directory: Profilen direktorioan ikusgai egotea aukeratu duten erabiltzaile guztiak zerrendatzen dira.
         require_invite_text: Izen emateak eskuz onartu behar direnean, "Zergatik elkartu nahi duzu?" testu sarrera derrigorrezko bezala ezarri, ez hautazko
         site_contact_email: Jendeak kontsulta legalak egin edo laguntza eskatzeko bidea.
@@ -227,6 +231,7 @@ eu:
           hide: Ezkutatu guztiz
           warn: Ezkutatu ohar batekin
       form_admin_settings:
+        activity_api_enabled: Argitaratu erabiltzaile-jardueraren guztizko estatistikak APIan
         backups_retention_period: Erabiltzailearen artxiboa gordetzeko epea
         bootstrap_timeline_accounts: Gomendatu beti kontu hauek erabiltzaile berriei
         closed_registrations_message: Izen-emateak itxita daudenerako mezu pertsonalizatua
@@ -234,6 +239,7 @@ eu:
         custom_css: CSS pertsonalizatua
         mascot: Maskota pertsonalizatua (zaharkitua)
         media_cache_retention_period: Multimediaren cachea atxikitzeko epea
+        peers_api_enabled: Argitaratu aurkitutako zerbitzarien zerrenda APIan
         profile_directory: Gaitu profil-direktorioa
         registrations_mode: Nork eman dezake izena
         require_invite_text: Eskatu arrazoi bat batzeko
diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml
index 49e3962b5..ffcf16e01 100644
--- a/config/locales/simple_form.fi.yml
+++ b/config/locales/simple_form.fi.yml
@@ -188,13 +188,13 @@ fi:
         password: Salasana
         phrase: Avainsana tai lause
         setting_advanced_layout: Ota käyttöön edistynyt selainkäyttöliittymä
-        setting_aggregate_reblogs: Ryhmitä boostaukset aikajanalla
+        setting_aggregate_reblogs: Ryhmitä tehostukset aikajanalla
         setting_always_send_emails: Lähetä aina sähköposti-ilmoituksia
         setting_auto_play_gif: Toista GIF-animaatiot automaattisesti
-        setting_boost_modal: Kysy vahvistusta ennen buustausta
+        setting_boost_modal: Kysy vahvistus ennen tehostusta
         setting_crop_images: Rajaa kuvat avaamattomissa tuuttauksissa 16:9 kuvasuhteeseen
         setting_default_language: Julkaisujen kieli
-        setting_default_privacy: Julkaisun näkyvyys
+        setting_default_privacy: Viestin näkyvyys
         setting_default_sensitive: Merkitse media aina arkaluontoiseksi
         setting_delete_modal: Kysy vahvistusta ennen viestin poistamista
         setting_disable_swiping: Poista pyyhkäisyt käytöstä
@@ -278,7 +278,7 @@ fi:
         follow_request: Lähetä sähköposti, kun joku pyytää seurata sinua
         mention: Lähetä sähköposti, kun sinut mainitaan
         pending_account: Uusi tili tarvitsee tarkastusta
-        reblog: Lähetä sähköposti, kun joku buustaa julkaisusi
+        reblog: Lähetä sähköposti, kun joku tehosti viestiäsi
         report: Uusi raportti on lähetetty
         trending_tag: Uusi trendi vaatii tarkastelua
       rule:
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
index efb5b0a5c..779555da3 100644
--- a/config/locales/simple_form.ja.yml
+++ b/config/locales/simple_form.ja.yml
@@ -18,8 +18,8 @@ ja:
           disable: ユーザーが自分のアカウントを使用できないようにします。コンテンツを削除したり非表示にすることはありません。
           none: これを使用すると、他の操作をせずにユーザーに警告を送信できます。
           sensitive: このユーザーが添付したメディアを強制的に閲覧注意にする
-          silence: ユーザーが公開投稿できないようにし、フォローしていない人に投稿や通知が表示されないようにする。このアカウントに対する全ての通報をクローズします。
-          suspend: このアカウントとのやりとりを止め、コンテンツを削除します。30日以内は取消可能です。このアカウントに対する全ての通報をクローズします。
+          silence: ユーザーによる公開投稿を禁止し、フォローしていない人に投稿や通知が表示されないようにします。また、このアカウントに対するすべての通報をクローズします。
+          suspend: このアカウントによるすべての活動を禁止し、コンテンツを削除します。この操作は30日以内であれば取り消しが可能です。また、このアカウントに対するすべての通報をクローズします。
         warning_preset_id: オプションです。プリセット警告文の末尾に任意の文字列を追加することができます
       announcement:
         all_day: 有効化すると、対象期間の箇所に日付だけが表示されます
@@ -71,26 +71,26 @@ ja:
       filters:
         action: 投稿がフィルタに一致したときに実行するアクションを選択
         actions:
-          hide: フィルタリングしたコンテンツを完全に隠し、あたかも存在しないかのようにします
-          warn: フィルタリングされたコンテンツを、フィルタータイトルの警告の後ろに隠します。
+          hide: フィルタに一致した投稿を完全に非表示にします
+          warn: フィルタに一致した投稿を非表示にし、フィルタのタイトルを含む警告を表示します
       form_admin_settings:
-        activity_api_enabled: 週単位でローカルで公開された投稿数、アクティブユーザー数、新規登録者数を表示する
+        activity_api_enabled: 週単位でローカルで公開された投稿数、アクティブユーザー数、新規登録者数を表示します
         backups_retention_period: 生成されたユーザーのアーカイブを指定した日数の間保持します。
-        bootstrap_timeline_accounts: これらのアカウントは、新しいユーザーのフォロー推奨リストの一番上にピン留めされます。
+        bootstrap_timeline_accounts: これらのアカウントは、新しいユーザー向けのおすすめユーザーの一番上にピン留めされます。
         closed_registrations_message: アカウント作成を停止している時に表示されます
         content_cache_retention_period: 正の値に設定されている場合、他のサーバーの投稿は指定された日数の後に削除されます。元に戻せません。
         custom_css: ウェブ版のMastodonでカスタムスタイルを適用できます。
-        mascot: 上級者向けWebインターフェースのイラストを上書きする。
+        mascot: 上級者向けWebインターフェースのイラストを上書きします。
         media_cache_retention_period: 正の値に設定されている場合、ダウンロードされたメディアファイルは指定された日数の後に削除され、リクエストに応じて再ダウンロードされます。
         peers_api_enabled: このサーバーが Fediverse で遭遇したドメイン名のリストです。このサーバーが知っているだけで、特定のサーバーと連合しているかのデータは含まれません。これは一般的に Fediverse に関する統計情報を収集するサービスによって使用されます。
-        profile_directory: プロフィールディレクトリには、掲載するよう設定したすべてのユーザーが一覧表示されます。
-        require_invite_text: アカウント登録が承認制の場合、「意気込みをお聞かせください」のテキストの入力を必須にする
+        profile_directory: ディレクトリには、掲載する設定をしたすべてのユーザーが一覧表示されます。
+        require_invite_text: アカウント登録が承認制の場合、申請事由の入力を必須にします
         site_contact_email: 法律またはサポートに関する問い合わせ先
         site_contact_username: マストドンでの連絡方法
         site_extended_description: 訪問者やユーザーに役立つかもしれない任意の追加情報。Markdownが使えます。
-        site_short_description: 誰が運営しているのか、誰に向けたものなのかなど、サーバーを特定する短い説明。
+        site_short_description: 運営している人や組織、想定しているユーザーなど、サーバーの特徴を説明する短いテキスト
         site_terms: 独自のプライバシーポリシーを使用するか空白にしてデフォルトのプライバシーポリシーを使用します。Markdownが使えます。
-        site_title: ドメイン名以外でサーバーを参照する方法です。
+        site_title: ドメイン名以外でサーバーを参照する方法
         theme: ログインしていない人と新規ユーザーに表示されるテーマ。
         thumbnail: サーバー情報と共に表示される、アスペクト比が約 2:1 の画像。
         timeline_preview: ログアウトした人でも、サーバー上で利用可能な最新の公開投稿を閲覧することができます。
diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml
index 742b3bcf4..007fa8561 100644
--- a/config/locales/simple_form.ko.yml
+++ b/config/locales/simple_form.ko.yml
@@ -30,7 +30,7 @@ ko:
       appeal:
         text: 처벌에 대해 단 한 번만 이의제기를 할 수 있습니다
       defaults:
-        autofollow: 이 초대를 통해 가입하는 사람은 당신을 자동으로 팔로우 하게 됩니다
+        autofollow: 이 초대로 가입한 사람은 나를 팔로우하게 됩니다.
         avatar: PNG, GIF 혹은 JPG. 최대 %{size}. %{dimensions}px로 축소 됨
         bot: 이 계정이 대부분 자동으로 작업을 수행하고 잘 확인하지 않는다는 것을 알립니다.
         context: 필터를 적용 할 한 개 이상의 컨텍스트
@@ -45,7 +45,7 @@ ko:
         irreversible: 필터링 된 게시물은 나중에 필터가 사라지더라도 돌아오지 않게 됩니다
         locale: 사용자 인터페이스, 이메일, 푸시 알림 언어
         locked: 팔로우 요청을 승인제로 두어 누가 당신을 팔로우 할 수 있는지를 수동으로 제어합니다.
-        password: 최소 8글자
+        password: 여덟 글자를 넘어야 합니다.
         phrase: 게시물 내용이나 열람주의 내용 안에서 대소문자 구분 없이 매칭 됩니다
         scopes: 애플리케이션에 허용할 API들입니다. 최상위 스코프를 선택하면 개별적인 것은 선택하지 않아도 됩니다.
         setting_aggregate_reblogs: 최근에 부스트 됐던 게시물은 새로 부스트 되어도 보여주지 않기 (새로 받은 부스트에만 적용됩니다)
@@ -101,7 +101,7 @@ ko:
       imports:
         data: 다른 마스토돈 서버에서 추출된 CSV 파일
       invite_request:
-        text: 이 정보는 우리가 심사를 하는 데에 참고할 수 있습니다
+        text: 이 정보는 신청을 검토하는데 도움을 줄 수 있습니다.
       ip_block:
         comment: 필수 아님. 왜 이 규칙을 추가했는지 기억하세요.
         expires_in: IP 주소는 한정된 자원입니다, 이것들은 가끔 공유 되거나 자주 소유자가 바뀌기도 합니다. 이런 이유로 인해, IP 차단을 영구히 유지하는 것은 추천하지 않습니다.
@@ -151,7 +151,7 @@ ko:
           disable: 비활성화
           none: 아무 것도 하지 않기
           sensitive: 민감함
-          silence: 침묵
+          silence: 제한
           suspend: 정지하고 되돌릴 수 없는 데이터 삭제
         warning_preset_id: 경고 틀 사용하기
       announcement:
@@ -279,16 +279,16 @@ ko:
         follow: 누군가 나를 팔로우 했을 때
         follow_request: 누군가 나를 팔로우 하길 요청할 때
         mention: 누군가 나를 언급했을 때
-        pending_account: 새 계정이 심사가 필요할 때
+        pending_account: 검토해야 할 새 계정
         reblog: 누군가 내 게시물을 부스트 했을 때
         report: 새 신고가 접수되었을 때
-        trending_tag: 새 트렌드에 대한 리뷰가 필요할 때
+        trending_tag: 검토해야 할 새 유행
       rule:
         text: 규칙
       tag:
         listable: 이 해시태그가 검색과 추천에 보여지도록 허용
         name: 해시태그
-        trendable: 이 해시태그가 유행에 보여지도록 허용
+        trendable: 이 해시태그를 유행에 나타나도록 허용
         usable: 이 해시태그를 게시물에 사용 가능하도록 허용
       user:
         role: 역할
diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml
index 4816dbacf..ce474dab7 100644
--- a/config/locales/simple_form.nl.yml
+++ b/config/locales/simple_form.nl.yml
@@ -18,6 +18,8 @@ nl:
           disable: Voorkom dat de gebruiker diens account gebruikt, maar verwijder of verberg de inhoud niet.
           none: Gebruik dit om een waarschuwing naar de gebruiker te sturen, zonder dat nog een andere maatregel wordt genomen.
           sensitive: Forceer dat alle mediabijlagen van deze gebruiker als gevoelig worden gemarkeerd.
+          silence: Voorkom dat de gebruiker berichten kan plaatsen met openbare zichtbaarheid, verberg diens berichten en meldingen van mensen die de gebruiker niet volgen. Sluit alle rapportages tegen dit account af.
+          suspend: Voorkom interactie van of naar dit account en verwijder de inhoud. Dit is omkeerbaar binnen 30 dagen. Dit sluit alle rapporten tegen dit account af.
         warning_preset_id: Optioneel. Je kunt nog steeds handmatig tekst toevoegen aan het eind van de voorinstelling
       announcement:
         all_day: Wanneer dit is aangevinkt worden alleen de datums binnen het tijdvak getoond
@@ -72,6 +74,7 @@ nl:
           hide: Verberg de gefilterde inhoud volledig, alsof het niet bestaat
           warn: Verberg de gefilterde inhoud achter een waarschuwing, met de titel van het filter als waarschuwingstekst
       form_admin_settings:
+        activity_api_enabled: Aantallen lokaal gepubliceerde berichten, actieve gebruikers en nieuwe registraties per week
         backups_retention_period: De aangemaakte gebruikersarchieven voor het opgegeven aantal dagen behouden.
         bootstrap_timeline_accounts: Deze accounts worden bovenaan de aanbevelingen aan nieuwe gebruikers getoond. Meerdere gebruikersnamen met komma's scheiden.
         closed_registrations_message: Weergegeven wanneer registratie van nieuwe accounts is uitgeschakeld
@@ -79,6 +82,7 @@ nl:
         custom_css: Je kunt aangepaste CSS toepassen op de webversie van deze Mastodon-server.
         mascot: Overschrijft de illustratie in de geavanceerde webomgeving.
         media_cache_retention_period: Mediabestanden die van andere servers zijn gedownload worden na het opgegeven aantal dagen verwijderd en worden op verzoek opnieuw gedownload.
+        peers_api_enabled: Een lijst met domeinnamen die deze server heeft aangetroffen in de fediverse. Er zijn hier geen gegevens inbegrepen over de vraag of je verbonden bent met een bepaalde server, alleen dat je server er van weet. Dit wordt gebruikt door diensten die statistieken over de federatie in algemene zin verzamelen.
         profile_directory: De gebruikersgids bevat een lijst van alle gebruikers die ervoor gekozen hebben om ontdekt te kunnen worden.
         require_invite_text: Maak het invullen van "Waarom wil je je hier registreren?" verplicht in plaats van optioneel, wanneer registraties handmatig moeten worden goedgekeurd
         site_contact_email: Hoe mensen je kunnen bereiken voor juridische vragen of support.
@@ -227,6 +231,7 @@ nl:
           hide: Volledig verbergen
           warn: Met een waarschuwing verbergen
       form_admin_settings:
+        activity_api_enabled: Statistieken over gebruikersactiviteit via de API publiceren
         backups_retention_period: Bewaartermijn gebruikersarchief
         bootstrap_timeline_accounts: Accounts die altijd aan nieuwe gebruikers worden aanbevolen
         closed_registrations_message: Aangepast bericht wanneer registratie is uitgeschakeld
@@ -234,6 +239,7 @@ nl:
         custom_css: Aangepaste CSS
         mascot: Aangepaste mascotte (legacy)
         media_cache_retention_period: Bewaartermijn mediacache
+        peers_api_enabled: Lijst van bekende servers via de API publiceren
         profile_directory: Gebruikersgids inschakelen
         registrations_mode: Wie kan zich registreren
         require_invite_text: Opgeven van een reden is verplicht
diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml
index f8bcf7adf..78c8ea7e0 100644
--- a/config/locales/simple_form.sk.yml
+++ b/config/locales/simple_form.sk.yml
@@ -163,7 +163,7 @@ sk:
       notification_emails:
         digest: Zasielať súhrnné emaily
         favourite: Zaslať email, ak si niekto obľúbi tvoj príspevok
-        follow: Zaslať email, ak ťa niekto začne následovať
+        follow: Niekto ťa začal nasledovať
         follow_request: Zaslať email, ak ti niekto pošle žiadosť o sledovanie
         mention: Zaslať email, ak ťa niekto spomenie vo svojom príspevku
         pending_account: Zaslať email, ak treba prehodnotiť nový účet
diff --git a/config/locales/simple_form.uz.yml b/config/locales/simple_form.uz.yml
new file mode 100644
index 000000000..3ed042df3
--- /dev/null
+++ b/config/locales/simple_form.uz.yml
@@ -0,0 +1 @@
+uz:
diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml
index 7eebb2d46..a4036e91b 100644
--- a/config/locales/simple_form.zh-TW.yml
+++ b/config/locales/simple_form.zh-TW.yml
@@ -84,7 +84,7 @@ zh-TW:
         media_cache_retention_period: 當設定成正值時,已下載的多媒體檔案會於指定天數後被刪除,並且視需要重新下載。
         peers_api_enabled: 浩瀚聯邦宇宙中與此伺服器曾經擦肩而過的網域列表。不包含關於您是否與此伺服器是否有與之串連,僅僅表示您的伺服器已知此網域。這是供收集聯邦宇宙中一般性統計資料服務使用。
         profile_directory: 個人檔案目錄將會列出那些有選擇被發現的使用者。
-        require_invite_text: 如果已設定為手動審核註冊,請將「加入原因」設定為必填項目。
+        require_invite_text: 如果已設定為手動審核註冊,請將「為什麼想要加入呢?」設定為必填項目。
         site_contact_email: 其他人如何聯繫您關於法律或支援之諮詢。
         site_contact_username: 其他人如何於 Mastodon 上聯繫您。
         site_extended_description: 任何其他可能對訪客或使用者有用的額外資訊。可由 Markdown 語法撰寫。
@@ -263,7 +263,7 @@ zh-TW:
       invite:
         comment: 備註
       invite_request:
-        text: 加入的原因
+        text: 為什麼想要加入呢?
       ip_block:
         comment: 備註
         ip: IP 位址
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index 58a689b0c..f5042f92b 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -21,8 +21,8 @@ sk:
     pin_errors:
       following: Musíš už následovať toho človeka, ktorého si praješ zviditeľniť
     posts:
-      few: Príspevkov
-      many: Príspevky
+      few: Príspevky
+      many: Príspevkov
       one: Príspevok
       other: Príspevkov
     posts_tab_heading: Príspevky
@@ -227,6 +227,7 @@ sk:
         destroy_ip_block_html: "%{name} vymazal/a pravidlo pre IP %{target}"
         destroy_status_html: "%{name} zmazal/a príspevok od %{target}"
       deleted_account: zmazaný účet
+      empty: Žiadne záznamy nenájdené.
       filter_by_action: Filtruj podľa úkonu
       filter_by_user: Trieď podľa užívateľa
       title: Kontrólny záznam
@@ -317,6 +318,7 @@ sk:
         hint: Blokovanie domény stále dovolí vytvárať nové účty v databázi, ale tieto budú spätne automaticky moderované.
         severity:
           noop: Nič
+          silence: Obmedz
           suspend: Vylúč
         title: Nové blokovanie domény
       obfuscate: Zatemniť názov domény
@@ -344,6 +346,8 @@ sk:
       resolved_through_html: Prevedená cez %{domain}
       title: Blokované emailové adresy
     export_domain_allows:
+      new:
+        title: Nahraj povolené domény
       no_file: Nevybraný žiaden súbor
     export_domain_blocks:
       import:
@@ -476,6 +480,11 @@ sk:
       resolved_msg: Hlásenie úspešne vyriešené!
       status: Stav
       statuses: Nahlásený obsah
+      summary:
+        actions:
+          delete_html: Vymaž pohoršujúce príspevky
+          mark_as_sensitive_html: Označ médiá pohoršujúcich príspevkov za chúlostivé
+        close_report: 'Označ hlásenie #%{id} za vyriešené'
       title: Hlásenia
       unassign: Odober
       unresolved: Nevyriešené
@@ -647,7 +656,7 @@ sk:
     description:
       prefix_invited_by_user: "@%{name} ťa pozýva na tento Mastodon server!"
       prefix_sign_up: Zaregistruj sa na Mastodone už dnes!
-      suffix: S pomocou účtu budeš môcť následovať ľudí, posielať príspevky, a vymienať si správy s užívateľmi na hociakom Mastodon serveri, ale aj na iných serveroch!
+      suffix: S pomocou účtu budeš môcť nasledovať ľudí, posielať príspevky, a vymieňať si správy s užívateľmi na hocijakom Mastodon serveri, ale aj na iných serveroch!
     didnt_get_confirmation: Neobdržal/a si kroky na potvrdenie?
     forgot_password: Zabudnuté heslo?
     invalid_reset_password_token: Token na obnovu hesla vypršal. Prosím vypítaj si nový.
@@ -677,7 +686,7 @@ sk:
     already_following: Tento účet už nasleduješ
     error: Naneštastie nastala chyba pri hľadaní vzdialeného účtu
     follow: Nasleduj
-    follow_request: 'Poslal/a si žiadosť následovať užívateľa:'
+    follow_request: 'Poslal/a si žiadosť nasledovať užívateľa:'
     following: 'Podarilo sa! Teraz nasleduješ užívateľa:'
     post_follow:
       close: Alebo môžeš iba zatvoriť toto okno.
diff --git a/config/locales/sl.yml b/config/locales/sl.yml
index d008543ad..e2f712d7f 100644
--- a/config/locales/sl.yml
+++ b/config/locales/sl.yml
@@ -457,6 +457,7 @@ sl:
         private_comment_description_html: 'Kot pomoč pri sledenju izvora uvoženih blokad bodo le-te ustvarjene z naslednjim zasebnim komentarjem: <q>%{comment}</q>'
         private_comment_template: Uvoženo iz %{source} %{date}
         title: Uvozi blokade domen
+      invalid_domain_block: 'Ena ali več blokad domen je bila preskočena zaradi naslednjih napak: %{error}'
       new:
         title: Uvozi blokade domen
       no_file: Nobena datoteka ni izbrana
@@ -613,6 +614,7 @@ sl:
       comment:
         none: Brez
       comment_description_html: 'V pojasnilo je %{name} zapisal/a:'
+      confirm_action: Potrdite dejanje moderiranja proti @%{acct}
       created_at: Prijavljeno
       delete_and_resolve: Izbriši objave
       forwarded: Posredovano
@@ -629,6 +631,7 @@ sl:
         placeholder: Opišite dejanja, ki ste jih izvedli, ali katere koli druge posodobitve ...
         title: Zapiski
       notes_description_html: Pokaži in pusti opombe drugim moderatorjem in sebi v prihodnosti
+      processed_msg: 'Prijava #%{id} uspešno obdelana'
       quick_actions_description_html: 'Opravite hitro dejanje ali podrsajte navzdol, da si ogledate prijavljeno vsebino:'
       remote_user_placeholder: oddaljeni uporabnik iz %{instance}
       reopen: Ponovno odpri prijavo
@@ -641,9 +644,28 @@ sl:
       status: Stanje
       statuses: Prijavljena vsebina
       statuses_description_html: Žaljiva vsebina bo citirana v komunikaciji z računom iz prijave
+      summary:
+        action_preambles:
+          delete_html: 'Nekatere od objav uporabnika/ce <strong>@%{acct}</strong> boste <strong>odstranili</strong>. S tem boste:'
+          mark_as_sensitive_html: 'Nekatere od objav uporabnika/ce <strong>@%{acct}</strong> boste <strong>označili</strong> kot <strong>občutljive</strong>. S tem boste:'
+          silence_html: 'Račun uporabnika/ce <strong>@%{acct}</strong> boste <strong>omejili</strong>. S tem boste:'
+          suspend_html: "<strong>Suspendirali</strong> boste račun uporabnika/ce <strong>@%{acct}</strong>. S tem boste:"
+        actions:
+          delete_html: Odstrani žaljive objave
+          mark_as_sensitive_html: Označi večpredstavnost žaljivih objav kot občutljivo
+          silence_html: Močno omejite doseg osebe <strong>@%{acct}</strong>, tako da naredite njihov profil in vsebino vidno samo osebam, ki jih že spremljajo ali ročno poiščejo profil
+          suspend_html: Suspendirajte <strong>@%{acct}</strong>, s čimer postaneta njihov profil in vsebina nedostopna in z njim ni mogoče komunicirati
+        close_report: 'Označi prijavo #%{id} kot rešeno'
+        close_reports_html: Označi <strong>vse</strong> prijave zoper <strong>@%{acct}</strong> kot rešene
+        delete_data_html: Izbriši profil in vsebine <strong>@%{acct}</strong> čez 30 dni, razen če suspenz v tem času ni preklican
+        preview_preamble_html: 'Oseba <strong>@%{acct}</strong> bo prejela opozorilo z naslednjo vsebino:'
+        record_strike_html: Izdajte opomin računu <strong>@%{acct}</strong>, da boste lažje stopnjevali svoj odziv ob prihodnjih kršitvah s tega računa
+        send_email_html: Pošlji <strong>@%{acct}</strong> opozorilno e-sporočilo
+        warning_placeholder: Neobvezna dodatna utemeljitev dejanja moderiranja.
       target_origin: Izvor prijavljenega računa
       title: Prijave
       unassign: Odstopljeni
+      unknown_action_msg: 'Neznano dejanje: %{action}'
       unresolved: Nerešeni
       updated_at: Posodobljeni
       view_profile: Pokaži profil
@@ -979,6 +1001,8 @@ sl:
   auth:
     apply_for_account: Zaprosite za račun
     change_password: Geslo
+    confirmations:
+      wrong_email_hint: Če ta e-poštni naslov ni pravilen, ga lahko spremenite v nastavitvah računa.
     delete_account: Izbriši račun
     delete_account_html: Če želite izbrisati svoj račun, lahko nadaljujete <a href="%{path}">tukaj</a>. Prosili vas bomo za potrditev.
     description:
diff --git a/config/locales/sq.yml b/config/locales/sq.yml
index 8b8f663aa..961a14d47 100644
--- a/config/locales/sq.yml
+++ b/config/locales/sq.yml
@@ -441,6 +441,7 @@ sq:
         private_comment_description_html: 'Për t’ju ndihmuar të ndiqni se nga vijnë bllokimet e importuara, këto do të krijohen me komentin vijues privat: <q>%{comment}</q>'
         private_comment_template: Importuar nga %{source} më %{date}
         title: Importoni bllokime përkatësish
+      invalid_domain_block: 'U anashkalua një ose më tepër bllokime përkatësish, për shkak të gabimit(eve) vijues: %{error}'
       new:
         title: Importoni bllokime përkatësish
       no_file: S’u përzgjodh kartelë
@@ -588,6 +589,7 @@ sq:
       comment:
         none: Asnjë
       comment_description_html: 'Për të dhënë më tepër informacion, %{name} shkroi:'
+      confirm_action: Ripohoni veprim moderimi kundër @%{acct}
       created_at: Raportuar më
       delete_and_resolve: Fshiji postimet
       forwarded: U përcoll
@@ -604,6 +606,7 @@ sq:
         placeholder: Përshkruani ç’veprime janë ndërmarrë, ose çfarëdo përditësimi tjetër që lidhet me të…
         title: Shënime
       notes_description_html: Shihni dhe lini shënime për moderatorët e tjerë dhe për veten në të ardhmen
+      processed_msg: 'Raportimi #%{id} u përpunua me sukses'
       quick_actions_description_html: 'Kryeni një veprim të shpejtë, ose rrëshqitni poshtë për të parë lëndën e raportuar:'
       remote_user_placeholder: përdoruesi i largët prej %{instance}
       reopen: Rihape raportimin
@@ -616,9 +619,28 @@ sq:
       status: Gjendje
       statuses: Lëndë e raportuar
       statuses_description_html: Lënda problematike do të citohet në komunikimin me llogarinë e raportuar
+      summary:
+        action_preambles:
+          delete_html: 'Ju ndan një hap nga <strong>heqja</strong> e disa postimeve të <strong>@%{acct}</strong>. Kjo do të sjellë:'
+          mark_as_sensitive_html: 'Ju ndan një hap nga <strong>vënia shenjë</strong> disa postimeve të <strong>@%{acct}</strong> si <strong>me spec</strong>. Kjo do të sjellë:'
+          silence_html: 'Ju ndan një hap nga <strong>kufizimi</strong> i llogarisë së <strong>@%{acct}</strong>. Kjo do të sjellë:'
+          suspend_html: 'Ju ndan një hap nga <strong>pezullimi</strong> i llogarisë së <strong>@%{acct}</strong>. Kjo do të sjellë:'
+        actions:
+          delete_html: Hiqi postimet fyese
+          mark_as_sensitive_html: Vëru shenjë si me spec mediave të postimeve fyese
+          silence_html: Kufizoje fort shtrirjen e <strong>@%{acct}</strong>, duke e bërë profilin dhe lëndën e tij të dukshme vetëm për persona që e ndjekin tashmë, ose që kërkojnë dorazi për profilin e tij
+          suspend_html: Pezulloje <strong>@%{acct}</strong>, duke e bërë profilin dhe lëndën e tij të pahapshme dhe të pamundur ndërveprimin me të
+        close_report: 'Vëri shenjë raportimit #%{id} si të zgjidhur'
+        close_reports_html: Vëru shenjë <strong>krejt</strong> raportimeve kundër <strong>@%{acct}</strong> si të zgjidhur
+        delete_data_html: Fshije profilin e <strong>@%{acct}</strong> dhe lëndën e 30 ditëve nga sot, veç në u pezulloftë ndërkohë
+        preview_preamble_html: "<strong>@%{acct}</strong> do të marrë një sinjalizim me lëndën vijuese:"
+        record_strike_html: Regjistroni një vërejtje kundër <strong>@%{acct}</strong> për t’ju ndihmuar të përshkallëzoni qëndrim në ras cenimesh të ardhshme nga kjo llogari
+        send_email_html: Dërgojini <strong>@%{acct}</strong> një vërejtje me email
+        warning_placeholder: Arsye shtesë, në daçi, për veprimin e moderimit.
       target_origin: Origjinë e llogarisë së raportuar
       title: Raportime
       unassign: Hiqja
+      unknown_action_msg: 'Veprim i panjohur: %{action}'
       unresolved: Të pazgjidhur
       updated_at: U përditësua më
       view_profile: Shihni profilin
@@ -938,6 +960,8 @@ sq:
   auth:
     apply_for_account: Kërkoni një llogari
     change_password: Fjalëkalim
+    confirmations:
+      wrong_email_hint: Nëse ajo adresë email s’është e saktë, mund ta ndryshoni te rregullimet e llogarisë.
     delete_account: Fshije llogarinë
     delete_account_html: Nëse dëshironi të fshihni llogarinë tuaj, mund <a href="%{path}">ta bëni që këtu</a>. Do t’ju kërkohet ta ripohoni.
     description:
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index 112716576..8558843d1 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -441,6 +441,7 @@ sv:
         private_comment_description_html: 'För att hjälpa dig spåra var importerade blockeringar kommer från kommer importerade blockeringar att skapas med följande privata kommentar: <q>%{comment}</q>'
         private_comment_template: Importerad från %{source} den %{date}
         title: Importera domänblockeringar
+      invalid_domain_block: 'Ett eller flera domänblock hoppades över på grund av följande fel: %{error}'
       new:
         title: Importera domänblockeringar
       no_file: Ingen fil vald
@@ -589,6 +590,7 @@ sv:
       comment:
         none: Ingen
       comment_description_html: 'För att ge mer information, skrev %{name}:'
+      confirm_action: Bekräfta modereringsåtgärd mot @%{acct}
       created_at: Anmäld
       delete_and_resolve: Ta bort inlägg
       forwarded: Vidarebefordrad
@@ -605,6 +607,7 @@ sv:
         placeholder: Beskriv vilka åtgärder som vidtagits eller andra uppdateringar till den här anmälan.
         title: Anteckningar
       notes_description_html: Visa och lämna anteckningar till andra moderatorer och ditt framtida jag
+      processed_msg: 'Rapporten #%{id} har behandlats'
       quick_actions_description_html: 'Ta en snabb åtgärd eller bläddra ner för att se rapporterat innehåll:'
       remote_user_placeholder: fjärranvändaren från %{instance}
       reopen: Återuppta anmälan
@@ -617,6 +620,14 @@ sv:
       status: Status
       statuses: Rapporterat innehåll
       statuses_description_html: Stötande innehåll kommer att citeras i kommunikationen med det rapporterade kontot
+      summary:
+        action_preambles:
+          delete_html: 'Du håller på att <strong>ta bort</strong> några av <strong>@%{acct}</strong>s inlägg. Detta kommer:'
+          mark_as_sensitive_html: 'Du håller på att <strong>markera</strong> några av <strong>@%{acct}</strong>s inlägg som <strong>känsliga</strong>. Detta kommer:'
+          silence_html: 'Du håller på att <strong>begränsa</strong> <strong>@%{acct}</strong>s konto. Detta kommer:'
+          suspend_html: 'Du håller på att <strong>stänga av</strong> <strong>@%{acct}</strong>s konto. Detta kommer:'
+        actions:
+          delete_html: Ta bort kränkande inlägg
       target_origin: Ursprung för anmält konto
       title: Anmälningar
       unassign: Otilldela
diff --git a/config/locales/th.yml b/config/locales/th.yml
index a1ed899e6..0135574ff 100644
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -433,6 +433,7 @@ th:
         private_comment_description_html: 'เพื่อช่วยให้คุณติดตามว่าการปิดกั้นที่นำเข้ามาจากที่ใด จะสร้างการปิดกั้นที่นำเข้าโดยมีความคิดเห็นส่วนตัวดังต่อไปนี้: <q>%{comment}</q>'
         private_comment_template: นำเข้าจาก %{source} เมื่อ %{date}
         title: นำเข้าการปิดกั้นโดเมน
+      invalid_domain_block: 'มีการข้ามการปิดกั้นโดเมนจำนวนหนึ่งหรือมากกว่าเนื่องจากข้อผิดพลาดดังต่อไปนี้: %{error}'
       new:
         title: นำเข้าการปิดกั้นโดเมน
       no_file: ไม่ได้เลือกไฟล์
@@ -562,10 +563,10 @@ th:
         delete_description_html: จะลบโพสต์ที่รายงานและจะบันทึกการดำเนินการเพื่อช่วยให้คุณเลื่อนระดับการละเมิดในอนาคตโดยบัญชีเดียวกัน
         mark_as_sensitive_description_html: จะทำเครื่องหมายสื่อในโพสต์ที่รายงานว่าละเอียดอ่อนและจะบันทึกการดำเนินการเพื่อช่วยให้คุณเลื่อนระดับการละเมิดในอนาคตโดยบัญชีเดียวกัน
         other_description_html: ดูตัวเลือกเพิ่มเติมสำหรับการควบคุมพฤติกรรมของบัญชีและปรับแต่งการสื่อสารไปยังบัญชีที่รายงาน
-        resolve_description_html: จะไม่ใช้การกระทำกับบัญชีที่รายงาน ไม่มีการบันทึกการดำเนินการ และจะปิดรายงาน
+        resolve_description_html: จะไม่ใช้การกระทำต่อบัญชีที่รายงาน ไม่มีการบันทึกการดำเนินการ และจะปิดรายงาน
         silence_description_html: บัญชีจะปรากฏแก่เฉพาะผู้ที่ติดตามโปรไฟล์อยู่แล้วหรือค้นหาโปรไฟล์ด้วยตนเองเท่านั้น จำกัดการเข้าถึงโปรไฟล์อย่างมาก สามารถแปลงกลับได้เสมอ ปิดรายงานต่อบัญชีนี้ทั้งหมด
         suspend_description_html: บัญชีและเนื้อหาของบัญชีทั้งหมดจะเข้าถึงไม่ได้และได้รับการลบในที่สุด และการโต้ตอบกับบัญชีจะเป็นไปไม่ได้ แปลงกลับได้ภายใน 30 วัน ปิดรายงานต่อบัญชีนี้ทั้งหมด
-      actions_description_html: ตัดสินใจว่าการกระทำใดที่จะใช้เพื่อแก้ปัญหารายงานนี้ หากคุณใช้การกระทำที่เป็นการลงโทษกับบัญชีที่รายงาน จะส่งการแจ้งเตือนอีเมลถึงเขา ยกเว้นเมื่อมีการเลือกหมวดหมู่ <strong>สแปม</strong>
+      actions_description_html: ตัดสินใจว่าการกระทำใดที่จะใช้เพื่อแก้ปัญหารายงานนี้ หากคุณใช้การกระทำที่เป็นการลงโทษต่อบัญชีที่รายงาน จะส่งการแจ้งเตือนอีเมลถึงเขา ยกเว้นเมื่อมีการเลือกหมวดหมู่ <strong>สแปม</strong>
       actions_description_remote_html: ตัดสินใจว่าการกระทำใดที่จะใช้เพื่อแก้ปัญหารายงานนี้ นี่จะมีผลต่อวิธีที่เซิร์ฟเวอร์ <strong>ของคุณ</strong> สื่อสารกับบัญชีระยะไกลนี้และจัดการเนื้อหาของบัญชีเท่านั้น
       add_to_report: เพิ่มข้อมูลเพิ่มเติมไปยังรายงาน
       are_you_sure: คุณแน่ใจหรือไม่?
@@ -577,6 +578,7 @@ th:
       comment:
         none: ไม่มี
       comment_description_html: 'เพื่อให้ข้อมูลเพิ่มเติม %{name} ได้เขียน:'
+      confirm_action: ยืนยันการกระทำการควบคุมต่อ @%{acct}
       created_at: รายงานเมื่อ
       delete_and_resolve: ลบโพสต์
       forwarded: ส่งต่อแล้ว
@@ -593,6 +595,7 @@ th:
         placeholder: อธิบายว่าการกระทำใดที่ใช้ หรือการอัปเดตที่เกี่ยวข้องอื่นใด...
         title: หมายเหตุ
       notes_description_html: ดูและฝากหมายเหตุถึงผู้ควบคุมอื่น ๆ และตัวคุณเองในอนาคต
+      processed_msg: 'ประมวลผลรายงาน #%{id} สำเร็จ'
       quick_actions_description_html: 'ดำเนินการอย่างรวดเร็วหรือเลื่อนลงเพื่อดูเนื้อหาที่รายงาน:'
       remote_user_placeholder: ผู้ใช้ระยะไกลจาก %{instance}
       reopen: เปิดรายงานใหม่
@@ -605,9 +608,28 @@ th:
       status: สถานะ
       statuses: เนื้อหาที่รายงาน
       statuses_description_html: จะอ้างถึงเนื้อหาที่ละเมิดในการสื่อสารกับบัญชีที่ได้รับการรายงาน
+      summary:
+        action_preambles:
+          delete_html: 'คุณกำลังจะ <strong>เอา</strong> โพสต์บางส่วนของ <strong>@%{acct}</strong> <strong>ออก</strong> นี่จะ:'
+          mark_as_sensitive_html: 'คุณกำลังจะ <strong>ทำเครื่องหมาย</strong> โพสต์บางส่วนของ <strong>@%{acct}</strong> ว่า <strong>ละเอียดอ่อน</strong> นี่จะ:'
+          silence_html: 'คุณกำลังจะ <strong>จำกัด</strong> บัญชีของ <strong>@%{acct}</strong> นี่จะ:'
+          suspend_html: 'คุณกำลังจะ <strong>ระงับ</strong> บัญชีของ <strong>@%{acct}</strong> นี่จะ:'
+        actions:
+          delete_html: เอาโพสต์ที่ละเมิดออก
+          mark_as_sensitive_html: ทำเครื่องหมายสื่อของโพสต์ที่ละเมิดว่าละเอียดอ่อน
+          silence_html: จำกัดการเข้าถึงของ <strong>@%{acct}</strong> อย่างมากโดยทำให้โปรไฟล์และเนื้อหาของเขาปรากฏแก่เฉพาะผู้คนที่กำลังติดตามเขาอยู่แล้วหรือค้นหาโปรไฟล์ของบัญชีด้วยตนเองเท่านั้น
+          suspend_html: ระงับ <strong>@%{acct}</strong> ทำให้โปรไฟล์และเนื้อหาของเขาเข้าถึงไม่ได้และไม่สามารถโต้ตอบด้วย
+        close_report: 'ทำเครื่องหมายรายงาน #%{id} ว่าแก้ปัญหาแล้ว'
+        close_reports_html: ทำเครื่องหมายรายงาน <strong>ทั้งหมด</strong> ต่อ <strong>@%{acct}</strong> ว่าแก้ปัญหาแล้ว
+        delete_data_html: ลบโปรไฟล์และเนื้อหาของ <strong>@%{acct}</strong> ในอีก 30 วันนับจากนี้เว้นแต่มีการเลิกระงับเขาในระหว่างนี้
+        preview_preamble_html: "<strong>@%{acct}</strong> จะได้รับคำเตือนโดยมีเนื้อหาดังต่อไปนี้:"
+        record_strike_html: บันทึกการดำเนินการต่อ <strong>@%{acct}</strong> เพื่อช่วยให้คุณเลื่อนระดับการละเมิดในอนาคตจากบัญชีนี้
+        send_email_html: ส่งอีเมลคำเตือนถึง <strong>@%{acct}</strong>
+        warning_placeholder: การให้เหตุผลเพิ่มเติมที่ไม่จำเป็นสำหรับการกระทำการควบคุม
       target_origin: จุดเริ่มต้นของบัญชีที่ได้รับการรายงาน
       title: รายงาน
       unassign: เลิกมอบหมาย
+      unknown_action_msg: 'การกระทำที่ไม่รู้จัก: %{action}'
       unresolved: ยังไม่ได้แก้ปัญหา
       updated_at: อัปเดตเมื่อ
       view_profile: ดูโปรไฟล์
@@ -648,7 +670,7 @@ th:
         manage_invites: จัดการคำเชิญ
         manage_invites_description: อนุญาตให้ผู้ใช้เรียกดูและปิดใช้งานลิงก์เชิญ
         manage_reports: จัดการรายงาน
-        manage_reports_description: อนุญาตให้ผู้ใช้ตรวจทานรายงานและทำการกระทำการควบคุมกับรายงาน
+        manage_reports_description: อนุญาตให้ผู้ใช้ตรวจทานรายงานและทำการกระทำการควบคุมต่อรายงาน
         manage_roles: จัดการบทบาท
         manage_roles_description: อนุญาตให้ผู้ใช้จัดการและกำหนดบทบาทที่ต่ำกว่าบทบาทของเขา
         manage_rules: จัดการกฎ
@@ -660,7 +682,7 @@ th:
         manage_user_access: จัดการการเข้าถึงของผู้ใช้
         manage_user_access_description: อนุญาตให้ผู้ใช้ปิดใช้งานการรับรองความถูกต้องด้วยสองปัจจัยของผู้ใช้อื่น เปลี่ยนที่อยู่อีเมลของเขา และตั้งรหัสผ่านของเขาใหม่
         manage_users: จัดการผู้ใช้
-        manage_users_description: อนุญาตให้ผู้ใช้ดูรายละเอียดของผู้ใช้อื่น ๆ และทำการกระทำการควบคุมกับผู้ใช้
+        manage_users_description: อนุญาตให้ผู้ใช้ดูรายละเอียดของผู้ใช้อื่น ๆ และทำการกระทำการควบคุมต่อผู้ใช้
         manage_webhooks: จัดการเว็บฮุค
         manage_webhooks_description: อนุญาตให้ผู้ใช้ตั้งค่าเว็บฮุคสำหรับเหตุการณ์การดูแล
         view_audit_log: ดูรายการบันทึกการตรวจสอบ
@@ -925,6 +947,8 @@ th:
   auth:
     apply_for_account: ขอบัญชี
     change_password: รหัสผ่าน
+    confirmations:
+      wrong_email_hint: หากที่อยู่อีเมลนั้นไม่ถูกต้อง คุณสามารถเปลี่ยนที่อยู่อีเมลได้ในการตั้งค่าบัญชี
     delete_account: ลบบัญชี
     delete_account_html: หากคุณต้องการลบบัญชีของคุณ คุณสามารถ <a href="%{path}">ดำเนินการต่อที่นี่</a> คุณจะได้รับการถามเพื่อการยืนยัน
     description:
@@ -972,7 +996,7 @@ th:
       functional: บัญชีของคุณทำงานได้อย่างเต็มที่
       pending: ใบสมัครของคุณกำลังรอดำเนินการตรวจทานโดยพนักงานของเรา นี่อาจใช้เวลาสักครู่ คุณจะได้รับอีเมลหากมีการอนุมัติใบสมัครของคุณ
       redirecting_to: บัญชีของคุณไม่ได้ใช้งานเนื่องจากบัญชีกำลังเปลี่ยนเส้นทางไปยัง %{acct} ในปัจจุบัน
-      view_strikes: ดูการดำเนินการที่ผ่านมากับบัญชีของคุณ
+      view_strikes: ดูการดำเนินการที่ผ่านมาต่อบัญชีของคุณ
     too_fast: ส่งแบบฟอร์มเร็วเกินไป ลองอีกครั้ง
     use_security_key: ใช้กุญแจความปลอดภัย
   authorize_follow:
@@ -1044,7 +1068,7 @@ th:
       approve_appeal: อนุมัติการอุทธรณ์
       associated_report: รายงานที่เกี่ยวข้อง
       created_at: ลงวันที่
-      description_html: นี่คือการกระทำที่ใช้กับบัญชีของคุณและคำเตือนที่ส่งถึงคุณโดยพนักงานของ %{instance}
+      description_html: นี่คือการกระทำที่ใช้ต่อบัญชีของคุณและคำเตือนที่ส่งถึงคุณโดยพนักงานของ %{instance}
       recipient: ส่งถึง
       reject_appeal: ปฏิเสธการอุทธรณ์
       status: 'โพสต์ #%{id}'
@@ -1556,11 +1580,11 @@ th:
   user_mailer:
     appeal_approved:
       action: ไปยังบัญชีของคุณ
-      explanation: อนุมัติการอุทธรณ์การดำเนินการกับบัญชีของคุณเมื่อ %{strike_date} ที่คุณได้ส่งเมื่อ %{appeal_date} แล้ว บัญชีของคุณอยู่ในสถานะที่ดีอีกครั้งหนึ่ง
+      explanation: อนุมัติการอุทธรณ์การดำเนินการต่อบัญชีของคุณเมื่อ %{strike_date} ที่คุณได้ส่งเมื่อ %{appeal_date} แล้ว บัญชีของคุณอยู่ในสถานะที่ดีอีกครั้งหนึ่ง
       subject: อนุมัติการอุทธรณ์ของคุณจาก %{date} แล้ว
       title: อนุมัติการอุทธรณ์แล้ว
     appeal_rejected:
-      explanation: ปฏิเสธการอุทธรณ์การดำเนินการกับบัญชีของคุณเมื่อ %{strike_date} ที่คุณได้ส่งเมื่อ %{appeal_date} แล้ว
+      explanation: ปฏิเสธการอุทธรณ์การดำเนินการต่อบัญชีของคุณเมื่อ %{strike_date} ที่คุณได้ส่งเมื่อ %{appeal_date} แล้ว
       subject: ปฏิเสธการอุทธรณ์ของคุณจาก %{date} แล้ว
       title: ปฏิเสธการอุทธรณ์แล้ว
     backup_ready:
@@ -1581,7 +1605,7 @@ th:
         spam: สแปม
         violation: เนื้อหาละเมิดหลักเกณฑ์ชุมชนดังต่อไปนี้
       explanation:
-        delete_statuses: มีการพบว่าโพสต์บางส่วนของคุณละเมิดหนึ่งหลักเกณฑ์ชุมชนหรือมากกว่าและได้รับการเอาออกโดยผู้ควบคุมของ %{instance} ในเวลาต่อมา
+        delete_statuses: มีการพบว่าโพสต์บางส่วนของคุณละเมิดหลักเกณฑ์ชุมชนจำนวนหนึ่งหรือมากกว่าและได้รับการเอาออกโดยผู้ควบคุมของ %{instance} ในเวลาต่อมา
         disable: คุณไม่สามารถใช้บัญชีของคุณได้อีกต่อไป แต่โปรไฟล์และข้อมูลอื่น ๆ ของคุณยังคงอยู่ในสภาพเดิม คุณสามารถขอข้อมูลสำรองของข้อมูลของคุณ เปลี่ยนการตั้งค่าบัญชี หรือลบบัญชีของคุณ
         mark_statuses_as_sensitive: ทำเครื่องหมายโพสต์บางส่วนของคุณว่าละเอียดอ่อนโดยผู้ควบคุมของ %{instance} แล้ว นี่หมายความว่าผู้คนจะต้องแตะสื่อในโพสต์ก่อนที่จะแสดงตัวอย่าง คุณสามารถทำเครื่องหมายสื่อว่าละเอียดอ่อนด้วยตัวคุณเองเมื่อโพสต์ในอนาคต
         sensitive: จากนี้ไป จะทำเครื่องหมายไฟล์สื่อที่อัปโหลดทั้งหมดของคุณว่าละเอียดอ่อนและซ่อนอยู่หลังการคลิกไปยังคำเตือน
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index 2baa5dd25..c4379552c 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -441,6 +441,7 @@ tr:
         private_comment_description_html: 'İçe aktarılan engellerin nereden geldiğini izlemenize olanak sağlamak için, içe aktarılan engeller şu özel yorum ile oluşturulacak: <q>%{comment}</q>'
         private_comment_template: "%{source} kaynağından %{date} tarihinde içe aktarıldı"
         title: Domain bloklarını içe aktar
+      invalid_domain_block: 'Bir veya daha fazla alan adı engeli şu hata(lar)dan dolayı atlandı: %{error}'
       new:
         title: Domain bloklarını içe aktar
       no_file: Dosya seçilmedi
@@ -589,6 +590,7 @@ tr:
       comment:
         none: Yok
       comment_description_html: 'Daha fazla bilgi vermek için %{name} şunu yazdı:'
+      confirm_action: "%{acct} üzerindeki denetleme eylemini onayla"
       created_at: Şikayet edildi
       delete_and_resolve: Gönderileri sil
       forwarded: İletildi
@@ -605,6 +607,7 @@ tr:
         placeholder: Hangi işlemlerin yapıldığını, ya da diğer ilgili güncellemeleri açıklayın...
         title: Notlar
       notes_description_html: Kendiniz ve diğer moderatörler için not bırakın veya notları görüntüleyin
+      processed_msg: "#%{id} Bildirimi başarıyla işlendi"
       quick_actions_description_html: 'Hemen bir şey yapın veya bildirilen içeriği görmek için aşağı kaydırın:'
       remote_user_placeholder: "%{instance}'dan uzak kullanıcı"
       reopen: Şikayeti tekrar aç
@@ -617,9 +620,24 @@ tr:
       status: Durum
       statuses: Bildirilen içerik
       statuses_description_html: İncitici içerik, bildirilen hesapla iletişimde alıntılanacaktır
+      summary:
+        action_preambles:
+          delete_html: "<strong>@%{acct}</strong> hesabının bazı gönderilerini <strong>kaldıracaksınız</strong>, böylece:"
+          mark_as_sensitive_html: "<strong>@%{acct}</strong> hesabının bazı gönderilerini <strong>hassas</strong> olarak <strong>işaretleyeceksiniz</strong>, böylece:"
+          silence_html: "<strong>@%{acct}</strong> hesabını <strong>sınırlayacaksınız</strong>, böylece:"
+          suspend_html: "<strong>@%{acct}</strong> hesabını <strong>askıya alacaksınız</strong>, böylece:"
+        actions:
+          delete_html: Kuralı ihlal eden gönderileri kaldır
+          mark_as_sensitive_html: Kuralı ihlal eden gönderilerin medyasını hassas olarak işaretle
+          suspend_html: "<strong>@%{acct}</strong> hesabını askıya al, profilini ve içeriğini erişilmez ve etkileşimi imkansız yap"
+        close_report: "#%{id} bildirimini çözüldü olarak işaretle"
+        close_reports_html: "<strong>@%{acct}</strong> hesabına yönelik <strong>tüm</strong> bildirimleri çözüldü olarak işaretle"
+        delete_data_html: İlgili sürede askıdan alınması kaldırılmazsa <strong>@%{acct}</strong> hesabının profilini ve içeriğini şu andan itibaren 30 gün içinde sil
+        preview_preamble_html: "<strong>@%{acct}</strong> aşağıdaki içerikle bir uyarı alacaktır:"
       target_origin: Şikayet edilen hesabın kökeni
       title: Şikayetler
       unassign: Atamayı geri al
+      unknown_action_msg: 'Bilinmeyen eylem: %{action}'
       unresolved: Giderilmedi
       updated_at: Güncellendi
       view_profile: Profili görüntüle
@@ -943,6 +961,8 @@ tr:
   auth:
     apply_for_account: Bir hesap talep et
     change_password: Parola
+    confirmations:
+      wrong_email_hint: Eğer bu e-posta adresi doğru değilse, hesap ayarlarında değiştirebilirsiniz.
     delete_account: Hesabı sil
     delete_account_html: Hesabını silmek istersen, <a href="%{path}">buradan devam edebilirsin</a>. Onay istenir.
     description:
diff --git a/config/locales/uk.yml b/config/locales/uk.yml
index 557074ed1..dd6d42daa 100644
--- a/config/locales/uk.yml
+++ b/config/locales/uk.yml
@@ -457,6 +457,7 @@ uk:
         private_comment_description_html: 'Щоб допомогти вам відстежувати, звідки беруться імпортовані блокування, вони створюються за допомогою наступного приватного коментаря: <q>%{comment}</q>'
         private_comment_template: Імпортовано з %{source} %{date}
         title: Імпорт блокувань домену
+      invalid_domain_block: 'Один або кілька доменних блоків пропущено через такі помилки: %{error}'
       new:
         title: Імпорт блокувань домену
       no_file: Файл не вибрано
@@ -613,6 +614,7 @@ uk:
       comment:
         none: Немає
       comment_description_html: 'Щоб надати більше відомостей, %{name} пише:'
+      confirm_action: Підтвердьте модераційну дію щодо @%{acct}
       created_at: Створено
       delete_and_resolve: Видалити дописи
       forwarded: Переслано
@@ -629,6 +631,7 @@ uk:
         placeholder: Опишіть, які дії були виконані, або інші зміни, що стосуються справи...
         title: Примітки
       notes_description_html: Переглядайте та залишайте примітки для інших модераторів та для себе на майбутнє
+      processed_msg: 'Звіт #%{id} успішно оброблено'
       quick_actions_description_html: 'Виберіть швидку дію або гортайте вниз, щоб побачити матеріал, на який надійшла скарга:'
       remote_user_placeholder: віддалений користувач із %{instance}
       reopen: Перевідкрити скаргу
@@ -641,9 +644,28 @@ uk:
       status: Стан
       statuses: Вміст, на який поскаржилися
       statuses_description_html: Замінений вміст буде цитований у спілкуванні з обліковим записом, на який поскаржилися
+      summary:
+        action_preambles:
+          delete_html: 'Ви збираєтеся <strong>вилучити</strong> деякі з дописів <strong>@%{acct}</strong>. Це буде:'
+          mark_as_sensitive_html: 'Ви збираєтеся <strong>позначити</strong> деякі з дописів <strong>@%{acct}</strong> <strong>делікатними</strong>. Це буде:'
+          silence_html: 'Ви збираєтеся <strong>обмежити</strong> обліковий запис <strong>@%{acct}</strong>. Це буде:'
+          suspend_html: 'Ви збираєтесь <strong>призупинити</strong> обліковий запис <strong>@%%{acct}</strong>. Це буде:'
+        actions:
+          delete_html: Вилучити образливі дописи
+          mark_as_sensitive_html: Позначити медіа образливих дописів делікатними
+          silence_html: Досягнуто жорсткого обмеження <strong>@%{acct}</strong>, зробивши їхній профіль і вміст видимим тільки людям, які вже слідують за ними або вручну шукають його профіль
+          suspend_html: Призупинити <strong>@%{acct}</strong>, зробити їх профіль і вміст недоступним та унеможливити взаємодію
+        close_report: 'Позначити звіт #%{id} розв''язаним'
+        close_reports_html: Позначити <strong>всі</strong> звіти проти <strong>@%{acct}</strong> розв'язаними
+        delete_data_html: Видаліть профіль <strong>@%{acct}</strong> і вміст за останні 30 днів, якщо вони не дія не скасується тим часом
+        preview_preamble_html: "<strong>@%{acct}</strong> отримає попередження з таким вмістом:"
+        record_strike_html: Запис попередження проти <strong>@%{acct}</strong>, що допоможе вам посилити ваші майбутні санкції проти цього облікового запису
+        send_email_html: Надіслати <strong>@%{acct}</strong> попереджувального електронного листа
+        warning_placeholder: Додаткові причини дії модерації.
       target_origin: Походження облікового запису, на який скаржаться
       title: Скарги
       unassign: Зняти призначення
+      unknown_action_msg: 'Невідома дія: %{action}'
       unresolved: Невирішені
       updated_at: Оновлені
       view_profile: Переглянути профіль
@@ -979,6 +1001,8 @@ uk:
   auth:
     apply_for_account: Запит облікового запису
     change_password: Пароль
+    confirmations:
+      wrong_email_hint: Якщо ця адреса електронної пошти неправильна, можна змінити її в налаштуваннях облікового запису.
     delete_account: Видалити обліковий запис
     delete_account_html: Якщо ви хочете видалити свій обліковий запис, ви можете <a href="%{path}">перейти сюди</a>. Вас попросять підтвердити дію.
     description:
diff --git a/config/locales/uz.yml b/config/locales/uz.yml
new file mode 100644
index 000000000..94260c666
--- /dev/null
+++ b/config/locales/uz.yml
@@ -0,0 +1,51 @@
+---
+uz:
+  about:
+    title: Haqida
+  accounts:
+    follow: Obuna bo‘lish
+  admin:
+    accounts:
+      display_name: Ko'rsatiladigan nom
+      domain: Domen
+      email: Email
+      email_status: Email holati
+      enable: Muzlatishtan olish
+      enabled: Yoqilgan
+      followers: Obunachilar
+      follows: Obuna
+      invited_by: Tomonidan taklif qilingan
+      ip: IP
+      joined: Qo'shilgan
+      location:
+        all: Barchasi
+        local: Mahalliy
+        remote: Masofadagi
+        title: Qayerda
+      login_status: Login holati
+      media_attachments: Media qo'shimchalari
+      memorialize: Xotiraga aylantiring
+      memorialized: Yodga olingan
+      memorialized_msg: "%{username} esdalik hisobiga muvaffaqiyatli aylantirildi"
+      moderation:
+        active: Aktiv
+        all: Barchasi
+        pending: Navbatdagi
+        silenced: Cheklangan
+        suspended: To'xtatilgan
+        title: Moderatsiya
+      moderation_notes: Moderatsiya eslatmalari
+      most_recent_activity: Eng oxirgi faoliyat
+      most_recent_ip: Eng oxirgi IP
+      perform_full_suspension: To'xtatilgan
+      reject: Rad etish
+  errors:
+    '400': The request you submitted was invalid or malformed.
+    '403': You don't have permission to view this page.
+    '404': The page you are looking for isn't here.
+    '406': This page is not available in the requested format.
+    '410': The page you were looking for doesn't exist here anymore.
+    '422': 
+    '429': Too many requests
+    '500': 
+    '503': The page could not be served due to a temporary server failure.
diff --git a/config/locales/vi.yml b/config/locales/vi.yml
index 43e000721..341776109 100644
--- a/config/locales/vi.yml
+++ b/config/locales/vi.yml
@@ -433,6 +433,7 @@ vi:
         private_comment_description_html: 'Để giúp bạn theo dõi nguồn gốc của các chặn tên miền đã nhập, các tên miền chặn đã nhập sẽ được tạo bằng nhận xét riêng tư sau: <q>%{comment}</q>'
         private_comment_template: Nhập từ %{source} vào %{date}
         title: Nhập máy chủ chặn
+      invalid_domain_block: 'Một hoặc nhiều tên miền đã bị bỏ qua do (các) lỗi sau: %{error}'
       new:
         title: Nhập máy chủ chặn
       no_file: Không có tập tin nào được chọn
@@ -577,6 +578,7 @@ vi:
       comment:
         none: Không có mô tả
       comment_description_html: "%{name} cho biết thêm:"
+      confirm_action: Xác nhận kiểm duyệt với %{acct}
       created_at: Báo cáo lúc
       delete_and_resolve: Xóa tút
       forwarded: Chuyển tiếp
@@ -593,6 +595,7 @@ vi:
         placeholder: Mô tả vi phạm của người này, hướng xử lý và những cập nhật liên quan khác...
         title: Lưu ý
       notes_description_html: Xem và để lại lưu ý cho các kiểm duyệt viên khác
+      processed_msg: 'Báo cáo #%{id} đã được xử lý thành công'
       quick_actions_description_html: 'Kiểm duyệt nhanh hoặc kéo xuống để xem nội dung bị báo cáo:'
       remote_user_placeholder: người ở %{instance}
       reopen: Mở lại báo cáo
@@ -605,9 +608,28 @@ vi:
       status: Trạng thái
       statuses: Nội dung bị báo cáo
       statuses_description_html: Lý do tài khoản hoặc nội dung này bị báo cáo sẽ được trích dẫn khi giao tiếp với họ
+      summary:
+        action_preambles:
+          delete_html: 'Bạn sắp <strong>xóa</strong> vài tút của <strong>@%{acct}</strong>. Việc này sẽ:'
+          mark_as_sensitive_html: 'Bạn sắp <strong>đánh dấu</strong> vài tút của <strong>@%{acct}</strong> là <strong>nhạy cảm</strong>. Việc này sẽ:'
+          silence_html: 'Bạn sắp <strong>hạn chế</strong> <strong>@%{acct}</strong>. Việc này sẽ:'
+          suspend_html: 'Bạn sắp <strong>vô hiệu hóa</strong> <strong>@%{acct}</strong>. Việc này sẽ:'
+        actions:
+          delete_html: Xóa các tút vi phạm
+          mark_as_sensitive_html: Đánh dấu media trong tút vi phạm là nhạy cảm
+          silence_html: Hạn chế mức ảnh hưởng của <strong>@%{acct}</strong> bằng cách làm cho trang và nội dung của họ chỉ hiển thị với những người đã theo dõi họ hoặc tìm kiếm theo cách thủ công
+          suspend_html: Vô hiệu hóa <strong>@%{acct}</strong>, làm cho trang và nội dung của họ không thể truy cập và không thể tương tác
+        close_report: 'Đánh dấu báo cáo #%{id} đã xử lý xong'
+        close_reports_html: Đánh dấu <strong>tất cả</strong> báo cáo chống lại <strong>@%{acct}</strong> đã xử lý xong
+        delete_data_html: Xóa trang <strong>@%{acct}</strong> và nội dung 30 ngày kể từ bây giờ trừ khi bỏ vô hiệu hóa
+        preview_preamble_html: "<strong>@%{acct}</strong> sẽ nhận được cảnh báo với nội dung như sau:"
+        record_strike_html: Lưu lại cảnh cáo <strong>@%{acct}</strong> để giúp bạn đánh giá các vi phạm trong tương lai từ tài khoản này
+        send_email_html: Gửi <strong>@%{acct}</strong> một email cảnh báo
+        warning_placeholder: Lý do bổ sung cho hành động kiểm duyệt.
       target_origin: Nguồn báo cáo
       title: Báo cáo
       unassign: Bỏ qua
+      unknown_action_msg: 'Hành động chưa biết: %{action}'
       unresolved: Chờ xử lý
       updated_at: Cập nhật lúc
       view_profile: Xem trang
@@ -925,6 +947,8 @@ vi:
   auth:
     apply_for_account: Xin đăng ký
     change_password: Mật khẩu
+    confirmations:
+      wrong_email_hint: Nếu địa chỉ email đó không chính xác, bạn có thể thay đổi nó trong cài đặt tài khoản.
     delete_account: Xóa tài khoản
     delete_account_html: Nếu bạn muốn xóa tài khoản của mình, hãy <a href="%{path}">yêu cầu tại đây</a>. Bạn sẽ được yêu cầu xác nhận.
     description:
diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml
index 49ec79799..78b0f5d13 100644
--- a/config/locales/zh-CN.yml
+++ b/config/locales/zh-CN.yml
@@ -57,7 +57,7 @@ zh-CN:
       destroyed_msg: "%{username} 的数据已被安排至删除队列"
       disable: 冻结
       disable_sign_in_token_auth: 禁用电子邮件令牌认证
-      disable_two_factor_authentication: 停用两步认证
+      disable_two_factor_authentication: 停用双重认证
       disabled: 已冻结
       display_name: 昵称
       domain: 域名
@@ -137,8 +137,8 @@ zh-CN:
       sensitized: 已标记为敏感内容
       shared_inbox_url: 公用收件箱(Shared Inbox)URL
       show:
-        created_reports: 这个帐户提交的举报
-        targeted_reports: 针对这个帐户的举报
+        created_reports: 这个账户提交的举报
+        targeted_reports: 针对这个账户的举报
       silence: 隐藏
       silenced: 已隐藏
       statuses: 嘟文
@@ -433,6 +433,7 @@ zh-CN:
         private_comment_description_html: 为了帮助您追踪域名列表来源,导入的域名列表将被添加如下的私人注释:<q>%{comment}</q>
         private_comment_template: 从 %{source} 导入 %{date}
         title: 导入域名列表
+      invalid_domain_block: 由于以下错误,一个或多个域名屏蔽被跳过: %{error}
       new:
         title: 导入域名列表
       no_file: 没有选择文件
@@ -577,6 +578,7 @@ zh-CN:
       comment:
         none: 没有
       comment_description_html: "%{name} 补充道:"
+      confirm_action: 确认对 @%{acct} 的管理操作
       created_at: 举报时间
       delete_and_resolve: 删除嘟文
       forwarded: 已转发
@@ -593,6 +595,7 @@ zh-CN:
         placeholder: 描述已经执行的操作,或其他任何相关的跟进情况…
         title: 备注
       notes_description_html: 查看备注或向其他监察员留言
+      processed_msg: '举报 #%{id} 处理成功'
       quick_actions_description_html: 快捷选择操作或向下滚动以查看举报内容:
       remote_user_placeholder: 来自 %{instance} 的远程实例用户
       reopen: 重开举报
@@ -605,9 +608,28 @@ zh-CN:
       status: 状态
       statuses: 被举报内容
       statuses_description_html: 在与该账号的通信中将引用违规内容
+      summary:
+        action_preambles:
+          delete_html: 您即将<strong>删除</strong> <strong>@%{acct}</strong> 的一些帖子。 这将:
+          mark_as_sensitive_html: 您即将 <strong>标记</strong> <strong>@%{acct}</strong> 的帖一些子为 <strong>敏感</strong>。这将:
+          silence_html: 您即将<strong>限制</strong> <strong>@%{acct}</strong> 的帐户。 这将:
+          suspend_html: 您即将<strong>暂停</strong> <strong>@%{acct}</strong> 的帐户。 这将:
+        actions:
+          delete_html: 删除违规帖子
+          mark_as_sensitive_html: 将违规帖子的媒体标记为敏感
+          silence_html: 严格限制 <strong>@%{acct}</strong> 的影响力,方法是让他们的个人资料和内容仅对已经关注他们的人可见,或手动查找其个人资料时
+          suspend_html: 暂停 <strong>@%{acct}</strong>,使他们的个人资料和内容无法访问,也无法与之互动
+        close_report: '将报告 #%{id} 标记为已解决'
+        close_reports_html: 将针对 <strong>@%{acct}</strong> 的<strong>所有</strong> 报告标记为已解决
+        delete_data_html: 从现在起 30 天后删除 <strong>@%{acct}</strong> 的个人资料和内容,除非他们同时解除暂停。
+        preview_preamble_html: "<strong>@%{acct}</strong> 将收到包含以下内容的警告:"
+        record_strike_html: 记录一次针对 <strong>@%{acct}</strong> 的警示,以帮助您在这个帐户上的未来违规事件中得到重视。
+        send_email_html: 向 <strong>@%{acct}</strong> 发送警告电子邮件
+        warning_placeholder: 可选的补充理由,以说明调整的情况。
       target_origin: 被举报账号的来源
       title: 举报
       unassign: 取消接管
+      unknown_action_msg: 未知操作:%{action}
       unresolved: 未处理
       updated_at: 更新时间
       view_profile: 查看资料
@@ -872,7 +894,7 @@ zh-CN:
       next_steps: 你可以批准此申诉并撤销该审核结果,也可以忽略此申诉。
       subject: "%{username} 对 %{instance} 的审核结果提出了申诉"
     new_pending_account:
-      body: 新帐户的详细信息如下。你可以批准或拒绝此申请。
+      body: 新账户的详细信息如下。你可以批准或拒绝此申请。
       subject: 在 %{instance} 上有新账号 (%{username}) 需要审核
     new_report:
       body: "%{reporter} 举报了用户 %{target}"
@@ -925,6 +947,8 @@ zh-CN:
   auth:
     apply_for_account: 申请账号
     change_password: 密码
+    confirmations:
+      wrong_email_hint: 如果该电子邮件地址不正确,您可以在帐户设置中进行更改。
     delete_account: 删除帐户
     delete_account_html: 如果你想删除你的帐户,请<a href="%{path}">点击这里继续</a>。你需要确认你的操作。
     description:
@@ -971,7 +995,7 @@ zh-CN:
       confirming: 等待电子邮件确认完成。
       functional: 你的账号可以正常使用了。
       pending: 工作人员正在审核你的申请。这需要花点时间。在申请被批准后,你将收到一封电子邮件。
-      redirecting_to: 你的帐户无效,因为它已被设置为跳转到 %{acct}
+      redirecting_to: 你的账户无效,因为它已被设置为跳转到 %{acct}
       view_strikes: 查看针对你账号的记录
     too_fast: 表单提交过快,请重试。
     use_security_key: 使用安全密钥
@@ -1027,7 +1051,7 @@ zh-CN:
       email_change_html: 你可以 <a href="%{path}">更换邮箱地址</a> 无需删除账号
       email_contact_html: 如果它还没送到,你可以发邮件给 <a href="mailto:%{email}">%{email}</a> 寻求帮助。
       email_reconfirmation_html: 如果你没有收到确认邮件,请点击 <a href="%{path}">重新发送</a> 。
-      irreversible: 你将无法恢复或重新激活你的帐户
+      irreversible: 你将无法恢复或重新激活你的账户
       more_details_html: 更多细节,请查看 <a href="%{terms_path}">隐私政策</a> 。
       username_available: 你的用户名现在又可以使用了
       username_unavailable: 你的用户名仍将无法使用
@@ -1240,13 +1264,13 @@ zh-CN:
     past_migrations: 迁移记录
     proceed_with_move: 移动关注者
     redirected_msg: 你的账号现在会跳转至 %{acct}
-    redirecting_to: 你的帐户被跳转到了 %{acct}。
+    redirecting_to: 你的账户正在跳转到 %{acct}。
     set_redirect: 设置跳转
     warning:
       backreference_required: 新账号必须先引用当前账号
       before: 在继续前,请仔细阅读下列说明:
       cooldown: 移动后会有一个冷却期,在此期间你将无法再次移动
-      disabled_account: 此后,你的当前帐户将无法使用。但是,你仍然有权导出数据或者重新激活。
+      disabled_account: 此后,你的当前账户将无法使用。但是,你仍然有权导出数据或者重新激活。
       followers: 这步操作将把所有关注者从当前账户移动到新账户
       only_redirect_html: 或者,你可以<a href="%{path}">只在你的账号资料上设置一个跳转</a>。
       other_data: 不会自动移动其它数据
@@ -1591,19 +1615,19 @@ zh-CN:
       statuses: 被引用的嘟文:
       subject:
         delete_statuses: 你在 %{acct} 的嘟文已被删除
-        disable: 你的帐户 %{acct} 已被冻结
+        disable: 你的账户 %{acct} 已被冻结
         mark_statuses_as_sensitive: 你在 %{acct} 的嘟文已被标记为敏感内容
         none: 对 %{acct} 的警告
         sensitive: 你在 %{acct} 的嘟文今后将被标记为敏感内容
-        silence: 你的帐户 %{acct} 已被隐藏
-        suspend: 你的帐户 %{acct} 已被封禁。
+        silence: 你的账户 %{acct} 已被隐藏
+        suspend: 你的账户 %{acct} 已被封禁
       title:
         delete_statuses: 嘟文已删除
         disable: 账号已冻结
         mark_statuses_as_sensitive: 嘟文已被标记为敏感内容
         none: 警示
         sensitive: 账户已被标记为敏感内容
-        silence: 帐户被隐藏
+        silence: 账户被隐藏
         suspend: 账号被封禁
     welcome:
       edit_profile_action: 设置个人资料
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index f618c8fa4..97169cfde 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -23,7 +23,7 @@ zh-TW:
   admin:
     account_actions:
       action: 執行動作
-      title: 在 %{acct} 執行管理員動作
+      title: 對 %{acct} 執行站務動作
     account_moderation_notes:
       create: 記錄
       created_msg: 已新增管理備忘!
@@ -373,8 +373,8 @@ zh-TW:
       undo: 從聯邦宇宙白名單移除
     domain_blocks:
       add_new: 新增欲封鎖域名
-      created_msg: 正在進行站點封鎖
-      destroyed_msg: 已撤銷站點封鎖
+      created_msg: 正在進行網域封鎖
+      destroyed_msg: 已撤銷網域封鎖
       domain: 站點
       edit: 更改封鎖的站台
       existing_domain_block: 您已對 %{name} 施加了更嚴格的限制。
@@ -401,7 +401,7 @@ zh-TW:
       reject_media: 拒絕媒體檔案
       reject_media_hint: 刪除本地快取的媒體檔案,並且不再接收來自該站點的任何媒體檔案。與停權無關
       reject_reports: 拒絕檢舉
-      reject_reports_hint: 忽略所有來自此站點的檢舉。與停權無關
+      reject_reports_hint: 忽略所有來自此網域的檢舉。與停權無關
       undo: 復原欲封鎖域名
       view: 顯示阻擋的網域
     email_domain_blocks:
@@ -413,9 +413,9 @@ zh-TW:
       dns:
         types:
           mx: MX 記錄
-      domain: 站點
+      domain: 網域
       new:
-        create: 新增站點
+        create: 新增網域
         resolve: 解析網域
         title: 新增電子郵件黑名單項目
       no_email_domain_block_selected: 因未選取項目,而未更改電子郵件網域黑名單
@@ -433,6 +433,7 @@ zh-TW:
         private_comment_description_html: 為了幫助您追蹤匯入黑名單之來源,匯入黑名單建立時將隨附以下私密備註:<q>%{comment}</q>
         private_comment_template: 於 %{date} 由 %{source} 匯入
         title: 匯入網域黑名單
+      invalid_domain_block: 由於此錯誤,以致一個或多個網域封鎖被略過:%{error}
       new:
         title: 匯入網域黑名單
       no_file: 尚未選擇檔案
@@ -579,6 +580,7 @@ zh-TW:
       comment:
         none: 無
       comment_description_html: 提供更多資訊,%{name} 寫道:
+      confirm_action: 確認對 @%{acct} 執行站務動作
       created_at: 日期
       delete_and_resolve: 刪除嘟文
       forwarded: 已轉寄
@@ -595,6 +597,7 @@ zh-TW:
         placeholder: 記錄已執行的動作,或其他相關的更新...
         title: 註記
       notes_description_html: 檢視及留下些給其他管理員和未來的自己的註記
+      processed_msg: '檢舉報告 #%{id} 已被成功處理'
       quick_actions_description_html: 採取一個快速行動,或者下捲以檢視檢舉內容:
       remote_user_placeholder: 來自 %{instance} 之遠端使用者
       reopen: 重開檢舉
@@ -607,9 +610,28 @@ zh-TW:
       status: 嘟文
       statuses: 被檢舉的內容
       statuses_description_html: 侵犯性違規內容會被引用在檢舉帳號通知中
+      summary:
+        action_preambles:
+          delete_html: 您將要 <strong>移除</strong> 某些 <strong>@%{acct}</strong> 之嘟文。此將會:
+          mark_as_sensitive_html: 您將要 <strong>標記</strong> 某些 <strong>@%{acct}</strong> 之嘟文為 <strong>敏感內容 </strong>。此將會:
+          silence_html: 您將要 <strong>限制</strong> <strong>@%{acct}</strong> 之帳號。此將會:
+          suspend_html: 您將要 <strong>暫停</strong> <strong>@%{acct}</strong> 之帳號。此將會:
+        actions:
+          delete_html: 移除違反規則之嘟文
+          mark_as_sensitive_html: 將違反規則之嘟文多媒體標記為敏感內容
+          silence_html: 藉由標記他們的個人檔案與內容為僅可見於已跟隨帳號或手動查詢此個人檔案,此將嚴格地限制 <strong>@%{acct}</strong> 之觸及率
+          suspend_html: 暫停 <strong>@%{acct}</strong>,將他們的個人檔案與內容標記為無法存取及無法與之互動
+        close_report: '將檢舉報告 #%{id} 標記為已處理'
+        close_reports_html: 將 <strong>所有</strong> 對於 <strong>@%{acct}</strong> 之檢舉報告標記為已處理
+        delete_data_html: 於即日起 30 天後刪除 <strong>@%{acct}</strong>之個人檔案與內容,除非他們於期限前被解除暫停
+        preview_preamble_html: "<strong>@%{acct}</strong> 將收到關於以下內容之警告:"
+        record_strike_html: 紀錄關於 <strong>@%{acct}</strong>之警示有助於您升級對此帳號未來違規處理
+        send_email_html: 寄一封警告 e-mail 給 <strong>@%{acct}</strong>
+        warning_placeholder: 選填之其他站務動作理由。
       target_origin: 檢舉帳號之來源
       title: 檢舉
       unassign: 取消指派
+      unknown_action_msg: 未知的動作:%{action}
       unresolved: 未解決
       updated_at: 更新
       view_profile: 檢視個人檔案頁面
@@ -927,6 +949,8 @@ zh-TW:
   auth:
     apply_for_account: 申請帳號
     change_password: 密碼
+    confirmations:
+      wrong_email_hint: 若電子郵件地址不正確,您可以於帳號設定中更改。
     delete_account: 刪除帳號
     delete_account_html: 如果您欲刪除您的帳號,請<a href="%{path}">點擊這裡繼續</a>。您需要再三確認您的操作。
     description:
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index ab122f4d6..9cb7a4411 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -17,7 +17,7 @@ module Mastodon
     end
 
     def flags
-      'rc2'
+      'rc3'
     end
 
     def suffix
diff --git a/package.json b/package.json
index 3542f45ff..726e5d2c0 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
     "start": "node ./streaming/index.js",
     "test": "${npm_execpath} run test:lint:js && ${npm_execpath} run test:jest",
     "test:lint": "${npm_execpath} run test:lint:js && ${npm_execpath} run test:lint:sass",
-    "test:lint:js": "eslint --ext=js . --cache",
+    "test:lint:js": "eslint --ext=js . --cache --report-unused-disable-directives",
     "test:lint:sass": "stylelint \"**/*.{css,scss}\"",
     "test:jest": "cross-env NODE_ENV=test jest",
     "format": "prettier --write \"**/*.{json,yml}\"",
@@ -39,7 +39,7 @@
     "atrament": "0.2.4",
     "arrow-key-navigation": "^1.2.0",
     "autoprefixer": "^9.8.8",
-    "axios": "^1.2.3",
+    "axios": "^1.2.6",
     "babel-loader": "^8.3.0",
     "babel-plugin-lodash": "^3.3.4",
     "babel-plugin-preval": "^5.1.0",
@@ -69,7 +69,6 @@
     "http-link-header": "^1.1.0",
     "immutable": "^4.2.2",
     "imports-loader": "^1.2.0",
-    "intersection-observer": "^0.12.2",
     "intl": "^1.2.5",
     "intl-messageformat": "^2.2.0",
     "intl-relativeformat": "^6.4.3",
@@ -80,7 +79,7 @@
     "mark-loader": "^0.1.6",
     "marky": "^1.2.5",
     "mini-css-extract-plugin": "^1.6.2",
-    "mkdirp": "^1.0.4",
+    "mkdirp": "^2.1.3",
     "npmlog": "^7.0.1",
     "object-assign": "^4.1.1",
     "object.values": "^1.1.6",
@@ -90,7 +89,7 @@
     "postcss-loader": "^3.0.0",
     "promise.prototype.finally": "^3.1.4",
     "prop-types": "^15.8.1",
-    "punycode": "^2.2.0",
+    "punycode": "^2.3.0",
     "react": "^16.14.0",
     "react-dom": "^16.14.0",
     "react-helmet": "^6.1.0",
@@ -111,13 +110,13 @@
     "react-textarea-autosize": "^8.4.0",
     "react-toggle": "^4.1.3",
     "redis": "^4.0.6 <4.1.0",
-    "redux": "^4.2.0",
+    "redux": "^4.2.1",
     "redux-immutable": "^4.0.0",
     "redux-thunk": "^2.4.2",
     "regenerator-runtime": "^0.13.11",
     "requestidlecallback": "^0.3.0",
     "reselect": "^4.1.7",
-    "rimraf": "^4.1.1",
+    "rimraf": "^4.1.2",
     "sass": "^1.57.1",
     "sass-loader": "^10.2.0",
     "stacktrace-js": "^2.0.2",
@@ -147,14 +146,14 @@
     "@babel/eslint-parser": "^7.19.1",
     "@testing-library/jest-dom": "^5.16.5",
     "@testing-library/react": "^12.1.5",
-    "babel-jest": "^29.3.1",
-    "eslint": "^7.32.0",
-    "eslint-plugin-import": "~2.26.0",
-    "eslint-plugin-jsx-a11y": "~6.6.1",
+    "babel-jest": "^29.4.1",
+    "eslint": "^8.33.0",
+    "eslint-plugin-import": "~2.27.5",
+    "eslint-plugin-jsx-a11y": "~6.7.1",
     "eslint-plugin-promise": "~6.1.1",
-    "eslint-plugin-react": "~7.32.1",
-    "jest": "^29.3.1",
-    "jest-environment-jsdom": "^29.3.1",
+    "eslint-plugin-react": "~7.32.2",
+    "jest": "^29.4.1",
+    "jest-environment-jsdom": "^29.4.1",
     "postcss-scss": "^4.0.6",
     "prettier": "^2.8.3",
     "raf": "^3.4.1",
@@ -170,6 +169,6 @@
   },
   "optionalDependencies": {
     "bufferutil": "^4.0.7",
-    "utf-8-validate": "^6.0.1"
+    "utf-8-validate": "^6.0.2"
   }
 }
diff --git a/spec/controllers/settings/applications_controller_spec.rb b/spec/controllers/settings/applications_controller_spec.rb
index 29c278148..1292e9ff8 100644
--- a/spec/controllers/settings/applications_controller_spec.rb
+++ b/spec/controllers/settings/applications_controller_spec.rb
@@ -132,7 +132,7 @@ describe Settings::ApplicationsController do
       end
 
       it 'redirects back to applications page' do
-        expect(call_update).to redirect_to(settings_applications_path)
+        expect(call_update).to redirect_to(settings_application_path(app))
       end
     end
 
diff --git a/yarn.lock b/yarn.lock
index f28878c0d..87619a1b8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -23,13 +23,6 @@
     jsonpointer "^5.0.0"
     leven "^3.1.0"
 
-"@babel/code-frame@7.12.11":
-  version "7.12.11"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
-  integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==
-  dependencies:
-    "@babel/highlight" "^7.10.4"
-
 "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6":
   version "7.18.6"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
@@ -305,7 +298,7 @@
     "@babel/traverse" "^7.20.7"
     "@babel/types" "^7.20.7"
 
-"@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6":
+"@babel/highlight@^7.18.6":
   version "7.18.6"
   resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
   integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==
@@ -1037,7 +1030,7 @@
   dependencies:
     regenerator-runtime "^0.12.0"
 
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.9", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
   version "7.20.13"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b"
   integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==
@@ -1171,19 +1164,19 @@
   resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
   integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
 
-"@eslint/eslintrc@^0.4.3":
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c"
-  integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==
+"@eslint/eslintrc@^1.4.1":
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e"
+  integrity sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==
   dependencies:
     ajv "^6.12.4"
-    debug "^4.1.1"
-    espree "^7.3.0"
-    globals "^13.9.0"
-    ignore "^4.0.6"
+    debug "^4.3.2"
+    espree "^9.4.0"
+    globals "^13.19.0"
+    ignore "^5.2.0"
     import-fresh "^3.2.1"
-    js-yaml "^3.13.1"
-    minimatch "^3.0.4"
+    js-yaml "^4.1.0"
+    minimatch "^3.1.2"
     strip-json-comments "^3.1.1"
 
 "@floating-ui/core@^1.0.1":
@@ -1220,19 +1213,24 @@
   resolved "https://registry.yarnpkg.com/@github/webauthn-json/-/webauthn-json-0.5.7.tgz#143bc67f6e0f75f8d188e565741507bb08c31214"
   integrity sha512-SUYsttDxFSvWvvJssJpwzjmRCqYfdfqC9VCmAHQYfdKCVelyJteCHo9/lK1CB72mx/jrl6cFNY08aua4J2jIyg==
 
-"@humanwhocodes/config-array@^0.5.0":
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9"
-  integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==
+"@humanwhocodes/config-array@^0.11.8":
+  version "0.11.8"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9"
+  integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==
   dependencies:
-    "@humanwhocodes/object-schema" "^1.2.0"
+    "@humanwhocodes/object-schema" "^1.2.1"
     debug "^4.1.1"
-    minimatch "^3.0.4"
+    minimatch "^3.0.5"
 
-"@humanwhocodes/object-schema@^1.2.0":
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf"
-  integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==
+"@humanwhocodes/module-importer@^1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
+  integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
+
+"@humanwhocodes/object-schema@^1.2.1":
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
+  integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
 
 "@istanbuljs/load-nyc-config@^1.0.0":
   version "1.1.0"
@@ -1250,109 +1248,109 @@
   resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
   integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
 
-"@jest/console@^29.3.1":
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.3.1.tgz#3e3f876e4e47616ea3b1464b9fbda981872e9583"
-  integrity sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==
+"@jest/console@^29.4.1":
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.4.1.tgz#cbc31d73f6329f693b3d34b365124de797704fff"
+  integrity sha512-m+XpwKSi3PPM9znm5NGS8bBReeAJJpSkL1OuFCqaMaJL2YX9YXLkkI+MBchMPwu+ZuM2rynL51sgfkQteQ1CKQ==
   dependencies:
-    "@jest/types" "^29.3.1"
+    "@jest/types" "^29.4.1"
     "@types/node" "*"
     chalk "^4.0.0"
-    jest-message-util "^29.3.1"
-    jest-util "^29.3.1"
+    jest-message-util "^29.4.1"
+    jest-util "^29.4.1"
     slash "^3.0.0"
 
-"@jest/core@^29.3.1":
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.3.1.tgz#bff00f413ff0128f4debec1099ba7dcd649774a1"
-  integrity sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==
-  dependencies:
-    "@jest/console" "^29.3.1"
-    "@jest/reporters" "^29.3.1"
-    "@jest/test-result" "^29.3.1"
-    "@jest/transform" "^29.3.1"
-    "@jest/types" "^29.3.1"
+"@jest/core@^29.4.1":
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.4.1.tgz#91371179b5959951e211dfaeea4277a01dcca14f"
+  integrity sha512-RXFTohpBqpaTebNdg5l3I5yadnKo9zLBajMT0I38D0tDhreVBYv3fA8kywthI00sWxPztWLD3yjiUkewwu/wKA==
+  dependencies:
+    "@jest/console" "^29.4.1"
+    "@jest/reporters" "^29.4.1"
+    "@jest/test-result" "^29.4.1"
+    "@jest/transform" "^29.4.1"
+    "@jest/types" "^29.4.1"
     "@types/node" "*"
     ansi-escapes "^4.2.1"
     chalk "^4.0.0"
     ci-info "^3.2.0"
     exit "^0.1.2"
     graceful-fs "^4.2.9"
-    jest-changed-files "^29.2.0"
-    jest-config "^29.3.1"
-    jest-haste-map "^29.3.1"
-    jest-message-util "^29.3.1"
+    jest-changed-files "^29.4.0"
+    jest-config "^29.4.1"
+    jest-haste-map "^29.4.1"
+    jest-message-util "^29.4.1"
     jest-regex-util "^29.2.0"
-    jest-resolve "^29.3.1"
-    jest-resolve-dependencies "^29.3.1"
-    jest-runner "^29.3.1"
-    jest-runtime "^29.3.1"
-    jest-snapshot "^29.3.1"
-    jest-util "^29.3.1"
-    jest-validate "^29.3.1"
-    jest-watcher "^29.3.1"
+    jest-resolve "^29.4.1"
+    jest-resolve-dependencies "^29.4.1"
+    jest-runner "^29.4.1"
+    jest-runtime "^29.4.1"
+    jest-snapshot "^29.4.1"
+    jest-util "^29.4.1"
+    jest-validate "^29.4.1"
+    jest-watcher "^29.4.1"
     micromatch "^4.0.4"
-    pretty-format "^29.3.1"
+    pretty-format "^29.4.1"
     slash "^3.0.0"
     strip-ansi "^6.0.0"
 
-"@jest/environment@^29.3.1":
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.3.1.tgz#eb039f726d5fcd14698acd072ac6576d41cfcaa6"
-  integrity sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==
+"@jest/environment@^29.4.1":
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.4.1.tgz#52d232a85cdc995b407a940c89c86568f5a88ffe"
+  integrity sha512-pJ14dHGSQke7Q3mkL/UZR9ZtTOxqskZaC91NzamEH4dlKRt42W+maRBXiw/LWkdJe+P0f/zDR37+SPMplMRlPg==
   dependencies:
-    "@jest/fake-timers" "^29.3.1"
-    "@jest/types" "^29.3.1"
+    "@jest/fake-timers" "^29.4.1"
+    "@jest/types" "^29.4.1"
     "@types/node" "*"
-    jest-mock "^29.3.1"
+    jest-mock "^29.4.1"
 
-"@jest/expect-utils@^29.3.1":
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.3.1.tgz#531f737039e9b9e27c42449798acb5bba01935b6"
-  integrity sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==
+"@jest/expect-utils@^29.4.1":
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.4.1.tgz#105b9f3e2c48101f09cae2f0a4d79a1b3a419cbb"
+  integrity sha512-w6YJMn5DlzmxjO00i9wu2YSozUYRBhIoJ6nQwpMYcBMtiqMGJm1QBzOf6DDgRao8dbtpDoaqLg6iiQTvv0UHhQ==
   dependencies:
     jest-get-type "^29.2.0"
 
-"@jest/expect@^29.3.1":
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.3.1.tgz#456385b62894349c1d196f2d183e3716d4c6a6cd"
-  integrity sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==
+"@jest/expect@^29.4.1":
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.4.1.tgz#3338fa20f547bb6e550c4be37d6f82711cc13c38"
+  integrity sha512-ZxKJP5DTUNF2XkpJeZIzvnzF1KkfrhEF6Rz0HGG69fHl6Bgx5/GoU3XyaeFYEjuuKSOOsbqD/k72wFvFxc3iTw==
   dependencies:
-    expect "^29.3.1"
-    jest-snapshot "^29.3.1"
+    expect "^29.4.1"
+    jest-snapshot "^29.4.1"
 
-"@jest/fake-timers@^29.3.1":
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.3.1.tgz#b140625095b60a44de820876d4c14da1aa963f67"
-  integrity sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==
+"@jest/fake-timers@^29.4.1":
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.4.1.tgz#7b673131e8ea2a2045858f08241cace5d518b42b"
+  integrity sha512-/1joI6rfHFmmm39JxNfmNAO3Nwm6Y0VoL5fJDy7H1AtWrD1CgRtqJbN9Ld6rhAkGO76qqp4cwhhxJ9o9kYjQMw==
   dependencies:
-    "@jest/types" "^29.3.1"
-    "@sinonjs/fake-timers" "^9.1.2"
+    "@jest/types" "^29.4.1"
+    "@sinonjs/fake-timers" "^10.0.2"
     "@types/node" "*"
-    jest-message-util "^29.3.1"
-    jest-mock "^29.3.1"
-    jest-util "^29.3.1"
+    jest-message-util "^29.4.1"
+    jest-mock "^29.4.1"
+    jest-util "^29.4.1"
 
-"@jest/globals@^29.3.1":
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.3.1.tgz#92be078228e82d629df40c3656d45328f134a0c6"
-  integrity sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==
+"@jest/globals@^29.4.1":
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.4.1.tgz#3cd78c5567ab0249f09fbd81bf9f37a7328f4713"
+  integrity sha512-znoK2EuFytbHH0ZSf2mQK2K1xtIgmaw4Da21R2C/NE/+NnItm5mPEFQmn8gmF3f0rfOlmZ3Y3bIf7bFj7DHxAA==
   dependencies:
-    "@jest/environment" "^29.3.1"
-    "@jest/expect" "^29.3.1"
-    "@jest/types" "^29.3.1"
-    jest-mock "^29.3.1"
+    "@jest/environment" "^29.4.1"
+    "@jest/expect" "^29.4.1"
+    "@jest/types" "^29.4.1"
+    jest-mock "^29.4.1"
 
-"@jest/reporters@^29.3.1":
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.3.1.tgz#9a6d78c109608e677c25ddb34f907b90e07b4310"
-  integrity sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==
+"@jest/reporters@^29.4.1":
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.4.1.tgz#50d509c08575c75e3cd2176d72ec3786419d5e04"
+  integrity sha512-AISY5xpt2Xpxj9R6y0RF1+O6GRy9JsGa8+vK23Lmzdy1AYcpQn5ItX79wJSsTmfzPKSAcsY1LNt/8Y5Xe5LOSg==
   dependencies:
     "@bcoe/v8-coverage" "^0.2.3"
-    "@jest/console" "^29.3.1"
-    "@jest/test-result" "^29.3.1"
-    "@jest/transform" "^29.3.1"
-    "@jest/types" "^29.3.1"
+    "@jest/console" "^29.4.1"
+    "@jest/test-result" "^29.4.1"
+    "@jest/transform" "^29.4.1"
+    "@jest/types" "^29.4.1"
     "@jridgewell/trace-mapping" "^0.3.15"
     "@types/node" "*"
     chalk "^4.0.0"
@@ -1365,20 +1363,20 @@
     istanbul-lib-report "^3.0.0"
     istanbul-lib-source-maps "^4.0.0"
     istanbul-reports "^3.1.3"
-    jest-message-util "^29.3.1"
-    jest-util "^29.3.1"
-    jest-worker "^29.3.1"
+    jest-message-util "^29.4.1"
+    jest-util "^29.4.1"
+    jest-worker "^29.4.1"
     slash "^3.0.0"
     string-length "^4.0.1"
     strip-ansi "^6.0.0"
     v8-to-istanbul "^9.0.1"
 
-"@jest/schemas@^29.0.0":
-  version "29.0.0"
-  resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.0.0.tgz#5f47f5994dd4ef067fb7b4188ceac45f77fe952a"
-  integrity sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==
+"@jest/schemas@^29.4.0":
+  version "29.4.0"
+  resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.0.tgz#0d6ad358f295cc1deca0b643e6b4c86ebd539f17"
+  integrity sha512-0E01f/gOZeNTG76i5eWWSupvSHaIINrTie7vCyjiYFKgzNdyEGd12BUv4oNBFHOqlHDbtoJi3HrQ38KCC90NsQ==
   dependencies:
-    "@sinclair/typebox" "^0.24.1"
+    "@sinclair/typebox" "^0.25.16"
 
 "@jest/source-map@^29.2.0":
   version "29.2.0"
@@ -1389,46 +1387,46 @@
     callsites "^3.0.0"
     graceful-fs "^4.2.9"
 
-"@jest/test-result@^29.3.1":
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.3.1.tgz#92cd5099aa94be947560a24610aa76606de78f50"
-  integrity sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==
+"@jest/test-result@^29.4.1":
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.4.1.tgz#997f19695e13b34779ceb3c288a416bd26c3238d"
+  integrity sha512-WRt29Lwt+hEgfN8QDrXqXGgCTidq1rLyFqmZ4lmJOpVArC8daXrZWkWjiaijQvgd3aOUj2fM8INclKHsQW9YyQ==
   dependencies:
-    "@jest/console" "^29.3.1"
-    "@jest/types" "^29.3.1"
+    "@jest/console" "^29.4.1"
+    "@jest/types" "^29.4.1"
     "@types/istanbul-lib-coverage" "^2.0.0"
     collect-v8-coverage "^1.0.0"
 
-"@jest/test-sequencer@^29.3.1":
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz#fa24b3b050f7a59d48f7ef9e0b782ab65123090d"
-  integrity sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==
+"@jest/test-sequencer@^29.4.1":
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.4.1.tgz#f7a006ec7058b194a10cf833c88282ef86d578fd"
+  integrity sha512-v5qLBNSsM0eHzWLXsQ5fiB65xi49A3ILPSFQKPXzGL4Vyux0DPZAIN7NAFJa9b4BiTDP9MBF/Zqc/QA1vuiJ0w==
   dependencies:
-    "@jest/test-result" "^29.3.1"
+    "@jest/test-result" "^29.4.1"
     graceful-fs "^4.2.9"
-    jest-haste-map "^29.3.1"
+    jest-haste-map "^29.4.1"
     slash "^3.0.0"
 
-"@jest/transform@^29.3.1":
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.3.1.tgz#1e6bd3da4af50b5c82a539b7b1f3770568d6e36d"
-  integrity sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==
+"@jest/transform@^29.4.1":
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.4.1.tgz#e4f517841bb795c7dcdee1ba896275e2c2d26d4a"
+  integrity sha512-5w6YJrVAtiAgr0phzKjYd83UPbCXsBRTeYI4BXokv9Er9CcrH9hfXL/crCvP2d2nGOcovPUnlYiLPFLZrkG5Hg==
   dependencies:
     "@babel/core" "^7.11.6"
-    "@jest/types" "^29.3.1"
+    "@jest/types" "^29.4.1"
     "@jridgewell/trace-mapping" "^0.3.15"
     babel-plugin-istanbul "^6.1.1"
     chalk "^4.0.0"
     convert-source-map "^2.0.0"
     fast-json-stable-stringify "^2.1.0"
     graceful-fs "^4.2.9"
-    jest-haste-map "^29.3.1"
+    jest-haste-map "^29.4.1"
     jest-regex-util "^29.2.0"
-    jest-util "^29.3.1"
+    jest-util "^29.4.1"
     micromatch "^4.0.4"
     pirates "^4.0.4"
     slash "^3.0.0"
-    write-file-atomic "^4.0.1"
+    write-file-atomic "^5.0.0"
 
 "@jest/types@^25.5.0":
   version "25.5.0"
@@ -1451,12 +1449,12 @@
     "@types/yargs" "^16.0.0"
     chalk "^4.0.0"
 
-"@jest/types@^29.3.1":
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.3.1.tgz#7c5a80777cb13e703aeec6788d044150341147e3"
-  integrity sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==
+"@jest/types@^29.3.1", "@jest/types@^29.4.1":
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.4.1.tgz#f9f83d0916f50696661da72766132729dcb82ecb"
+  integrity sha512-zbrAXDUOnpJ+FMST2rV7QZOgec8rskg2zv8g2ajeqitp4tvZiyqTCYXANrKsM+ryj5o+LI+ZN2EgU9drrkiwSA==
   dependencies:
-    "@jest/schemas" "^29.0.0"
+    "@jest/schemas" "^29.4.0"
     "@types/istanbul-lib-coverage" "^2.0.0"
     "@types/istanbul-reports" "^3.0.0"
     "@types/node" "*"
@@ -1550,7 +1548,7 @@
   resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
   integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
 
-"@nodelib/fs.walk@^1.2.3":
+"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
   version "1.2.8"
   resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
   integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
@@ -1624,24 +1622,24 @@
     estree-walker "^1.0.1"
     picomatch "^2.2.2"
 
-"@sinclair/typebox@^0.24.1":
-  version "0.24.20"
-  resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.20.tgz#11a657875de6008622d53f56e063a6347c51a6dd"
-  integrity sha512-kVaO5aEFZb33nPMTZBxiPEkY+slxiPtqC7QX8f9B3eGOMBvEfuMfxp9DSTTCsRJPumPKjrge4yagyssO4q6qzQ==
+"@sinclair/typebox@^0.25.16":
+  version "0.25.21"
+  resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.21.tgz#763b05a4b472c93a8db29b2c3e359d55b29ce272"
+  integrity sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g==
 
-"@sinonjs/commons@^1.7.0":
-  version "1.8.1"
-  resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
-  integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==
+"@sinonjs/commons@^2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3"
+  integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==
   dependencies:
     type-detect "4.0.8"
 
-"@sinonjs/fake-timers@^9.1.2":
-  version "9.1.2"
-  resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c"
-  integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==
+"@sinonjs/fake-timers@^10.0.2":
+  version "10.0.2"
+  resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz#d10549ed1f423d80639c528b6c7f5a1017747d0c"
+  integrity sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==
   dependencies:
-    "@sinonjs/commons" "^1.7.0"
+    "@sinonjs/commons" "^2.0.0"
 
 "@surma/rollup-plugin-off-main-thread@^2.2.3":
   version "2.2.3"
@@ -2167,10 +2165,10 @@ acorn-globals@^7.0.0:
     acorn "^8.1.0"
     acorn-walk "^8.0.2"
 
-acorn-jsx@^5.3.1:
-  version "5.3.1"
-  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
-  integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
+acorn-jsx@^5.3.2:
+  version "5.3.2"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
+  integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
 
 acorn-walk@^8.0.0, acorn-walk@^8.0.2:
   version "8.2.0"
@@ -2182,16 +2180,16 @@ acorn@^6.4.1:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
   integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
 
-acorn@^7.4.0:
-  version "7.4.1"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
-  integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
-
 acorn@^8.0.4, acorn@^8.1.0, acorn@^8.5.0, acorn@^8.8.1:
   version "8.8.1"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73"
   integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==
 
+acorn@^8.8.0:
+  version "8.8.2"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
+  integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
+
 agent-base@6:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
@@ -2247,11 +2245,6 @@ ansi-colors@^3.0.0:
   resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
   integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
 
-ansi-colors@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
-  integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
-
 ansi-escapes@^4.2.1:
   version "4.3.1"
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
@@ -2348,10 +2341,12 @@ aria-query@^4.2.2:
     "@babel/runtime" "^7.10.2"
     "@babel/runtime-corejs3" "^7.10.2"
 
-aria-query@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c"
-  integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==
+aria-query@^5.0.0, aria-query@^5.1.3:
+  version "5.1.3"
+  resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e"
+  integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==
+  dependencies:
+    deep-equal "^2.0.5"
 
 arr-diff@^4.0.0:
   version "4.0.0"
@@ -2378,7 +2373,7 @@ array-flatten@^2.1.0:
   resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
   integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==
 
-array-includes@^3.1.4, array-includes@^3.1.5, array-includes@^3.1.6:
+array-includes@^3.1.5, array-includes@^3.1.6:
   version "3.1.6"
   resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f"
   integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==
@@ -2411,14 +2406,15 @@ array-unique@^0.3.2:
   resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
   integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
 
-array.prototype.flat@^1.2.5:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13"
-  integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==
+array.prototype.flat@^1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2"
+  integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==
   dependencies:
     call-bind "^1.0.2"
-    define-properties "^1.1.3"
-    es-abstract "^1.19.0"
+    define-properties "^1.1.4"
+    es-abstract "^1.20.4"
+    es-shim-unscopables "^1.0.0"
 
 array.prototype.flatmap@^1.3.1:
   version "1.3.1"
@@ -2539,34 +2535,41 @@ autoprefixer@^9.8.8:
     postcss "^7.0.32"
     postcss-value-parser "^4.1.0"
 
-axe-core@^4.4.3:
-  version "4.4.3"
-  resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f"
-  integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==
+available-typed-arrays@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
+  integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
 
-axios@^1.2.3:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.3.tgz#31a3d824c0ebf754a004b585e5f04a5f87e6c4ff"
-  integrity sha512-pdDkMYJeuXLZ6Xj/Q5J3Phpe+jbGdsSzlQaFVkMQzRUL05+6+tetX8TV3p4HrU4kzuO9bt+io/yGQxuyxA/xcw==
+axe-core@^4.6.2:
+  version "4.6.3"
+  resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.6.3.tgz#fc0db6fdb65cc7a80ccf85286d91d64ababa3ece"
+  integrity sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==
+
+axios@^1.2.6:
+  version "1.2.6"
+  resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.6.tgz#eacb6d065baa11bad5959e7ffa0cb6745c65f392"
+  integrity sha512-rC/7F08XxZwjMV4iuWv+JpD3E0Ksqg9nac4IIg6RwNuF0JTeWoCo/mBNG54+tNhhI11G3/VDRbdDQTs9hGp4pQ==
   dependencies:
     follow-redirects "^1.15.0"
     form-data "^4.0.0"
     proxy-from-env "^1.1.0"
 
-axobject-query@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
-  integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==
+axobject-query@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1"
+  integrity sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==
+  dependencies:
+    deep-equal "^2.0.5"
 
-babel-jest@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.3.1.tgz#05c83e0d128cd48c453eea851482a38782249f44"
-  integrity sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==
+babel-jest@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.4.1.tgz#01fa167e27470b35c2d4a1b841d9586b1764da19"
+  integrity sha512-xBZa/pLSsF/1sNpkgsiT3CmY7zV1kAsZ9OxxtrFqYucnOuRftXAfcJqcDVyOPeN4lttWTwhLdu0T9f8uvoPEUg==
   dependencies:
-    "@jest/transform" "^29.3.1"
+    "@jest/transform" "^29.4.1"
     "@types/babel__core" "^7.1.14"
     babel-plugin-istanbul "^6.1.1"
-    babel-preset-jest "^29.2.0"
+    babel-preset-jest "^29.4.0"
     chalk "^4.0.0"
     graceful-fs "^4.2.9"
     slash "^3.0.0"
@@ -2592,10 +2595,10 @@ babel-plugin-istanbul@^6.1.1:
     istanbul-lib-instrument "^5.0.4"
     test-exclude "^6.0.0"
 
-babel-plugin-jest-hoist@^29.2.0:
-  version "29.2.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz#23ee99c37390a98cfddf3ef4a78674180d823094"
-  integrity sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==
+babel-plugin-jest-hoist@^29.4.0:
+  version "29.4.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.4.0.tgz#3fd3dfcedf645932df6d0c9fc3d9a704dd860248"
+  integrity sha512-a/sZRLQJEmsmejQ2rPEUe35nO1+C9dc9O1gplH1SXmJxveQSRUYdBk8yGZG/VOUuZs1u2aHZJusEGoRMbhhwCg==
   dependencies:
     "@babel/template" "^7.3.3"
     "@babel/types" "^7.3.3"
@@ -2701,12 +2704,12 @@ babel-preset-current-node-syntax@^1.0.0:
     "@babel/plugin-syntax-optional-chaining" "^7.8.3"
     "@babel/plugin-syntax-top-level-await" "^7.8.3"
 
-babel-preset-jest@^29.2.0:
-  version "29.2.0"
-  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz#3048bea3a1af222e3505e4a767a974c95a7620dc"
-  integrity sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==
+babel-preset-jest@^29.4.0:
+  version "29.4.0"
+  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.4.0.tgz#c2b03c548b02dea0a18ae21d5759c136f9251ee4"
+  integrity sha512-fUB9vZflUSM3dO/6M2TCAepTzvA4VkOvl67PjErcrQMGt9Eve7uazaeyCZ2th3UtI7ljpiBJES0F7A1vBRsLZA==
   dependencies:
-    babel-plugin-jest-hoist "^29.2.0"
+    babel-plugin-jest-hoist "^29.4.0"
     babel-preset-current-node-syntax "^1.0.0"
 
 balanced-match@^1.0.0:
@@ -3864,14 +3867,14 @@ data-urls@^3.0.2:
     whatwg-mimetype "^3.0.0"
     whatwg-url "^11.0.0"
 
-debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
+debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
   dependencies:
     ms "2.0.0"
 
-debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.4:
+debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
   version "4.3.4"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
   integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -3925,6 +3928,29 @@ deep-equal@^1.0.1:
     object-keys "^1.1.1"
     regexp.prototype.flags "^1.2.0"
 
+deep-equal@^2.0.5:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.0.tgz#5caeace9c781028b9ff459f33b779346637c43e6"
+  integrity sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==
+  dependencies:
+    call-bind "^1.0.2"
+    es-get-iterator "^1.1.2"
+    get-intrinsic "^1.1.3"
+    is-arguments "^1.1.1"
+    is-array-buffer "^3.0.1"
+    is-date-object "^1.0.5"
+    is-regex "^1.1.4"
+    is-shared-array-buffer "^1.0.2"
+    isarray "^2.0.5"
+    object-is "^1.1.5"
+    object-keys "^1.1.1"
+    object.assign "^4.1.4"
+    regexp.prototype.flags "^1.4.3"
+    side-channel "^1.0.4"
+    which-boxed-primitive "^1.0.2"
+    which-collection "^1.0.1"
+    which-typed-array "^1.1.9"
+
 deep-is@^0.1.3, deep-is@~0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@@ -4289,13 +4315,6 @@ enhanced-resolve@^4.1.1, enhanced-resolve@^4.5.0:
     memory-fs "^0.5.0"
     tapable "^1.0.0"
 
-enquirer@^2.3.5:
-  version "2.3.6"
-  resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
-  integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
-  dependencies:
-    ansi-colors "^4.1.1"
-
 entities@^2.0.0:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f"
@@ -4357,6 +4376,21 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.18.0-next.1, es-
     string.prototype.trimstart "^1.0.5"
     unbox-primitive "^1.0.2"
 
+es-get-iterator@^1.1.2:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6"
+  integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==
+  dependencies:
+    call-bind "^1.0.2"
+    get-intrinsic "^1.1.3"
+    has-symbols "^1.0.3"
+    is-arguments "^1.1.1"
+    is-map "^2.0.2"
+    is-set "^2.0.2"
+    is-string "^1.0.7"
+    isarray "^2.0.5"
+    stop-iteration-iterator "^1.0.0"
+
 es-shim-unscopables@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241"
@@ -4436,58 +4470,63 @@ escodegen@^2.0.0:
   optionalDependencies:
     source-map "~0.6.1"
 
-eslint-import-resolver-node@^0.3.6:
-  version "0.3.6"
-  resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd"
-  integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==
+eslint-import-resolver-node@^0.3.7:
+  version "0.3.7"
+  resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7"
+  integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==
   dependencies:
     debug "^3.2.7"
-    resolve "^1.20.0"
+    is-core-module "^2.11.0"
+    resolve "^1.22.1"
 
-eslint-module-utils@^2.7.3:
-  version "2.7.3"
-  resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee"
-  integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==
+eslint-module-utils@^2.7.4:
+  version "2.7.4"
+  resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974"
+  integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==
   dependencies:
     debug "^3.2.7"
-    find-up "^2.1.0"
 
-eslint-plugin-import@~2.26.0:
-  version "2.26.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b"
-  integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==
+eslint-plugin-import@~2.27.5:
+  version "2.27.5"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65"
+  integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==
   dependencies:
-    array-includes "^3.1.4"
-    array.prototype.flat "^1.2.5"
-    debug "^2.6.9"
+    array-includes "^3.1.6"
+    array.prototype.flat "^1.3.1"
+    array.prototype.flatmap "^1.3.1"
+    debug "^3.2.7"
     doctrine "^2.1.0"
-    eslint-import-resolver-node "^0.3.6"
-    eslint-module-utils "^2.7.3"
+    eslint-import-resolver-node "^0.3.7"
+    eslint-module-utils "^2.7.4"
     has "^1.0.3"
-    is-core-module "^2.8.1"
+    is-core-module "^2.11.0"
     is-glob "^4.0.3"
     minimatch "^3.1.2"
-    object.values "^1.1.5"
-    resolve "^1.22.0"
+    object.values "^1.1.6"
+    resolve "^1.22.1"
+    semver "^6.3.0"
     tsconfig-paths "^3.14.1"
 
-eslint-plugin-jsx-a11y@~6.6.1:
-  version "6.6.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz#93736fc91b83fdc38cc8d115deedfc3091aef1ff"
-  integrity sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==
+eslint-plugin-jsx-a11y@~6.7.1:
+  version "6.7.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz#fca5e02d115f48c9a597a6894d5bcec2f7a76976"
+  integrity sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==
   dependencies:
-    "@babel/runtime" "^7.18.9"
-    aria-query "^4.2.2"
-    array-includes "^3.1.5"
+    "@babel/runtime" "^7.20.7"
+    aria-query "^5.1.3"
+    array-includes "^3.1.6"
+    array.prototype.flatmap "^1.3.1"
     ast-types-flow "^0.0.7"
-    axe-core "^4.4.3"
-    axobject-query "^2.2.0"
+    axe-core "^4.6.2"
+    axobject-query "^3.1.1"
     damerau-levenshtein "^1.0.8"
     emoji-regex "^9.2.2"
     has "^1.0.3"
-    jsx-ast-utils "^3.3.2"
-    language-tags "^1.0.5"
+    jsx-ast-utils "^3.3.3"
+    language-tags "=1.0.5"
     minimatch "^3.1.2"
+    object.entries "^1.1.6"
+    object.fromentries "^2.0.6"
     semver "^6.3.0"
 
 eslint-plugin-promise@~6.1.1:
@@ -4495,10 +4534,10 @@ eslint-plugin-promise@~6.1.1:
   resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz#269a3e2772f62875661220631bd4dafcb4083816"
   integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==
 
-eslint-plugin-react@~7.32.1:
-  version "7.32.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.32.1.tgz#88cdeb4065da8ca0b64e1274404f53a0f9890200"
-  integrity sha512-vOjdgyd0ZHBXNsmvU+785xY8Bfe57EFbTYYk8XrROzWpr9QBvpjITvAXt9xqcE6+8cjR/g1+mfumPToxsl1www==
+eslint-plugin-react@~7.32.2:
+  version "7.32.2"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz#e71f21c7c265ebce01bcbc9d0955170c55571f10"
+  integrity sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==
   dependencies:
     array-includes "^3.1.6"
     array.prototype.flatmap "^1.3.1"
@@ -4516,7 +4555,7 @@ eslint-plugin-react@~7.32.1:
     semver "^6.3.0"
     string.prototype.matchall "^4.0.8"
 
-eslint-scope@5.1.1, eslint-scope@^5.1.1:
+eslint-scope@5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
   integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
@@ -4532,77 +4571,84 @@ eslint-scope@^4.0.3:
     esrecurse "^4.1.0"
     estraverse "^4.1.1"
 
-eslint-utils@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
-  integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
+eslint-scope@^7.1.1:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642"
+  integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==
   dependencies:
-    eslint-visitor-keys "^1.1.0"
+    esrecurse "^4.3.0"
+    estraverse "^5.2.0"
 
-eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
-  integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
+eslint-utils@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672"
+  integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==
+  dependencies:
+    eslint-visitor-keys "^2.0.0"
 
 eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
   integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
 
-eslint@^7.32.0:
-  version "7.32.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
-  integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==
-  dependencies:
-    "@babel/code-frame" "7.12.11"
-    "@eslint/eslintrc" "^0.4.3"
-    "@humanwhocodes/config-array" "^0.5.0"
+eslint-visitor-keys@^3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
+  integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
+
+eslint@^8.33.0:
+  version "8.33.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.33.0.tgz#02f110f32998cb598c6461f24f4d306e41ca33d7"
+  integrity sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==
+  dependencies:
+    "@eslint/eslintrc" "^1.4.1"
+    "@humanwhocodes/config-array" "^0.11.8"
+    "@humanwhocodes/module-importer" "^1.0.1"
+    "@nodelib/fs.walk" "^1.2.8"
     ajv "^6.10.0"
     chalk "^4.0.0"
     cross-spawn "^7.0.2"
-    debug "^4.0.1"
+    debug "^4.3.2"
     doctrine "^3.0.0"
-    enquirer "^2.3.5"
     escape-string-regexp "^4.0.0"
-    eslint-scope "^5.1.1"
-    eslint-utils "^2.1.0"
-    eslint-visitor-keys "^2.0.0"
-    espree "^7.3.1"
+    eslint-scope "^7.1.1"
+    eslint-utils "^3.0.0"
+    eslint-visitor-keys "^3.3.0"
+    espree "^9.4.0"
     esquery "^1.4.0"
     esutils "^2.0.2"
     fast-deep-equal "^3.1.3"
     file-entry-cache "^6.0.1"
-    functional-red-black-tree "^1.0.1"
-    glob-parent "^5.1.2"
-    globals "^13.6.0"
-    ignore "^4.0.6"
+    find-up "^5.0.0"
+    glob-parent "^6.0.2"
+    globals "^13.19.0"
+    grapheme-splitter "^1.0.4"
+    ignore "^5.2.0"
     import-fresh "^3.0.0"
     imurmurhash "^0.1.4"
     is-glob "^4.0.0"
-    js-yaml "^3.13.1"
+    is-path-inside "^3.0.3"
+    js-sdsl "^4.1.4"
+    js-yaml "^4.1.0"
     json-stable-stringify-without-jsonify "^1.0.1"
     levn "^0.4.1"
     lodash.merge "^4.6.2"
-    minimatch "^3.0.4"
+    minimatch "^3.1.2"
     natural-compare "^1.4.0"
     optionator "^0.9.1"
-    progress "^2.0.0"
-    regexpp "^3.1.0"
-    semver "^7.2.1"
-    strip-ansi "^6.0.0"
+    regexpp "^3.2.0"
+    strip-ansi "^6.0.1"
     strip-json-comments "^3.1.0"
-    table "^6.0.9"
     text-table "^0.2.0"
-    v8-compile-cache "^2.0.3"
 
-espree@^7.3.0, espree@^7.3.1:
-  version "7.3.1"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
-  integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==
+espree@^9.4.0:
+  version "9.4.1"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.1.tgz#51d6092615567a2c2cff7833445e37c28c0065bd"
+  integrity sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==
   dependencies:
-    acorn "^7.4.0"
-    acorn-jsx "^5.3.1"
-    eslint-visitor-keys "^1.3.0"
+    acorn "^8.8.0"
+    acorn-jsx "^5.3.2"
+    eslint-visitor-keys "^3.3.0"
 
 esprima@^4.0.0, esprima@^4.0.1:
   version "4.0.1"
@@ -4736,16 +4782,16 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
   dependencies:
     homedir-polyfill "^1.0.1"
 
-expect@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/expect/-/expect-29.3.1.tgz#92877aad3f7deefc2e3f6430dd195b92295554a6"
-  integrity sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==
+expect@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/expect/-/expect-29.4.1.tgz#58cfeea9cbf479b64ed081fd1e074ac8beb5a1fe"
+  integrity sha512-OKrGESHOaMxK3b6zxIq9SOW8kEXztKff/Dvg88j4xIJxur1hspEbedVkR3GpHe5LO+WB2Qw7OWN0RMTdp6as5A==
   dependencies:
-    "@jest/expect-utils" "^29.3.1"
+    "@jest/expect-utils" "^29.4.1"
     jest-get-type "^29.2.0"
-    jest-matcher-utils "^29.3.1"
-    jest-message-util "^29.3.1"
-    jest-util "^29.3.1"
+    jest-matcher-utils "^29.4.1"
+    jest-message-util "^29.4.1"
+    jest-util "^29.4.1"
 
 express@^4.17.1, express@^4.18.2:
   version "4.18.2"
@@ -4967,13 +5013,6 @@ find-root@^1.1.0:
   resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
   integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
 
-find-up@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
-  integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
-  dependencies:
-    locate-path "^2.0.0"
-
 find-up@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
@@ -4989,6 +5028,14 @@ find-up@^4.0.0, find-up@^4.1.0:
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
+find-up@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+  integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+  dependencies:
+    locate-path "^6.0.0"
+    path-exists "^4.0.0"
+
 findup-sync@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1"
@@ -5030,6 +5077,13 @@ font-awesome@^4.7.0:
   resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133"
   integrity sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=
 
+for-each@^0.3.3:
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
+  integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
+  dependencies:
+    is-callable "^1.1.3"
+
 for-in@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -5138,11 +5192,6 @@ function.prototype.name@^1.1.5:
     es-abstract "^1.19.0"
     functions-have-names "^1.2.2"
 
-functional-red-black-tree@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
-  integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
-
 functions-have-names@^1.2.2:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
@@ -5241,6 +5290,13 @@ glob-parent@^5.1.2, glob-parent@~5.1.0:
   dependencies:
     is-glob "^4.0.1"
 
+glob-parent@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
+  integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+  dependencies:
+    is-glob "^4.0.3"
+
 glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
@@ -5305,10 +5361,10 @@ globals@^11.1.0:
   resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
   integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
 
-globals@^13.6.0, globals@^13.9.0:
-  version "13.9.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb"
-  integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==
+globals@^13.19.0:
+  version "13.20.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82"
+  integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==
   dependencies:
     type-fest "^0.20.2"
 
@@ -5340,11 +5396,23 @@ globjoin@^0.1.4:
   resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
   integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==
 
+gopd@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
+  integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
+  dependencies:
+    get-intrinsic "^1.1.3"
+
 graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9:
   version "4.2.9"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
   integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==
 
+grapheme-splitter@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
+  integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
+
 gzip-size@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462"
@@ -5676,11 +5744,6 @@ iferr@^0.1.5:
   resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
   integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
 
-ignore@^4.0.6:
-  version "4.0.6"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
-  integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
-
 ignore@^5.2.0, ignore@^5.2.1:
   version "5.2.4"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
@@ -5817,12 +5880,21 @@ internal-slot@^1.0.3:
     has "^1.0.3"
     side-channel "^1.0.4"
 
+internal-slot@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.4.tgz#8551e7baf74a7a6ba5f749cfb16aa60722f0d6f3"
+  integrity sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==
+  dependencies:
+    get-intrinsic "^1.1.3"
+    has "^1.0.3"
+    side-channel "^1.0.4"
+
 interpret@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
   integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
 
-intersection-observer@^0.12.0, intersection-observer@^0.12.2:
+intersection-observer@^0.12.0:
   version "0.12.2"
   resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.12.2.tgz#4a45349cc0cd91916682b1f44c28d7ec737dc375"
   integrity sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==
@@ -5919,6 +5991,23 @@ is-arguments@^1.0.4:
   resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3"
   integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==
 
+is-arguments@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
+  integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
+  dependencies:
+    call-bind "^1.0.2"
+    has-tostringtag "^1.0.0"
+
+is-array-buffer@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.1.tgz#deb1db4fcae48308d54ef2442706c0393997052a"
+  integrity sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==
+  dependencies:
+    call-bind "^1.0.2"
+    get-intrinsic "^1.1.3"
+    is-typed-array "^1.1.10"
+
 is-arrayish@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@@ -5955,7 +6044,7 @@ is-boolean-object@^1.1.0:
   dependencies:
     call-bind "^1.0.2"
 
-is-callable@^1.1.4, is-callable@^1.2.7:
+is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
   integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
@@ -5972,14 +6061,7 @@ is-color-stop@^1.0.0:
     rgb-regex "^1.0.1"
     rgba-regex "^1.0.0"
 
-is-core-module@^2.5.0, is-core-module@^2.8.1:
-  version "2.9.0"
-  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
-  integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
-  dependencies:
-    has "^1.0.3"
-
-is-core-module@^2.9.0:
+is-core-module@^2.11.0, is-core-module@^2.5.0, is-core-module@^2.9.0:
   version "2.11.0"
   resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
   integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
@@ -6005,6 +6087,13 @@ is-date-object@^1.0.1:
   resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
   integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
 
+is-date-object@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
+  integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
+  dependencies:
+    has-tostringtag "^1.0.0"
+
 is-descriptor@^0.1.0:
   version "0.1.6"
   resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
@@ -6079,6 +6168,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
   dependencies:
     is-extglob "^2.1.1"
 
+is-map@^2.0.1, is-map@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127"
+  integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==
+
 is-module@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
@@ -6143,6 +6237,11 @@ is-path-inside@^2.1.0:
   dependencies:
     path-is-inside "^1.0.2"
 
+is-path-inside@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+  integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+
 is-plain-obj@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
@@ -6183,6 +6282,11 @@ is-resolvable@^1.0.0:
   resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
   integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==
 
+is-set@^2.0.1, is-set@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec"
+  integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==
+
 is-shared-array-buffer@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79"
@@ -6214,11 +6318,27 @@ is-symbol@^1.0.2, is-symbol@^1.0.3:
   dependencies:
     has-symbols "^1.0.2"
 
+is-typed-array@^1.1.10:
+  version "1.1.10"
+  resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f"
+  integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==
+  dependencies:
+    available-typed-arrays "^1.0.5"
+    call-bind "^1.0.2"
+    for-each "^0.3.3"
+    gopd "^1.0.1"
+    has-tostringtag "^1.0.0"
+
 is-url@^1.2.4:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
   integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==
 
+is-weakmap@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2"
+  integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==
+
 is-weakref@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
@@ -6226,6 +6346,14 @@ is-weakref@^1.0.2:
   dependencies:
     call-bind "^1.0.2"
 
+is-weakset@^2.0.1:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d"
+  integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==
+  dependencies:
+    call-bind "^1.0.2"
+    get-intrinsic "^1.1.1"
+
 is-windows@^1.0.1, is-windows@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
@@ -6246,6 +6374,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
   integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
 
+isarray@^2.0.5:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
+  integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
+
 isexe@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -6315,82 +6448,82 @@ jake@^10.8.5:
     filelist "^1.0.1"
     minimatch "^3.0.4"
 
-jest-changed-files@^29.2.0:
-  version "29.2.0"
-  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.2.0.tgz#b6598daa9803ea6a4dce7968e20ab380ddbee289"
-  integrity sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==
+jest-changed-files@^29.4.0:
+  version "29.4.0"
+  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.4.0.tgz#ac2498bcd394228f7eddcadcf928b3583bf2779d"
+  integrity sha512-rnI1oPxgFghoz32Y8eZsGJMjW54UlqT17ycQeCEktcxxwqqKdlj9afl8LNeO0Pbu+h2JQHThQP0BzS67eTRx4w==
   dependencies:
     execa "^5.0.0"
     p-limit "^3.1.0"
 
-jest-circus@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.3.1.tgz#177d07c5c0beae8ef2937a67de68f1e17bbf1b4a"
-  integrity sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==
+jest-circus@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.4.1.tgz#ff1b63eb04c3b111cefea9489e8dbadd23ce49bd"
+  integrity sha512-v02NuL5crMNY4CGPHBEflLzl4v91NFb85a+dH9a1pUNx6Xjggrd8l9pPy4LZ1VYNRXlb+f65+7O/MSIbLir6pA==
   dependencies:
-    "@jest/environment" "^29.3.1"
-    "@jest/expect" "^29.3.1"
-    "@jest/test-result" "^29.3.1"
-    "@jest/types" "^29.3.1"
+    "@jest/environment" "^29.4.1"
+    "@jest/expect" "^29.4.1"
+    "@jest/test-result" "^29.4.1"
+    "@jest/types" "^29.4.1"
     "@types/node" "*"
     chalk "^4.0.0"
     co "^4.6.0"
     dedent "^0.7.0"
     is-generator-fn "^2.0.0"
-    jest-each "^29.3.1"
-    jest-matcher-utils "^29.3.1"
-    jest-message-util "^29.3.1"
-    jest-runtime "^29.3.1"
-    jest-snapshot "^29.3.1"
-    jest-util "^29.3.1"
+    jest-each "^29.4.1"
+    jest-matcher-utils "^29.4.1"
+    jest-message-util "^29.4.1"
+    jest-runtime "^29.4.1"
+    jest-snapshot "^29.4.1"
+    jest-util "^29.4.1"
     p-limit "^3.1.0"
-    pretty-format "^29.3.1"
+    pretty-format "^29.4.1"
     slash "^3.0.0"
     stack-utils "^2.0.3"
 
-jest-cli@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.3.1.tgz#e89dff427db3b1df50cea9a393ebd8640790416d"
-  integrity sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==
+jest-cli@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.4.1.tgz#7abef96944f300feb9b76f68b1eb2d68774fe553"
+  integrity sha512-jz7GDIhtxQ37M+9dlbv5K+/FVcIo1O/b1sX3cJgzlQUf/3VG25nvuWzlDC4F1FLLzUThJeWLu8I7JF9eWpuURQ==
   dependencies:
-    "@jest/core" "^29.3.1"
-    "@jest/test-result" "^29.3.1"
-    "@jest/types" "^29.3.1"
+    "@jest/core" "^29.4.1"
+    "@jest/test-result" "^29.4.1"
+    "@jest/types" "^29.4.1"
     chalk "^4.0.0"
     exit "^0.1.2"
     graceful-fs "^4.2.9"
     import-local "^3.0.2"
-    jest-config "^29.3.1"
-    jest-util "^29.3.1"
-    jest-validate "^29.3.1"
+    jest-config "^29.4.1"
+    jest-util "^29.4.1"
+    jest-validate "^29.4.1"
     prompts "^2.0.1"
     yargs "^17.3.1"
 
-jest-config@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.3.1.tgz#0bc3dcb0959ff8662957f1259947aedaefb7f3c6"
-  integrity sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==
+jest-config@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.4.1.tgz#e62670c6c980ec21d75941806ec4d0c0c6402728"
+  integrity sha512-g7p3q4NuXiM4hrS4XFATTkd+2z0Ml2RhFmFPM8c3WyKwVDNszbl4E7cV7WIx1YZeqqCtqbtTtZhGZWJlJqngzg==
   dependencies:
     "@babel/core" "^7.11.6"
-    "@jest/test-sequencer" "^29.3.1"
-    "@jest/types" "^29.3.1"
-    babel-jest "^29.3.1"
+    "@jest/test-sequencer" "^29.4.1"
+    "@jest/types" "^29.4.1"
+    babel-jest "^29.4.1"
     chalk "^4.0.0"
     ci-info "^3.2.0"
     deepmerge "^4.2.2"
     glob "^7.1.3"
     graceful-fs "^4.2.9"
-    jest-circus "^29.3.1"
-    jest-environment-node "^29.3.1"
+    jest-circus "^29.4.1"
+    jest-environment-node "^29.4.1"
     jest-get-type "^29.2.0"
     jest-regex-util "^29.2.0"
-    jest-resolve "^29.3.1"
-    jest-runner "^29.3.1"
-    jest-util "^29.3.1"
-    jest-validate "^29.3.1"
+    jest-resolve "^29.4.1"
+    jest-runner "^29.4.1"
+    jest-util "^29.4.1"
+    jest-validate "^29.4.1"
     micromatch "^4.0.4"
     parse-json "^5.2.0"
-    pretty-format "^29.3.1"
+    pretty-format "^29.4.1"
     slash "^3.0.0"
     strip-json-comments "^3.1.1"
 
@@ -6404,15 +6537,15 @@ jest-diff@^25.2.1:
     jest-get-type "^25.2.6"
     pretty-format "^25.5.0"
 
-jest-diff@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.3.1.tgz#d8215b72fed8f1e647aed2cae6c752a89e757527"
-  integrity sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==
+jest-diff@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.4.1.tgz#9a6dc715037e1fa7a8a44554e7d272088c4029bd"
+  integrity sha512-uazdl2g331iY56CEyfbNA0Ut7Mn2ulAG5vUaEHXycf1L6IPyuImIxSz4F0VYBKi7LYIuxOwTZzK3wh5jHzASMw==
   dependencies:
     chalk "^4.0.0"
     diff-sequences "^29.3.1"
     jest-get-type "^29.2.0"
-    pretty-format "^29.3.1"
+    pretty-format "^29.4.1"
 
 jest-docblock@^29.2.0:
   version "29.2.0"
@@ -6421,42 +6554,42 @@ jest-docblock@^29.2.0:
   dependencies:
     detect-newline "^3.0.0"
 
-jest-each@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.3.1.tgz#bc375c8734f1bb96625d83d1ca03ef508379e132"
-  integrity sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==
+jest-each@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.4.1.tgz#05ce9979e7486dbd0f5d41895f49ccfdd0afce01"
+  integrity sha512-QlYFiX3llJMWUV0BtWht/esGEz9w+0i7BHwODKCze7YzZzizgExB9MOfiivF/vVT0GSQ8wXLhvHXh3x2fVD4QQ==
   dependencies:
-    "@jest/types" "^29.3.1"
+    "@jest/types" "^29.4.1"
     chalk "^4.0.0"
     jest-get-type "^29.2.0"
-    jest-util "^29.3.1"
-    pretty-format "^29.3.1"
+    jest-util "^29.4.1"
+    pretty-format "^29.4.1"
 
-jest-environment-jsdom@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.3.1.tgz#14ca63c3e0ef5c63c5bcb46033e50bc649e3b639"
-  integrity sha512-G46nKgiez2Gy4zvYNhayfMEAFlVHhWfncqvqS6yCd0i+a4NsSUD2WtrKSaYQrYiLQaupHXxCRi8xxVL2M9PbhA==
+jest-environment-jsdom@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.4.1.tgz#34d491244ddd6fe3d666da603b576bd0ae6aef78"
+  integrity sha512-+KfYmRTl5CBHQst9hIz77TiiriHYvuWoLjMT855gx2AMxhHxpk1vtKvag1DQfyWCPVTWV/AG7SIqVh5WI1O/uw==
   dependencies:
-    "@jest/environment" "^29.3.1"
-    "@jest/fake-timers" "^29.3.1"
-    "@jest/types" "^29.3.1"
+    "@jest/environment" "^29.4.1"
+    "@jest/fake-timers" "^29.4.1"
+    "@jest/types" "^29.4.1"
     "@types/jsdom" "^20.0.0"
     "@types/node" "*"
-    jest-mock "^29.3.1"
-    jest-util "^29.3.1"
+    jest-mock "^29.4.1"
+    jest-util "^29.4.1"
     jsdom "^20.0.0"
 
-jest-environment-node@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.3.1.tgz#5023b32472b3fba91db5c799a0d5624ad4803e74"
-  integrity sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==
+jest-environment-node@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.4.1.tgz#22550b7d0f8f0b16228639c9f88ca04bbf3c1974"
+  integrity sha512-x/H2kdVgxSkxWAIlIh9MfMuBa0hZySmfsC5lCsWmWr6tZySP44ediRKDUiNggX/eHLH7Cd5ZN10Rw+XF5tXsqg==
   dependencies:
-    "@jest/environment" "^29.3.1"
-    "@jest/fake-timers" "^29.3.1"
-    "@jest/types" "^29.3.1"
+    "@jest/environment" "^29.4.1"
+    "@jest/fake-timers" "^29.4.1"
+    "@jest/types" "^29.4.1"
     "@types/node" "*"
-    jest-mock "^29.3.1"
-    jest-util "^29.3.1"
+    jest-mock "^29.4.1"
+    jest-util "^29.4.1"
 
 jest-get-type@^25.2.6:
   version "25.2.6"
@@ -6468,66 +6601,66 @@ jest-get-type@^29.2.0:
   resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.2.0.tgz#726646f927ef61d583a3b3adb1ab13f3a5036408"
   integrity sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==
 
-jest-haste-map@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.3.1.tgz#af83b4347f1dae5ee8c2fb57368dc0bb3e5af843"
-  integrity sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==
+jest-haste-map@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.4.1.tgz#b0579dc82d94b40ed9041af56ad25c2f80bedaeb"
+  integrity sha512-imTjcgfVVTvg02khXL11NNLTx9ZaofbAWhilrMg/G8dIkp+HYCswhxf0xxJwBkfhWb3e8dwbjuWburvxmcr58w==
   dependencies:
-    "@jest/types" "^29.3.1"
+    "@jest/types" "^29.4.1"
     "@types/graceful-fs" "^4.1.3"
     "@types/node" "*"
     anymatch "^3.0.3"
     fb-watchman "^2.0.0"
     graceful-fs "^4.2.9"
     jest-regex-util "^29.2.0"
-    jest-util "^29.3.1"
-    jest-worker "^29.3.1"
+    jest-util "^29.4.1"
+    jest-worker "^29.4.1"
     micromatch "^4.0.4"
     walker "^1.0.8"
   optionalDependencies:
     fsevents "^2.3.2"
 
-jest-leak-detector@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz#95336d020170671db0ee166b75cd8ef647265518"
-  integrity sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==
+jest-leak-detector@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.4.1.tgz#632186c546e084da2b490b7496fee1a1c9929637"
+  integrity sha512-akpZv7TPyGMnH2RimOCgy+hPmWZf55EyFUvymQ4LMsQP8xSPlZumCPtXGoDhFNhUE2039RApZkTQDKU79p/FiQ==
   dependencies:
     jest-get-type "^29.2.0"
-    pretty-format "^29.3.1"
+    pretty-format "^29.4.1"
 
-jest-matcher-utils@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz#6e7f53512f80e817dfa148672bd2d5d04914a572"
-  integrity sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==
+jest-matcher-utils@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.4.1.tgz#73d834e305909c3b43285fbc76f78bf0ad7e1954"
+  integrity sha512-k5h0u8V4nAEy6lSACepxL/rw78FLDkBnXhZVgFneVpnJONhb2DhZj/Gv4eNe+1XqQ5IhgUcqj745UwH0HJmMnA==
   dependencies:
     chalk "^4.0.0"
-    jest-diff "^29.3.1"
+    jest-diff "^29.4.1"
     jest-get-type "^29.2.0"
-    pretty-format "^29.3.1"
+    pretty-format "^29.4.1"
 
-jest-message-util@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.3.1.tgz#37bc5c468dfe5120712053dd03faf0f053bd6adb"
-  integrity sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==
+jest-message-util@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.4.1.tgz#522623aa1df9a36ebfdffb06495c7d9d19e8a845"
+  integrity sha512-H4/I0cXUaLeCw6FM+i4AwCnOwHRgitdaUFOdm49022YD5nfyr8C/DrbXOBEyJaj+w/y0gGJ57klssOaUiLLQGQ==
   dependencies:
     "@babel/code-frame" "^7.12.13"
-    "@jest/types" "^29.3.1"
+    "@jest/types" "^29.4.1"
     "@types/stack-utils" "^2.0.0"
     chalk "^4.0.0"
     graceful-fs "^4.2.9"
     micromatch "^4.0.4"
-    pretty-format "^29.3.1"
+    pretty-format "^29.4.1"
     slash "^3.0.0"
     stack-utils "^2.0.3"
 
-jest-mock@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.3.1.tgz#60287d92e5010979d01f218c6b215b688e0f313e"
-  integrity sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==
+jest-mock@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.4.1.tgz#a218a2abf45c99c501d4665207748a6b9e29afbd"
+  integrity sha512-MwA4hQ7zBOcgVCVnsM8TzaFLVUD/pFWTfbkY953Y81L5ret3GFRZtmPmRFAjKQSdCKoJvvqOu6Bvfpqlwwb0dQ==
   dependencies:
-    "@jest/types" "^29.3.1"
+    "@jest/types" "^29.4.1"
     "@types/node" "*"
-    jest-util "^29.3.1"
+    jest-util "^29.4.1"
 
 jest-pnp-resolver@^1.2.2:
   version "1.2.2"
@@ -6539,88 +6672,89 @@ jest-regex-util@^29.2.0:
   resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.2.0.tgz#82ef3b587e8c303357728d0322d48bbfd2971f7b"
   integrity sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==
 
-jest-resolve-dependencies@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz#a6a329708a128e68d67c49f38678a4a4a914c3bf"
-  integrity sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==
+jest-resolve-dependencies@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.4.1.tgz#02420a2e055da105e5fca8218c471d8b9553c904"
+  integrity sha512-Y3QG3M1ncAMxfjbYgtqNXC5B595zmB6e//p/qpA/58JkQXu/IpLDoLeOa8YoYfsSglBKQQzNUqtfGJJT/qLmJg==
   dependencies:
     jest-regex-util "^29.2.0"
-    jest-snapshot "^29.3.1"
+    jest-snapshot "^29.4.1"
 
-jest-resolve@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.3.1.tgz#9a4b6b65387a3141e4a40815535c7f196f1a68a7"
-  integrity sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==
+jest-resolve@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.4.1.tgz#4c6bf71a07b8f0b79c5fdf4f2a2cf47317694c5e"
+  integrity sha512-j/ZFNV2lm9IJ2wmlq1uYK0Y/1PiyDq9g4HEGsNTNr3viRbJdV+8Lf1SXIiLZXFvyiisu0qUyIXGBnw+OKWkJwQ==
   dependencies:
     chalk "^4.0.0"
     graceful-fs "^4.2.9"
-    jest-haste-map "^29.3.1"
+    jest-haste-map "^29.4.1"
     jest-pnp-resolver "^1.2.2"
-    jest-util "^29.3.1"
-    jest-validate "^29.3.1"
+    jest-util "^29.4.1"
+    jest-validate "^29.4.1"
     resolve "^1.20.0"
-    resolve.exports "^1.1.0"
+    resolve.exports "^2.0.0"
     slash "^3.0.0"
 
-jest-runner@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.3.1.tgz#a92a879a47dd096fea46bb1517b0a99418ee9e2d"
-  integrity sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==
-  dependencies:
-    "@jest/console" "^29.3.1"
-    "@jest/environment" "^29.3.1"
-    "@jest/test-result" "^29.3.1"
-    "@jest/transform" "^29.3.1"
-    "@jest/types" "^29.3.1"
+jest-runner@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.4.1.tgz#57460d9ebb0eea2e27eeddca1816cf8537469661"
+  integrity sha512-8d6XXXi7GtHmsHrnaqBKWxjKb166Eyj/ksSaUYdcBK09VbjPwIgWov1VwSmtupCIz8q1Xv4Qkzt/BTo3ZqiCeg==
+  dependencies:
+    "@jest/console" "^29.4.1"
+    "@jest/environment" "^29.4.1"
+    "@jest/test-result" "^29.4.1"
+    "@jest/transform" "^29.4.1"
+    "@jest/types" "^29.4.1"
     "@types/node" "*"
     chalk "^4.0.0"
     emittery "^0.13.1"
     graceful-fs "^4.2.9"
     jest-docblock "^29.2.0"
-    jest-environment-node "^29.3.1"
-    jest-haste-map "^29.3.1"
-    jest-leak-detector "^29.3.1"
-    jest-message-util "^29.3.1"
-    jest-resolve "^29.3.1"
-    jest-runtime "^29.3.1"
-    jest-util "^29.3.1"
-    jest-watcher "^29.3.1"
-    jest-worker "^29.3.1"
+    jest-environment-node "^29.4.1"
+    jest-haste-map "^29.4.1"
+    jest-leak-detector "^29.4.1"
+    jest-message-util "^29.4.1"
+    jest-resolve "^29.4.1"
+    jest-runtime "^29.4.1"
+    jest-util "^29.4.1"
+    jest-watcher "^29.4.1"
+    jest-worker "^29.4.1"
     p-limit "^3.1.0"
     source-map-support "0.5.13"
 
-jest-runtime@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.3.1.tgz#21efccb1a66911d6d8591276a6182f520b86737a"
-  integrity sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==
+jest-runtime@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.4.1.tgz#9a50f9c69d3a391690897c01b0bfa8dc5dd45808"
+  integrity sha512-UXTMU9uKu2GjYwTtoAw5rn4STxWw/nadOfW7v1sx6LaJYa3V/iymdCLQM6xy3+7C6mY8GfX22vKpgxY171UIoA==
   dependencies:
-    "@jest/environment" "^29.3.1"
-    "@jest/fake-timers" "^29.3.1"
-    "@jest/globals" "^29.3.1"
+    "@jest/environment" "^29.4.1"
+    "@jest/fake-timers" "^29.4.1"
+    "@jest/globals" "^29.4.1"
     "@jest/source-map" "^29.2.0"
-    "@jest/test-result" "^29.3.1"
-    "@jest/transform" "^29.3.1"
-    "@jest/types" "^29.3.1"
+    "@jest/test-result" "^29.4.1"
+    "@jest/transform" "^29.4.1"
+    "@jest/types" "^29.4.1"
     "@types/node" "*"
     chalk "^4.0.0"
     cjs-module-lexer "^1.0.0"
     collect-v8-coverage "^1.0.0"
     glob "^7.1.3"
     graceful-fs "^4.2.9"
-    jest-haste-map "^29.3.1"
-    jest-message-util "^29.3.1"
-    jest-mock "^29.3.1"
+    jest-haste-map "^29.4.1"
+    jest-message-util "^29.4.1"
+    jest-mock "^29.4.1"
     jest-regex-util "^29.2.0"
-    jest-resolve "^29.3.1"
-    jest-snapshot "^29.3.1"
-    jest-util "^29.3.1"
+    jest-resolve "^29.4.1"
+    jest-snapshot "^29.4.1"
+    jest-util "^29.4.1"
+    semver "^7.3.5"
     slash "^3.0.0"
     strip-bom "^4.0.0"
 
-jest-snapshot@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.3.1.tgz#17bcef71a453adc059a18a32ccbd594b8cc4e45e"
-  integrity sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==
+jest-snapshot@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.4.1.tgz#5692210b3690c94f19317913d4082b123bd83dd9"
+  integrity sha512-l4iV8EjGgQWVz3ee/LR9sULDk2pCkqb71bjvlqn+qp90lFwpnulHj4ZBT8nm1hA1C5wowXLc7MGnw321u0tsYA==
   dependencies:
     "@babel/core" "^7.11.6"
     "@babel/generator" "^7.7.2"
@@ -6628,61 +6762,61 @@ jest-snapshot@^29.3.1:
     "@babel/plugin-syntax-typescript" "^7.7.2"
     "@babel/traverse" "^7.7.2"
     "@babel/types" "^7.3.3"
-    "@jest/expect-utils" "^29.3.1"
-    "@jest/transform" "^29.3.1"
-    "@jest/types" "^29.3.1"
+    "@jest/expect-utils" "^29.4.1"
+    "@jest/transform" "^29.4.1"
+    "@jest/types" "^29.4.1"
     "@types/babel__traverse" "^7.0.6"
     "@types/prettier" "^2.1.5"
     babel-preset-current-node-syntax "^1.0.0"
     chalk "^4.0.0"
-    expect "^29.3.1"
+    expect "^29.4.1"
     graceful-fs "^4.2.9"
-    jest-diff "^29.3.1"
+    jest-diff "^29.4.1"
     jest-get-type "^29.2.0"
-    jest-haste-map "^29.3.1"
-    jest-matcher-utils "^29.3.1"
-    jest-message-util "^29.3.1"
-    jest-util "^29.3.1"
+    jest-haste-map "^29.4.1"
+    jest-matcher-utils "^29.4.1"
+    jest-message-util "^29.4.1"
+    jest-util "^29.4.1"
     natural-compare "^1.4.0"
-    pretty-format "^29.3.1"
+    pretty-format "^29.4.1"
     semver "^7.3.5"
 
-jest-util@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.3.1.tgz#1dda51e378bbcb7e3bc9d8ab651445591ed373e1"
-  integrity sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==
+jest-util@^29.3.1, jest-util@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.4.1.tgz#2eeed98ff4563b441b5a656ed1a786e3abc3e4c4"
+  integrity sha512-bQy9FPGxVutgpN4VRc0hk6w7Hx/m6L53QxpDreTZgJd9gfx/AV2MjyPde9tGyZRINAUrSv57p2inGBu2dRLmkQ==
   dependencies:
-    "@jest/types" "^29.3.1"
+    "@jest/types" "^29.4.1"
     "@types/node" "*"
     chalk "^4.0.0"
     ci-info "^3.2.0"
     graceful-fs "^4.2.9"
     picomatch "^2.2.3"
 
-jest-validate@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.3.1.tgz#d56fefaa2e7d1fde3ecdc973c7f7f8f25eea704a"
-  integrity sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==
+jest-validate@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.4.1.tgz#0d5174510415083ec329d4f981bf6779211f17e9"
+  integrity sha512-qNZXcZQdIQx4SfUB/atWnI4/I2HUvhz8ajOSYUu40CSmf9U5emil8EDHgE7M+3j9/pavtk3knlZBDsgFvv/SWw==
   dependencies:
-    "@jest/types" "^29.3.1"
+    "@jest/types" "^29.4.1"
     camelcase "^6.2.0"
     chalk "^4.0.0"
     jest-get-type "^29.2.0"
     leven "^3.1.0"
-    pretty-format "^29.3.1"
+    pretty-format "^29.4.1"
 
-jest-watcher@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.3.1.tgz#3341547e14fe3c0f79f9c3a4c62dbc3fc977fd4a"
-  integrity sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==
+jest-watcher@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.4.1.tgz#6e3e2486918bd778849d4d6e67fd77b814f3e6ed"
+  integrity sha512-vFOzflGFs27nU6h8dpnVRER3O2rFtL+VMEwnG0H3KLHcllLsU8y9DchSh0AL/Rg5nN1/wSiQ+P4ByMGpuybaVw==
   dependencies:
-    "@jest/test-result" "^29.3.1"
-    "@jest/types" "^29.3.1"
+    "@jest/test-result" "^29.4.1"
+    "@jest/types" "^29.4.1"
     "@types/node" "*"
     ansi-escapes "^4.2.1"
     chalk "^4.0.0"
     emittery "^0.13.1"
-    jest-util "^29.3.1"
+    jest-util "^29.4.1"
     string-length "^4.0.1"
 
 jest-worker@^26.2.1, jest-worker@^26.5.0:
@@ -6694,25 +6828,30 @@ jest-worker@^26.2.1, jest-worker@^26.5.0:
     merge-stream "^2.0.0"
     supports-color "^7.0.0"
 
-jest-worker@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.3.1.tgz#e9462161017a9bb176380d721cab022661da3d6b"
-  integrity sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==
+jest-worker@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.4.1.tgz#7cb4a99a38975679600305650f86f4807460aab1"
+  integrity sha512-O9doU/S1EBe+yp/mstQ0VpPwpv0Clgn68TkNwGxL6/usX/KUW9Arnn4ag8C3jc6qHcXznhsT5Na1liYzAsuAbQ==
   dependencies:
     "@types/node" "*"
-    jest-util "^29.3.1"
+    jest-util "^29.4.1"
     merge-stream "^2.0.0"
     supports-color "^8.0.0"
 
-jest@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/jest/-/jest-29.3.1.tgz#c130c0d551ae6b5459b8963747fed392ddbde122"
-  integrity sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==
+jest@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/jest/-/jest-29.4.1.tgz#bb34baca8e05901b49c02c62f1183a6182ea1785"
+  integrity sha512-cknimw7gAXPDOmj0QqztlxVtBVCw2lYY9CeIE5N6kD+kET1H4H79HSNISJmijb1HF+qk+G+ploJgiDi5k/fRlg==
   dependencies:
-    "@jest/core" "^29.3.1"
-    "@jest/types" "^29.3.1"
+    "@jest/core" "^29.4.1"
+    "@jest/types" "^29.4.1"
     import-local "^3.0.2"
-    jest-cli "^29.3.1"
+    jest-cli "^29.4.1"
+
+js-sdsl@^4.1.4:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711"
+  integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==
 
 "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
   version "4.0.0"
@@ -6888,13 +7027,13 @@ jsonpointer@^5.0.0:
   resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.0.tgz#f802669a524ec4805fa7389eadbc9921d5dc8072"
   integrity sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg==
 
-"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.2:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz#afe5efe4332cd3515c065072bd4d6b0aa22152bd"
-  integrity sha512-4ZCADZHRkno244xlNnn4AOG6sRQ7iBZ5BbgZ4vW4y5IZw7cVUD1PPeblm1xx/nfmMxPdt/LHsXZW8z/j58+l9Q==
+"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3:
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz#76b3e6e6cece5c69d49a5792c3d01bd1a0cdc7ea"
+  integrity sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==
   dependencies:
     array-includes "^3.1.5"
-    object.assign "^4.1.2"
+    object.assign "^4.1.3"
 
 keycode@^2.1.7:
   version "2.2.0"
@@ -6931,10 +7070,10 @@ language-subtag-registry@~0.3.2:
   resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz#a00a37121894f224f763268e431c55556b0c0755"
   integrity sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg==
 
-language-tags@^1.0.5:
+language-tags@=1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a"
-  integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=
+  integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==
   dependencies:
     language-subtag-registry "~0.3.2"
 
@@ -6987,14 +7126,6 @@ loader-utils@^2.0.0:
     emojis-list "^3.0.0"
     json5 "^2.1.2"
 
-locate-path@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
-  integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
-  dependencies:
-    p-locate "^2.0.0"
-    path-exists "^3.0.0"
-
 locate-path@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@@ -7010,6 +7141,13 @@ locate-path@^5.0.0:
   dependencies:
     p-locate "^4.1.0"
 
+locate-path@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+  integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+  dependencies:
+    p-locate "^5.0.0"
+
 lockfile@^1.0:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609"
@@ -7348,7 +7486,7 @@ minimalistic-crypto-utils@^1.0.1:
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
 
-minimatch@^3.0.4, minimatch@^3.1.2:
+minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
   integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@@ -7448,6 +7586,11 @@ mkdirp@^1.0, mkdirp@^1.0.3, mkdirp@^1.0.4:
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
   integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
 
+mkdirp@^2.1.3:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.3.tgz#b083ff37be046fd3d6552468c1f0ff44c1545d1f"
+  integrity sha512-sjAkg21peAG9HS+Dkx7hlG9Ztx7HLeKnvB3NQRcu/mltCVmvkF0pisbiTSfDVYTT86XEfZrTUosLdZLStquZUw==
+
 mousetrap@^1.5.2:
   version "1.6.5"
   resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9"
@@ -7711,6 +7854,14 @@ object-is@^1.0.1:
     define-properties "^1.1.3"
     es-abstract "^1.18.0-next.1"
 
+object-is@^1.1.5:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
+  integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
+  dependencies:
+    call-bind "^1.0.2"
+    define-properties "^1.1.3"
+
 object-keys@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
@@ -7723,7 +7874,7 @@ object-visit@^1.0.0:
   dependencies:
     isobject "^3.0.0"
 
-object.assign@^4.1.2, object.assign@^4.1.4:
+object.assign@^4.1.3, object.assign@^4.1.4:
   version "4.1.4"
   resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
   integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==
@@ -7774,7 +7925,7 @@ object.pick@^1.3.0:
   dependencies:
     isobject "^3.0.1"
 
-object.values@^1.1.0, object.values@^1.1.5, object.values@^1.1.6:
+object.values@^1.1.0, object.values@^1.1.6:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d"
   integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==
@@ -7872,13 +8023,6 @@ p-finally@^1.0.0:
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
   integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
 
-p-limit@^1.1.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
-  integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
-  dependencies:
-    p-try "^1.0.0"
-
 p-limit@^2.0.0, p-limit@^2.2.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
@@ -7893,13 +8037,6 @@ p-limit@^3.0.2, p-limit@^3.1.0:
   dependencies:
     yocto-queue "^0.1.0"
 
-p-locate@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
-  integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
-  dependencies:
-    p-limit "^1.1.0"
-
 p-locate@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
@@ -7914,6 +8051,13 @@ p-locate@^4.1.0:
   dependencies:
     p-limit "^2.2.0"
 
+p-locate@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+  integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+  dependencies:
+    p-limit "^3.0.2"
+
 p-map@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
@@ -7933,11 +8077,6 @@ p-retry@^3.0.1:
   dependencies:
     retry "^0.12.0"
 
-p-try@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
-  integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
-
 p-try@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
@@ -8648,12 +8787,12 @@ pretty-format@^27.0.2:
     ansi-styles "^5.0.0"
     react-is "^17.0.1"
 
-pretty-format@^29.3.1:
-  version "29.3.1"
-  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.3.1.tgz#1841cac822b02b4da8971dacb03e8a871b4722da"
-  integrity sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==
+pretty-format@^29.4.1:
+  version "29.4.1"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.1.tgz#0da99b532559097b8254298da7c75a0785b1751c"
+  integrity sha512-dt/Z761JUVsrIKaY215o1xQJBGlSmTx/h4cSqXqjHLnU1+Kt+mavVE7UgqJJO5ukx5HjSswHfmXz4LjS2oIJfg==
   dependencies:
-    "@jest/schemas" "^29.0.0"
+    "@jest/schemas" "^29.4.0"
     ansi-styles "^5.0.0"
     react-is "^18.0.0"
 
@@ -8667,11 +8806,6 @@ process@^0.11.10:
   resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
   integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
 
-progress@^2.0.0:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
-  integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
-
 promise-inflight@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
@@ -8773,10 +8907,10 @@ punycode@1.4.1, punycode@^1.2.4:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
   integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
 
-punycode@^2.1.0, punycode@^2.1.1, punycode@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.2.0.tgz#2092cc57cd2582c38e4e7e8bb869dc8d3148bc74"
-  integrity sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==
+punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
+  integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
 
 q@^1.1.2:
   version "1.5.1"
@@ -9245,10 +9379,10 @@ redux-thunk@^2.4.2:
   resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b"
   integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==
 
-redux@^4.0.0, redux@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13"
-  integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==
+redux@^4.0.0, redux@^4.2.1:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
+  integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
   dependencies:
     "@babel/runtime" "^7.9.2"
 
@@ -9298,10 +9432,10 @@ regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.3:
     define-properties "^1.1.3"
     functions-have-names "^1.2.2"
 
-regexpp@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2"
-  integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
+regexpp@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
+  integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
 
 regexpu-core@^5.1.0:
   version "5.1.0"
@@ -9424,17 +9558,17 @@ resolve-url@^0.2.1:
   resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
   integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
 
-resolve.exports@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9"
-  integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==
+resolve.exports@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.0.tgz#c1a0028c2d166ec2fbf7d0644584927e76e7400e"
+  integrity sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==
 
-resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0:
-  version "1.22.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
-  integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
+resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1:
+  version "1.22.1"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
+  integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
   dependencies:
-    is-core-module "^2.8.1"
+    is-core-module "^2.9.0"
     path-parse "^1.0.7"
     supports-preserve-symlinks-flag "^1.0.0"
 
@@ -9486,10 +9620,10 @@ rimraf@^3.0.2:
   dependencies:
     glob "^7.1.3"
 
-rimraf@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.1.1.tgz#ec29817863e5d82d22bca82f9dc4325be2f1e72b"
-  integrity sha512-Z4Y81w8atcvaJuJuBB88VpADRH66okZAuEm+Jtaufa+s7rZmIz+Hik2G53kGaNytE7lsfXyWktTmfVz0H9xuDg==
+rimraf@^4.1.2:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.1.2.tgz#20dfbc98083bdfaa28b01183162885ef213dbf7c"
+  integrity sha512-BlIbgFryTbw3Dz6hyoWFhKk+unCcHMSkZGrTFVAx2WmttdBSonsdtRlwiuTbDqTKr+UlXIUqJVS4QT5tUzGENQ==
 
 ripemd160@^2.0.0, ripemd160@^2.0.1:
   version "2.0.2"
@@ -9658,7 +9792,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
-semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
+semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
   version "7.3.7"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
   integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
@@ -10110,6 +10244,13 @@ statuses@2.0.1:
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
   integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
 
+stop-iteration-iterator@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4"
+  integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==
+  dependencies:
+    internal-slot "^1.0.4"
+
 stream-browserify@^2.0.1:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"
@@ -10459,7 +10600,7 @@ symbol-tree@^3.2.4:
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
   integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
 
-table@^6.0.9, table@^6.8.1:
+table@^6.8.1:
   version "6.8.1"
   resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf"
   integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==
@@ -11012,10 +11153,10 @@ use@^3.1.0:
   resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
   integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
 
-utf-8-validate@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-6.0.1.tgz#ca94aac987856c17c1b556b8b692323de98f521e"
-  integrity sha512-gAZEa1DMXeBiHEwxef81kLtZjBrC1hib7UWnsvMVtxY8oJGtDSXt9McWu2D6V/xgrjbfRBsS5UIGEUBg2SrAsg==
+utf-8-validate@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-6.0.2.tgz#2d80529963e4cc55ac5a1ca9dafdaa990d5ea16b"
+  integrity sha512-yd7PQEOW+EgecUzSD7XUXTyq/vREGXk7t7fzGfOvwOAr0Z64h5rfGrmkNk8+ddVmf/FrkjPPhVyYBa7fuSPVTg==
   dependencies:
     node-gyp-build "^4.3.0"
 
@@ -11063,7 +11204,7 @@ uuid@^8.3.1:
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
   integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
 
-v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1, v8-compile-cache@^2.3.0:
+v8-compile-cache@^2.1.1, v8-compile-cache@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
   integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
@@ -11385,11 +11526,33 @@ which-boxed-primitive@^1.0.2:
     is-string "^1.0.5"
     is-symbol "^1.0.3"
 
+which-collection@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906"
+  integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==
+  dependencies:
+    is-map "^2.0.1"
+    is-set "^2.0.1"
+    is-weakmap "^2.0.1"
+    is-weakset "^2.0.1"
+
 which-module@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
   integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
 
+which-typed-array@^1.1.9:
+  version "1.1.9"
+  resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6"
+  integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==
+  dependencies:
+    available-typed-arrays "^1.0.5"
+    call-bind "^1.0.2"
+    for-each "^0.3.3"
+    gopd "^1.0.1"
+    has-tostringtag "^1.0.0"
+    is-typed-array "^1.1.10"
+
 which@^1.2.14, which@^1.2.9, which@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
@@ -11625,7 +11788,7 @@ wrappy@1:
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
 
-write-file-atomic@^4.0.1, write-file-atomic@^4.0.2:
+write-file-atomic@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd"
   integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==
@@ -11633,6 +11796,14 @@ write-file-atomic@^4.0.1, write-file-atomic@^4.0.2:
     imurmurhash "^0.1.4"
     signal-exit "^3.0.7"
 
+write-file-atomic@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.0.tgz#54303f117e109bf3d540261125c8ea5a7320fab0"
+  integrity sha512-R7NYMnHSlV42K54lwY9lvW6MnSm1HSJqZL3xiSgi9E7//FYaI74r2G0rd+/X6VAMkHEdzxQaU5HUOXWUz5kA/w==
+  dependencies:
+    imurmurhash "^0.1.4"
+    signal-exit "^3.0.7"
+
 ws@^6.2.1:
   version "6.2.1"
   resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"