From dd9341a196fd501d421c9054631f12e5176a9f57 Mon Sep 17 00:00:00 2001
From: Audric Ackermann <audric@loki.network>
Date: Wed, 16 Jun 2021 15:26:00 +1000
Subject: [PATCH] remove i18n from props everywhere where possible

---
 ts/components/Avatar.tsx                      |  1 -
 .../AvatarPlaceHolder/ClosedGroupAvatar.tsx   |  2 -
 ts/components/ContactListItem.tsx             | 11 +--
 ts/components/ConversationListItem.tsx        | 20 ++---
 ts/components/Intl.tsx                        |  7 +-
 ts/components/MessageBodyHighlight.tsx        | 22 +----
 ts/components/MessageSearchResult.tsx         | 32 +++-----
 ts/components/SearchResults.tsx               | 19 ++---
 ts/components/conversation/AttachmentList.tsx |  2 -
 ts/components/conversation/ContactName.tsx    |  9 +-
 ts/components/conversation/Emojify.tsx        |  8 +-
 .../conversation/GroupNotification.tsx        | 15 ++--
 ts/components/conversation/Image.tsx          |  5 +-
 ts/components/conversation/ImageGrid.tsx      | 50 ++++-------
 ts/components/conversation/Message.tsx        |  6 --
 ts/components/conversation/MessageBody.tsx    | 10 +--
 ts/components/conversation/MessageDetail.tsx  |  2 -
 ts/components/conversation/Quote.tsx          | 41 ++++------
 .../conversation/StagedGenericAttachment.tsx  |  2 -
 .../conversation/StagedLinkPreview.tsx        |  8 +-
 .../conversation/TimerNotification.tsx        |  2 +-
 ts/components/conversation/Timestamp.tsx      |  2 +-
 .../conversation/TypingAnimation.tsx          |  7 +-
 ts/components/conversation/TypingBubble.tsx   |  2 +-
 .../conversation/UpdateGroupMembersDialog.tsx |  2 -
 .../media-gallery/AttachmentSection.tsx       | 11 +--
 .../media-gallery/MediaGallery.tsx            |  1 -
 .../media-gallery/MediaGridItem.tsx           |  5 +-
 ts/components/session/ActionsPanel.tsx        | 11 ++-
 .../session/LeftPaneContactSection.tsx        |  1 -
 .../session/LeftPaneMessageSection.tsx        |  2 -
 .../session/SessionPasswordModal.tsx          |  2 +-
 .../SessionQuotedMessageComposition.tsx       |  3 +-
 .../session/menu/ConversationHeaderMenu.tsx   | 38 ++++-----
 .../menu/ConversationListItemContextMenu.tsx  | 27 ++----
 ts/components/session/menu/Menu.tsx           | 82 ++++++++-----------
 .../session/registration/RegistrationTabs.tsx |  2 +-
 ts/session/types/PubKey.ts                    | 10 +--
 ts/state/ducks/conversations.ts               | 12 ---
 ts/state/ducks/user.ts                        |  4 -
 ts/state/selectors/conversations.ts           | 14 ++--
 ts/state/selectors/user.ts                    |  5 +-
 ts/types/Attachment.ts                        |  7 +-
 ts/util/formatRelativeTime.ts                 | 19 ++---
 ts/util/passwordUtils.ts                      | 10 +--
 45 files changed, 183 insertions(+), 370 deletions(-)

