about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features
diff options
context:
space:
mode:
authorStarfall <us@starfall.systems>2023-04-14 19:22:47 -0500
committerStarfall <us@starfall.systems>2023-04-14 19:22:47 -0500
commit4fe1689de43f4404eb9530fcfbcbfb26d6c1c13a (patch)
tree6811b845bb7f4966b10dcefa3dea404246f161c7 /app/javascript/flavours/glitch/features
parent65c1e53a32cabcdbb7bca57002bb0f6acdebe07e (diff)
parentbed63f6dae0879ac840066b031229e0d139089cd (diff)
Merge remote-tracking branch 'glitch/main' HEAD main
Diffstat (limited to 'app/javascript/flavours/glitch/features')
-rw-r--r--app/javascript/flavours/glitch/features/about/index.jsx (renamed from app/javascript/flavours/glitch/features/about/index.js)10
-rw-r--r--app/javascript/flavours/glitch/features/account/components/account_note.jsx (renamed from app/javascript/flavours/glitch/features/account/components/account_note.js)11
-rw-r--r--app/javascript/flavours/glitch/features/account/components/action_bar.jsx (renamed from app/javascript/flavours/glitch/features/account/components/action_bar.js)7
-rw-r--r--app/javascript/flavours/glitch/features/account/components/featured_tags.jsx (renamed from app/javascript/flavours/glitch/features/account/components/featured_tags.js)3
-rw-r--r--app/javascript/flavours/glitch/features/account/components/follow_request_note.jsx (renamed from app/javascript/flavours/glitch/features/account/components/follow_request_note.js)0
-rw-r--r--app/javascript/flavours/glitch/features/account/components/header.jsx (renamed from app/javascript/flavours/glitch/features/account/components/header.js)28
-rw-r--r--app/javascript/flavours/glitch/features/account/components/profile_column_header.jsx (renamed from app/javascript/flavours/glitch/features/account/components/profile_column_header.js)3
-rw-r--r--app/javascript/flavours/glitch/features/account/navigation.jsx (renamed from app/javascript/flavours/glitch/features/account/navigation.js)3
-rw-r--r--app/javascript/flavours/glitch/features/account_gallery/components/media_item.jsx (renamed from app/javascript/flavours/glitch/features/account_gallery/components/media_item.js)11
-rw-r--r--app/javascript/flavours/glitch/features/account_gallery/index.jsx (renamed from app/javascript/flavours/glitch/features/account_gallery/index.js)19
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/components/header.jsx (renamed from app/javascript/flavours/glitch/features/account_timeline/components/header.js)32
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/components/limited_account_hint.jsx (renamed from app/javascript/flavours/glitch/features/account_timeline/components/limited_account_hint.js)5
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/components/moved_note.jsx (renamed from app/javascript/flavours/glitch/features/account_timeline/components/moved_note.js)4
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/containers/header_container.jsx (renamed from app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js)2
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/index.jsx (renamed from app/javascript/flavours/glitch/features/account_timeline/index.js)9
-rw-r--r--app/javascript/flavours/glitch/features/audio/index.jsx (renamed from app/javascript/flavours/glitch/features/audio/index.js)101
-rw-r--r--app/javascript/flavours/glitch/features/blocks/index.jsx (renamed from app/javascript/flavours/glitch/features/blocks/index.js)4
-rw-r--r--app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx (renamed from app/javascript/flavours/glitch/features/bookmarked_statuses/index.js)14
-rw-r--r--app/javascript/flavours/glitch/features/closed_registrations_modal/index.jsx (renamed from app/javascript/flavours/glitch/features/closed_registrations_modal/index.js)5
-rw-r--r--app/javascript/flavours/glitch/features/community_timeline/components/column_settings.jsx (renamed from app/javascript/flavours/glitch/features/community_timeline/components/column_settings.js)3
-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.jsx (renamed from app/javascript/flavours/glitch/features/community_timeline/index.js)14
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/action_bar.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/action_bar.js)7
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/autosuggest_account.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js)0
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/character_counter.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/character_counter.js)0
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/compose_form.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/compose_form.js)51
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/dropdown.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/dropdown.js)22
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/dropdown_menu.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js)16
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js)46
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/header.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/header.js)15
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/language_dropdown.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/language_dropdown.js)37
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/navigation_bar.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/navigation_bar.js)0
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/options.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/options.js)18
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/poll_form.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/poll_form.js)25
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js)3
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/publisher.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/publisher.js)6
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/reply_indicator.js)5
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/search.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/search.js)19
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/search_results.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/search_results.js)8
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/text_icon_button.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/text_icon_button.js)0
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/textarea_icons.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/textarea_icons.js)6
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/upload.js)21
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload_form.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/upload_form.js)1
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload_progress.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/upload_progress.js)0
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/warning.jsx (renamed from app/javascript/flavours/glitch/features/compose/components/warning.js)0
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js35
-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.js6
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.jsx (renamed from app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js)0
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/upload_container.js3
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/warning_container.jsx (renamed from app/javascript/flavours/glitch/features/compose/containers/warning_container.js)6
-rw-r--r--app/javascript/flavours/glitch/features/compose/index.jsx (renamed from app/javascript/flavours/glitch/features/compose/index.js)8
-rw-r--r--app/javascript/flavours/glitch/features/compose/util/counter.js4
-rw-r--r--app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.jsx (renamed from app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.js)3
-rw-r--r--app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx (renamed from app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js)29
-rw-r--r--app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.jsx (renamed from app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.js)14
-rw-r--r--app/javascript/flavours/glitch/features/direct_timeline/index.jsx (renamed from app/javascript/flavours/glitch/features/direct_timeline/index.js)20
-rw-r--r--app/javascript/flavours/glitch/features/directory/components/account_card.jsx (renamed from app/javascript/flavours/glitch/features/directory/components/account_card.js)15
-rw-r--r--app/javascript/flavours/glitch/features/directory/index.jsx (renamed from app/javascript/flavours/glitch/features/directory/index.js)18
-rw-r--r--app/javascript/flavours/glitch/features/domain_blocks/index.jsx (renamed from app/javascript/flavours/glitch/features/domain_blocks/index.js)4
-rw-r--r--app/javascript/flavours/glitch/features/emoji/emoji.js4
-rw-r--r--app/javascript/flavours/glitch/features/emoji/emoji_utils.js12
-rw-r--r--app/javascript/flavours/glitch/features/explore/components/story.jsx (renamed from app/javascript/flavours/glitch/features/explore/components/story.js)0
-rw-r--r--app/javascript/flavours/glitch/features/explore/index.jsx (renamed from app/javascript/flavours/glitch/features/explore/index.js)12
-rw-r--r--app/javascript/flavours/glitch/features/explore/links.jsx (renamed from app/javascript/flavours/glitch/features/explore/links.js)3
-rw-r--r--app/javascript/flavours/glitch/features/explore/results.jsx (renamed from app/javascript/flavours/glitch/features/explore/results.js)4
-rw-r--r--app/javascript/flavours/glitch/features/explore/statuses.jsx (renamed from app/javascript/flavours/glitch/features/explore/statuses.js)5
-rw-r--r--app/javascript/flavours/glitch/features/explore/suggestions.jsx (renamed from app/javascript/flavours/glitch/features/explore/suggestions.js)5
-rw-r--r--app/javascript/flavours/glitch/features/explore/tags.jsx (renamed from app/javascript/flavours/glitch/features/explore/tags.js)3
-rw-r--r--app/javascript/flavours/glitch/features/favourited_statuses/index.jsx (renamed from app/javascript/flavours/glitch/features/favourited_statuses/index.js)14
-rw-r--r--app/javascript/flavours/glitch/features/favourites/index.jsx (renamed from app/javascript/flavours/glitch/features/favourites/index.js)10
-rw-r--r--app/javascript/flavours/glitch/features/filters/added_to_filter.jsx (renamed from app/javascript/flavours/glitch/features/filters/added_to_filter.js)3
-rw-r--r--app/javascript/flavours/glitch/features/filters/select_filter.jsx (renamed from app/javascript/flavours/glitch/features/filters/select_filter.js)22
-rw-r--r--app/javascript/flavours/glitch/features/follow_recommendations/components/account.jsx (renamed from app/javascript/flavours/glitch/features/follow_recommendations/components/account.js)8
-rw-r--r--app/javascript/flavours/glitch/features/follow_recommendations/index.jsx (renamed from app/javascript/flavours/glitch/features/follow_recommendations/index.js)5
-rw-r--r--app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx (renamed from app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.js)3
-rw-r--r--app/javascript/flavours/glitch/features/follow_requests/index.jsx (renamed from app/javascript/flavours/glitch/features/follow_requests/index.js)16
-rw-r--r--app/javascript/flavours/glitch/features/followed_tags/index.jsx89
-rw-r--r--app/javascript/flavours/glitch/features/followers/index.jsx (renamed from app/javascript/flavours/glitch/features/followers/index.js)7
-rw-r--r--app/javascript/flavours/glitch/features/following/index.jsx (renamed from app/javascript/flavours/glitch/features/following/index.js)7
-rw-r--r--app/javascript/flavours/glitch/features/generic_not_found/index.jsx (renamed from app/javascript/flavours/glitch/features/generic_not_found/index.js)0
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx (renamed from app/javascript/flavours/glitch/features/getting_started/components/announcements.js)34
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/components/trends.jsx (renamed from app/javascript/flavours/glitch/features/getting_started/components/trends.js)0
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/index.jsx (renamed from app/javascript/flavours/glitch/features/getting_started/index.js)8
-rw-r--r--app/javascript/flavours/glitch/features/getting_started_misc/index.jsx (renamed from app/javascript/flavours/glitch/features/getting_started_misc/index.js)37
-rw-r--r--app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.jsx (renamed from app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js)7
-rw-r--r--app/javascript/flavours/glitch/features/hashtag_timeline/index.jsx (renamed from app/javascript/flavours/glitch/features/hashtag_timeline/index.js)26
-rw-r--r--app/javascript/flavours/glitch/features/home_timeline/components/column_settings.jsx (renamed from app/javascript/flavours/glitch/features/home_timeline/components/column_settings.js)3
-rw-r--r--app/javascript/flavours/glitch/features/home_timeline/index.jsx (renamed from app/javascript/flavours/glitch/features/home_timeline/index.js)16
-rw-r--r--app/javascript/flavours/glitch/features/interaction_modal/index.jsx (renamed from app/javascript/flavours/glitch/features/interaction_modal/index.js)11
-rw-r--r--app/javascript/flavours/glitch/features/keyboard_shortcuts/index.jsx (renamed from app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js)4
-rw-r--r--app/javascript/flavours/glitch/features/list_adder/components/account.jsx (renamed from app/javascript/flavours/glitch/features/list_adder/components/account.js)4
-rw-r--r--app/javascript/flavours/glitch/features/list_adder/components/list.jsx (renamed from app/javascript/flavours/glitch/features/list_adder/components/list.js)6
-rw-r--r--app/javascript/flavours/glitch/features/list_adder/index.jsx (renamed from app/javascript/flavours/glitch/features/list_adder/index.js)4
-rw-r--r--app/javascript/flavours/glitch/features/list_editor/components/account.jsx (renamed from app/javascript/flavours/glitch/features/list_editor/components/account.js)0
-rw-r--r--app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.jsx (renamed from app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.js)10
-rw-r--r--app/javascript/flavours/glitch/features/list_editor/components/search.jsx (renamed from app/javascript/flavours/glitch/features/list_editor/components/search.js)8
-rw-r--r--app/javascript/flavours/glitch/features/list_editor/index.jsx (renamed from app/javascript/flavours/glitch/features/list_editor/index.js)6
-rw-r--r--app/javascript/flavours/glitch/features/list_timeline/index.jsx (renamed from app/javascript/flavours/glitch/features/list_timeline/index.js)26
-rw-r--r--app/javascript/flavours/glitch/features/lists/components/new_list_form.jsx (renamed from app/javascript/flavours/glitch/features/lists/components/new_list_form.js)10
-rw-r--r--app/javascript/flavours/glitch/features/lists/index.jsx (renamed from app/javascript/flavours/glitch/features/lists/index.js)4
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/index.jsx (renamed from app/javascript/flavours/glitch/features/local_settings/index.js)0
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx (renamed from app/javascript/flavours/glitch/features/local_settings/navigation/index.js)5
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/navigation/item/index.jsx (renamed from app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js)4
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/deprecated_item/index.jsx (renamed from app/javascript/flavours/glitch/features/local_settings/page/deprecated_item/index.js)0
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/index.jsx (renamed from app/javascript/flavours/glitch/features/local_settings/page/index.js)27
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/item/index.jsx (renamed from app/javascript/flavours/glitch/features/local_settings/page/item/index.js)14
-rw-r--r--app/javascript/flavours/glitch/features/mutes/index.jsx (renamed from app/javascript/flavours/glitch/features/mutes/index.js)4
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/admin_report.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/admin_report.js)12
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/admin_signup.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/admin_signup.js)12
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/clear_column_button.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/clear_column_button.js)2
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/column_settings.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/column_settings.js)2
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/filter_bar.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/filter_bar.js)3
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/follow.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/follow.js)12
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/follow_request.js)15
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/grant_permission_button.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/grant_permission_button.js)2
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/notification.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/notification.js)5
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.js)8
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/overlay.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/overlay.js)5
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/pill_bar_button.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/pill_bar_button.js)6
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/report.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/report.js)3
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/setting_toggle.jsx (renamed from app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js)4
-rw-r--r--app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js3
-rw-r--r--app/javascript/flavours/glitch/features/notifications/index.jsx (renamed from app/javascript/flavours/glitch/features/notifications/index.js)22
-rw-r--r--app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx (renamed from app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js)8
-rw-r--r--app/javascript/flavours/glitch/features/picture_in_picture/components/header.jsx (renamed from app/javascript/flavours/glitch/features/picture_in_picture/components/header.js)4
-rw-r--r--app/javascript/flavours/glitch/features/picture_in_picture/index.jsx (renamed from app/javascript/flavours/glitch/features/picture_in_picture/index.js)5
-rw-r--r--app/javascript/flavours/glitch/features/pinned_accounts_editor/containers/search_container.js2
-rw-r--r--app/javascript/flavours/glitch/features/pinned_accounts_editor/index.jsx (renamed from app/javascript/flavours/glitch/features/pinned_accounts_editor/index.js)6
-rw-r--r--app/javascript/flavours/glitch/features/pinned_statuses/index.jsx (renamed from app/javascript/flavours/glitch/features/pinned_statuses/index.js)8
-rw-r--r--app/javascript/flavours/glitch/features/privacy_policy/index.jsx (renamed from app/javascript/flavours/glitch/features/privacy_policy/index.js)3
-rw-r--r--app/javascript/flavours/glitch/features/public_timeline/components/column_settings.jsx (renamed from app/javascript/flavours/glitch/features/public_timeline/components/column_settings.js)3
-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.jsx (renamed from app/javascript/flavours/glitch/features/public_timeline/index.js)14
-rw-r--r--app/javascript/flavours/glitch/features/reblogs/index.jsx (renamed from app/javascript/flavours/glitch/features/reblogs/index.js)10
-rw-r--r--app/javascript/flavours/glitch/features/report/category.jsx (renamed from app/javascript/flavours/glitch/features/report/category.js)4
-rw-r--r--app/javascript/flavours/glitch/features/report/comment.jsx (renamed from app/javascript/flavours/glitch/features/report/comment.js)3
-rw-r--r--app/javascript/flavours/glitch/features/report/components/option.jsx (renamed from app/javascript/flavours/glitch/features/report/components/option.js)6
-rw-r--r--app/javascript/flavours/glitch/features/report/components/status_check_box.jsx (renamed from app/javascript/flavours/glitch/features/report/components/status_check_box.js)0
-rw-r--r--app/javascript/flavours/glitch/features/report/rules.jsx (renamed from app/javascript/flavours/glitch/features/report/rules.js)3
-rw-r--r--app/javascript/flavours/glitch/features/report/statuses.jsx (renamed from app/javascript/flavours/glitch/features/report/statuses.js)3
-rw-r--r--app/javascript/flavours/glitch/features/report/thanks.jsx (renamed from app/javascript/flavours/glitch/features/report/thanks.js)3
-rw-r--r--app/javascript/flavours/glitch/features/standalone/compose/index.jsx (renamed from app/javascript/flavours/glitch/features/standalone/compose/index.js)2
-rw-r--r--app/javascript/flavours/glitch/features/status/components/action_bar.jsx (renamed from app/javascript/flavours/glitch/features/status/components/action_bar.js)37
-rw-r--r--app/javascript/flavours/glitch/features/status/components/card.jsx (renamed from app/javascript/flavours/glitch/features/status/components/card.js)11
-rw-r--r--app/javascript/flavours/glitch/features/status/components/detailed_status.jsx (renamed from app/javascript/flavours/glitch/features/status/components/detailed_status.js)26
-rw-r--r--app/javascript/flavours/glitch/features/status/index.jsx (renamed from app/javascript/flavours/glitch/features/status/index.js)108
-rw-r--r--app/javascript/flavours/glitch/features/subscribed_languages_modal/index.jsx (renamed from app/javascript/flavours/glitch/features/subscribed_languages_modal/index.js)6
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/actions_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/actions_modal.js)4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/audio_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/audio_modal.js)8
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/block_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/block_modal.js)12
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/boost_modal.js)16
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/bundle.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/bundle.js)10
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/bundle_column_error.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js)11
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.js)4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/column.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/column.js)6
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/column_header.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/column_header.js)2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/column_link.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/column_link.js)4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/column_loading.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/column_loading.js)0
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/column_subheading.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/column_subheading.js)0
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/columns_area.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/columns_area.js)12
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/compare_history_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js)14
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/compose_panel.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/compose_panel.js)5
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/confirmation_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js)13
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js)7
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.js)8
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/doodle_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/doodle_modal.js)9
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/drawer_loading.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/drawer_loading.js)0
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/embed_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/embed_modal.js)9
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/favourite_modal.js)11
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/filter_modal.js)4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js)47
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.js)4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/header.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/header.js)4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/image_loader.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/image_loader.js)13
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/image_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/image_modal.js)3
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/link_footer.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/link_footer.js)24
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/list_panel.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/list_panel.js)4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/media_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/media_modal.js)29
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/modal_loading.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/modal_loading.js)0
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/modal_root.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/modal_root.js)16
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/mute_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/mute_modal.js)14
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/navigation_panel.js)5
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/onboarding_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js)24
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/report_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/report_modal.js)6
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/sign_in_banner.js)2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/upload_area.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/upload_area.js)2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/video_modal.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/video_modal.js)13
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/zoomable_image.jsx (renamed from app/javascript/flavours/glitch/features/ui/components/zoomable_image.js)36
-rw-r--r--app/javascript/flavours/glitch/features/ui/index.jsx (renamed from app/javascript/flavours/glitch/features/ui/index.js)84
-rw-r--r--app/javascript/flavours/glitch/features/ui/util/async-components.js4
-rw-r--r--app/javascript/flavours/glitch/features/ui/util/react_router_helpers.jsx (renamed from app/javascript/flavours/glitch/features/ui/util/react_router_helpers.js)10
-rw-r--r--app/javascript/flavours/glitch/features/ui/util/reduced_motion.jsx (renamed from app/javascript/flavours/glitch/features/ui/util/reduced_motion.js)2
-rw-r--r--app/javascript/flavours/glitch/features/video/index.jsx (renamed from app/javascript/flavours/glitch/features/video/index.js)67
194 files changed, 1266 insertions, 982 deletions
diff --git a/app/javascript/flavours/glitch/features/about/index.js b/app/javascript/flavours/glitch/features/about/index.jsx
index 57f295ea9..67d5afbdd 100644
--- a/app/javascript/flavours/glitch/features/about/index.js
+++ b/app/javascript/flavours/glitch/features/about/index.jsx
@@ -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;
@@ -67,7 +67,7 @@ class Section extends React.PureComponent {
 
     return (
       <div className={classNames('about__section', { active: !collapsed })}>
-        <div className='about__section__title' role='button' tabIndex='0' onClick={this.handleClick}>
+        <div className='about__section__title' role='button' tabIndex={0} onClick={this.handleClick}>
           <Icon id={collapsed ? 'chevron-right' : 'chevron-down'} fixedWidth /> {title}
         </div>
 
@@ -80,8 +80,6 @@ class Section extends React.PureComponent {
 
 }
 
-export default @connect(mapStateToProps)
-@injectIntl
 class About extends React.PureComponent {
 
   static propTypes = {
@@ -106,7 +104,7 @@ class About extends React.PureComponent {
   handleDomainBlocksOpen = () => {
     const { dispatch } = this.props;
     dispatch(fetchDomainBlocks());
-  }
+  };
 
   render () {
     const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props;
@@ -218,3 +216,5 @@ class About extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(About));
diff --git a/app/javascript/flavours/glitch/features/account/components/account_note.js b/app/javascript/flavours/glitch/features/account/components/account_note.jsx
index 6e5ed7703..8ab00f6d4 100644
--- a/app/javascript/flavours/glitch/features/account/components/account_note.js
+++ b/app/javascript/flavours/glitch/features/account/components/account_note.jsx
@@ -10,7 +10,6 @@ const messages = defineMessages({
   placeholder: { id: 'account_note.glitch_placeholder', defaultMessage: 'No comment provided' },
 });
 
-export default @injectIntl
 class Header extends ImmutablePureComponent {
 
   static propTypes = {
@@ -41,7 +40,7 @@ class Header extends ImmutablePureComponent {
     } else if (e.keyCode === 27) {
       this.props.onCancelAccountNote();
     }
-  }
+  };
 
   render () {
     const { account, accountNote, isEditing, isSubmitting, intl } = this.props;
@@ -54,11 +53,11 @@ class Header extends ImmutablePureComponent {
     if (isEditing) {
       action_buttons = (
         <div className='account__header__account-note__buttons'>
-          <button className='icon-button' tabIndex='0' onClick={this.props.onCancelAccountNote} disabled={isSubmitting}>
+          <button className='icon-button' tabIndex={0} onClick={this.props.onCancelAccountNote} disabled={isSubmitting}>
             <Icon id='times' size={15} /> <FormattedMessage id='account_note.cancel' defaultMessage='Cancel' />
           </button>
           <div className='flex-spacer' />
-          <button className='icon-button' tabIndex='0' onClick={this.props.onSaveAccountNote} disabled={isSubmitting}>
+          <button className='icon-button' tabIndex={0} onClick={this.props.onSaveAccountNote} disabled={isSubmitting}>
             <Icon id='check' size={15} /> <FormattedMessage id='account_note.save' defaultMessage='Save' />
           </button>
         </div>
@@ -66,7 +65,7 @@ class Header extends ImmutablePureComponent {
     } else {
       action_buttons = (
         <div className='account__header__account-note__buttons'>
-          <button className='icon-button' tabIndex='0' onClick={this.props.onEditAccountNote} disabled={isSubmitting}>
+          <button className='icon-button' tabIndex={0} onClick={this.props.onEditAccountNote} disabled={isSubmitting}>
             <Icon id='pencil' size={15} /> <FormattedMessage id='account_note.edit' defaultMessage='Edit' />
           </button>
         </div>
@@ -102,3 +101,5 @@ class Header extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(Header);
diff --git a/app/javascript/flavours/glitch/features/account/components/action_bar.js b/app/javascript/flavours/glitch/features/account/components/action_bar.jsx
index ce0584124..e32bc0141 100644
--- a/app/javascript/flavours/glitch/features/account/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/account/components/action_bar.jsx
@@ -8,7 +8,6 @@ import { me, isStaff } from 'flavours/glitch/initial_state';
 import { profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links';
 import Icon from 'flavours/glitch/components/icon';
 
-export default @injectIntl
 class ActionBar extends React.PureComponent {
 
   static propTypes = {
@@ -21,7 +20,7 @@ class ActionBar extends React.PureComponent {
       return false;
     }
     return !location.pathname.match(/\/(followers|following)\/?$/);
-  }
+  };
 
   render () {
     const { account, intl } = this.props;
@@ -32,7 +31,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>
@@ -83,3 +82,5 @@ class ActionBar extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ActionBar);
diff --git a/app/javascript/flavours/glitch/features/account/components/featured_tags.js b/app/javascript/flavours/glitch/features/account/components/featured_tags.jsx
index d646b08b2..42e0a8d2f 100644
--- a/app/javascript/flavours/glitch/features/account/components/featured_tags.js
+++ b/app/javascript/flavours/glitch/features/account/components/featured_tags.jsx
@@ -10,7 +10,6 @@ const messages = defineMessages({
   empty: { id: 'account.featured_tags.last_status_never', defaultMessage: 'No posts' },
 });
 
-export default @injectIntl
 class FeaturedTags extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -51,3 +50,5 @@ class FeaturedTags extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(FeaturedTags);
diff --git a/app/javascript/flavours/glitch/features/account/components/follow_request_note.js b/app/javascript/flavours/glitch/features/account/components/follow_request_note.jsx
index 73c1737a6..73c1737a6 100644
--- a/app/javascript/flavours/glitch/features/account/components/follow_request_note.js
+++ b/app/javascript/flavours/glitch/features/account/components/follow_request_note.jsx
diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.jsx
index ec4a192bc..6f918abcf 100644
--- a/app/javascript/flavours/glitch/features/account/components/header.js
+++ b/app/javascript/flavours/glitch/features/account/components/header.jsx
@@ -45,6 +45,7 @@ const messages = defineMessages({
   follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
   favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
   lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
+  followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
   blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
   domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
   mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
@@ -75,7 +76,6 @@ const dateFormatOptions = {
   minute: '2-digit',
 };
 
-export default @injectIntl
 class Header extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -108,7 +108,7 @@ class Header extends ImmutablePureComponent {
 
   openEditProfile = () => {
     window.open(profileLink, '_blank');
-  }
+  };
 
   handleMouseEnter = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -121,7 +121,7 @@ class Header extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -134,14 +134,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;
@@ -152,7 +152,7 @@ class Header extends ImmutablePureComponent {
     }).catch((e) => {
       if (e.name !== 'AbortError') console.error(e);
     });
-  }
+  };
 
   render () {
     const { account, hidden, intl, domain } = this.props;
@@ -176,8 +176,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>);
     }
 
@@ -188,7 +187,7 @@ class Header extends ImmutablePureComponent {
     }
 
     if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) {
-      bellBtn = <IconButton icon='bell-o' size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
+      bellBtn = <IconButton icon={account.getIn(['relationship', 'notifying']) ? 'bell' : 'bell-o'} size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
     }
 
     if (me !== account.get('id')) {
@@ -245,6 +244,7 @@ class Header extends ImmutablePureComponent {
       menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
       menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
       menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
+      menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
       menu.push(null);
       menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
       menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
@@ -319,6 +319,11 @@ class Header extends ImmutablePureComponent {
       badge = null;
     }
 
+    let role = null;
+    if (account.getIn(['roles', 0])) {
+      role = (<div key='role' className={`account-role user-role-${account.getIn(['roles', 0, 'id'])}`}>{account.getIn(['roles', 0, 'name'])}</div>);
+    }
+
     return (
       <div className={classNames('account__header', { inactive: !!account.get('moved') })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
         {!(suspended || hidden || account.get('moved')) && account.getIn(['relationship', 'requested_by']) && <FollowRequestNoteContainer account={account} />}
@@ -335,6 +340,7 @@ class Header extends ImmutablePureComponent {
           <div className='account__header__tabs'>
             <a className='avatar' href={account.get('avatar')} rel='noopener noreferrer' target='_blank' onClick={this.handleAvatarClick}>
               <Avatar account={suspended || hidden ? undefined : account} size={90} />
+              {role}
             </a>
 
             {!suspended && (
@@ -370,7 +376,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>
@@ -396,3 +402,5 @@ class Header extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(Header);
diff --git a/app/javascript/flavours/glitch/features/account/components/profile_column_header.js b/app/javascript/flavours/glitch/features/account/components/profile_column_header.jsx
index 17c08e375..62a459fff 100644
--- a/app/javascript/flavours/glitch/features/account/components/profile_column_header.js
+++ b/app/javascript/flavours/glitch/features/account/components/profile_column_header.jsx
@@ -7,7 +7,6 @@ const messages = defineMessages({
   profile: { id: 'column_header.profile', defaultMessage: 'Profile' },
 });
 
-export default @injectIntl
 class ProfileColumnHeader extends React.PureComponent {
 
   static propTypes = {
@@ -31,3 +30,5 @@ class ProfileColumnHeader extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ProfileColumnHeader);
diff --git a/app/javascript/flavours/glitch/features/account/navigation.js b/app/javascript/flavours/glitch/features/account/navigation.jsx
index edae38ce5..b8b8e54de 100644
--- a/app/javascript/flavours/glitch/features/account/navigation.js
+++ b/app/javascript/flavours/glitch/features/account/navigation.jsx
@@ -19,7 +19,6 @@ const mapStateToProps = (state, { match: { params: { acct } } }) => {
   };
 };
 
-export default @connect(mapStateToProps)
 class AccountNavigation extends React.PureComponent {
 
   static propTypes = {
@@ -50,3 +49,5 @@ class AccountNavigation extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(AccountNavigation);
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.jsx
index f20ee685e..5fd84996b 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.jsx
@@ -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;
@@ -76,6 +76,7 @@ export default class MediaItem extends ImmutablePureComponent {
           <img
             src={attachment.get('preview_url') || attachment.getIn(['account', 'avatar_static'])}
             alt={attachment.get('description')}
+            lang={status.get('language')}
             onLoad={this.handleImageLoad}
           />
         );
@@ -95,6 +96,7 @@ export default class MediaItem extends ImmutablePureComponent {
           <img
             src={attachment.get('preview_url')}
             alt={attachment.get('description')}
+            lang={status.get('language')}
             style={{ objectPosition: `${x}% ${y}%` }}
             onLoad={this.handleImageLoad}
           />
@@ -105,6 +107,7 @@ export default class MediaItem extends ImmutablePureComponent {
             className='media-gallery__item-gifv-thumbnail'
             aria-label={attachment.get('description')}
             title={attachment.get('description')}
+            lang={status.get('language')}
             role='application'
             src={attachment.get('url')}
             onMouseEnter={this.handleMouseEnter}
diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.jsx
index 638224bc0..6914bcba7 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/index.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/index.jsx
@@ -45,7 +45,7 @@ class LoadMoreMedia extends ImmutablePureComponent {
 
   handleLoadMore = () => {
     this.props.onLoadMore(this.props.maxId);
-  }
+  };
 
   render () {
     return (
@@ -58,7 +58,6 @@ class LoadMoreMedia extends ImmutablePureComponent {
 
 }
 
-export default @connect(mapStateToProps)
 class AccountGallery extends ImmutablePureComponent {
 
   static propTypes = {
@@ -109,13 +108,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 +123,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 +132,11 @@ class AccountGallery extends ImmutablePureComponent {
   handleLoadOlder = e => {
     e.preventDefault();
     this.handleScrollToBottom();
-  }
+  };
 
   setColumnRef = c => {
     this.column = c;
-  }
+  };
 
   handleOpenMedia = attachment => {
     const { dispatch } = this.props;
@@ -153,13 +152,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;
@@ -223,3 +222,5 @@ class AccountGallery extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(AccountGallery);
diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.jsx
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.jsx
@@ -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.jsx
index ca0e60672..c622b7607 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.jsx
@@ -14,13 +14,12 @@ const mapDispatchToProps = (dispatch, { accountId }) => ({
 
 });
 
-export default @connect(() => {}, mapDispatchToProps)
 class LimitedAccountHint extends React.PureComponent {
 
   static propTypes = {
     accountId: PropTypes.string.isRequired,
     reveal: PropTypes.func,
-  }
+  };
 
   render () {
     const { reveal } = this.props;
@@ -34,3 +33,5 @@ class LimitedAccountHint extends React.PureComponent {
   }
 
 }
+
+export default connect(() => {}, mapDispatchToProps)(LimitedAccountHint);
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.jsx
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.jsx
@@ -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.jsx
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.jsx
@@ -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.jsx
index b79082f00..38361b1ca 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/index.jsx
@@ -62,7 +62,6 @@ RemoteHint.propTypes = {
   url: PropTypes.string.isRequired,
 };
 
-export default @connect(mapStateToProps)
 class AccountTimeline extends ImmutablePureComponent {
 
   static propTypes = {
@@ -140,15 +139,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;
@@ -207,3 +206,5 @@ class AccountTimeline extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(AccountTimeline);
diff --git a/app/javascript/flavours/glitch/features/audio/index.js b/app/javascript/flavours/glitch/features/audio/index.jsx
index 014a0a213..556a74ac4 100644
--- a/app/javascript/flavours/glitch/features/audio/index.js
+++ b/app/javascript/flavours/glitch/features/audio/index.jsx
@@ -1,12 +1,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
-import { formatTime } from 'flavours/glitch/features/video';
+import { formatTime, getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video';
 import Icon from 'flavours/glitch/components/icon';
 import classNames from 'classnames';
-import { throttle } from 'lodash';
-import { getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video';
-import { debounce } from 'lodash';
+import { throttle, debounce } from 'lodash';
 import Visualizer from './visualizer';
 import { displayMedia, useBlurhash } from 'flavours/glitch/initial_state';
 import Blurhash from 'flavours/glitch/components/blurhash';
@@ -24,12 +22,12 @@ const messages = defineMessages({
 const TICK_SIZE = 10;
 const PADDING   = 180;
 
-export default @injectIntl
 class Audio extends React.PureComponent {
 
   static propTypes = {
     src: PropTypes.string.isRequired,
     alt: PropTypes.string,
+    lang: PropTypes.string,
     poster: PropTypes.string,
     duration: PropTypes.number,
     width: PropTypes.number,
@@ -59,7 +57,7 @@ class Audio extends React.PureComponent {
     duration: null,
     paused: true,
     muted: false,
-    volume: 0.5,
+    volume: 1,
     dragging: false,
     revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
   };
@@ -75,13 +73,13 @@ class Audio extends React.PureComponent {
     if (this.player) {
       this._setDimensions();
     }
-  }
+  };
 
   _pack() {
     return {
       src: this.props.src,
-      volume: this.audio.volume,
-      muted: this.audio.muted,
+      volume: this.state.volume,
+      muted: this.state.muted,
       currentTime: this.audio.currentTime,
       poster: this.props.poster,
       backgroundColor: this.props.backgroundColor,
@@ -107,26 +105,27 @@ class Audio extends React.PureComponent {
 
   setSeekRef = c => {
     this.seek = c;
-  }
+  };
 
   setVolumeRef = c => {
     this.volume = c;
-  }
+  };
 
   setAudioRef = c => {
     this.audio = c;
 
     if (this.audio) {
-      this.setState({ volume: this.audio.volume, muted: this.audio.muted });
+      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 });
@@ -168,7 +167,7 @@ class Audio extends React.PureComponent {
     } else {
       this.setState({ paused: true }, () => this.audio.pause());
     }
-  }
+  };
 
   handleResize = debounce(() => {
     if (this.player) {
@@ -186,7 +185,7 @@ class Audio extends React.PureComponent {
     }
 
     this._renderCanvas();
-  }
+  };
 
   handlePause = () => {
     this.setState({ paused: true });
@@ -194,7 +193,7 @@ class Audio extends React.PureComponent {
     if (this.audioContext) {
       this.audioContext.suspend();
     }
-  }
+  };
 
   handleProgress = () => {
     const lastTimeRange = this.audio.buffered.length - 1;
@@ -202,15 +201,17 @@ 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;
 
     this.setState({ muted }, () => {
-      this.audio.muted = muted;
+      if (this.gainNode) {
+        this.gainNode.gain.value = muted ? 0 : this.state.volume;
+      }
     });
-  }
+  };
 
   toggleReveal = () => {
     if (this.props.onToggleVisibility) {
@@ -218,7 +219,7 @@ class Audio extends React.PureComponent {
     } else {
       this.setState({ revealed: !this.state.revealed });
     }
-  }
+  };
 
   handleVolumeMouseDown = e => {
     document.addEventListener('mousemove', this.handleMouseVolSlide, true);
@@ -230,14 +231,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);
@@ -251,7 +252,7 @@ class Audio extends React.PureComponent {
 
     e.preventDefault();
     e.stopPropagation();
-  }
+  };
 
   handleMouseUp = () => {
     document.removeEventListener('mousemove', this.handleMouseMove, true);
@@ -261,7 +262,7 @@ class Audio extends React.PureComponent {
 
     this.setState({ dragging: false });
     this.audio.play();
-  }
+  };
 
   handleMouseMove = throttle(e => {
     const { x } = getPointerPosition(this.seek, e);
@@ -279,14 +280,16 @@ class Audio extends React.PureComponent {
       currentTime: this.audio.currentTime,
       duration: this.audio.duration,
     });
-  }
+  };
 
   handleMouseVolSlide = throttle(e => {
     const { x } = getPointerPosition(this.volume, e);
 
     if(!isNaN(x)) {
       this.setState({ volume: x }, () => {
-        this.audio.volume = x;
+        if (this.gainNode) {
+          this.gainNode.gain.value = this.state.muted ? 0 : x;
+        }
       });
     }
   }, 15);
@@ -312,41 +315,38 @@ class Audio extends React.PureComponent {
 
   handleMouseEnter = () => {
     this.setState({ hovered: true });
-  }
+  };
 
   handleMouseLeave = () => {
     this.setState({ hovered: false });
-  }
+  };
 
   handleLoadedData = () => {
-    const { autoPlay, currentTime, volume, muted } = this.props;
+    const { autoPlay, currentTime } = this.props;
 
     if (currentTime) {
       this.audio.currentTime = currentTime;
     }
 
-    if (volume !== undefined) {
-      this.audio.volume = volume;
-    }
-
-    if (muted !== undefined) {
-      this.audio.muted = muted;
-    }
-
     if (autoPlay) {
       this.togglePlay();
     }
-  }
+  };
 
   _initAudioContext () {
     const AudioContext = window.AudioContext || window.webkitAudioContext;
     const context      = new AudioContext();
     const source       = context.createMediaElementSource(this.audio);
+    const gainNode     = context.createGain();
+
+    gainNode.gain.value = this.state.muted ? 0 : this.state.volume;
 
     this.visualizer.setAudioContext(context, source);
-    source.connect(context.destination);
+    source.connect(gainNode);
+    gainNode.connect(context.destination);
 
     this.audioContext = context;
+    this.gainNode = gainNode;
   }
 
   handleDownload = () => {
@@ -365,7 +365,7 @@ class Audio extends React.PureComponent {
     }).catch(err => {
       console.error(err);
     });
-  }
+  };
 
   _renderCanvas () {
     requestAnimationFrame(() => {
@@ -436,7 +436,7 @@ class Audio extends React.PureComponent {
       e.stopPropagation();
       this.togglePlay();
     }
-  }
+  };
 
   handleKeyDown = e => {
     switch(e.key) {
@@ -461,10 +461,10 @@ class Audio extends React.PureComponent {
       this.seekBy(10);
       break;
     }
-  }
+  };
 
   render () {
-    const { src, intl, alt, editable, autoPlay, sensitive, blurhash } = this.props;
+    const { src, intl, alt, lang, editable, autoPlay, sensitive, blurhash } = this.props;
     const { paused, muted, volume, currentTime, duration, buffer, dragging, revealed } = this.state;
     const progress = Math.min((currentTime / duration) * 100, 100);
 
@@ -476,7 +476,7 @@ class Audio extends React.PureComponent {
     }
 
     return (
-      <div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex='0' onKeyDown={this.handleKeyDown}>
+      <div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex={0} onKeyDown={this.handleKeyDown}>
 
         <Blurhash
           hash={blurhash}
@@ -499,7 +499,7 @@ class Audio extends React.PureComponent {
 
         <canvas
           role='button'
-          tabIndex='0'
+          tabIndex={0}
           className='audio-player__canvas'
           width={this.state.width}
           height={this.state.height}
@@ -509,6 +509,7 @@ class Audio extends React.PureComponent {
           onKeyDown={this.handleAudioKeyDown}
           title={alt}
           aria-label={alt}
+          lang={lang}
         />
 
         <div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
@@ -531,7 +532,7 @@ class Audio extends React.PureComponent {
 
           <span
             className={classNames('video-player__seek__handle', { active: dragging })}
-            tabIndex='0'
+            tabIndex={0}
             style={{ left: `${progress}%`, backgroundColor: this._getAccentColor() }}
             onKeyDown={this.handleAudioKeyDown}
           />
@@ -548,7 +549,7 @@ class Audio extends React.PureComponent {
 
                 <span
                   className='video-player__volume__handle'
-                  tabIndex='0'
+                  tabIndex={0}
                   style={{ left: `${volume * 100}%`, backgroundColor: this._getAccentColor() }}
                 />
               </div>
@@ -573,3 +574,5 @@ class Audio extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(Audio);
diff --git a/app/javascript/flavours/glitch/features/blocks/index.js b/app/javascript/flavours/glitch/features/blocks/index.jsx
index 4461bd14d..461dac2ec 100644
--- a/app/javascript/flavours/glitch/features/blocks/index.js
+++ b/app/javascript/flavours/glitch/features/blocks/index.jsx
@@ -22,8 +22,6 @@ const mapStateToProps = state => ({
   isLoading: state.getIn(['user_lists', 'blocks', 'isLoading'], true),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class Blocks extends ImmutablePureComponent {
 
   static propTypes = {
@@ -77,3 +75,5 @@ class Blocks extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(Blocks));
diff --git a/app/javascript/flavours/glitch/features/bookmarked_statuses/index.js b/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx
index 8978ac5fc..90d8fd0ef 100644
--- a/app/javascript/flavours/glitch/features/bookmarked_statuses/index.js
+++ b/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx
@@ -22,8 +22,6 @@ const mapStateToProps = state => ({
   hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class Bookmarks extends ImmutablePureComponent {
 
   static propTypes = {
@@ -48,24 +46,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;
@@ -106,3 +104,5 @@ class Bookmarks extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(Bookmarks));
diff --git a/app/javascript/flavours/glitch/features/closed_registrations_modal/index.js b/app/javascript/flavours/glitch/features/closed_registrations_modal/index.jsx
index cb91636cb..1f17ea9cf 100644
--- a/app/javascript/flavours/glitch/features/closed_registrations_modal/index.js
+++ b/app/javascript/flavours/glitch/features/closed_registrations_modal/index.jsx
@@ -9,7 +9,6 @@ const mapStateToProps = state => ({
   message: state.getIn(['server', 'server', 'registrations', 'message']),
 });
 
-export default @connect(mapStateToProps)
 class ClosedRegistrationsModal extends ImmutablePureComponent {
 
   componentDidMount () {
@@ -72,4 +71,6 @@ class ClosedRegistrationsModal extends ImmutablePureComponent {
     );
   }
 
-};
+}
+
+export default connect(mapStateToProps)(ClosedRegistrationsModal);
diff --git a/app/javascript/flavours/glitch/features/community_timeline/components/column_settings.js b/app/javascript/flavours/glitch/features/community_timeline/components/column_settings.jsx
index 69a4699ac..0ea874e95 100644
--- a/app/javascript/flavours/glitch/features/community_timeline/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/community_timeline/components/column_settings.jsx
@@ -10,7 +10,6 @@ const messages = defineMessages({
   settings: { id: 'home.settings', defaultMessage: 'Column settings' },
 });
 
-export default @injectIntl
 class ColumnSettings extends React.PureComponent {
 
   static propTypes = {
@@ -39,3 +38,5 @@ class ColumnSettings extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ColumnSettings);
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.jsx
index 67bf54875..8f3e10fe9 100644
--- a/app/javascript/flavours/glitch/features/community_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/community_timeline/index.jsx
@@ -32,8 +32,6 @@ const mapStateToProps = (state, { columnId }) => {
   };
 };
 
-export default @connect(mapStateToProps)
-@injectIntl
 class CommunityTimeline extends React.PureComponent {
 
   static defaultProps = {
@@ -63,16 +61,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 +110,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;
@@ -162,3 +160,5 @@ class CommunityTimeline extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(CommunityTimeline));
diff --git a/app/javascript/flavours/glitch/features/compose/components/action_bar.js b/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx
index 267c0ba69..af1f02efc 100644
--- a/app/javascript/flavours/glitch/features/compose/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx
@@ -12,6 +12,7 @@ const messages = defineMessages({
   follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
   favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
   lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
+  followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
   blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
   domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
   mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
@@ -20,7 +21,6 @@ const messages = defineMessages({
   bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
 });
 
-export default @injectIntl
 class ActionBar extends React.PureComponent {
 
   static propTypes = {
@@ -31,7 +31,7 @@ class ActionBar extends React.PureComponent {
 
   handleLogout = () => {
     this.props.onLogout();
-  }
+  };
 
   render () {
     const { intl } = this.props;
@@ -46,6 +46,7 @@ class ActionBar extends React.PureComponent {
     menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
     menu.push({ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' });
     menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
+    menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
     menu.push(null);
     menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
     menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
@@ -64,3 +65,5 @@ class ActionBar extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ActionBar);
diff --git a/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js b/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.jsx
index fb9bb5035..fb9bb5035 100644
--- a/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js
+++ b/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.jsx
diff --git a/app/javascript/flavours/glitch/features/compose/components/character_counter.js b/app/javascript/flavours/glitch/features/compose/components/character_counter.jsx
index 0ecfc9141..0ecfc9141 100644
--- a/app/javascript/flavours/glitch/features/compose/components/character_counter.js
+++ b/app/javascript/flavours/glitch/features/compose/components/character_counter.jsx
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx
index 0462c7c4b..973a17a1a 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx
@@ -21,14 +21,17 @@ 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' },
 });
 
-export default @injectIntl
 class ComposeForm extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -61,6 +64,7 @@ class ComposeForm extends ImmutablePureComponent {
     anyMedia: PropTypes.bool,
     isInReply: PropTypes.bool,
     singleColumn: PropTypes.bool,
+    lang: PropTypes.string,
 
     advancedOptions: ImmutablePropTypes.map,
     layout: PropTypes.string,
@@ -82,22 +86,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 {
@@ -128,7 +132,7 @@ class ComposeForm extends ImmutablePureComponent {
       }
       onSubmit(this.context.router ? this.context.router.history : null);
     }
-  }
+  };
 
   //  Changes the text value of the spoiler.
   handleChangeSpoiler = ({ target: { value } }) => {
@@ -136,7 +140,7 @@ class ComposeForm extends ImmutablePureComponent {
     if (onChangeSpoilerText) {
       onChangeSpoilerText(value);
     }
-  }
+  };
 
   setRef = c => {
     this.composeForm = c;
@@ -149,7 +153,7 @@ class ComposeForm extends ImmutablePureComponent {
     if (onPickEmoji) {
       onPickEmoji(selectionStart, data);
     }
-  }
+  };
 
   //  Handles the secondary submit button.
   handleSecondarySubmit = () => {
@@ -157,16 +161,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)) {
@@ -176,21 +180,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) {
@@ -199,7 +203,7 @@ class ComposeForm extends ImmutablePureComponent {
         this.composeForm.scrollIntoView();
       }
     }
-  }
+  };
 
   componentDidMount () {
     this._updateFocusAndSelection({ });
@@ -216,7 +220,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,
@@ -270,7 +274,7 @@ class ComposeForm extends ImmutablePureComponent {
         }
       }
     }
-  }
+  };
 
 
   render () {
@@ -325,7 +329,9 @@ class ComposeForm extends ImmutablePureComponent {
             searchTokens={[':']}
             id='glitch.composer.spoiler.input'
             className='spoiler-input__input'
+            lang={this.props.lang}
             autoFocus={false}
+            spellCheck
           />
         </div>
 
@@ -343,6 +349,7 @@ class ComposeForm extends ImmutablePureComponent {
           onSuggestionSelected={this.onSuggestionSelected}
           onPaste={onPaste}
           autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
+          lang={this.props.lang}
         >
           <EmojiPickerDropdown onPickEmoji={handleEmojiPick} />
           <TextareaIcons advancedOptions={advancedOptions} />
@@ -381,3 +388,5 @@ class ComposeForm extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(ComposeForm);
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.jsx
index d98b311d9..fe4ab36f5 100644
--- a/app/javascript/flavours/glitch/features/compose/components/dropdown.js
+++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.jsx
@@ -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.jsx
index c4895dfd0..1ccccad31 100644
--- a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
+++ b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.jsx
@@ -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;
@@ -169,7 +169,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
         onClick={this.handleClick}
         onKeyDown={this.handleKeyDown}
         role='option'
-        tabIndex='0'
+        tabIndex={0}
         key={name}
         data-index={i}
         ref={active ? this.setFocusRef : null}
@@ -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.jsx
index 38c735551..1b8991f00 100644
--- a/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js
+++ b/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.jsx
@@ -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;
@@ -145,8 +145,7 @@ class ModifierPicker extends React.PureComponent {
 
 }
 
-@injectIntl
-class EmojiPickerMenu extends React.PureComponent {
+class EmojiPickerMenuImpl extends React.PureComponent {
 
   static propTypes = {
     custom_emojis: ImmutablePropTypes.list,
@@ -175,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);
@@ -199,7 +198,7 @@ class EmojiPickerMenu extends React.PureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   getI18n = () => {
     const { intl } = this.props;
@@ -220,7 +219,7 @@ class EmojiPickerMenu extends React.PureComponent {
         custom: intl.formatMessage(messages.custom),
       },
     };
-  }
+  };
 
   handleClick = (emoji, event) => {
     if (!emoji.native) {
@@ -230,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;
@@ -307,7 +306,8 @@ class EmojiPickerMenu extends React.PureComponent {
 
 }
 
-export default @injectIntl
+const EmojiPickerMenu = injectIntl(EmojiPickerMenuImpl);
+
 class EmojiPickerDropdown extends React.PureComponent {
 
   static propTypes = {
@@ -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;
@@ -411,3 +411,5 @@ class EmojiPickerDropdown extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(EmojiPickerDropdown);
diff --git a/app/javascript/flavours/glitch/features/compose/components/header.js b/app/javascript/flavours/glitch/features/compose/components/header.jsx
index 7ecb573ab..764fcec5e 100644
--- a/app/javascript/flavours/glitch/features/compose/components/header.js
+++ b/app/javascript/flavours/glitch/features/compose/components/header.jsx
@@ -45,8 +45,8 @@ const messages = defineMessages({
   },
 });
 
-export default @injectIntl
 class Header extends ImmutablePureComponent {
+
   static propTypes = {
     columns: ImmutablePropTypes.list,
     unreadNotifications: PropTypes.number,
@@ -63,7 +63,7 @@ class Header extends ImmutablePureComponent {
     this.props.onLogout();
 
     return false;
-  }
+  };
 
   render () {
     const { intl, columns, unreadNotifications, showNotificationsBadge, onSettingsClick } = this.props;
@@ -71,8 +71,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 +125,13 @@ 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>
     );
-  };
+  }
+
 }
+
+export default injectIntl(Header);
diff --git a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/language_dropdown.jsx
index 3a1fa0226..05614de01 100644
--- a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js
+++ b/app/javascript/flavours/glitch/features/compose/components/language_dropdown.jsx
@@ -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,21 +199,21 @@ class LanguageDropdownMenu extends React.PureComponent {
 
       break;
     }
-  }
+  };
 
   handleClear = () => {
     this.setState({ searchValue: '' });
-  }
+  };
 
   renderItem = lang => {
     const { value } = this.props;
 
     return (
-      <div key={lang[0]} role='option' tabIndex='0' data-index={lang[0]} className={classNames('language-dropdown__dropdown__results__item', { active: lang[0] === value })} aria-selected={lang[0] === value} onClick={this.handleClick} onKeyDown={this.handleKeyDown}>
-        <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 key={lang[0]} role='option' tabIndex={0} data-index={lang[0]} className={classNames('language-dropdown__dropdown__results__item', { active: lang[0] === value })} aria-selected={lang[0] === value} onClick={this.handleClick} onKeyDown={this.handleKeyDown}>
+        <span className='language-dropdown__dropdown__results__item__native-name' lang={lang[0]}>{lang[2]}</span> <span className='language-dropdown__dropdown__results__item__common-name'>({lang[1]})</span>
       </div>
     );
-  }
+  };
 
   render () {
     const { intl } = this.props;
@@ -237,7 +237,6 @@ class LanguageDropdownMenu extends React.PureComponent {
 
 }
 
-export default @injectIntl
 class LanguageDropdown extends React.PureComponent {
 
   static propTypes = {
@@ -259,7 +258,7 @@ class LanguageDropdown extends React.PureComponent {
     }
 
     this.setState({ open: !this.state.open });
-  }
+  };
 
   handleClose = () => {
     const { value, onClose } = this.props;
@@ -270,24 +269,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;
@@ -325,3 +324,5 @@ class LanguageDropdown extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(LanguageDropdown);
diff --git a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.jsx
index 1a68f1e12..1a68f1e12 100644
--- a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js
+++ b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.jsx
diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.jsx
index b5276c371..19ead2f21 100644
--- a/app/javascript/flavours/glitch/features/compose/components/options.js
+++ b/app/javascript/flavours/glitch/features/compose/components/options.jsx
@@ -83,8 +83,11 @@ const messages = defineMessages({
   },
 });
 
-@connect((state, { name }) => ({ checked: state.getIn(['compose', 'advanced_options', name]) }))
-class ToggleOption extends ImmutablePureComponent {
+const mapStateToProps = (state, { name }) => ({
+  checked: state.getIn(['compose', 'advanced_options', name]),
+});
+
+class ToggleOptionImpl extends ImmutablePureComponent {
 
   static propTypes = {
     name: PropTypes.string.isRequired,
@@ -113,7 +116,8 @@ class ToggleOption extends ImmutablePureComponent {
 
 }
 
-export default @injectIntl
+const ToggleOption = connect(mapStateToProps)(ToggleOptionImpl);
+
 class ComposerOptions extends ImmutablePureComponent {
 
   static propTypes = {
@@ -144,7 +148,7 @@ class ComposerOptions extends ImmutablePureComponent {
     if (files.length && onUpload) {
       onUpload(files);
     }
-  }
+  };
 
   //  Handles attachment clicks.
   handleClickAttach = (name) => {
@@ -164,12 +168,12 @@ class ComposerOptions extends ImmutablePureComponent {
       }
       return;
     }
-  }
+  };
 
   //  Handles a ref to the file input.
   handleRefFileElement = (fileElement) => {
     this.fileElement = fileElement;
-  }
+  };
 
   renderToggleItemContents = (item) => {
     const { onChangeAdvancedOption } = this.props;
@@ -315,3 +319,5 @@ class ComposerOptions extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(ComposerOptions);
diff --git a/app/javascript/flavours/glitch/features/compose/components/poll_form.js b/app/javascript/flavours/glitch/features/compose/components/poll_form.jsx
index afb5da097..cbd53c4d5 100644
--- a/app/javascript/flavours/glitch/features/compose/components/poll_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/poll_form.jsx
@@ -21,11 +21,11 @@ const messages = defineMessages({
   days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
 });
 
-@injectIntl
-class Option extends React.PureComponent {
+class OptionIntl extends React.PureComponent {
 
   static propTypes = {
     title: PropTypes.string.isRequired,
+    lang: PropTypes.string,
     index: PropTypes.number.isRequired,
     isPollMultiple: PropTypes.bool,
     autoFocus: PropTypes.bool,
@@ -48,18 +48,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 +70,8 @@ class Option extends React.PureComponent {
             placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
             maxLength={pollLimits.max_option_chars}
             value={title}
+            lang={lang}
+            spellCheck
             onChange={this.handleOptionTitleChange}
             suggestions={this.props.suggestions}
             onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
@@ -89,12 +91,13 @@ class Option extends React.PureComponent {
 
 }
 
-export default
-@injectIntl
+const Option = injectIntl(OptionIntl);
+
 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 }} />
@@ -164,3 +167,5 @@ class PollForm extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(PollForm);
diff --git a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.jsx
index 02cf72289..4bfbb5b8c 100644
--- a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js
+++ b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.jsx
@@ -16,7 +16,6 @@ const messages = defineMessages({
   change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
 });
 
-export default @injectIntl
 class PrivacyDropdown extends React.PureComponent {
 
   static propTypes = {
@@ -86,3 +85,5 @@ class PrivacyDropdown extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(PrivacyDropdown);
diff --git a/app/javascript/flavours/glitch/features/compose/components/publisher.js b/app/javascript/flavours/glitch/features/compose/components/publisher.jsx
index 9d53b7ee3..3128303c6 100644
--- a/app/javascript/flavours/glitch/features/compose/components/publisher.js
+++ b/app/javascript/flavours/glitch/features/compose/components/publisher.jsx
@@ -26,7 +26,6 @@ const messages = defineMessages({
   saveChanges: { id: 'compose_form.save_changes', defaultMessage: 'Save changes' },
 });
 
-export default @injectIntl
 class Publisher extends ImmutablePureComponent {
 
   static propTypes = {
@@ -94,5 +93,8 @@ class Publisher extends ImmutablePureComponent {
         </div>
       </div>
     );
-  };
+  }
+
 }
+
+export default injectIntl(Publisher);
diff --git a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx
index 7ad9e2b64..179d85ac3 100644
--- a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js
+++ b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx
@@ -19,7 +19,6 @@ const messages = defineMessages({
 });
 
 
-export default @injectIntl
 class ReplyIndicator extends ImmutablePureComponent {
 
   static propTypes = {
@@ -33,7 +32,7 @@ class ReplyIndicator extends ImmutablePureComponent {
     if (onCancel) {
       onCancel();
     }
-  }
+  };
 
   //  Rendering.
   render () {
@@ -80,3 +79,5 @@ class ReplyIndicator extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(ReplyIndicator);
diff --git a/app/javascript/flavours/glitch/features/compose/components/search.js b/app/javascript/flavours/glitch/features/compose/components/search.jsx
index e5874de75..cb6afca8d 100644
--- a/app/javascript/flavours/glitch/features/compose/components/search.js
+++ b/app/javascript/flavours/glitch/features/compose/components/search.jsx
@@ -45,7 +45,6 @@ class SearchPopout extends React.PureComponent {
 }
 
 //  The component.
-export default @injectIntl
 class Search extends React.PureComponent {
 
   static contextTypes = {
@@ -71,14 +70,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 +89,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 +105,7 @@ class Search extends React.PureComponent {
         this.searchForm.scrollIntoView();
       }
     }
-  }
+  };
 
   handleKeyUp = (e) => {
     const { onSubmit } = this.props;
@@ -121,11 +120,11 @@ class Search extends React.PureComponent {
     case 'Escape':
       focusRoot();
     }
-  }
+  };
 
   findTarget = () => {
     return this.searchForm;
-  }
+  };
 
   render () {
     const { intl, value, submitted } = this.props;
@@ -148,7 +147,7 @@ class Search extends React.PureComponent {
           onBlur={this.handleBlur}
         />
 
-        <div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
+        <div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
           <Icon id='search' className={hasValue ? '' : 'active'} />
           <Icon id='times-circle' className={hasValue ? 'active' : ''} />
         </div>
@@ -166,3 +165,5 @@ class Search extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(Search);
diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.js b/app/javascript/flavours/glitch/features/compose/components/search_results.jsx
index c2178702c..bf009d13a 100644
--- a/app/javascript/flavours/glitch/features/compose/components/search_results.js
+++ b/app/javascript/flavours/glitch/features/compose/components/search_results.jsx
@@ -14,7 +14,6 @@ const messages = defineMessages({
   dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
 });
 
-export default @injectIntl
 class SearchResults extends ImmutablePureComponent {
 
   static propTypes = {
@@ -103,7 +102,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 +135,8 @@ class SearchResults extends ImmutablePureComponent {
         {hashtags}
       </div>
     );
-  };
+  }
+
 }
+
+export default injectIntl(SearchResults);
diff --git a/app/javascript/flavours/glitch/features/compose/components/text_icon_button.js b/app/javascript/flavours/glitch/features/compose/components/text_icon_button.jsx
index a35bd4ff5..a35bd4ff5 100644
--- a/app/javascript/flavours/glitch/features/compose/components/text_icon_button.js
+++ b/app/javascript/flavours/glitch/features/compose/components/text_icon_button.jsx
diff --git a/app/javascript/flavours/glitch/features/compose/components/textarea_icons.js b/app/javascript/flavours/glitch/features/compose/components/textarea_icons.jsx
index 25c2443b1..73281fc74 100644
--- a/app/javascript/flavours/glitch/features/compose/components/textarea_icons.js
+++ b/app/javascript/flavours/glitch/features/compose/components/textarea_icons.jsx
@@ -27,7 +27,6 @@ const iconMap = [
   ['threaded_mode', 'comments', messages.threadedMode],
 ];
 
-export default @injectIntl
 class TextareaIcons extends ImmutablePureComponent {
 
   static propTypes = {
@@ -51,9 +50,12 @@ class TextareaIcons extends ImmutablePureComponent {
                 id={icon}
               />
             </span>
-          ) : null
+          ) : null,
         ) : null}
       </div>
     );
   }
+
 }
+
+export default injectIntl(TextareaIcons);
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload.js b/app/javascript/flavours/glitch/features/compose/components/upload.jsx
index 6528bbc84..7d5784561 100644
--- a/app/javascript/flavours/glitch/features/compose/components/upload.js
+++ b/app/javascript/flavours/glitch/features/compose/components/upload.jsx
@@ -23,33 +23,38 @@ 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;
+
+    if (!media) {
+      return null;
+    }
+
     const focusX = media.getIn(['meta', 'focus', 'x']);
     const focusY = media.getIn(['meta', 'focus', 'y']);
     const x = ((focusX /  2) + .5) * 100;
     const y = ((focusY / -2) + .5) * 100;
 
     return (
-      <div className='compose-form__upload' tabIndex='0' role='button'>
-        <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12, }) }}>
+      <div className='compose-form__upload' tabIndex={0} role='button'>
+        <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'>
-                <button className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
-                {!!media.get('unattached') && (<button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>)}
+                <button type='button' className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
+                <button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>
               </div>
 
-              {(media.get('description') || '').length === 0 && !!media.get('unattached') && (
+              {(media.get('description') || '').length === 0 && (
                 <div className='compose-form__upload__warning'>
-                  <button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button>
+                  <button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button>
                 </div>
               )}
             </div>
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_form.js b/app/javascript/flavours/glitch/features/compose/components/upload_form.jsx
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.jsx
@@ -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/components/upload_progress.js b/app/javascript/flavours/glitch/features/compose/components/upload_progress.jsx
index 39ac31053..39ac31053 100644
--- a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js
+++ b/app/javascript/flavours/glitch/features/compose/components/upload_progress.jsx
diff --git a/app/javascript/flavours/glitch/features/compose/components/warning.js b/app/javascript/flavours/glitch/features/compose/components/warning.jsx
index 803b7f86a..803b7f86a 100644
--- a/app/javascript/flavours/glitch/features/compose/components/warning.js
+++ b/app/javascript/flavours/glitch/features/compose/components/warning.jsx
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 d12c98c01..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 {
@@ -70,8 +76,9 @@ function mapStateToProps (state) {
     mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']),
     preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']),
     isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
+    lang: state.getIn(['compose', 'language']),
   };
-};
+}
 
 //  Dispatch mapping.
 const mapDispatchToProps = (dispatch, { intl }) => ({
@@ -123,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..14038b3e8 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
@@ -1,7 +1,10 @@
 import { connect } from 'react-redux';
 import PollForm from '../components/poll_form';
-import { addPollOption, removePollOption, changePollOption, changePollSettings } from 'flavours/glitch/actions/compose';
 import {
+  addPollOption,
+  removePollOption,
+  changePollOption,
+  changePollSettings,
   clearComposeSuggestions,
   fetchComposeSuggestions,
   selectComposeSuggestion,
@@ -10,6 +13,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/sensitive_button_container.js b/app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.jsx
index 9c23d3f47..9c23d3f47 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.jsx
diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_container.js
index f3ca4ce7b..2189c870b 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/upload_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/upload_container.js
@@ -1,7 +1,6 @@
 import { connect } from 'react-redux';
 import Upload from '../components/upload';
-import { undoUploadCompose, initMediaEditModal } from 'flavours/glitch/actions/compose';
-import { submitCompose } from 'flavours/glitch/actions/compose';
+import { undoUploadCompose, initMediaEditModal, submitCompose } from 'flavours/glitch/actions/compose';
 
 const mapStateToProps = (state, { id }) => ({
   media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
diff --git a/app/javascript/flavours/glitch/features/compose/containers/warning_container.js b/app/javascript/flavours/glitch/features/compose/containers/warning_container.jsx
index b2ed40b82..5b48c45e4 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/warning_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/warning_container.jsx
@@ -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,10 +22,10 @@ const buildHashtagRE = () => {
       '[' + WORD + '_]*' +
       '[' + ALPHA + ']' +
       '[' + WORD + '_]*' +
-      '))', 'iu'
+      '))', 'iu',
     );
   } catch {
-    return /(?:^|[^\/\)\w])#(\w*[a-zA-Z·]\w*)/i;
+    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.jsx
index 8ca378672..5547a1210 100644
--- a/app/javascript/flavours/glitch/features/compose/index.js
+++ b/app/javascript/flavours/glitch/features/compose/index.jsx
@@ -4,7 +4,7 @@ import NavigationContainer from './containers/navigation_container';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { connect } from 'react-redux';
-import { mountCompose, unmountCompose } from 'flavours/glitch/actions/compose';
+import { mountCompose, unmountCompose, cycleElefriendCompose } from 'flavours/glitch/actions/compose';
 import { injectIntl, defineMessages } from 'react-intl';
 import classNames from 'classnames';
 import SearchContainer from './containers/search_container';
@@ -12,7 +12,6 @@ import Motion from '../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import SearchResultsContainer from './containers/search_results_container';
 import { me, mascot } from 'flavours/glitch/initial_state';
-import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
 import HeaderContainer from './containers/header_container';
 import Column from 'flavours/glitch/components/column';
 import { Helmet } from 'react-helmet';
@@ -40,9 +39,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
   },
 });
 
-export default @connect(mapStateToProps, mapDispatchToProps)
-@injectIntl
 class Compose extends React.PureComponent {
+
   static propTypes = {
     multiColumn: PropTypes.bool,
     showSearch: PropTypes.bool,
@@ -114,3 +112,5 @@ class Compose extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Compose));
diff --git a/app/javascript/flavours/glitch/features/compose/util/counter.js b/app/javascript/flavours/glitch/features/compose/util/counter.js
index 7aa9e87b1..ec2431096 100644
--- a/app/javascript/flavours/glitch/features/compose/util/counter.js
+++ b/app/javascript/flavours/glitch/features/compose/util/counter.js
@@ -5,5 +5,5 @@ const urlPlaceholder = '$2xxxxxxxxxxxxxxxxxxxxxxx';
 export function countableText(inputText) {
   return inputText
     .replace(urlRegex, urlPlaceholder)
-    .replace(/(^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+[a-z0-9]+)/ig, '$1@$3');
-};
+    .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/column_settings.js b/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.jsx
index 18c3c7e21..79e98ec6f 100644
--- a/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.jsx
@@ -10,7 +10,6 @@ const messages = defineMessages({
   settings: { id: 'home.settings', defaultMessage: 'Column settings' },
 });
 
-export default @injectIntl
 class ColumnSettings extends React.PureComponent {
 
   static propTypes = {
@@ -40,3 +39,5 @@ class ColumnSettings extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ColumnSettings);
diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx
index 00d9fdcd0..63a331086 100644
--- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js
+++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx
@@ -24,7 +24,6 @@ const messages = defineMessages({
   unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
 });
 
-export default @injectIntl
 class Conversation extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -60,12 +59,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 +77,7 @@ class Conversation extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -91,7 +90,7 @@ class Conversation extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   handleClick = () => {
     if (!this.context.router) {
@@ -105,31 +104,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 +140,7 @@ class Conversation extends ImmutablePureComponent {
 
   setExpansion = value => {
     this.setState({ isExpanded: value });
-  }
+  };
 
   render () {
     const { accounts, lastStatus, unread, scrollKey, intl } = this.props;
@@ -183,7 +182,7 @@ class Conversation extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={handlers}>
-        <div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex='0'>
+        <div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex={0}>
           <div className='conversation__avatar' onClick={this.handleClick} role='presentation'>
             <AvatarComposite accounts={accounts} size={48} />
           </div>
@@ -230,3 +229,5 @@ class Conversation extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(Conversation);
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.jsx
index c2aff1b57..2bfe6fbf1 100644
--- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.js
+++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversations_list.jsx
@@ -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,13 +52,13 @@ 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;
+    const { conversations, isLoading, onLoadMore, ...other } = this.props;
 
     return (
-      <ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
+      <ScrollableList {...other} isLoading={isLoading} showLoading={isLoading && conversations.isEmpty()} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
         {conversations.map(item => (
           <ConversationContainer
             key={item.get('id')}
diff --git a/app/javascript/flavours/glitch/features/direct_timeline/index.js b/app/javascript/flavours/glitch/features/direct_timeline/index.jsx
index d55c63c2b..2fe3f2568 100644
--- a/app/javascript/flavours/glitch/features/direct_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/direct_timeline/index.jsx
@@ -22,8 +22,6 @@ const mapStateToProps = state => ({
   conversationsMode: state.getIn(['settings', 'direct', 'conversations']),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class DirectTimeline extends React.PureComponent {
 
   static propTypes = {
@@ -43,16 +41,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 +87,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;
@@ -110,8 +108,10 @@ class DirectTimeline extends React.PureComponent {
           trackScroll={!pinned}
           scrollKey={`direct_timeline-${columnId}`}
           timelineId='direct'
+          bindToDocument={!multiColumn}
           onLoadMore={this.handleLoadMore}
           prepend={<div className='follow_requests-unlocked_explanation'><span><FormattedMessage id='compose_form.encryption_warning' defaultMessage='Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.' /> <a href='/terms' target='_blank'><FormattedMessage id='compose_form.direct_message_warning_learn_more' defaultMessage='Learn more' /></a></span></div>}
+          alwaysPrepend
           emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
         />
       );
@@ -121,8 +121,10 @@ class DirectTimeline extends React.PureComponent {
           trackScroll={!pinned}
           scrollKey={`direct_timeline-${columnId}`}
           timelineId='direct'
+          bindToDocument={!multiColumn}
           onLoadMore={this.handleLoadMoreTimeline}
           prepend={<div className='follow_requests-unlocked_explanation'><span><FormattedMessage id='compose_form.encryption_warning' defaultMessage='Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.' /> <a href='/terms' target='_blank'><FormattedMessage id='compose_form.direct_message_warning_learn_more' defaultMessage='Learn more' /></a></span></div>}
+          alwaysPrepend
           emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
         />
       );
@@ -154,3 +156,5 @@ class DirectTimeline extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(DirectTimeline));
diff --git a/app/javascript/flavours/glitch/features/directory/components/account_card.js b/app/javascript/flavours/glitch/features/directory/components/account_card.jsx
index ccc3dd3d2..663710b06 100644
--- a/app/javascript/flavours/glitch/features/directory/components/account_card.js
+++ b/app/javascript/flavours/glitch/features/directory/components/account_card.jsx
@@ -93,9 +93,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 
 });
 
-export default
-@injectIntl
-@connect(makeMapStateToProps, mapDispatchToProps)
 class AccountCard extends ImmutablePureComponent {
 
   static propTypes = {
@@ -118,7 +115,7 @@ class AccountCard extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -131,7 +128,7 @@ class AccountCard extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   handleFollow = () => {
     this.props.onFollow(this.props.account);
@@ -143,11 +140,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 +152,7 @@ class AccountCard extends ImmutablePureComponent {
 
     e.preventDefault();
     e.stopPropagation();
-  }
+  };
 
   render() {
     const { account, intl } = this.props;
@@ -246,3 +243,5 @@ class AccountCard extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(AccountCard));
diff --git a/app/javascript/flavours/glitch/features/directory/index.js b/app/javascript/flavours/glitch/features/directory/index.jsx
index 94bcd578c..4278a4e71 100644
--- a/app/javascript/flavours/glitch/features/directory/index.js
+++ b/app/javascript/flavours/glitch/features/directory/index.jsx
@@ -29,8 +29,6 @@ const mapStateToProps = state => ({
   domain: state.getIn(['meta', 'domain']),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class Directory extends React.PureComponent {
 
   static contextTypes = {
@@ -64,7 +62,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 +72,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 +95,7 @@ class Directory extends React.PureComponent {
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   handleChangeOrder = e => {
     const { dispatch, columnId } = this.props;
@@ -107,7 +105,7 @@ class Directory extends React.PureComponent {
     } else {
       this.setState({ order: e.target.value });
     }
-  }
+  };
 
   handleChangeLocal = e => {
     const { dispatch, columnId } = this.props;
@@ -117,12 +115,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;
@@ -176,3 +174,5 @@ class Directory extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(Directory));
diff --git a/app/javascript/flavours/glitch/features/domain_blocks/index.js b/app/javascript/flavours/glitch/features/domain_blocks/index.jsx
index cb0b55c63..1ab7c3663 100644
--- a/app/javascript/flavours/glitch/features/domain_blocks/index.js
+++ b/app/javascript/flavours/glitch/features/domain_blocks/index.jsx
@@ -23,8 +23,6 @@ const mapStateToProps = state => ({
   hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class Blocks extends ImmutablePureComponent {
 
   static propTypes = {
@@ -81,3 +79,5 @@ class Blocks extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(Blocks));
diff --git a/app/javascript/flavours/glitch/features/emoji/emoji.js b/app/javascript/flavours/glitch/features/emoji/emoji.js
index 4f33200b6..24c5814c4 100644
--- a/app/javascript/flavours/glitch/features/emoji/emoji.js
+++ b/app/javascript/flavours/glitch/features/emoji/emoji.js
@@ -50,7 +50,7 @@ const emojifyTextNode = (node, customEmojis) => {
         if (shortname in customEmojis) {
           const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
           replacement = document.createElement('img');
-          replacement.setAttribute('draggable', false);
+          replacement.setAttribute('draggable', 'false');
           replacement.setAttribute('class', 'emojione custom-emoji');
           replacement.setAttribute('alt', shortname);
           replacement.setAttribute('title', shortname);
@@ -65,7 +65,7 @@ const emojifyTextNode = (node, customEmojis) => {
       const { filename, shortCode } = unicodeMapping[match];
       const title = shortCode ? `:${shortCode}:` : '';
       replacement = document.createElement('img');
-      replacement.setAttribute('draggable', false);
+      replacement.setAttribute('draggable', 'false');
       replacement.setAttribute('class', 'emojione');
       replacement.setAttribute('alt', match);
       replacement.setAttribute('title', title);
diff --git a/app/javascript/flavours/glitch/features/emoji/emoji_utils.js b/app/javascript/flavours/glitch/features/emoji/emoji_utils.js
index dbf725c1f..be793526d 100644
--- a/app/javascript/flavours/glitch/features/emoji/emoji_utils.js
+++ b/app/javascript/flavours/glitch/features/emoji/emoji_utils.js
@@ -73,7 +73,7 @@ const stringFromCodePoint = _String.fromCodePoint || function () {
 
 const _JSON = JSON;
 
-const COLONS_REGEX = /^(?:\:([^\:]+)\:)(?:\:skin-tone-(\d)\:)?$/;
+const COLONS_REGEX = /^(?::([^:]+):)(?::skin-tone-(\d):)?$/;
 const SKINS = [
   '1F3FA', '1F3FB', '1F3FC',
   '1F3FD', '1F3FE', '1F3FF',
@@ -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/components/story.js b/app/javascript/flavours/glitch/features/explore/components/story.jsx
index 8270d3ccb..8270d3ccb 100644
--- a/app/javascript/flavours/glitch/features/explore/components/story.js
+++ b/app/javascript/flavours/glitch/features/explore/components/story.jsx
diff --git a/app/javascript/flavours/glitch/features/explore/index.js b/app/javascript/flavours/glitch/features/explore/index.jsx
index da0dc7f7c..3587de1db 100644
--- a/app/javascript/flavours/glitch/features/explore/index.js
+++ b/app/javascript/flavours/glitch/features/explore/index.jsx
@@ -24,8 +24,6 @@ const mapStateToProps = state => ({
   isSearching: state.getIn(['search', 'submitted']) || !showTrends,
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class Explore extends React.PureComponent {
 
   static contextTypes = {
@@ -41,11 +39,11 @@ class Explore extends React.PureComponent {
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   render() {
     const { intl, multiColumn, isSearching } = this.props;
@@ -90,7 +88,9 @@ class Explore extends React.PureComponent {
                 <Route path='/explore/tags' component={Tags} />
                 <Route path='/explore/links' component={Links} />
                 <Route path='/explore/suggestions' component={Suggestions} />
-                <Route exact path={['/explore', '/explore/posts', '/search']} component={Statuses} componentParams={{ multiColumn }} />
+                <Route exact path={['/explore', '/explore/posts', '/search']}>
+                  <Statuses multiColumn={multiColumn} />
+                </Route>
               </Switch>
 
               <Helmet>
@@ -105,3 +105,5 @@ class Explore extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(Explore));
diff --git a/app/javascript/flavours/glitch/features/explore/links.js b/app/javascript/flavours/glitch/features/explore/links.jsx
index 092f86b29..425934c4a 100644
--- a/app/javascript/flavours/glitch/features/explore/links.js
+++ b/app/javascript/flavours/glitch/features/explore/links.jsx
@@ -13,7 +13,6 @@ const mapStateToProps = state => ({
   isLoading: state.getIn(['trends', 'links', 'isLoading']),
 });
 
-export default @connect(mapStateToProps)
 class Links extends React.PureComponent {
 
   static propTypes = {
@@ -68,3 +67,5 @@ class Links extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(Links);
diff --git a/app/javascript/flavours/glitch/features/explore/results.js b/app/javascript/flavours/glitch/features/explore/results.jsx
index 892980d95..0d6c0e8f1 100644
--- a/app/javascript/flavours/glitch/features/explore/results.js
+++ b/app/javascript/flavours/glitch/features/explore/results.jsx
@@ -42,8 +42,6 @@ const renderStatuses = (results, onLoadMore) => appendLoadMore('statuses', resul
   <Status key={`status-${item}`} id={item} />
 )), onLoadMore);
 
-export default @connect(mapStateToProps)
-@injectIntl
 class Results extends React.PureComponent {
 
   static propTypes = {
@@ -124,3 +122,5 @@ class Results extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(Results));
diff --git a/app/javascript/flavours/glitch/features/explore/statuses.js b/app/javascript/flavours/glitch/features/explore/statuses.jsx
index 0a5c9de23..381c50c5d 100644
--- a/app/javascript/flavours/glitch/features/explore/statuses.js
+++ b/app/javascript/flavours/glitch/features/explore/statuses.jsx
@@ -14,7 +14,6 @@ const mapStateToProps = state => ({
   hasMore: !!state.getIn(['status_lists', 'trending', 'next']),
 });
 
-export default @connect(mapStateToProps)
 class Statuses extends React.PureComponent {
 
   static propTypes = {
@@ -33,7 +32,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;
@@ -62,3 +61,5 @@ class Statuses extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(Statuses);
diff --git a/app/javascript/flavours/glitch/features/explore/suggestions.js b/app/javascript/flavours/glitch/features/explore/suggestions.jsx
index 52e5ce62b..e1b84098a 100644
--- a/app/javascript/flavours/glitch/features/explore/suggestions.js
+++ b/app/javascript/flavours/glitch/features/explore/suggestions.jsx
@@ -12,7 +12,6 @@ const mapStateToProps = state => ({
   isLoading: state.getIn(['suggestions', 'isLoading']),
 });
 
-export default @connect(mapStateToProps)
 class Suggestions extends React.PureComponent {
 
   static propTypes = {
@@ -29,7 +28,7 @@ class Suggestions extends React.PureComponent {
   handleDismiss = (accountId) => {
     const { dispatch } = this.props;
     dispatch(dismissSuggestion(accountId));
-  }
+  };
 
   render () {
     const { isLoading, suggestions } = this.props;
@@ -54,3 +53,5 @@ class Suggestions extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(Suggestions);
diff --git a/app/javascript/flavours/glitch/features/explore/tags.js b/app/javascript/flavours/glitch/features/explore/tags.jsx
index 938036b64..e0fdd1d91 100644
--- a/app/javascript/flavours/glitch/features/explore/tags.js
+++ b/app/javascript/flavours/glitch/features/explore/tags.jsx
@@ -13,7 +13,6 @@ const mapStateToProps = state => ({
   isLoadingHashtags: state.getIn(['trends', 'tags', 'isLoading']),
 });
 
-export default @connect(mapStateToProps)
 class Tags extends React.PureComponent {
 
   static propTypes = {
@@ -60,3 +59,5 @@ class Tags extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(Tags);
diff --git a/app/javascript/flavours/glitch/features/favourited_statuses/index.js b/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx
index a03e1a4eb..60d281f97 100644
--- a/app/javascript/flavours/glitch/features/favourited_statuses/index.js
+++ b/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx
@@ -22,8 +22,6 @@ const mapStateToProps = state => ({
   hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class Favourites extends ImmutablePureComponent {
 
   static propTypes = {
@@ -48,24 +46,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;
@@ -106,3 +104,5 @@ class Favourites extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(Favourites));
diff --git a/app/javascript/flavours/glitch/features/favourites/index.js b/app/javascript/flavours/glitch/features/favourites/index.jsx
index 47c3279c4..21ce7fcc7 100644
--- a/app/javascript/flavours/glitch/features/favourites/index.js
+++ b/app/javascript/flavours/glitch/features/favourites/index.jsx
@@ -22,8 +22,6 @@ const mapStateToProps = (state, props) => ({
   accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class Favourites extends ImmutablePureComponent {
 
   static propTypes = {
@@ -48,15 +46,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;
@@ -101,3 +99,5 @@ class Favourites extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(Favourites));
diff --git a/app/javascript/flavours/glitch/features/filters/added_to_filter.js b/app/javascript/flavours/glitch/features/filters/added_to_filter.jsx
index becb170cd..2f3f98c81 100644
--- a/app/javascript/flavours/glitch/features/filters/added_to_filter.js
+++ b/app/javascript/flavours/glitch/features/filters/added_to_filter.jsx
@@ -10,7 +10,6 @@ const mapStateToProps = (state, { filterId }) => ({
   filter: state.getIn(['filters', filterId]),
 });
 
-export default @connect(mapStateToProps)
 class AddedToFilter extends React.PureComponent {
 
   static propTypes = {
@@ -100,3 +99,5 @@ class AddedToFilter extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(AddedToFilter);
diff --git a/app/javascript/flavours/glitch/features/filters/select_filter.js b/app/javascript/flavours/glitch/features/filters/select_filter.jsx
index 5391766c9..a33892f83 100644
--- a/app/javascript/flavours/glitch/features/filters/select_filter.js
+++ b/app/javascript/flavours/glitch/features/filters/select_filter.jsx
@@ -22,8 +22,6 @@ const mapStateToProps = (state, { contextType }) => ({
   ]),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class SelectFilter extends React.PureComponent {
 
   static propTypes = {
@@ -67,15 +65,15 @@ class SelectFilter extends React.PureComponent {
     }
 
     return (
-      <div key={filter[0]} role='button' tabIndex='0' data-index={filter[0]} className='language-dropdown__dropdown__results__item' onClick={this.handleItemClick} onKeyDown={this.handleKeyDown}>
+      <div key={filter[0]} role='button' tabIndex={0} data-index={filter[0]} className='language-dropdown__dropdown__results__item' onClick={this.handleItemClick} onKeyDown={this.handleKeyDown}>
         <span className='language-dropdown__dropdown__results__item__native-name'>{filter[1]}</span> {warning}
       </div>
     );
-  }
+  };
 
   renderCreateNew (name) {
     return (
-      <div key='add-new-filter' role='button' tabIndex='0' className='language-dropdown__dropdown__results__item' onClick={this.handleNewFilterClick} onKeyDown={this.handleKeyDown}>
+      <div key='add-new-filter' role='button' tabIndex={0} className='language-dropdown__dropdown__results__item' onClick={this.handleNewFilterClick} onKeyDown={this.handleKeyDown}>
         <Icon id='plus' fixedWidth /> <FormattedMessage id='filter_modal.select_filter.prompt_new' defaultMessage='New category: {name}' values={{ name }} />
       </div>
     );
@@ -83,11 +81,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 +123,7 @@ class SelectFilter extends React.PureComponent {
       e.preventDefault();
       e.stopPropagation();
     }
-  }
+  };
 
   handleSearchKeyDown = e => {
     let element = null;
@@ -143,11 +141,11 @@ class SelectFilter extends React.PureComponent {
 
       break;
     }
-  }
+  };
 
   handleClear = () => {
     this.setState({ searchValue: '' });
-  }
+  };
 
   handleItemClick = e => {
     const value = e.currentTarget.getAttribute('data-index');
@@ -155,7 +153,7 @@ class SelectFilter extends React.PureComponent {
     e.preventDefault();
 
     this.props.onSelectFilter(value);
-  }
+  };
 
   handleNewFilterClick = e => {
     e.preventDefault();
@@ -190,3 +188,5 @@ class SelectFilter extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(SelectFilter));
diff --git a/app/javascript/flavours/glitch/features/follow_recommendations/components/account.js b/app/javascript/flavours/glitch/features/follow_recommendations/components/account.jsx
index 2c668da3e..e56af7364 100644
--- a/app/javascript/flavours/glitch/features/follow_recommendations/components/account.js
+++ b/app/javascript/flavours/glitch/features/follow_recommendations/components/account.jsx
@@ -27,13 +27,11 @@ const makeMapStateToProps = () => {
 };
 
 const getFirstSentence = str => {
-  const arr = str.split(/(([\.\?!]+\s)|[.。?!\n•])/);
+  const arr = str.split(/(([.?!]+\s)|[.。?!\n•])/);
 
   return arr[0];
 };
 
-export default @connect(makeMapStateToProps)
-@injectIntl
 class Account extends ImmutablePureComponent {
 
   static propTypes = {
@@ -50,7 +48,7 @@ class Account extends ImmutablePureComponent {
     } else {
       dispatch(followAccount(account.get('id')));
     }
-  }
+  };
 
   render () {
     const { account, intl } = this.props;
@@ -83,3 +81,5 @@ class Account extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(makeMapStateToProps)(injectIntl(Account));
diff --git a/app/javascript/flavours/glitch/features/follow_recommendations/index.js b/app/javascript/flavours/glitch/features/follow_recommendations/index.jsx
index d9d962b7c..70f2191f1 100644
--- a/app/javascript/flavours/glitch/features/follow_recommendations/index.js
+++ b/app/javascript/flavours/glitch/features/follow_recommendations/index.jsx
@@ -19,7 +19,6 @@ const mapStateToProps = state => ({
   isLoading: state.getIn(['suggestions', 'isLoading']),
 });
 
-export default @connect(mapStateToProps)
 class FollowRecommendations extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -69,7 +68,7 @@ class FollowRecommendations extends ImmutablePureComponent {
     }));
 
     router.history.push('/home');
-  }
+  };
 
   render () {
     const { suggestions, isLoading } = this.props;
@@ -114,3 +113,5 @@ class FollowRecommendations extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(FollowRecommendations);
diff --git a/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.js b/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx
index cbe7a1032..af8a534fa 100644
--- a/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.js
+++ b/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx
@@ -13,7 +13,6 @@ const messages = defineMessages({
   reject: { id: 'follow_request.reject', defaultMessage: 'Reject' },
 });
 
-export default @injectIntl
 class AccountAuthorize extends ImmutablePureComponent {
 
   static propTypes = {
@@ -47,3 +46,5 @@ class AccountAuthorize extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(AccountAuthorize);
diff --git a/app/javascript/flavours/glitch/features/follow_requests/index.js b/app/javascript/flavours/glitch/features/follow_requests/index.jsx
index 7b35e3ec9..a9a35f54b 100644
--- a/app/javascript/flavours/glitch/features/follow_requests/index.js
+++ b/app/javascript/flavours/glitch/features/follow_requests/index.jsx
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { debounce } from 'lodash';
-import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
 import Column from 'flavours/glitch/features/ui/components/column';
 import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim';
 import AccountAuthorizeContainer from './containers/account_authorize_container';
@@ -26,8 +25,6 @@ const mapStateToProps = state => ({
   domain: state.getIn(['meta', 'domain']),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class FollowRequests extends ImmutablePureComponent {
 
   static propTypes = {
@@ -53,16 +50,8 @@ class FollowRequests extends ImmutablePureComponent {
   render () {
     const { intl, accountIds, hasMore, multiColumn, locked, domain, isLoading } = this.props;
 
-    if (!accountIds) {
-      return (
-        <Column name='follow-requests'>
-          <LoadingIndicator />
-        </Column>
-      );
-    }
-
     const emptyMessage = <FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />;
-    const unlockedPrependMessage = locked ? null : (
+    const unlockedPrependMessage = !locked && accountIds.size > 0 && (
       <div className='follow_requests-unlocked_explanation'>
         <FormattedMessage
           id='follow_requests.unlocked_explanation'
@@ -81,6 +70,7 @@ class FollowRequests extends ImmutablePureComponent {
           onLoadMore={this.handleLoadMore}
           hasMore={hasMore}
           isLoading={isLoading}
+          showLoading={isLoading && accountIds.size === 0}
           emptyMessage={emptyMessage}
           bindToDocument={!multiColumn}
           prepend={unlockedPrependMessage}
@@ -98,3 +88,5 @@ class FollowRequests extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(FollowRequests));
diff --git a/app/javascript/flavours/glitch/features/followed_tags/index.jsx b/app/javascript/flavours/glitch/features/followed_tags/index.jsx
new file mode 100644
index 000000000..a5abb151f
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/followed_tags/index.jsx
@@ -0,0 +1,89 @@
+import { debounce } from 'lodash';
+import PropTypes from 'prop-types';
+import React from 'react';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { connect } from 'react-redux';
+import ColumnHeader from 'flavours/glitch/components/column_header';
+import ScrollableList from 'flavours/glitch/components/scrollable_list';
+import Column from 'flavours/glitch/features/ui/components/column';
+import { Helmet } from 'react-helmet';
+import Hashtag from 'flavours/glitch/components/hashtag';
+import { expandFollowedHashtags, fetchFollowedHashtags } from 'flavours/glitch/actions/tags';
+
+const messages = defineMessages({
+  heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' },
+});
+
+const mapStateToProps = state => ({
+  hashtags: state.getIn(['followed_tags', 'items']),
+  isLoading: state.getIn(['followed_tags', 'isLoading'], true),
+  hasMore: !!state.getIn(['followed_tags', 'next']),
+});
+
+class FollowedTags extends ImmutablePureComponent {
+
+  static propTypes = {
+    params: PropTypes.object.isRequired,
+    dispatch: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+    hashtags: ImmutablePropTypes.list,
+    isLoading: PropTypes.bool,
+    hasMore: PropTypes.bool,
+    multiColumn: PropTypes.bool,
+  };
+
+  componentDidMount() {
+    this.props.dispatch(fetchFollowedHashtags());
+  }
+
+  handleLoadMore = debounce(() => {
+    this.props.dispatch(expandFollowedHashtags());
+  }, 300, { leading: true });
+
+  render () {
+    const { intl, hashtags, isLoading, hasMore, multiColumn } = this.props;
+
+    const emptyMessage = <FormattedMessage id='empty_column.followed_tags' defaultMessage='You have not followed any hashtags yet. When you do, they will show up here.' />;
+
+    return (
+      <Column bindToDocument={!multiColumn}>
+        <ColumnHeader
+          icon='hashtag'
+          title={intl.formatMessage(messages.heading)}
+          showBackButton
+          multiColumn={multiColumn}
+        />
+
+        <ScrollableList
+          scrollKey='followed_tags'
+          emptyMessage={emptyMessage}
+          hasMore={hasMore}
+          isLoading={isLoading}
+          onLoadMore={this.handleLoadMore}
+          bindToDocument={!multiColumn}
+        >
+          {hashtags.map((hashtag) => (
+            <Hashtag
+              key={hashtag.get('name')}
+              name={hashtag.get('name')}
+              to={`/tags/${hashtag.get('name')}`}
+              withGraph={false}
+              // Taken from ImmutableHashtag. Should maybe refactor ImmutableHashtag to accept more options?
+              people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
+              history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
+            />
+          ))}
+        </ScrollableList>
+
+        <Helmet>
+          <meta name='robots' content='noindex' />
+        </Helmet>
+      </Column>
+    );
+  }
+
+}
+
+export default connect(mapStateToProps)(injectIntl(FollowedTags));
diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.jsx
index 7122c1905..2565772d1 100644
--- a/app/javascript/flavours/glitch/features/followers/index.js
+++ b/app/javascript/flavours/glitch/features/followers/index.jsx
@@ -53,7 +53,6 @@ RemoteHint.propTypes = {
   url: PropTypes.string.isRequired,
 };
 
-export default @connect(mapStateToProps)
 class Followers extends ImmutablePureComponent {
 
   static propTypes = {
@@ -107,11 +106,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;
@@ -172,3 +171,5 @@ class Followers extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(Followers);
diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.jsx
index 4ad670105..2c05e3310 100644
--- a/app/javascript/flavours/glitch/features/following/index.js
+++ b/app/javascript/flavours/glitch/features/following/index.jsx
@@ -53,7 +53,6 @@ RemoteHint.propTypes = {
   url: PropTypes.string.isRequired,
 };
 
-export default @connect(mapStateToProps)
 class Following extends ImmutablePureComponent {
 
   static propTypes = {
@@ -107,11 +106,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;
@@ -172,3 +171,5 @@ class Following extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(Following);
diff --git a/app/javascript/flavours/glitch/features/generic_not_found/index.js b/app/javascript/flavours/glitch/features/generic_not_found/index.jsx
index 4412adaed..4412adaed 100644
--- a/app/javascript/flavours/glitch/features/generic_not_found/index.js
+++ b/app/javascript/flavours/glitch/features/generic_not_found/index.jsx
diff --git a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js b/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx
index 93f3c9428..29288076b 100644
--- a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js
+++ b/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx
@@ -6,9 +6,8 @@ import PropTypes from 'prop-types';
 import IconButton from 'flavours/glitch/components/icon_button';
 import Icon from 'flavours/glitch/components/icon';
 import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
-import { autoPlayGif, reduceMotion, disableSwiping } from 'flavours/glitch/initial_state';
+import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'flavours/glitch/initial_state';
 import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg';
-import { mascot } from 'flavours/glitch/initial_state';
 import unicodeMapping from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light';
 import classNames from 'classnames';
 import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container';
@@ -35,7 +34,7 @@ class Content extends ImmutablePureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   componentDidMount () {
     this._updateLinks();
@@ -89,7 +88,7 @@ class Content extends ImmutablePureComponent {
       e.preventDefault();
       this.context.router.history.push(`/@${mention.get('acct')}`);
     }
-  }
+  };
 
   onHashtagClick = (hashtag, e) => {
     hashtag = hashtag.replace(/^#/, '');
@@ -98,14 +97,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 +117,7 @@ class Content extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -131,7 +130,7 @@ class Content extends ImmutablePureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   render () {
     const { announcement } = this.props;
@@ -216,11 +215,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 +253,7 @@ class ReactionsBar extends ImmutablePureComponent {
   handleEmojiPick = data => {
     const { addReaction, announcementId } = this.props;
     addReaction(announcementId, data.native.replace(/:/g, ''));
-  }
+  };
 
   willEnter () {
     return { scale: reduceMotion ? 1 : 0 };
@@ -356,7 +355,6 @@ class Announcement extends ImmutablePureComponent {
 
 }
 
-export default @injectIntl
 class Announcements extends ImmutablePureComponent {
 
   static propTypes = {
@@ -397,15 +395,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;
@@ -420,7 +418,7 @@ class Announcements extends ImmutablePureComponent {
         <img className='announcements__mastodon' alt='' draggable='false' src={mascot || elephantUIPlane} />
 
         <div className='announcements__container'>
-          <ReactSwipeableViews animateHeight={!reduceMotion} adjustHeight={reduceMotion} index={index} onChangeIndex={this.handleChangeIndex}>
+          <ReactSwipeableViews animateHeight animateTransitions={!reduceMotion} index={index} onChangeIndex={this.handleChangeIndex}>
             {announcements.map((announcement, idx) => (
               <Announcement
                 key={announcement.get('id')}
@@ -448,3 +446,5 @@ class Announcements extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(Announcements);
diff --git a/app/javascript/flavours/glitch/features/getting_started/components/trends.js b/app/javascript/flavours/glitch/features/getting_started/components/trends.jsx
index d45934d6e..d45934d6e 100644
--- a/app/javascript/flavours/glitch/features/getting_started/components/trends.js
+++ b/app/javascript/flavours/glitch/features/getting_started/components/trends.jsx
diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.jsx
index f9d79013b..4064a5451 100644
--- a/app/javascript/flavours/glitch/features/getting_started/index.js
+++ b/app/javascript/flavours/glitch/features/getting_started/index.jsx
@@ -79,9 +79,7 @@ const badgeDisplay = (number, limit) => {
 
 const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
 
- export default @connect(makeMapStateToProps, mapDispatchToProps)
- @injectIntl
- class GettingStarted extends ImmutablePureComponent {
+class GettingStarted extends ImmutablePureComponent {
 
   static contextTypes = {
     router: PropTypes.object.isRequired,
@@ -164,7 +162,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>,
       ]);
@@ -202,3 +200,5 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
   }
 
 }
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted));
diff --git a/app/javascript/flavours/glitch/features/getting_started_misc/index.js b/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx
index de354d6b1..fb4ec2fce 100644
--- a/app/javascript/flavours/glitch/features/getting_started_misc/index.js
+++ b/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx
@@ -16,17 +16,18 @@ const messages = defineMessages({
   blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
   domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
   mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
-  info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' },
   show_me_around: { id: 'getting_started.onboarding', defaultMessage: 'Show me around' },
   pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
-  info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' },
   keyboard_shortcuts: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Keyboard shortcuts' },
   featured_users: { id: 'navigation_bar.featured_users', defaultMessage: 'Featured users' },
 });
 
-export default @connect()
-@injectIntl
-class gettingStartedMisc extends ImmutablePureComponent {
+class GettingStartedMisc extends ImmutablePureComponent {
+
+  static contextTypes = {
+    router: PropTypes.object.isRequired,
+    identity: PropTypes.object,
+  };
 
   static propTypes = {
     intl: PropTypes.object.isRequired,
@@ -35,16 +36,15 @@ 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;
-
-    let i = 1;
+    const { signedIn } = this.context.identity;
 
     return (
       <Column icon='ellipsis-h' heading={intl.formatMessage(messages.heading)}>
@@ -52,18 +52,19 @@ class gettingStartedMisc extends ImmutablePureComponent {
 
         <div className='scrollable'>
           <ColumnSubheading text={intl.formatMessage(messages.subheading)} />
-          <ColumnLink key='{i++}' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />
-          <ColumnLink key='{i++}' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />
-          <ColumnLink key='{i++}' icon='users' text={intl.formatMessage(messages.featured_users)} onClick={this.openFeaturedAccountsModal} />
-          <ColumnLink key='{i++}' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />
-          <ColumnLink key='{i++}' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />
-          <ColumnLink key='{i++}' icon='minus-circle' text={intl.formatMessage(messages.domain_blocks)} to='/domain_blocks' />
-          <ColumnLink key='{i++}' icon='question' text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' />
-          <ColumnLink key='{i++}' icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />
-          <ColumnLink key='{i++}' icon='hand-o-right' text={intl.formatMessage(messages.show_me_around)} onClick={this.openOnboardingModal} />
+          {signedIn && (<ColumnLink key='favourites' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />)}
+          {signedIn && (<ColumnLink key='pinned' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />)}
+          {signedIn && (<ColumnLink key='featured_users' icon='users' text={intl.formatMessage(messages.featured_users)} onClick={this.openFeaturedAccountsModal} />)}
+          {signedIn && (<ColumnLink key='mutes' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />)}
+          {signedIn && (<ColumnLink key='blocks' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />)}
+          {signedIn && (<ColumnLink key='domain_blocks' icon='minus-circle' text={intl.formatMessage(messages.domain_blocks)} to='/domain_blocks' />)}
+          <ColumnLink key='shortcuts' icon='question' text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' />
+          {signedIn && (<ColumnLink key='onboarding' icon='hand-o-right' text={intl.formatMessage(messages.show_me_around)} onClick={this.openOnboardingModal} />)}
         </div>
       </Column>
     );
   }
 
 }
+
+export default connect()(injectIntl(GettingStartedMisc));
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.jsx
index ede8907e5..f140f2d01 100644
--- a/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.jsx
@@ -12,7 +12,6 @@ const messages = defineMessages({
   noOptions: { id: 'hashtag.column_settings.select.no_options_message', defaultMessage: 'No suggestions found' },
 });
 
-export default @injectIntl
 class ColumnSettings extends React.PureComponent {
 
   static propTypes = {
@@ -38,7 +37,7 @@ class ColumnSettings extends React.PureComponent {
     } else {
       return tags;
     }
-  };
+  }
 
   onSelect = mode => value => {
     const oldValue = this.tags(mode);
@@ -98,7 +97,7 @@ class ColumnSettings extends React.PureComponent {
     default:
       return '';
     }
-  };
+  }
 
   render () {
     const { settings, onChange } = this.props;
@@ -131,3 +130,5 @@ class ColumnSettings extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ColumnSettings);
diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js b/app/javascript/flavours/glitch/features/hashtag_timeline/index.jsx
index 219dc0ec6..fe5afa240 100644
--- a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/hashtag_timeline/index.jsx
@@ -26,8 +26,6 @@ const mapStateToProps = (state, props) => ({
   tag: state.getIn(['tags', props.params.id]),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class HashtagTimeline extends React.PureComponent {
 
   disconnects = [];
@@ -54,7 +52,7 @@ class HashtagTimeline extends React.PureComponent {
     } else {
       dispatch(addColumn('HASHTAG', { id: this.props.params.id }));
     }
-  }
+  };
 
   title = () => {
     const { id } = this.props.params;
@@ -73,7 +71,7 @@ class HashtagTimeline extends React.PureComponent {
     }
 
     return title;
-  }
+  };
 
   additionalFor = (mode) => {
     const { tags } = this.props.params;
@@ -83,16 +81,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 +155,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 +178,7 @@ class HashtagTimeline extends React.PureComponent {
     } else {
       dispatch(followHashtag(id));
     }
-  }
+  };
 
   render () {
     const { hasUnread, columnId, multiColumn, tag, intl } = this.props;
@@ -193,8 +191,12 @@ class HashtagTimeline extends React.PureComponent {
     if (tag) {
       const following = tag.get('following');
 
+      const classes = classNames('column-header__button', {
+        active: following,
+      });
+
       followButton = (
-        <button className={classNames('column-header__button')} onClick={this.handleFollow} disabled={!signedIn} active={following} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)}>
+        <button className={classes} onClick={this.handleFollow} disabled={!signedIn} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)}>
           <Icon id={following ? 'user-times' : 'user-plus'} fixedWidth className='column-header__icon' />
         </button>
       );
@@ -235,3 +237,5 @@ class HashtagTimeline extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(HashtagTimeline));
diff --git a/app/javascript/flavours/glitch/features/home_timeline/components/column_settings.js b/app/javascript/flavours/glitch/features/home_timeline/components/column_settings.jsx
index df615db65..1eeeaa378 100644
--- a/app/javascript/flavours/glitch/features/home_timeline/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/home_timeline/components/column_settings.jsx
@@ -10,7 +10,6 @@ const messages = defineMessages({
   settings: { id: 'home.settings', defaultMessage: 'Column settings' },
 });
 
-export default @injectIntl
 class ColumnSettings extends React.PureComponent {
 
   static propTypes = {
@@ -48,3 +47,5 @@ class ColumnSettings extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ColumnSettings);
diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.js b/app/javascript/flavours/glitch/features/home_timeline/index.jsx
index 5ed108ad2..71619394b 100644
--- a/app/javascript/flavours/glitch/features/home_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/home_timeline/index.jsx
@@ -31,8 +31,6 @@ const mapStateToProps = state => ({
   regex: state.getIn(['settings', 'home', 'regex', 'body']),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class HomeTimeline extends React.PureComponent {
 
   static contextTypes = {
@@ -60,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);
@@ -116,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;
@@ -176,3 +174,5 @@ class HomeTimeline extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(HomeTimeline));
diff --git a/app/javascript/flavours/glitch/features/interaction_modal/index.js b/app/javascript/flavours/glitch/features/interaction_modal/index.jsx
index b71c041c9..20e4959e6 100644
--- a/app/javascript/flavours/glitch/features/interaction_modal/index.js
+++ b/app/javascript/flavours/glitch/features/interaction_modal/index.jsx
@@ -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);
@@ -74,7 +74,6 @@ class Copypaste extends React.PureComponent {
 
 }
 
-export default @connect(mapStateToProps, mapDispatchToProps)
 class InteractionModal extends React.PureComponent {
 
   static propTypes = {
@@ -86,7 +85,7 @@ class InteractionModal extends React.PureComponent {
 
   handleSignupClick = () => {
     this.props.onSignupClick();
-  }
+  };
 
   render () {
     const { url, type, displayNameHtml } = this.props;
@@ -159,3 +158,5 @@ class InteractionModal extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(InteractionModal);
diff --git a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.jsx
index 2bc0116d4..7160e7efb 100644
--- a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js
+++ b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.jsx
@@ -15,8 +15,6 @@ const mapStateToProps = state => ({
   collapseEnabled: state.getIn(['local_settings', 'collapsed', 'enabled']),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class KeyboardShortcuts extends ImmutablePureComponent {
 
   static propTypes = {
@@ -147,3 +145,5 @@ class KeyboardShortcuts extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(KeyboardShortcuts));
diff --git a/app/javascript/flavours/glitch/features/list_adder/components/account.js b/app/javascript/flavours/glitch/features/list_adder/components/account.jsx
index 1369aac07..034ed0edc 100644
--- a/app/javascript/flavours/glitch/features/list_adder/components/account.js
+++ b/app/javascript/flavours/glitch/features/list_adder/components/account.jsx
@@ -18,8 +18,6 @@ const makeMapStateToProps = () => {
 };
 
 
-export default @connect(makeMapStateToProps)
-@injectIntl
 class Account extends ImmutablePureComponent {
 
   static propTypes = {
@@ -41,3 +39,5 @@ class Account extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(makeMapStateToProps)(injectIntl(Account));
diff --git a/app/javascript/flavours/glitch/features/list_adder/components/list.js b/app/javascript/flavours/glitch/features/list_adder/components/list.jsx
index 4666ca47b..1957bbe42 100644
--- a/app/javascript/flavours/glitch/features/list_adder/components/list.js
+++ b/app/javascript/flavours/glitch/features/list_adder/components/list.jsx
@@ -13,7 +13,7 @@ const messages = defineMessages({
   add: { id: 'lists.account.add', defaultMessage: 'Add to list' },
 });
 
-const MapStateToProps = (state, { listId, added }) => ({
+const mapStateToProps = (state, { listId, added }) => ({
   list: state.get('lists').get(listId),
   added: typeof added === 'undefined' ? state.getIn(['listAdder', 'lists', 'items']).includes(listId) : added,
 });
@@ -23,8 +23,6 @@ const mapDispatchToProps = (dispatch, { listId }) => ({
   onAdd: () => dispatch(addToListAdder(listId)),
 });
 
-export default @connect(MapStateToProps, mapDispatchToProps)
-@injectIntl
 class List extends ImmutablePureComponent {
 
   static propTypes = {
@@ -67,3 +65,5 @@ class List extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(List));
diff --git a/app/javascript/flavours/glitch/features/list_adder/index.js b/app/javascript/flavours/glitch/features/list_adder/index.jsx
index cb8a15e8c..45d5589f9 100644
--- a/app/javascript/flavours/glitch/features/list_adder/index.js
+++ b/app/javascript/flavours/glitch/features/list_adder/index.jsx
@@ -28,8 +28,6 @@ const mapDispatchToProps = dispatch => ({
   onReset: () => dispatch(resetListAdder()),
 });
 
-export default @connect(mapStateToProps, mapDispatchToProps)
-@injectIntl
 class ListAdder extends ImmutablePureComponent {
 
   static propTypes = {
@@ -71,3 +69,5 @@ class ListAdder extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ListAdder));
diff --git a/app/javascript/flavours/glitch/features/list_editor/components/account.js b/app/javascript/flavours/glitch/features/list_editor/components/account.jsx
index 71a8b7673..71a8b7673 100644
--- a/app/javascript/flavours/glitch/features/list_editor/components/account.js
+++ b/app/javascript/flavours/glitch/features/list_editor/components/account.jsx
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.jsx
index a8cab2762..b4886ef0e 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.jsx
@@ -19,8 +19,6 @@ const mapDispatchToProps = dispatch => ({
   onSubmit: () => dispatch(submitListEditor(false)),
 });
 
-export default @connect(mapStateToProps, mapDispatchToProps)
-@injectIntl
 class ListForm extends React.PureComponent {
 
   static propTypes = {
@@ -33,16 +31,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;
@@ -68,3 +66,5 @@ class ListForm extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ListForm));
diff --git a/app/javascript/flavours/glitch/features/list_editor/components/search.js b/app/javascript/flavours/glitch/features/list_editor/components/search.jsx
index 192643f77..3b66bc325 100644
--- a/app/javascript/flavours/glitch/features/list_editor/components/search.js
+++ b/app/javascript/flavours/glitch/features/list_editor/components/search.jsx
@@ -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;
@@ -51,7 +51,7 @@ export default class Search extends React.PureComponent {
           />
         </label>
 
-        <div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
+        <div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
           <Icon id='search' className={classNames({ active: !hasValue })} />
           <Icon id='times-circle' aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} />
         </div>
diff --git a/app/javascript/flavours/glitch/features/list_editor/index.js b/app/javascript/flavours/glitch/features/list_editor/index.jsx
index c2ca07053..44951d1c6 100644
--- a/app/javascript/flavours/glitch/features/list_editor/index.js
+++ b/app/javascript/flavours/glitch/features/list_editor/index.jsx
@@ -22,8 +22,6 @@ const mapDispatchToProps = dispatch => ({
   onReset: () => dispatch(resetListEditor()),
 });
 
-export default @connect(mapStateToProps, mapDispatchToProps)
-@injectIntl
 class ListEditor extends ImmutablePureComponent {
 
   static propTypes = {
@@ -62,7 +60,7 @@ class ListEditor extends ImmutablePureComponent {
             {accountIds.map(accountId => <AccountContainer key={accountId} accountId={accountId} added />)}
           </div>
 
-          {showSearch && <div role='button' tabIndex='-1' className='drawer__backdrop' onClick={onClear} />}
+          {showSearch && <div role='button' tabIndex={-1} className='drawer__backdrop' onClick={onClear} />}
 
           <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
             {({ x }) =>
@@ -77,3 +75,5 @@ class ListEditor extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ListEditor));
diff --git a/app/javascript/flavours/glitch/features/list_timeline/index.js b/app/javascript/flavours/glitch/features/list_timeline/index.jsx
index a94c05c56..a32383b13 100644
--- a/app/javascript/flavours/glitch/features/list_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/list_timeline/index.jsx
@@ -31,8 +31,6 @@ const mapStateToProps = (state, props) => ({
   hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class ListTimeline extends React.PureComponent {
 
   static contextTypes = {
@@ -58,16 +56,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 +103,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 +124,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;
@@ -179,11 +177,11 @@ class ListTimeline extends React.PureComponent {
           multiColumn={multiColumn}
         >
           <div className='column-settings__row column-header__links'>
-            <button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}>
+            <button className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditClick}>
               <Icon id='pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
             </button>
 
-            <button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleDeleteClick}>
+            <button className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}>
               <Icon id='trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
             </button>
           </div>
@@ -222,3 +220,5 @@ class ListTimeline extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(ListTimeline));
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.jsx
index cc78d30b7..be94ff559 100644
--- a/app/javascript/flavours/glitch/features/lists/components/new_list_form.js
+++ b/app/javascript/flavours/glitch/features/lists/components/new_list_form.jsx
@@ -20,8 +20,6 @@ const mapDispatchToProps = dispatch => ({
   onSubmit: () => dispatch(submitListEditor(true)),
 });
 
-export default @connect(mapStateToProps, mapDispatchToProps)
-@injectIntl
 class NewListForm extends React.PureComponent {
 
   static propTypes = {
@@ -34,16 +32,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;
@@ -76,3 +74,5 @@ class NewListForm extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(NewListForm));
diff --git a/app/javascript/flavours/glitch/features/lists/index.js b/app/javascript/flavours/glitch/features/lists/index.jsx
index 8773be5e6..dce0dcd8f 100644
--- a/app/javascript/flavours/glitch/features/lists/index.js
+++ b/app/javascript/flavours/glitch/features/lists/index.jsx
@@ -32,8 +32,6 @@ const mapStateToProps = state => ({
   lists: getOrderedLists(state),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class Lists extends ImmutablePureComponent {
 
   static propTypes = {
@@ -87,3 +85,5 @@ class Lists extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(Lists));
diff --git a/app/javascript/flavours/glitch/features/local_settings/index.js b/app/javascript/flavours/glitch/features/local_settings/index.jsx
index 4e4605ea9..4e4605ea9 100644
--- a/app/javascript/flavours/glitch/features/local_settings/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/index.jsx
diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js b/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx
index 98dda182f..fe08e5d7b 100644
--- a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx
@@ -19,7 +19,6 @@ const messages = defineMessages({
   close: { id: 'settings.close', defaultMessage: 'Close' },
 });
 
-export default @injectIntl
 class LocalSettingsNavigation extends React.PureComponent {
 
   static propTypes = {
@@ -72,7 +71,7 @@ class LocalSettingsNavigation extends React.PureComponent {
         />
         <LocalSettingsNavigationItem
           active={index === 5}
-          href={ preferencesLink }
+          href={preferencesLink}
           index={5}
           icon='cog'
           title={intl.formatMessage(messages.preferences)}
@@ -90,3 +89,5 @@ class LocalSettingsNavigation extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(LocalSettingsNavigation);
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.jsx
index 739c5ebae..9ac6d9b73 100644
--- a/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.jsx
@@ -26,7 +26,7 @@ export default class LocalSettingsPage extends React.PureComponent {
       onNavigate(index);
       e.preventDefault();
     }
-  }
+  };
 
   render () {
     const { handleClick } = this;
@@ -60,7 +60,7 @@ export default class LocalSettingsPage extends React.PureComponent {
       <a
         onClick={handleClick}
         role='button'
-        tabIndex='0'
+        tabIndex={0}
         className={finalClassName}
         title={title}
         aria-label={title}
diff --git a/app/javascript/flavours/glitch/features/local_settings/page/deprecated_item/index.js b/app/javascript/flavours/glitch/features/local_settings/page/deprecated_item/index.jsx
index 362bd97c0..362bd97c0 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/deprecated_item/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/deprecated_item/index.jsx
diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.js b/app/javascript/flavours/glitch/features/local_settings/page/index.jsx
index d01eec811..83b0c7960 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/index.jsx
@@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
 
 //  Our imports
-import { expandSpoilers, disableSwiping } from 'flavours/glitch/initial_state';
+import { expandSpoilers } from 'flavours/glitch/initial_state';
 import { preferenceLink } from 'flavours/glitch/utils/backend_links';
 import LocalSettingsPageItem from './item';
 import DeprecatedLocalSettingsPageItem from './deprecated_item';
@@ -31,7 +31,6 @@ const messages = defineMessages({
   pop_in_right: { id: 'settings.pop_in_right', defaultMessage:  'Right' },
 });
 
-export default @injectIntl
 class LocalSettingsPage extends React.PureComponent {
 
   static propTypes = {
@@ -60,7 +59,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 +76,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 +99,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 +109,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 +305,7 @@ class LocalSettingsPage extends React.PureComponent {
                         defaultMessage='user preferences'
                       />
                     </a>
-                  )
+                  ),
                 }}
               />
             </span>
@@ -406,6 +405,18 @@ class LocalSettingsPage extends React.PureComponent {
           >
             <FormattedMessage id='settings.auto_collapse_media' defaultMessage='Toots with media' />
           </LocalSettingsPageItem>
+          <LocalSettingsPageItem
+            settings={settings}
+            item={['collapsed', 'auto', 'height']}
+            id='mastodon-settings--collapsed-auto-height'
+            placeholder='400'
+            onChange={onChange}
+            dependsOn={[['collapsed', 'enabled']]}
+            dependsOnNot={[['collapsed', 'auto', 'all']]}
+            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>
         </section>
         <section>
           <h2><FormattedMessage id='settings.image_backgrounds' defaultMessage='Image backgrounds' /></h2>
@@ -501,3 +512,5 @@ class LocalSettingsPage extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(LocalSettingsPage);
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.jsx
index 6b24e4143..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.jsx
@@ -14,6 +14,7 @@ export default class LocalSettingsPageItem extends React.PureComponent {
     id: PropTypes.string.isRequired,
     item: PropTypes.array.isRequired,
     onChange: PropTypes.func.isRequired,
+    inputProps: PropTypes.object,
     options: PropTypes.arrayOf(PropTypes.shape({
       value: PropTypes.string.isRequired,
       message: PropTypes.string.isRequired,
@@ -30,11 +31,11 @@ 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;
-    const { settings, item, id, options, children, dependsOn, dependsOnNot, placeholder, disabled } = this.props;
+    const { settings, item, id, inputProps, options, children, dependsOn, dependsOnNot, placeholder, disabled } = this.props;
     let enabled = !disabled;
 
     if (dependsOn) {
@@ -54,14 +55,17 @@ export default class LocalSettingsPageItem extends React.PureComponent {
         let optionId = `${id}--${opt.value}`;
         return (
           <label htmlFor={optionId}>
-            <input type='radio'
+            <input
+              type='radio'
               name={id}
               id={optionId}
+              key={optionId}
               value={opt.value}
               onBlur={handleChange}
               onChange={handleChange}
-              checked={ currentValue === opt.value }
+              checked={currentValue === opt.value}
               disabled={!enabled}
+              {...inputProps}
             />
             {opt.message}
             {opt.hint && <span className='hint'>{opt.hint}</span>}
@@ -89,6 +93,7 @@ export default class LocalSettingsPageItem extends React.PureComponent {
                 placeholder={placeholder}
                 onChange={handleChange}
                 disabled={!enabled}
+	        {...inputProps}
               />
             </p>
           </label>
@@ -103,6 +108,7 @@ export default class LocalSettingsPageItem extends React.PureComponent {
             checked={settings.getIn(item)}
             onChange={handleChange}
             disabled={!enabled}
+            {...inputProps}
           />
           {children}
         </label>
diff --git a/app/javascript/flavours/glitch/features/mutes/index.js b/app/javascript/flavours/glitch/features/mutes/index.jsx
index 8da106e47..b699fdb27 100644
--- a/app/javascript/flavours/glitch/features/mutes/index.js
+++ b/app/javascript/flavours/glitch/features/mutes/index.jsx
@@ -23,8 +23,6 @@ const mapStateToProps = state => ({
   isLoading: state.getIn(['user_lists', 'mutes', 'isLoading'], true),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class Mutes extends ImmutablePureComponent {
 
   static propTypes = {
@@ -82,3 +80,5 @@ class Mutes extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(Mutes));
diff --git a/app/javascript/flavours/glitch/features/notifications/components/admin_report.js b/app/javascript/flavours/glitch/features/notifications/components/admin_report.jsx
index 4662bd953..9b55fe4e9 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/admin_report.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/admin_report.jsx
@@ -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 {
@@ -91,7 +91,7 @@ export default class AdminReport extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className={classNames('notification notification-admin-report focusable', { unread })} tabIndex='0'>
+        <div className={classNames('notification notification-admin-report focusable', { unread })} tabIndex={0}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <Icon id='flag' fixedWidth />
diff --git a/app/javascript/flavours/glitch/features/notifications/components/admin_signup.js b/app/javascript/flavours/glitch/features/notifications/components/admin_signup.jsx
index 355ebef94..d982108e9 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/admin_signup.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/admin_signup.jsx
@@ -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 {
@@ -78,7 +78,7 @@ export default class NotificationFollow extends ImmutablePureComponent {
     //  Renders.
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex='0'>
+        <div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex={0}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <Icon fixedWidth id='user-plus' />
diff --git a/app/javascript/flavours/glitch/features/notifications/components/clear_column_button.js b/app/javascript/flavours/glitch/features/notifications/components/clear_column_button.jsx
index ee77cfb8e..cd150314b 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/clear_column_button.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/clear_column_button.jsx
@@ -11,7 +11,7 @@ export default class ClearColumnButton extends React.Component {
 
   render () {
     return (
-      <button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.props.onClick}><Icon id='eraser' /> <FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' /></button>
+      <button className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.props.onClick}><Icon id='eraser' /> <FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' /></button>
     );
   }
 
diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js b/app/javascript/flavours/glitch/features/notifications/components/column_settings.jsx
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.jsx
@@ -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/filter_bar.js b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.jsx
index c1de0f90e..7f36fb813 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.jsx
@@ -12,7 +12,6 @@ const tooltips = defineMessages({
   statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' },
 });
 
-export default @injectIntl
 class FilterBar extends React.PureComponent {
 
   static propTypes = {
@@ -108,3 +107,5 @@ class FilterBar extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(FilterBar);
diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow.js b/app/javascript/flavours/glitch/features/notifications/components/follow.jsx
index b8fad19d0..e9ef70911 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/follow.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/follow.jsx
@@ -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 {
@@ -78,7 +78,7 @@ export default class NotificationFollow extends ImmutablePureComponent {
     //  Renders.
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className={classNames('notification notification-follow focusable', { unread })} tabIndex='0'>
+        <div className={classNames('notification notification-follow focusable', { unread })} tabIndex={0}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <Icon fixedWidth id='user-plus' />
diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow_request.js b/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx
index 69b92a06f..2b985bc08 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/follow_request.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx
@@ -17,7 +17,6 @@ const messages = defineMessages({
   reject: { id: 'follow_request.reject', defaultMessage: 'Reject' },
 });
 
-export default @injectIntl
 class FollowRequest extends ImmutablePureComponent {
 
   static propTypes = {
@@ -32,28 +31,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 {
@@ -96,7 +95,7 @@ class FollowRequest extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className={classNames('notification notification-follow-request focusable', { unread })} tabIndex='0'>
+        <div className={classNames('notification notification-follow-request focusable', { unread })} tabIndex={0}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <Icon id='user' fixedWidth />
@@ -130,3 +129,5 @@ class FollowRequest extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(FollowRequest);
diff --git a/app/javascript/flavours/glitch/features/notifications/components/grant_permission_button.js b/app/javascript/flavours/glitch/features/notifications/components/grant_permission_button.jsx
index 798e4c787..5b2db48fd 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/grant_permission_button.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/grant_permission_button.jsx
@@ -10,7 +10,7 @@ export default class GrantPermissionButton extends React.PureComponent {
 
   render () {
     return (
-      <button className='text-btn column-header__permission-btn' tabIndex='0' onClick={this.props.onClick}>
+      <button className='text-btn column-header__permission-btn' tabIndex={0} onClick={this.props.onClick}>
         <FormattedMessage id='notifications.grant_permission' defaultMessage='Grant permission.' />
       </button>
     );
diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.js b/app/javascript/flavours/glitch/features/notifications/components/notification.jsx
index d676a4207..d1aea1b21 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/notification.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/notification.jsx
@@ -124,6 +124,7 @@ export default class Notification extends ImmutablePureComponent {
           onMoveDown={onMoveDown}
           onMoveUp={onMoveUp}
           onMention={onMention}
+          contextType='notifications'
           getScrollPosition={getScrollPosition}
           updateScrollBottom={updateScrollBottom}
           cachedMediaWidth={this.props.cachedMediaWidth}
@@ -146,6 +147,7 @@ export default class Notification extends ImmutablePureComponent {
           onMoveDown={onMoveDown}
           onMoveUp={onMoveUp}
           onMention={onMention}
+          contextType='notifications'
           getScrollPosition={getScrollPosition}
           updateScrollBottom={updateScrollBottom}
           cachedMediaWidth={this.props.cachedMediaWidth}
@@ -168,6 +170,7 @@ export default class Notification extends ImmutablePureComponent {
           onMoveDown={onMoveDown}
           onMoveUp={onMoveUp}
           onMention={onMention}
+          contextType='notifications'
           getScrollPosition={getScrollPosition}
           updateScrollBottom={updateScrollBottom}
           cachedMediaWidth={this.props.cachedMediaWidth}
@@ -190,6 +193,7 @@ export default class Notification extends ImmutablePureComponent {
           onMoveDown={onMoveDown}
           onMoveUp={onMoveUp}
           onMention={onMention}
+          contextType='notifications'
           getScrollPosition={getScrollPosition}
           updateScrollBottom={updateScrollBottom}
           cachedMediaWidth={this.props.cachedMediaWidth}
@@ -212,6 +216,7 @@ export default class Notification extends ImmutablePureComponent {
           onMoveDown={onMoveDown}
           onMoveUp={onMoveUp}
           onMention={onMention}
+          contextType='notifications'
           getScrollPosition={getScrollPosition}
           updateScrollBottom={updateScrollBottom}
           cachedMediaWidth={this.props.cachedMediaWidth}
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.jsx
index dd163225e..5a12191a5 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.jsx
@@ -12,8 +12,6 @@ const messages = defineMessages({
   close: { id: 'lightbox.close', defaultMessage: 'Close' },
 });
 
-export default @connect()
-@injectIntl
 class NotificationsPermissionBanner extends React.PureComponent {
 
   static propTypes = {
@@ -23,11 +21,11 @@ class NotificationsPermissionBanner extends React.PureComponent {
 
   handleClick = () => {
     this.props.dispatch(requestBrowserPermission());
-  }
+  };
 
   handleClose = () => {
     this.props.dispatch(changeSetting(['notifications', 'dismissPermissionBanner'], true));
-  }
+  };
 
   render () {
     const { intl } = this.props;
@@ -46,3 +44,5 @@ class NotificationsPermissionBanner extends React.PureComponent {
   }
 
 }
+
+export default connect()(injectIntl(NotificationsPermissionBanner));
diff --git a/app/javascript/flavours/glitch/features/notifications/components/overlay.js b/app/javascript/flavours/glitch/features/notifications/components/overlay.jsx
index f3ccafc06..554a7a668 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/overlay.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/overlay.jsx
@@ -15,7 +15,6 @@ const messages = defineMessages({
   markForDeletion: { id: 'notification.markForDeletion', defaultMessage: 'Mark for deletion' },
 });
 
-export default @injectIntl
 class NotificationOverlay extends ImmutablePureComponent {
 
   static propTypes = {
@@ -29,7 +28,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;
@@ -56,3 +55,5 @@ class NotificationOverlay extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(NotificationOverlay);
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.jsx
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.jsx
@@ -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/report.js b/app/javascript/flavours/glitch/features/notifications/components/report.jsx
index 46a307250..9110735a1 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/report.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/report.jsx
@@ -13,7 +13,6 @@ const messages = defineMessages({
   violation: { id: 'report_notification.categories.violation', defaultMessage: 'Rule violation' },
 });
 
-export default @injectIntl
 class Report extends ImmutablePureComponent {
 
   static propTypes = {
@@ -60,3 +59,5 @@ class Report extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(Report);
diff --git a/app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js b/app/javascript/flavours/glitch/features/notifications/components/setting_toggle.jsx
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.jsx
@@ -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/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js
index c2564f44e..27c2f96fe 100644
--- a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js
+++ b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js
@@ -2,8 +2,7 @@ import { connect } from 'react-redux';
 import { defineMessages, injectIntl } from 'react-intl';
 import ColumnSettings from '../components/column_settings';
 import { changeSetting } from 'flavours/glitch/actions/settings';
-import { setFilter } from 'flavours/glitch/actions/notifications';
-import { clearNotifications, requestBrowserPermission } from 'flavours/glitch/actions/notifications';
+import { setFilter, clearNotifications, requestBrowserPermission } from 'flavours/glitch/actions/notifications';
 import { changeAlerts as changePushNotifications } from 'flavours/glitch/actions/push_notifications';
 import { openModal } from 'flavours/glitch/actions/modal';
 import { showAlert } from 'flavours/glitch/actions/alerts';
diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.jsx
index fc42a4de4..2de073077 100644
--- a/app/javascript/flavours/glitch/features/notifications/index.js
+++ b/app/javascript/flavours/glitch/features/notifications/index.jsx
@@ -92,8 +92,6 @@ const mapDispatchToProps = dispatch => ({
   dispatch,
 });
 
-export default @connect(mapStateToProps, mapDispatchToProps)
-@injectIntl
 class Notifications extends React.PureComponent {
 
   static contextTypes = {
@@ -158,30 +156,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 +211,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;
@@ -380,3 +378,5 @@ class Notifications extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Notifications));
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.jsx
index f05a763e0..51fe023d3 100644
--- a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
+++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx
@@ -38,8 +38,6 @@ const makeMapStateToProps = () => {
   return mapStateToProps;
 };
 
-export default @connect(makeMapStateToProps)
-@injectIntl
 class Footer extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -114,7 +112,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 +149,7 @@ class Footer extends ImmutablePureComponent {
     }
 
     router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
-  }
+  };
 
   render () {
     const { status, intl, showReplyCount, withOpenButton } = this.props;
@@ -215,3 +213,5 @@ class Footer extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(makeMapStateToProps)(injectIntl(Footer));
diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/header.js b/app/javascript/flavours/glitch/features/picture_in_picture/components/header.jsx
index 26f2da374..b9b90f1d8 100644
--- a/app/javascript/flavours/glitch/features/picture_in_picture/components/header.js
+++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/header.jsx
@@ -17,8 +17,6 @@ const mapStateToProps = (state, { accountId }) => ({
   account: state.getIn(['accounts', accountId]),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class Header extends ImmutablePureComponent {
 
   static propTypes = {
@@ -45,3 +43,5 @@ class Header extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(Header));
diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/index.js b/app/javascript/flavours/glitch/features/picture_in_picture/index.jsx
index 3e6a20faa..e6fb64ff9 100644
--- a/app/javascript/flavours/glitch/features/picture_in_picture/index.js
+++ b/app/javascript/flavours/glitch/features/picture_in_picture/index.jsx
@@ -13,7 +13,6 @@ const mapStateToProps = state => ({
   left: state.getIn(['local_settings', 'media', 'pop_in_position']) === 'left',
 });
 
-export default @connect(mapStateToProps)
 class PictureInPicture extends React.Component {
 
   static propTypes = {
@@ -35,7 +34,7 @@ class PictureInPicture extends React.Component {
   handleClose = () => {
     const { dispatch } = this.props;
     dispatch(removePictureInPicture());
-  }
+  };
 
   render () {
     const { type, src, currentTime, accountId, statusId, left } = this.props;
@@ -86,3 +85,5 @@ class PictureInPicture extends React.Component {
   }
 
 }
+
+export default connect(mapStateToProps)(PictureInPicture);
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_accounts_editor/index.js b/app/javascript/flavours/glitch/features/pinned_accounts_editor/index.jsx
index 43ae0ec2f..834de652f 100644
--- a/app/javascript/flavours/glitch/features/pinned_accounts_editor/index.js
+++ b/app/javascript/flavours/glitch/features/pinned_accounts_editor/index.jsx
@@ -21,8 +21,6 @@ const mapDispatchToProps = dispatch => ({
   onReset: () => dispatch(resetPinnedAccountsEditor()),
 });
 
-export default @connect(mapStateToProps, mapDispatchToProps)
-@injectIntl
 class PinnedAccountsEditor extends ImmutablePureComponent {
 
   static propTypes = {
@@ -61,7 +59,7 @@ class PinnedAccountsEditor extends ImmutablePureComponent {
             {accountIds.map(accountId => <AccountContainer key={accountId} accountId={accountId} added />)}
           </div>
 
-          {showSearch && <div role='button' tabIndex='-1' className='drawer__backdrop' onClick={onClear} />}
+          {showSearch && <div role='button' tabIndex={-1} className='drawer__backdrop' onClick={onClear} />}
 
           <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
             {({ x }) =>
@@ -76,3 +74,5 @@ class PinnedAccountsEditor extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(PinnedAccountsEditor));
diff --git a/app/javascript/flavours/glitch/features/pinned_statuses/index.js b/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx
index eeeab46ab..41be2f7f3 100644
--- a/app/javascript/flavours/glitch/features/pinned_statuses/index.js
+++ b/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx
@@ -19,8 +19,6 @@ const mapStateToProps = state => ({
   hasMore: !!state.getIn(['status_lists', 'pins', 'next']),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class PinnedStatuses extends ImmutablePureComponent {
 
   static propTypes = {
@@ -37,11 +35,11 @@ class PinnedStatuses extends ImmutablePureComponent {
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   setRef = c => {
     this.column = c;
-  }
+  };
 
   render () {
     const { intl, statusIds, hasMore, multiColumn } = this.props;
@@ -63,3 +61,5 @@ class PinnedStatuses extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(PinnedStatuses));
diff --git a/app/javascript/flavours/glitch/features/privacy_policy/index.js b/app/javascript/flavours/glitch/features/privacy_policy/index.jsx
index 4618d9e32..a43befa73 100644
--- a/app/javascript/flavours/glitch/features/privacy_policy/index.js
+++ b/app/javascript/flavours/glitch/features/privacy_policy/index.jsx
@@ -10,7 +10,6 @@ const messages = defineMessages({
   title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' },
 });
 
-export default @injectIntl
 class PrivacyPolicy extends React.PureComponent {
 
   static propTypes = {
@@ -59,3 +58,5 @@ class PrivacyPolicy extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(PrivacyPolicy);
diff --git a/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.js b/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.jsx
index cfe821cfc..a44d5c784 100644
--- a/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.jsx
@@ -9,7 +9,6 @@ const messages = defineMessages({
   filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' },
 });
 
-export default @injectIntl
 class ColumnSettings extends React.PureComponent {
 
   static propTypes = {
@@ -40,3 +39,5 @@ class ColumnSettings extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ColumnSettings);
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.jsx
index a61a47de1..737e5723f 100644
--- a/app/javascript/flavours/glitch/features/public_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/public_timeline/index.jsx
@@ -35,8 +35,6 @@ const mapStateToProps = (state, { columnId }) => {
   };
 };
 
-export default @connect(mapStateToProps)
-@injectIntl
 class PublicTimeline extends React.PureComponent {
 
   static defaultProps = {
@@ -68,16 +66,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 +114,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;
@@ -166,3 +164,5 @@ class PublicTimeline extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(PublicTimeline));
diff --git a/app/javascript/flavours/glitch/features/reblogs/index.js b/app/javascript/flavours/glitch/features/reblogs/index.jsx
index b097ff9d7..34fe24d3f 100644
--- a/app/javascript/flavours/glitch/features/reblogs/index.js
+++ b/app/javascript/flavours/glitch/features/reblogs/index.jsx
@@ -22,8 +22,6 @@ const mapStateToProps = (state, props) => ({
   accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class Reblogs extends ImmutablePureComponent {
 
   static propTypes = {
@@ -48,15 +46,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;
@@ -102,3 +100,5 @@ class Reblogs extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(Reblogs));
diff --git a/app/javascript/flavours/glitch/features/report/category.js b/app/javascript/flavours/glitch/features/report/category.jsx
index 55c43577b..43e311f3d 100644
--- a/app/javascript/flavours/glitch/features/report/category.js
+++ b/app/javascript/flavours/glitch/features/report/category.jsx
@@ -24,8 +24,6 @@ const mapStateToProps = state => ({
   rules: state.getIn(['server', 'server', 'rules'], ImmutableList()),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class Category extends React.PureComponent {
 
   static propTypes = {
@@ -102,3 +100,5 @@ class Category extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(Category));
diff --git a/app/javascript/flavours/glitch/features/report/comment.js b/app/javascript/flavours/glitch/features/report/comment.jsx
index ec261afcb..afcb7afa4 100644
--- a/app/javascript/flavours/glitch/features/report/comment.js
+++ b/app/javascript/flavours/glitch/features/report/comment.jsx
@@ -8,7 +8,6 @@ const messages = defineMessages({
   placeholder: { id: 'report.placeholder', defaultMessage: 'Type or paste additional comments' },
 });
 
-export default @injectIntl
 class Comment extends React.PureComponent {
 
   static propTypes = {
@@ -81,3 +80,5 @@ class Comment extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(Comment);
diff --git a/app/javascript/flavours/glitch/features/report/components/option.js b/app/javascript/flavours/glitch/features/report/components/option.jsx
index 7e94f0654..7827a6b3b 100644
--- a/app/javascript/flavours/glitch/features/report/components/option.js
+++ b/app/javascript/flavours/glitch/features/report/components/option.jsx
@@ -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;
@@ -40,7 +40,7 @@ export default class Option extends React.PureComponent {
 
         <span
           className={classNames('poll__input', { active: checked, checkbox: multiple })}
-          tabIndex='0'
+          tabIndex={0}
           role='radio'
           onKeyPress={this.handleKeyPress}
           aria-checked={checked}
diff --git a/app/javascript/flavours/glitch/features/report/components/status_check_box.js b/app/javascript/flavours/glitch/features/report/components/status_check_box.jsx
index 2231fc0ce..2231fc0ce 100644
--- a/app/javascript/flavours/glitch/features/report/components/status_check_box.js
+++ b/app/javascript/flavours/glitch/features/report/components/status_check_box.jsx
diff --git a/app/javascript/flavours/glitch/features/report/rules.js b/app/javascript/flavours/glitch/features/report/rules.jsx
index efcdf1fcf..72ba75b48 100644
--- a/app/javascript/flavours/glitch/features/report/rules.js
+++ b/app/javascript/flavours/glitch/features/report/rules.jsx
@@ -10,7 +10,6 @@ const mapStateToProps = state => ({
   rules: state.getIn(['server', 'server', 'rules']),
 });
 
-export default @connect(mapStateToProps)
 class Rules extends React.PureComponent {
 
   static propTypes = {
@@ -62,3 +61,5 @@ class Rules extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(Rules);
diff --git a/app/javascript/flavours/glitch/features/report/statuses.js b/app/javascript/flavours/glitch/features/report/statuses.jsx
index 47d5ee863..a687917ce 100644
--- a/app/javascript/flavours/glitch/features/report/statuses.js
+++ b/app/javascript/flavours/glitch/features/report/statuses.jsx
@@ -13,7 +13,6 @@ const mapStateToProps = (state, { accountId }) => ({
   isLoading: state.getIn(['timelines', `account:${accountId}:with_replies`, 'isLoading']),
 });
 
-export default @connect(mapStateToProps)
 class Statuses extends React.PureComponent {
 
   static propTypes = {
@@ -59,3 +58,5 @@ class Statuses extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(Statuses);
diff --git a/app/javascript/flavours/glitch/features/report/thanks.js b/app/javascript/flavours/glitch/features/report/thanks.jsx
index 454979f9f..30c88e2f2 100644
--- a/app/javascript/flavours/glitch/features/report/thanks.js
+++ b/app/javascript/flavours/glitch/features/report/thanks.jsx
@@ -12,7 +12,6 @@ import {
 
 const mapStateToProps = () => ({});
 
-export default @connect(mapStateToProps)
 class Thanks extends React.PureComponent {
 
   static propTypes = {
@@ -82,3 +81,5 @@ class Thanks extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(Thanks);
diff --git a/app/javascript/flavours/glitch/features/standalone/compose/index.js b/app/javascript/flavours/glitch/features/standalone/compose/index.jsx
index b33c21cb5..c53442435 100644
--- a/app/javascript/flavours/glitch/features/standalone/compose/index.js
+++ b/app/javascript/flavours/glitch/features/standalone/compose/index.jsx
@@ -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.jsx
index 73913dd49..d5ab730d6 100644
--- a/app/javascript/flavours/glitch/features/status/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx
@@ -39,7 +39,6 @@ const messages = defineMessages({
   openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
 });
 
-export default @injectIntl
 class ActionBar extends React.PureComponent {
 
   static contextTypes = {
@@ -68,75 +67,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;
@@ -228,3 +227,5 @@ class ActionBar extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ActionBar);
diff --git a/app/javascript/flavours/glitch/features/status/components/card.js b/app/javascript/flavours/glitch/features/status/components/card.jsx
index 2d2e49eb8..359dbbc20 100644
--- a/app/javascript/flavours/glitch/features/status/components/card.js
+++ b/app/javascript/flavours/glitch/features/status/components/card.jsx
@@ -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;
@@ -188,11 +188,12 @@ export default class Card extends React.PureComponent {
     const interactive = card.get('type') !== 'link';
     const className   = classnames('status-card', { horizontal, compact, interactive });
     const title       = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener noreferrer' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
+    const language    = card.get('language') || '';
     const ratio       = card.get('width') / card.get('height');
     const height      = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
 
     const description = (
-      <div className='status-card__content'>
+      <div className='status-card__content' lang={language}>
         {title}
         {!(horizontal || compact) && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>}
         <span className='status-card__host'>{provider}</span>
diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx
index 907fc3f1c..cfe6c965e 100644
--- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js
+++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx
@@ -21,7 +21,6 @@ import AnimatedNumber from 'flavours/glitch/components/animated_number';
 import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
 import EditedTimestamp from 'flavours/glitch/components/edited_timestamp';
 
-export default @injectIntl
 class DetailedStatus extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -56,28 +55,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 +91,7 @@ class DetailedStatus extends ImmutablePureComponent {
   setRef = c => {
     this.node = c;
     this._measureHeight();
-  }
+  };
 
   componentDidUpdate (prevProps, prevState) {
     this._measureHeight(prevState.height !== this.state.height);
@@ -100,7 +99,7 @@ class DetailedStatus extends ImmutablePureComponent {
 
   handleChildUpdate = () => {
     this._measureHeight();
-  }
+  };
 
   handleModalLink = e => {
     e.preventDefault();
@@ -114,12 +113,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;
@@ -169,6 +168,7 @@ class DetailedStatus extends ImmutablePureComponent {
           <Audio
             src={attachment.get('url')}
             alt={attachment.get('description')}
+            lang={status.get('language')}
             duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
             poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
             backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
@@ -191,6 +191,7 @@ class DetailedStatus extends ImmutablePureComponent {
             blurhash={attachment.get('blurhash')}
             src={attachment.get('url')}
             alt={attachment.get('description')}
+            lang={status.get('language')}
             inline
             sensitive={status.get('sensitive')}
             letterbox={settings.getIn(['media', 'letterbox'])}
@@ -209,6 +210,7 @@ class DetailedStatus extends ImmutablePureComponent {
             standalone
             sensitive={status.get('sensitive')}
             media={status.get('media_attachments')}
+            lang={status.get('language')}
             letterbox={settings.getIn(['media', 'letterbox'])}
             fullwidth={settings.getIn(['media', 'fullwidth'])}
             hidden={!expanded}
@@ -225,7 +227,7 @@ class DetailedStatus extends ImmutablePureComponent {
     }
 
     if (status.get('poll')) {
-      contentMedia.push(<PollContainer pollId={status.get('poll')} />);
+      contentMedia.push(<PollContainer pollId={status.get('poll')} lang={status.get('language')} />);
       contentMediaIcons.push('tasks');
     }
 
@@ -333,3 +335,5 @@ class DetailedStatus extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(DetailedStatus);
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.jsx
index c22e7f0bd..f01ad2dbe 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.jsx
@@ -5,7 +5,17 @@ import PropTypes from 'prop-types';
 import classNames from 'classnames';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { createSelector } from 'reselect';
-import { fetchStatus } from 'flavours/glitch/actions/statuses';
+import {
+  fetchStatus,
+  muteStatus,
+  unmuteStatus,
+  deleteStatus,
+  editStatus,
+  hideStatus,
+  revealStatus,
+  translateStatus,
+  undoStatusTranslation,
+} from 'flavours/glitch/actions/statuses';
 import MissingIndicator from 'flavours/glitch/components/missing_indicator';
 import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
 import DetailedStatus from './components/detailed_status';
@@ -27,16 +37,6 @@ import {
   directCompose,
 } from 'flavours/glitch/actions/compose';
 import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
-import {
-  muteStatus,
-  unmuteStatus,
-  deleteStatus,
-  editStatus,
-  hideStatus,
-  revealStatus,
-  translateStatus,
-  undoStatusTranslation,
-} from 'flavours/glitch/actions/statuses';
 import { initMuteModal } from 'flavours/glitch/actions/mutes';
 import { initBlockModal } from 'flavours/glitch/actions/blocks';
 import { initReport } from 'flavours/glitch/actions/reports';
@@ -171,8 +171,6 @@ const titleFromStatus = status => {
   return `${prefix}: "${truncate(text, 30)}"`;
 };
 
-export default @injectIntl
-@connect(makeMapStateToProps)
 class Status extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -263,15 +261,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 +292,7 @@ class Status extends ImmutablePureComponent {
         url: status.get('url'),
       }));
     }
-  }
+  };
 
   handlePin = (status) => {
     if (status.get('pinned')) {
@@ -302,7 +300,7 @@ class Status extends ImmutablePureComponent {
     } else {
       this.props.dispatch(pin(status));
     }
-  }
+  };
 
   handleReplyClick = (status) => {
     const { askReplyConfirmation, dispatch, intl } = this.props;
@@ -326,7 +324,7 @@ class Status extends ImmutablePureComponent {
         url: status.get('url'),
       }));
     }
-  }
+  };
 
   handleModalReblog = (status, privacy) => {
     const { dispatch } = this.props;
@@ -336,7 +334,7 @@ class Status extends ImmutablePureComponent {
     } else {
       dispatch(reblog(status, privacy));
     }
-  }
+  };
 
   handleReblogClick = (status, e) => {
     const { settings, dispatch } = this.props;
@@ -357,7 +355,7 @@ class Status extends ImmutablePureComponent {
         url: status.get('url'),
       }));
     }
-  }
+  };
 
   handleBookmarkClick = (status) => {
     if (status.get('bookmarked')) {
@@ -365,7 +363,7 @@ class Status extends ImmutablePureComponent {
     } else {
       this.props.dispatch(bookmark(status));
     }
-  }
+  };
 
   handleDeleteClick = (status, history, withRedraft = false) => {
     const { dispatch, intl } = this.props;
@@ -379,27 +377,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 +411,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 +423,7 @@ class Status extends ImmutablePureComponent {
     } else {
       this.props.dispatch(muteStatus(status.get('id')));
     }
-  }
+  };
 
   handleToggleAll = () => {
     const { status, ancestorsIds, descendantsIds, settings } = this.props;
@@ -442,7 +440,7 @@ class Status extends ImmutablePureComponent {
     }
 
     this.setState({ isExpanded: !isExpanded, threadExpanded: !isExpanded });
-  }
+  };
 
   handleTranslate = status => {
     const { dispatch } = this.props;
@@ -452,61 +450,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 +521,7 @@ class Status extends ImmutablePureComponent {
         this._selectChild(index - 1, true);
       }
     }
-  }
+  };
 
   handleMoveDown = id => {
     const { status, ancestorsIds, descendantsIds } = this.props;
@@ -540,7 +538,7 @@ class Status extends ImmutablePureComponent {
         this._selectChild(index + 1, false);
       }
     }
-  }
+  };
 
   _selectChild (index, align_top) {
     const container = this.node;
@@ -558,7 +556,7 @@ class Status extends ImmutablePureComponent {
 
   handleHeaderClick = () => {
     this.column.scrollTop();
-  }
+  };
 
   renderChildren (list) {
     return list.map(id => (
@@ -575,15 +573,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 +603,7 @@ class Status extends ImmutablePureComponent {
 
   onFullScreenChange = () => {
     this.setState({ fullscreen: isFullscreen() });
-  }
+  };
 
   render () {
     let ancestors, descendants;
@@ -674,7 +672,7 @@ class Status extends ImmutablePureComponent {
             {ancestors}
 
             <HotKeys handlers={handlers}>
-              <div className='focusable' tabIndex='0' aria-label={textForScreenReader(intl, status, false, isExpanded)}>
+              <div className='focusable' tabIndex={0} aria-label={textForScreenReader(intl, status, false, isExpanded)}>
                 <DetailedStatus
                   key={`details-${status.get('id')}`}
                   status={status}
@@ -724,3 +722,5 @@ class Status extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(connect(makeMapStateToProps)(Status));
diff --git a/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.js b/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.jsx
index fa69d82a4..85144a191 100644
--- a/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.js
+++ b/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.jsx
@@ -36,8 +36,6 @@ const mapDispatchToProps = (dispatch, { accountId }) => ({
 
 });
 
-export default @connect(mapStateToProps, mapDispatchToProps)
-@injectIntl
 class SubscribedLanguagesModal extends ImmutablePureComponent {
 
   static propTypes = {
@@ -72,7 +70,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);
@@ -123,3 +121,5 @@ class SubscribedLanguagesModal extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SubscribedLanguagesModal));
diff --git a/app/javascript/flavours/glitch/features/ui/components/actions_modal.js b/app/javascript/flavours/glitch/features/ui/components/actions_modal.jsx
index aae2e4426..9a9b1a3f1 100644
--- a/app/javascript/flavours/glitch/features/ui/components/actions_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/actions_modal.jsx
@@ -36,7 +36,7 @@ export default class ActionsModal extends ImmutablePureComponent {
     if (!contents) {
       contents = (
         <React.Fragment>
-          {icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' inverted />}
+          {icon && <IconButton title={text} icon={icon} role='presentation' tabIndex={-1} inverted />}
           <div>
             <div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div>
             <div>{meta}</div>
@@ -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/audio_modal.js b/app/javascript/flavours/glitch/features/ui/components/audio_modal.jsx
index fc98cc6af..0aeabd94c 100644
--- a/app/javascript/flavours/glitch/features/ui/components/audio_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/audio_modal.jsx
@@ -7,15 +7,16 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
 
 const mapStateToProps = (state, { statusId }) => ({
+  language: state.getIn(['statuses', statusId, 'language']),
   accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']),
 });
 
-export default @connect(mapStateToProps)
 class AudioModal extends ImmutablePureComponent {
 
   static propTypes = {
     media: ImmutablePropTypes.map.isRequired,
     statusId: PropTypes.string.isRequired,
+    language: PropTypes.string,
     accountStaticAvatar: PropTypes.string.isRequired,
     options: PropTypes.shape({
       autoPlay: PropTypes.bool,
@@ -29,7 +30,7 @@ class AudioModal extends ImmutablePureComponent {
   };
 
   render () {
-    const { media, accountStaticAvatar, statusId, onClose } = this.props;
+    const { media, language, accountStaticAvatar, statusId, onClose } = this.props;
     const options = this.props.options || {};
 
     return (
@@ -38,6 +39,7 @@ class AudioModal extends ImmutablePureComponent {
           <Audio
             src={media.get('url')}
             alt={media.get('description')}
+            lang={language}
             duration={media.getIn(['meta', 'original', 'duration'], 0)}
             height={150}
             poster={media.get('preview_url') || accountStaticAvatar}
@@ -56,3 +58,5 @@ class AudioModal extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, null, null, { forwardRef: true })(AudioModal);
diff --git a/app/javascript/flavours/glitch/features/ui/components/block_modal.js b/app/javascript/flavours/glitch/features/ui/components/block_modal.jsx
index a07baeaa6..a9506aa69 100644
--- a/app/javascript/flavours/glitch/features/ui/components/block_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/block_modal.jsx
@@ -36,8 +36,6 @@ const mapDispatchToProps = dispatch => {
   };
 };
 
-export default @connect(makeMapStateToProps, mapDispatchToProps)
-@injectIntl
 class BlockModal extends React.PureComponent {
 
   static propTypes = {
@@ -55,20 +53,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;
@@ -101,3 +99,5 @@ class BlockModal extends React.PureComponent {
   }
 
 }
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(BlockModal));
diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js b/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx
index 8d9496bb9..d9523a26e 100644
--- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx
@@ -35,8 +35,6 @@ const mapDispatchToProps = dispatch => {
   };
 };
 
-export default @connect(mapStateToProps, mapDispatchToProps)
-@injectIntl
 class BoostModal extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -58,17 +56,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 +74,7 @@ class BoostModal extends ImmutablePureComponent {
 
   setRef = (c) => {
     this.button = c;
-  }
+  };
 
   render () {
     const { status, missingMediaDescription, privacy, intl } = this.props;
@@ -116,9 +114,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>
 
@@ -137,3 +135,5 @@ class BoostModal extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(BoostModal));
diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle.js b/app/javascript/flavours/glitch/features/ui/components/bundle.jsx
index 8f0d7b8b1..27b13ecfe 100644
--- a/app/javascript/flavours/glitch/features/ui/components/bundle.js
+++ b/app/javascript/flavours/glitch/features/ui/components/bundle.jsx
@@ -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.jsx
index 7cbe1413d..eaabbc460 100644
--- a/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js
+++ b/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.jsx
@@ -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);
@@ -92,7 +92,6 @@ class CopyButton extends React.PureComponent {
 
 }
 
-export default @injectIntl
 class BundleColumnError extends React.PureComponent {
 
   static propTypes = {
@@ -113,7 +112,7 @@ class BundleColumnError extends React.PureComponent {
     if (onRetry) {
       onRetry();
     }
-  }
+  };
 
   render () {
     const { errorType, multiColumn, stacktrace } = this.props;
@@ -160,3 +159,5 @@ class BundleColumnError extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(BundleColumnError);
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.jsx
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.jsx
@@ -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.jsx
index e9c1e2f87..cc2abc43a 100644
--- a/app/javascript/flavours/glitch/features/ui/components/column.js
+++ b/app/javascript/flavours/glitch/features/ui/components/column.jsx
@@ -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.jsx
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.jsx
@@ -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.jsx
index bd1c20b47..4fffa54f4 100644
--- a/app/javascript/flavours/glitch/features/ui/components/column_link.js
+++ b/app/javascript/flavours/glitch/features/ui/components/column_link.jsx
@@ -30,9 +30,9 @@ 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'>
+      <a href='#' onClick={onClick && handleOnClick} className={className} title={text} {...other} tabIndex={0}>
         {iconElement}
         <span>{text}</span>
         {badgeElement}
diff --git a/app/javascript/flavours/glitch/features/ui/components/column_loading.js b/app/javascript/flavours/glitch/features/ui/components/column_loading.jsx
index b07385397..b07385397 100644
--- a/app/javascript/flavours/glitch/features/ui/components/column_loading.js
+++ b/app/javascript/flavours/glitch/features/ui/components/column_loading.jsx
diff --git a/app/javascript/flavours/glitch/features/ui/components/column_subheading.js b/app/javascript/flavours/glitch/features/ui/components/column_subheading.jsx
index 8160c4aa3..8160c4aa3 100644
--- a/app/javascript/flavours/glitch/features/ui/components/column_subheading.js
+++ b/app/javascript/flavours/glitch/features/ui/components/column_subheading.jsx
diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx
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.jsx
@@ -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/compare_history_modal.js b/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.jsx
index baf7f25be..cc3a16d17 100644
--- a/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.jsx
@@ -12,6 +12,7 @@ import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
 import MediaAttachments from 'flavours/glitch/components/media_attachments';
 
 const mapStateToProps = (state, { statusId }) => ({
+  language: state.getIn(['statuses', statusId, 'language']),
   versions: state.getIn(['history', statusId, 'items']),
 });
 
@@ -23,18 +24,18 @@ const mapDispatchToProps = dispatch => ({
 
 });
 
-export default @connect(mapStateToProps, mapDispatchToProps)
 class CompareHistoryModal extends React.PureComponent {
 
   static propTypes = {
     onClose: PropTypes.func.isRequired,
     index: PropTypes.number.isRequired,
     statusId: PropTypes.string.isRequired,
+    language: PropTypes.string.isRequired,
     versions: ImmutablePropTypes.list.isRequired,
   };
 
   render () {
-    const { index, versions, onClose } = this.props;
+    const { index, versions, language, onClose } = this.props;
     const currentVersion = versions.get(index);
 
     const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => {
@@ -65,12 +66,12 @@ class CompareHistoryModal extends React.PureComponent {
           <div className='status__content'>
             {currentVersion.get('spoiler_text').length > 0 && (
               <React.Fragment>
-                <div className='translate' dangerouslySetInnerHTML={spoilerContent} />
+                <div className='translate' dangerouslySetInnerHTML={spoilerContent} lang={language} />
                 <hr />
               </React.Fragment>
             )}
 
-            <div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
+            <div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} lang={language} />
 
             {!!currentVersion.get('poll') && (
               <div className='poll'>
@@ -82,6 +83,7 @@ class CompareHistoryModal extends React.PureComponent {
                       <span
                         className='poll__option__text translate'
                         dangerouslySetInnerHTML={{ __html: emojify(escapeTextContentForBrowser(option.get('title')), emojiMap) }}
+                        lang={language}
                       />
                     </li>
                   ))}
@@ -89,7 +91,7 @@ class CompareHistoryModal extends React.PureComponent {
               </div>
             )}
 
-            <MediaAttachments status={currentVersion} />
+            <MediaAttachments status={currentVersion} lang={language} />
           </div>
         </div>
       </div>
@@ -97,3 +99,5 @@ class CompareHistoryModal extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(CompareHistoryModal);
diff --git a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js b/app/javascript/flavours/glitch/features/ui/components/compose_panel.jsx
index dde252a61..1dedf92ca 100644
--- a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js
+++ b/app/javascript/flavours/glitch/features/ui/components/compose_panel.jsx
@@ -8,7 +8,6 @@ import LinkFooter from './link_footer';
 import ServerBanner from 'flavours/glitch/components/server_banner';
 import { mountCompose, unmountCompose } from 'flavours/glitch/actions/compose';
 
-export default @connect()
 class ComposePanel extends React.PureComponent {
 
   static contextTypes = {
@@ -55,4 +54,6 @@ class ComposePanel extends React.PureComponent {
     );
   }
 
-};
+}
+
+export default connect()(ComposePanel);
diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js b/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.jsx
index a665b9fb1..08f55c125 100644
--- a/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.jsx
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
 import { injectIntl, FormattedMessage } from 'react-intl';
 import Button from 'flavours/glitch/components/button';
 
-export default @injectIntl
 class ConfirmationModal extends React.PureComponent {
 
   static propTypes = {
@@ -34,24 +33,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;
@@ -86,3 +85,5 @@ class ConfirmationModal extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ConfirmationModal);
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.jsx
index 68f04cb21..5a1c1ee1b 100644
--- a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx
@@ -13,7 +13,6 @@ const messages = defineMessages({
   user_setting_disable_swiping: { id: 'settings.swipe_to_change_columns', defaultMessage: 'Allow swiping to change columns (Mobile only)' },
 });
 
-export default @injectIntl
 class DeprecatedSettingsModal extends React.PureComponent {
 
   static propTypes = {
@@ -30,11 +29,11 @@ class DeprecatedSettingsModal extends React.PureComponent {
   handleClick = () => {
     this.props.onConfirm();
     this.props.onClose();
-  }
+  };
 
   setRef = (c) => {
     this.button = c;
-  }
+  };
 
   render () {
     const { settings, intl } = this.props;
@@ -84,3 +83,5 @@ class DeprecatedSettingsModal extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(DeprecatedSettingsModal);
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.jsx
index c861d4d81..0ba79d648 100644
--- a/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.js
+++ b/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx
@@ -28,8 +28,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
   },
 });
 
-export default @injectIntl
-@connect(mapStateToProps, mapDispatchToProps)
 class DisabledAccountBanner extends React.PureComponent {
 
   static propTypes = {
@@ -46,7 +44,7 @@ class DisabledAccountBanner extends React.PureComponent {
     this.props.onLogout();
 
     return false;
-  }
+  };
 
   render () {
     const { disabledAcct, movedToAcct } = this.props;
@@ -89,4 +87,6 @@ class DisabledAccountBanner extends React.PureComponent {
     );
   }
 
-};
+}
+
+export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(DisabledAccountBanner));
diff --git a/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js b/app/javascript/flavours/glitch/features/ui/components/doodle_modal.jsx
index 0d10204fc..162957ad8 100644
--- a/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/doodle_modal.jsx
@@ -145,7 +145,6 @@ const mapDispatchToProps = dispatch => ({
  * - Ctrl + left mouse button: pick background
  * - Right mouse button: pick background
  */
-export default @connect(mapStateToProps, mapDispatchToProps)
 class DoodleModal extends ImmutablePureComponent {
 
   static propTypes = {
@@ -279,7 +278,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 +574,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 +601,7 @@ class DoodleModal extends ImmutablePureComponent {
                       'foreground': this.fg === c[0],
                       'background': this.bg === c[0],
                     })}
-                  />
+                  />,
               )
             }
           </div>
@@ -612,3 +611,5 @@ class DoodleModal extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(DoodleModal);
diff --git a/app/javascript/flavours/glitch/features/ui/components/drawer_loading.js b/app/javascript/flavours/glitch/features/ui/components/drawer_loading.jsx
index 08b0d2347..08b0d2347 100644
--- a/app/javascript/flavours/glitch/features/ui/components/drawer_loading.js
+++ b/app/javascript/flavours/glitch/features/ui/components/drawer_loading.jsx
diff --git a/app/javascript/flavours/glitch/features/ui/components/embed_modal.js b/app/javascript/flavours/glitch/features/ui/components/embed_modal.jsx
index 624b68f7e..4f1173fd5 100644
--- a/app/javascript/flavours/glitch/features/ui/components/embed_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/embed_modal.jsx
@@ -9,7 +9,6 @@ const messages = defineMessages({
   close: { id: 'lightbox.close', defaultMessage: 'Close' },
 });
 
-export default @injectIntl
 class EmbedModal extends ImmutablePureComponent {
 
   static propTypes = {
@@ -17,7 +16,7 @@ class EmbedModal extends ImmutablePureComponent {
     onClose: PropTypes.func.isRequired,
     onError: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
-  }
+  };
 
   state = {
     loading: false,
@@ -48,11 +47,11 @@ class EmbedModal extends ImmutablePureComponent {
 
   setIframeRef = c =>  {
     this.iframe = c;
-  }
+  };
 
   handleTextareaClick = (e) => {
     e.target.select();
-  }
+  };
 
   render () {
     const { intl, onClose } = this.props;
@@ -95,3 +94,5 @@ class EmbedModal extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(EmbedModal);
diff --git a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx
index d7f671d58..fa6f11792 100644
--- a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx
@@ -17,7 +17,6 @@ const messages = defineMessages({
   favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
 });
 
-export default @injectIntl
 class FavouriteModal extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -38,21 +37,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;
@@ -99,3 +98,5 @@ class FavouriteModal extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(FavouriteModal);
diff --git a/app/javascript/flavours/glitch/features/ui/components/filter_modal.js b/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx
index d2482e733..2d49312e5 100644
--- a/app/javascript/flavours/glitch/features/ui/components/filter_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx
@@ -13,8 +13,6 @@ const messages = defineMessages({
   close: { id: 'lightbox.close', defaultMessage: 'Close' },
 });
 
-export default @connect(undefined)
-@injectIntl
 class FilterModal extends ImmutablePureComponent {
 
   static propTypes = {
@@ -132,3 +130,5 @@ class FilterModal extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(injectIntl(FilterModal));
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.jsx
index 0dd07fb76..a5637d31c 100644
--- a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx
@@ -5,11 +5,10 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import { connect } from 'react-redux';
 import classNames from 'classnames';
 import { changeUploadCompose, uploadThumbnail, onChangeMediaDescription, onChangeMediaFocus } from 'flavours/glitch/actions/compose';
-import { getPointerPosition } from 'flavours/glitch/features/video';
+import Video, { getPointerPosition } from 'flavours/glitch/features/video';
 import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
 import IconButton from 'flavours/glitch/components/icon_button';
 import Button from 'flavours/glitch/components/button';
-import Video from 'flavours/glitch/features/video';
 import Audio from 'flavours/glitch/features/audio';
 import Textarea from 'react-textarea-autosize';
 import UploadProgress from 'flavours/glitch/features/compose/components/upload_progress';
@@ -39,6 +38,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']),
@@ -99,8 +99,6 @@ class ImageLoader extends React.PureComponent {
 
 }
 
-export default @connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true })
-@(component => injectIntl(component, { withRef: true }))
 class FocalPointModal extends ImmutablePureComponent {
 
   static propTypes = {
@@ -134,7 +132,7 @@ class FocalPointModal extends ImmutablePureComponent {
 
     this.updatePosition(e);
     this.setState({ dragging: true });
-  }
+  };
 
   handleTouchStart = e => {
     document.addEventListener('touchmove', this.handleMouseMove);
@@ -142,25 +140,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 +166,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 +179,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 +196,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 +255,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;
@@ -320,7 +318,7 @@ class FocalPointModal extends ImmutablePureComponent {
               <React.Fragment>
                 <label className='setting-text-label' htmlFor='upload-modal__thumbnail'><FormattedMessage id='upload_form.thumbnail' defaultMessage='Change thumbnail' /></label>
 
-                <Button disabled={isUploadingThumbnail} text={intl.formatMessage(messages.chooseImage)} onClick={this.handleFileInputClick} />
+                <Button disabled={isUploadingThumbnail || !media.get('unattached')} text={intl.formatMessage(messages.chooseImage)} onClick={this.handleFileInputClick} />
 
                 <label>
                   <span style={{ display: 'none' }}>{intl.formatMessage(messages.chooseImage)}</span>
@@ -349,6 +347,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}
@@ -415,3 +414,7 @@ class FocalPointModal extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps, null, {
+  forwardRef: true,
+})(injectIntl(FocalPointModal, { withRef: true }));
diff --git a/app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.js b/app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.jsx
index 301392a52..f3e3b78ed 100644
--- a/app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.js
+++ b/app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.jsx
@@ -15,8 +15,6 @@ const mapStateToProps = state => ({
   count: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
 });
 
-export default @injectIntl
-@connect(mapStateToProps)
 class FollowRequestsColumnLink extends React.Component {
 
   static propTypes = {
@@ -49,3 +47,5 @@ class FollowRequestsColumnLink extends React.Component {
   }
 
 }
+
+export default injectIntl(connect(mapStateToProps)(FollowRequestsColumnLink));
diff --git a/app/javascript/flavours/glitch/features/ui/components/header.js b/app/javascript/flavours/glitch/features/ui/components/header.jsx
index d9ad94961..f7bab2487 100644
--- a/app/javascript/flavours/glitch/features/ui/components/header.js
+++ b/app/javascript/flavours/glitch/features/ui/components/header.jsx
@@ -23,8 +23,6 @@ const mapDispatchToProps = (dispatch) => ({
   },
 });
 
-export default @connect(null, mapDispatchToProps)
-@withRouter
 class Header extends React.PureComponent {
 
   static contextTypes = {
@@ -86,3 +84,5 @@ class Header extends React.PureComponent {
   }
 
 }
+
+export default withRouter(connect(null, mapDispatchToProps)(Header));
diff --git a/app/javascript/flavours/glitch/features/ui/components/image_loader.js b/app/javascript/flavours/glitch/features/ui/components/image_loader.jsx
index dfa0efe49..9093eab28 100644
--- a/app/javascript/flavours/glitch/features/ui/components/image_loader.js
+++ b/app/javascript/flavours/glitch/features/ui/components/image_loader.jsx
@@ -8,16 +8,18 @@ export default class ImageLoader extends PureComponent {
 
   static propTypes = {
     alt: PropTypes.string,
+    lang: PropTypes.string,
     src: PropTypes.string.isRequired,
     previewSrc: PropTypes.string,
     width: PropTypes.number,
     height: PropTypes.number,
     onClick: PropTypes.func,
     zoomButtonHidden: PropTypes.bool,
-  }
+  };
 
   static defaultProps = {
     alt: '',
+    lang: '',
     width: null,
     height: null,
   };
@@ -26,7 +28,7 @@ export default class ImageLoader extends PureComponent {
     loading: true,
     error: false,
     width: null,
-  }
+  };
 
   removers = [];
   canvas = null;
@@ -86,7 +88,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,10 +128,10 @@ 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;
+    const { alt, lang, src, width, height, onClick } = this.props;
     const { loading } = this.state;
 
     const className = classNames('image-loader', {
@@ -154,6 +156,7 @@ export default class ImageLoader extends PureComponent {
         ) : (
           <ZoomableImage
             alt={alt}
+            lang={lang}
             src={src}
             onClick={onClick}
             width={width}
diff --git a/app/javascript/flavours/glitch/features/ui/components/image_modal.js b/app/javascript/flavours/glitch/features/ui/components/image_modal.jsx
index a792b9be7..5198b8809 100644
--- a/app/javascript/flavours/glitch/features/ui/components/image_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/image_modal.jsx
@@ -9,7 +9,6 @@ const messages = defineMessages({
   close: { id: 'lightbox.close', defaultMessage: 'Close' },
 });
 
-export default @injectIntl
 class ImageModal extends React.PureComponent {
 
   static propTypes = {
@@ -57,3 +56,5 @@ class ImageModal extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ImageModal);
diff --git a/app/javascript/flavours/glitch/features/ui/components/link_footer.js b/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx
index ac0c78674..e813a72b0 100644
--- a/app/javascript/flavours/glitch/features/ui/components/link_footer.js
+++ b/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx
@@ -3,7 +3,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
 import { Link } from 'react-router-dom';
-import { domain, version, source_url, profile_directory as profileDirectory } from 'flavours/glitch/initial_state';
+import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'flavours/glitch/initial_state';
 import { logOut } from 'flavours/glitch/utils/log_out';
 import { openModal } from 'flavours/glitch/actions/modal';
 import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions';
@@ -24,8 +24,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
   },
 });
 
-export default @injectIntl
-@connect(null, mapDispatchToProps)
 class LinkFooter extends React.PureComponent {
 
   static contextTypes = {
@@ -44,7 +42,7 @@ class LinkFooter extends React.PureComponent {
     this.props.onLogout();
 
     return false;
-  }
+  };
 
   render () {
     const { signedIn, permissions } = this.context.identity;
@@ -59,21 +57,27 @@ class LinkFooter extends React.PureComponent {
         <p>
           <strong>{domain}</strong>:
           {' '}
-          <Link key='about' to='/about'><FormattedMessage id='footer.about' defaultMessage='About' /></Link>
+          <Link to='/about'><FormattedMessage id='footer.about' defaultMessage='About' /></Link>
+          {statusPageUrl && (
+            <>
+              {DividingCircle}
+              <a href={statusPageUrl} target='_blank' rel='noopener'><FormattedMessage id='footer.status' defaultMessage='Status' /></a>
+            </>
+          )}
           {canInvite && (
             <>
               {DividingCircle}
-              <a key='invites' href='/invites' target='_blank'><FormattedMessage id='footer.invite' defaultMessage='Invite people' /></a>
+              <a href='/invites' target='_blank'><FormattedMessage id='footer.invite' defaultMessage='Invite people' /></a>
             </>
           )}
           {canProfileDirectory && (
             <>
               {DividingCircle}
-              <Link key='directory' to='/directory'><FormattedMessage id='footer.directory' defaultMessage='Profiles directory' /></Link>
+              <Link to='/directory'><FormattedMessage id='footer.directory' defaultMessage='Profiles directory' /></Link>
             </>
           )}
           {DividingCircle}
-          <Link key='privacy-policy' to='/privacy-policy'><FormattedMessage id='footer.privacy_policy' defaultMessage='Privacy policy' /></Link>
+          <Link to='/privacy-policy'><FormattedMessage id='footer.privacy_policy' defaultMessage='Privacy policy' /></Link>
         </p>
 
         <p>
@@ -93,4 +97,6 @@ class LinkFooter extends React.PureComponent {
     );
   }
 
-};
+}
+
+export default injectIntl(connect(null, mapDispatchToProps)(LinkFooter));
diff --git a/app/javascript/flavours/glitch/features/ui/components/list_panel.js b/app/javascript/flavours/glitch/features/ui/components/list_panel.jsx
index dff830065..489f3a1b4 100644
--- a/app/javascript/flavours/glitch/features/ui/components/list_panel.js
+++ b/app/javascript/flavours/glitch/features/ui/components/list_panel.jsx
@@ -20,8 +20,6 @@ const mapStateToProps = state => ({
   lists: getOrderedLists(state),
 });
 
-export default @withRouter
-@connect(mapStateToProps)
 class ListPanel extends ImmutablePureComponent {
 
   static propTypes = {
@@ -53,3 +51,5 @@ class ListPanel extends ImmutablePureComponent {
   }
 
 }
+
+export default withRouter(connect(mapStateToProps)(ListPanel));
diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.js b/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx
index ec3af857d..fd2bd43cf 100644
--- a/app/javascript/flavours/glitch/features/ui/components/media_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx
@@ -3,6 +3,7 @@ import ReactSwipeableViews from 'react-swipeable-views';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import Video from 'flavours/glitch/features/video';
+import { connect } from 'react-redux';
 import classNames from 'classnames';
 import { defineMessages, injectIntl } from 'react-intl';
 import IconButton from 'flavours/glitch/components/icon_button';
@@ -20,7 +21,10 @@ const messages = defineMessages({
   next: { id: 'lightbox.next', defaultMessage: 'Next' },
 });
 
-export default @injectIntl
+const mapStateToProps = (state, { statusId }) => ({
+  language: state.getIn(['statuses', statusId, 'language']),
+});
+
 class MediaModal extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -47,27 +51,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 +80,7 @@ class MediaModal extends ImmutablePureComponent {
       index: index % this.props.media.size,
       zoomButtonHidden: true,
     });
-  }
+  };
 
   handleKeyDown = (e) => {
     switch(e.key) {
@@ -91,7 +95,7 @@ class MediaModal extends ImmutablePureComponent {
       e.stopPropagation();
       break;
     }
-  }
+  };
 
   componentDidMount () {
     window.addEventListener('keydown', this.handleKeyDown, false);
@@ -131,13 +135,13 @@ class MediaModal extends ImmutablePureComponent {
   }
 
   render () {
-    const { media, statusId, intl, onClose } = this.props;
+    const { media, language, statusId, intl, onClose } = this.props;
     const { navigationHidden } = this.state;
 
     const index = this.getIndex();
 
-    const leftNav  = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' fixedWidth /></button>;
-    const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav  media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' fixedWidth /></button>;
+    const leftNav  = media.size > 1 && <button tabIndex={0} className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' fixedWidth /></button>;
+    const rightNav = media.size > 1 && <button tabIndex={0} className='media-modal__nav  media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' fixedWidth /></button>;
 
     const content = media.map((image) => {
       const width  = image.getIn(['meta', 'original', 'width']) || null;
@@ -151,6 +155,7 @@ class MediaModal extends ImmutablePureComponent {
             width={width}
             height={height}
             alt={image.get('description')}
+            lang={language}
             key={image.get('url')}
             onClick={this.toggleNavigation}
             zoomButtonHidden={this.state.zoomButtonHidden}
@@ -173,6 +178,7 @@ class MediaModal extends ImmutablePureComponent {
             onCloseVideo={onClose}
             detailed
             alt={image.get('description')}
+            lang={language}
             key={image.get('url')}
           />
         );
@@ -184,6 +190,7 @@ class MediaModal extends ImmutablePureComponent {
             height={height}
             key={image.get('preview_url')}
             alt={image.get('description')}
+            lang={language}
             onClick={this.toggleNavigation}
           />
         );
@@ -250,3 +257,5 @@ class MediaModal extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, null, null, { forwardRef: true })(injectIntl(MediaModal));
diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_loading.js b/app/javascript/flavours/glitch/features/ui/components/modal_loading.jsx
index b1c322154..b1c322154 100644
--- a/app/javascript/flavours/glitch/features/ui/components/modal_loading.js
+++ b/app/javascript/flavours/glitch/features/ui/components/modal_loading.jsx
diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.js b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx
index 379f57cbb..c133f2b6a 100644
--- a/app/javascript/flavours/glitch/features/ui/components/modal_root.js
+++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx
@@ -76,28 +76,28 @@ 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 {
       document.body.classList.remove('with-modals--active');
-      document.documentElement.style.marginRight = 0;
+      document.documentElement.style.marginRight = '0';
     }
   }
 
   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.jsx
index 7d25db316..a74ebfb05 100644
--- a/app/javascript/flavours/glitch/features/ui/components/mute_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/mute_modal.jsx
@@ -43,8 +43,6 @@ const mapDispatchToProps = dispatch => {
   };
 };
 
-export default @connect(mapStateToProps, mapDispatchToProps)
-@injectIntl
 class MuteModal extends React.PureComponent {
 
   static propTypes = {
@@ -65,23 +63,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;
@@ -138,3 +136,5 @@ class MuteModal extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(MuteModal));
diff --git a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx
index 3b46c6eec..6e8744ef0 100644
--- a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js
+++ b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx
@@ -29,7 +29,6 @@ const messages = defineMessages({
   app_settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
 });
 
-export default @injectIntl
 class NavigationPanel extends React.Component {
 
   static contextTypes = {
@@ -78,8 +77,8 @@ class NavigationPanel extends React.Component {
         {signedIn && (
           <React.Fragment>
             <ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
-            <ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
             <ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
+            <ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
             <ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
 
             <ListPanel />
@@ -102,3 +101,5 @@ class NavigationPanel extends React.Component {
   }
 
 }
+
+export default injectIntl(NavigationPanel);
diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.jsx
index 5ca003ee9..7c9105c58 100644
--- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.jsx
@@ -49,7 +49,7 @@ const PageTwo = ({ intl, myAccount }) => (
           privacy='public'
           text='Awoo! #introductions'
           spoilerText=''
-          suggestions={ [] }
+          suggestions={[]}
         />
       </div>
     </div>
@@ -171,8 +171,6 @@ const mapStateToProps = state => ({
   domain: state.getIn(['meta', 'domain']),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class OnboardingModal extends React.PureComponent {
 
   static propTypes = {
@@ -196,7 +194,7 @@ class OnboardingModal extends React.PureComponent {
       <PageFour domain={domain} intl={intl} />,
       <PageSix admin={admin} domain={domain} />,
     ];
-  };
+  }
 
   componentDidMount() {
     window.addEventListener('keyup', this.handleKeyUp);
@@ -209,30 +207,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) {
@@ -243,11 +241,11 @@ class OnboardingModal extends React.PureComponent {
       this.handleNext();
       break;
     }
-  }
+  };
 
   handleClose = () => {
     this.props.onClose();
-  }
+  };
 
   render () {
     const { pages } = this;
@@ -302,7 +300,7 @@ class OnboardingModal extends React.PureComponent {
                 <div
                   key={`dot-${i}`}
                   role='button'
-                  tabIndex='0'
+                  tabIndex={0}
                   data-index={i}
                   onClick={this.handleDot}
                   className={className}
@@ -320,3 +318,5 @@ class OnboardingModal extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(OnboardingModal));
diff --git a/app/javascript/flavours/glitch/features/ui/components/report_modal.js b/app/javascript/flavours/glitch/features/ui/components/report_modal.jsx
index 7b6a4a784..79b495877 100644
--- a/app/javascript/flavours/glitch/features/ui/components/report_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/report_modal.jsx
@@ -31,8 +31,6 @@ const makeMapStateToProps = () => {
   return mapStateToProps;
 };
 
-export default @connect(makeMapStateToProps)
-@injectIntl
 class ReportModal extends ImmutablePureComponent {
 
   static propTypes = {
@@ -96,7 +94,7 @@ class ReportModal extends ImmutablePureComponent {
     } else {
       this.setState({ selectedRuleIds: selectedRuleIds.remove(ruleId) });
     }
-  }
+  };
 
   handleChangeCategory = category => {
     this.setState({ category });
@@ -219,3 +217,5 @@ class ReportModal extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(makeMapStateToProps)(injectIntl(ReportModal));
diff --git a/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.js b/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx
index e8023803f..c0d62aca0 100644
--- a/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.js
+++ b/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx
@@ -30,7 +30,7 @@ const SignInBanner = () => {
 
   return (
     <div className='sign-in-banner'>
-      <p><FormattedMessage id='sign_in_banner.text' defaultMessage='Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.' /></p>
+      <p><FormattedMessage id='sign_in_banner.text' defaultMessage='Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.' /></p>
       <a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
       {signupButton}
     </div>
diff --git a/app/javascript/flavours/glitch/features/ui/components/upload_area.js b/app/javascript/flavours/glitch/features/ui/components/upload_area.jsx
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.jsx
@@ -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/video_modal.js b/app/javascript/flavours/glitch/features/ui/components/video_modal.jsx
index 90be11e4b..4cde0ebad 100644
--- a/app/javascript/flavours/glitch/features/ui/components/video_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/video_modal.jsx
@@ -2,11 +2,16 @@ import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import Video from 'flavours/glitch/features/video';
+import { connect } from 'react-redux';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
 import { getAverageFromBlurhash } from 'flavours/glitch/blurhash';
 
-export default class VideoModal extends ImmutablePureComponent {
+const mapStateToProps = (state, { statusId }) => ({
+  language: state.getIn(['statuses', statusId, 'language']),
+});
+
+class VideoModal extends ImmutablePureComponent {
 
   static contextTypes = {
     router: PropTypes.object,
@@ -15,6 +20,7 @@ export default class VideoModal extends ImmutablePureComponent {
   static propTypes = {
     media: ImmutablePropTypes.map.isRequired,
     statusId: PropTypes.string,
+    language: PropTypes.string,
     options: PropTypes.shape({
       startTime: PropTypes.number,
       autoPlay: PropTypes.bool,
@@ -35,7 +41,7 @@ export default class VideoModal extends ImmutablePureComponent {
   }
 
   render () {
-    const { media, statusId, onClose } = this.props;
+    const { media, statusId, language, onClose } = this.props;
     const options = this.props.options || {};
 
     return (
@@ -53,6 +59,7 @@ export default class VideoModal extends ImmutablePureComponent {
             autoFocus
             detailed
             alt={media.get('description')}
+            lang={language}
           />
         </div>
 
@@ -64,3 +71,5 @@ export default class VideoModal extends ImmutablePureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, null, null, { forwardRef: true })(VideoModal);
diff --git a/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js b/app/javascript/flavours/glitch/features/ui/components/zoomable_image.jsx
index caeeced64..47401cfe4 100644
--- a/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js
+++ b/app/javascript/flavours/glitch/features/ui/components/zoomable_image.jsx
@@ -91,21 +91,22 @@ const normalizeWheel = event => {
   };
 };
 
-export default @injectIntl
 class ZoomableImage extends React.PureComponent {
 
   static propTypes = {
     alt: PropTypes.string,
+    lang: PropTypes.string,
     src: PropTypes.string.isRequired,
     width: PropTypes.number,
     height: PropTypes.number,
     onClick: PropTypes.func,
     zoomButtonHidden: PropTypes.bool,
     intl: PropTypes.object.isRequired,
-  }
+  };
 
   static defaultProps = {
     alt: '',
+    lang: '',
     width: null,
     height: null,
   };
@@ -132,7 +133,7 @@ class ZoomableImage extends React.PureComponent {
     dragged: false,
     lockScroll: { x: 0, y: 0 },
     lockTranslate: { x: 0, y: 0 },
-  }
+  };
 
   removers = [];
   container = null;
@@ -212,7 +213,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 +229,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 +239,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 +247,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 +276,7 @@ class ZoomableImage extends React.PureComponent {
 
     this.lastMidpoint = midpoint;
     this.lastDistance = distance;
-  }
+  };
 
   zoom(nextScale, midpoint) {
     const { scale, zoomMatrix } = this.state;
@@ -314,11 +315,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 +351,7 @@ class ZoomableImage extends React.PureComponent {
         translateY: translateY,
       },
     });
-  }
+  };
 
   handleZoomClick = e => {
     e.preventDefault();
@@ -392,18 +393,18 @@ 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;
+    const { alt, lang, src, width, height, intl } = this.props;
     const { scale, lockTranslate } = this.state;
     const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll';
     const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : '';
@@ -431,6 +432,7 @@ class ZoomableImage extends React.PureComponent {
             ref={this.setImageRef}
             alt={alt}
             title={alt}
+            lang={lang}
             src={src}
             width={width}
             height={height}
@@ -448,3 +450,5 @@ class ZoomableImage extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ZoomableImage);
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.jsx
index 72e13d9d6..fa35f689d 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.jsx
@@ -10,7 +10,7 @@ import { debounce } from 'lodash';
 import { uploadCompose, resetCompose, changeComposeSpoilerness } from 'flavours/glitch/actions/compose';
 import { expandHomeTimeline } from 'flavours/glitch/actions/timelines';
 import { expandNotifications, notificationsSetVisibility } from 'flavours/glitch/actions/notifications';
-import { fetchServer } from 'flavours/glitch/actions/server';
+import { fetchServer, fetchServerTranslationLanguages } from 'flavours/glitch/actions/server';
 import { clearHeight } from 'flavours/glitch/actions/height_cache';
 import { changeLayout } from 'flavours/glitch/actions/app';
 import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers';
@@ -42,6 +42,7 @@ import {
   FollowRequests,
   FavouritedStatuses,
   BookmarkedStatuses,
+  FollowedTags,
   ListTimeline,
   Blocks,
   DomainBlocks,
@@ -56,7 +57,7 @@ import {
   PrivacyPolicy,
 } from './util/async-components';
 import { HotKeys } from 'react-hotkeys';
-import initialState, { me, owner, singleUserMode, showTrends } from '../../initial_state';
+import initialState, { me, owner, singleUserMode, showTrends, trendsAsLanding } from '../../initial_state';
 import { closeOnboarding, INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding';
 import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
 import { Helmet } from 'react-helmet';
@@ -161,7 +162,7 @@ class SwitchingColumnsArea extends React.PureComponent {
     if (c) {
       this.node = c;
     }
-  }
+  };
 
   render () {
     const { children, mobile, navbarUnder } = this.props;
@@ -177,7 +178,7 @@ class SwitchingColumnsArea extends React.PureComponent {
       }
     } else if (singleUserMode && owner && initialState?.accounts[owner]) {
       redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />;
-    } else if (showTrends) {
+    } else if (showTrends && trendsAsLanding) {
       redirect = <Redirect from='/' to='/explore' exact />;
     } else {
       redirect = <Redirect from='/' to='/about' exact />;
@@ -230,6 +231,7 @@ class SwitchingColumnsArea extends React.PureComponent {
           <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
           <WrappedRoute path='/blocks' component={Blocks} content={children} />
           <WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} />
+          <WrappedRoute path='/followed_tags' component={FollowedTags} content={children} />
           <WrappedRoute path='/mutes' component={Mutes} content={children} />
           <WrappedRoute path='/lists' component={Lists} content={children} />
           <WrappedRoute path='/getting-started-misc' component={GettingStartedMisc} content={children} />
@@ -238,13 +240,10 @@ class SwitchingColumnsArea extends React.PureComponent {
         </WrappedSwitch>
       </ColumnsAreaContainer>
     );
-  };
+  }
 
 }
 
-export default @connect(mapStateToProps)
-@injectIntl
-@withRouter
 class UI extends React.Component {
 
   static contextTypes = {
@@ -290,7 +289,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();
@@ -306,7 +305,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;
@@ -320,7 +319,7 @@ class UI extends React.Component {
     }
 
     return false;
-  }
+  };
 
   handleDrop = (e) => {
     if (this.dataTransferIsText(e.dataTransfer)) return;
@@ -333,7 +332,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();
@@ -346,15 +345,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') {
@@ -362,7 +361,7 @@ class UI extends React.Component {
     } else {
       console.warn('Unknown message type:', data.type);
     }
-  }
+  };
 
   handleVisibilityChange = () => {
     const visibility = !document[this.visibilityHiddenProp];
@@ -370,7 +369,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
@@ -387,7 +386,7 @@ class UI extends React.Component {
     } else {
       this.handleLayoutChange();
     }
-  }
+  };
 
   componentDidMount () {
     const { signedIn } = this.context.identity;
@@ -405,7 +404,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) {
@@ -417,6 +416,7 @@ class UI extends React.Component {
       this.props.dispatch(fetchMarkers());
       this.props.dispatch(expandHomeTimeline());
       this.props.dispatch(expandNotifications());
+      this.props.dispatch(fetchServerTranslationLanguages());
 
       setTimeout(() => this.props.dispatch(fetchServer()), 3000);
     }
@@ -485,7 +485,7 @@ class UI extends React.Component {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   handleHotkeyNew = e => {
     e.preventDefault();
@@ -495,7 +495,7 @@ class UI extends React.Component {
     if (element) {
       element.focus();
     }
-  }
+  };
 
   handleHotkeySearch = e => {
     e.preventDefault();
@@ -505,17 +505,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
@@ -533,7 +533,7 @@ class UI extends React.Component {
         status.focus();
       }
     }
-  }
+  };
 
   handleHotkeyBack = () => {
     // if history is exhausted, or we would leave mastodon, just go to root.
@@ -542,11 +542,11 @@ class UI extends React.Component {
     } else {
       this.props.history.push('/');
     }
-  }
+  };
 
   setHotkeysRef = c => {
     this.hotkeys = c;
-  }
+  };
 
   handleHotkeyToggleHelp = () => {
     if (this.props.location.pathname === '/keyboard-shortcuts') {
@@ -554,55 +554,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;
@@ -659,7 +659,7 @@ class UI extends React.Component {
                 <PermaLink href={moved.get('url')} to={`/@${moved.get('acct')}`}>
                   @{moved.get('acct')}
                 </PermaLink>
-              )}}
+              ) }}
             />
           </div>)}
 
@@ -680,3 +680,5 @@ class UI extends React.Component {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(withRouter(UI)));
diff --git a/app/javascript/flavours/glitch/features/ui/util/async-components.js b/app/javascript/flavours/glitch/features/ui/util/async-components.js
index 025b22e61..03e501628 100644
--- a/app/javascript/flavours/glitch/features/ui/util/async-components.js
+++ b/app/javascript/flavours/glitch/features/ui/util/async-components.js
@@ -98,6 +98,10 @@ export function FavouritedStatuses () {
   return import(/* webpackChunkName: "flavours/glitch/async/favourited_statuses" */'flavours/glitch/features/favourited_statuses');
 }
 
+export function FollowedTags () {
+  return import(/* webpackChunkName: "flavours/glitch/async/followed_tags" */'flavours/glitch/features/followed_tags');
+}
+
 export function BookmarkedStatuses () {
   return import(/* webpackChunkName: "flavours/glitch/async/bookmarked_statuses" */'flavours/glitch/features/bookmarked_statuses');
 }
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.jsx
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.jsx
@@ -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.jsx
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.jsx
@@ -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.jsx
index 0daab747b..28a8bb1fd 100644
--- a/app/javascript/flavours/glitch/features/video/index.js
+++ b/app/javascript/flavours/glitch/features/video/index.jsx
@@ -93,7 +93,6 @@ export const fileNameFromURL = str => {
   return pathname.slice(index + 1);
 };
 
-export default @injectIntl
 class Video extends React.PureComponent {
 
   static propTypes = {
@@ -101,6 +100,7 @@ class Video extends React.PureComponent {
     frameRate: PropTypes.string,
     src: PropTypes.string.isRequired,
     alt: PropTypes.string,
+    lang: PropTypes.string,
     width: PropTypes.number,
     height: PropTypes.number,
     sensitive: PropTypes.bool,
@@ -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)) {
@@ -538,7 +538,7 @@ class Video extends React.PureComponent {
   }
 
   render () {
-    const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
+    const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, lang, letterbox, fullwidth, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
     const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
     const progress = Math.min((currentTime / duration) * 100, 100);
     const playerStyle = {};
@@ -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;
@@ -600,9 +600,10 @@ class Video extends React.PureComponent {
           preload={preload}
           loop
           role='button'
-          tabIndex='0'
+          tabIndex={0}
           aria-label={alt}
           title={alt}
+          lang={lang}
           width={width}
           height={height}
           volume={volume}
@@ -628,7 +629,7 @@ class Video extends React.PureComponent {
 
             <span
               className={classNames('video-player__seek__handle', { active: dragging })}
-              tabIndex='0'
+              tabIndex={0}
               style={{ left: `${progress}%` }}
               onKeyDown={this.handleVideoKeyDown}
             />
@@ -644,7 +645,7 @@ class Video extends React.PureComponent {
 
                 <span
                   className={classNames('video-player__volume__handle')}
-                  tabIndex='0'
+                  tabIndex={0}
                   style={{ left: `${volume * 100}%` }}
                 />
               </div>
@@ -671,3 +672,5 @@ class Video extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(Video);