diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index c611ae1c9..a0d7192a3 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -28,14 +28,14 @@
   "notificationsSlowModeDescription": "{app_name} will occasionally check for new messages in the background.",
   "attachmentsNotification": "{emoji} Attachment",
   "notificationsAndroidSystem": "{message_count} new messages in {conversation_count} conversations",
-  "emojiReactsHoverTwoName": "{name} & {other_name} reacted with {emoji}",
+  "emojiReactsHoverTwoName": "{name} & {other_name} reacted with <emoji />",
   "callsCalledYou": "{name} called you",
   "disappearingMessagesLegacy": "{name} is using an outdated client. Disappearing messages may not work as expected.",
   "attachmentsMedia": "{name} on {date_time}",
-  "emojiReactsHoverName": "{name} reacted with {emoji}",
+  "emojiReactsHoverName": "{name} reacted with <emoji />",
   "notificationsIosGroup": "{name} to {conversation_name}",
-  "emojiReactsHoverTwoNameMultiple": "{name}, {other_name} and {count} others reacted with {emoji}",
-  "emojiReactsHoverTwoNameOne": "{name}, {other_name} and 1 other reacted with {emoji}",
+  "emojiReactsHoverTwoNameMultiple": "{name}, {other_name} and {count} others reacted with <emoji />",
+  "emojiReactsHoverTwoNameOne": "{name}, {other_name} and 1 other reacted with <emoji />",
   "disappearingMessagesUpdated": "<b>{admin_name}</b> updated disappearing message settings.",
   "groupMemberMoreNew": "<b>{name}</b> and <b>{count} others</b> joined the group.",
   "groupMemberLeftMore": "<b>{name}</b> and <b>{count} others</b> left the group.",
@@ -704,7 +704,7 @@
   "membersInviteShareDescription": "Would you like to share group message history with <b>{name}</b>?",
   "yes": "Yes",
   "you": "You",
-  "emojiReactsHoverYouName": "You & {name} reacted with {emoji}",
+  "emojiReactsHoverYouName": "You & {name} reacted with <emoji />",
   "communityJoinedAlready": "You are already a member of this community.",
   "groupOnlyAdmin": "You are the only admin in &lt;b&gt;{group_name}&lt;/b&gt;.&lt;/br&gt;&lt;/br&gt;Group members and settings cannot be changed without an admin.",
   "callsYouCalled": "You called {name}",
@@ -726,11 +726,11 @@
   "callsYouMissedCallPermissions": "You missed a call from <b>{name}</b> because you haven't enabled <b>Voice and Video Calls</b> in Privacy Settings.",
   "lockAppEnablePasscode": "You must enable a passcode in your iOS Settings in order to use Screen Lock.",
   "settingsRestartDescription": "You must restart {app_name} to apply your new settings.",
-  "emojiReactsHoverYou": "You reacted with {emoji}",
+  "emojiReactsHoverYou": "You reacted with <emoji />",
   "groupRemovedYou": "You were removed from <b>{group_name}</b>.",
   "messageRequestPendingDescription": "You will be able to send voice messages and attachments once the recipient has approved this message request.",
   "linkPreviewsSendModalDescription": "You will not have full metadata protection when sending link previews.",
-  "emojiReactsHoverYouNameMultiple": "You, {name} & {count} others reacted with {emoji}",
+  "emojiReactsHoverYouNameMultiple": "You, {name} & {count} others reacted with <emoji />",
   "emojiReactsHoverYouNameOne": "You, {name} & 1 other reacted with <emoji />",
   "notificationsFastModeDescriptionIos": "You'll be notified of new messages reliably and immediately using Apple's notification Servers.",
   "notificationsFastModeDescriptionAndroid": "You'll be notified of new messages reliably and immediately using Google's notification Servers.",