diff --git a/ts/components/Avatar.tsx b/ts/components/Avatar.tsx
index a39e0d47e..313d165b1 100644
--- a/ts/components/Avatar.tsx
+++ b/ts/components/Avatar.tsx
@@ -53,7 +53,6 @@ const NoImage = (props: {
       <ClosedGroupAvatar
         size={size}
         memberAvatars={memberAvatars}
-        i18n={window.i18n}
         onAvatarClick={props.onAvatarClick}
       />
     );
diff --git a/ts/components/AvatarPlaceHolder/ClosedGroupAvatar.tsx b/ts/components/AvatarPlaceHolder/ClosedGroupAvatar.tsx
index 6994652de..4dc344564 100644
--- a/ts/components/AvatarPlaceHolder/ClosedGroupAvatar.tsx
+++ b/ts/components/AvatarPlaceHolder/ClosedGroupAvatar.tsx
@@ -1,12 +1,10 @@
 import React from 'react';
 import { Avatar, AvatarSize } from '../Avatar';
-import { LocalizerType } from '../../types/Util';
 import { ConversationAvatar } from '../session/usingClosedConversationDetails';
 
 interface Props {
   size: number;
   memberAvatars: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
-  i18n: LocalizerType;
   onAvatarClick?: () => void;
 }
 
diff --git a/ts/components/ContactListItem.tsx b/ts/components/ContactListItem.tsx
index 47a58f5c5..9fd9aaf68 100644
--- a/ts/components/ContactListItem.tsx
+++ b/ts/components/ContactListItem.tsx
@@ -4,15 +4,12 @@ import classNames from 'classnames';
 import { Avatar, AvatarSize } from './Avatar';
 import { Emojify } from './conversation/Emojify';
 
-import { LocalizerType } from '../types/Util';
-
 interface Props {
   phoneNumber: string;
   isMe?: boolean;
   name?: string;
   profileName?: string;
   avatarPath?: string;
-  i18n: LocalizerType;
   onClick?: () => void;
 }
 
@@ -28,16 +25,16 @@ export class ContactListItem extends React.Component<Props> {
   }
 
   public render() {
-    const { i18n, name, onClick, isMe, phoneNumber, profileName } = this.props;
+    const { name, onClick, isMe, phoneNumber, profileName } = this.props;
 
     const title = name ? name : phoneNumber;
-    const displayName = isMe ? i18n('me') : title;
+    const displayName = isMe ? window.i18n('me') : title;
 
     const profileElement =
       !isMe && profileName && !name ? (
         <span className="module-contact-list-item__text__profile-name">
           ~
-          <Emojify text={profileName} i18n={i18n} key={`emojify-list-item-${phoneNumber}`} />
+          <Emojify text={profileName} key={`emojify-list-item-${phoneNumber}`} />
         </span>
       ) : null;
 
@@ -55,7 +52,7 @@ export class ContactListItem extends React.Component<Props> {
         {this.renderAvatar()}
         <div className="module-contact-list-item__text">
           <div className="module-contact-list-item__text__name">
-            <Emojify text={displayName} i18n={i18n} /> {profileElement}
+            <Emojify text={displayName} /> {profileElement}
           </div>
         </div>
       </div>
diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx
index 76f1ce05f..6b5ccf85d 100644
--- a/ts/components/ConversationListItem.tsx
+++ b/ts/components/ConversationListItem.tsx
@@ -9,8 +9,6 @@ import { Timestamp } from './conversation/Timestamp';
 import { ContactName } from './conversation/ContactName';
 import { TypingAnimation } from './conversation/TypingAnimation';
 
-import { LocalizerType } from '../types/Util';
-
 import {
   ConversationAvatar,
   usingClosedConversationDetails,
@@ -31,7 +29,6 @@ export interface ConversationListItemProps extends ConversationType {
 }
 
 type PropsHousekeeping = {
-  i18n: LocalizerType;
   style?: Object;
   onClick?: (id: string) => void;
   onDeleteMessages?: () => void;
@@ -120,7 +117,7 @@ class ConversationListItem extends React.PureComponent<Props> {
   }
 
   public renderMessage() {
-    const { lastMessage, isTyping, unreadCount, i18n } = this.props;
+    const { lastMessage, isTyping, unreadCount } = this.props;
 
     if (!lastMessage && !isTyping) {
       return null;
@@ -140,15 +137,9 @@ class ConversationListItem extends React.PureComponent<Props> {
           )}
         >
           {isTyping ? (
-            <TypingAnimation i18n={i18n} />
+            <TypingAnimation />
           ) : (
-            <MessageBody
-              isGroup={true}
-              text={text}
-              disableJumbomoji={true}
-              disableLinks={true}
-              i18n={i18n}
-            />
+            <MessageBody isGroup={true} text={text} disableJumbomoji={true} disableLinks={true} />
           )}
         </div>
         {lastMessage && lastMessage.status ? (
@@ -221,12 +212,12 @@ class ConversationListItem extends React.PureComponent<Props> {
   }
 
   private renderUser() {
-    const { name, phoneNumber, profileName, isMe, i18n } = this.props;
+    const { name, phoneNumber, profileName, isMe } = this.props;
 
     const shortenedPubkey = PubKey.shorten(phoneNumber);
 
     const displayedPubkey = profileName ? shortenedPubkey : phoneNumber;
-    const displayName = isMe ? i18n('noteToSelf') : profileName;
+    const displayName = isMe ? window.i18n('noteToSelf') : profileName;
 
     let shouldShowPubkey = false;
     if ((!name || name.length === 0) && (!displayName || displayName.length === 0)) {
@@ -240,7 +231,6 @@ class ConversationListItem extends React.PureComponent<Props> {
           name={name}
           profileName={displayName}
           module="module-conversation__user"
-          i18n={window.i18n}
           boldProfileName={true}
           shouldShowPubkey={shouldShowPubkey}
         />
diff --git a/ts/components/Intl.tsx b/ts/components/Intl.tsx
index 01d5b000a..f8ddfb5df 100644
--- a/ts/components/Intl.tsx
+++ b/ts/components/Intl.tsx
@@ -1,13 +1,12 @@
 import React from 'react';
 
-import { LocalizerType, RenderTextCallbackType } from '../types/Util';
+import { RenderTextCallbackType } from '../types/Util';
 
 type FullJSX = Array<JSX.Element | string> | JSX.Element | string;
 
 interface Props {
   /** The translation string id */
   id: string;
-  i18n: LocalizerType;
   components?: Array<FullJSX>;
   renderText?: RenderTextCallbackType;
 }
@@ -31,9 +30,9 @@ export class Intl extends React.Component<Props> {
   }
 
   public render() {
-    const { id, i18n, renderText } = this.props;
+    const { id, renderText } = this.props;
 
-    const text = i18n(id);
+    const text = window.i18n(id);
     const results: Array<any> = [];
     const FIND_REPLACEMENTS = /\$[^$]+\$/g;
 
diff --git a/ts/components/MessageBodyHighlight.tsx b/ts/components/MessageBodyHighlight.tsx
index 53cce10c4..2bf6e39ec 100644
--- a/ts/components/MessageBodyHighlight.tsx
+++ b/ts/components/MessageBodyHighlight.tsx
@@ -6,11 +6,10 @@ import { AddNewLines } from './conversation/AddNewLines';
 
 import { SizeClassType } from '../util/emoji';
 
-import { LocalizerType, RenderTextCallbackType } from '../types/Util';
+import { RenderTextCallbackType } from '../types/Util';
 
 interface Props {
   text: string;
-  i18n: LocalizerType;
 }
 
 const renderNewLines: RenderTextCallbackType = ({ text, key }) => (
@@ -18,30 +17,20 @@ const renderNewLines: RenderTextCallbackType = ({ text, key }) => (
 );
 
 const renderEmoji = ({
-  i18n,
   text,
   key,
   sizeClass,
   renderNonEmoji,
 }: {
-  i18n: LocalizerType;
   text: string;
   key: number;
   sizeClass?: SizeClassType;
   renderNonEmoji: RenderTextCallbackType;
-}) => (
-  <Emojify
-    i18n={i18n}
-    key={key}
-    text={text}
-    sizeClass={sizeClass}
-    renderNonEmoji={renderNonEmoji}
-  />
-);
+}) => <Emojify key={key} text={text} sizeClass={sizeClass} renderNonEmoji={renderNonEmoji} />;
 
 export class MessageBodyHighlight extends React.Component<Props> {
   public render() {
-    const { text, i18n } = this.props;
+    const { text } = this.props;
     const results: Array<any> = [];
     const FIND_BEGIN_END = /<<left>>(.+?)<<right>>/g;
 
@@ -50,7 +39,7 @@ export class MessageBodyHighlight extends React.Component<Props> {
     let count = 1;
 
     if (!match) {
-      return <MessageBody disableJumbomoji={true} disableLinks={true} text={text} i18n={i18n} />;
+      return <MessageBody disableJumbomoji={true} disableLinks={true} text={text} />;
     }
 
     const sizeClass = '';
@@ -63,7 +52,6 @@ export class MessageBodyHighlight extends React.Component<Props> {
             text: beforeText,
             sizeClass,
             key: count++,
-            i18n,
             renderNonEmoji: renderNewLines,
           })
         );
@@ -76,7 +64,6 @@ export class MessageBodyHighlight extends React.Component<Props> {
             text: toHighlight,
             sizeClass,
             key: count++,
-            i18n,
             renderNonEmoji: renderNewLines,
           })}
         </span>
@@ -93,7 +80,6 @@ export class MessageBodyHighlight extends React.Component<Props> {
           text: text.slice(last),
           sizeClass,
           key: count++,
-          i18n,
           renderNonEmoji: renderNewLines,
         })
       );
diff --git a/ts/components/MessageSearchResult.tsx b/ts/components/MessageSearchResult.tsx
index 8b1a9c43d..7a7111cc4 100644
--- a/ts/components/MessageSearchResult.tsx
+++ b/ts/components/MessageSearchResult.tsx
@@ -6,7 +6,6 @@ import { MessageBodyHighlight } from './MessageBodyHighlight';
 import { Timestamp } from './conversation/Timestamp';
 import { ContactName } from './conversation/ContactName';
 
-import { LocalizerType } from '../types/Util';
 import { DefaultTheme, withTheme } from 'styled-components';
 
 export type MessageSearchResultProps = {
@@ -37,7 +36,6 @@ export type MessageSearchResultProps = {
 type PropsHousekeeping = {
   isSelected?: boolean;
   theme: DefaultTheme;
-  i18n: LocalizerType;
   onClick: (conversationId: string, messageId?: string) => void;
 };
 
@@ -45,15 +43,19 @@ type Props = MessageSearchResultProps & PropsHousekeeping;
 
 class MessageSearchResultInner extends React.PureComponent<Props> {
   public renderFromName() {
-    const { from, i18n, to } = this.props;
+    const { from, to } = this.props;
 
     if (from.isMe && to.isMe) {
       return (
-        <span className="module-message-search-result__header__name">{i18n('noteToSelf')}</span>
+        <span className="module-message-search-result__header__name">
+          {window.i18n('noteToSelf')}
+        </span>
       );
     }
     if (from.isMe) {
-      return <span className="module-message-search-result__header__name">{i18n('you')}</span>;
+      return (
+        <span className="module-message-search-result__header__name">{window.i18n('you')}</span>
+      );
     }
 
     return (
@@ -61,7 +63,6 @@ class MessageSearchResultInner extends React.PureComponent<Props> {
         phoneNumber={from.phoneNumber}
         name={from.name}
         profileName={from.profileName}
-        i18n={i18n}
         module="module-message-search-result__header__name"
         shouldShowPubkey={false}
       />
@@ -69,19 +70,18 @@ class MessageSearchResultInner extends React.PureComponent<Props> {
   }
 
   public renderFrom() {
-    const { i18n, to } = this.props;
+    const { to } = this.props;
     const fromName = this.renderFromName();
 
     if (!to.isMe) {
       return (
         <div className="module-message-search-result__header__from">
-          {fromName} {i18n('to')}{' '}
+          {fromName} {window.i18n('to')}{' '}
           <span className="module-mesages-search-result__header__group">
             <ContactName
               phoneNumber={to.phoneNumber}
               name={to.name}
               profileName={to.profileName}
-              i18n={i18n}
               shouldShowPubkey={false}
             />
           </span>
@@ -107,17 +107,7 @@ class MessageSearchResultInner extends React.PureComponent<Props> {
   }
 
   public render() {
-    const {
-      from,
-      i18n,
-      id,
-      isSelected,
-      conversationId,
-      onClick,
-      receivedAt,
-      snippet,
-      to,
-    } = this.props;
+    const { from, id, isSelected, conversationId, onClick, receivedAt, snippet, to } = this.props;
 
     if (!from || !to) {
       return null;
@@ -145,7 +135,7 @@ class MessageSearchResultInner extends React.PureComponent<Props> {
             </div>
           </div>
           <div className="module-message-search-result__body">
-            <MessageBodyHighlight text={snippet} i18n={i18n} />
+            <MessageBodyHighlight text={snippet} />
           </div>
         </div>
       </div>
diff --git a/ts/components/SearchResults.tsx b/ts/components/SearchResults.tsx
index f36100021..3c4fa0162 100644
--- a/ts/components/SearchResults.tsx
+++ b/ts/components/SearchResults.tsx
@@ -2,8 +2,6 @@ import React from 'react';
 import { ConversationListItemProps, ConversationListItemWithDetails } from './ConversationListItem';
 import { MessageSearchResult, MessageSearchResultProps } from './MessageSearchResult';
 
-import { LocalizerType } from '../types/Util';
-
 export type SearchResultsProps = {
   contacts: Array<ConversationListItemProps>;
   conversations: Array<ConversationListItemProps>;
@@ -13,7 +11,6 @@ export type SearchResultsProps = {
 };
 
 type PropsHousekeeping = {
-  i18n: LocalizerType;
   openConversationExternal: (id: string, messageId?: string) => void;
 };
 
@@ -25,7 +22,6 @@ export class SearchResults extends React.Component<Props> {
       conversations,
       contacts,
       hideMessagesHeader,
-      i18n,
       messages,
       openConversationExternal,
       searchTerm,
@@ -40,37 +36,37 @@ export class SearchResults extends React.Component<Props> {
       <div className="module-search-results">
         {noResults ? (
           <div className="module-search-results__no-results">
-            {i18n('noSearchResults', [searchTerm])}
+            {window.i18n('noSearchResults', [searchTerm])}
           </div>
         ) : null}
         {haveConversations ? (
           <div className="module-search-results__conversations">
             <div className="module-search-results__conversations-header">
-              {i18n('conversationsHeader')}
+              {window.i18n('conversationsHeader')}
             </div>
             {conversations.map(conversation => (
               <ConversationListItemWithDetails
                 key={conversation.phoneNumber}
                 {...conversation}
                 onClick={openConversationExternal}
-                i18n={i18n}
               />
             ))}
           </div>
         ) : null}
-        {haveContacts ? this.renderContacts(i18n('contactsHeader'), contacts) : null}
+        {haveContacts ? this.renderContacts(window.i18n('contactsHeader'), contacts) : null}
 
         {haveMessages ? (
           <div className="module-search-results__messages">
             {hideMessagesHeader ? null : (
-              <div className="module-search-results__messages-header">{i18n('messagesHeader')}</div>
+              <div className="module-search-results__messages-header">
+                {window.i18n('messagesHeader')}
+              </div>
             )}
             {messages.map(message => (
               <MessageSearchResult
                 key={message.id}
                 {...message}
                 onClick={openConversationExternal}
-                i18n={i18n}
               />
             ))}
           </div>
@@ -79,7 +75,7 @@ export class SearchResults extends React.Component<Props> {
     );
   }
   private renderContacts(header: string, items: Array<ConversationListItemProps>) {
-    const { i18n, openConversationExternal } = this.props;
+    const { openConversationExternal } = this.props;
 
     return (
       <div className="module-search-results__contacts">
@@ -89,7 +85,6 @@ export class SearchResults extends React.Component<Props> {
             key={contact.phoneNumber}
             {...contact}
             onClick={openConversationExternal}
-            i18n={i18n}
           />
         ))}
       </div>
diff --git a/ts/components/conversation/AttachmentList.tsx b/ts/components/conversation/AttachmentList.tsx
index dd25cd262..809f460b4 100644
--- a/ts/components/conversation/AttachmentList.tsx
+++ b/ts/components/conversation/AttachmentList.tsx
@@ -58,7 +58,6 @@ export class AttachmentList extends React.Component<Props> {
                 <Image
                   key={imageKey}
                   alt={window.i18n('stagedImageAttachment', [attachment.fileName])}
-                  i18n={window.i18n}
                   attachment={attachment}
                   softCorners={true}
                   playIconOverlay={isVideoAttachment(attachment)}
@@ -78,7 +77,6 @@ export class AttachmentList extends React.Component<Props> {
               <StagedGenericAttachment
                 key={genericKey}
                 attachment={attachment}
-                i18n={window.i18n}
                 onClose={onCloseAttachment}
               />
             );
diff --git a/ts/components/conversation/ContactName.tsx b/ts/components/conversation/ContactName.tsx
index ecbe6900d..f897db492 100644
--- a/ts/components/conversation/ContactName.tsx
+++ b/ts/components/conversation/ContactName.tsx
@@ -2,13 +2,11 @@ import React from 'react';
 import classNames from 'classnames';
 
 import { Emojify } from './Emojify';
-import { LocalizerType } from '../../types/Util';
 
 type Props = {
   phoneNumber: string;
   name?: string;
   profileName?: string;
-  i18n: LocalizerType;
   module?: string;
   boldProfileName?: Boolean;
   compact?: Boolean;
@@ -20,7 +18,6 @@ export const ContactName = (props: Props) => {
     phoneNumber,
     name,
     profileName,
-    i18n,
     module,
     boldProfileName,
     compact,
@@ -35,16 +32,16 @@ export const ContactName = (props: Props) => {
         fontWeight: 'bold',
       }
     : {}) as React.CSSProperties;
-  const textProfile = profileName || name || i18n('anonymous');
+  const textProfile = profileName || name || window.i18n('anonymous');
   const profileElement = shouldShowProfile ? (
     <span style={styles} className={`${prefix}__profile-name`}>
-      <Emojify text={textProfile} i18n={i18n} />
+      <Emojify text={textProfile} />
     </span>
   ) : null;
 
   const pubKeyElement = shouldShowPubkey ? (
     <span className={`${prefix}__profile-number`}>
-      <Emojify text={title} i18n={i18n} />
+      <Emojify text={title} />
     </span>
   ) : null;
 
diff --git a/ts/components/conversation/Emojify.tsx b/ts/components/conversation/Emojify.tsx
index 839fdd1c0..9fa41a70d 100644
--- a/ts/components/conversation/Emojify.tsx
+++ b/ts/components/conversation/Emojify.tsx
@@ -1,11 +1,8 @@
 import React from 'react';
 
-import classNames from 'classnames';
-import is from '@sindresorhus/is';
-
 import { getRegex, SizeClassType } from '../../util/emoji';
 
-import { LocalizerType, RenderTextCallbackType } from '../../types/Util';
+import { RenderTextCallbackType } from '../../types/Util';
 import { Twemoji } from 'react-emoji-render';
 
 interface Props {
@@ -14,7 +11,6 @@ interface Props {
   sizeClass?: SizeClassType;
   /** Allows you to customize now non-newlines are rendered. Simplest is just a <span>. */
   renderNonEmoji?: RenderTextCallbackType;
-  i18n: LocalizerType;
   isGroup?: boolean;
   convoId: string;
 }
@@ -26,7 +22,7 @@ export class Emojify extends React.Component<Props> {
   };
 
   public render() {
-    const { text, sizeClass, renderNonEmoji, i18n, isGroup, convoId } = this.props;
+    const { text, sizeClass, renderNonEmoji, isGroup, convoId } = this.props;
     const results: Array<any> = [];
     const regex = getRegex();
 
diff --git a/ts/components/conversation/GroupNotification.tsx b/ts/components/conversation/GroupNotification.tsx
index d54997ef8..00d780742 100644
--- a/ts/components/conversation/GroupNotification.tsx
+++ b/ts/components/conversation/GroupNotification.tsx
@@ -26,7 +26,6 @@ type Props = {
 export const GroupNotification = (props: Props) => {
   function renderChange(change: Change) {
     const { isMe, contacts, type, newName } = change;
-    const { i18n } = window;
 
     const people = compact(
       flatten(
@@ -47,7 +46,7 @@ export const GroupNotification = (props: Props) => {
 
     switch (type) {
       case 'name':
-        return `${i18n('titleIsNow', [newName || ''])}.`;
+        return `${window.i18n('titleIsNow', [newName || ''])}.`;
       case 'add':
         if (!contacts || !contacts.length) {
           throw new Error('Group update add is missing contacts');
@@ -55,10 +54,10 @@ export const GroupNotification = (props: Props) => {
 
         const joinKey = contacts.length > 1 ? 'multipleJoinedTheGroup' : 'joinedTheGroup';
 
-        return <Intl i18n={i18n} id={joinKey} components={[people]} />;
+        return <Intl id={joinKey} components={[people]} />;
       case 'remove':
         if (isMe) {
-          return i18n('youLeftTheGroup');
+          return window.i18n('youLeftTheGroup');
         }
 
         if (!contacts || !contacts.length) {
@@ -67,10 +66,10 @@ export const GroupNotification = (props: Props) => {
 
         const leftKey = contacts.length > 1 ? 'multipleLeftTheGroup' : 'leftTheGroup';
 
-        return <Intl i18n={i18n} id={leftKey} components={[people]} />;
+        return <Intl id={leftKey} components={[people]} />;
       case 'kicked':
         if (isMe) {
-          return i18n('youGotKickedFromGroup');
+          return window.i18n('youGotKickedFromGroup');
         }
 
         if (!contacts || !contacts.length) {
@@ -79,9 +78,9 @@ export const GroupNotification = (props: Props) => {
 
         const kickedKey = contacts.length > 1 ? 'multipleKickedFromTheGroup' : 'kickedFromTheGroup';
 
-        return <Intl i18n={i18n} id={kickedKey} components={[people]} />;
+        return <Intl id={kickedKey} components={[people]} />;
       case 'general':
-        return i18n('updatedTheGroup');
+        return window.i18n('updatedTheGroup');
       default:
         throw missingCaseError(type);
     }
diff --git a/ts/components/conversation/Image.tsx b/ts/components/conversation/Image.tsx
index e9a31eac3..76dc1c687 100644
--- a/ts/components/conversation/Image.tsx
+++ b/ts/components/conversation/Image.tsx
@@ -2,7 +2,6 @@ import React from 'react';
 import classNames from 'classnames';
 
 import { Spinner } from '../basic/Spinner';
-import { LocalizerType } from '../../types/Util';
 import { AttachmentType } from '../../types/Attachment';
 import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
 
@@ -29,7 +28,6 @@ type Props = {
   playIconOverlay?: boolean;
   softCorners?: boolean;
 
-  i18n: LocalizerType;
   onClick?: (attachment: AttachmentType) => void;
   onClickClose?: (attachment: AttachmentType) => void;
   onError?: () => void;
@@ -48,7 +46,6 @@ export const Image = (props: Props) => {
     curveTopRight,
     darkOverlay,
     height,
-    i18n,
     onClick,
     onClickClose,
     onError,
@@ -114,7 +111,7 @@ export const Image = (props: Props) => {
         <img
           className="module-image__caption-icon"
           src="images/caption-shadow.svg"
-          alt={i18n('imageCaptionIconAlt')}
+          alt={window.i18n('imageCaptionIconAlt')}
         />
       ) : null}
       <div
diff --git a/ts/components/conversation/ImageGrid.tsx b/ts/components/conversation/ImageGrid.tsx
index 2d77d845b..6b74da56c 100644
--- a/ts/components/conversation/ImageGrid.tsx
+++ b/ts/components/conversation/ImageGrid.tsx
@@ -13,16 +13,12 @@ import {
 
 import { Image } from './Image';
 
-import { LocalizerType } from '../../types/Util';
-
 type Props = {
   attachments: Array<AttachmentType>;
   withContentAbove?: boolean;
   withContentBelow?: boolean;
   bottomOverlay?: boolean;
 
-  i18n: LocalizerType;
-
   onError: () => void;
   onClickAttachment?: (attachment: AttachmentType) => void;
 };
@@ -32,7 +28,6 @@ export const ImageGrid = (props: Props) => {
   const {
     attachments,
     bottomOverlay,
-    i18n,
     onError,
     onClickAttachment,
     withContentAbove,
@@ -58,8 +53,7 @@ export const ImageGrid = (props: Props) => {
     return (
       <div className={classNames('module-image-grid', 'module-image-grid--one-image')}>
         <Image
-          alt={getAlt(attachments[0], i18n)}
-          i18n={i18n}
+          alt={getAlt(attachments[0])}
           bottomOverlay={withBottomOverlay}
           curveTopLeft={curveTopLeft}
           curveTopRight={curveTopRight}
@@ -81,8 +75,7 @@ export const ImageGrid = (props: Props) => {
     return (
       <div className="module-image-grid">
         <Image
-          alt={getAlt(attachments[0], i18n)}
-          i18n={i18n}
+          alt={getAlt(attachments[0])}
           attachment={attachments[0]}
           bottomOverlay={withBottomOverlay}
           curveTopLeft={curveTopLeft}
@@ -95,8 +88,7 @@ export const ImageGrid = (props: Props) => {
           onError={onError}
         />
         <Image
-          alt={getAlt(attachments[1], i18n)}
-          i18n={i18n}
+          alt={getAlt(attachments[1])}
           bottomOverlay={withBottomOverlay}
           curveTopRight={curveTopRight}
           curveBottomRight={curveBottomRight}
@@ -116,8 +108,7 @@ export const ImageGrid = (props: Props) => {
     return (
       <div className="module-image-grid">
         <Image
-          alt={getAlt(attachments[0], i18n)}
-          i18n={i18n}
+          alt={getAlt(attachments[0])}
           bottomOverlay={withBottomOverlay}
           curveTopLeft={curveTopLeft}
           curveBottomLeft={curveBottomLeft}
@@ -131,8 +122,7 @@ export const ImageGrid = (props: Props) => {
         />
         <div className="module-image-grid__column">
           <Image
-            alt={getAlt(attachments[1], i18n)}
-            i18n={i18n}
+            alt={getAlt(attachments[1])}
             curveTopRight={curveTopRight}
             height={99}
             width={99}
@@ -143,8 +133,7 @@ export const ImageGrid = (props: Props) => {
             onError={onError}
           />
           <Image
-            alt={getAlt(attachments[2], i18n)}
-            i18n={i18n}
+            alt={getAlt(attachments[2])}
             bottomOverlay={withBottomOverlay}
             curveBottomRight={curveBottomRight}
             height={99}
@@ -166,8 +155,7 @@ export const ImageGrid = (props: Props) => {
         <div className="module-image-grid__column">
           <div className="module-image-grid__row">
             <Image
-              alt={getAlt(attachments[0], i18n)}
-              i18n={i18n}
+              alt={getAlt(attachments[0])}
               curveTopLeft={curveTopLeft}
               attachment={attachments[0]}
               playIconOverlay={isVideoAttachment(attachments[0])}
@@ -178,8 +166,7 @@ export const ImageGrid = (props: Props) => {
               onError={onError}
             />
             <Image
-              alt={getAlt(attachments[1], i18n)}
-              i18n={i18n}
+              alt={getAlt(attachments[1])}
               curveTopRight={curveTopRight}
               playIconOverlay={isVideoAttachment(attachments[1])}
               height={149}
@@ -192,8 +179,7 @@ export const ImageGrid = (props: Props) => {
           </div>
           <div className="module-image-grid__row">
             <Image
-              alt={getAlt(attachments[2], i18n)}
-              i18n={i18n}
+              alt={getAlt(attachments[2])}
               bottomOverlay={withBottomOverlay}
               curveBottomLeft={curveBottomLeft}
               playIconOverlay={isVideoAttachment(attachments[2])}
@@ -205,8 +191,7 @@ export const ImageGrid = (props: Props) => {
               onError={onError}
             />
             <Image
-              alt={getAlt(attachments[3], i18n)}
-              i18n={i18n}
+              alt={getAlt(attachments[3])}
               bottomOverlay={withBottomOverlay}
               curveBottomRight={curveBottomRight}
               playIconOverlay={isVideoAttachment(attachments[3])}
@@ -231,8 +216,7 @@ export const ImageGrid = (props: Props) => {
       <div className="module-image-grid__column">
         <div className="module-image-grid__row">
           <Image
-            alt={getAlt(attachments[0], i18n)}
-            i18n={i18n}
+            alt={getAlt(attachments[0])}
             curveTopLeft={curveTopLeft}
             attachment={attachments[0]}
             playIconOverlay={isVideoAttachment(attachments[0])}
@@ -243,8 +227,7 @@ export const ImageGrid = (props: Props) => {
             onError={onError}
           />
           <Image
-            alt={getAlt(attachments[1], i18n)}
-            i18n={i18n}
+            alt={getAlt(attachments[1])}
             curveTopRight={curveTopRight}
             playIconOverlay={isVideoAttachment(attachments[1])}
             height={149}
@@ -257,8 +240,7 @@ export const ImageGrid = (props: Props) => {
         </div>
         <div className="module-image-grid__row">
           <Image
-            alt={getAlt(attachments[2], i18n)}
-            i18n={i18n}
+            alt={getAlt(attachments[2])}
             bottomOverlay={withBottomOverlay}
             curveBottomLeft={curveBottomLeft}
             playIconOverlay={isVideoAttachment(attachments[2])}
@@ -270,8 +252,7 @@ export const ImageGrid = (props: Props) => {
             onError={onError}
           />
           <Image
-            alt={getAlt(attachments[3], i18n)}
-            i18n={i18n}
+            alt={getAlt(attachments[3])}
             bottomOverlay={withBottomOverlay}
             playIconOverlay={isVideoAttachment(attachments[3])}
             height={99}
@@ -282,8 +263,7 @@ export const ImageGrid = (props: Props) => {
             onError={onError}
           />
           <Image
-            alt={getAlt(attachments[4], i18n)}
-            i18n={i18n}
+            alt={getAlt(attachments[4])}
             bottomOverlay={withBottomOverlay}
             curveBottomRight={curveBottomRight}
             playIconOverlay={isVideoAttachment(attachments[4])}
diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx
index d3f181ca7..9e98e116f 100644
--- a/ts/components/conversation/Message.tsx
+++ b/ts/components/conversation/Message.tsx
@@ -216,7 +216,6 @@ class MessageInner extends React.PureComponent<MessageRegularProps, State> {
             withContentAbove={withContentAbove}
             withContentBelow={withContentBelow}
             bottomOverlay={!collapseMetadata}
-            i18n={window.i18n}
             onError={this.handleImageErrorBound}
             onClickAttachment={(attachment: AttachmentType) => {
               if (multiSelectMode) {
@@ -358,7 +357,6 @@ class MessageInner extends React.PureComponent<MessageRegularProps, State> {
             withContentAbove={withContentAbove}
             withContentBelow={true}
             onError={this.handleImageErrorBound}
-            i18n={window.i18n}
           />
         ) : null}
         <div
@@ -380,7 +378,6 @@ class MessageInner extends React.PureComponent<MessageRegularProps, State> {
                 url={first.image.url}
                 attachment={first.image}
                 onError={this.handleImageErrorBound}
-                i18n={window.i18n}
               />
             </div>
           ) : null}
@@ -423,7 +420,6 @@ class MessageInner extends React.PureComponent<MessageRegularProps, State> {
 
     return (
       <Quote
-        i18n={window.i18n}
         onClick={(e: any) => {
           e.preventDefault();
           e.stopPropagation();
@@ -519,7 +515,6 @@ class MessageInner extends React.PureComponent<MessageRegularProps, State> {
       >
         <MessageBody
           text={contents || ''}
-          i18n={window.i18n}
           isGroup={conversationType === 'group'}
           convoId={convoId}
           disableLinks={multiSelectMode}
@@ -872,7 +867,6 @@ class MessageInner extends React.PureComponent<MessageRegularProps, State> {
           name={authorName}
           profileName={authorProfileName}
           module="module-message__author"
-          i18n={window.i18n}
           boldProfileName={true}
           shouldShowPubkey={Boolean(isPublic)}
         />
diff --git a/ts/components/conversation/MessageBody.tsx b/ts/components/conversation/MessageBody.tsx
index a8347c9ab..a68e2622d 100644
--- a/ts/components/conversation/MessageBody.tsx
+++ b/ts/components/conversation/MessageBody.tsx
@@ -6,7 +6,7 @@ import { AddNewLines } from './AddNewLines';
 import { AddMentions } from './AddMentions';
 import { Linkify } from './Linkify';
 
-import { LocalizerType, RenderTextCallbackType } from '../../types/Util';
+import { RenderTextCallbackType } from '../../types/Util';
 
 interface Props {
   text: string;
@@ -15,7 +15,6 @@ interface Props {
   /** If set, links will be left alone instead of turned into clickable `<a>` tags. */
   disableLinks?: boolean;
   isGroup?: boolean;
-  i18n: LocalizerType;
   convoId: string;
 }
 
@@ -44,7 +43,6 @@ const renderNewLines: RenderTextCallbackType = ({
 };
 
 const renderEmoji = ({
-  i18n,
   text,
   key,
   sizeClass,
@@ -52,7 +50,6 @@ const renderEmoji = ({
   isGroup,
   convoId,
 }: {
-  i18n: LocalizerType;
   text: string;
   key: number;
   sizeClass?: SizeClassType;
@@ -61,7 +58,6 @@ const renderEmoji = ({
   convoId?: string;
 }) => (
   <Emojify
-    i18n={i18n}
     key={key}
     text={text}
     sizeClass={sizeClass}
@@ -87,13 +83,12 @@ export class MessageBody extends React.Component<Props> {
   }
 
   public render() {
-    const { text, disableJumbomoji, disableLinks, i18n, isGroup, convoId } = this.props;
+    const { text, disableJumbomoji, disableLinks, isGroup, convoId } = this.props;
     const sizeClass = disableJumbomoji ? undefined : getSizeClass(text);
 
     if (disableLinks) {
       return this.renderJsxSelectable(
         renderEmoji({
-          i18n,
           text,
           sizeClass,
           key: 0,
@@ -109,7 +104,6 @@ export class MessageBody extends React.Component<Props> {
         text={text}
         renderNonLink={({ key, text: nonLinkText }) => {
           return renderEmoji({
-            i18n,
             text: nonLinkText,
             sizeClass,
             key,
diff --git a/ts/components/conversation/MessageDetail.tsx b/ts/components/conversation/MessageDetail.tsx
index 7349179ab..3654e717c 100644
--- a/ts/components/conversation/MessageDetail.tsx
+++ b/ts/components/conversation/MessageDetail.tsx
@@ -60,7 +60,6 @@ export class MessageDetail extends React.Component<Props> {
   }
 
   public renderContact(contact: Contact) {
-    const { i18n } = window;
     const errors = contact.errors || [];
 
     const statusComponent = !contact.isOutgoingKeyError ? (
@@ -81,7 +80,6 @@ export class MessageDetail extends React.Component<Props> {
               phoneNumber={contact.phoneNumber}
               name={contact.name}
               profileName={contact.profileName}
-              i18n={i18n}
               shouldShowPubkey={true}
             />
           </div>
diff --git a/ts/components/conversation/Quote.tsx b/ts/components/conversation/Quote.tsx
index d5b4ce05e..539c63f53 100644
--- a/ts/components/conversation/Quote.tsx
+++ b/ts/components/conversation/Quote.tsx
@@ -7,7 +7,6 @@ import * as MIME from '../../../ts/types/MIME';
 import * as GoogleChrome from '../../../ts/util/GoogleChrome';
 
 import { MessageBody } from './MessageBody';
-import { ColorType, LocalizerType } from '../../types/Util';
 import { ContactName } from './ContactName';
 import { PubKey } from '../../session/types';
 import { ConversationTypeEnum } from '../../models/conversation';
@@ -19,7 +18,6 @@ interface QuoteProps {
   authorPhoneNumber: string;
   authorProfileName?: string;
   authorName?: string;
-  i18n: LocalizerType;
   isFromMe: boolean;
   isIncoming: boolean;
   conversationType: ConversationTypeEnum;
@@ -66,25 +64,23 @@ function getObjectUrl(thumbnail: Attachment | undefined): string | undefined {
 }
 
 function getTypeLabel({
-  i18n,
   contentType,
   isVoiceMessage,
 }: {
-  i18n: LocalizerType;
   contentType: MIME.MIMEType;
   isVoiceMessage: boolean;
 }): string | undefined {
   if (GoogleChrome.isVideoTypeSupported(contentType)) {
-    return i18n('video');
+    return window.i18n('video');
   }
   if (GoogleChrome.isImageTypeSupported(contentType)) {
-    return i18n('photo');
+    return window.i18n('photo');
   }
   if (MIME.isAudio(contentType) && isVoiceMessage) {
-    return i18n('voiceMessage');
+    return window.i18n('voiceMessage');
   }
   if (MIME.isAudio(contentType)) {
-    return i18n('audio');
+    return window.i18n('audio');
   }
 
   return;
@@ -109,7 +105,7 @@ export const QuoteIcon = (props: any) => {
 };
 
 export const QuoteImage = (props: any) => {
-  const { url, i18n, icon, contentType, handleImageErrorBound } = props;
+  const { url, icon, contentType, handleImageErrorBound } = props;
 
   const { loading, urlToLoad } = useEncryptedFileFetch(url, contentType);
   const srcData = !loading ? urlToLoad : '';
@@ -129,7 +125,7 @@ export const QuoteImage = (props: any) => {
 
   return (
     <div className="module-quote__icon-container">
-      <img src={srcData} alt={i18n('quoteThumbnailAlt')} onError={handleImageErrorBound} />
+      <img src={srcData} alt={window.i18n('quoteThumbnailAlt')} onError={handleImageErrorBound} />
       {iconElement}
     </div>
   );
@@ -168,7 +164,7 @@ export const QuoteGenericFile = (props: any) => {
 };
 
 export const QuoteIconContainer = (props: any) => {
-  const { attachment, i18n, imageBroken, handleImageErrorBound } = props;
+  const { attachment, imageBroken, handleImageErrorBound } = props;
 
   if (!attachment) {
     return null;
@@ -179,7 +175,7 @@ export const QuoteIconContainer = (props: any) => {
 
   if (GoogleChrome.isVideoTypeSupported(contentType)) {
     return objectUrl && !imageBroken ? (
-      <QuoteImage url={objectUrl} i18n={i18n} icon={'play'} />
+      <QuoteImage url={objectUrl} icon={'play'} />
     ) : (
       <QuoteIcon icon="movie" />
     );
@@ -188,7 +184,6 @@ export const QuoteIconContainer = (props: any) => {
     return objectUrl && !imageBroken ? (
       <QuoteImage
         url={objectUrl}
-        i18n={i18n}
         contentType={contentType}
         handleImageErrorBound={handleImageErrorBound}
       />
@@ -203,7 +198,7 @@ export const QuoteIconContainer = (props: any) => {
 };
 
 export const QuoteText = (props: any) => {
-  const { i18n, text, attachment, isIncoming, conversationType, convoId } = props;
+  const { text, attachment, isIncoming, conversationType, convoId } = props;
   const isGroup = conversationType === ConversationTypeEnum.GROUP;
 
   if (text) {
@@ -215,13 +210,7 @@ export const QuoteText = (props: any) => {
           isIncoming ? 'module-quote__primary__text--incoming' : null
         )}
       >
-        <MessageBody
-          isGroup={isGroup}
-          convoId={convoId}
-          text={text}
-          disableLinks={true}
-          i18n={i18n}
-        />
+        <MessageBody isGroup={isGroup} convoId={convoId} text={text} disableLinks={true} />
       </div>
     );
   }
@@ -232,7 +221,7 @@ export const QuoteText = (props: any) => {
 
   const { contentType, isVoiceMessage } = attachment;
 
-  const typeLabel = getTypeLabel({ i18n, contentType, isVoiceMessage });
+  const typeLabel = getTypeLabel({ contentType, isVoiceMessage });
   if (typeLabel) {
     return (
       <div
@@ -254,7 +243,6 @@ export const QuoteAuthor = (props: any) => {
     authorProfileName,
     authorPhoneNumber,
     authorName,
-    i18n,
     isFromMe,
     isIncoming,
     isPublic,
@@ -268,13 +256,12 @@ export const QuoteAuthor = (props: any) => {
       )}
     >
       {isFromMe ? (
-        i18n('you')
+        window.i18n('you')
       ) : (
         <ContactName
           phoneNumber={PubKey.shorten(authorPhoneNumber)}
           name={authorName}
           profileName={authorProfileName}
-          i18n={i18n}
           compact={true}
           shouldShowPubkey={Boolean(isPublic)}
         />
@@ -284,7 +271,7 @@ export const QuoteAuthor = (props: any) => {
 };
 
 export const QuoteReferenceWarning = (props: any) => {
-  const { i18n, isIncoming, referencedMessageNotFound } = props;
+  const { isIncoming, referencedMessageNotFound } = props;
 
   if (!referencedMessageNotFound) {
     return null;
@@ -309,7 +296,7 @@ export const QuoteReferenceWarning = (props: any) => {
           isIncoming ? 'module-quote__reference-warning__text--incoming' : null
         )}
       >
-        {i18n('originalMessageNotFound')}
+        {window.i18n('originalMessageNotFound')}
       </div>
     </div>
   );
diff --git a/ts/components/conversation/StagedGenericAttachment.tsx b/ts/components/conversation/StagedGenericAttachment.tsx
index 9e7903081..7ce239c49 100644
--- a/ts/components/conversation/StagedGenericAttachment.tsx
+++ b/ts/components/conversation/StagedGenericAttachment.tsx
@@ -1,12 +1,10 @@
 import React from 'react';
 
 import { AttachmentType, getExtensionForDisplay } from '../../types/Attachment';
-import { LocalizerType } from '../../types/Util';
 
 interface Props {
   attachment: AttachmentType;
   onClose: (attachment: AttachmentType) => void;
-  i18n: LocalizerType;
 }
 
 export class StagedGenericAttachment extends React.Component<Props> {
diff --git a/ts/components/conversation/StagedLinkPreview.tsx b/ts/components/conversation/StagedLinkPreview.tsx
index cb913fe11..823fb84db 100644
--- a/ts/components/conversation/StagedLinkPreview.tsx
+++ b/ts/components/conversation/StagedLinkPreview.tsx
@@ -20,7 +20,6 @@ export const StagedLinkPreview = (props: Props) => {
   const { isLoaded, onClose, title, image, domain, description, url } = props;
 
   const isImage = image && isImageAttachment(image);
-  const i18n = window.i18n;
   if (isLoaded && !(title && domain)) {
     return <></>;
   }
@@ -33,18 +32,17 @@ export const StagedLinkPreview = (props: Props) => {
       )}
     >
       {!isLoaded ? (
-        <div className="module-staged-link-preview__loading">{i18n('loading')}</div>
+        <div className="module-staged-link-preview__loading">{window.i18n('loading')}</div>
       ) : null}
       {isLoaded && image && isImage ? (
         <div className="module-staged-link-preview__icon-container">
           <Image
-            alt={i18n('stagedPreviewThumbnail', [domain])}
+            alt={window.i18n('stagedPreviewThumbnail', [domain])}
             softCorners={true}
             height={72}
             width={72}
             url={image.url}
             attachment={image}
-            i18n={i18n}
           />
         </div>
       ) : null}
@@ -65,7 +63,7 @@ export const StagedLinkPreview = (props: Props) => {
         onClick={() => {
           onClose(url || '');
         }}
-        aria-label={i18n('close')}
+        aria-label={window.i18n('close')}
       />
     </div>
   );
diff --git a/ts/components/conversation/TimerNotification.tsx b/ts/components/conversation/TimerNotification.tsx
index 9e435fe95..9e6c8db16 100644
--- a/ts/components/conversation/TimerNotification.tsx
+++ b/ts/components/conversation/TimerNotification.tsx
@@ -28,7 +28,7 @@ export const TimerNotification = (props: Props) => {
 
     switch (type) {
       case 'fromOther':
-        return <Intl i18n={window.i18n} id={changeKey} components={[contact, timespan]} />;
+        return <Intl id={changeKey} components={[contact, timespan]} />;
       case 'fromMe':
         return disabled
           ? window.i18n('youDisabledDisappearingMessages')
diff --git a/ts/components/conversation/Timestamp.tsx b/ts/components/conversation/Timestamp.tsx
index 31338ad5d..276513500 100644
--- a/ts/components/conversation/Timestamp.tsx
+++ b/ts/components/conversation/Timestamp.tsx
@@ -68,7 +68,7 @@ export const Timestamp = (props: Props) => {
 
   let dateString;
   if (messageAgeInDays > daysBeforeRelativeTiming) {
-    dateString = formatRelativeTime(timestamp, { i18n: window.i18n, extended });
+    dateString = formatRelativeTime(timestamp, { extended });
   } else {
     dateString = moment(timestamp).fromNow();
     // Prevent times reading "NOW AGO"
diff --git a/ts/components/conversation/TypingAnimation.tsx b/ts/components/conversation/TypingAnimation.tsx
index 744a4f387..7fd261b35 100644
--- a/ts/components/conversation/TypingAnimation.tsx
+++ b/ts/components/conversation/TypingAnimation.tsx
@@ -1,19 +1,16 @@
 import React from 'react';
 import classNames from 'classnames';
 
-import { LocalizerType } from '../../types/Util';
-
 interface Props {
-  i18n: LocalizerType;
   color?: string;
 }
 
 export class TypingAnimation extends React.Component<Props> {
   public render() {
-    const { i18n, color } = this.props;
+    const { color } = this.props;
 
     return (
-      <div className="module-typing-animation" title={i18n('typingAlt')}>
+      <div className="module-typing-animation" title={window.i18n('typingAlt')}>
         <div
           className={classNames(
             'module-typing-animation__dot',
diff --git a/ts/components/conversation/TypingBubble.tsx b/ts/components/conversation/TypingBubble.tsx
index 322832f04..d808424b5 100644
--- a/ts/components/conversation/TypingBubble.tsx
+++ b/ts/components/conversation/TypingBubble.tsx
@@ -31,7 +31,7 @@ export const TypingBubble = (props: TypingBubbleProps) => {
 
   return (
     <TypingBubbleContainer {...props}>
-      <TypingAnimation i18n={window.i18n} />
+      <TypingAnimation />
     </TypingBubbleContainer>
   );
 };
diff --git a/ts/components/conversation/UpdateGroupMembersDialog.tsx b/ts/components/conversation/UpdateGroupMembersDialog.tsx
index ffe589433..64f7fd9b0 100644
--- a/ts/components/conversation/UpdateGroupMembersDialog.tsx
+++ b/ts/components/conversation/UpdateGroupMembersDialog.tsx
@@ -6,7 +6,6 @@ import { SessionButton, SessionButtonColor, SessionButtonType } from '../session
 import { ContactType, SessionMemberListItem } from '../session/SessionMemberListItem';
 import { DefaultTheme } from 'styled-components';
 import { ToastUtils } from '../../session/utils';
-import { LocalizerType } from '../../types/Util';
 import autoBind from 'auto-bind';
 import { ConversationController } from '../../session/conversations';
 
@@ -25,7 +24,6 @@ interface Props {
   existingZombies: Array<string>;
   admins: Array<string>; // used for closed group
 
-  i18n: LocalizerType;
   onSubmit: (membersLeft: Array<string>) => void;
   onClose: () => void;
   theme: DefaultTheme;
diff --git a/ts/components/conversation/media-gallery/AttachmentSection.tsx b/ts/components/conversation/media-gallery/AttachmentSection.tsx
index 63c5a7a42..da2a63896 100644
--- a/ts/components/conversation/media-gallery/AttachmentSection.tsx
+++ b/ts/components/conversation/media-gallery/AttachmentSection.tsx
@@ -5,10 +5,8 @@ import { ItemClickEvent } from './types/ItemClickEvent';
 import { MediaGridItem } from './MediaGridItem';
 import { MediaItemType } from '../../LightboxGallery';
 import { missingCaseError } from '../../../util/missingCaseError';
-import { LocalizerType } from '../../../types/Util';
 
 interface Props {
-  i18n: LocalizerType;
   type: 'media' | 'documents';
   mediaItems: Array<MediaItemType>;
   onItemClick?: (event: ItemClickEvent) => void;
@@ -28,7 +26,7 @@ export class AttachmentSection extends React.Component<Props> {
   }
 
   private renderItems() {
-    const { i18n, mediaItems, type } = this.props;
+    const { mediaItems, type } = this.props;
 
     return mediaItems.map((mediaItem, position, array) => {
       const shouldShowSeparator = position < array.length - 1;
@@ -38,12 +36,7 @@ export class AttachmentSection extends React.Component<Props> {
       switch (type) {
         case 'media':
           return (
-            <MediaGridItem
-              key={`${message.id}-${index}`}
-              mediaItem={mediaItem}
-              onClick={onClick}
-              i18n={i18n}
-            />
+            <MediaGridItem key={`${message.id}-${index}`} mediaItem={mediaItem} onClick={onClick} />
           );
         case 'documents':
           return (
diff --git a/ts/components/conversation/media-gallery/MediaGallery.tsx b/ts/components/conversation/media-gallery/MediaGallery.tsx
index e4ecda667..2518327d4 100644
--- a/ts/components/conversation/media-gallery/MediaGallery.tsx
+++ b/ts/components/conversation/media-gallery/MediaGallery.tsx
@@ -114,7 +114,6 @@ export class MediaGallery extends React.Component<Props, State> {
       <div className="module-media-gallery__sections">
         <AttachmentSection
           key="mediaItems"
-          i18n={window.i18n}
           type={type}
           mediaItems={mediaItems}
           onItemClick={onItemClick}
diff --git a/ts/components/conversation/media-gallery/MediaGridItem.tsx b/ts/components/conversation/media-gallery/MediaGridItem.tsx
index c65fe313e..79ec9e1b2 100644
--- a/ts/components/conversation/media-gallery/MediaGridItem.tsx
+++ b/ts/components/conversation/media-gallery/MediaGridItem.tsx
@@ -2,18 +2,17 @@ import React, { useState } from 'react';
 import classNames from 'classnames';
 
 import { isImageTypeSupported, isVideoTypeSupported } from '../../../util/GoogleChrome';
-import { LocalizerType } from '../../../types/Util';
 import { MediaItemType } from '../../LightboxGallery';
 import { useEncryptedFileFetch } from '../../../hooks/useEncryptedFileFetch';
 
 type Props = {
   mediaItem: MediaItemType;
   onClick?: () => void;
-  i18n: LocalizerType;
 };
 
 const MediaGridItemContent = (props: Props) => {
-  const { mediaItem, i18n } = props;
+  const { mediaItem } = props;
+  const i18n = window.i18n;
   const { attachment, contentType } = mediaItem;
 
   const urlToDecrypt = mediaItem.thumbnailObjectUrl || '';
diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx
index 6a2fa7feb..646885ee2 100644
--- a/ts/components/session/ActionsPanel.tsx
+++ b/ts/components/session/ActionsPanel.tsx
@@ -144,12 +144,12 @@ const showResetSessionIDDialogIfNeeded = async () => {
 
 const cleanUpMediasInterval = DURATION.MINUTES * 30;
 
-const setupTheme = (dispatch: Dispatch<any>) => {
+const setupTheme = () => {
   const theme = window.Events.getThemeSetting();
   window.setTheme(theme);
 
   const newThemeObject = theme === 'dark' ? darkTheme : lightTheme;
-  dispatch(applyTheme(newThemeObject));
+  window?.inboxStore?.dispatch(applyTheme(newThemeObject));
 };
 
 // Do this only if we created a new Session ID, or if we already received the initial configuration message
@@ -273,7 +273,7 @@ const triggerAvatarReUploadIfNeeded = async () => {
 /**
  * This function is called only once: on app startup with a logged in user
  */
-const doAppStartUp = (dispatch: Dispatch<any>) => {
+const doAppStartUp = () => {
   if (window.lokiFeatureFlags.useOnionRequests || window.lokiFeatureFlags.useFileOnionRequests) {
     // Initialize paths for onion requests
     void OnionPaths.buildNewOnionPathsOneAtATime();
@@ -282,7 +282,7 @@ const doAppStartUp = (dispatch: Dispatch<any>) => {
   // init the messageQueue. In the constructor, we add all not send messages
   // this call does nothing except calling the constructor, which will continue sending message in the pipeline
   void getMessageQueue().processAllPending();
-  void setupTheme(dispatch);
+  void setupTheme();
 
   // keep that one to make sure our users upgrade to new sessionIDS
   void showResetSessionIDDialogIfNeeded();
@@ -310,7 +310,6 @@ const doAppStartUp = (dispatch: Dispatch<any>) => {
  * The panel with buttons to switch between the message/contact/settings/theme views
  */
 export const ActionsPanel = () => {
-  const dispatch = useDispatch();
   const [startCleanUpMedia, setStartCleanUpMedia] = useState(false);
 
   const ourPrimaryConversation = useSelector(getOurPrimaryConversation);
@@ -318,7 +317,7 @@ export const ActionsPanel = () => {
   // this maxi useEffect is called only once: when the component is mounted.
   // For the action panel, it means this is called only one per app start/with a user loggedin
   useEffect(() => {
-    void doAppStartUp(dispatch);
+    void doAppStartUp();
   }, []);
 
   // wait for cleanUpMediasInterval and then start cleaning up medias
diff --git a/ts/components/session/LeftPaneContactSection.tsx b/ts/components/session/LeftPaneContactSection.tsx
index 8666520dc..826db465c 100644
--- a/ts/components/session/LeftPaneContactSection.tsx
+++ b/ts/components/session/LeftPaneContactSection.tsx
@@ -42,7 +42,6 @@ export class LeftPaneContactSection extends React.Component<Props> {
         key={item.id}
         style={style}
         {...item}
-        i18n={window.i18n}
         onClick={this.props.openConversationExternal}
       />
     );
diff --git a/ts/components/session/LeftPaneMessageSection.tsx b/ts/components/session/LeftPaneMessageSection.tsx
index 40f9975fe..205cd3cb0 100644
--- a/ts/components/session/LeftPaneMessageSection.tsx
+++ b/ts/components/session/LeftPaneMessageSection.tsx
@@ -93,7 +93,6 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
         style={style}
         {...conversation}
         onClick={openConversationExternal}
-        i18n={window.i18n}
       />
     );
   };
@@ -108,7 +107,6 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
           {...searchResults}
           contacts={contacts}
           openConversationExternal={openConversationExternal}
-          i18n={window.i18n}
         />
       );
     }
diff --git a/ts/components/session/SessionPasswordModal.tsx b/ts/components/session/SessionPasswordModal.tsx
index 24c2dd72a..5d6619563 100644
--- a/ts/components/session/SessionPasswordModal.tsx
+++ b/ts/components/session/SessionPasswordModal.tsx
@@ -154,7 +154,7 @@ class SessionPasswordModalInner extends React.Component<Props, State> {
    */
   private validatePassword(firstPassword: string) {
     // if user did not fill the first password field, we can't do anything
-    const errorFirstInput = PasswordUtil.validatePassword(firstPassword, window.i18n);
+    const errorFirstInput = PasswordUtil.validatePassword(firstPassword);
     if (errorFirstInput !== null) {
       this.setState({
         error: errorFirstInput,
diff --git a/ts/components/session/conversation/SessionQuotedMessageComposition.tsx b/ts/components/session/conversation/SessionQuotedMessageComposition.tsx
index 96a1c9218..902381307 100644
--- a/ts/components/session/conversation/SessionQuotedMessageComposition.tsx
+++ b/ts/components/session/conversation/SessionQuotedMessageComposition.tsx
@@ -83,8 +83,7 @@ export const SessionQuotedMessageComposition = (props: Props) => {
 
           {hasImageAttachment && (
             <Image
-              alt={getAlt(firstImageAttachment, window.i18n)}
-              i18n={window.i18n}
+              alt={getAlt(firstImageAttachment)}
               attachment={firstImageAttachment}
               height={100}
               width={100}
diff --git a/ts/components/session/menu/ConversationHeaderMenu.tsx b/ts/components/session/menu/ConversationHeaderMenu.tsx
index fbe7d1f8c..5c1d6a1da 100644
--- a/ts/components/session/menu/ConversationHeaderMenu.tsx
+++ b/ts/components/session/menu/ConversationHeaderMenu.tsx
@@ -93,8 +93,7 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
         left,
         isBlocked,
         timerOptions,
-        onSetDisappearingMessages,
-        window.i18n
+        onSetDisappearingMessages
       )}
       {getNotificationForConvoMenuItem(
         isKickedFromGroup,
@@ -102,31 +101,22 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
         isBlocked,
         notificationForConvo,
         currentNotificationSetting,
-        onSetNotificationForConvo,
-        window.i18n
+        onSetNotificationForConvo
       )}
-      {getBlockMenuItem(isMe, isPrivate, isBlocked, onBlockUser, onUnblockUser, window.i18n)}
+      {getBlockMenuItem(isMe, isPrivate, isBlocked, onBlockUser, onUnblockUser)}
 
-      {getCopyMenuItem(isPublic, isGroup, onCopyPublicKey, window.i18n)}
-      {getMarkAllReadMenuItem(onMarkAllRead, window.i18n)}
-      {getChangeNicknameMenuItem(isMe, onChangeNickname, isGroup, window.i18n)}
-      {getClearNicknameMenuItem(isMe, hasNickname, onClearNickname, isGroup, window.i18n)}
-      {getDeleteMessagesMenuItem(isPublic, onDeleteMessages, window.i18n)}
-      {getAddModeratorsMenuItem(isAdmin, isKickedFromGroup, onAddModerators, window.i18n)}
-      {getRemoveModeratorsMenuItem(isAdmin, isKickedFromGroup, onRemoveModerators, window.i18n)}
-      {getUpdateGroupNameMenuItem(isAdmin, isKickedFromGroup, left, onUpdateGroupName, window.i18n)}
-      {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, onLeaveGroup, window.i18n)}
+      {getCopyMenuItem(isPublic, isGroup, onCopyPublicKey)}
+      {getMarkAllReadMenuItem(onMarkAllRead)}
+      {getChangeNicknameMenuItem(isMe, onChangeNickname, isGroup)}
+      {getClearNicknameMenuItem(isMe, hasNickname, onClearNickname, isGroup)}
+      {getDeleteMessagesMenuItem(isPublic, onDeleteMessages)}
+      {getAddModeratorsMenuItem(isAdmin, isKickedFromGroup, onAddModerators)}
+      {getRemoveModeratorsMenuItem(isAdmin, isKickedFromGroup, onRemoveModerators)}
+      {getUpdateGroupNameMenuItem(isAdmin, isKickedFromGroup, left, onUpdateGroupName)}
+      {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, onLeaveGroup)}
       {/* TODO: add delete group */}
-      {getInviteContactMenuItem(isGroup, isPublic, onInviteContacts, window.i18n)}
-      {getDeleteContactMenuItem(
-        isMe,
-        isGroup,
-        isPublic,
-        left,
-        isKickedFromGroup,
-        onDeleteContact,
-        window.i18n
-      )}
+      {getInviteContactMenuItem(isGroup, isPublic, onInviteContacts)}
+      {getDeleteContactMenuItem(isMe, isGroup, isPublic, left, isKickedFromGroup, onDeleteContact)}
     </Menu>
   );
 };
diff --git a/ts/components/session/menu/ConversationListItemContextMenu.tsx b/ts/components/session/menu/ConversationListItemContextMenu.tsx
index baa098893..e65220b2f 100644
--- a/ts/components/session/menu/ConversationListItemContextMenu.tsx
+++ b/ts/components/session/menu/ConversationListItemContextMenu.tsx
@@ -67,26 +67,17 @@ export const ConversationListItemContextMenu = (props: PropsContextConversationI
         type === ConversationTypeEnum.PRIVATE,
         isBlocked,
         onBlockContact,
-        onUnblockContact,
-        window.i18n
+        onUnblockContact
       )}
-      {getCopyMenuItem(isPublic, isGroup, onCopyPublicKey, window.i18n)}
-      {getMarkAllReadMenuItem(onMarkAllRead, window.i18n)}
-      {getChangeNicknameMenuItem(isMe, onChangeNickname, isGroup, window.i18n)}
-      {getClearNicknameMenuItem(isMe, hasNickname, onClearNickname, isGroup, window.i18n)}
+      {getCopyMenuItem(isPublic, isGroup, onCopyPublicKey)}
+      {getMarkAllReadMenuItem(onMarkAllRead)}
+      {getChangeNicknameMenuItem(isMe, onChangeNickname, isGroup)}
+      {getClearNicknameMenuItem(isMe, hasNickname, onClearNickname, isGroup)}
 
-      {getDeleteMessagesMenuItem(isPublic, onDeleteMessages, window.i18n)}
-      {getInviteContactMenuItem(isGroup, isPublic, onInviteContacts, window.i18n)}
-      {getDeleteContactMenuItem(
-        isMe,
-        isGroup,
-        isPublic,
-        left,
-        isKickedFromGroup,
-        onDeleteContact,
-        window.i18n
-      )}
-      {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, onLeaveGroup, window.i18n)}
+      {getDeleteMessagesMenuItem(isPublic, onDeleteMessages)}
+      {getInviteContactMenuItem(isGroup, isPublic, onInviteContacts)}
+      {getDeleteContactMenuItem(isMe, isGroup, isPublic, left, isKickedFromGroup, onDeleteContact)}
+      {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, onLeaveGroup)}
     </Menu>
   );
 };
diff --git a/ts/components/session/menu/Menu.tsx b/ts/components/session/menu/Menu.tsx
index 064fae02b..385527e61 100644
--- a/ts/components/session/menu/Menu.tsx
+++ b/ts/components/session/menu/Menu.tsx
@@ -1,11 +1,7 @@
 import React from 'react';
-import { LocalizerType } from '../../../types/Util';
 import { NotificationForConvoOption, TimerOption } from '../../conversation/ConversationHeader';
 import { Item, Submenu } from 'react-contexify';
-import {
-  ConversationNotificationSetting,
-  ConversationNotificationSettingType,
-} from '../../../models/conversation';
+import { ConversationNotificationSettingType } from '../../../models/conversation';
 
 function showTimerOptions(
   isPublic: boolean,
@@ -90,11 +86,10 @@ function showInviteContact(isGroup: boolean, isPublic: boolean): boolean {
 export function getInviteContactMenuItem(
   isGroup: boolean | undefined,
   isPublic: boolean | undefined,
-  action: any,
-  i18n: LocalizerType
+  action: any
 ): JSX.Element | null {
   if (showInviteContact(Boolean(isGroup), Boolean(isPublic))) {
-    return <Item onClick={action}>{i18n('inviteContacts')}</Item>;
+    return <Item onClick={action}>{window.i18n('inviteContacts')}</Item>;
   }
   return null;
 }
@@ -105,8 +100,7 @@ export function getDeleteContactMenuItem(
   isPublic: boolean | undefined,
   isLeft: boolean | undefined,
   isKickedFromGroup: boolean | undefined,
-  action: any,
-  i18n: LocalizerType
+  action: any
 ): JSX.Element | null {
   if (
     showDeleteContact(
@@ -118,9 +112,9 @@ export function getDeleteContactMenuItem(
     )
   ) {
     if (isPublic) {
-      return <Item onClick={action}>{i18n('leaveGroup')}</Item>;
+      return <Item onClick={action}>{window.i18n('leaveGroup')}</Item>;
     }
-    return <Item onClick={action}>{i18n('delete')}</Item>;
+    return <Item onClick={action}>{window.i18n('delete')}</Item>;
   }
   return null;
 }
@@ -130,13 +124,12 @@ export function getLeaveGroupMenuItem(
   left: boolean | undefined,
   isGroup: boolean | undefined,
   isPublic: boolean | undefined,
-  action: any,
-  i18n: LocalizerType
+  action: any
 ): JSX.Element | null {
   if (
     showLeaveGroup(Boolean(isKickedFromGroup), Boolean(left), Boolean(isGroup), Boolean(isPublic))
   ) {
-    return <Item onClick={action}>{i18n('leaveGroup')}</Item>;
+    return <Item onClick={action}>{window.i18n('leaveGroup')}</Item>;
   }
   return null;
 }
@@ -145,11 +138,10 @@ export function getUpdateGroupNameMenuItem(
   isAdmin: boolean | undefined,
   isKickedFromGroup: boolean | undefined,
   left: boolean | undefined,
-  action: any,
-  i18n: LocalizerType
+  action: any
 ): JSX.Element | null {
   if (showUpdateGroupName(Boolean(isAdmin), Boolean(isKickedFromGroup), Boolean(left))) {
-    return <Item onClick={action}>{i18n('editGroup')}</Item>;
+    return <Item onClick={action}>{window.i18n('editGroup')}</Item>;
   }
   return null;
 }
@@ -157,11 +149,10 @@ export function getUpdateGroupNameMenuItem(
 export function getRemoveModeratorsMenuItem(
   isAdmin: boolean | undefined,
   isKickedFromGroup: boolean | undefined,
-  action: any,
-  i18n: LocalizerType
+  action: any
 ): JSX.Element | null {
   if (showRemoveModerators(Boolean(isAdmin), Boolean(isKickedFromGroup))) {
-    return <Item onClick={action}>{i18n('removeModerators')}</Item>;
+    return <Item onClick={action}>{window.i18n('removeModerators')}</Item>;
   }
   return null;
 }
@@ -169,11 +160,10 @@ export function getRemoveModeratorsMenuItem(
 export function getAddModeratorsMenuItem(
   isAdmin: boolean | undefined,
   isKickedFromGroup: boolean | undefined,
-  action: any,
-  i18n: LocalizerType
+  action: any
 ): JSX.Element | null {
   if (showAddModerators(Boolean(isAdmin), Boolean(isKickedFromGroup))) {
-    return <Item onClick={action}>{i18n('addModerators')}</Item>;
+    return <Item onClick={action}>{window.i18n('addModerators')}</Item>;
   }
   return null;
 }
@@ -181,18 +171,17 @@ export function getAddModeratorsMenuItem(
 export function getCopyMenuItem(
   isPublic: boolean | undefined,
   isGroup: boolean | undefined,
-  action: any,
-  i18n: LocalizerType
+  action: any
 ): JSX.Element | null {
   if (showCopyId(Boolean(isPublic), Boolean(isGroup))) {
-    const copyIdLabel = isPublic ? i18n('copyOpenGroupURL') : i18n('copySessionID');
+    const copyIdLabel = isPublic ? window.i18n('copyOpenGroupURL') : window.i18n('copySessionID');
     return <Item onClick={action}>{copyIdLabel}</Item>;
   }
   return null;
 }
 
-export function getMarkAllReadMenuItem(action: any, i18n: LocalizerType): JSX.Element | null {
-  return <Item onClick={action}>{i18n('markAllAsRead')}</Item>;
+export function getMarkAllReadMenuItem(action: any): JSX.Element | null {
+  return <Item onClick={action}>{window.i18n('markAllAsRead')}</Item>;
 }
 
 export function getDisappearingMenuItem(
@@ -201,8 +190,7 @@ export function getDisappearingMenuItem(
   left: boolean | undefined,
   isBlocked: boolean | undefined,
   timerOptions: Array<TimerOption>,
-  action: any,
-  i18n: LocalizerType
+  action: any
 ): JSX.Element | null {
   if (
     showTimerOptions(
@@ -216,7 +204,7 @@ export function getDisappearingMenuItem(
     return (
       // Remove the && false to make context menu work with RTL support
       <Submenu
-        label={i18n('disappearingMessages') as any}
+        label={window.i18n('disappearingMessages') as any}
         // rtl={isRtlMode && false}
       >
         {(timerOptions || []).map(item => (
@@ -241,15 +229,14 @@ export function getNotificationForConvoMenuItem(
   isBlocked: boolean | undefined,
   notificationForConvoOptions: Array<NotificationForConvoOption>,
   currentNotificationSetting: ConversationNotificationSettingType,
-  action: (selected: ConversationNotificationSettingType) => any,
-  i18n: LocalizerType
+  action: (selected: ConversationNotificationSettingType) => any
 ): JSX.Element | null {
   if (showNotificationConvo(Boolean(isKickedFromGroup), Boolean(left), Boolean(isBlocked))) {
     // const isRtlMode = isRtlBody();
     return (
       // Remove the && false to make context menu work with RTL support
       <Submenu
-        label={i18n('notificationForConvo') as any}
+        label={window.i18n('notificationForConvo') as any}
         // rtl={isRtlMode && false}
       >
         {(notificationForConvoOptions || []).map(item => (
@@ -277,11 +264,10 @@ export function isRtlBody(): boolean {
 export function getShowMemberMenuItem(
   isPublic: boolean | undefined,
   isGroup: boolean | undefined,
-  action: any,
-  i18n: LocalizerType
+  action: any
 ): JSX.Element | null {
   if (showMemberMenu(Boolean(isPublic), Boolean(isGroup))) {
-    return <Item onClick={action}>{i18n('groupMembers')}</Item>;
+    return <Item onClick={action}>{window.i18n('groupMembers')}</Item>;
   }
   return null;
 }
@@ -291,11 +277,10 @@ export function getBlockMenuItem(
   isPrivate: boolean | undefined,
   isBlocked: boolean | undefined,
   actionBlock: any,
-  actionUnblock: any,
-  i18n: LocalizerType
+  actionUnblock: any
 ): JSX.Element | null {
   if (showBlock(Boolean(isMe), Boolean(isPrivate))) {
-    const blockTitle = isBlocked ? i18n('unblockUser') : i18n('blockUser');
+    const blockTitle = isBlocked ? window.i18n('unblockUser') : window.i18n('blockUser');
     const blockHandler = isBlocked ? actionUnblock : actionBlock;
     return <Item onClick={blockHandler}>{blockTitle}</Item>;
   }
@@ -306,11 +291,10 @@ export function getClearNicknameMenuItem(
   isMe: boolean | undefined,
   hasNickname: boolean | undefined,
   action: any,
-  isGroup: boolean | undefined,
-  i18n: LocalizerType
+  isGroup: boolean | undefined
 ): JSX.Element | null {
   if (showClearNickname(Boolean(isMe), Boolean(hasNickname), Boolean(isGroup))) {
-    return <Item onClick={action}>{i18n('clearNickname')}</Item>;
+    return <Item onClick={action}>{window.i18n('clearNickname')}</Item>;
   }
   return null;
 }
@@ -318,22 +302,20 @@ export function getClearNicknameMenuItem(
 export function getChangeNicknameMenuItem(
   isMe: boolean | undefined,
   action: any,
-  isGroup: boolean | undefined,
-  i18n: LocalizerType
+  isGroup: boolean | undefined
 ): JSX.Element | null {
   if (showChangeNickname(Boolean(isMe), Boolean(isGroup))) {
-    return <Item onClick={action}>{i18n('changeNickname')}</Item>;
+    return <Item onClick={action}>{window.i18n('changeNickname')}</Item>;
   }
   return null;
 }
 
 export function getDeleteMessagesMenuItem(
   isPublic: boolean | undefined,
-  action: any,
-  i18n: LocalizerType
+  action: any
 ): JSX.Element | null {
   if (showDeleteMessages(Boolean(isPublic))) {
-    return <Item onClick={action}>{i18n('deleteMessages')}</Item>;
+    return <Item onClick={action}>{window.i18n('deleteMessages')}</Item>;
   }
   return null;
 }
diff --git a/ts/components/session/registration/RegistrationTabs.tsx b/ts/components/session/registration/RegistrationTabs.tsx
index 4d83a4d0e..0676ad37e 100644
--- a/ts/components/session/registration/RegistrationTabs.tsx
+++ b/ts/components/session/registration/RegistrationTabs.tsx
@@ -38,7 +38,7 @@ export function validatePassword(password: string, verifyPassword: string) {
     };
   }
 
-  const error = PasswordUtil.validatePassword(trimmedPassword, window.i18n);
+  const error = PasswordUtil.validatePassword(trimmedPassword);
   if (error) {
     return {
       passwordErrorString: error,
diff --git a/ts/session/types/PubKey.ts b/ts/session/types/PubKey.ts
index 49f4ac3b6..94ef6f686 100644
--- a/ts/session/types/PubKey.ts
+++ b/ts/session/types/PubKey.ts
@@ -1,4 +1,3 @@
-import { LocalizerType } from '../../types/Util';
 import { fromHexToArray } from '../utils/String';
 
 export class PubKey {
@@ -100,20 +99,17 @@ export class PubKey {
   /**
    * Returns a localized string of the error, or undefined in the given pubkey is valid.
    */
-  public static validateWithError(
-    pubkey: string,
-    i18n: LocalizerType = window.i18n
-  ): string | undefined {
+  public static validateWithError(pubkey: string): string | undefined {
     // Check if it's hex
     const isHex = pubkey.replace(/[\s]*/g, '').match(/^[0-9a-fA-F]+$/);
     if (!isHex) {
-      return i18n('invalidSessionId');
+      return window.i18n('invalidSessionId');
     }
 
     // Check if the pubkey length is 33 and leading with 05 or of length 32
     const len = pubkey.length;
     if ((len !== 33 * 2 || !/^05/.test(pubkey)) && len !== 32 * 2) {
-      return i18n('invalidPubkeyFormat');
+      return window.i18n('invalidPubkeyFormat');
     }
     return undefined;
   }
diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts
index 2b89103fc..7c7177649 100644
--- a/ts/state/ducks/conversations.ts
+++ b/ts/state/ducks/conversations.ts
@@ -82,18 +82,6 @@ export interface ConversationType {
   avatarPath?: string; // absolute filepath to the avatar
   groupAdmins?: Array<string>; // admins for closed groups and moderators for open groups
   members?: Array<string>; // members for closed groups only
-
-  onClick?: () => void;
-  onBlockContact?: () => void;
-  onUnblockContact?: () => void;
-  onCopyPublicKey?: () => void;
-  onDeleteContact?: () => void;
-  onLeaveGroup?: () => void;
-  onDeleteMessages?: () => void;
-  onInviteContacts?: () => void;
-  onMarkAllRead?: () => void;
-  onClearNickname?: () => void;
-  onChangeNickname?: () => void;
 }
 
 export type ConversationLookupType = {
diff --git a/ts/state/ducks/user.ts b/ts/state/ducks/user.ts
index 220db597c..7b6289e9d 100644
--- a/ts/state/ducks/user.ts
+++ b/ts/state/ducks/user.ts
@@ -1,10 +1,7 @@
-import { LocalizerType } from '../../types/Util';
-
 // State
 
 export type UserStateType = {
   ourNumber: string;
-  i18n: LocalizerType;
 };
 
 // Actions
@@ -37,7 +34,6 @@ function userChanged(attributes: { ourNumber: string; ourPrimary: string }): Use
 function getEmptyState(): UserStateType {
   return {
     ourNumber: 'missing',
-    i18n: () => 'missing',
   };
 }
 
diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts
index 8d6f89227..b054f312a 100644
--- a/ts/state/selectors/conversations.ts
+++ b/ts/state/selectors/conversations.ts
@@ -10,8 +10,8 @@ import {
 
 import { getIntl, getOurNumber } from './user';
 import { BlockedNumberController } from '../../util';
-import { LocalizerType } from '../../types/Util';
 import { ConversationTypeEnum } from '../../models/conversation';
+import { LocalizerType } from '../../types/Util';
 
 export const getConversations = (state: StateType): ConversationsStateType => state.conversations;
 
@@ -49,20 +49,20 @@ export const getMessagesOfSelectedConversation = createSelector(
   (state: ConversationsStateType): Array<MessageTypeInConvo> => state.messages
 );
 
-function getConversationTitle(conversation: ConversationType, i18n: LocalizerType): string {
+function getConversationTitle(conversation: ConversationType, testingi18n?: LocalizerType): string {
   if (conversation.name) {
     return conversation.name;
   }
 
   if (conversation.type === 'group') {
-    return i18n('unknown');
+    return (testingi18n || window.i18n)('unknown');
   }
   return conversation.id;
 }
 
 const collator = new Intl.Collator();
 
-export const _getConversationComparator = (i18n: LocalizerType) => {
+export const _getConversationComparator = (testingi18n?: LocalizerType) => {
   return (left: ConversationType, right: ConversationType): number => {
     const leftActiveAt = left.activeAt;
     const rightActiveAt = right.activeAt;
@@ -75,8 +75,8 @@ export const _getConversationComparator = (i18n: LocalizerType) => {
     if (leftActiveAt && rightActiveAt && leftActiveAt !== rightActiveAt) {
       return rightActiveAt - leftActiveAt;
     }
-    const leftTitle = getConversationTitle(left, i18n || window?.i18n).toLowerCase();
-    const rightTitle = getConversationTitle(right, i18n || window?.i18n).toLowerCase();
+    const leftTitle = getConversationTitle(left, testingi18n).toLowerCase();
+    const rightTitle = getConversationTitle(right, testingi18n).toLowerCase();
 
     return collator.compare(leftTitle, rightTitle);
   };
@@ -120,7 +120,7 @@ export const _getLeftPaneLists = (
       };
     }
 
-    conversation.index = index;
+    // conversation.index = index;
 
     // Add Open Group to list as soon as the name has been set
     if (conversation.isPublic && (!conversation.name || conversation.name === 'Unknown group')) {
diff --git a/ts/state/selectors/user.ts b/ts/state/selectors/user.ts
index e3468583c..5bbd9cdfe 100644
--- a/ts/state/selectors/user.ts
+++ b/ts/state/selectors/user.ts
@@ -12,4 +12,7 @@ export const getOurNumber = createSelector(
   (state: UserStateType): string => state.ourNumber
 );
 
-export const getIntl = createSelector(getUser, (state: UserStateType): LocalizerType => state.i18n);
+export const getIntl = createSelector(
+  getUser,
+  (state: UserStateType): LocalizerType => window.i18n
+);
diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts
index bc2984fb0..e8aa6454c 100644
--- a/ts/types/Attachment.ts
+++ b/ts/types/Attachment.ts
@@ -6,7 +6,6 @@ import * as MIME from './MIME';
 import { saveURLAsFile } from '../util/saveURLAsFile';
 import { SignalService } from '../protobuf';
 import { isImageTypeSupported, isVideoTypeSupported } from '../util/GoogleChrome';
-import { LocalizerType } from './Util';
 import { fromHexToArray } from '../session/utils/String';
 import { getSodium } from '../session/crypto';
 
@@ -225,8 +224,10 @@ export function getGridDimensions(attachments?: Array<AttachmentType>): null | D
   };
 }
 
-export function getAlt(attachment: AttachmentType, i18n: LocalizerType): string {
-  return isVideoAttachment(attachment) ? i18n('videoAttachmentAlt') : i18n('imageAttachmentAlt');
+export function getAlt(attachment: AttachmentType): string {
+  return isVideoAttachment(attachment)
+    ? window.i18n('videoAttachmentAlt')
+    : window.i18n('imageAttachmentAlt');
 }
 
 // Migration-related attachment stuff
diff --git a/ts/util/formatRelativeTime.ts b/ts/util/formatRelativeTime.ts
index 6b83bb70a..a271cd272 100644
--- a/ts/util/formatRelativeTime.ts
+++ b/ts/util/formatRelativeTime.ts
@@ -1,14 +1,13 @@
 import moment from 'moment';
-import { LocalizerType } from '../types/Util';
 
-const getExtendedFormats = (i18n: LocalizerType) => ({
+const getExtendedFormats = () => ({
   y: 'lll',
-  M: `${i18n('timestampFormat_M') || 'MMM D'} LT`,
+  M: `${window.i18n('timestampFormat_M') || 'MMM D'} LT`,
   d: 'ddd LT',
 });
-const getShortFormats = (i18n: LocalizerType) => ({
+const getShortFormats = () => ({
   y: 'll',
-  M: i18n('timestampFormat_M') || 'MMM D',
+  M: window.i18n('timestampFormat_M') || 'MMM D',
   d: 'ddd',
 });
 
@@ -19,13 +18,9 @@ function isYear(timestamp: moment.Moment) {
   return year === targetYear;
 }
 
-export function formatRelativeTime(
-  rawTimestamp: number | Date,
-  options: { extended?: boolean; i18n: LocalizerType }
-) {
-  const { extended, i18n } = options;
-
-  const formats = extended ? getExtendedFormats(i18n) : getShortFormats(i18n);
+export function formatRelativeTime(rawTimestamp: number | Date, options: { extended?: boolean }) {
+  const { extended } = options;
+  const formats = extended ? getExtendedFormats() : getShortFormats();
   const timestamp = moment(rawTimestamp);
   const now = moment();
   const diff = moment.duration(now.diff(timestamp));
diff --git a/ts/util/passwordUtils.ts b/ts/util/passwordUtils.ts
index d91bf624f..397365e03 100644
--- a/ts/util/passwordUtils.ts
+++ b/ts/util/passwordUtils.ts
@@ -19,24 +19,24 @@ export const generateHash = (phrase: string) => phrase && sha512(phrase.trim());
 export const matchesHash = (phrase: string | null, hash: string) =>
   phrase && sha512(phrase.trim()) === hash.trim();
 
-export const validatePassword = (phrase: string, i18n?: LocalizerType) => {
+export const validatePassword = (phrase: string) => {
   if (typeof phrase !== 'string') {
-    return i18n ? i18n('passwordTypeError') : ERRORS.TYPE;
+    return window?.i18n ? window?.i18n('passwordTypeError') : ERRORS.TYPE;
   }
 
   const trimmed = phrase.trim();
   if (trimmed.length === 0) {
-    return i18n ? i18n('noGivenPassword') : ERRORS.LENGTH;
+    return window?.i18n ? window?.i18n('noGivenPassword') : ERRORS.LENGTH;
   }
 
   if (trimmed.length < 6 || trimmed.length > MAX_PASSWORD_LENGTH) {
-    return i18n ? i18n('passwordLengthError') : ERRORS.LENGTH;
+    return window?.i18n ? window?.i18n('passwordLengthError') : ERRORS.LENGTH;
   }
 
   // Restrict characters to letters, numbers and symbols
   const characterRegex = /^[a-zA-Z0-9-!()._`~@#$%^&*+=[\]{}|<>,;: ]+$/;
   if (!characterRegex.test(trimmed)) {
-    return i18n ? i18n('passwordCharacterError') : ERRORS.CHARACTER;
+    return window?.i18n ? window?.i18n('passwordCharacterError') : ERRORS.CHARACTER;
   }
 
   return null;