diff --git a/CMakeLists.txt b/CMakeLists.txt
index b3e31012627ae46ef04c2af3aab50045fc197392..01f1d658dfc87f6c3f89f9234adcde22506c413a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -347,6 +347,7 @@ SET(libringclient_api_LIB_HDRS
   src/api/contact.h
   src/api/call.h
   src/api/account.h
+  src/api/chatview.h
   src/api/lrc.h
   src/api/avmodel.h
   src/api/pluginmodel.h
diff --git a/src/api/chatview.h b/src/api/chatview.h
new file mode 100644
index 0000000000000000000000000000000000000000..9a0a70eb7eab58b0fff3316117da40611f40e654
--- /dev/null
+++ b/src/api/chatview.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+ *    Copyright (C) 2020 Savoir-faire Linux Inc.                            *
+ *   Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>           *
+ *                                                                          *
+ *   This library is free software; you can redistribute it and/or          *
+ *   modify it under the terms of the GNU Lesser General Public             *
+ *   License as published by the Free Software Foundation; either           *
+ *   version 2.1 of the License, or (at your option) any later version.     *
+ *                                                                          *
+ *   This library is distributed in the hope that it will be useful,        *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of         *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU      *
+ *   Lesser General Public License for more details.                        *
+ *                                                                          *
+ *   You should have received a copy of the GNU General Public License      *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
+ ***************************************************************************/
+#pragma once
+
+// Qt
+#include <QObject>
+#include <QVariantMap>
+#include <QString>
+
+namespace lrc {
+
+namespace api {
+
+namespace chatview {
+
+QVariantMap
+getTranslatedStrings()
+{
+    return {
+        {"Hide chat view", QObject::tr("Hide chat view")},
+        {"Place video call", QObject::tr("Place video call")},
+        {"Place audio call", QObject::tr("Place audio call")},
+        {"Add to conversations", QObject::tr("Add to conversations")},
+        {"Unban contact", QObject::tr("Unban contact")},
+        {"Send", QObject::tr("Send")},
+        {"Options", QObject::tr("Options")},
+        {"Jump to latest", QObject::tr("Jump to latest")},
+        {"Send file", QObject::tr("Send file")},
+        {"Leave video message", QObject::tr("Leave video message")},
+        {"Leave audio message", QObject::tr("Leave audio message")},
+        {"Accept", QObject::tr("Accept")},
+        {"Refuse", QObject::tr("Refuse")},
+        {"Block", QObject::tr("Block")},
+        {"Type a message", QObject::tr("Type a message")},
+        {"Note: an interaction will create a new contact.",
+         QObject::tr("Note: an interaction will create a new contact.")},
+        {"is not in your contacts", QObject::tr("is not in your contacts")},
+        {"Note: you can automatically accept this invitation by sending a message.",
+         QObject::tr("Note: you can automatically accept this invitation by sending a message.")},
+#if defined(WIN32)
+        {"%d days ago", QObject::tr("{0} days ago")},
+        {"%d hours ago", QObject::tr("{0} hours ago")},
+        {"%d minutes ago", QObject::tr("{0} minutes ago")},
+#else
+        {"%d days ago", QObject::tr("%d days ago")},
+        {"%d hours ago", QObject::tr("%d hours ago")},
+        {"%d minutes ago", QObject::tr("%d minutes ago")},
+#endif
+        {"one day ago", QObject::tr("one day ago")},
+        {"one hour ago", QObject::tr("one hour ago")},
+        {"just now", QObject::tr("just now")},
+        {"Failure", QObject::tr("Failure")},
+        {"Accept", QObject::tr("Accept")},
+        {"Refuse", QObject::tr("Refuse")},
+        {"Delete", QObject::tr("Delete")},
+        {"Retry", QObject::tr("Retry")},
+    };
+}
+
+} // namespace chatview
+} // namespace api
+} // namespace lrc
diff --git a/src/web-chatview/chatview.js b/src/web-chatview/chatview.js
index 94d4afc5615736bc312ae3d18fd550319578b828..8d88ec4e69f476cfda03ca1a1c6130d68959b6b4 100644
--- a/src/web-chatview/chatview.js
+++ b/src/web-chatview/chatview.js
@@ -133,7 +133,16 @@ function init_i18n(data) {
         if (data === undefined) {
             i18n = new Jed({ locale_data: { "messages": { "": {} } } }) // eslint-disable-line no-undef
         } else {
-            i18n = new Jed(data) // eslint-disable-line no-undef
+            var domain = {
+                "" : {
+                    // Domain name
+                    "domain" : "messages",
+                }
+            }
+            for  (var key in data) {
+                domain[key] = [data[key]]
+            }
+            i18n = new Jed({ locale_data: { "messages": domain } })
         }
         reset_message_bar_input()
         set_titles()
@@ -142,20 +151,20 @@ function init_i18n(data) {
 
 function set_titles() {
     if (use_qt){
-        backButton.title = i18nStringData["backButtonTitle"]
-        placeCallButton.title = i18nStringData["placeCallButtonTitle"]
-        placeAudioCallButton.title = i18nStringData["placeAudioCallButtonTitle"]
-        addToConversationsButton.title = i18nStringData["addToConversationsButtonTitle"]
-        unbanButton.title = i18nStringData["unbanButtonTitle"]
-        sendButton.title = i18nStringData["sendButtonTitle"]
-        optionsButton.title = i18nStringData["optionsButtonTitle"]
-        backToBottomBtn.innerHTML = `${i18nStringData["backToBottomBtnInnerHTML"]} &#9660;`
-        sendFileButton.title = i18nStringData["sendFileButtonTitle"]
-        videoRecordButton.title = i18nStringData["videoRecordButtonTitle"]
-        audioRecordButton.title = i18nStringData["audioRecordButtonTitle"]
-        acceptButton.title = i18nStringData["acceptButtonTitle"]
-        refuseButton.title = i18nStringData["refuseButtonTitle"]
-        blockButton.title = i18nStringData["blockButtonTitle"]
+        backButton.title = i18nStringData["Hide chat view"]
+        placeCallButton.title = i18nStringData["Place video call"]
+        placeAudioCallButton.title = i18nStringData["Place audio call"]
+        addToConversationsButton.title = i18nStringData["Add to conversations"]
+        unbanButton.title = i18nStringData["Unban contact"]
+        sendButton.title = i18nStringData["Send"]
+        optionsButton.title = i18nStringData["Options"]
+        backToBottomBtn.innerHTML = `${i18nStringData["Jump to latest"]} &#9660;`
+        sendFileButton.title = i18nStringData["Send file"]
+        videoRecordButton.title = i18nStringData["Leave video message"]
+        audioRecordButton.title = i18nStringData["Leave audio message"]
+        acceptButton.title = i18nStringData["Accept"]
+        refuseButton.title = i18nStringData["Refuse"]
+        blockButton.title = i18nStringData["Block"]
     } else {
         backButton.title = i18n.gettext("Hide chat view")
         placeCallButton.title = i18n.gettext("Place video call")
@@ -176,7 +185,7 @@ function set_titles() {
 
 function reset_message_bar_input() {
     messageBarInput.placeholder = use_qt ?
-        i18nStringData["messageBarInputPlaceholder"] : i18n.gettext("Type a message")
+        i18nStringData["Type a message"] : i18n.gettext("Type a message")
 }
 
 function onScrolled_() {
@@ -301,7 +310,8 @@ function update_chatview_frame(accountEnabled, banned, temporary, alias, bestid)
         isTemporary = temporary
         if (temporary) {
             addToConvButton.style.display = "flex"
-            messageBarInput.placeholder = use_qt ? i18nStringData["placeHolderTemporaryContact"] :
+            messageBarInput.placeholder = use_qt ?
+                i18nStringData["Note: an interaction will create a new contact."] :
                 i18n.gettext("Note: an interaction will create a new contact.")
         } else {
             addToConvButton.style.display = ""
@@ -343,10 +353,11 @@ function showInvitation(contactAlias, contactId) {
             }
         }
         invitationText.innerHTML = "<b>"
-            + contactAlias + use_qt ? i18nStringData["isNotInYourContacts"] :
+            + contactAlias + use_qt ? i18nStringData["is not in your contacts"] :
             i18n.sprintf(i18n.gettext("%s is not in your contacts"), contactAlias)
             + "</b><br/>"
-            + use_qt ? i18nStringData["automaticallyAcceptInvitation"] :
+            + use_qt ?
+            i18nStringData["Note: you can automatically accept this invitation by sending a message."] :
             i18n.gettext("Note: you can automatically accept this invitation by sending a message.")
         hasInvitation = true
         invitation.style.visibility = "visible"
@@ -552,20 +563,20 @@ function formatDate(date) {
         if (interval > 5)
             return date.toLocaleDateString()
         if (interval > 1)
-            return "\u200E " + i18nStringData["daysAgo"].format(interval)
+            return "\u200E " + i18nStringData["%d days ago"].format(interval)
         if (interval === 1)
-            return "\u200E " + i18nStringData["oneDayAgo"]
+            return "\u200E " + i18nStringData["one day ago"]
 
         interval = Math.floor(seconds / 3600)
         if (interval > 1)
-            return "\u200E " + i18nStringData["hoursAgo"].format(interval)
+            return "\u200E " + i18nStringData["%d hours ago"].format(interval)
         if (interval === 1)
-            return "\u200E " + i18nStringData["oneHourAgo"]
+            return "\u200E " + i18nStringData["one hour ago"]
 
         interval = Math.floor(seconds / 60)
         if (interval > 1)
-            return "\u200E " + i18nStringData["minutesAgo"].format(interval)
-        return i18nStringData["justNow"]
+            return "\u200E " + i18nStringData["%d minutes ago"].format(interval)
+        return i18nStringData["just now"]
     } else {
         if (interval > 5) {
             return date.toLocaleDateString()
@@ -759,7 +770,7 @@ function getMessageDeliveryStatusText(message_delivery_status) {
     var formatted_delivery_status = message_delivery_status
 
     if (message_delivery_status === "failure") {
-        formatted_delivery_status = use_qt ? i18nStringData["failureString"] :  i18n.gettext("Failure") +
+        formatted_delivery_status = use_qt ? i18nStringData["Failure"] :  i18n.gettext("Failure") +
         "<svg overflow='visible' viewBox='0 -2 16 14' height='16px' width='16px'><path class='status-x x-first' stroke='#AA0000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' fill='none' d='M4,4 L12,12'/><path class='status-x x-second' stroke='#AA0000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' fill='none' d='M12,4 L4,12'/></svg>"
     } else {
         formatted_delivery_status = ""
@@ -1168,7 +1179,7 @@ function updateFileInteraction(message_div, message_object, forceTypeToFile = fa
             // add buttons to accept or refuse a call.
             var accept_button = document.createElement("div")
             accept_button.innerHTML = acceptSvg
-            accept_button.setAttribute("title", use_qt ? i18nStringData["acceptString"] :
+            accept_button.setAttribute("title", use_qt ? i18nStringData["Accept"] :
                 i18n.gettext("Accept"))
             accept_button.setAttribute("class", "flat-button accept")
             accept_button.onclick = function () {
@@ -1183,7 +1194,7 @@ function updateFileInteraction(message_div, message_object, forceTypeToFile = fa
 
         var refuse_button = document.createElement("div")
         refuse_button.innerHTML = refuseSvg
-        refuse_button.setAttribute("title", use_qt ? i18nStringData["refuseString"] :
+        refuse_button.setAttribute("title", use_qt ? i18nStringData["Refuse"] :
             i18n.gettext("Refuse"))
         refuse_button.setAttribute("class", "flat-button refuse")
         refuse_button.onclick = function () {
@@ -1560,7 +1571,7 @@ function buildMessageDropdown(message_id) {
 
     const remove = document.createElement("div")
     remove.setAttribute("class", "menuoption")
-    remove.innerHTML = use_qt ? i18nStringData["deleteString"] : i18n.gettext("Delete")
+    remove.innerHTML = use_qt ? i18nStringData["Delete"] : i18n.gettext("Delete")
     remove.msg_id = message_id
     remove.onclick = function () {
         if (use_qt) {
@@ -1801,7 +1812,7 @@ function addOrUpdateMessage(message_object, new_message, insert_after = true, me
         if (!dropdown.querySelector(".retry")) {
             const retry = document.createElement("div")
             retry.setAttribute("class", "retry")
-            retry.innerHTML = use_qt ? i18nStringData["retryString"] : i18n.gettext("Retry")
+            retry.innerHTML = use_qt ? i18nStringData["Retry"] : i18n.gettext("Retry")
             retry.msg_id = message_id
             retry.onclick = function () {
                 if (use_qt) {