diff --git a/Ring/Ring/Constants/Generated/Strings.swift b/Ring/Ring/Constants/Generated/Strings.swift
index b1cd370f9ba6b89747bec6360467fb44c6b46bb7..f362a886d07595efa6c6bfbab40a728f0103169c 100644
--- a/Ring/Ring/Constants/Generated/Strings.swift
+++ b/Ring/Ring/Constants/Generated/Strings.swift
@@ -41,8 +41,8 @@ internal enum L10n {
     internal static let enableAccount = L10n.tr("Localizable", "account.enableAccount", fallback: "Enable Account")
     /// Me
     internal static let me = L10n.tr("Localizable", "account.me", fallback: "Me")
-    /// account need to be migrated
-    internal static let needMigration = L10n.tr("Localizable", "account.needMigration", fallback: "account need to be migrated")
+    /// Account requires migration.
+    internal static let needMigration = L10n.tr("Localizable", "account.needMigration", fallback: "Account requires migration.")
     /// Port
     internal static let port = L10n.tr("Localizable", "account.port", fallback: "Port")
     /// Enter Port Number
@@ -105,8 +105,6 @@ internal enum L10n {
     internal static let contactMeOnJamiTitle = L10n.tr("Localizable", "accountPage.contactMeOnJamiTitle", fallback: "Contact me on Jami!")
     /// Encrypt account
     internal static let createPassword = L10n.tr("Localizable", "accountPage.createPassword", fallback: "Encrypt account")
-    /// Account Details
-    internal static let credentialsHeader = L10n.tr("Localizable", "accountPage.credentialsHeader", fallback: "Account Details")
     /// Enter current password
     internal static let currentPasswordPlaceholder = L10n.tr("Localizable", "accountPage.currentPasswordPlaceholder", fallback: "Enter current password")
     /// Device revocation error
@@ -267,8 +265,6 @@ internal enum L10n {
     internal static let upnpEnabled = L10n.tr("Localizable", "accountPage.upnpEnabled", fallback: "Use UPnP")
     /// Use proxy list
     internal static let useProxyList = L10n.tr("Localizable", "accountPage.useProxyList", fallback: "Use proxy list")
-    /// username: not registered
-    internal static let usernameNotRegistered = L10n.tr("Localizable", "accountPage.usernameNotRegistered", fallback: "username: not registered")
     /// Enter desired username
     internal static let usernamePlaceholder = L10n.tr("Localizable", "accountPage.usernamePlaceholder", fallback: "Enter desired username")
     /// Register
@@ -289,10 +285,6 @@ internal enum L10n {
     internal static let doneAction = L10n.tr("Localizable", "actions.doneAction", fallback: "Done")
     /// Go to Settings
     internal static let goToSettings = L10n.tr("Localizable", "actions.goToSettings", fallback: "Go to Settings")
-    ///   Audio Call
-    internal static let startAudioCall = L10n.tr("Localizable", "actions.startAudioCall", fallback: "  Audio Call")
-    ///   Video Call
-    internal static let startVideoCall = L10n.tr("Localizable", "actions.startVideoCall", fallback: "  Video Call")
     /// Stop sharing
     internal static let stopLocationSharing = L10n.tr("Localizable", "actions.stopLocationSharing", fallback: "Stop sharing")
   }
@@ -329,10 +321,6 @@ internal enum L10n {
     internal static let dbFailedTitle = L10n.tr("Localizable", "alerts.dbFailedTitle", fallback: "An error happened when launching Jami")
     /// An error occurred while connecting to Jami Account Management Server (JAMS). Please try again. If the problem persists, contact your system administrator.
     internal static let errorWrongCredentials = L10n.tr("Localizable", "alerts.errorWrongCredentials", fallback: "An error occurred while connecting to Jami Account Management Server (JAMS). Please try again. If the problem persists, contact your system administrator.")
-    /// Incoming call from 
-    internal static let incomingCallAllertTitle = L10n.tr("Localizable", "alerts.incomingCallAllertTitle", fallback: "Incoming call from ")
-    /// Ignore
-    internal static let incomingCallButtonIgnore = L10n.tr("Localizable", "alerts.incomingCallButtonIgnore", fallback: "Ignore")
     /// Turn on “Location Services” to allow “Jami” to determine device location.
     internal static let locationServiceIsDisabled = L10n.tr("Localizable", "alerts.locationServiceIsDisabled", fallback: "Turn on “Location Services” to allow “Jami” to determine device location.")
     /// Share my location
@@ -405,8 +393,6 @@ internal enum L10n {
     internal static let connecting = L10n.tr("Localizable", "calls.connecting", fallback: "Connecting…")
     /// Call with 
     internal static let currentCallWith = L10n.tr("Localizable", "calls.currentCallWith", fallback: "Call with ")
-    /// wants to talk to you
-    internal static let incomingCallInfo = L10n.tr("Localizable", "calls.incomingCallInfo", fallback: "wants to talk to you")
     /// Lower hand
     internal static let lowerHand = L10n.tr("Localizable", "calls.lowerHand", fallback: "Lower hand")
     /// Maximize
@@ -441,16 +427,14 @@ internal enum L10n {
     internal static let addToContactsLabel = L10n.tr("Localizable", "conversation.addToContactsLabel", fallback: "Add to contacts?")
     /// Contact blocked
     internal static let contactBlocked = L10n.tr("Localizable", "conversation.contactBlocked", fallback: "Contact blocked")
-    /// deleted a message
-    internal static let deletedMessage = L10n.tr("Localizable", "conversation.deletedMessage", fallback: "deleted a message")
+    /// %@ deleted a message
+    internal static func deletedMessage(_ p1: Any) -> String {
+      return L10n.tr("Localizable", "conversation.deletedMessage", String(describing: p1), fallback: "%@ deleted a message")
+    }
     /// Edited
     internal static let edited = L10n.tr("Localizable", "conversation.edited", fallback: "Edited")
     /// An error occurred while saving the image to the gallery.
     internal static let errorSavingImage = L10n.tr("Localizable", "conversation.errorSavingImage", fallback: "An error occurred while saving the image to the gallery.")
-    /// You are currently receiving a live location from 
-    internal static let explanationReceivingLocationFrom = L10n.tr("Localizable", "conversation.explanationReceivingLocationFrom", fallback: "You are currently receiving a live location from ")
-    /// You are currently sharing your location with 
-    internal static let explanationSendingLocationTo = L10n.tr("Localizable", "conversation.explanationSendingLocationTo", fallback: "You are currently sharing your location with ")
     /// sent you a conversation invitation.
     internal static let incomingRequest = L10n.tr("Localizable", "conversation.incomingRequest", fallback: "sent you a conversation invitation.")
     /// In reply to
@@ -463,8 +447,10 @@ internal enum L10n {
     internal static func receivedRequest(_ p1: Any) -> String {
       return L10n.tr("Localizable", "conversation.receivedRequest", String(describing: p1), fallback: "%@ sent you a conversation invitation.")
     }
-    /// replied to
-    internal static let repliedTo = L10n.tr("Localizable", "conversation.repliedTo", fallback: "replied to")
+    /// %@ replied to %@
+    internal static func repliedTo(_ p1: Any, _ p2: Any) -> String {
+      return L10n.tr("Localizable", "conversation.repliedTo", String(describing: p1), String(describing: p2), fallback: "%@ replied to %@")
+    }
     /// Hello,
     /// Do you want to join the conversation?
     internal static let requestMessage = L10n.tr("Localizable", "conversation.requestMessage", fallback: "Hello,\nDo you want to join the conversation?")
@@ -498,10 +484,8 @@ internal enum L10n {
     internal static let encryptTitle = L10n.tr("Localizable", "createAccount.encryptTitle", fallback: "Encrypt account with password")
     /// Invalid username. Please enter a valid username.
     internal static let invalidUsername = L10n.tr("Localizable", "createAccount.invalidUsername", fallback: "Invalid username. Please enter a valid username.")
-    /// Loading…
-    internal static let loading = L10n.tr("Localizable", "createAccount.loading", fallback: "Loading…")
-    /// looking for availability…
-    internal static let lookingForUsernameAvailability = L10n.tr("Localizable", "createAccount.lookingForUsernameAvailability", fallback: "looking for availability…")
+    /// Looking for availability…
+    internal static let lookingForUsernameAvailability = L10n.tr("Localizable", "createAccount.lookingForUsernameAvailability", fallback: "Looking for availability…")
     /// You can choose a username to help others more easily find and reach you on Jami.
     internal static let nameExplanation = L10n.tr("Localizable", "createAccount.nameExplanation", fallback: "You can choose a username to help others more easily find and reach you on Jami.")
     /// New account
@@ -512,16 +496,14 @@ internal enum L10n {
     internal static let timeoutMessage = L10n.tr("Localizable", "createAccount.timeoutMessage", fallback: "Username registration is in progress. Please wait…")
     /// Account Created
     internal static let timeoutTitle = L10n.tr("Localizable", "createAccount.timeoutTitle", fallback: "Account Created")
-    /// username already taken
-    internal static let usernameAlreadyTaken = L10n.tr("Localizable", "createAccount.usernameAlreadyTaken", fallback: "username already taken")
+    /// Username already taken
+    internal static let usernameAlreadyTaken = L10n.tr("Localizable", "createAccount.usernameAlreadyTaken", fallback: "Username already taken")
     /// Account was created but username was not registered
     internal static let usernameNotRegisteredMessage = L10n.tr("Localizable", "createAccount.UsernameNotRegisteredMessage", fallback: "Account was created but username was not registered")
     /// Network error
     internal static let usernameNotRegisteredTitle = L10n.tr("Localizable", "createAccount.UsernameNotRegisteredTitle", fallback: "Network error")
     /// The username is available.
     internal static let usernameValid = L10n.tr("Localizable", "createAccount.usernameValid", fallback: "The username is available.")
-    /// Adding account
-    internal static let waitCreateAccountTitle = L10n.tr("Localizable", "createAccount.waitCreateAccountTitle", fallback: "Adding account")
   }
   internal enum CreateProfile {
     /// Create profile picture
@@ -592,22 +574,36 @@ internal enum L10n {
   internal enum GeneratedMessage {
     /// Invitation received
     internal static let contactAdded = L10n.tr("Localizable", "generatedMessage.contactAdded", fallback: "Invitation received")
-    /// was blocked from the conversation.
-    internal static let contactBlocked = L10n.tr("Localizable", "generatedMessage.contactBlocked", fallback: "was blocked from the conversation.")
-    /// has left the conversation.
-    internal static let contactLeftConversation = L10n.tr("Localizable", "generatedMessage.contactLeftConversation", fallback: "has left the conversation.")
-    /// was unblocked from the conversation.
-    internal static let contactUnblocked = L10n.tr("Localizable", "generatedMessage.contactUnblocked", fallback: "was unblocked from the conversation.")
-    /// has joined the conversation.
-    internal static let invitationAccepted = L10n.tr("Localizable", "generatedMessage.invitationAccepted", fallback: "has joined the conversation.")
-    /// was invited to join the conversation.
-    internal static let invitationReceived = L10n.tr("Localizable", "generatedMessage.invitationReceived", fallback: "was invited to join the conversation.")
+    /// %@ was blocked from the conversation.
+    internal static func contactBlocked(_ p1: Any) -> String {
+      return L10n.tr("Localizable", "generatedMessage.contactBlocked", String(describing: p1), fallback: "%@ was blocked from the conversation.")
+    }
+    /// %@ has left the conversation.
+    internal static func contactLeftConversation(_ p1: Any) -> String {
+      return L10n.tr("Localizable", "generatedMessage.contactLeftConversation", String(describing: p1), fallback: "%@ has left the conversation.")
+    }
+    /// %@ was unblocked from the conversation.
+    internal static func contactUnblocked(_ p1: Any) -> String {
+      return L10n.tr("Localizable", "generatedMessage.contactUnblocked", String(describing: p1), fallback: "%@ was unblocked from the conversation.")
+    }
+    /// %@ has joined the conversation.
+    internal static func invitationAccepted(_ p1: Any) -> String {
+      return L10n.tr("Localizable", "generatedMessage.invitationAccepted", String(describing: p1), fallback: "%@ has joined the conversation.")
+    }
+    /// %@ was invited to join the conversation.
+    internal static func invitationReceived(_ p1: Any) -> String {
+      return L10n.tr("Localizable", "generatedMessage.invitationReceived", String(describing: p1), fallback: "%@ was invited to join the conversation.")
+    }
     /// Live location sharing
     internal static let liveLocationSharing = L10n.tr("Localizable", "generatedMessage.liveLocationSharing", fallback: "Live location sharing")
     /// Missed incoming call
     internal static let missedIncomingCall = L10n.tr("Localizable", "generatedMessage.missedIncomingCall", fallback: "Missed incoming call")
     /// Missed outgoing call
     internal static let missedOutgoingCall = L10n.tr("Localizable", "generatedMessage.missedOutgoingCall", fallback: "Missed outgoing call")
+    /// Your invitation was accepted.
+    internal static let nonSwarmInvitationAccepted = L10n.tr("Localizable", "generatedMessage.nonSwarmInvitationAccepted", fallback: "Your invitation was accepted.")
+    /// You sent an invitation.
+    internal static let nonSwarmInvitationReceived = L10n.tr("Localizable", "generatedMessage.nonSwarmInvitationReceived", fallback: "You sent an invitation.")
     /// Outgoing call
     internal static let outgoingCall = L10n.tr("Localizable", "generatedMessage.outgoingCall", fallback: "Outgoing call")
     /// Conversation created
@@ -724,18 +720,16 @@ internal enum L10n {
     internal static let title = L10n.tr("Localizable", "importFromArchive.title", fallback: "Import from archive")
   }
   internal enum Invitations {
-    /// accepted
-    internal static let accepted = L10n.tr("Localizable", "invitations.accepted", fallback: "accepted")
-    /// blocked
-    internal static let blocked = L10n.tr("Localizable", "invitations.blocked", fallback: "blocked")
-    /// declined
-    internal static let declined = L10n.tr("Localizable", "invitations.declined", fallback: "declined")
+    /// Accepted
+    internal static let accepted = L10n.tr("Localizable", "invitations.accepted", fallback: "Accepted")
+    /// Blocked
+    internal static let blocked = L10n.tr("Localizable", "invitations.blocked", fallback: "Blocked")
+    /// Declined
+    internal static let declined = L10n.tr("Localizable", "invitations.declined", fallback: "Declined")
     /// Invitations received
     internal static let list = L10n.tr("Localizable", "invitations.list", fallback: "Invitations received")
     /// No invitations
     internal static let noInvitations = L10n.tr("Localizable", "invitations.noInvitations", fallback: "No invitations")
-    /// pending
-    internal static let pending = L10n.tr("Localizable", "invitations.pending", fallback: "pending")
   }
   internal enum LinkDevice {
     /// An error occurred while exporting the account.
diff --git a/Ring/Ring/Database/DBManager.swift b/Ring/Ring/Database/DBManager.swift
index ad89e6766d29d2482751944ddd5065b07438c69f..d40243aee5259d033dd7639c14af3bc355a44e8d 100644
--- a/Ring/Ring/Database/DBManager.swift
+++ b/Ring/Ring/Database/DBManager.swift
@@ -49,9 +49,9 @@ enum GeneratedMessage: Int {
         case .contactAdded:
             return L10n.GeneratedMessage.contactAdded
         case .invitationReceived:
-            return L10n.GeneratedMessage.invitationReceived
+            return L10n.GeneratedMessage.nonSwarmInvitationReceived
         case .invitationAccepted:
-            return L10n.GeneratedMessage.invitationAccepted
+            return L10n.GeneratedMessage.nonSwarmInvitationAccepted
         case .missedOutgoingCall:
             return L10n.GeneratedMessage.missedOutgoingCall
         case .missedIncomingCall:
@@ -156,7 +156,7 @@ enum InteractionType: String {
         case .call:
             return .call
         case .contact:
-            return .contact
+            return .contact( .add)
         case .iTransfer:
             return .fileTransfer
         case .oTransfer:
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/ContactMessageVM.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/ContactMessageVM.swift
index 72d44f208b428b6bd8cceaa5e95d37114c88d926..434c5742aee5314c2013055c324de3b57e8ebebe 100644
--- a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/ContactMessageVM.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/ContactMessageVM.swift
@@ -43,7 +43,9 @@ class ContactMessageVM: ObservableObject, MessageAppearanceProtocol, AvatarImage
     var message: MessageModel
     var username = "" {
         didSet {
-            self.content = self.username.isEmpty ? self.message.content : self.username + " " + self.message.content
+            let jamiId = message.uri.isEmpty ? message.authorId : message.uri
+            let name = self.username.isEmpty ? jamiId : self.username
+            self.content = self.message.getContactInteractionString(name: name) ?? ""
         }
     }
     var infoState: PublishSubject<State>?
@@ -64,7 +66,7 @@ class ContactMessageVM: ObservableObject, MessageAppearanceProtocol, AvatarImage
 
     func setInfoState(state: PublishSubject<State>) {
         self.infoState = state
-        if message.type == .contact && message.incoming {
+        if message.type.isContact && message.incoming {
             let jamiId = message.uri.isEmpty ? message.authorId : message.uri
             requestAvatar(jamiId: jamiId)
             requestName(jamiId: jamiId)
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageContentVM.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageContentVM.swift
index 904681582bdcd99883003e62fd6b14423053efb4..b809a89f8a87a7a77f42067910990a0b50828605 100644
--- a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageContentVM.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageContentVM.swift
@@ -123,7 +123,7 @@ class MessageContentVM: ObservableObject, PreviewViewControllerDelegate, PlayerD
     @Published var finalImage: UIImage?
     @Published var messageDeleted = false
     @Published var messageEdited = false
-    @Published var messageDeletedText = " " + L10n.Conversation.deletedMessage
+    @Published var messageDeletedText = ""
     @Published var editIndicator = L10n.Conversation.edited
     @Published var editionColor = Color.secondary
     @Published var scale: CGFloat = 1
@@ -173,7 +173,7 @@ class MessageContentVM: ObservableObject, PreviewViewControllerDelegate, PlayerD
         didSet {
             DispatchQueue.main.async { [weak self] in
                 guard let self = self else { return }
-                self.messageDeletedText = self.username + " " + L10n.Conversation.deletedMessage
+                self.messageDeletedText = L10n.Conversation.deletedMessage(self.username)
             }
         }
     }
@@ -222,7 +222,7 @@ class MessageContentVM: ObservableObject, PreviewViewControllerDelegate, PlayerD
         if self.isLink() {
             let backgroundIsLightColor: Bool = self.backgroundColor.isLight(threshold: 0.8) ?? true
             self.styling.textColor = backgroundIsLightColor ? .blue : .white
-        } else if !self.isIncoming && self.type != .contact {
+        } else if !self.isIncoming && !self.type.isContact {
             self.styling.textColor = Color.white
         } else {
             self.styling.textColor = self.styling.defaultTextColor
@@ -238,7 +238,7 @@ class MessageContentVM: ObservableObject, PreviewViewControllerDelegate, PlayerD
     }
 
     private func updateBackgroundColor() {
-        if self.type == .contact || self.content.containsOnlyEmoji && !self.messageDeleted && !self.messageEdited {
+        if self.type.isContact || self.content.containsOnlyEmoji && !self.messageDeleted && !self.messageEdited {
             self.backgroundColor = .clear
         } else {
             self.backgroundColor = isIncoming ? Color(.jamiMsgCellReceived) : Color(preferencesColor)
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageReplyTargetVM.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageReplyTargetVM.swift
index a3284bdf65a0a4c898be48f6b89d5b5a2d2d65f7..7bf0e15776029d420e17dce4bc2d8175eb03075a 100644
--- a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageReplyTargetVM.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageReplyTargetVM.swift
@@ -97,8 +97,8 @@ class MessageReplyTargetVM: ObservableObject, MessageAppearanceProtocol, AvatarI
     private func getInReplyMessage() -> String {
         let inReplyToSelf = L10n.Conversation.inReplyTo + " \(L10n.Account.me)"
         let inReplyToOther = L10n.Conversation.inReplyTo + " \(targetReplyUsername)"
-        let repliedByOtherToSelf = "\(username) " + L10n.Conversation.repliedTo + " \(L10n.Account.me)"
-        let repliedByOtherToOther = "\(username) " + L10n.Conversation.repliedTo + " \(targetReplyUsername)"
+        let repliedByOtherToSelf = L10n.Conversation.repliedTo(username, L10n.Account.me)
+        let repliedByOtherToOther = L10n.Conversation.repliedTo(username, targetReplyUsername)
 
         switch (replyIsIncoming(), targetReplyIsIncoming()) {
         case (true, true):
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageRowVM.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageRowVM.swift
index 17f0e3f72e9619e25f90ab248f634e6933f1baa6..dfa68791d4a382edba92fb5f2662e3ffc6021e9d 100644
--- a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageRowVM.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageRowVM.swift
@@ -78,7 +78,7 @@ class MessageRowVM: ObservableObject, MessageAppearanceProtocol, MessageReadObse
     init(message: MessageModel) {
         self.message = message
         self.incoming = message.incoming
-        self.centeredMessage = message.type == .contact || message.type == .initial
+        self.centeredMessage = message.type.isContact || message.type == .initial
         self.readBorderColor = Color(UIColor.systemBackground)
         self.timeString = getTimeLabelString()
         self.updateMessageStatus()
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessagesListVM.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessagesListVM.swift
index 6c61e9963105d2b22124d89776cecb5782d710e2..cf88288291782c6efe6b76ebe2c88d582f5fb17f 100644
--- a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessagesListVM.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessagesListVM.swift
@@ -584,7 +584,7 @@ class MessagesListVM: ObservableObject {
         let lastMessage = lastMessageContainer.message
         self.lastMessageDate.accept(lastMessage.receivedDate.conversationTimestamp())
 
-        if lastMessage.type != .contact {
+        if !lastMessage.type.isContact {
             self.lastMessage.accept(lastMessage.content)
         } else {
             // For contact messages, update when the display name is available.
@@ -925,7 +925,7 @@ class MessagesListVM: ObservableObject {
 
     private func isBreakingSequence(message: MessageModel, secondMessage: MessageModel) -> Bool {
         let differentUri = message.uri != secondMessage.uri
-        let messageTypeCheck = message.type == .contact || message.type == .initial
+        let messageTypeCheck = message.type.isContact || message.type == .initial
         let differentAuthor = message.authorId != secondMessage.authorId
         let isReplyCheck = message.isReply() || secondMessage.isReply()
         let hasReactions = !message.reactions.isEmpty || !secondMessage.reactions.isEmpty
diff --git a/Ring/Ring/Models/MessageModel.swift b/Ring/Ring/Models/MessageModel.swift
index 69d611f6e570817c1e84a8c8ad9350b54a095d8a..950e6025573d9a316a2d4363b37b860497d44388 100644
--- a/Ring/Ring/Models/MessageModel.swift
+++ b/Ring/Ring/Models/MessageModel.swift
@@ -37,14 +37,66 @@ enum MessageAttributes: String {
     case totalSize = "totalSize"
 }
 
-enum MessageType: String {
-    case text = "text/plain"
-    case fileTransfer = "application/data-transfer+json"
-    case contact = "member"
-    case call = "application/call-history+json"
-    case merge = "merge"
-    case initial = "initial"
-    case profile = "application/update-profile"
+enum MessageType: Equatable {
+    case text
+    case fileTransfer
+    case contact(ContactAction)
+    case call
+    case merge
+    case initial
+    case profile
+
+    var rawValue: String {
+        switch self {
+        case .text: return "text/plain"
+        case .fileTransfer: return "application/data-transfer+json"
+        case .contact: return "member"
+        case .call: return "application/call-history+json"
+        case .merge: return "merge"
+        case .initial: return "initial"
+        case .profile: return "application/update-profile"
+        }
+    }
+
+    init?(rawValue: String) {
+        switch rawValue {
+        case "text/plain": self = .text
+        case "application/data-transfer+json": self = .fileTransfer
+        case "member": self = .contact(.add)
+        case "application/call-history+json": self = .call
+        case "merge": self = .merge
+        case "initial": self = .initial
+        case "application/update-profile": self = .profile
+        default: return nil
+        }
+    }
+
+    static func == (lhs: MessageType, rhs: MessageType) -> Bool {
+        switch (lhs, rhs) {
+        case (.text, .text),
+             (.fileTransfer, .fileTransfer),
+             (.call, .call),
+             (.merge, .merge),
+             (.initial, .initial),
+             (.contact, .contact),
+             (.profile, .profile):
+            return true
+        default:
+            return false
+        }
+    }
+
+    var isContact: Bool {
+        if case .contact = self { return true }
+        return false
+    }
+
+    func getInteractionString(name: String, isIncoming: Bool) -> String? {
+        if case .contact(let action) = self {
+            return action.getInteractionString(name: name, isIncomig: isIncoming)
+        }
+        return nil
+    }
 }
 
 enum ContactAction: String {
@@ -53,6 +105,22 @@ enum ContactAction: String {
     case join
     case banned
     case unban
+
+    func getInteractionString(name: String, isIncomig: Bool) -> String {
+        switch self {
+        case .add:
+            return isIncomig ? L10n.GeneratedMessage.invitationReceived(name) :
+                L10n.GeneratedMessage.contactAdded
+        case .join:
+            return isIncomig ? L10n.GeneratedMessage.invitationAccepted(name) : L10n.GeneratedMessage.youJoined
+        case .remove:
+            return L10n.GeneratedMessage.contactLeftConversation(name)
+        case.banned:
+            return L10n.GeneratedMessage.contactBlocked(name)
+        case .unban:
+            return L10n.GeneratedMessage.contactUnblocked(name)
+        }
+    }
 }
 
 class MessageAction: Identifiable, Equatable, Hashable {
@@ -161,7 +229,7 @@ public class MessageModel {
            let messageType = MessageType(rawValue: type) {
             self.type = messageType
         }
-        if let content = info[MessageAttributes.body.rawValue], self.type == .text {
+        if let content = info[MessageAttributes.body.rawValue], self.type == MessageType.text {
             self.content = content
         }
         if let reply = info[MessageAttributes.reply.rawValue] {
@@ -209,19 +277,7 @@ public class MessageModel {
         case .contact:
             if let action = info[MessageAttributes.action.rawValue],
                let contactAction = ContactAction(rawValue: action) {
-                switch contactAction {
-                case .add:
-                    self.content = self.incoming ? L10n.GeneratedMessage.invitationReceived :
-                        L10n.GeneratedMessage.contactAdded
-                case .join:
-                    self.content = self.incoming ? L10n.GeneratedMessage.invitationAccepted : L10n.GeneratedMessage.youJoined
-                case .remove:
-                    self.content = L10n.GeneratedMessage.contactLeftConversation
-                case.banned:
-                    self.content = L10n.GeneratedMessage.contactBlocked
-                case .unban:
-                    self.content = L10n.GeneratedMessage.contactUnblocked
-                }
+                self.type = .contact(contactAction)
             }
         case .fileTransfer:
             if let fileid = info[MessageAttributes.fileId.rawValue] {
@@ -238,6 +294,10 @@ public class MessageModel {
         }
     }
 
+    func getContactInteractionString(name: String) -> String? {
+        return self.type.getInteractionString(name: name, isIncoming: incoming)
+    }
+
     func updateFrom(info: [String: String]) {
         if let content = info[MessageAttributes.body.rawValue], self.type == .text {
             self.content = content
diff --git a/Ring/Ring/Resources/en.lproj/Localizable.strings b/Ring/Ring/Resources/en.lproj/Localizable.strings
index d6f852ef906a4f88656562785fa78c227d68288f..a45673362a0a32990e9430ef695e4a52f507458d 100644
--- a/Ring/Ring/Resources/en.lproj/Localizable.strings
+++ b/Ring/Ring/Resources/en.lproj/Localizable.strings
@@ -129,8 +129,6 @@
 "conversation.addToContactsLabel" = "Add to contacts?";
 "conversation.notContactLabel" = "is not in your contact list";
 "conversation.messagePlaceholder" = "Write to";
-"conversation.explanationSendingLocationTo" = "You are currently sharing your location with ";
-"conversation.explanationReceivingLocationFrom" = "You are currently receiving a live location from ";
 "conversation.errorSavingImage" = "An error occurred while saving the image to the gallery.";
 "conversation.receivedRequest" = "%@ sent you a conversation invitation.";
 "conversation.incomingRequest" = "sent you a conversation invitation.";
@@ -140,18 +138,17 @@
 "conversation.synchronizationTitle" = "You have accepted the conversation invitation.";
 "conversation.synchronizationMessage" = "Waiting for %@ to connect to synchronize the conversation…";
 "conversation.inReplyTo" = "In reply to";
-"conversation.repliedTo" = "replied to";
+"conversation.repliedTo" = "%@ replied to %@";
 "conversation.yourself" = "You";
 "conversation.edited" = "Edited";
-"conversation.deletedMessage" = "deleted a message";
+"conversation.deletedMessage" = "%@ deleted a message";
 "conversation.contactBlocked" = "Contact blocked";
 
 // Invitations
 "invitations.noInvitations" = "No invitations";
-"invitations.pending" = "pending";
-"invitations.accepted" = "accepted";
-"invitations.declined" = "declined";
-"invitations.blocked" = "blocked";
+"invitations.accepted" = "Accepted";
+"invitations.declined" = "Declined";
+"invitations.blocked" = "Blocked";
 "invitations.list" = "Invitations received";
 
 // Walkthrough
@@ -176,12 +173,10 @@
 
 // Create Account form
 "createAccount.createAccountFormTitle" = "Join Jami";
-"createAccount.lookingForUsernameAvailability" = "looking for availability…";
+"createAccount.lookingForUsernameAvailability" = "Looking for availability…";
 "createAccount.invalidUsername" = "Invalid username. Please enter a valid username.";
-"createAccount.usernameAlreadyTaken" = "username already taken";
+"createAccount.usernameAlreadyTaken" = "Username already taken";
 "createAccount.usernameValid" = "The username is available.";
-"createAccount.loading" = "Loading…";
-"createAccount.waitCreateAccountTitle" = "Adding account";
 "createAccount.UsernameNotRegisteredTitle" = "Network error";
 "createAccount.UsernameNotRegisteredMessage" = "Account was created but username was not registered";
 "createAccount.timeoutTitle" = "Account Created";
@@ -261,15 +256,10 @@
 "actions.deleteAction" = "Delete";
 "actions.backAction" = "Back";
 "actions.doneAction" = "Done";
-"alerts.incomingCallAllertTitle" = "Incoming call from ";
-"alerts.incomingCallButtonIgnore" = "Ignore";
-"actions.startAudioCall" = "  Audio Call";
-"actions.startVideoCall" = "  Video Call";
 "actions.goToSettings" = "Go to Settings";
 "actions.stopLocationSharing" = "Stop sharing";
 
 // Calls
-"calls.incomingCallInfo" = "wants to talk to you";
 "calls.ringing" = "Ringing…";
 "calls.connecting" = "Connecting…";
 "calls.callFinished" = "Call finished";
@@ -286,8 +276,6 @@
 "accountPage.devicesListHeader" = "Devices";
 "accountPage.settingsHeader" = "Settings";
 "accountPage.notificationsHeader" = "Notifications";
-"accountPage.usernameNotRegistered" = "username: not registered";
-"accountPage.credentialsHeader" = "Account Details";
 "accountPage.blockedContacts" = "Blocked contacts";
 "accountPage.unblockContact" = "Unblock";
 "accountPage.proxyAddressAlert" = "Provide proxy address";
@@ -420,7 +408,7 @@
 "account.statusConnecting" = "Connecting";
 "account.statusUnknown" = "Unknown";
 "account.statusConnectionerror" = "Connection Error";
-"account.needMigration" = "account need to be migrated";
+"account.needMigration" = "Account requires migration.";
 "account.me" = "Me";
 
 // Block List Page
@@ -464,16 +452,18 @@
 // Generated Message
 "generatedMessage.contactAdded" = "Invitation received";
 "generatedMessage.swarmCreated" = "Conversation created";
-"generatedMessage.invitationReceived" = "was invited to join the conversation.";
-"generatedMessage.invitationAccepted" = "has joined the conversation.";
+"generatedMessage.invitationReceived" = "%@ was invited to join the conversation.";
+"generatedMessage.invitationAccepted" = "%@ has joined the conversation.";
 "generatedMessage.youJoined" = "You joined the conversation.";
-"generatedMessage.contactBlocked" = "was blocked from the conversation.";
-"generatedMessage.contactUnblocked" = "was unblocked from the conversation.";
+"generatedMessage.contactBlocked" = "%@ was blocked from the conversation.";
+"generatedMessage.contactUnblocked" = "%@ was unblocked from the conversation.";
 "generatedMessage.outgoingCall" = "Outgoing call";
 "generatedMessage.missedOutgoingCall" = "Missed outgoing call";
 "generatedMessage.missedIncomingCall" = "Missed incoming call";
 "generatedMessage.liveLocationSharing" = "Live location sharing";
-"generatedMessage.contactLeftConversation" = "has left the conversation.";
+"generatedMessage.contactLeftConversation" = "%@ has left the conversation.";
+"generatedMessage.nonSwarmInvitationReceived" = "You sent an invitation.";
+"generatedMessage.nonSwarmInvitationAccepted" = "Your invitation was accepted.";
 
 // General Settings
 "generalSettings.videoSettings" = "Video settings";
diff --git a/Ring/Ring/Services/ConversationsService.swift b/Ring/Ring/Services/ConversationsService.swift
index 514b60568ede5972a826867ce3b661969216990a..be6aed489a7759b435957ccd4d319e73163494c5 100644
--- a/Ring/Ring/Services/ConversationsService.swift
+++ b/Ring/Ring/Services/ConversationsService.swift
@@ -99,7 +99,7 @@ class ConversationsService {
                 }
                 /// filter out contact requests
                 let conversationsFromDB = conversationsModels.filter { conversation in
-                    !(conversation.messages.count == 1 && conversation.messages.first!.content == L10n.GeneratedMessage.invitationReceived)
+                    !(conversation.messages.count == 1 && conversation.messages.first!.content == L10n.GeneratedMessage.nonSwarmInvitationReceived)
                 }
                 /// Filter out conversations that already added to swarm
                 .filter { conversation in
@@ -487,7 +487,7 @@ class ConversationsService {
                                 conversation.accountId == toAccountId
                         })
                         .first {
-                        let content = (message.type == .contact || message.type == .call) ?
+                        let content = (message.type.isContact || message.type == .call) ?
                             GeneratedMessage.init(from: message.content).toMessage(with: Int(duration))
                             : message.content
                         message.content = content
@@ -750,7 +750,7 @@ class ConversationsService {
                                 conversation.accountId == accountId
                         })
                         .first {
-                        let content = (message.type == .contact || message.type == .call) ?
+                        let content = (message.type.isContact || message.type == .call) ?
                             GeneratedMessage.init(from: message.content).toMessage(with: Int(0))
                             : message.content
                         message.content = content
diff --git a/Ring/Ring/Services/GeneratedInteractionsManager.swift b/Ring/Ring/Services/GeneratedInteractionsManager.swift
index 131a81336fb86a68d0818cd9c92a44972574e21d..76454df41e209540351fe21fffc971dcae6d8532 100644
--- a/Ring/Ring/Services/GeneratedInteractionsManager.swift
+++ b/Ring/Ring/Services/GeneratedInteractionsManager.swift
@@ -103,7 +103,7 @@ class GeneratedInteractionsManager {
             return
         }
         // remove conversation if it contain only contact messages
-        let messages = conversation.messages.filter({ $0.type != .contact })
+        let messages = conversation.messages.filter({ !$0.type.isContact })
 
         if !messages.isEmpty {
             return