about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/components
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/components
parent65c1e53a32cabcdbb7bca57002bb0f6acdebe07e (diff)
parentbed63f6dae0879ac840066b031229e0d139089cd (diff)
Merge remote-tracking branch 'glitch/main' HEAD main
Diffstat (limited to 'app/javascript/flavours/glitch/components')
-rw-r--r--app/javascript/flavours/glitch/components/account.jsx (renamed from app/javascript/flavours/glitch/components/account.js)15
-rw-r--r--app/javascript/flavours/glitch/components/admin/Counter.jsx (renamed from app/javascript/flavours/glitch/components/admin/Counter.js)0
-rw-r--r--app/javascript/flavours/glitch/components/admin/Dimension.jsx (renamed from app/javascript/flavours/glitch/components/admin/Dimension.js)0
-rw-r--r--app/javascript/flavours/glitch/components/admin/ReportReasonSelector.jsx (renamed from app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js)7
-rw-r--r--app/javascript/flavours/glitch/components/admin/Retention.jsx (renamed from app/javascript/flavours/glitch/components/admin/Retention.js)2
-rw-r--r--app/javascript/flavours/glitch/components/admin/Trends.jsx (renamed from app/javascript/flavours/glitch/components/admin/Trends.js)2
-rw-r--r--app/javascript/flavours/glitch/components/animated_number.jsx (renamed from app/javascript/flavours/glitch/components/animated_number.js)4
-rw-r--r--app/javascript/flavours/glitch/components/attachment_list.jsx (renamed from app/javascript/flavours/glitch/components/attachment_list.js)0
-rw-r--r--app/javascript/flavours/glitch/components/autosuggest_emoji.jsx (renamed from app/javascript/flavours/glitch/components/autosuggest_emoji.js)0
-rw-r--r--app/javascript/flavours/glitch/components/autosuggest_hashtag.jsx (renamed from app/javascript/flavours/glitch/components/autosuggest_hashtag.js)0
-rw-r--r--app/javascript/flavours/glitch/components/autosuggest_input.jsx (renamed from app/javascript/flavours/glitch/components/autosuggest_input.js)22
-rw-r--r--app/javascript/flavours/glitch/components/autosuggest_textarea.jsx (renamed from app/javascript/flavours/glitch/components/autosuggest_textarea.js)22
-rw-r--r--app/javascript/flavours/glitch/components/avatar.jsx (renamed from app/javascript/flavours/glitch/components/avatar.js)4
-rw-r--r--app/javascript/flavours/glitch/components/avatar_composite.jsx (renamed from app/javascript/flavours/glitch/components/avatar_composite.js)0
-rw-r--r--app/javascript/flavours/glitch/components/avatar_overlay.jsx (renamed from app/javascript/flavours/glitch/components/avatar_overlay.js)0
-rw-r--r--app/javascript/flavours/glitch/components/blurhash.jsx (renamed from app/javascript/flavours/glitch/components/blurhash.js)0
-rw-r--r--app/javascript/flavours/glitch/components/button.jsx (renamed from app/javascript/flavours/glitch/components/button.js)4
-rw-r--r--app/javascript/flavours/glitch/components/check.jsx (renamed from app/javascript/flavours/glitch/components/check.js)0
-rw-r--r--app/javascript/flavours/glitch/components/column.jsx (renamed from app/javascript/flavours/glitch/components/column.js)4
-rw-r--r--app/javascript/flavours/glitch/components/column_back_button.jsx (renamed from app/javascript/flavours/glitch/components/column_back_button.js)2
-rw-r--r--app/javascript/flavours/glitch/components/column_back_button_slim.jsx (renamed from app/javascript/flavours/glitch/components/column_back_button_slim.js)4
-rw-r--r--app/javascript/flavours/glitch/components/column_header.jsx (renamed from app/javascript/flavours/glitch/components/column_header.js)19
-rw-r--r--app/javascript/flavours/glitch/components/common_counter.jsx (renamed from app/javascript/flavours/glitch/components/common_counter.js)0
-rw-r--r--app/javascript/flavours/glitch/components/dismissable_banner.jsx (renamed from app/javascript/flavours/glitch/components/dismissable_banner.js)5
-rw-r--r--app/javascript/flavours/glitch/components/display_name.jsx (renamed from app/javascript/flavours/glitch/components/display_name.js)6
-rw-r--r--app/javascript/flavours/glitch/components/domain.jsx (renamed from app/javascript/flavours/glitch/components/domain.js)5
-rw-r--r--app/javascript/flavours/glitch/components/dropdown_menu.jsx (renamed from app/javascript/flavours/glitch/components/dropdown_menu.js)36
-rw-r--r--app/javascript/flavours/glitch/components/edited_timestamp/index.jsx (renamed from app/javascript/flavours/glitch/components/edited_timestamp/index.js)8
-rw-r--r--app/javascript/flavours/glitch/components/error_boundary.jsx (renamed from app/javascript/flavours/glitch/components/error_boundary.js)4
-rw-r--r--app/javascript/flavours/glitch/components/gifv.jsx (renamed from app/javascript/flavours/glitch/components/gifv.js)13
-rw-r--r--app/javascript/flavours/glitch/components/hashtag.jsx (renamed from app/javascript/flavours/glitch/components/hashtag.js)0
-rw-r--r--app/javascript/flavours/glitch/components/icon.jsx (renamed from app/javascript/flavours/glitch/components/icon.js)0
-rw-r--r--app/javascript/flavours/glitch/components/icon_button.jsx (renamed from app/javascript/flavours/glitch/components/icon_button.js)10
-rw-r--r--app/javascript/flavours/glitch/components/icon_with_badge.jsx (renamed from app/javascript/flavours/glitch/components/icon_with_badge.js)0
-rw-r--r--app/javascript/flavours/glitch/components/image.jsx (renamed from app/javascript/flavours/glitch/components/image.js)0
-rw-r--r--app/javascript/flavours/glitch/components/inline_account.jsx (renamed from app/javascript/flavours/glitch/components/inline_account.js)3
-rw-r--r--app/javascript/flavours/glitch/components/intersection_observer_article.jsx (renamed from app/javascript/flavours/glitch/components/intersection_observer_article.js)19
-rw-r--r--app/javascript/flavours/glitch/components/link.jsx (renamed from app/javascript/flavours/glitch/components/link.js)0
-rw-r--r--app/javascript/flavours/glitch/components/load_gap.jsx (renamed from app/javascript/flavours/glitch/components/load_gap.js)5
-rw-r--r--app/javascript/flavours/glitch/components/load_more.jsx (renamed from app/javascript/flavours/glitch/components/load_more.js)4
-rw-r--r--app/javascript/flavours/glitch/components/load_pending.jsx (renamed from app/javascript/flavours/glitch/components/load_pending.js)2
-rw-r--r--app/javascript/flavours/glitch/components/loading_indicator.jsx (renamed from app/javascript/flavours/glitch/components/loading_indicator.js)0
-rw-r--r--app/javascript/flavours/glitch/components/logo.jsx (renamed from app/javascript/flavours/glitch/components/logo.js)0
-rw-r--r--app/javascript/flavours/glitch/components/media_attachments.jsx (renamed from app/javascript/flavours/glitch/components/media_attachments.js)12
-rw-r--r--app/javascript/flavours/glitch/components/media_gallery.jsx (renamed from app/javascript/flavours/glitch/components/media_gallery.js)35
-rw-r--r--app/javascript/flavours/glitch/components/missing_indicator.jsx (renamed from app/javascript/flavours/glitch/components/missing_indicator.js)0
-rw-r--r--app/javascript/flavours/glitch/components/modal_root.jsx (renamed from app/javascript/flavours/glitch/components/modal_root.js)9
-rw-r--r--app/javascript/flavours/glitch/components/navigation_portal.jsx (renamed from app/javascript/flavours/glitch/components/navigation_portal.js)3
-rw-r--r--app/javascript/flavours/glitch/components/not_signed_in_indicator.jsx (renamed from app/javascript/flavours/glitch/components/not_signed_in_indicator.js)0
-rw-r--r--app/javascript/flavours/glitch/components/notification_purge_buttons.jsx (renamed from app/javascript/flavours/glitch/components/notification_purge_buttons.js)3
-rw-r--r--app/javascript/flavours/glitch/components/permalink.jsx (renamed from app/javascript/flavours/glitch/components/permalink.js)4
-rw-r--r--app/javascript/flavours/glitch/components/picture_in_picture_placeholder.jsx (renamed from app/javascript/flavours/glitch/components/picture_in_picture_placeholder.js)9
-rw-r--r--app/javascript/flavours/glitch/components/poll.jsx (renamed from app/javascript/flavours/glitch/components/poll.js)14
-rw-r--r--app/javascript/flavours/glitch/components/radio_button.jsx (renamed from app/javascript/flavours/glitch/components/radio_button.js)0
-rw-r--r--app/javascript/flavours/glitch/components/regeneration_indicator.jsx (renamed from app/javascript/flavours/glitch/components/regeneration_indicator.js)0
-rw-r--r--app/javascript/flavours/glitch/components/relative_timestamp.jsx (renamed from app/javascript/flavours/glitch/components/relative_timestamp.js)3
-rw-r--r--app/javascript/flavours/glitch/components/scrollable_list.jsx (renamed from app/javascript/flavours/glitch/components/scrollable_list.js)25
-rw-r--r--app/javascript/flavours/glitch/components/server_banner.jsx (renamed from app/javascript/flavours/glitch/components/server_banner.js)4
-rw-r--r--app/javascript/flavours/glitch/components/setting_text.jsx (renamed from app/javascript/flavours/glitch/components/setting_text.js)2
-rw-r--r--app/javascript/flavours/glitch/components/short_number.jsx (renamed from app/javascript/flavours/glitch/components/short_number.js)0
-rw-r--r--app/javascript/flavours/glitch/components/skeleton.jsx (renamed from app/javascript/flavours/glitch/components/skeleton.js)0
-rw-r--r--app/javascript/flavours/glitch/components/spoilers.jsx (renamed from app/javascript/flavours/glitch/components/spoilers.js)32
-rw-r--r--app/javascript/flavours/glitch/components/status.jsx (renamed from app/javascript/flavours/glitch/components/status.js)87
-rw-r--r--app/javascript/flavours/glitch/components/status_action_bar.jsx (renamed from app/javascript/flavours/glitch/components/status_action_bar.js)47
-rw-r--r--app/javascript/flavours/glitch/components/status_content.jsx (renamed from app/javascript/flavours/glitch/components/status_content.js)51
-rw-r--r--app/javascript/flavours/glitch/components/status_header.jsx (renamed from app/javascript/flavours/glitch/components/status_header.js)4
-rw-r--r--app/javascript/flavours/glitch/components/status_icons.jsx (renamed from app/javascript/flavours/glitch/components/status_icons.js)25
-rw-r--r--app/javascript/flavours/glitch/components/status_list.jsx (renamed from app/javascript/flavours/glitch/components/status_list.js)12
-rw-r--r--app/javascript/flavours/glitch/components/status_prepend.jsx (renamed from app/javascript/flavours/glitch/components/status_prepend.js)6
-rw-r--r--app/javascript/flavours/glitch/components/status_visibility_icon.jsx (renamed from app/javascript/flavours/glitch/components/status_visibility_icon.js)3
-rw-r--r--app/javascript/flavours/glitch/components/timeline_hint.jsx (renamed from app/javascript/flavours/glitch/components/timeline_hint.js)0
71 files changed, 341 insertions, 280 deletions
diff --git a/app/javascript/flavours/glitch/components/account.js b/app/javascript/flavours/glitch/components/account.jsx
index 8e810ce5f..7b66d5a6e 100644
--- a/app/javascript/flavours/glitch/components/account.js
+++ b/app/javascript/flavours/glitch/components/account.jsx
@@ -23,7 +23,6 @@ const messages = defineMessages({
   block: { id: 'account.block', defaultMessage: 'Block @{name}' },
 });
 
