Bug 1550488 - Change the OTR localization to use Fluent. r=mkmelin
authorKai Engert <kaie@kuix.de>
Mon, 20 May 2019 11:33:11 +0200
changeset 32965 ae087d73b3ce70604c356f6043cc8998243bf87f
parent 32964 38204c5426e09d5ac3f6b431cb19d38538485ccb
child 32966 551822c41355acc5402e2bc242c2903b37810844
push idunknown
push userunknown
push dateunknown
reviewersmkmelin
bugs1550488
Bug 1550488 - Change the OTR localization to use Fluent. r=mkmelin
chat/content/jar.mn
chat/content/otr-add-finger.dtd
chat/content/otr-add-finger.properties
chat/content/otr-add-fingerprint.js
chat/content/otr-add-fingerprint.xul
chat/content/otr-auth.dtd
chat/content/otr-auth.js
chat/content/otr-auth.properties
chat/content/otr-auth.xul
chat/content/otr-chat.dtd
chat/content/otr-generate-key.dtd
chat/content/otr-generate-key.js
chat/content/otr-generate-key.properties
chat/content/otr-generate-key.xul
chat/content/otr.properties
chat/content/otr/add-finger.ftl
chat/content/otr/auth.ftl
chat/content/otr/chat.ftl
chat/content/otr/generate-key.ftl
chat/content/otr/otr.ftl
chat/content/otr/otrUI.ftl
chat/content/otrUI.properties
chat/modules/OTR.jsm
chat/modules/OTRUI.jsm
mail/components/im/content/chat-conversation-info.js
mail/components/im/content/chat-messenger.js
mail/locales/jar.mn
--- a/chat/content/jar.mn
+++ b/chat/content/jar.mn
@@ -14,17 +14,8 @@ chat.jar:
 	content/chat/conv.html
 	content/chat/otr-add-fingerprint.js
 	content/chat/otr-add-fingerprint.xul
 	content/chat/otr-auth.js
 	content/chat/otr-auth.xul
 	content/chat/otr-generate-key.js
 	content/chat/otr-generate-key.xul
 	content/chat/otrWorker.js
-	content/chat/otr-auth.dtd
-	content/chat/otr-auth.properties
-	content/chat/otr-add-finger.dtd
-	content/chat/otr-add-finger.properties
-	content/chat/otr.properties
-	content/chat/otr-generate-key.dtd
-	content/chat/otr-generate-key.properties
-	content/chat/otrUI.properties
-	content/chat/otr-chat.dtd
deleted file mode 100644
--- a/chat/content/otr-add-finger.dtd
+++ /dev/null
@@ -1,9 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!ENTITY addFingerDialog.title "Add Fingerprint">
-<!ENTITY addFingerDialog.finger "Fingerprint">
-<!ENTITY addFingerDialog.cancel "Skip">
-<!-- LOCALIZATION NOTE (addFingerDialog.tooltip): do not translate OTR which is the name of an encryption protocol -->
-<!ENTITY addFingerDialog.tooltip "If you know the 40 hex char fingerprint of your contact's OTR private key, enter it now.">
deleted file mode 100644
--- a/chat/content/otr-add-finger.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# LOCALIZATION NOTE (addfinger.title): %S is the name of a chat contact person
-addfinger.title=Enter the fingerprint of the OTR key used by %S
--- a/chat/content/otr-add-fingerprint.js
+++ b/chat/content/otr-add-fingerprint.js
@@ -3,26 +3,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const {
   XPCOMUtils,
   l10nHelper,
 } = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
 const {OTR} = ChromeUtils.import("resource:///modules/OTR.jsm");
 
