diff --git a/.eslintrc.js b/.eslintrc.js
index a9aa86605926f718519849d8b170b5cb8165bf1b..176879034bea4f5da93a313c8c348070bceb3fb1 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -284,7 +284,6 @@ module.exports = defineConfig({
     'formatjs/no-id': 'off', // IDs are used for translation keys
     'formatjs/no-invalid-icu': 'error',
     'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings
-    'formatjs/no-multiple-plurals': 'off', // Only used by hashtag.jsx
     'formatjs/no-multiple-whitespaces': 'error',
     'formatjs/no-offset': 'error',
     'formatjs/no-useless-message': 'error',
@@ -354,7 +353,7 @@ module.exports = defineConfig({
         '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
         '@typescript-eslint/consistent-type-exports': 'error',
         '@typescript-eslint/consistent-type-imports': 'error',
-        "@typescript-eslint/prefer-nullish-coalescing": ['error', {ignorePrimitives: {boolean: true}}],
+        "@typescript-eslint/prefer-nullish-coalescing": ['error', { ignorePrimitives: { boolean: true } }],
 
         'jsdoc/require-jsdoc': 'off',
 
diff --git a/app/javascript/mastodon/components/admin/Trends.jsx b/app/javascript/mastodon/components/admin/Trends.jsx
index 49976276ee50117619319872ed10f4c7876e3bae..c69b4a8cbaf4149cb3a11c951e71538fad76b552 100644
--- a/app/javascript/mastodon/components/admin/Trends.jsx
+++ b/app/javascript/mastodon/components/admin/Trends.jsx
@@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
 import classNames from 'classnames';
 
 import api from 'mastodon/api';
-import Hashtag from 'mastodon/components/hashtag';
+import { Hashtag } from 'mastodon/components/hashtag';
 
 export default class Trends extends PureComponent {
 
diff --git a/app/javascript/mastodon/components/hashtag.jsx b/app/javascript/mastodon/components/hashtag.jsx
deleted file mode 100644
index 14bb4ddc64c8f12756cf136ebef7eaa693bba18c..0000000000000000000000000000000000000000
--- a/app/javascript/mastodon/components/hashtag.jsx
+++ /dev/null
@@ -1,120 +0,0 @@
-// @ts-check
-import PropTypes from 'prop-types';
-import { Component } from 'react';
-
-import { FormattedMessage } from 'react-intl';
-
-import classNames from 'classnames';
-import { Link } from 'react-router-dom';
-
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-import { Sparklines, SparklinesCurve } from 'react-sparklines';
-
-import { ShortNumber } from 'mastodon/components/short_number';
-import { Skeleton } from 'mastodon/components/skeleton';
-
-class SilentErrorBoundary extends Component {
-
-  static propTypes = {
-    children: PropTypes.node,
-  };
-
-  state = {
-    error: false,
-  };
-
-  componentDidCatch() {
-    this.setState({ error: true });
-  }
-
-  render() {
-    if (this.state.error) {
-      return null;
-    }
-
-    return this.props.children;
-  }
-
-}
-
-/**
- * Used to render counter of how much people are talking about hashtag
- * @type {(displayNumber: JSX.Element, pluralReady: number) => JSX.Element}
- */
-export const accountsCountRenderer = (displayNumber, pluralReady) => (
-  <FormattedMessage
-    id='trends.counter_by_accounts'
-    defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}'
-    values={{
-      count: pluralReady,
-      counter: <strong>{displayNumber}</strong>,
-      days: 2,
-    }}
-  />
-);
-
-// @ts-expect-error
-export const ImmutableHashtag = ({ hashtag }) => (
-  <Hashtag
-    name={hashtag.get('name')}
-    to={`/tags/${hashtag.get('name')}`}
-    people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
-    // @ts-expect-error
-    history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
-  />
-);
-
-ImmutableHashtag.propTypes = {
-  hashtag: ImmutablePropTypes.map.isRequired,
-};
-
-// @ts-expect-error
-const Hashtag = ({ name, to, people, uses, history, className, description, withGraph }) => (
-  <div className={classNames('trends__item', className)}>
-    <div className='trends__item__name'>
-      <Link to={to}>
-        {name ? <>#<span>{name}</span></> : <Skeleton width={50} />}
-      </Link>
-
-      {description ? (
-        <span>{description}</span>
-      ) : (
-        typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />
-      )}
-    </div>
-
-    {typeof uses !== 'undefined' && (
-      <div className='trends__item__current'>
-        <ShortNumber value={uses} />
-      </div>
-    )}
-
-    {withGraph && (
-      <div className='trends__item__sparkline'>
-        <SilentErrorBoundary>
-          <Sparklines width={50} height={28} data={history ? history : Array.from(Array(7)).map(() => 0)}>
-            <SparklinesCurve style={{ fill: 'none' }} />
-          </Sparklines>
-        </SilentErrorBoundary>
-      </div>
-    )}
-  </div>
-);
-
-Hashtag.propTypes = {
-  name: PropTypes.string,
-  to: PropTypes.string,
-  people: PropTypes.number,
-  description: PropTypes.node,
-  uses: PropTypes.number,
-  history: PropTypes.arrayOf(PropTypes.number),
-  className: PropTypes.string,
-  withGraph: PropTypes.bool,
-};
-
-Hashtag.defaultProps = {
-  withGraph: true,
-};
-
-export default Hashtag;
diff --git a/app/javascript/mastodon/components/hashtag.tsx b/app/javascript/mastodon/components/hashtag.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8963e4a40d820dd40b9c7eb07c6070cf47cb8a84
--- /dev/null
+++ b/app/javascript/mastodon/components/hashtag.tsx
@@ -0,0 +1,145 @@
+import type { JSX } from 'react';
+import { Component } from 'react';
+
+import { FormattedMessage } from 'react-intl';
+
+import classNames from 'classnames';
+import { Link } from 'react-router-dom';
+
+import type Immutable from 'immutable';
+
+import { Sparklines, SparklinesCurve } from 'react-sparklines';
+
+import { ShortNumber } from 'mastodon/components/short_number';
+import { Skeleton } from 'mastodon/components/skeleton';
+
+interface SilentErrorBoundaryProps {
+  children: React.ReactNode;
+}
+
+class SilentErrorBoundary extends Component<SilentErrorBoundaryProps> {
+  state = {
+    error: false,
+  };
+
+  componentDidCatch() {
+    this.setState({ error: true });
+  }
+
+  render() {
+    if (this.state.error) {
+      return null;
+    }
+
+    return this.props.children;
+  }
+}
+
+/**
+ * Used to render counter of how much people are talking about hashtag
+ * @param displayNumber Counter number to display
+ * @param pluralReady Whether the count is plural
+ * @returns Formatted counter of how much people are talking about hashtag
+ */
+export const accountsCountRenderer = (
+  displayNumber: JSX.Element,
+  pluralReady: number,
+) => (
+  <FormattedMessage
+    id='trends.counter_by_accounts'
+    defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}'
+    values={{
+      count: pluralReady,
+      counter: <strong>{displayNumber}</strong>,
+      days: 2,
+    }}
+  />
+);
+
+interface ImmutableHashtagProps {
+  hashtag: Immutable.Map<string, unknown>;
+}
+
+export const ImmutableHashtag = ({ hashtag }: ImmutableHashtagProps) => (
+  <Hashtag
+    name={hashtag.get('name') as string}
+    to={`/tags/${hashtag.get('name') as string}`}
+    people={
+      (hashtag.getIn(['history', 0, 'accounts']) as number) * 1 +
+      (hashtag.getIn(['history', 1, 'accounts']) as number) * 1
+    }
+    history={(
+      hashtag.get('history') as Immutable.Collection.Indexed<
+        Immutable.Map<string, number>
+      >
+    )
+      .reverse()
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      .map((day) => day.get('uses')!)
+      .toArray()}
+  />
+);
+
+export interface HashtagProps {
+  className?: string;
+  description?: React.ReactNode;
+  history?: number[];
+  name: string;
+  people: number;
+  to: string;
+  uses?: number;
+  withGraph?: boolean;
+}
+
+export const Hashtag: React.FC<HashtagProps> = ({
+  name,
+  to,
+  people,
+  uses,
+  history,
+  className,
+  description,
+  withGraph = true,
+}) => (
+  <div className={classNames('trends__item', className)}>
+    <div className='trends__item__name'>
+      <Link to={to}>
+        {name ? (
+          <>
+            #<span>{name}</span>
+          </>
+        ) : (
+          <Skeleton width={50} />
+        )}
+      </Link>
+
+      {description ? (
+        <span>{description}</span>
+      ) : typeof people !== 'undefined' ? (
+        <ShortNumber value={people} renderer={accountsCountRenderer} />
+      ) : (
+        <Skeleton width={100} />
+      )}
+    </div>
+
+    {typeof uses !== 'undefined' && (
+      <div className='trends__item__current'>
+        <ShortNumber value={uses} />
+      </div>
+    )}
+
+    {withGraph && (
+      <div className='trends__item__sparkline'>
+        <SilentErrorBoundary>
+          <Sparklines
+            width={50}
+            height={28}
+            data={history ? history : Array.from(Array(7)).map(() => 0)}
+          >
+            <SparklinesCurve style={{ fill: 'none' }} />
+          </Sparklines>
+        </SilentErrorBoundary>
+      </div>
+    )}
+  </div>
+);
diff --git a/app/javascript/mastodon/features/account/components/featured_tags.jsx b/app/javascript/mastodon/features/account/components/featured_tags.jsx
index 4d7dd86560a34de8107d5c1b49125fc3bc89e7f9..56a9efac0227095e7b031fc7c69fa5b9df5038f1 100644
--- a/app/javascript/mastodon/features/account/components/featured_tags.jsx
+++ b/app/javascript/mastodon/features/account/components/featured_tags.jsx
@@ -5,7 +5,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
-import Hashtag from 'mastodon/components/hashtag';
+import { Hashtag } from 'mastodon/components/hashtag';
 
 const messages = defineMessages({
   lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' },
diff --git a/app/javascript/mastodon/features/followed_tags/index.jsx b/app/javascript/mastodon/features/followed_tags/index.jsx
index 7042f2438aa2cc9d17d9b6f3fb97220b02f8d6e5..dec53f012101075da4b7133f3d4ea8e96b4ed16e 100644
--- a/app/javascript/mastodon/features/followed_tags/index.jsx
+++ b/app/javascript/mastodon/features/followed_tags/index.jsx
@@ -13,7 +13,7 @@ import { debounce } from 'lodash';
 
 import { expandFollowedHashtags, fetchFollowedHashtags } from 'mastodon/actions/tags';
 import ColumnHeader from 'mastodon/components/column_header';
-import Hashtag from 'mastodon/components/hashtag';
+import { Hashtag } from 'mastodon/components/hashtag';
 import ScrollableList from 'mastodon/components/scrollable_list';
 import Column from 'mastodon/features/ui/components/column';