-export default @injectIntl
 class Account extends ImmutablePureComponent {
 
   static propTypes = {
@@ -48,27 +47,27 @@ class Account extends ImmutablePureComponent {
 
   handleFollow = () => {
     this.props.onFollow(this.props.account);
-  }
+  };
 
   handleBlock = () => {
     this.props.onBlock(this.props.account);
-  }
+  };
 
   handleMute = () => {
     this.props.onMute(this.props.account);
-  }
+  };
 
   handleMuteNotifications = () => {
     this.props.onMuteNotifications(this.props.account, true);
-  }
+  };
 
   handleUnmuteNotifications = () => {
     this.props.onMuteNotifications(this.props.account, false);
-  }
+  };
 
   handleAction = () => {
     this.props.onActionClick(this.props.account);
-  }
+  };
 
   render () {
     const {
@@ -184,3 +183,5 @@ class Account extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(Account);
diff --git a/app/javascript/flavours/glitch/components/admin/Counter.js b/app/javascript/flavours/glitch/components/admin/Counter.jsx
index 5b6a19f8d..5b6a19f8d 100644
--- a/app/javascript/flavours/glitch/components/admin/Counter.js
+++ b/app/javascript/flavours/glitch/components/admin/Counter.jsx
diff --git a/app/javascript/flavours/glitch/components/admin/Dimension.js b/app/javascript/flavours/glitch/components/admin/Dimension.jsx
index 3dac8c6c2..3dac8c6c2 100644
--- a/app/javascript/flavours/glitch/components/admin/Dimension.js
+++ b/app/javascript/flavours/glitch/components/admin/Dimension.jsx
diff --git a/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js b/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.jsx
index 771dbb452..ecefe7a84 100644
--- a/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js
+++ b/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.jsx
@@ -33,7 +33,7 @@ class Category extends React.PureComponent {
     const { id, text, disabled, selected, children } = this.props;
 
     return (
-      <div tabIndex='0' role='button' className={classNames('report-reason-selector__category', { selected, disabled })} onClick={this.handleClick}>
+      <div tabIndex={0} role='button' className={classNames('report-reason-selector__category', { selected, disabled })} onClick={this.handleClick}>
         {selected && <input type='hidden' name='report[category]' value={id} />}
 
         <div className='report-reason-selector__category__label'>
@@ -74,7 +74,7 @@ class Rule extends React.PureComponent {
     const { id, text, disabled, selected } = this.props;
 
     return (
-      <div tabIndex='0' role='button' className={classNames('report-reason-selector__rule', { selected, disabled })} onClick={this.handleClick}>
+      <div tabIndex={0} role='button' className={classNames('report-reason-selector__rule', { selected, disabled })} onClick={this.handleClick}>
         <span className={classNames('poll__input', { checkbox: true, active: selected, disabled })} />
         {selected && <input type='hidden' name='report[rule_ids][]' value={id} />}
         {text}
@@ -84,7 +84,6 @@ class Rule extends React.PureComponent {
 
 }
 
-export default @injectIntl
 class ReportReasonSelector extends React.PureComponent {
 
   static propTypes = {
@@ -157,3 +156,5 @@ class ReportReasonSelector extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ReportReasonSelector);
diff --git a/app/javascript/flavours/glitch/components/admin/Retention.js b/app/javascript/flavours/glitch/components/admin/Retention.jsx
index 9cc39040b..e1ba3f6c9 100644
--- a/app/javascript/flavours/glitch/components/admin/Retention.js
+++ b/app/javascript/flavours/glitch/components/admin/Retention.jsx
@@ -137,7 +137,7 @@ export default class Retention extends React.PureComponent {
       break;
     default:
       title = <FormattedMessage id='admin.dashboard.monthly_retention' defaultMessage='User retention rate by month after sign-up' />;
-    };
+    }
 
     return (
       <div className='retention'>
diff --git a/app/javascript/flavours/glitch/components/admin/Trends.js b/app/javascript/flavours/glitch/components/admin/Trends.jsx
index 4c17b69a0..774bf36e6 100644
--- a/app/javascript/flavours/glitch/components/admin/Trends.js
+++ b/app/javascript/flavours/glitch/components/admin/Trends.jsx
@@ -50,7 +50,7 @@ export default class Trends extends React.PureComponent {
             <Hashtag
               key={hashtag.name}
               name={hashtag.name}
-              href={`/admin/tags/${hashtag.id}`}
+              href={hashtag.id === undefined ? undefined : `/admin/tags/${hashtag.id}`}
               people={hashtag.history[0].accounts * 1 + hashtag.history[1].accounts * 1}
               uses={hashtag.history[0].uses * 1 + hashtag.history[1].uses * 1}
               history={hashtag.history.reverse().map(day => day.uses)}
diff --git a/app/javascript/flavours/glitch/components/animated_number.js b/app/javascript/flavours/glitch/components/animated_number.jsx
index 9431c96f7..dd21d97f0 100644
--- a/app/javascript/flavours/glitch/components/animated_number.js
+++ b/app/javascript/flavours/glitch/components/animated_number.jsx
@@ -38,13 +38,13 @@ export default class AnimatedNumber extends React.PureComponent {
     const { direction } = this.state;
 
     return { y: -1 * direction };
-  }
+  };
 
   willLeave = () => {
     const { direction } = this.state;
 
     return { y: spring(1 * direction, { damping: 35, stiffness: 400 }) };
-  }
+  };
 
   render () {
     const { value, obfuscate } = this.props;
diff --git a/app/javascript/flavours/glitch/components/attachment_list.js b/app/javascript/flavours/glitch/components/attachment_list.jsx
index 68b80b19f..68b80b19f 100644
--- a/app/javascript/flavours/glitch/components/attachment_list.js
+++ b/app/javascript/flavours/glitch/components/attachment_list.jsx
diff --git a/app/javascript/flavours/glitch/components/autosuggest_emoji.js b/app/javascript/flavours/glitch/components/autosuggest_emoji.jsx
index 83fafbd10..83fafbd10 100644
--- a/app/javascript/flavours/glitch/components/autosuggest_emoji.js
+++ b/app/javascript/flavours/glitch/components/autosuggest_emoji.jsx
diff --git a/app/javascript/flavours/glitch/components/autosuggest_hashtag.js b/app/javascript/flavours/glitch/components/autosuggest_hashtag.jsx
index d787ed07a..d787ed07a 100644
--- a/app/javascript/flavours/glitch/components/autosuggest_hashtag.js
+++ b/app/javascript/flavours/glitch/components/autosuggest_hashtag.jsx
diff --git a/app/javascript/flavours/glitch/components/autosuggest_input.js b/app/javascript/flavours/glitch/components/autosuggest_input.jsx
index b40a2ff35..ea9fd0828 100644
--- a/app/javascript/flavours/glitch/components/autosuggest_input.js
+++ b/app/javascript/flavours/glitch/components/autosuggest_input.jsx
@@ -50,6 +50,8 @@ export default class AutosuggestInput extends ImmutablePureComponent {
     id: PropTypes.string,
     searchTokens: PropTypes.arrayOf(PropTypes.string),
     maxLength: PropTypes.number,
+    lang: PropTypes.string,
+    spellCheck: PropTypes.bool,
   };
 
   static defaultProps = {
@@ -77,7 +79,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
     }
 
     this.props.onChange(e);
-  }
+  };
 
   onKeyDown = (e) => {
     const { suggestions, disabled } = this.props;
@@ -135,22 +137,22 @@ export default class AutosuggestInput extends ImmutablePureComponent {
     }
 
     this.props.onKeyDown(e);
-  }
+  };
 
   onBlur = () => {
     this.setState({ suggestionsHidden: true, focused: false });
-  }
+  };
 
   onFocus = () => {
     this.setState({ focused: true });
-  }
+  };
 
   onSuggestionClick = (e) => {
     const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
     e.preventDefault();
     this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
     this.input.focus();
-  }
+  };
 
   componentWillReceiveProps (nextProps) {
     if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
@@ -160,7 +162,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
 
   setInput = (c) => {
     this.input = c;
-  }
+  };
 
   renderSuggestion = (suggestion, i) => {
     const { selectedSuggestion } = this.state;
@@ -178,14 +180,14 @@ export default class AutosuggestInput extends ImmutablePureComponent {
     }
 
     return (
-      <div role='button' tabIndex='0' key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
+      <div role='button' tabIndex={0} key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
         {inner}
       </div>
     );
-  }
+  };
 
   render () {
-    const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength } = this.props;
+    const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength, lang, spellCheck } = this.props;
     const { suggestionsHidden } = this.state;
 
     return (
@@ -210,6 +212,8 @@ export default class AutosuggestInput extends ImmutablePureComponent {
             id={id}
             className={className}
             maxLength={maxLength}
+            lang={lang}
+            spellCheck={spellCheck}
           />
         </label>
 
diff --git a/app/javascript/flavours/glitch/components/autosuggest_textarea.js b/app/javascript/flavours/glitch/components/autosuggest_textarea.jsx
index 967c593af..a016e44b7 100644
--- a/app/javascript/flavours/glitch/components/autosuggest_textarea.js
+++ b/app/javascript/flavours/glitch/components/autosuggest_textarea.jsx
@@ -48,6 +48,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
     onKeyDown: PropTypes.func,
     onPaste: PropTypes.func.isRequired,
     autoFocus: PropTypes.bool,
+    lang: PropTypes.string,
   };
 
   static defaultProps = {
@@ -74,7 +75,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
     }
 
     this.props.onChange(e);
-  }
+  };
 
   onKeyDown = (e) => {
     const { suggestions, disabled } = this.props;
@@ -132,25 +133,25 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
     }
 
     this.props.onKeyDown(e);
-  }
+  };
 
   onBlur = () => {
     this.setState({ suggestionsHidden: true, focused: false });
-  }
+  };
 
   onFocus = (e) => {
     this.setState({ focused: true });
     if (this.props.onFocus) {
       this.props.onFocus(e);
     }
-  }
+  };
 
   onSuggestionClick = (e) => {
     const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
     e.preventDefault();
     this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
     this.textarea.focus();
-  }
+  };
 
   componentWillReceiveProps (nextProps) {
     if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
@@ -160,14 +161,14 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
 
   setTextarea = (c) => {
     this.textarea = c;
-  }
+  };
 
   onPaste = (e) => {
     if (e.clipboardData && e.clipboardData.files.length === 1) {
       this.props.onPaste(e.clipboardData.files);
       e.preventDefault();
     }
-  }
+  };
 
   renderSuggestion = (suggestion, i) => {
     const { selectedSuggestion } = this.state;
@@ -185,14 +186,14 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
     }
 
     return (
-      <div role='button' tabIndex='0' key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
+      <div role='button' tabIndex={0} key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
         {inner}
       </div>
     );