-XPCOMUtils.defineLazyGetter(this, "_", () =>
-  l10nHelper("chrome://chat/content/otr-add-finger.properties")
-);
-
 var args = window.arguments[0].wrappedJSObject;
 
 var otrAddFinger = {
-  onload() {
-    document.title = _("addfinger.title", args.screenname);
-
+  async onload() {
+    let title = await document.l10n.formatValue(
+      "otr-add-finger-name-title", {name: args.screenname});
+    document.title = title;
     document.addEventListener("dialogaccept", () => {
       return this.add();
     });
   },
 
   oninput(e) {
     e.value = e.value.replace(/[^0-9a-fA-F]/gi, "");
     document.documentElement.getButton("accept").disabled = (e.value.length != 40);
--- a/chat/content/otr-add-fingerprint.xul
+++ b/chat/content/otr-add-fingerprint.xul
@@ -1,27 +1,32 @@
 <?xml version="1.0" ?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css" ?>
 
-<!DOCTYPE window SYSTEM "chrome://chat/content/otr-add-finger.dtd">
+<!DOCTYPE dialog>
 
 <dialog id="otrAddFingerDialog"
+        data-l10n-id="otr-add-finger"
+        data-l10n-attrs="buttonlabelcancel"
         windowtype="OTR:AddFinger"
         onload="otrAddFinger.onload()"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        title="&addFingerDialog.title;"
+        xmlns:html="http://www.w3.org/1999/xhtml"
         buttons="accept,cancel"
-        buttonlabelcancel="&addFingerDialog.cancel;"
         buttondisabledaccept="true">
 
+  <linkset>
+    <html:link rel="localization" href="messenger/otr/add-finger.ftl"/>
+  </linkset>
+
   <script type="application/javascript" src="chrome://chat/content/otr-add-fingerprint.js"/>
   <vbox flex="1">
-    <label value="&addFingerDialog.tooltip;" control="name" flex="1"/>
+    <label data-l10n-id="otr-add-finger-tooltip" control="name" flex="1"/>
     <hbox id="fingerBox" align="baseline" flex="1">
-      <label value="&addFingerDialog.finger;" control="name"/>
+      <label data-l10n-id="otr-add-finger-fingerprint" control="name"/>
       <textbox id="finger" oninput="otrAddFinger.oninput(this)" flex="1"/>
     </hbox>
   </vbox>
 </dialog>
deleted file mode 100644
--- a/chat/content/otr-auth.dtd
+++ /dev/null
@@ -1,20 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!ENTITY authDialog.title "Verify contact's identity">
-<!ENTITY authDialog.authenticate "Verify">
-<!ENTITY authDialog.help "Help">
-<!ENTITY authDialog.yes "Yes">
-<!ENTITY authDialog.no "No">
-<!ENTITY authDialog.verified "I have verified that this is in fact the correct fingerprint.">
-<!ENTITY authDialog.manualVerification "Manual fingerprint verification">
-<!ENTITY authDialog.questionAndAnswer "Question and answer">
-<!ENTITY authDialog.sharedSecret "Shared secret">
-<!ENTITY authDialog.manualInstruction "To verify the fingerprint, contact your intended chat partner via some other authenticated channel, such as the telephone or GPG-signed email. Each of you should tell your fingerprint to the other. If everything matches up, you should indicate in the dialog below that you have verified the fingerprint.">
-<!ENTITY authDialog.how "How would you like to verify your contact's identity?">
-<!ENTITY authDialog.qaInstruction "To verify their identity, pick a question whose answer is known only to you and your contact. Enter this question and answer, then wait for your contact to enter the answer as well. If the answers do not match, then you may be talking to an imposter.">
-<!ENTITY authDialog.secretInstruction "To verify their identity, pick a secret known only to you and your contact. Enter this secret, then wait for your contact to enter it as well. If the secrets do not match, then you may be talking to an imposter.">
-<!ENTITY authDialog.question "Enter question here:">
-<!ENTITY authDialog.answer "Enter secret answer here (case sensitive):">
-<!ENTITY authDialog.secret "Enter secret here:">
--- a/chat/content/otr-auth.js
+++ b/chat/content/otr-auth.js
@@ -4,32 +4,18 @@
 
 const {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
 const {
   XPCOMUtils,
   l10nHelper,
 } = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
 const {OTR} = ChromeUtils.import("resource:///modules/OTR.jsm");
 
-XPCOMUtils.defineLazyGetter(this, "_", () =>
-  l10nHelper("chrome://chat/content/otr-auth.properties")
-);
-
 var [mode, uiConv, contactInfo] = window.arguments;
 
-// This window implements the interactive authentication of a buddy's
-// key. At open time, we're given several parameters, and the "mode"
-// parameter tells us, from where we've been called.
-// mode == "pref" means that we have been opened from the preferences,
-// and it means we cannot rely on the other user being online, and
-// we there might be no uiConv active currently, so we fall back.
-
-document.title = _("auth.title",
-  (mode === "pref") ? contactInfo.screenname : uiConv.normalizedName);
-
 function showSection(selected, hideMenu) {
   document.getElementById("how").hidden = !!hideMenu;
   [ "questionAndAnswer",
     "sharedSecret",
     "manualVerification",
     "ask",
   ].forEach(function(key) {
     document.getElementById(key).hidden = (key !== selected);
@@ -43,36 +29,55 @@ function startSMP(context, answer, quest
 }
 
 function manualVerification(fingerprint, context) {
   let opts = document.getElementById("verifiedOption");
   let trust = (opts.selectedItem.value === "yes");
   OTR.setTrust(fingerprint, trust, context);
 }
 
-function populateFingers(context, theirs, trust) {
-  let fingers = document.getElementById("fingerprints");
+async function populateFingers(context, theirs, trust) {
   let yours = OTR.privateKeyFingerprint(context.account, context.protocol);
   if (!yours)
     throw new Error("Fingerprint should already be generated.");
-  fingers.value =
-    _("auth.yourFingerprint", context.account, yours) + "\n\n" +
-    _("auth.theirFingerprint", context.username, theirs);
+
+  document.getElementById("yourFPLabel").value =
+    await document.l10n.formatValue("auth-your-fp-value", {own_name: context.account});
+
+  document.getElementById("theirFPLabel").value =
+    await document.l10n.formatValue("auth-their-fp-value", {their_name: context.username});
+
+  document.getElementById("yourFPValue").value = yours;
+  document.getElementById("theirFPValue").value = theirs;
+
   let opts = document.getElementById("verifiedOption");
   let verified = trust ? "yes" : "no";
   for (let item of opts.menupopup.childNodes) {
     if (verified === item.value) {
       opts.selectedItem = item;
       break;
     }
   }
 }
 
 var otrAuth = {
-  onload() {
+  async onload() {
+    // This window implements the interactive authentication of a buddy's
+    // key. At open time, we're given several parameters, and the "mode"
+    // parameter tells us from where we've been called.
+    // mode == "pref" means that we have been opened from the preferences,
+    // and it means we cannot rely on the other user being online, and
+    // we there might be no uiConv active currently, so we fall back.
+
+    let nameSource =
+      (mode === "pref") ? contactInfo.screenname : uiConv.normalizedName;
+    let title = await document.l10n.formatValue(
+      "auth-title", {name: nameSource});
+    document.title = title;
+
     document.addEventListener("dialogaccept", () => {
       return this.accept();
     });
 
     document.addEventListener("dialogcancel", () => {
       return this.cancel();
     });
 
@@ -95,46 +100,61 @@ var otrAuth = {
           contactInfo.screenname
         );
         theirs = contactInfo.fingerprint;
         populateFingers(context, theirs, contactInfo.trust);
         showSection("manualVerification", true);
         this.oninput({ value: true });
         break;
       case "ask":
-        document.getElementById("askLabel").textContent = contactInfo.question ?
-          _("auth.question", contactInfo.question)
-          : _("auth.secret");
+        let receivedQuestionLabel =
+          document.getElementById("receivedQuestionLabel");
+        let receivedQuestionDisplay =
+          document.getElementById("receivedQuestion");
+        let responseLabel =
+          document.getElementById("responseLabel");
+        if (contactInfo.question) {
+          receivedQuestionLabel.hidden = false;
+          receivedQuestionDisplay.hidden = false;
+          receivedQuestionDisplay.value = contactInfo.question;
+          responseLabel.value =
+            await document.l10n.formatValue("auth-answer");
+        } else {
+          receivedQuestionLabel.hidden = true;
+          receivedQuestionDisplay.hidden = true;
+          responseLabel.value =
+            await document.l10n.formatValue("auth-secret");
+        }
         showSection("ask", true);
         break;
     }
   },
 
   accept() {
     // uiConv may not be present in pref mode
     let context = uiConv ? OTR.getContext(uiConv.target) : null;
     if (mode === "pref") {
       manualVerification(contactInfo.fpointer, context);
     } else if (mode === "start") {
       let how = document.getElementById("howOption");
       switch (how.selectedItem.value) {
-      case "questionAndAnswer":
-        let question = document.getElementById("question").value;
-        let answer = document.getElementById("answer").value;
-        startSMP(context, answer, question);
-        break;
-      case "sharedSecret":
-        let secret = document.getElementById("secret").value;
-        startSMP(context, secret);
-        break;
-      case "manualVerification":
-        manualVerification(context.fingerprint, context);
-        break;
-      default:
-        throw new Error("Unreachable!");
+        case "questionAndAnswer":
+          let question = document.getElementById("question").value;
+          let answer = document.getElementById("answer").value;
+          startSMP(context, answer, question);
+          break;
+        case "sharedSecret":
+          let secret = document.getElementById("secret").value;
+          startSMP(context, secret);
+          break;
+        case "manualVerification":
+          manualVerification(context.fingerprint, context);
+          break;
+        default:
+          throw new Error("Unreachable!");
       }
     } else if (mode === "ask") {
       let response = document.getElementById("response").value;
       OTR.sendResponse(context, response);
       OTR.authUpdate(context, contactInfo.progress);
     } else {
       throw new Error("Unreachable!");
     }
@@ -163,13 +183,14 @@ var otrAuth = {
       break;
     case "manualVerification":
       this.oninput({ value: true });
       break;
     }
     showSection(how);
   },
 
-  help() {
-    Services.prompt.alert(window, _("auth.helpTitle"), _("auth.help"));
+  async help() {
+    let helpTitle = await document.l10n.formatValue("auth-helpTitle");
+    let helpText = await document.l10n.formatValue("auth-help");
+    Services.prompt.alert(window, helpTitle, helpText);
   },
-
 };
deleted file mode 100644
--- a/chat/content/otr-auth.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# LOCALIZATION NOTE (auth.title): %S is the screen name of a chat contact person
-auth.title=Verify the identity of %S
-# LOCALIZATION NOTE (auth.yourFingerprint): 1st %S is the user's own screen name. 2nd %S is the fingerprint (a checksum) of the user's own encryption key.
-auth.yourFingerprint=Fingerprint for you, %S:\n%S
-# LOCALIZATION NOTE (auth.theirFingerprint): 1st %S is the screen name of a chat contact. 2nd %S is the fingerprint (a checksum) of the chat contact's encryption key.
-auth.theirFingerprint=Purported fingerprint for %S:\n%S
-auth.help=Verifying a contact's identity helps ensure that the person you are talking to is who they claim to be.
-auth.helpTitle=Verification help
-# LOCALIZATION NOTE (auth.question): %S is a question (any text is possible) that was received from a chat contact
-auth.question=This is the question asked by your contact:\n\n%S\n\nEnter secret answer here (case sensitive):
-auth.secret=Enter secret here:
--- a/chat/content/otr-auth.xul
+++ b/chat/content/otr-auth.xul
@@ -1,71 +1,81 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css" ?>
 
-<!DOCTYPE window SYSTEM "chrome://chat/content/otr-auth.dtd">
+<!DOCTYPE dialog>
 
 <dialog
   xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:html="http://www.w3.org/1999/xhtml"
   id="otrAuthDialog"
+  data-l10n-id="otr-auth"
+  data-l10n-attrs="buttonlabelaccept"
   windowtype="OTR:Auth"
-  title="&authDialog.title;"
   onload="otrAuth.onload()"
   buttons="accept,cancel,help"
-  buttonlabelaccept="&authDialog.authenticate;"
-  buttonlabelhelp="&authDialog.help;"
   buttondisabledaccept="true">
 
+  <linkset>
+    <html:link rel="localization" href="messenger/otr/auth.ftl"/>
+  </linkset>
+
   <script type="application/javascript" src="chrome://chat/content/otr-auth.js" />
 
   <groupbox id="how" hidden="true">
-    <caption label="&authDialog.how;"/>
+    <label><html:h4 data-l10n-id="auth-how"/></label>
     <menulist id="howOption" oncommand="otrAuth.how();">
       <menupopup>
-        <menuitem label="&authDialog.questionAndAnswer;" value="questionAndAnswer" />
-        <menuitem label="&authDialog.sharedSecret;" value="sharedSecret" />
-        <menuitem label="&authDialog.manualVerification;" value="manualVerification" />
+        <menuitem data-l10n-id="auth-questionAndAnswer-label" value="questionAndAnswer" />
+        <menuitem data-l10n-id="auth-sharedSecret-label" value="sharedSecret" />
+        <menuitem data-l10n-id="auth-manualVerification-label" value="manualVerification" />
       </menupopup>
     </menulist>
   </groupbox>
 
   <groupbox id="questionAndAnswer" hidden="true">
-    <caption label="&authDialog.questionAndAnswer;" />
-    <description style="width: 300px; white-space: pre-wrap;">&authDialog.qaInstruction;</description>
-    <label value="&authDialog.question;" control="question" flex="1" />
+    <label><html:h4 data-l10n-id="auth-questionAndAnswer" /></label>
+    <description style="width: 300px; white-space: pre-wrap;" data-l10n-id="auth-qaInstruction"></description>
+    <label data-l10n-id="auth-question" control="question" flex="1" />
     <textbox id="question" />
-    <label value="&authDialog.answer;" control="answer" flex="1" />
+    <label data-l10n-id="auth-answer" control="answer" flex="1" />
     <textbox id="answer" oninput="otrAuth.oninput(this)" />
   </groupbox>
 
   <groupbox id="sharedSecret" hidden="true">
-    <caption label="&authDialog.sharedSecret;" />
-    <description style="width: 300px; white-space: pre-wrap;">&authDialog.secretInstruction;</description>
-    <label value="&authDialog.secret;" control="secret" flex="1" />
+    <label><html:h4 data-l10n-id="auth-sharedSecret" /></label>
+    <description style="width: 300px; white-space: pre-wrap;" data-l10n-id="auth-secretInstruction"></description>
+    <label data-l10n-id="auth-secret" control="secret" flex="1" />
     <textbox id="secret" oninput="otrAuth.oninput(this)" />
   </groupbox>
 
   <groupbox id="manualVerification" hidden="true">
-    <caption label="&authDialog.manualVerification;" />
-    <description style="width: 300px; white-space: pre-wrap;">&authDialog.manualInstruction;</description>
-    <html:textarea id="fingerprints" rows="5" readonly="true" />
+    <label><html:h4 data-l10n-id="auth-manualVerification" /></label>
+    <description style="width: 300px; white-space: pre-wrap;" data-l10n-id="auth-manualInstruction"></description>
+
+    <label id="yourFPLabel" />
+    <html:textarea id="yourFPValue" rows="1" readonly="true" />
+    <label id="theirFPLabel" />
+    <html:textarea id="theirFPValue" rows="1" readonly="true" />
+
     <hbox align="center">
-      <label value="&authDialog.verified;" />
+      <label data-l10n-id="auth-verified" />
       <menulist id="verifiedOption">
         <menupopup>
-          <menuitem label="&authDialog.yes;" value="yes" />
-          <menuitem label="&authDialog.no;" value="no" />
+          <menuitem data-l10n-id="auth-yes" value="yes" />
+          <menuitem data-l10n-id="auth-no" value="no" />
         </menupopup>
       </menulist>
     </hbox>
   </groupbox>
 
   <groupbox id="ask" hidden="true">
-    <description id="askLabel" style="width: 300px; white-space: pre-wrap;" />
+    <label id="receivedQuestionLabel" data-l10n-id="auth-questionReceived" />
+    <description id="receivedQuestion" style="width: 300px; white-space: pre-wrap;" />
+    <label id="responseLabel" control="response" flex="1" />
     <textbox id="response" oninput="otrAuth.oninput(this)" />
   </groupbox>
 
 </dialog>
deleted file mode 100644
--- a/chat/content/otr-chat.dtd
+++ /dev/null
@@ -1,8 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!ENTITY state.label        "Encryption Status:">
-<!ENTITY start.label        "Start private conversation">
-<!ENTITY end.label          "End private conversation">
-<!ENTITY auth.label         "Verify your contact's identity">
deleted file mode 100644
--- a/chat/content/otr-generate-key.dtd
+++ /dev/null
@@ -1,6 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!ENTITY privDialog.title "Generating private key">
-<!ENTITY privDialog.done "Done">
--- a/chat/content/otr-generate-key.js
+++ b/chat/content/otr-generate-key.js
@@ -3,27 +3,29 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const {
   XPCOMUtils,
   l10nHelper,
 } = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
 const {OTR} = ChromeUtils.import("resource:///modules/OTR.jsm");
 
-XPCOMUtils.defineLazyGetter(this, "_", () =>
-  l10nHelper("chrome://chat/content/otr-generate-key.properties")
-);
-
 var otrPriv = {
 
-  onload() {
+  async onload() {
     let args = window.arguments[0].wrappedJSObject;
     let priv = document.getElementById("priv");
-    priv.textContent = _("priv.account", args.account, OTR.protocolName(args.protocol));
+
+    let text = await document.l10n.formatValue(
+      "otr-genkey-account", {name: args.account, protocol: OTR.protocolName(args.protocol)});
+    priv.textContent = text;
+console.log("genkey: " + text);
+
     OTR.generatePrivateKey(args.account, args.protocol).then(function() {
       document.documentElement.getButton("accept").disabled = false;
       document.documentElement.acceptDialog();
-    }).catch(function(err) {
+    }).catch(async function(err) {
+      priv.textContent = await document.l10n.formatValue(
+          "otr-genkey-failed", {error: String(err)});
       document.documentElement.getButton("accept").disabled = false;
-      priv.textContent = _("priv.failed", String(err));
     });
   },
 };
deleted file mode 100644
--- a/chat/content/otr-generate-key.properties
+++ /dev/null
@@ -1,8 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# LOCALIZATION NOTE (priv.account): 1st %S is the name of the user's own chat account. 2nd %S is the chat communication protocol used by that account.
-priv.account=Generating private key for %S (%S) …
-# LOCALIZATION NOTE (priv.failed): %S contains an error message that describes the cause of the failure
-priv.failed=Generating key failed: %S
--- a/chat/content/otr-generate-key.xul
+++ b/chat/content/otr-generate-key.xul
@@ -1,26 +1,28 @@
 <?xml version="1.0" ?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css" ?>
 
-<!DOCTYPE dialog [
-  <!ENTITY % chatPrivDTD SYSTEM "chrome://chat/content/otr-generate-key.dtd">
-  %chatPrivDTD;
-]>
+<!DOCTYPE dialog>
 
 <dialog
   xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+  xmlns:html="http://www.w3.org/1999/xhtml"
   id="otrPrivDialog"
+  data-l10n-id="otr-generate-key"
+  data-l10n-attrs="buttonlabelaccept"
   windowtype="OTR:Priv"
-  title="&privDialog.title;"
   buttons="accept"
-  buttonlabelaccept="&privDialog.done;"
   onload="otrPriv.onload()"
   buttondisabledaccept="true">
 
+  <linkset>
+    <html:link rel="localization" href="messenger/otr/generate-key.ftl"/>
+  </linkset>
+
   <script type="application/javascript" src="chrome://chat/content/otr-generate-key.js" />
   <description id="priv" style="width: 300px; white-space: pre-wrap;"></description>
 
 </dialog>
deleted file mode 100644
--- a/chat/content/otr.properties
+++ /dev/null
@@ -1,72 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# LOCALIZATION NOTE (msgevent.encryption_required_part1): %S is the name of a chat contact
-msgevent.encryption_required_part1=You attempted to send an unencrypted message to %S. As a policy, unencrypted messages are not allowed.
-msgevent.encryption_required_part2=Attempting to start a private conversation. Your message will be retransmitted when the private conversation starts.
-msgevent.encryption_error=An error occurred when encrypting your message. The message was not sent.
-
-# LOCALIZATION NOTE (msgevent.connection_ended): %S is the name of a chat contact
-msgevent.connection_ended=%S has already closed their private connection to you. Your message was not sent. Either end your private conversation, or restart it.
-
-# LOCALIZATION NOTE (msgevent.setup_error): %S is the name of a chat contact
-msgevent.setup_error=An error occured while setting up a private conversation with %S.
-# LOCALIZATION NOTE (msgevent.msg_reflected): do not translate OTR which is the name of an encryption protocol
-msgevent.msg_reflected=You are receiving your own OTR messages. You are either trying to talk to yourself, or someone is reflecting your messages back at you.
-
-# LOCALIZATION NOTE (msgevent.msg_resent): %S is the name of a chat contact
-msgevent.msg_resent=The last message to %S was resent.
-
-# LOCALIZATION NOTE (msgevent.rcvdmsg_not_private): %S is the name of a chat contact
-msgevent.rcvdmsg_not_private=The encrypted message received from %S is unreadable, as you are not currently communicating privately.
-
-# LOCALIZATION NOTE (msgevent.rcvdmsg_unreadable): %S is the name of a chat contact
-msgevent.rcvdmsg_unreadable=We received an unreadable encrypted message from %S.
-
-# LOCALIZATION NOTE (msgevent.rcvdmsg_malformed): %S is the name of a chat contact
-msgevent.rcvdmsg_malformed=We received a malformed data message from %S.
-
-# LOCALIZATION NOTE (msgevent.log_heartbeat_rcvd): %S is the name of a chat contact. A Heartbeat is a technical message used to keep a connection alive.
-msgevent.log_heartbeat_rcvd=Heartbeat received from %S.
-
-# LOCALIZATION NOTE (msgevent.log_heartbeat_sent): %S is the name of a chat contact. A Heartbeat is a technical message used to keep a connection alive.
-msgevent.log_heartbeat_sent=Heartbeat sent to %S.
-
-# LOCALIZATION NOTE (msgevent.rcvdmsg_general_err): do not translate OTR which is the name of an encryption protocol
-msgevent.rcvdmsg_general_err=An OTR error occured.
-
-# LOCALIZATION NOTE (msgevent.rcvdmsg_unencrypted): 1st %S is the name of a chat contact. 2nd %S is the message that was received.
-msgevent.rcvdmsg_unencrypted=The following message received from %S was not encrypted: %S
-
-# LOCALIZATION NOTE (msgevent.rcvdmsg_unrecognized): do not translate OTR which is the name of an encryption protocol. %S is the name of a chat contact.
-msgevent.rcvdmsg_unrecognized=We received an unrecognized OTR message from %S.
-
-# LOCALIZATION NOTE (msgevent.rcvdmsg_for_other_instance): %S is the name of a chat contact
-msgevent.rcvdmsg_for_other_instance=%S has sent a message intended for a different session. If you are logged in multiple times, another session may have received the message.
-
-# LOCALIZATION NOTE (context.gone_secure_private): %S is the name of a chat contact
-context.gone_secure_private=Private conversation with %S started.
-
-# LOCALIZATION NOTE (context.gone_secure_unverified): %S is the name of a chat contact
-context.gone_secure_unverified=Private conversation with %S started. However, their identity has not been verified.
-
-# LOCALIZATION NOTE (context.still_secure): %S is the name of a chat contact
-context.still_secure=Successfully refreshed the private conversation with %S.
-
-error.enc=Error occurred encrypting message.
-
-# LOCALIZATION NOTE (error.not_priv): %S is the name of a chat contact
-error.not_priv=You sent encrypted data to %S, who wasn't expecting it.
-error.unreadable=You transmitted an unreadable encrypted message.
-error.malformed=You transmitted a malformed data message.
-resent=[resent]
-# LOCALIZATION NOTE (tlv.disconnected): %S is the name of a chat contact
-tlv.disconnected=%S has ended their private conversation with you; you should do the same.
-# LOCALIZATION NOTE (query.msg): %S is the name of a chat contact. Do not translate "Off-the-Record" and "OTR" which is the name of an encryption protocol
-query.msg=%S has requested an Off-the-Record (OTR) private conversation. However, you do not have a plugin to support that. See https://en.wikipedia.org/wiki/Off-the-Record_Messaging for more information.
-trust.unused=Unused
-trust.not_private=Not Private
-trust.unverified=Unverified
-trust.private=Private
-trust.finished=Finished
new file mode 100644
--- /dev/null
+++ b/chat/content/otr/add-finger.ftl
@@ -0,0 +1,16 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+otr-add-finger =
+    .title = Add Fingerprint
+    .buttonlabelcancel = Skip
+
+# Variables:
+#   $name (String) - name of a chat contact person
+otr-add-finger-name-title = Enter the fingerprint of the OTR key used by { $name }
+
+otr-add-finger-fingerprint = Fingerprint
+
+# Do not translate 'OTR' (name of an encryption protocol)
+otr-add-finger-tooltip = If you know the 40 character long HEX fingerprint of your contact's OTR private key, enter it now.
new file mode 100644
--- /dev/null
+++ b/chat/content/otr/auth.ftl
@@ -0,0 +1,59 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+otr-auth =
+    .title = Verify contact's identity
+    .buttonlabelaccept = Verify
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+auth-title = Verify the identity of { $name }
+
+# Variables:
+#   $own_name (String) - the user's own screen name
+auth-your-fp-value = Fingerprint for you, { $own_name }:
+
+# Variables:
+#   $their_name (String) - the screen name of a chat contact
+auth-their-fp-value = Purported fingerprint for { $their_name }:
+
+auth-help = Verifying a contact's identity helps ensure that the person you are talking to is who they claim to be.
+auth-helpTitle = Verification help
+
+auth-questionReceived = This is the question asked by your contact:
+
+auth-yes =
+    .label = Yes
+
+auth-no =
+    .label = No
+
+auth-verified = I have verified that this is in fact the correct fingerprint.
+
+auth-manualVerification = Manual fingerprint verification
+auth-questionAndAnswer = Question and answer
+auth-sharedSecret = Shared secret
+
+auth-manualVerification-label =
+    .label = { auth-manualVerification }
+
+auth-questionAndAnswer-label =
+    .label = { auth-questionAndAnswer }
+
+auth-sharedSecret-label =
+    .label = { auth-sharedSecret }
+
+auth-manualInstruction = To verify the fingerprint, contact your conversation partner via some other authenticated channel, such as the telephone or GPG-signed email. Both conversation partners should tell the other person their fingerprint. If the fingerprint matches, you should indicate in the dialog below that you have verified the fingerprint.
+
+auth-how = How would you like to verify your contact's identity?
+
+auth-qaInstruction = To verify their identity, pick a question whose answer is known only to you and your contact. Enter this question and answer, then wait for your contact to enter the answer as well. If the answers do not match, then you may be talking to an imposter.
+
+auth-secretInstruction = To verify their identity, pick a secret known only to you and your contact. Enter this secret, then wait for your contact to enter it as well. If the secrets do not match, then you may be talking to an imposter.
+
+auth-question = Enter question here:
+
+auth-answer = Enter secret answer here (case sensitive):
+
+auth-secret = Enter secret here:
new file mode 100644
--- /dev/null
+++ b/chat/content/otr/chat.ftl
@@ -0,0 +1,19 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+state-label = Encryption Status:
+
+start-text = Start private conversation
+
+start-label =
+    .label = { start-text }
+
+start-tooltip =
+    .tooltiptext = { start-text }
+
+end-label =
+    .label = End private conversation
+
+auth-label =
+    .label = Verify your contact's identity
new file mode 100644
--- /dev/null
+++ b/chat/content/otr/generate-key.ftl
@@ -0,0 +1,16 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+otr-generate-key =
+    .title = Generating private key
+    .buttonlabelaccept = Done
+
+# Variables:
+#   $name (String) - the name of the user's own chat account
+#   $protocol (String) - the chat communication protocol used by that account
+otr-genkey-account = Generating private key for { $name }({ $protocol }) …
+
+# Variables:
+#   $error (String) - contains an error message that describes the cause of the failure
+otr-genkey-failed = Generating key failed: { $error }
new file mode 100644
--- /dev/null
+++ b/chat/content/otr/otr.ftl
@@ -0,0 +1,102 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+msgevent-encryption_required_part1 = You attempted to send an unencrypted message to { $name }. As a policy, unencrypted messages are not allowed.
+
+msgevent-encryption_required_part2 = Attempting to start a private conversation. Your message will be retransmitted when the private conversation starts.
+msgevent-encryption_error = An error occurred when encrypting your message. The message was not sent.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+msgevent-connection_ended = { $name } has already closed their private connection to you. Your message was not sent. Either end your private conversation, or restart it.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+msgevent-setup_error = An error occured while setting up a private conversation with { $name }.
+
+# Do not translate 'OTR' (name of an encryption protocol)
+msgevent-msg_reflected = You are receiving your own OTR messages. You are either trying to talk to yourself, or someone is reflecting your messages back at you.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+msgevent-msg_resent = The last message to { $name } was resent.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+msgevent-rcvdmsg_not_private = The encrypted message received from { $name } is unreadable, as you are not currently communicating privately.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+msgevent-rcvdmsg_unreadable = We received an unreadable encrypted message from { $name }.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+msgevent-rcvdmsg_malformed = We received a malformed data message from { $name }.
+
+# A Heartbeat is a technical message used to keep a connection alive.
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+msgevent-log_heartbeat_rcvd = Heartbeat received from { $name }.
+
+# A Heartbeat is a technical message used to keep a connection alive.
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+msgevent-log_heartbeat_sent = Heartbeat sent to { $name }.
+
+# Do not translate 'OTR' (name of an encryption protocol)
+msgevent-rcvdmsg_general_err = An OTR error occured.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+#   $msg (string) - the message that was received.
+msgevent-rcvdmsg_unencrypted = The following message received from { $name } was not encrypted: { $msg }
+
+# Do not translate 'OTR' (name of an encryption protocol)
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+msgevent-rcvdmsg_unrecognized = We received an unrecognized OTR message from { $name }.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+msgevent-rcvdmsg_for_other_instance = { $name } has sent a message intended for a different session. If you are logged in multiple times, another session may have received the message.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+context-gone_secure_private = Private conversation with { $name } started.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+context-gone_secure_unverified = Private conversation with { $name } started. However, their identity has not been verified.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+context-still_secure = Successfully refreshed the private conversation with { $name }.
+
+error-enc = Error occurred encrypting message.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+error-not_priv = You sent encrypted data to { $name }, who wasn't expecting it.
+
+error-unreadable = You transmitted an unreadable encrypted message.
+error-malformed = You transmitted a malformed data message.
+
+resent = [resent]
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+tlv-disconnected = { $name } has ended their private conversation with you; you should do the same.
+
+# Do not translate "Off-the-Record" and "OTR" which is the name of an encryption protocol
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+query-msg = { $name } has requested an Off-the-Record (OTR) private conversation. However, you do not have a plugin to support that. See https://en.wikipedia.org/wiki/Off-the-Record_Messaging for more information.
+
+trust-unused = Unused
+trust-not_private = Not Private
+trust-unverified = Unverified
+trust-private = Private
+trust-finished = Finished
new file mode 100644
--- /dev/null
+++ b/chat/content/otr/otrUI.ftl
@@ -0,0 +1,73 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+start-label = Start private conversation
+refresh-label = Refresh private conversation
+auth-label = Verify your contact's identity
+reauth-label = Reverify your contact's identity
+
+auth-cancel = Cancel
+auth-cancelAccessKey = C
+
+auth-error = An error occurred while verifying your contact's identity.
+auth-success = Verifying your contact's identity completed successfully.
+auth-successThem = Your contact has successfully verified your identity. You may want to verify their identity as well by asking your own question.
+auth-fail = Failed to verify your contact's identity.
+auth-waiting = Waiting for contact to complete verification …
+
+finger-verify = Verify
+finger-verify-accessKey = V
+
+# Do not translate 'OTR' (name of an encryption protocol)
+buddycontextmenu-label = Add Contact's OTR Fingerprint
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+alert-start = Attempting to start a private conversation with { $name }.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+alert-refresh = Attempting to refresh the private conversation with { $name }.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+alert-gone_insecure = Private conversation with { $name } ended.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+finger-unseen = The identity of { $name } has not been verified yet. Casual eavesdropping is not possible, but with some effort someone could be listening in. You should verify this contact's identity.
+
+state-not_private = The current conversation is not private.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+state-unverified = The current conversation is private but the identity of { $name } has not been verified.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+state-private = The current conversation is private and the identity of { $name } has been verified.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+state-finished = { $name } has ended their private conversation with you; you should do the same.
+
+state-not_private-label = Insecure
+state-unverified-label = Unverified
+state-private-label = Private
+state-finished-label = Finished
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+afterauth-private = You have verified the identity of { $name }.
+
+# Variables:
+#   $name (String) - the screen name of a chat contact person
+afterauth-unverified = The identity of { $name } has not been verified.
+
+verify-title = Verify your contact's identity
+error-title = Error
+success-title = End to End Encryption
+successThem-title = Verify your contact's identity
+fail-title = Unable to verify
+waiting-title = Verification request sent
deleted file mode 100644
--- a/chat/content/otrUI.properties
+++ /dev/null
@@ -1,59 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-start.label=Start private conversation
-refresh.label=Refresh private conversation
-auth.label=Verify your contact's identity
-auth.cancel=Cancel
-auth.cancelAccessKey=C
-auth.error=An error occurred while verifying your contact's identity.
-auth.success=Verifying your contact's identity completed successfully.
-auth.successThem=Your contact has successfully verified your identity. You may want to verify their identity as well by asking your own question.
-auth.fail=Failed to verify your contact's identity.
-auth.waiting=Waiting for contact to complete verification …
-reauth.label=Reverify your contact's identity
-finger.verify=Verify
-verify.accessKey=V
-
-# LOCALIZATION NOTE (buddycontextmenu.label): do not translate OTR which is the name of an encryption protocol
-buddycontextmenu.label=Add Contact's OTR Fingerprint
-
-# LOCALIZATION NOTE (alert.start): %S is the name of a chat contact
-alert.start=Attempting to start a private conversation with %S.
-# LOCALIZATION NOTE (alert.refresh): %S is the name of a chat contact
-alert.refresh=Attempting to refresh the private conversation with %S.
-# LOCALIZATION NOTE (alert.gone_insecure): %S is the name of a chat contact
-alert.gone_insecure=Private conversation with %S ended.
-
-# LOCALIZATION NOTE (finger.unseen): %S is the name of a chat contact
-finger.unseen=The identity of %S has not been verified yet. Casual eavesdropping is not possible, but with some effort someone could be listening in. You should verify this contact's identity.
-
-state.not_private=The current conversation is not private.
-
-# LOCALIZATION NOTE (state.unverified): %S is the name of a chat contact
-state.unverified=The current conversation is private but the identity of %S has not been verified.
-
-# LOCALIZATION NOTE (state.private): %S is the name of a chat contact
-state.private=The current conversation is private and the identity of %S has been verified.
-
-# LOCALIZATION NOTE (state.finished): %S is the name of a chat contact
-state.finished=%S has ended their private conversation with you; you should do the same.
-
-state.not_private.label=Insecure
-state.unverified.label=Unverified
-state.private.label=Private
-state.finished.label=Finished
-
-# LOCALIZATION NOTE (afterauth.private): %S is the name of a chat contact
-afterauth.private=You have verified the identity of %S.
-
-# LOCALIZATION NOTE (afterauth.unverified): %S is the name of a chat contact
-afterauth.unverified=The identity of %S has not been verified.
-
-verify.title=Verify your contact's identity
-error.title=Error
-success.title=End to End Encryption
-successThem.title=Verify your contact's identity
-fail.title=Unable to verify
-waiting.title=Verification request sent
--- a/chat/modules/OTR.jsm
+++ b/chat/modules/OTR.jsm
@@ -1,28 +1,34 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+const { LocalizationSync } =
+  ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
 const {BasePromiseWorker} = ChromeUtils.import("resource://gre/modules/PromiseWorker.jsm");
 const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 const {ctypes} = ChromeUtils.import("resource://gre/modules/ctypes.jsm");
 const {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
-const {
-  XPCOMUtils,
-  l10nHelper,
-} = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
 const {CLib} = ChromeUtils.import("resource:///modules/CLib.jsm");
 const {OTRLib} = ChromeUtils.import("resource:///modules/OTRLib.jsm");
 var workerPath = "chrome://chat/content/otrWorker.js";
 const {OTRHelpers} = ChromeUtils.import("resource:///modules/OTRHelpers.jsm");
 
-XPCOMUtils.defineLazyGetter(this, "_", () =>
-  l10nHelper("chrome://chat/content/otr.properties")
-);
+const syncL10n = new LocalizationSync([
+  "messenger/otr/otr.ftl",
+]);
+
+function _str(id) {
+  return syncL10n.formatValue(id);
+}
+
+function _strArgs(id, args) {
+  return syncL10n.formatValue(id, args);
+}
 
 // some helpers
 
 function setInterval(fn, delay) {
   let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   timer.init(fn, delay, Ci.nsITimer.TYPE_REPEATING_SLACK);
   return timer;
 }
@@ -273,23 +279,23 @@ var OTR = {
       return OTR.trustState.TRUST_FINISHED;
     }
     return OTR.trustState.TRUST_NOT_PRIVATE;
   },
 
   getStatus(level) {
     switch (level) {
     case OTR.trustState.TRUST_NOT_PRIVATE:
-      return _("trust.not_private");
+      return _str("trust-not_private");
     case OTR.trustState.TRUST_UNVERIFIED:
-      return _("trust.unverified");
+      return _str("trust-unverified");
     case OTR.trustState.TRUST_PRIVATE:
-      return _("trust.private");
+      return _str("trust-private");
     case OTR.trustState.TRUST_FINISHED:
-      return _("trust.finished");
+      return _str("trust-finished");
     }
     throw new Error("unknown level");
   },
 
   // get list of known fingerprints
   knownFingerprints() {
     let fps = [];
     for (
@@ -324,17 +330,17 @@ var OTR = {
         }
         fps.push({
           fpointer: fingerprint.contents.address(),
           fingerprint: OTR.hashToHuman(fingerprint),
           screenname: wContext.username,
           account: wContext.account,
           protocol: wContext.protocol,
           trust,
-          status: used ? OTR.getStatus(best_level) : _("trust.unused"),
+          status: used ? OTR.getStatus(best_level) : _str("trust-unused"),
           purge: false,
         });
       }
     }
     return fps;
   },
 
   forgetFingerprints(fps) {
@@ -492,17 +498,17 @@ var OTR = {
     );
     if (query.isNull()) {
       Cu.reportError(new Error("Sending query message failed."));
       return;
     }
     // Use the default msg to format the version.
     // We don't supprt v1 of the protocol so this should be fine.
     let queryMsg = /^\?OTR.*?\?/.exec(query.readString())[0] + "\n";
-    queryMsg += _("query.msg", conv.account.normalizedName);
+    queryMsg += _strArgs("query-msg", {name: conv.account.normalizedName});
     conv.sendMsg(queryMsg);
     OTRLib.otrl_message_free(query);
   },
 
   trustState: {
     TRUST_NOT_PRIVATE: 0,
     TRUST_UNVERIFIED: 1,
     TRUST_PRIVATE: 2,
@@ -593,36 +599,36 @@ var OTR = {
   // The list of known fingerprints has changed.  Write them to disk.
   write_fingerprint_cb(opdata) {
     this.writeFingerprints();
   },
 
   // A ConnContext has entered a secure state.
   gone_secure_cb(opdata, context) {
     context = new Context(context);
-    let str = "context.gone_secure_" + (context.trust ? "private" : "unverified");
+    let strid = "context-gone_secure_" + (context.trust ? "private" : "unverified");
     this.notifyObservers(context, "otr:msg-state");
-    this.sendAlert(context, _(str, context.username));
+    this.sendAlert(context, _strArgs(strid, {name: context.username}));
     if (this.verifyNudge && !context.trust)
       this.notifyObservers(context, "otr:unverified", "unseen");
   },
 
   // A ConnContext has left a secure state.
   gone_insecure_cb(opdata, context) {
     // This isn't used. See: https://bugs.otr.im/lib/libotr/issues/48
   },
 
   // We have completed an authentication, using the D-H keys we already knew.
   // is_reply indicates whether we initiated the AKE.
   still_secure_cb(opdata, context, is_reply) {
     // Indicate the private conversation was refreshed.
     if (!is_reply) {
       context = new Context(context);
       this.notifyObservers(context, "otr:msg-state");
-      this.sendAlert(context, _("context.still_secure", context.username));
+      this.sendAlert(context, _strArgs("context-still_secure", {name: context.username}));
     }
   },
 
   // Find the maximum message size supported by this protocol.
   max_message_size_cb(opdata, context) {
     context = new Context(context);
     // These values are, for the most part, from pidgin-otr's mms_table.
     switch (context.protocol) {
@@ -660,42 +666,42 @@ var OTR = {
   },
 
   // Return a string according to the error event.
   otr_error_message_cb(opdata, context, err_code) {
     context = new Context(context);
     let msg;
     switch (err_code) {
     case OTRLib.errorCode.OTRL_ERRCODE_ENCRYPTION_ERROR:
-      msg = _("error.enc");
+      msg = _str("error-enc");
       break;
     case OTRLib.errorCode.OTRL_ERRCODE_MSG_NOT_IN_PRIVATE:
-      msg = _("error.not_priv", context.username);
+      msg = _strArgs("error-not_priv", context.username);
       break;
     case OTRLib.errorCode.OTRL_ERRCODE_MSG_UNREADABLE:
-      msg = _("error.unreadable");
+      msg = _str("error-unreadable");
       break;
     case OTRLib.errorCode.OTRL_ERRCODE_MSG_MALFORMED:
-      msg = _("error.malformed");
+      msg = _str("error-malformed");
       break;
     default:
       return null;
     }
     return CLib.strdup(msg);
   },
 
   // Deallocate a string returned by otr_error_message_cb.
   otr_error_message_free_cb(opdata, err_msg) {
     if (!err_msg.isNull())
       CLib.free(err_msg);
   },
 
   // Return a string that will be prefixed to any resent message.
   resent_msg_prefix_cb(opdata, context) {
-    return CLib.strdup(_("resent"));
+    return CLib.strdup(_str("resent"));
   },
 
   // Deallocate a string returned by resent_msg_prefix.
   resent_msg_prefix_free_cb(opdata, prefix) {
     if (!prefix.isNull())
       CLib.free(prefix);
   },
 
@@ -734,60 +740,61 @@ var OTR = {
   // Handle and send the appropriate message(s) to the sender/recipient
   // depending on the message events.
   handle_msg_event_cb(opdata, msg_event, context, message, err) {
     context = new Context(context);
     switch (msg_event) {
     case OTRLib.messageEvent.OTRL_MSGEVENT_NONE:
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_ENCRYPTION_REQUIRED:
-      this.sendAlert(context, _("msgevent.encryption_required_part1", context.username));
-      this.sendAlert(context, _("msgevent.encryption_required_part2"));
+      this.sendAlert(context, _strArgs("msgevent-encryption_required_part1", {name: context.username}));
+      this.sendAlert(context, _str("msgevent-encryption_required_part2"));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_ENCRYPTION_ERROR:
-      this.sendAlert(context, _("msgevent.encryption_error"));
+      this.sendAlert(context, _str("msgevent-encryption_error"));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_CONNECTION_ENDED:
-      this.sendAlert(context, _("msgevent.connection_ended", context.username));
+      this.sendAlert(context, _strArgs("msgevent-connection_ended", {name: context.username}));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_SETUP_ERROR:
-      this.sendAlert(context, _("msgevent.setup_error", context.username));
+      this.sendAlert(context, _strArgs("msgevent-setup_error", {name: context.username}));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_MSG_REFLECTED:
-      this.sendAlert(context, _("msgevent.msg_reflected"));
+      this.sendAlert(context, _str("msgevent-msg_reflected"));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_MSG_RESENT:
-      this.sendAlert(context, _("msgevent.msg_resent", context.username));
+      this.sendAlert(context, _strArgs("msgevent-msg_resent", {name: context.username}));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE:
-      this.sendAlert(context, _("msgevent.rcvdmsg_not_private", context.username));
+      this.sendAlert(context, _strArgs("msgevent-rcvdmsg_not_private", {name: context.username}));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_RCVDMSG_UNREADABLE:
-      this.sendAlert(context, _("msgevent.rcvdmsg_unreadable", context.username));
+      this.sendAlert(context, _strArgs("msgevent-rcvdmsg_unreadable", {name: context.username}));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_RCVDMSG_MALFORMED:
-      this.sendAlert(context, _("msgevent.rcvdmsg_malformed", context.username));
+      this.sendAlert(context, _strArgs("msgevent-rcvdmsg_malformed", {name: context.username}));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD:
-      this.log(_("msgevent.log_heartbeat_rcvd", context.username));
+      this.log(_strArgs("msgevent-log_heartbeat_rcvd", {name: context.username}));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_LOG_HEARTBEAT_SENT:
-      this.log(_("msgevent.log_heartbeat_sent", context.username));
+      this.log(_strArgs("msgevent-log_heartbeat_sent", {name: context.username}));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR:
-      this.sendAlert(context, _("msgevent.rcvdmsg_general_err"));
+      this.sendAlert(context, _str("msgevent-rcvdmsg_general_err"));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED:
-      this.sendAlert(context, _("msgevent.rcvdmsg_unencrypted", context.username, message.isNull() ? "" : message.readString()));
+      this.sendAlert(context, _strArgs("msgevent-rcvdmsg_unencrypted",
+        {name: context.username, msg: message.isNull() ? "" : message.readString()}));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED:
-      this.sendAlert(context, _("msgevent.rcvdmsg_unrecognized", context.username));
+      this.sendAlert(context, _strArgs("msgevent-rcvdmsg_unrecognized", {name: context.username}));
       break;
     case OTRLib.messageEvent.OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE:
-      this.log(_("msgevent.rcvdmsg_for_other_instance", context.username));
+      this.log(_strArgs("msgevent-rcvdmsg_for_other_instance", {name: context.username}));
       break;
     default:
       this.log("msg event: " + msg_event);
     }
   },
 
   // Create an instance tag for the given accountname/protocol if desired.
   create_instag_cb(opdata, accountname, protocol) {
@@ -1036,17 +1043,17 @@ var OTR = {
     }
 
     // search tlvs for a disconnect msg
     // https://bugs.otr.im/lib/libotr/issues/54
     let tlv = OTRLib.otrl_tlv_find(tlvs, OTRLib.tlvs.OTRL_TLV_DISCONNECTED);
     if (!tlv.isNull()) {
       let context = this.getContext(conv);
       this.notifyObservers(context, "otr:disconnected");
-      this.sendAlert(context, _("tlv.disconnected", conv.normalizedName));
+      this.sendAlert(context, _strArgs("tlv-disconnected", {name: conv.normalizedName}));
     }
 
     if (err) {
       this.log("error (" + err + ") ignoring: " + im.displayMessage);
       im.cancelled = true;  // ignore
     } else {
       this.log("post receiving: " + im.displayMessage);
     }
--- a/chat/modules/OTRUI.jsm
+++ b/chat/modules/OTRUI.jsm
@@ -1,86 +1,102 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 this.EXPORTED_SYMBOLS = ["OTRUI"];
 
+const { LocalizationSync } =
+  ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
 const {Services} = ChromeUtils.import("resource:///modules/imServices.jsm");
-const {
-  XPCOMUtils,
-  l10nHelper,
-} = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
+const {OTR}  = ChromeUtils.import("resource:///modules/OTR.jsm");
+
+const syncL10n = new LocalizationSync([
+  "messenger/otr/otrUI.ftl",
+]);
 
-const {OTR}  = ChromeUtils.import("resource:///modules/OTR.jsm");
+function _str(id) {
+  return syncL10n.formatValue(id);
+}
+
+function _strArgs(id, args) {
+  return syncL10n.formatValue(id, args);
+}
 
 const privDialog = "chrome://chat/content/otr-generate-key.xul";
 const authDialog = "chrome://chat/content/otr-auth.xul";
 const addFingerDialog = "chrome://chat/content/otr-add-fingerprint.xul";
 
-XPCOMUtils.defineLazyGetter(this, "_", () =>
-  l10nHelper("chrome://chat/content/otrUI.properties")
-);
-
 const authVerify = "otr-auth-unverified";
+var authLabelMap;
+var authTitleMap;
+var trustMap;
 
-var authLabelMap = new Map([
-  ["otr:auth-error", _("auth.error")],
-  ["otr:auth-success", _("auth.success")],
-  ["otr:auth-successThem", _("auth.successThem")],
-  ["otr:auth-fail", _("auth.fail")],
-  ["otr:auth-waiting", _("auth.waiting")],
-]);
+function initStrings() {
+  authLabelMap = new Map([
+    ["otr:auth-error", _str("auth-error")],
+    ["otr:auth-success", _str("auth-success")],
+    ["otr:auth-successThem", _str("auth-successThem")],
+    ["otr:auth-fail", _str("auth-fail")],
+    ["otr:auth-waiting", _str("auth-waiting")],
+  ]);
 
-var authTitleMap = new Map([
-  ["otr:auth-error", "error"],
-  ["otr:auth-success", "success"],
-  ["otr:auth-successThem", "successThem"],
-  ["otr:auth-fail", "fail"],
-  ["otr:auth-waiting", "waiting"],
-]);
+  authTitleMap = new Map([
+    ["otr:auth-error", _str("error-title")],
+    ["otr:auth-success", _str("success-title")],
+    ["otr:auth-successThem", _str("successThem-title")],
+    ["otr:auth-fail", _str("fail-title")],
+    ["otr:auth-waiting", _str("waiting-title")],
+  ]);
+
+  let sl = _str("start-label");
+  let al = _str("auth-label");
+  let rfl = _str("refresh-label");
+  let ral = _str("reauth-label");
 
-var trustMap = new Map([
-  [OTR.trustState.TRUST_NOT_PRIVATE, {
-    startLabel: _("start.label"),
-    authLabel: _("auth.label"),
-    disableStart: false,
-    disableEnd: true,
-    disableAuth: true,
-    class: "not_private",
-  }],
-  [OTR.trustState.TRUST_UNVERIFIED, {
-    startLabel: _("refresh.label"),
-    authLabel: _("auth.label"),
-    disableStart: false,
-    disableEnd: false,
-    disableAuth: false,
-    class: "unverified",
-  }],
-  [OTR.trustState.TRUST_PRIVATE, {
-    startLabel: _("refresh.label"),
-    authLabel: _("reauth.label"),
-    disableStart: false,
-    disableEnd: false,
-    disableAuth: false,
-    class: "private",
-  }],
-  [OTR.trustState.TRUST_FINISHED, {
-    startLabel: _("start.label"),
-    authLabel: _("auth.label"),
-    disableStart: false,
-    disableEnd: false,
-    disableAuth: true,
-    class: "finished",
-  }],
-]);
+  trustMap = new Map([
+    [OTR.trustState.TRUST_NOT_PRIVATE, {
+      startLabel: sl,
+      authLabel: al,
+      disableStart: false,
+      disableEnd: true,
+      disableAuth: true,
+      class: "not_private",
+    }],
+    [OTR.trustState.TRUST_UNVERIFIED, {
+      startLabel: rfl,
+      authLabel: al,
+      disableStart: false,
+      disableEnd: false,
+      disableAuth: false,
+      class: "unverified",
+    }],
+    [OTR.trustState.TRUST_PRIVATE, {
+      startLabel: rfl,
+      authLabel: ral,
+      disableStart: false,
+      disableEnd: false,
+      disableAuth: false,
+      class: "private",
+    }],
+    [OTR.trustState.TRUST_FINISHED, {
+      startLabel: sl,
+      authLabel: al,
+      disableStart: false,
+      disableEnd: false,
+      disableAuth: true,
+      class: "finished",
+    }],
+  ]);
+}
 
 var windowRefs = new Map();
 
 var OTRUI = {
+  stringsLoaded: false,
   globalDoc: null,
   visibleConv: null,
 
   debug: true,
   logMsg(msg) {
     if (!OTRUI.debug)
       return;
     Services.console.logStringMessage(msg);
@@ -135,17 +151,17 @@ var OTRUI = {
     if (!buddyContextMenu || !OTR.libLoaded) {
       return;  // Not the buddy list context menu
     }
     OTRUI.removeBuddyContextMenu(doc);
 
     let sep = doc.createXULElement("menuseparator");
     sep.setAttribute("id", "otrsep");
     let menuitem = doc.createXULElement("menuitem");
-    menuitem.setAttribute("label", _("buddycontextmenu.label"));
+    menuitem.setAttribute("label", _str("buddycontextmenu.label"));
     menuitem.setAttribute("id", "otrcont");
     menuitem.addEventListener("command", () => {
       let target = buddyContextMenu.triggerNode;
       if (target.localName == "richlistitem") {
         let contact = target.contact;
         let args = OTRUI.contactWrapper(contact);
         args.wrappedJSObject = args;
         let features = "chrome,modal,centerscreen,resizable=no,minimizable=no";
@@ -174,17 +190,22 @@ var OTRUI = {
       s.remove();
     }
     let p = doc.getElementById("otrcont");
     if (p) {
       p.remove();
     }
   },
 
-  init() {
+  async init() {
+    if (!OTRUI.stringsLoaded) {
+      initStrings();
+      OTRUI.stringsLoaded = true;
+    }
+
     // console.log("====> OTRUI init\n");
     OTRUI.setPrefs();
     OTR.init({
       requireEncryption: OTRUI.prefs.getBoolPref("requireEncryption"),
       verifyNudge: OTRUI.prefs.getBoolPref("verifyNudge"),
     });
     if (!OTR.libLoaded) {
       return;
@@ -272,17 +293,17 @@ var OTRUI = {
     if (context) {
       OTRUI.hideUserNotifications(context);
     } else {
       OTRUI.hideAllNotifications();
     }
   },
 
   sendSystemAlert(uiConv, conv, bundleId) {
-    uiConv.systemMessage(_(bundleId, conv.normalizedName));
+    uiConv.systemMessage(_strArgs(bundleId, {name: conv.normalizedName}));
   },
 
   setNotificationBox(notificationbox) {
     this.globalBox = notificationbox;
   },
 
 /*
  *  possible states:
@@ -358,44 +379,54 @@ var OTRUI = {
       }
 
       if (uiConv.isChat) {
         OTRUI.noOtrPossible(otrContainer, context);
         return;
       }
       if (addSystemMessage) {
         let trust = OTRUI.getTrustSettings(context);
-        uiConv.systemMessage(_("state." + trust.class, context.username));
+        let id = "state-" + trust.class;
+        let msg;
+        if (OTR.trust(context) == OTR.trustState.TRUST_NOT_PRIVATE) {
+          msg = syncL10n.formatValue(id);
+        } else {
+          msg = syncL10n.formatValue(id, {name: context.username});
+        }
+        uiConv.systemMessage(msg);
       }
     } catch (e) {
       OTRUI.noOtrPossible(otrContainer, context);
       return;
     }
 
     otrContainer.hidden = false;
     let otrStart = doc.querySelector(".otr-start");
     let otrEnd = doc.querySelector(".otr-end");
     let otrAuth = doc.querySelector(".otr-auth");
     let trust = OTRUI.getTrustSettings(context);
-    otrButton.setAttribute("tooltiptext", _("state." + trust.class, context.username));
-    otrButton.setAttribute("label", _("state." + trust.class + ".label"));
+    otrButton.setAttribute("tooltiptext",
+      _strArgs("state-" + trust.class, {name: context.username}));
+    otrButton.setAttribute("label",
+      _str("state-" + trust.class + "-label"));
     otrButton.className = "otr-button otr-" + trust.class;
     otrStart.setAttribute("label", trust.startLabel);
     otrStart.setAttribute("disabled", trust.disableStart);
     otrEnd.setAttribute("disabled", trust.disableEnd);
     otrAuth.setAttribute("label", trust.authLabel);
     otrAuth.setAttribute("disabled", trust.disableAuth);
     OTRUI.hideAllNotifications();
     OTRUI.showUserNotifications(context);
   },
 
   alertTrust(context) {
     let uiConv = OTR.getUIConvFromContext(context);
     let trust = OTRUI.getTrustSettings(context);
-    uiConv.systemMessage(_("afterauth." + trust.class, context.username));
+    uiConv.systemMessage(
+      _strArgs("afterauth-" + trust.class, {name: context.username}));
   },
 
   getTrustSettings(context) {
     let result = trustMap.get(OTR.trust(context));
     return result;
   },
 
   askAuth(aObject) {
@@ -444,46 +475,47 @@ var OTRUI = {
     let uiConv = OTR.getUIConvFromContext(context);
     if (!uiConv) return;
 
     if (this.globalBox.getNotificationWithValue(authVerify))
       return;
 
     let window = this.globalDoc.defaultView;
 
-    let msg = _("finger." + seen, context.username);
+    let msg = _strArgs("finger-" + seen, {name: context.username});
     let buttons = [{
-      label: _("finger.verify"),
-      accessKey: _("verify.accessKey"),
+      label: _str("finger-verify"),
+      accessKey: _str("finger-verify-accessKey"),
       callback() {
         let name = uiConv.target.normalizedName;
         OTRUI.openAuth(window, name, "start", uiConv);
         // prevent closing of notification bar when the button is hit
         return true;
       },
     }];
 
     let priority = this.globalBox.PRIORITY_WARNING_MEDIUM;
     this.globalBox.appendNotification(msg, authVerify, null, priority, buttons, null);
 
-    this.updateNotificationUI(context, "verify", authVerify);
+    let verifyTitle = syncL10n.formatValue("verify-title");
+    this.updateNotificationUI(context, verifyTitle, authVerify);
   },
 
-  updateNotificationUI(context, type, value) {
+  updateNotificationUI(context, typeTitle, value) {
     let notification = this.globalBox.getNotificationWithValue(value);
     notification.setAttribute("user", context.username);
     notification.setAttribute("orient", "vertical");
     notification.messageDetails.setAttribute("orient", "vertical");
     notification.messageDetails.removeAttribute("oncommand");
     notification.messageDetails.removeAttribute("align");
 
     let title = this.globalDoc.createElement("title");
     title.setAttribute("flex", "1");
     title.setAttribute("crop", "end");
-    title.textContent = _(type + ".title");
+    title.textContent = typeTitle;
 
     let close = notification.querySelector("toolbarbutton");
     close.setAttribute("oncommand", "this.parentNode.parentNode.dismiss();");
 
     let top = this.globalDoc.createXULElement("hbox");
     top.setAttribute("flex", "1");
     top.setAttribute("align", "center");
     top.classList.add("otr-notification-header");
@@ -519,35 +551,35 @@ var OTRUI = {
     let uiConv = OTR.getUIConvFromContext(context);
     if (!uiConv) return;
 
     // TODO: maybe update the .label property on the notification instead
     // of closing it ... although, buttons need to be updated too.
     OTRUI.closeVerification(context);
 
     let msg = authLabelMap.get(key);
-    let type = authTitleMap.get(key);
+    let typeTitle = authTitleMap.get(key);
     let buttons = [];
     if (cancelable) {
       buttons = [{
-        label: _("auth.cancel"),
-        accessKey: _("auth.cancelAccessKey"),
+        label: _str("auth-cancel"),
+        accessKey: _str("auth-cancelAccessKey"),
         callback() {
           let context = OTR.getContext(uiConv.target);
           OTR.abortSMP(context);
         },
       }];
     }
 
     // higher priority to overlay the current notifyUnverified
     let priority = this.globalBox.PRIORITY_WARNING_HIGH;
     OTRUI.closeUnverified(context);
     this.globalBox.appendNotification(msg, key, null, priority, buttons, null);
 
-    this.updateNotificationUI(context, type, key);
+    this.updateNotificationUI(context, typeTitle, key);
   },
 
   updateAuth(aObj) {
     // let uiConv = OTR.getUIConvFromContext(aObj.context);
     if (!aObj.progress) {
       OTRUI.closeAuth(aObj.context);
       OTRUI.notifyVerification(aObj.context, "otr:auth-error", false);
     } else if (aObj.progress === 100) {
--- a/mail/components/im/content/chat-conversation-info.js
+++ b/mail/components/im/content/chat-conversation-info.js
@@ -41,16 +41,20 @@ class MozChatConversationInfo extends Mo
 
   connectedCallback() {
     if (this.hasChildNodes() || this.delayConnectedCallback()) {
       return;
     }
     this.setAttribute("orient", "vertical");
 
     this.appendChild(MozXULElement.parseXULToFragment(`
+      <linkset>
+        <html:link rel="localization" href="messenger/otr/chat.ftl"/>
+      </linkset>
+
       <hbox class="displayUserAccount" flex="1">
         <stack class="statusImageStack">
           <box class="userIconHolder">
             <image class="userIcon" mousethrough="always"></image>
           </box>
           <image class="statusTypeIcon"></image>
         </stack>
         <stack class="displayNameAndstatusMessageStack" mousethrough="always" flex="1">
@@ -59,29 +63,29 @@ class MozChatConversationInfo extends Mo
             </description>
             <image class="prplIcon"></image>
           </hbox>
           <description class="statusMessage" mousethrough="never" crop="end" flex="100000">
           </description>
         </stack>
       </hbox>
       <hbox class="otr-container" align="left" valign="middle" flex="1" hidden="true">
-        <label class="otr-label" crop="end" value="&state.label;" flex="1"/>
+        <label class="otr-label" crop="end" data-l10n-id="state-label" flex="1"/>
         <toolbarbutton id="otrButton"
                        mode="dialog"
                        class="otr-button toolbarbutton-1"
                        type="menu"
                        label="Insecure"
-                       tooltiptext="&start.label;">
+                       data-l10n-id="start-tooltip">
           <menupopup class="otr-menu-popup">
-            <menuitem class="otr-start" label="&start.label;"
+            <menuitem class="otr-start" data-l10n-id="start-label"
                       oncommand='this.closest("chat-conversation-info").onOtrStartClicked();'/>
-            <menuitem class="otr-end" label="&end.label;"
+            <menuitem class="otr-end" data-l10n-id="end-label"
                       oncommand='this.closest("chat-conversation-info").onOtrEndClicked();'/>
-            <menuitem class="otr-auth" label="&auth.label;"
+            <menuitem class="otr-auth" data-l10n-id="auth-label"
                       oncommand='this.closest("chat-conversation-info").onOtrAuthClicked();'/>
           </menupopup>
         </toolbarbutton>
       </hbox>
       <hbox id="otr-notification-box"></hbox>
     `, ["chrome://chat/content/otr-chat.dtd"]));
 
     this.topic.addEventListener("click", this.startEditTopic.bind(this));
@@ -168,29 +172,29 @@ class MozChatConversationInfo extends Mo
   }
 
   onOtrStartClicked() {
     // check if start-menu-command is disabled, if yes exit
     let convBinding = document.getElementById("conversationsDeck").selectedPanel;
     let uiConv = convBinding._conv;
     let conv = uiConv.target;
     let context = OTR.getContext(conv);
-    let bundleId = "alert." + (
+    let bundleId = "alert-" + (
       context.msgstate === OTR.getMessageState().OTRL_MSGSTATE_ENCRYPTED ?
         "refresh" : "start");
     OTRUI.sendSystemAlert(uiConv, conv, bundleId);
     OTR.sendQueryMsg(conv);
   }
 
   onOtrEndClicked() {
     let convBinding = document.getElementById("conversationsDeck").selectedPanel;
     let uiConv = convBinding._conv;
     let conv = uiConv.target;
     OTR.disconnect(conv, false);
-    let bundleId = "alert.gone_insecure";
+    let bundleId = "alert-gone_insecure";
     OTRUI.sendSystemAlert(uiConv, conv, bundleId);
   }
 
   onOtrAuthClicked() {
     let convBinding = document.getElementById("conversationsDeck").selectedPanel;
     let uiConv = convBinding._conv;
     let conv = uiConv.target;
     OTRUI.openAuth(window, conv.normalizedName, "start", uiConv);
--- a/mail/components/im/content/chat-messenger.js
+++ b/mail/components/im/content/chat-messenger.js
@@ -1163,17 +1163,17 @@ var chatHandler = {
     let rawConversations = conversations.map((c) => c.conv);
     let current;
     if (aList.selectedItem.localName == "richlistitem" && aList.selectedItem.getAttribute("is") == "chat-imconv")
       current = aList.selectedIndex - aList.getIndexOfItem(conversations[0]);
     let newIndex = this._getNextUnreadConversation(rawConversations, current, aReverse);
     if (newIndex !== -1)
       aList.selectedItem = conversations[newIndex];
   },
-  init() {
+  async init() {
     Notifications.init();
     if (!Services.prefs.getBoolPref("mail.chat.enabled")) {
       ["button-chat", "menu_goChat", "goChatSeparator",
        "imAccountsStatus", "joinChatMenuItem", "newIMAccountMenuItem",
        "newIMContactMenuItem", "appmenu_joinChatMenuItem", "appmenu_afterChatSeparator",
        "appmenu_goChat", "appmenu_imAccountsStatus", "appmenu_goChatSeparator",
        "appmenu_newIMAccountMenuItem", "appmenu_newIMContactMenuItem"].forEach(function(aId) {
          let elt = document.getElementById(aId);
--- a/mail/locales/jar.mn
+++ b/mail/locales/jar.mn
@@ -4,16 +4,25 @@
 
 # Note: This file should only contain locale entries. All
 # override and resource entries should go to mail/base/jar.mn to avoid
 # having to create the same entry for each locale.
 
 #filter substitution
 
 [localization] @AB_CD@.jar:
+  # When ready for l10n, move chat/content/otr/*.ftl
+  #                        to mail/locales/en-US/messenger/otr/
+  # and remove the following otr/ lines.
+  messenger/otr/add-finger.ftl                                          (../../chat/content/otr/add-finger.ftl)
+  messenger/otr/auth.ftl                                                (../../chat/content/otr/auth.ftl)
+  messenger/otr/chat.ftl                                                (../../chat/content/otr/chat.ftl)
+  messenger/otr/generate-key.ftl                                        (../../chat/content/otr/generate-key.ftl)
+  messenger/otr/otr.ftl                                                 (../../chat/content/otr/otr.ftl)
+  messenger/otr/otrUI.ftl                                               (../../chat/content/otr/otrUI.ftl)
   messenger                                                             (%messenger/**/*.ftl)
 
 
 @AB_CD@.jar:
 % locale messenger @AB_CD@ %locale/@AB_CD@/messenger/
   locale/@AB_CD@/messenger/aboutDialog.dtd                              (%chrome/messenger/aboutDialog.dtd)
   locale/@AB_CD@/messenger/aboutDownloads.dtd                           (%chrome/messenger/aboutDownloads.dtd)
   locale/@AB_CD@/messenger/aboutRights.dtd                              (%chrome/messenger/aboutRights.dtd)