diff --git a/preload.js b/preload.js
index 13712388d..2526b2796 100644
--- a/preload.js
+++ b/preload.js
@@ -32,6 +32,7 @@ window.sessionFeatureFlags = {
   useTestNet: isTestNet(),
   integrationTestEnv: isTestIntegration(),
   useClosedGroupV3: false,
+  replaceLocalizedStringsWithKeys: false,
   debug: {
     debugLogging: !_.isEmpty(process.env.SESSION_DEBUG),
     debugLibsessionDumps: !_.isEmpty(process.env.SESSION_DEBUG_LIBSESSION_DUMPS),
diff --git a/ts/components/basic/I18n.tsx b/ts/components/basic/I18n.tsx
index d3abcf2ff..022ba1737 100644
--- a/ts/components/basic/I18n.tsx
+++ b/ts/components/basic/I18n.tsx
@@ -20,6 +20,13 @@ const formattingTagRegex = new RegExp(
   'g'
 );
 
+const supportedCustomTags = ['emoji'];
+
+const customTagRegex = new RegExp(
+  `<(?:${supportedFormattingTags.join('|')})>.*?</(?:${supportedCustomTags.join('|')})>`,
+  'g'
+);
+
 const StyledHtmlRenderer = styled.span<{ darkMode: boolean }>`
   span {
     color: ${props => (props.darkMode ? 'var(--primary-color)' : 'var(--text-primary-color)')};
@@ -50,8 +57,11 @@ export const I18n = <T extends LocalizerToken>(props: I18nProps<T>) => {
     ...([props.token, i18nArgs] as GetMessageArgs<T>)
   );
 
+  const containsFormattingTag = i18nString.match(formattingTagRegex);
+  const containsCustomTag = i18nString.match(customTagRegex);
+
   /** If the string contains a relevant formatting tag, render it as HTML */
-  if (i18nString.match(formattingTagRegex)) {
+  if (containsFormattingTag || containsCustomTag) {
     return (
       <StyledHtmlRenderer darkMode={darkMode}>
         <SessionHtmlRenderer tag={props.as} html={i18nString} />
diff --git a/ts/components/basic/SessionCustomTagRenderer.tsx b/ts/components/basic/SessionCustomTagRenderer.tsx
index 5c8eb361a..10704e06e 100644
--- a/ts/components/basic/SessionCustomTagRenderer.tsx
+++ b/ts/components/basic/SessionCustomTagRenderer.tsx
@@ -15,7 +15,7 @@ export const customTag = {
   ),
 };
 
-export const SessionCustomTagRenderer = <Tag extends keyof typeof customTag>({
+export const SessionCustomTag = <Tag extends keyof typeof customTag>({
   tag,
   props,
 }: {
@@ -25,4 +25,6 @@ export const SessionCustomTagRenderer = <Tag extends keyof typeof customTag>({
   return customTag[tag](props);
 };
 
-SessionCustomTagRenderer({ tag: 'emoji', props: { emoji: '' } });
+export const SessionCustomTagRenderer = ({ str }: { str: string }) => {
+  const splitString = str.split();
+};
diff --git a/ts/components/conversation/message/reactions/ReactionPopup.tsx b/ts/components/conversation/message/reactions/ReactionPopup.tsx
index 4f54db50e..75e14bf96 100644
--- a/ts/components/conversation/message/reactions/ReactionPopup.tsx
+++ b/ts/components/conversation/message/reactions/ReactionPopup.tsx
@@ -92,32 +92,29 @@ const generateReactionString = (
 ) => {
   const name = contacts[0];
   const other_name = contacts[1];
-  const emoji = '';
 
   switch (numberOfReactors) {
     case 1:
       return isYou
-        ? window.i18n('emojiReactsHoverYou', { emoji })
-        : window.i18n('emojiReactsHoverName', { name, emoji });
+        ? window.i18n('emojiReactsHoverYou')
+        : window.i18n('emojiReactsHoverName', { name });
     case 2:
       return isYou
-        ? window.i18n('emojiReactsHoverYouName', { name, emoji })
-        : window.i18n('emojiReactsHoverTwoName', { name, other_name, emoji });
+        ? window.i18n('emojiReactsHoverYouName', { name })
+        : window.i18n('emojiReactsHoverTwoName', { name, other_name });
     case 3:
       return isYou
-        ? window.i18n('emojiReactsHoverYouNameOne', { name, emoji })
-        : window.i18n('emojiReactsHoverTwoNameOne', { name, other_name, emoji });
+        ? window.i18n('emojiReactsHoverYouNameOne', { name })
+        : window.i18n('emojiReactsHoverTwoNameOne', { name, other_name });
     default:
       return isYou
         ? window.i18n('emojiReactsHoverYouNameMultiple', {
             name,
-            emoji,
             count: numberOfReactors - 2,
           })
         : window.i18n('emojiReactsHoverTwoNameMultiple', {
             name,
             other_name,
-            emoji,
             count: numberOfReactors - 2,
           });
   }
diff --git a/ts/util/i18n.ts b/ts/util/i18n.ts
index 353a4d0b0..76927ac91 100644
--- a/ts/util/i18n.ts
+++ b/ts/util/i18n.ts
@@ -16,9 +16,10 @@ import {
   PluralString,
 } from '../types/Localizer';
 import { LOCALE_DEFAULTS } from '../session/constants';
+import { updateLocale } from '../state/ducks/dictionary';
 
 export function loadDictionary(locale: Locale) {
-  return import(`../../_locales/${locale}/messages.json`) as Promise<Dictionary>;
+  return import(`../../_locales/${locale}/messages.json`) as Promise<LocalizerDictionary>;
 }
 
 const timeLocaleMap = {
@@ -144,10 +145,17 @@ export const setupi18n = (locale: Locale, dictionary: LocalizerDictionary) => {
   if (!locale) {
     throw new Error('i18n: locale parameter is required');
   }
+
   if (!dictionary) {
     throw new Error('i18n: messages parameter is required');
   }
 
+  if (window.inboxStore) {
+    window.inboxStore.dispatch(updateLocale(locale));
+    window.log.info('Loaded dictionary dispatch');
+  }
+  window.log.info('i18n setup');
+
   /**
    * Retrieves a localized message string, substituting variables where necessary.
    *
@@ -165,12 +173,25 @@ export const setupi18n = (locale: Locale, dictionary: LocalizerDictionary) => {
     ...[token, args]: GetMessageArgs<T>
   ): R {
     try {
-      const inboxStore = window.inboxStore;
+      const {
+        inboxStore,
+        sessionFeatureFlags: { replaceLocalizedStringsWithKeys },
+      } = window;
 
-      const localizedDictionary =
+      if (replaceLocalizedStringsWithKeys) {
+        return token as R;
+      }
+
+      const storedDictionary =
         inboxStore && 'getState' in inboxStore && typeof inboxStore.getState === 'function'
           ? (inboxStore.getState().dictionary.dictionary as LocalizerDictionary)
-          : dictionary;
+          : undefined;
+
+      if (!storedDictionary) {
+        i18nLog(`i18n: Stored dictionary not found, using setup dictionary as fallback`);
+      }
+
+      const localizedDictionary = storedDictionary ?? dictionary;
 
       let localizedString = localizedDictionary[token] as R;
 
diff --git a/ts/window.d.ts b/ts/window.d.ts
index 4f522139a..99a6618b0 100644
--- a/ts/window.d.ts
+++ b/ts/window.d.ts
@@ -51,6 +51,7 @@ declare global {
       useTestNet: boolean;
       useClosedGroupV3: boolean;
       integrationTestEnv: boolean;
+      replaceLocalizedStringsWithKeys: boolean;
       debug: {
         debugLogging: boolean;
         debugLibsessionDumps: boolean;