-  }
+  };
 
   render () {
-    const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children } = this.props;
+    const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, lang, children } = this.props;
     const { suggestionsHidden } = this.state;
 
     return [
@@ -216,6 +217,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
               onPaste={this.onPaste}
               dir='auto'
               aria-autocomplete='list'
+              lang={lang}
             />
           </label>
         </div>
diff --git a/app/javascript/flavours/glitch/components/avatar.js b/app/javascript/flavours/glitch/components/avatar.jsx
index 38fd99af5..f30b33e70 100644
--- a/app/javascript/flavours/glitch/components/avatar.js
+++ b/app/javascript/flavours/glitch/components/avatar.jsx
@@ -28,12 +28,12 @@ export default class Avatar extends React.PureComponent {
   handleMouseEnter = () => {
     if (this.props.animate) return;
     this.setState({ hovering: true });
-  }
+  };
 
   handleMouseLeave = () => {
     if (this.props.animate) return;
     this.setState({ hovering: false });
-  }
+  };
 
   render () {
     const {
diff --git a/app/javascript/flavours/glitch/components/avatar_composite.js b/app/javascript/flavours/glitch/components/avatar_composite.jsx
index c0ce7761d..c0ce7761d 100644
--- a/app/javascript/flavours/glitch/components/avatar_composite.js
+++ b/app/javascript/flavours/glitch/components/avatar_composite.jsx
diff --git a/app/javascript/flavours/glitch/components/avatar_overlay.js b/app/javascript/flavours/glitch/components/avatar_overlay.jsx
index 01dec587a..01dec587a 100644
--- a/app/javascript/flavours/glitch/components/avatar_overlay.js
+++ b/app/javascript/flavours/glitch/components/avatar_overlay.jsx
diff --git a/app/javascript/flavours/glitch/components/blurhash.js b/app/javascript/flavours/glitch/components/blurhash.jsx
index 2af5cfc56..2af5cfc56 100644
--- a/app/javascript/flavours/glitch/components/blurhash.js
+++ b/app/javascript/flavours/glitch/components/blurhash.jsx
diff --git a/app/javascript/flavours/glitch/components/button.js b/app/javascript/flavours/glitch/components/button.jsx
index b1815c3e1..40b8f5a15 100644
--- a/app/javascript/flavours/glitch/components/button.js
+++ b/app/javascript/flavours/glitch/components/button.jsx
@@ -19,11 +19,11 @@ export default class Button extends React.PureComponent {
     if (!this.props.disabled) {
       this.props.onClick(e);
     }
-  }
+  };
 
   setRef = (c) => {
     this.node = c;
-  }
+  };
 
   focus() {
     this.node.focus();
diff --git a/app/javascript/flavours/glitch/components/check.js b/app/javascript/flavours/glitch/components/check.jsx
index ee2ef1595..ee2ef1595 100644
--- a/app/javascript/flavours/glitch/components/check.js
+++ b/app/javascript/flavours/glitch/components/check.jsx
diff --git a/app/javascript/flavours/glitch/components/column.js b/app/javascript/flavours/glitch/components/column.jsx
index cf0e6d5e4..47293ef18 100644
--- a/app/javascript/flavours/glitch/components/column.js
+++ b/app/javascript/flavours/glitch/components/column.jsx
@@ -29,11 +29,11 @@ export default class Column extends React.PureComponent {
     }
 
     this._interruptScrollAnimation();
-  }
+  };
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   componentDidMount () {
     if (this.props.bindToDocument) {
diff --git a/app/javascript/flavours/glitch/components/column_back_button.js b/app/javascript/flavours/glitch/components/column_back_button.jsx
index 05688f867..e9e2615cb 100644
--- a/app/javascript/flavours/glitch/components/column_back_button.js
+++ b/app/javascript/flavours/glitch/components/column_back_button.jsx
@@ -26,7 +26,7 @@ export default class ColumnBackButton extends React.PureComponent {
     } else {
       this.context.router.history.push('/');
     }
-  }
+  };
 
   render () {
     const { multiColumn } = this.props;
diff --git a/app/javascript/flavours/glitch/components/column_back_button_slim.js b/app/javascript/flavours/glitch/components/column_back_button_slim.jsx
index faa0c23a8..4df045b5f 100644
--- a/app/javascript/flavours/glitch/components/column_back_button_slim.js
+++ b/app/javascript/flavours/glitch/components/column_back_button_slim.jsx
@@ -21,12 +21,12 @@ export default class ColumnBackButtonSlim extends React.PureComponent {
     } else {
       this.context.router.history.push('/');
     }
-  }
+  };
 
   render () {
     return (
       <div className='column-back-button--slim'>
-        <div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button column-back-button--slim-button'>
+        <div role='button' tabIndex={0} onClick={this.handleClick} className='column-back-button column-back-button--slim-button'>
           <Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
           <FormattedMessage id='column_back_button.label' defaultMessage='Back' />
         </div>
diff --git a/app/javascript/flavours/glitch/components/column_header.js b/app/javascript/flavours/glitch/components/column_header.jsx
index 0f89b3a97..6fbe2955d 100644
--- a/app/javascript/flavours/glitch/components/column_header.js
+++ b/app/javascript/flavours/glitch/components/column_header.jsx
@@ -12,7 +12,6 @@ const messages = defineMessages({
   moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' },
 });
 
-export default @injectIntl
 class ColumnHeader extends React.PureComponent {
 
   static contextTypes = {
@@ -55,39 +54,39 @@ class ColumnHeader extends React.PureComponent {
     } else {
       this.context.router.history.push('/');
     }
-  }
+  };
 
   handleToggleClick = (e) => {
     e.stopPropagation();
     this.setState({ collapsed: !this.state.collapsed, animating: true });
-  }
+  };
 
   handleTitleClick = () => {
     this.props.onClick?.();
-  }
+  };
 
   handleMoveLeft = () => {
     this.props.onMove(-1);
-  }
+  };
 
   handleMoveRight = () => {
     this.props.onMove(1);
-  }
+  };
 
   handleBackClick = (event) => {
     this.historyBack(event.shiftKey);
-  }
+  };
 
   handleTransitionEnd = () => {
     this.setState({ animating: false });
-  }
+  };
 
   handlePin = () => {
     if (!this.props.pinned) {
       this.historyBack();
     }
     this.props.onPin();
-  }
+  };
 
   render () {
     const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
@@ -218,3 +217,5 @@ class ColumnHeader extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(ColumnHeader);
diff --git a/app/javascript/flavours/glitch/components/common_counter.js b/app/javascript/flavours/glitch/components/common_counter.jsx
index dd9b62de9..dd9b62de9 100644
--- a/app/javascript/flavours/glitch/components/common_counter.js
+++ b/app/javascript/flavours/glitch/components/common_counter.jsx
diff --git a/app/javascript/flavours/glitch/components/dismissable_banner.js b/app/javascript/flavours/glitch/components/dismissable_banner.jsx
index ff52a619d..9b3faf6f2 100644
--- a/app/javascript/flavours/glitch/components/dismissable_banner.js
+++ b/app/javascript/flavours/glitch/components/dismissable_banner.jsx
@@ -8,7 +8,6 @@ const messages = defineMessages({
   dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' },
 });
 
-export default @injectIntl
 class DismissableBanner extends React.PureComponent {
 
   static propTypes = {
@@ -24,7 +23,7 @@ class DismissableBanner extends React.PureComponent {
   handleDismiss = () => {
     const { id } = this.props;
     this.setState({ visible: false }, () => bannerSettings.set(id, true));
-  }
+  };
 
   render () {
     const { visible } = this.state;
@@ -49,3 +48,5 @@ class DismissableBanner extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(DismissableBanner);
diff --git a/app/javascript/flavours/glitch/components/display_name.js b/app/javascript/flavours/glitch/components/display_name.jsx
index 1c2297578..19f63ec60 100644
--- a/app/javascript/flavours/glitch/components/display_name.js
+++ b/app/javascript/flavours/glitch/components/display_name.jsx
@@ -27,7 +27,7 @@ export default class DisplayName extends React.PureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -40,7 +40,7 @@ export default class DisplayName extends React.PureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   render() {
     const { account, className, inline, localDomain, others, onAccountClick } = this.props;
@@ -74,7 +74,7 @@ export default class DisplayName extends React.PureComponent {
       )).reduce((prev, cur) => [prev, ', ', cur]);
 
       if (others.size - 2 > 0) {
-       displayName.push(` +${others.size - 2}`);
+        displayName.push(` +${others.size - 2}`);
       }
 
       suffix = (
diff --git a/app/javascript/flavours/glitch/components/domain.js b/app/javascript/flavours/glitch/components/domain.jsx
index 697065d87..85ebdbde9 100644
--- a/app/javascript/flavours/glitch/components/domain.js
+++ b/app/javascript/flavours/glitch/components/domain.jsx
@@ -8,7 +8,6 @@ const messages = defineMessages({
   unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
 });
 
-export default @injectIntl
 class Account extends ImmutablePureComponent {
 
   static propTypes = {
@@ -19,7 +18,7 @@ class Account extends ImmutablePureComponent {
 
   handleDomainUnblock = () => {
     this.props.onUnblockDomain(this.props.domain);
-  }
+  };
 
   render () {
     const { domain, intl } = this.props;
@@ -40,3 +39,5 @@ class Account extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(Account);
diff --git a/app/javascript/flavours/glitch/components/dropdown_menu.js b/app/javascript/flavours/glitch/components/dropdown_menu.jsx
index 7c70f750f..7fb75b59e 100644
--- a/app/javascript/flavours/glitch/components/dropdown_menu.js
+++ b/app/javascript/flavours/glitch/components/dropdown_menu.jsx
@@ -36,7 +36,7 @@ class DropdownMenu extends React.PureComponent {
     if (this.node && !this.node.contains(e.target)) {
       this.props.onClose();
     }
-  }
+  };
 
   componentDidMount () {
     document.addEventListener('click', this.handleDocumentClick, false);
@@ -56,11 +56,11 @@ class DropdownMenu extends React.PureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   setFocusRef = c => {
     this.focusedItem = c;
-  }
+  };
 
   handleKeyDown = e => {
     const items = Array.from(this.node.querySelectorAll('a, button'));
@@ -97,18 +97,18 @@ class DropdownMenu extends React.PureComponent {
       e.preventDefault();
       e.stopPropagation();
     }
-  }
+  };
 
   handleItemKeyPress = e => {
     if (e.key === 'Enter' || e.key === ' ') {
       this.handleClick(e);
     }
-  }
+  };
 
   handleClick = e => {
     const { onItemClick } = this.props;
     onItemClick(e);
-  }
+  };
 
   renderItem = (option, i) => {
     if (option === null) {
@@ -119,12 +119,12 @@ class DropdownMenu extends React.PureComponent {
 
     return (
       <li className='dropdown-menu__item' key={`${text}-${i}`}>
-        <a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
+        <a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex={0} ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
           {text}
         </a>
       </li>
     );
-  }
+  };
 
   render () {
     const { items, scrollable, renderHeader, loading } = this.props;
@@ -194,7 +194,7 @@ export default class Dropdown extends React.PureComponent {
     } else {
       this.props.onOpen(this.state.id, this.handleItemClick, type !== 'click');
     }
-  }
+  };
 
   handleClose = () => {
     if (this.activeElement) {
@@ -202,13 +202,13 @@ export default class Dropdown extends React.PureComponent {
       this.activeElement = null;
     }
     this.props.onClose(this.state.id);
-  }
+  };
 
   handleMouseDown = () => {
     if (!this.state.open) {
       this.activeElement = document.activeElement;
     }
-  }
+  };
 
   handleButtonKeyDown = (e) => {
     switch(e.key) {
@@ -217,7 +217,7 @@ export default class Dropdown extends React.PureComponent {
       this.handleMouseDown();
       break;
     }
-  }
+  };
 
   handleKeyPress = (e) => {
     switch(e.key) {
@@ -228,7 +228,7 @@ export default class Dropdown extends React.PureComponent {
       e.preventDefault();
       break;
     }
-  }
+  };
 
   handleItemClick = e => {
     const { onItemClick } = this.props;
@@ -247,25 +247,25 @@ export default class Dropdown extends React.PureComponent {
       e.preventDefault();
       this.context.router.history.push(item.to);
     }
-  }
+  };
 
   setTargetRef = c => {
     this.target = c;
-  }
+  };
 
   findTarget = () => {
     return this.target;
-  }
+  };
 
   componentWillUnmount = () => {
     if (this.state.id === this.props.openDropdownId) {
       this.handleClose();
     }
-  }
+  };
 
   close = () => {
     this.handleClose();
-  }
+  };
 
   render () {
     const {
diff --git a/app/javascript/flavours/glitch/components/edited_timestamp/index.js b/app/javascript/flavours/glitch/components/edited_timestamp/index.jsx
index 9648133af..6d73fa68c 100644
--- a/app/javascript/flavours/glitch/components/edited_timestamp/index.js
+++ b/app/javascript/flavours/glitch/components/edited_timestamp/index.jsx
@@ -16,8 +16,6 @@ const mapDispatchToProps = (dispatch, { statusId }) => ({
 
 });
 
-export default @connect(null, mapDispatchToProps)
-@injectIntl
 class EditedTimestamp extends React.PureComponent {
 
   static propTypes = {
@@ -36,7 +34,7 @@ class EditedTimestamp extends React.PureComponent {
     return (
       <FormattedMessage id='status.edited_x_times' defaultMessage='Edited {count, plural, one {{count} time} other {{count} times}}' values={{ count: items.size - 1 }} />
     );
-  }
+  };
 
   renderItem = (item, index, { onClick, onKeyPress }) => {
     const formattedDate = <RelativeTimestamp timestamp={item.get('created_at')} short={false} />;
@@ -53,7 +51,7 @@ class EditedTimestamp extends React.PureComponent {
         <button data-index={index} onClick={onClick} onKeyPress={onKeyPress}>{label}</button>
       </li>
     );
-  }
+  };
 
   render () {
     const { timestamp, intl, statusId } = this.props;
@@ -68,3 +66,5 @@ class EditedTimestamp extends React.PureComponent {
   }
 
 }
+
+export default connect(null, mapDispatchToProps)(injectIntl(EditedTimestamp));
diff --git a/app/javascript/flavours/glitch/components/error_boundary.js b/app/javascript/flavours/glitch/components/error_boundary.jsx
index e0ca3e2b0..234a53417 100644
--- a/app/javascript/flavours/glitch/components/error_boundary.js
+++ b/app/javascript/flavours/glitch/components/error_boundary.jsx
@@ -18,7 +18,7 @@ export default class ErrorBoundary extends React.PureComponent {
     stackTrace: undefined,
     mappedStackTrace: undefined,
     componentStack: undefined,
-  }
+  };
 
   componentDidCatch(error, info) {
     this.setState({
@@ -72,7 +72,7 @@ export default class ErrorBoundary extends React.PureComponent {
     }
 
     return (
-      <div tabIndex='-1'>
+      <div tabIndex={-1}>
         <div className='error-boundary'>
           <h1><FormattedMessage id='web_app_crash.title' defaultMessage="We're sorry, but something went wrong with the Mastodon app." /></h1>
           <p>
diff --git a/app/javascript/flavours/glitch/components/gifv.js b/app/javascript/flavours/glitch/components/gifv.jsx
index b775e5200..1ce7e7c29 100644
--- a/app/javascript/flavours/glitch/components/gifv.js
+++ b/app/javascript/flavours/glitch/components/gifv.jsx
@@ -6,6 +6,7 @@ export default class GIFV extends React.PureComponent {
   static propTypes = {
     src: PropTypes.string.isRequired,
     alt: PropTypes.string,
+    lang: PropTypes.string,
     width: PropTypes.number,
     height: PropTypes.number,
     onClick: PropTypes.func,
@@ -17,7 +18,7 @@ export default class GIFV extends React.PureComponent {
 
   handleLoadedData = () => {
     this.setState({ loading: false });
-  }
+  };
 
   componentWillReceiveProps (nextProps) {
     if (nextProps.src !== this.props.src) {
@@ -32,10 +33,10 @@ export default class GIFV extends React.PureComponent {
       e.stopPropagation();
       onClick();
     }
-  }
+  };
 
   render () {
-    const { src, width, height, alt } = this.props;
+    const { src, width, height, alt, lang } = this.props;
     const { loading } = this.state;
 
     return (
@@ -45,9 +46,10 @@ export default class GIFV extends React.PureComponent {
             width={width}
             height={height}
             role='button'
-            tabIndex='0'
+            tabIndex={0}
             aria-label={alt}
             title={alt}
+            lang={lang}
             onClick={this.handleClick}
           />
         )}
@@ -55,9 +57,10 @@ export default class GIFV extends React.PureComponent {
         <video
           src={src}
           role='button'
-          tabIndex='0'
+          tabIndex={0}
           aria-label={alt}
           title={alt}
+          lang={lang}
           muted
           loop
           autoPlay
diff --git a/app/javascript/flavours/glitch/components/hashtag.js b/app/javascript/flavours/glitch/components/hashtag.jsx
index 422b9a8fa..422b9a8fa 100644
--- a/app/javascript/flavours/glitch/components/hashtag.js
+++ b/app/javascript/flavours/glitch/components/hashtag.jsx
diff --git a/app/javascript/flavours/glitch/components/icon.js b/app/javascript/flavours/glitch/components/icon.jsx
index d8a17722f..d8a17722f 100644
--- a/app/javascript/flavours/glitch/components/icon.js
+++ b/app/javascript/flavours/glitch/components/icon.jsx
diff --git a/app/javascript/flavours/glitch/components/icon_button.js b/app/javascript/flavours/glitch/components/icon_button.jsx
index 2485f0f48..10d7926be 100644
--- a/app/javascript/flavours/glitch/components/icon_button.js
+++ b/app/javascript/flavours/glitch/components/icon_button.jsx
@@ -46,7 +46,7 @@ export default class IconButton extends React.PureComponent {
   state = {
     activate: false,
     deactivate: false,
-  }
+  };
 
   componentWillReceiveProps (nextProps) {
     if (!nextProps.animate) return;
@@ -64,25 +64,25 @@ export default class IconButton extends React.PureComponent {
     if (!this.props.disabled) {
       this.props.onClick(e);
     }
-  }
+  };
 
   handleKeyPress = (e) => {
     if (this.props.onKeyPress && !this.props.disabled) {
       this.props.onKeyPress(e);
     }
-  }
+  };
 
   handleMouseDown = (e) => {
     if (!this.props.disabled && this.props.onMouseDown) {
       this.props.onMouseDown(e);
     }
-  }
+  };
 
   handleKeyDown = (e) => {
     if (!this.props.disabled && this.props.onKeyDown) {
       this.props.onKeyDown(e);
     }
-  }
+  };
 
   render () {
     // Hack required for some icons which have an overriden size
diff --git a/app/javascript/flavours/glitch/components/icon_with_badge.js b/app/javascript/flavours/glitch/components/icon_with_badge.jsx
index a42ba4589..a42ba4589 100644
--- a/app/javascript/flavours/glitch/components/icon_with_badge.js
+++ b/app/javascript/flavours/glitch/components/icon_with_badge.jsx
diff --git a/app/javascript/flavours/glitch/components/image.js b/app/javascript/flavours/glitch/components/image.jsx
index 6e81ddf08..6e81ddf08 100644
--- a/app/javascript/flavours/glitch/components/image.js
+++ b/app/javascript/flavours/glitch/components/image.jsx
diff --git a/app/javascript/flavours/glitch/components/inline_account.js b/app/javascript/flavours/glitch/components/inline_account.jsx
index 2ef1f52cc..c04618d66 100644
--- a/app/javascript/flavours/glitch/components/inline_account.js
+++ b/app/javascript/flavours/glitch/components/inline_account.jsx
@@ -14,7 +14,6 @@ const makeMapStateToProps = () => {
   return mapStateToProps;
 };
 
-export default @connect(makeMapStateToProps)
 class InlineAccount extends React.PureComponent {
 
   static propTypes = {
@@ -32,3 +31,5 @@ class InlineAccount extends React.PureComponent {
   }
 
 }
+
+export default connect(makeMapStateToProps)(InlineAccount);
diff --git a/app/javascript/flavours/glitch/components/intersection_observer_article.js b/app/javascript/flavours/glitch/components/intersection_observer_article.jsx
index b28e44e4c..6c00e557d 100644
--- a/app/javascript/flavours/glitch/components/intersection_observer_article.js
+++ b/app/javascript/flavours/glitch/components/intersection_observer_article.jsx
@@ -21,7 +21,7 @@ export default class IntersectionObserverArticle extends React.Component {
 
   state = {
     isHidden: false, // set to true in requestIdleCallback to trigger un-render
-  }
+  };
 
   shouldComponentUpdate (nextProps, nextState) {
     const isUnrendered = !this.state.isIntersecting && (this.state.isHidden || this.props.cachedHeight);
@@ -63,7 +63,7 @@ export default class IntersectionObserverArticle extends React.Component {
 
     scheduleIdleTask(this.calculateHeight);
     this.setState(this.updateStateAfterIntersection);
-  }
+  };
 
   updateStateAfterIntersection = (prevState) => {
     if (prevState.isIntersecting !== false && !this.entry.isIntersecting) {
@@ -73,7 +73,7 @@ export default class IntersectionObserverArticle extends React.Component {
       isIntersecting: this.entry.isIntersecting,
       isHidden: false,
     };
-  }
+  };
 
   calculateHeight = () => {
     const { onHeightChange, saveHeightKey, id } = this.props;
@@ -84,7 +84,7 @@ export default class IntersectionObserverArticle extends React.Component {
     if (onHeightChange && saveHeightKey) {
       onHeightChange(saveHeightKey, id, this.height);
     }
-  }
+  };
 
   hideIfNotIntersecting = () => {
     if (!this.componentMounted) {
@@ -96,11 +96,11 @@ export default class IntersectionObserverArticle extends React.Component {
     // this is to save DOM nodes and avoid using up too much memory.
     // See: https://github.com/mastodon/mastodon/issues/2900
     this.setState((prevState) => ({ isHidden: !prevState.isIntersecting }));
-  }
+  };
 
   handleRef = (node) => {
     this.node = node;
-  }
+  };
 
   render () {
     const { children, id, index, listLength, cachedHeight } = this.props;
@@ -120,9 +120,10 @@ export default class IntersectionObserverArticle extends React.Component {
         aria-posinset={index + 1}
         aria-setsize={listLength}
         data-id={id}
-        tabIndex='0'
-        style={style}>
-          {children && React.cloneElement(children, { hidden: !isIntersecting && (isHidden || !!cachedHeight) })}
+        tabIndex={0}
+        style={style}
+      >
+        {children && React.cloneElement(children, { hidden: !isIntersecting && (isHidden || !!cachedHeight) })}
       </article>
     );
   }
diff --git a/app/javascript/flavours/glitch/components/link.js b/app/javascript/flavours/glitch/components/link.jsx
index bbec121a8..bbec121a8 100644
--- a/app/javascript/flavours/glitch/components/link.js
+++ b/app/javascript/flavours/glitch/components/link.jsx
diff --git a/app/javascript/flavours/glitch/components/load_gap.js b/app/javascript/flavours/glitch/components/load_gap.jsx
index fe3f60a58..e70365d9e 100644
--- a/app/javascript/flavours/glitch/components/load_gap.js
+++ b/app/javascript/flavours/glitch/components/load_gap.jsx
@@ -7,7 +7,6 @@ const messages = defineMessages({
   load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
 });
 
-export default @injectIntl
 class LoadGap extends React.PureComponent {
 
   static propTypes = {
@@ -19,7 +18,7 @@ class LoadGap extends React.PureComponent {
 
   handleClick = () => {
     this.props.onClick(this.props.maxId);
-  }
+  };
 
   render () {
     const { disabled, intl } = this.props;
@@ -32,3 +31,5 @@ class LoadGap extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(LoadGap);
diff --git a/app/javascript/flavours/glitch/components/load_more.js b/app/javascript/flavours/glitch/components/load_more.jsx
index 389c3e1e1..ab9428e35 100644
--- a/app/javascript/flavours/glitch/components/load_more.js
+++ b/app/javascript/flavours/glitch/components/load_more.jsx
@@ -8,11 +8,11 @@ export default class LoadMore extends React.PureComponent {
     onClick: PropTypes.func,
     disabled: PropTypes.bool,
     visible: PropTypes.bool,
-  }
+  };
 
   static defaultProps = {
     visible: true,
-  }
+  };
 
   render() {
     const { disabled, visible } = this.props;
diff --git a/app/javascript/flavours/glitch/components/load_pending.js b/app/javascript/flavours/glitch/components/load_pending.jsx
index 7e2702403..a75259146 100644
--- a/app/javascript/flavours/glitch/components/load_pending.js
+++ b/app/javascript/flavours/glitch/components/load_pending.jsx
@@ -7,7 +7,7 @@ export default class LoadPending extends React.PureComponent {
   static propTypes = {
     onClick: PropTypes.func,
     count: PropTypes.number,
-  }
+  };
 
   render() {
     const { count } = this.props;
diff --git a/app/javascript/flavours/glitch/components/loading_indicator.js b/app/javascript/flavours/glitch/components/loading_indicator.jsx
index 59f721c50..59f721c50 100644
--- a/app/javascript/flavours/glitch/components/loading_indicator.js
+++ b/app/javascript/flavours/glitch/components/loading_indicator.jsx
diff --git a/app/javascript/flavours/glitch/components/logo.js b/app/javascript/flavours/glitch/components/logo.jsx
index ee5c22496..ee5c22496 100644
--- a/app/javascript/flavours/glitch/components/logo.js
+++ b/app/javascript/flavours/glitch/components/logo.jsx
diff --git a/app/javascript/flavours/glitch/components/media_attachments.js b/app/javascript/flavours/glitch/components/media_attachments.jsx
index a517fcf30..b11d3526f 100644
--- a/app/javascript/flavours/glitch/components/media_attachments.js
+++ b/app/javascript/flavours/glitch/components/media_attachments.jsx
@@ -10,6 +10,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
 
   static propTypes = {
     status: ImmutablePropTypes.map.isRequired,
+    lang: PropTypes.string,
     height: PropTypes.number,
     width: PropTypes.number,
     revealed: PropTypes.bool,
@@ -30,7 +31,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
     return (
       <div className='media-gallery' style={{ height, width }} />
     );
-  }
+  };
 
   renderLoadingVideoPlayer = () => {
     const { height, width } = this.props;
@@ -38,7 +39,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
     return (
       <div className='video-player' style={{ height, width }} />
     );
-  }
+  };
 
   renderLoadingAudioPlayer = () => {
     const { height, width } = this.props;
@@ -46,10 +47,10 @@ export default class MediaAttachments extends ImmutablePureComponent {
     return (
       <div className='audio-player' style={{ height, width }} />
     );
-  }
+  };
 
   render () {
-    const { status, width, height, revealed } = this.props;
+    const { status, lang, width, height, revealed } = this.props;
     const mediaAttachments = status.get('media_attachments');
 
     if (mediaAttachments.size === 0) {
@@ -65,6 +66,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
             <Component
               src={audio.get('url')}
               alt={audio.get('description')}
+              lang={lang || status.get('language')}
               width={width}
               height={height}
               poster={audio.get('preview_url') || status.getIn(['account', 'avatar_static'])}
@@ -88,6 +90,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
               blurhash={video.get('blurhash')}
               src={video.get('url')}
               alt={video.get('description')}
+              lang={lang || status.get('language')}
               width={width}
               height={height}
               inline
@@ -104,6 +107,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
           {Component => (
             <Component
               media={mediaAttachments}
+              lang={lang || status.get('language')}
               sensitive={status.get('sensitive')}
               defaultWidth={width}
               revealed={revealed}
diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.jsx
index 23e279589..b38f732f1 100644
--- a/app/javascript/flavours/glitch/components/media_gallery.js
+++ b/app/javascript/flavours/glitch/components/media_gallery.jsx
@@ -36,6 +36,7 @@ class Item extends React.PureComponent {
 
   static propTypes = {
     attachment: ImmutablePropTypes.map.isRequired,
+    lang: PropTypes.string,
     standalone: PropTypes.bool,
     index: PropTypes.number.isRequired,
     size: PropTypes.number.isRequired,
@@ -60,14 +61,14 @@ class Item extends React.PureComponent {
     if (this.hoverToPlay()) {
       e.target.play();
     }
-  }
+  };
 
   handleMouseLeave = (e) => {
     if (this.hoverToPlay()) {
       e.target.pause();
       e.target.currentTime = 0;
     }
-  }
+  };
 
   getAutoPlay() {
     return this.props.autoplay || autoPlayGif;
@@ -91,14 +92,14 @@ class Item extends React.PureComponent {
     }
 
     e.stopPropagation();
-  }
+  };
 
   handleImageLoad = () => {
     this.setState({ loaded: true });
-  }
+  };
 
   render () {
-    const { attachment, index, size, standalone, letterbox, displayWidth, visible } = this.props;
+    const { attachment, lang, index, size, standalone, letterbox, displayWidth, visible } = this.props;
 
     let width  = 50;
     let height = 100;
@@ -154,7 +155,7 @@ class Item extends React.PureComponent {
     if (attachment.get('type') === 'unknown') {
       return (
         <div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
-          <a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} target='_blank' rel='noopener noreferrer'>
+          <a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} lang={lang} target='_blank' rel='noopener noreferrer'>
             <Blurhash
               hash={attachment.get('blurhash')}
               className='media-gallery__preview'
@@ -195,6 +196,7 @@ class Item extends React.PureComponent {
             sizes={sizes}
             alt={attachment.get('description')}
             title={attachment.get('description')}
+            lang={lang}
             style={{ objectPosition: letterbox ? null : `${x}% ${y}%` }}
             onLoad={this.handleImageLoad}
           />
@@ -209,6 +211,7 @@ class Item extends React.PureComponent {
             className={`media-gallery__item-gifv-thumbnail${letterbox ? ' letterbox' : ''}`}
             aria-label={attachment.get('description')}
             title={attachment.get('description')}
+            lang={lang}
             role='application'
             src={attachment.get('url')}
             onClick={this.handleClick}
@@ -241,7 +244,6 @@ class Item extends React.PureComponent {
 
 }
 
-export default @injectIntl
 class MediaGallery extends React.PureComponent {
 
   static propTypes = {
@@ -251,6 +253,7 @@ class MediaGallery extends React.PureComponent {
     fullwidth: PropTypes.bool,
     hidden: PropTypes.bool,
     media: ImmutablePropTypes.list.isRequired,
+    lang: PropTypes.string,
     size: PropTypes.object,
     onOpenMedia: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
@@ -307,11 +310,11 @@ class MediaGallery extends React.PureComponent {
     } else {
       this.setState({ visible: !this.state.visible });
     }
-  }
+  };
 
   handleClick = (index) => {
     this.props.onOpenMedia(this.props.media, index);
-  }
+  };
 
   handleRef = (node) => {
     this.node = node;
@@ -319,11 +322,11 @@ class MediaGallery extends React.PureComponent {
     if (this.node) {
       this._setDimensions();
     }
-  }
+  };
 
   _setDimensions () {
     const width = this.node.offsetWidth;
- 
+
     if (width && width != this.state.width) {
       // offsetWidth triggers a layout, so only calculate when we need to
       if (this.props.cacheWidth) {
@@ -342,7 +345,7 @@ class MediaGallery extends React.PureComponent {
   }
 
   render () {
-    const { media, intl, sensitive, letterbox, fullwidth, defaultWidth, autoplay } = this.props;
+    const { media, lang, intl, sensitive, letterbox, fullwidth, defaultWidth, autoplay } = this.props;
     const { visible } = this.state;
     const size     = media.take(4).size;
     const uncached = media.every(attachment => attachment.get('type') === 'unknown');
@@ -360,13 +363,13 @@ class MediaGallery extends React.PureComponent {
     } else if (width) {
       style.height = width / (16/9);
     } else {
-      return (<div className={computedClass} ref={this.handleRef}></div>);
+      return (<div className={computedClass} ref={this.handleRef} />);
     }
 
     if (this.isStandaloneEligible()) {
-      children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} />;
+      children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} lang={lang} displayWidth={width} visible={visible} />;
     } else {
-      children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} size={size} letterbox={letterbox} displayWidth={width} visible={visible || uncached} />);
+      children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} lang={lang} size={size} letterbox={letterbox} displayWidth={width} visible={visible || uncached} />);
     }
 
     if (uncached) {
@@ -402,3 +405,5 @@ class MediaGallery extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(MediaGallery);
diff --git a/app/javascript/flavours/glitch/components/missing_indicator.js b/app/javascript/flavours/glitch/components/missing_indicator.jsx
index 08e39c236..08e39c236 100644
--- a/app/javascript/flavours/glitch/components/missing_indicator.js
+++ b/app/javascript/flavours/glitch/components/missing_indicator.jsx
diff --git a/app/javascript/flavours/glitch/components/modal_root.js b/app/javascript/flavours/glitch/components/modal_root.jsx
index 056277447..5a5563e87 100644
--- a/app/javascript/flavours/glitch/components/modal_root.js
+++ b/app/javascript/flavours/glitch/components/modal_root.jsx
@@ -5,6 +5,7 @@ import { createBrowserHistory } from 'history';
 import { multiply } from 'color-blend';
 
 export default class ModalRoot extends React.PureComponent {
+
   static contextTypes = {
     router: PropTypes.object,
   };
@@ -28,7 +29,7 @@ export default class ModalRoot extends React.PureComponent {
          && !!this.props.children && !this.props.noEsc) {
       this.props.onClose();
     }
-  }
+  };
 
   handleKeyDown = (e) => {
     if (e.key === 'Tab') {
@@ -49,7 +50,7 @@ export default class ModalRoot extends React.PureComponent {
         e.preventDefault();
       }
     }
-  }
+  };
 
   componentDidMount () {
     window.addEventListener('keyup', this.handleKeyUp, false);
@@ -125,11 +126,11 @@ export default class ModalRoot extends React.PureComponent {
 
   getSiblings = () => {
     return Array(...this.node.parentElement.childNodes).filter(node => node !== this.node);
-  }
+  };
 
   setRef = ref => {
     this.node = ref;
-  }
+  };
 
   render () {
     const { children, onClose } = this.props;
diff --git a/app/javascript/flavours/glitch/components/navigation_portal.js b/app/javascript/flavours/glitch/components/navigation_portal.jsx
index 90afa1da0..9e8494179 100644
--- a/app/javascript/flavours/glitch/components/navigation_portal.js
+++ b/app/javascript/flavours/glitch/components/navigation_portal.jsx
@@ -15,7 +15,6 @@ const DefaultNavigation = () => (
   </>
 );
 
-export default @withRouter
 class NavigationPortal extends React.PureComponent {
 
   render () {
@@ -33,3 +32,5 @@ class NavigationPortal extends React.PureComponent {
   }
 
 }
+
+export default withRouter(NavigationPortal);
diff --git a/app/javascript/flavours/glitch/components/not_signed_in_indicator.js b/app/javascript/flavours/glitch/components/not_signed_in_indicator.jsx
index b440c6be2..b440c6be2 100644
--- a/app/javascript/flavours/glitch/components/not_signed_in_indicator.js
+++ b/app/javascript/flavours/glitch/components/not_signed_in_indicator.jsx
diff --git a/app/javascript/flavours/glitch/components/notification_purge_buttons.js b/app/javascript/flavours/glitch/components/notification_purge_buttons.jsx
index 3c7d67109..1d807bc23 100644
--- a/app/javascript/flavours/glitch/components/notification_purge_buttons.js
+++ b/app/javascript/flavours/glitch/components/notification_purge_buttons.jsx
@@ -19,7 +19,6 @@ const messages = defineMessages({
   btnApply : { id: 'notification_purge.btn_apply', defaultMessage: 'Clear\nselected' },
 });
 
-export default @injectIntl
 class NotificationPurgeButtons extends ImmutablePureComponent {
 
   static propTypes = {
@@ -57,3 +56,5 @@ class NotificationPurgeButtons extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(NotificationPurgeButtons);
diff --git a/app/javascript/flavours/glitch/components/permalink.js b/app/javascript/flavours/glitch/components/permalink.jsx
index 718b02115..b09b17eeb 100644
--- a/app/javascript/flavours/glitch/components/permalink.js
+++ b/app/javascript/flavours/glitch/components/permalink.jsx
@@ -24,12 +24,12 @@ export default class Permalink extends React.PureComponent {
 
       if (this.context.router) {
         e.preventDefault();
-        let state = {...this.context.router.history.location.state};
+        let state = { ...this.context.router.history.location.state };
         state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
         this.context.router.history.push(this.props.to, state);
       }
     }
-  }
+  };
 
   render () {
     const {
diff --git a/app/javascript/flavours/glitch/components/picture_in_picture_placeholder.js b/app/javascript/flavours/glitch/components/picture_in_picture_placeholder.jsx
index 01dce0a38..9d1b7f55a 100644
--- a/app/javascript/flavours/glitch/components/picture_in_picture_placeholder.js
+++ b/app/javascript/flavours/glitch/components/picture_in_picture_placeholder.jsx
@@ -6,7 +6,6 @@ import { connect } from 'react-redux';
 import { debounce } from 'lodash';
 import { FormattedMessage } from 'react-intl';
 
-export default @connect()
 class PictureInPicturePlaceholder extends React.PureComponent {
 
   static propTypes = {
@@ -22,7 +21,7 @@ class PictureInPicturePlaceholder extends React.PureComponent {
   handleClick = () => {
     const { dispatch } = this.props;
     dispatch(removePictureInPicture());
-  }
+  };
 
   setRef = c => {
     this.node = c;
@@ -30,7 +29,7 @@ class PictureInPicturePlaceholder extends React.PureComponent {
     if (this.node) {
       this._setDimensions();
     }
-  }
+  };
 
   _setDimensions () {
     const width  = this.node.offsetWidth;
@@ -59,7 +58,7 @@ class PictureInPicturePlaceholder extends React.PureComponent {
     const { height } = this.state;
 
     return (
-      <div ref={this.setRef} className='picture-in-picture-placeholder' style={{ height }} role='button' tabIndex='0' onClick={this.handleClick}>
+      <div ref={this.setRef} className='picture-in-picture-placeholder' style={{ height }} role='button' tabIndex={0} onClick={this.handleClick}>
         <Icon id='window-restore' />
         <FormattedMessage id='picture_in_picture.restore' defaultMessage='Put it back' />
       </div>
@@ -67,3 +66,5 @@ class PictureInPicturePlaceholder extends React.PureComponent {
   }
 
 }
+
+export default connect()(PictureInPicturePlaceholder);
diff --git a/app/javascript/flavours/glitch/components/poll.js b/app/javascript/flavours/glitch/components/poll.jsx
index da65cd241..fb37612d9 100644
--- a/app/javascript/flavours/glitch/components/poll.js
+++ b/app/javascript/flavours/glitch/components/poll.jsx
@@ -31,7 +31,6 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
   return obj;
 }, {});
 
-export default @injectIntl
 class Poll extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -40,6 +39,7 @@ class Poll extends ImmutablePureComponent {
 
   static propTypes = {
     poll: ImmutablePropTypes.map,
+    lang: PropTypes.string,
     intl: PropTypes.object.isRequired,
     disabled: PropTypes.bool,
     refresh: PropTypes.func,
@@ -95,7 +95,7 @@ class Poll extends ImmutablePureComponent {
       tmp[value] = true;
       this.setState({ selected: tmp });
     }
-  }
+  };
 
   handleOptionChange = ({ target: { value } }) => {
     this._toggleOption(value);
@@ -107,7 +107,7 @@ class Poll extends ImmutablePureComponent {
       e.stopPropagation();
       e.preventDefault();
     }
-  }
+  };
 
   handleVote = () => {
     if (this.props.disabled) {
@@ -126,7 +126,7 @@ class Poll extends ImmutablePureComponent {
   };
 
   renderOption (option, optionIndex, showResults) {
-    const { poll, disabled, intl } = this.props;
+    const { poll, lang, disabled, intl } = this.props;
     const pollVotesCount  = poll.get('voters_count') || poll.get('votes_count');
     const percent         = pollVotesCount === 0 ? 0 : (option.get('votes_count') / pollVotesCount) * 100;
     const leading         = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') >= other.get('votes_count'));
@@ -154,11 +154,12 @@ class Poll extends ImmutablePureComponent {
           {!showResults && (
             <span
               className={classNames('poll__input', { checkbox: poll.get('multiple'), active })}
-              tabIndex='0'
+              tabIndex={0}
               role={poll.get('multiple') ? 'checkbox' : 'radio'}
               onKeyPress={this.handleOptionKeyPress}
               aria-checked={active}
               aria-label={option.get('title')}
+              lang={lang}
               data-index={optionIndex}
             />
           )}
@@ -175,6 +176,7 @@ class Poll extends ImmutablePureComponent {
 
           <span
             className='poll__option__text translate'
+            lang={lang}
             dangerouslySetInnerHTML={{ __html: titleEmojified }}
           />
 
@@ -231,3 +233,5 @@ class Poll extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(Poll);
diff --git a/app/javascript/flavours/glitch/components/radio_button.js b/app/javascript/flavours/glitch/components/radio_button.jsx
index 0496fa286..0496fa286 100644
--- a/app/javascript/flavours/glitch/components/radio_button.js
+++ b/app/javascript/flavours/glitch/components/radio_button.jsx
diff --git a/app/javascript/flavours/glitch/components/regeneration_indicator.js b/app/javascript/flavours/glitch/components/regeneration_indicator.jsx
index 68ce09df9..68ce09df9 100644
--- a/app/javascript/flavours/glitch/components/regeneration_indicator.js
+++ b/app/javascript/flavours/glitch/components/regeneration_indicator.jsx
diff --git a/app/javascript/flavours/glitch/components/relative_timestamp.js b/app/javascript/flavours/glitch/components/relative_timestamp.jsx
index 512480339..e6c3e0880 100644
--- a/app/javascript/flavours/glitch/components/relative_timestamp.js
+++ b/app/javascript/flavours/glitch/components/relative_timestamp.jsx
@@ -121,7 +121,6 @@ const timeRemainingString = (intl, date, now, timeGiven = true) => {
   return relativeTime;
 };
 
-export default @injectIntl
 class RelativeTimestamp extends React.Component {
 
   static propTypes = {
@@ -197,3 +196,5 @@ class RelativeTimestamp extends React.Component {
   }
 
 }
+
+export default injectIntl(RelativeTimestamp);
diff --git a/app/javascript/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.jsx
index 8eb2b66d4..fc7dc989d 100644
--- a/app/javascript/flavours/glitch/components/scrollable_list.js
+++ b/app/javascript/flavours/glitch/components/scrollable_list.jsx
@@ -20,7 +20,6 @@ const mapStateToProps = (state, { scrollKey }) => {
   };
 };
 
-export default @connect(mapStateToProps, null, null, { forwardRef: true })
 class ScrollableList extends PureComponent {
 
   static contextTypes = {
@@ -137,7 +136,7 @@ class ScrollableList extends PureComponent {
     }
     this.mouseMovedRecently = false;
     this.scrollToTopOnMouseIdle = false;
-  }
+  };
 
   componentDidMount () {
     this.attachScrollListener();
@@ -154,29 +153,29 @@ class ScrollableList extends PureComponent {
     } else {
       return null;
     }
-  }
+  };
 
   getScrollTop = () => {
     return this.props.bindToDocument ? document.scrollingElement.scrollTop : this.node.scrollTop;
-  }
+  };
 
   getScrollHeight = () => {
     return this.props.bindToDocument ? document.scrollingElement.scrollHeight : this.node.scrollHeight;
-  }
+  };
 
   getClientHeight = () => {
     return this.props.bindToDocument ? document.scrollingElement.clientHeight : this.node.clientHeight;
-  }
+  };
 
   updateScrollBottom = (snapshot) => {
     const newScrollTop = this.getScrollHeight() - snapshot;
 
     this.setScrollTop(newScrollTop);
-  }
+  };
 
   cacheMediaWidth = (width) => {
     if (width && this.state.cachedMediaWidth != width) this.setState({ cachedMediaWidth: width });
-  }
+  };
 
   getSnapshotBeforeUpdate (prevProps, prevState) {
     const someItemInserted = React.Children.count(prevProps.children) > 0 &&
@@ -208,7 +207,7 @@ class ScrollableList extends PureComponent {
 
   onFullScreenChange = () => {
     this.setState({ fullscreen: isFullscreen() });
-  }
+  };
 
   attachIntersectionObserver () {
     this.intersectionObserverWrapper.connect({
@@ -256,12 +255,12 @@ class ScrollableList extends PureComponent {
 
   setRef = (c) => {
     this.node = c;
-  }
+  };
 
   handleLoadMore = e => {
     e.preventDefault();
     this.props.onLoadMore();
-  }
+  };
 
   handleLoadPending = e => {
     e.preventDefault();
@@ -273,7 +272,7 @@ class ScrollableList extends PureComponent {
     this.clearMouseIdleTimer();
     this.mouseIdleTimer = setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY);
     this.mouseMovedRecently = true;
-  }
+  };
 
   render () {
     const { children, scrollKey, trackScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, append, emptyMessage, onLoadMore } = this.props;
@@ -352,3 +351,5 @@ class ScrollableList extends PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps, null, null, { forwardRef: true })(ScrollableList);
diff --git a/app/javascript/flavours/glitch/components/server_banner.js b/app/javascript/flavours/glitch/components/server_banner.jsx
index 36e0ff238..ba84064a8 100644
--- a/app/javascript/flavours/glitch/components/server_banner.js
+++ b/app/javascript/flavours/glitch/components/server_banner.jsx
@@ -18,8 +18,6 @@ const mapStateToProps = state => ({
   server: state.getIn(['server', 'server']),
 });
 
-export default @connect(mapStateToProps)
-@injectIntl
 class ServerBanner extends React.PureComponent {
 
   static propTypes = {
@@ -91,3 +89,5 @@ class ServerBanner extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(ServerBanner));
diff --git a/app/javascript/flavours/glitch/components/setting_text.js b/app/javascript/flavours/glitch/components/setting_text.jsx
index 2c1b70bc3..3a21a0601 100644
--- a/app/javascript/flavours/glitch/components/setting_text.js
+++ b/app/javascript/flavours/glitch/components/setting_text.jsx
@@ -13,7 +13,7 @@ export default class SettingText extends React.PureComponent {
 
   handleChange = (e) => {
     this.props.onChange(this.props.settingPath, e.target.value);
-  }
+  };
 
   render () {
     const { settings, settingPath, label } = this.props;
diff --git a/app/javascript/flavours/glitch/components/short_number.js b/app/javascript/flavours/glitch/components/short_number.jsx
index 535c17727..535c17727 100644
--- a/app/javascript/flavours/glitch/components/short_number.js
+++ b/app/javascript/flavours/glitch/components/short_number.jsx
diff --git a/app/javascript/flavours/glitch/components/skeleton.js b/app/javascript/flavours/glitch/components/skeleton.jsx
index 6a17ffb26..6a17ffb26 100644
--- a/app/javascript/flavours/glitch/components/skeleton.js
+++ b/app/javascript/flavours/glitch/components/skeleton.jsx
diff --git a/app/javascript/flavours/glitch/components/spoilers.js b/app/javascript/flavours/glitch/components/spoilers.jsx
index 8527403c1..d1d875807 100644
--- a/app/javascript/flavours/glitch/components/spoilers.js
+++ b/app/javascript/flavours/glitch/components/spoilers.jsx
@@ -4,6 +4,7 @@ import { FormattedMessage } from 'react-intl';
 
 export default
 class Spoilers extends React.PureComponent {
+
   static propTypes = {
     spoilerText: PropTypes.string,
     children: PropTypes.node,
@@ -11,40 +12,41 @@ class Spoilers extends React.PureComponent {
 
   state = {
     hidden: true,
-  }
+  };
 
   handleSpoilerClick = () => {
     this.setState({ hidden: !this.state.hidden });
-  }
+  };
 
   render () {
     const { spoilerText, children } = this.props;
     const { hidden } = this.state;
 
-      const toggleText = hidden ?
-        <FormattedMessage
-          id='status.show_more'
-          defaultMessage='Show more'
-          key='0'
-        /> :
-        <FormattedMessage
-          id='status.show_less'
-          defaultMessage='Show less'
-          key='0'
-        />;
+    const toggleText = hidden ?
+      (<FormattedMessage
+        id='status.show_more'
+        defaultMessage='Show more'
+        key='0'
+      />) :
+      (<FormattedMessage
+        id='status.show_less'
+        defaultMessage='Show less'
+        key='0'
+      />);
 
     return ([
       <p className='spoiler__text'>
         {spoilerText}
         {' '}
-        <button tabIndex='0' className='status__content__spoiler-link' onClick={this.handleSpoilerClick}>
+        <button tabIndex={0} className='status__content__spoiler-link' onClick={this.handleSpoilerClick}>
           {toggleText}
         </button>
       </p>,
       <div className={`status__content__spoiler ${!hidden ? 'status__content__spoiler--visible' : ''}`}>
         {children}
-      </div>
+      </div>,
     ]);
   }
+
 }
 
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.jsx
index 409ec0adc..fbb610823 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.jsx
@@ -54,9 +54,8 @@ export const defaultMediaVisibility = (status, settings) => {
   }
 
   return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all');
-}
+};
 
-export default @injectIntl
 class Status extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -117,7 +116,7 @@ class Status extends ImmutablePureComponent {
     revealBehindCW: undefined,
     showCard: false,
     forceFilter: undefined,
-  }
+  };
 
   // Avoid checking props that are functions (and whose equality will always
   // evaluate to false. See react-immutable-pure-component for usage.
@@ -132,14 +131,14 @@ class Status extends ImmutablePureComponent {
     'expanded',
     'unread',
     'pictureInPicture',
-  ]
+  ];
 
   updateOnStates = [
     'isExpanded',
     'isCollapsed',
     'showMedia',
     'forceFilter',
-  ]
+  ];
 
   //  If our settings have changed to disable collapsed statuses, then we
   //  need to make sure that we uncollapse every one. We do that by watching
@@ -224,7 +223,7 @@ class Status extends ImmutablePureComponent {
   //   -  The user has decided to collapse all notifications ('muted'
   //      statuses).
   //   -  The user has decided to collapse long statuses and the status is
-  //      over 400px (without media, or 650px with).
+  //      over the user set value (default 400 without media, or 610px with).
   //   -  The status is a reply and the user has decided to collapse all
   //      replies.
   //   -  The status contains media and the user has decided to collapse all
@@ -251,10 +250,15 @@ class Status extends ImmutablePureComponent {
     // as it could cause surprising changes when receiving notifications
     if (settings.getIn(['content_warnings', 'shared_state']) && status.get('spoiler_text').length && !status.get('hidden')) return;
 
+    let autoCollapseHeight = parseInt(autoCollapseSettings.get('height'));
+    if (status.get('media_attachments').size && !muted) {
+      autoCollapseHeight += 210;
+    }
+
     if (collapse ||
       autoCollapseSettings.get('all') ||
       (autoCollapseSettings.get('notifications') && muted) ||
-      (autoCollapseSettings.get('lengthy') && node.clientHeight > ((status.get('media_attachments').size && !muted) ? 650 : 400)) ||
+      (autoCollapseSettings.get('lengthy') && node.clientHeight > autoCollapseHeight) ||
       (autoCollapseSettings.get('reblogs') && prepend === 'reblogged_by') ||
       (autoCollapseSettings.get('replies') && status.get('in_reply_to_id', null) !== null) ||
       (autoCollapseSettings.get('media') && !(status.get('spoiler_text').length) && status.get('media_attachments').size > 0)
@@ -297,7 +301,9 @@ class Status extends ImmutablePureComponent {
     if (this.node && this.props.getScrollPosition) {
       const position = this.props.getScrollPosition();
       if (position !== null && this.node.offsetTop < position.top) {
-         requestAnimationFrame(() => { this.props.updateScrollBottom(position.height - position.top); });
+        requestAnimationFrame(() => {
+          this.props.updateScrollBottom(position.height - position.top);
+        });
       }
     }
   }
@@ -316,7 +322,7 @@ class Status extends ImmutablePureComponent {
     } else {
       this.setState({ isCollapsed: false });
     }
-  }
+  };
 
   setExpansion = (value) => {
     if (this.props.settings.getIn(['content_warnings', 'shared_state']) && this.props.status.get('hidden') === value) {
@@ -327,7 +333,7 @@ class Status extends ImmutablePureComponent {
     if (value) {
       this.setCollapsed(false);
     }
-  }
+  };
 
   //  `parseClick()` takes a click event and responds appropriately.
   //  If our status is collapsed, then clicking on it should uncollapse it.
@@ -356,17 +362,17 @@ class Status extends ImmutablePureComponent {
             status.getIn(['reblog', 'id'], status.get('id'))
           }`;
         }
-        let state = {...router.history.location.state};
+        let state = { ...router.history.location.state };
         state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
         router.history.push(destination, state);
       }
       e.preventDefault();
     }
-  }
+  };
 
   handleToggleMediaVisibility = () => {
     this.setState({ showMedia: !this.state.showMedia });
-  }
+  };
 
   handleExpandedToggle = () => {
     if (this.props.settings.getIn(['content_warnings', 'shared_state'])) {
@@ -379,11 +385,11 @@ class Status extends ImmutablePureComponent {
   handleOpenVideo = (options) => {
     const { status } = this.props;
     this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options);
-  }
+  };
 
   handleOpenMedia = (media, index) => {
     this.props.onOpenMedia(this.props.status.get('id'), media, index);
-  }
+  };
 
   handleHotkeyOpenMedia = e => {
     const { status, onOpenMedia, onOpenVideo } = this.props;
@@ -398,84 +404,84 @@ class Status extends ImmutablePureComponent {
         onOpenMedia(statusId, status.get('media_attachments'), 0);
       }
     }
-  }
+  };
 
   handleDeployPictureInPicture = (type, mediaProps) => {
     const { deployPictureInPicture, status } = this.props;
 
     deployPictureInPicture(status, type, mediaProps);
-  }
+  };
 
   handleHotkeyReply = e => {
     e.preventDefault();
     this.props.onReply(this.props.status, this.context.router.history);
-  }
+  };
 
   handleHotkeyFavourite = (e) => {
     this.props.onFavourite(this.props.status, e);
-  }
+  };
 
   handleHotkeyBoost = e => {
     this.props.onReblog(this.props.status, e);
-  }
+  };
 
   handleHotkeyBookmark = e => {
     this.props.onBookmark(this.props.status, e);
-  }
+  };
 
   handleHotkeyMention = e => {
     e.preventDefault();
     this.props.onMention(this.props.status.get('account'), this.context.router.history);
-  }
+  };
 
   handleHotkeyOpen = () => {
-    let state = {...this.context.router.history.location.state};
+    let state = { ...this.context.router.history.location.state };
     state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
     const status = this.props.status;
     this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`, state);
-  }
+  };
 
   handleHotkeyOpenProfile = () => {
-    let state = {...this.context.router.history.location.state};
+    let state = { ...this.context.router.history.location.state };
     state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
     this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`, state);
-  }
+  };
 
   handleHotkeyMoveUp = e => {
     this.props.onMoveUp(this.props.containerId || this.props.id, e.target.getAttribute('data-featured'));
-  }
+  };
 
   handleHotkeyMoveDown = e => {
     this.props.onMoveDown(this.props.containerId || this.props.id, e.target.getAttribute('data-featured'));
-  }
+  };
 
   handleHotkeyCollapse = e => {
     if (!this.props.settings.getIn(['collapsed', 'enabled']))
       return;
 
     this.setCollapsed(!this.state.isCollapsed);
-  }
+  };
 
   handleHotkeyToggleSensitive = () => {
     this.handleToggleMediaVisibility();
-  }
+  };
 
   handleUnfilterClick = e => {
     this.setState({ forceFilter: false });
     e.preventDefault();
-  }
+  };
 
   handleFilterClick = () => {
     this.setState({ forceFilter: true });
-  }
+  };
 
   handleRef = c => {
     this.node = c;
-  }
+  };
 
   handleTranslate = () => {
     this.props.onTranslate(this.props.status);
-  }
+  };
 
   renderLoadingMediaGallery () {
     return <div className='media-gallery' style={{ height: '110px' }} />;
@@ -558,7 +564,7 @@ class Status extends ImmutablePureComponent {
     if (hidden) {
       return (
         <HotKeys handlers={handlers}>
-          <div ref={this.handleRef} className='status focusable' tabIndex='0'>
+          <div ref={this.handleRef} className='status focusable' tabIndex={0}>
             <span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
             <span>{status.get('content')}</span>
           </div>
@@ -575,7 +581,7 @@ class Status extends ImmutablePureComponent {
 
       return (
         <HotKeys handlers={minHandlers}>
-          <div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0' ref={this.handleRef}>
+          <div className='status__wrapper status__wrapper--filtered focusable' tabIndex={0} ref={this.handleRef}>
             <FormattedMessage id='status.filtered' defaultMessage='Filtered' />: {matchedFilters.join(', ')}.
             {' '}
             <button className='status__wrapper--filtered__button' onClick={this.handleUnfilterClick}>
@@ -623,6 +629,7 @@ class Status extends ImmutablePureComponent {
               <Component
                 src={attachment.get('url')}
                 alt={attachment.get('description')}
+                lang={status.get('language')}
                 poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
                 backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
                 foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
@@ -652,6 +659,7 @@ class Status 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'])}
@@ -673,6 +681,7 @@ class Status extends ImmutablePureComponent {
             {Component => (
               <Component
                 media={attachments}
+                lang={status.get('language')}
                 sensitive={status.get('sensitive')}
                 letterbox={settings.getIn(['media', 'letterbox'])}
                 fullwidth={settings.getIn(['media', 'fullwidth'])}
@@ -707,7 +716,7 @@ class Status 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');
     }
 
@@ -760,7 +769,7 @@ class Status extends ImmutablePureComponent {
           style={isCollapsed && background ? { backgroundImage: `url(${background})` } : null}
           {...selectorAttribs}
           ref={handleRef}
-          tabIndex='0'
+          tabIndex={0}
           data-featured={featured ? 'true' : null}
           aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))}
         >
@@ -820,3 +829,5 @@ class Status extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(Status);
diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.jsx
index baf61000a..091d0b24b 100644
--- a/app/javascript/flavours/glitch/components/status_action_bar.js
+++ b/app/javascript/flavours/glitch/components/status_action_bar.jsx
@@ -46,7 +46,6 @@ const messages = defineMessages({
   openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
 });
 
-export default @injectIntl
 class StatusActionBar extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -86,7 +85,7 @@ class StatusActionBar extends ImmutablePureComponent {
     'showReplyCount',
     'withCounters',
     'withDismiss',
-  ]
+  ];
 
   handleReplyClick = () => {
     const { signedIn } = this.context.identity;
@@ -96,14 +95,14 @@ class StatusActionBar extends ImmutablePureComponent {
     } else {
       this.props.onInteractionModal('reply', this.props.status);
     }
-  }
+  };
 
   handleShareClick = () => {
     navigator.share({
       text: this.props.status.get('search_index'),
       url: this.props.status.get('url'),
     });
-  }
+  };
 
   handleFavouriteClick = (e) => {
     const { signedIn } = this.context.identity;
@@ -113,7 +112,7 @@ class StatusActionBar extends ImmutablePureComponent {
     } else {
       this.props.onInteractionModal('favourite', this.props.status);
     }
-  }
+  };
 
   handleReblogClick = e => {
     const { signedIn } = this.context.identity;
@@ -123,78 +122,78 @@ class StatusActionBar extends ImmutablePureComponent {
     } else {
       this.props.onInteractionModal('reblog', this.props.status);
     }
-  }
+  };
 
   handleBookmarkClick = (e) => {
     this.props.onBookmark(this.props.status, e);
-  }
+  };
 
   handleDeleteClick = () => {
     this.props.onDelete(this.props.status, this.context.router.history);
-  }
+  };
 
   handleRedraftClick = () => {
     this.props.onDelete(this.props.status, this.context.router.history, true);
-  }
+  };
 
   handleEditClick = () => {
     this.props.onEdit(this.props.status, this.context.router.history);
-  }
+  };
 
   handlePinClick = () => {
     this.props.onPin(this.props.status);
-  }
+  };
 
   handleMentionClick = () => {
     this.props.onMention(this.props.status.get('account'), this.context.router.history);
-  }
+  };
 
   handleDirectClick = () => {
     this.props.onDirect(this.props.status.get('account'), this.context.router.history);
-  }
+  };
 
   handleMuteClick = () => {
     this.props.onMute(this.props.status.get('account'));
-  }
+  };
 
   handleBlockClick = () => {
     this.props.onBlock(this.props.status);
-  }
+  };
 
   handleOpen = () => {
-    let state = {...this.context.router.history.location.state};
+    let state = { ...this.context.router.history.location.state };
     if (state.mastodonModalKey) {
       this.context.router.history.replace(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`, { mastodonBackSteps: (state.mastodonBackSteps || 0) + 1 });
     } else {
       state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
       this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`, state);
     }
-  }
+  };
 
   handleEmbed = () => {
     this.props.onEmbed(this.props.status);
-  }
+  };
 
   handleReport = () => {
     this.props.onReport(this.props.status);
-  }
+  };
 
   handleConversationMuteClick = () => {
     this.props.onMuteConversation(this.props.status);
-  }
+  };
 
   handleCopy = () => {
     const url = this.props.status.get('url');
     navigator.clipboard.writeText(url);
-  }
+  };
 
   handleHideClick = () => {
     this.props.onFilter();
-  }
+  };
 
   handleFilterClick = () => {
     this.props.onAddFilter(this.props.status);
-  }
+  };
 
   render () {
     const { status, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props;
@@ -339,3 +338,5 @@ class StatusActionBar extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(StatusActionBar);
diff --git a/app/javascript/flavours/glitch/components/status_content.js b/app/javascript/flavours/glitch/components/status_content.jsx
index c59f42220..e66df588a 100644
--- a/app/javascript/flavours/glitch/components/status_content.js
+++ b/app/javascript/flavours/glitch/components/status_content.jsx
@@ -3,16 +3,17 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import { FormattedMessage, injectIntl } from 'react-intl';
 import Permalink from './permalink';
+import { connect } from 'react-redux';
 import classnames from 'classnames';
 import Icon from 'flavours/glitch/components/icon';
-import { autoPlayGif, languages as preloadedLanguages, translationEnabled } from 'flavours/glitch/initial_state';
+import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
 import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
 
 const textMatchesTarget = (text, origin, host) => {
   return (text === origin || text === host
           || text.startsWith(origin + '/') || text.startsWith(host + '/')
           || 'www.' + text === host || ('www.' + text).startsWith(host + '/'));
-}
+};
 
 const isLinkMisleading = (link) => {
   let linkTextParts = [];
@@ -99,7 +100,10 @@ class TranslateButton extends React.PureComponent {
 
 }
 
-export default @injectIntl
+const mapStateToProps = state => ({
+  languages: state.getIn(['server', 'translationLanguages', 'items']),
+});
+
 class StatusContent extends React.PureComponent {
 
   static contextTypes = {
@@ -120,6 +124,7 @@ class StatusContent extends React.PureComponent {
     onUpdate: PropTypes.func,
     tagLinks: PropTypes.bool,
     rewriteMentions: PropTypes.string,
+    languages: ImmutablePropTypes.map,
     intl: PropTypes.object,
   };
 
@@ -168,8 +173,8 @@ class StatusContent extends React.PureComponent {
         link.setAttribute('title', link.href);
         link.classList.add('unhandled-link');
 
-      link.setAttribute('target', '_blank');
-      link.setAttribute('rel', 'noopener nofollow noreferrer');
+        link.setAttribute('target', '_blank');
+        link.setAttribute('rel', 'noopener nofollow noreferrer');
 
         try {
           if (tagLinks && isLinkMisleading(link)) {
@@ -210,7 +215,7 @@ class StatusContent extends React.PureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-original');
     }
-  }
+  };
 
   handleMouseLeave = ({ currentTarget }) => {
     if (autoPlayGif) {
@@ -223,7 +228,7 @@ class StatusContent extends React.PureComponent {
       let emoji = emojis[i];
       emoji.src = emoji.getAttribute('data-static');
     }
-  }
+  };
 
   componentDidMount () {
     this._updateStatusLinks();
@@ -238,13 +243,13 @@ class StatusContent extends React.PureComponent {
     if (this.props.collapsed) {
       if (this.props.parseClick) this.props.parseClick(e);
     }
-  }
+  };
 
   onMentionClick = (mention, e) => {
     if (this.props.parseClick) {
       this.props.parseClick(e, `/@${mention.get('acct')}`);
     }
-  }
+  };
 
   onHashtagClick = (hashtag, e) => {
     hashtag = hashtag.replace(/^#/, '');
@@ -252,11 +257,11 @@ class StatusContent extends React.PureComponent {
     if (this.props.parseClick) {
       this.props.parseClick(e, `/tags/${hashtag}`);
     }
-  }
+  };
 
   handleMouseDown = (e) => {
     this.startXY = [e.clientX, e.clientY];
-  }
+  };
 
   handleMouseUp = (e) => {
     const { parseClick, disabled } = this.props;
@@ -281,7 +286,7 @@ class StatusContent extends React.PureComponent {
     }
 
     this.startXY = null;
-  }
+  };
 
   handleSpoilerClick = (e) => {
     e.preventDefault();
@@ -291,15 +296,15 @@ class StatusContent extends React.PureComponent {
     } else {
       this.setState({ hidden: !this.state.hidden });
     }
-  }
+  };
 
   handleTranslate = () => {
     this.props.onTranslate();
-  }
+  };
 
   setContentsRef = (c) => {
     this.contentsNode = c;
-  }
+  };
 
   render () {
     const {
@@ -315,7 +320,9 @@ class StatusContent extends React.PureComponent {
     } = this.props;
 
     const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
-    const renderTranslate = translationEnabled && this.context.identity.signedIn && this.props.onTranslate && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('contentHtml').length > 0 && status.get('language') !== null && intl.locale !== status.get('language');
+    const contentLocale = intl.locale.replace(/[_-].*/, '');
+    const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
+    const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('contentHtml').length > 0 && targetLanguages?.includes(contentLocale);
 
     const content = { __html: status.get('translation') ? status.getIn(['translation', 'content']) : status.get('contentHtml') };
     const spoilerContent = { __html: status.get('spoilerHtml') };
@@ -380,7 +387,7 @@ class StatusContent extends React.PureComponent {
       }
 
       return (
-        <div className={classNames} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
+        <div className={classNames} tabIndex={0} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
           <p
             style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}
           >
@@ -417,14 +424,14 @@ class StatusContent extends React.PureComponent {
           className={classNames}
           onMouseDown={this.handleMouseDown}
           onMouseUp={this.handleMouseUp}
-          tabIndex='0'
+          tabIndex={0}
         >
           <div
             ref={this.setContentsRef}
             key={`contents-${tagLinks}-${rewriteMentions}`}
             dangerouslySetInnerHTML={content}
             className='status__content__text translate'
-            tabIndex='0'
+            tabIndex={0}
             onMouseEnter={this.handleMouseEnter}
             onMouseLeave={this.handleMouseLeave}
             lang={lang}
@@ -438,14 +445,14 @@ class StatusContent extends React.PureComponent {
       return (
         <div
           className='status__content'
-          tabIndex='0'
+          tabIndex={0}
         >
           <div
             ref={this.setContentsRef}
             key={`contents-${tagLinks}`}
             className='status__content__text translate'
             dangerouslySetInnerHTML={content}
-            tabIndex='0'
+            tabIndex={0}
             onMouseEnter={this.handleMouseEnter}
             onMouseLeave={this.handleMouseLeave}
             lang={lang}
@@ -459,3 +466,5 @@ class StatusContent extends React.PureComponent {
   }
 
 }
+
+export default connect(mapStateToProps)(injectIntl(StatusContent));
diff --git a/app/javascript/flavours/glitch/components/status_header.js b/app/javascript/flavours/glitch/components/status_header.jsx
index 990dea536..21d8b4212 100644
--- a/app/javascript/flavours/glitch/components/status_header.js
+++ b/app/javascript/flavours/glitch/components/status_header.jsx
@@ -21,12 +21,12 @@ export default class StatusHeader extends React.PureComponent {
   handleClick = (acct, e) => {
     const { parseClick } = this.props;
     parseClick(e, `/@${acct}`);
-  }
+  };
 
   handleAccountClick = (e) => {
     const { status } = this.props;
     this.handleClick(status.getIn(['account', 'acct']), e);
-  }
+  };
 
   //  Rendering.
   render () {
diff --git a/app/javascript/flavours/glitch/components/status_icons.js b/app/javascript/flavours/glitch/components/status_icons.jsx
index 71ffb2e56..3baff2206 100644
--- a/app/javascript/flavours/glitch/components/status_icons.js
+++ b/app/javascript/flavours/glitch/components/status_icons.jsx
@@ -40,7 +40,6 @@ LanguageIcon.propTypes = {
   language: PropTypes.string.isRequired,
 };
 
-export default @injectIntl
 class StatusIcons extends React.PureComponent {
 
   static propTypes = {
@@ -60,22 +59,22 @@ class StatusIcons extends React.PureComponent {
       setCollapsed(!collapsed);
       e.preventDefault();
     }
-  }
+  };
 
   mediaIconTitleText (mediaIcon) {
     const { intl } = this.props;
 
     switch (mediaIcon) {
-      case 'link':
-        return intl.formatMessage(messages.previewCard);
-      case 'picture-o':
-        return intl.formatMessage(messages.pictures);
-      case 'tasks':
-        return intl.formatMessage(messages.poll);
-      case 'video-camera':
-        return intl.formatMessage(messages.video);
-      case 'music':
-        return intl.formatMessage(messages.audio);
+    case 'link':
+      return intl.formatMessage(messages.previewCard);
+    case 'picture-o':
+      return intl.formatMessage(messages.pictures);
+    case 'tasks':
+      return intl.formatMessage(messages.poll);
+    case 'video-camera':
+      return intl.formatMessage(messages.video);
+    case 'music':
+      return intl.formatMessage(messages.audio);
     }
   }
 
@@ -143,3 +142,5 @@ class StatusIcons extends React.PureComponent {
   }
 
 }
+
+export default injectIntl(StatusIcons);
diff --git a/app/javascript/flavours/glitch/components/status_list.js b/app/javascript/flavours/glitch/components/status_list.jsx
index 0d843a27d..a9c06f693 100644
--- a/app/javascript/flavours/glitch/components/status_list.js
+++ b/app/javascript/flavours/glitch/components/status_list.jsx
@@ -35,7 +35,7 @@ export default class StatusList extends ImmutablePureComponent {
 
   getFeaturedStatusCount = () => {
     return this.props.featuredStatusIds ? this.props.featuredStatusIds.size : 0;
-  }
+  };
 
   getCurrentStatusIndex = (id, featured) => {
     if (featured) {
@@ -43,21 +43,21 @@ export default class StatusList extends ImmutablePureComponent {
     } else {
       return this.props.statusIds.indexOf(id) + this.getFeaturedStatusCount();
     }
-  }
+  };
 
   handleMoveUp = (id, featured) => {
     const elementIndex = this.getCurrentStatusIndex(id, featured) - 1;
     this._selectChild(elementIndex, true);
-  }
+  };
 
   handleMoveDown = (id, featured) => {
     const elementIndex = this.getCurrentStatusIndex(id, featured) + 1;
     this._selectChild(elementIndex, false);
-  }
+  };
 
   handleLoadOlder = debounce(() => {
     this.props.onLoadMore(this.props.statusIds.size > 0 ? this.props.statusIds.last() : undefined);
-  }, 300, { leading: true })
+  }, 300, { leading: true });
 
   _selectChild (index, align_top) {
     const container = this.node.node;
@@ -75,7 +75,7 @@ export default class StatusList extends ImmutablePureComponent {
 
   setRef = c => {
     this.node = c;
-  }
+  };
 
   render () {
     const { statusIds, featuredStatusIds, onLoadMore, timelineId, ...other }  = this.props;
diff --git a/app/javascript/flavours/glitch/components/status_prepend.js b/app/javascript/flavours/glitch/components/status_prepend.jsx
index f82533062..8c4343b04 100644
--- a/app/javascript/flavours/glitch/components/status_prepend.js
+++ b/app/javascript/flavours/glitch/components/status_prepend.jsx
@@ -18,7 +18,7 @@ export default class StatusPrepend extends React.PureComponent {
   handleClick = (e) => {
     const { account, parseClick } = this.props;
     parseClick(e, `/@${account.get('acct')}`);
-  }
+  };
 
   Message = () => {
     const { type, account } = this.props;
@@ -98,7 +98,7 @@ export default class StatusPrepend extends React.PureComponent {
       );
     }
     return null;
-  }
+  };
 
   render () {
     const { Message } = this;
@@ -126,7 +126,7 @@ export default class StatusPrepend extends React.PureComponent {
     case 'update':
       iconId = 'pencil';
       break;
-    };
+    }
 
     return !type ? null : (
       <aside className={type === 'reblogged_by' || type === 'featured' ? 'status__prepend' : 'notification__message'}>
diff --git a/app/javascript/flavours/glitch/components/status_visibility_icon.js b/app/javascript/flavours/glitch/components/status_visibility_icon.jsx
index 07d56c7a8..fcedfbfd6 100644
--- a/app/javascript/flavours/glitch/components/status_visibility_icon.js
+++ b/app/javascript/flavours/glitch/components/status_visibility_icon.jsx
@@ -12,7 +12,6 @@ const messages = defineMessages({
   direct: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
 });
 
-export default @injectIntl
 class VisibilityIcon extends ImmutablePureComponent {
 
   static propTypes = {
@@ -49,3 +48,5 @@ class VisibilityIcon extends ImmutablePureComponent {
   }
 
 }
+
+export default injectIntl(VisibilityIcon);
diff --git a/app/javascript/flavours/glitch/components/timeline_hint.js b/app/javascript/flavours/glitch/components/timeline_hint.jsx
index fb55a62cc..fb55a62cc 100644
--- a/app/javascript/flavours/glitch/components/timeline_hint.js
+++ b/app/javascript/flavours/glitch/components/timeline_hint.jsx