[tor-commits] [snowflake/master] Start localization

arlo at torproject.org arlo at torproject.org
Thu Aug 15 21:16:03 UTC 2019


commit 4e5a50f2b54db62991e4ce3313aa9b7f92a1c573
Author: Arlo Breault <arlolra at gmail.com>
Date:   Wed Aug 14 13:45:15 2019 -0400

    Start localization
    
    Trac 30310
---
 .gitignore                                |  1 +
 proxy/init-badge.js                       | 53 ++++++++++++++++++++++++-------
 proxy/make.js                             |  7 ++--
 proxy/static/_locales/en_US/messages.json | 32 +++++++++++++++++++
 proxy/static/embed.html                   |  6 ++--
 proxy/static/index.html                   |  2 +-
 proxy/static/popup.js                     | 12 +++++++
 proxy/webext/embed.js                     | 25 +++++++++++----
 proxy/webext/manifest.json                |  3 +-
 9 files changed, 115 insertions(+), 26 deletions(-)

diff --git a/.gitignore b/.gitignore
index f3af78e..1bae622 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,5 +19,6 @@ proxy/webext/popup.js
 proxy/webext/embed.html
 proxy/webext/embed.css
 proxy/webext/assets/
+proxy/webext/_locales/
 ignore/
 npm-debug.log
diff --git a/proxy/init-badge.js b/proxy/init-badge.js
index dbe7fea..b906f62 100644
--- a/proxy/init-badge.js
+++ b/proxy/init-badge.js
@@ -4,6 +4,20 @@
 UI
 */
 
+class Messages {
+  constructor(json) {
+    this.json = json;
+  }
+  getMessage(m, ...rest) {
+    let message = this.json[m].message;
+    return message.replace(/\$(\d+)/g, (...args) => {
+      return rest[Number(args[1]) - 1];
+    });
+  }
+}
+
+let messages = null;
+
 class BadgeUI extends UI {
 
   constructor() {
@@ -16,7 +30,7 @@ class BadgeUI extends UI {
   missingFeature(missing) {
     this.popup.setEnabled(false);
     this.popup.setActive(false);
-    this.popup.setStatusText("Snowflake is off");
+    this.popup.setStatusText(messages.getMessage('popupStatusOff'));
     this.popup.setStatusDesc(missing, true);
     this.popup.hideButton();
   }
@@ -24,20 +38,23 @@ class BadgeUI extends UI {
   turnOn() {
     const clients = this.active ? 1 : 0;
     this.popup.setChecked(true);
-    this.popup.setToggleText('Turn Off');
-    this.popup.setStatusText(`${clients} client${(clients !== 1) ? 's' : ''} connected.`);
+    this.popup.setToggleText(messages.getMessage('popupTurnOff'));
+    if (clients > 0) {
+      this.popup.setStatusText(messages.getMessage('popupStatusOn', String(clients)));
+    } else {
+      this.popup.setStatusText(messages.getMessage('popupStatusReady'));
+    }
     // FIXME: Share stats from webext
-    const total = 0;
-    this.popup.setStatusDesc(`Your snowflake has helped ${total} user${(total !== 1) ? 's' : ''} circumvent censorship in the last 24 hours.`);
+    this.popup.setStatusDesc('');
     this.popup.setEnabled(true);
     this.popup.setActive(this.active);
   }
 
   turnOff() {
     this.popup.setChecked(false);
-    this.popup.setToggleText('Turn On');
-    this.popup.setStatusText("Snowflake is off");
-    this.popup.setStatusDesc("");
+    this.popup.setToggleText(messages.getMessage('popupTurnOn'));
+    this.popup.setStatusText(messages.getMessage('popupStatusOff'));
+    this.popup.setStatusDesc('');
     this.popup.setEnabled(false);
     this.popup.setActive(false);
   }
@@ -108,12 +125,12 @@ var debug, snowflake, config, broker, ui, log, dbg, init, update, silenceNotific
     ui = new BadgeUI();
 
     if (!Util.hasWebRTC()) {
-      ui.missingFeature("WebRTC feature is not detected.");
+      ui.missingFeature(messages.getMessage('popupWebRTCOff'));
       return;
     }
 
     if (!Util.hasCookies()) {
-      ui.missingFeature("Cookies are not enabled.");
+      ui.missingFeature(messages.getMessage('badgeCookiesOff'));
       return;
     }
 
@@ -153,6 +170,20 @@ var debug, snowflake, config, broker, ui, log, dbg, init, update, silenceNotific
     return null;
   };
 
-  window.onload = init;
+  window.onload = function() {
+    const lang = 'en_US';
+    fetch(`./_locales/${lang}/messages.json`)
+    .then((res) => {
+      if (!res.ok) { return; }
+      return res.json();
+    })
+    .then((json) => {
+      messages = new Messages(json);
+      Popup.fill(document.body, (m) => {
+        return messages.getMessage(m);
+      });
+      init();
+    });
+  }
 
 }());
diff --git a/proxy/make.js b/proxy/make.js
index 59165b2..58b7fce 100755
--- a/proxy/make.js
+++ b/proxy/make.js
@@ -32,7 +32,8 @@ var SHARED_FILES = [
   'embed.html',
   'embed.css',
   'popup.js',
-  'assets'
+  'assets',
+  '_locales',
 ];
 
 var concatJS = function(outDir, init, outFile) {
@@ -67,7 +68,7 @@ task('test', 'snowflake unit tests', function() {
 });
 
 task('build', 'build the snowflake proxy', function() {
-  execSync('rm -r build');
+  execSync('rm -rf build');
   execSync('cp -r ' + STATIC + '/ build/');
   concatJS('build', 'badge', 'embed.js');
   console.log('Snowflake prepared.');
@@ -87,7 +88,7 @@ task('node', 'build the node binary', function() {
 });
 
 task('clean', 'remove all built files', function() {
-  execSync('rm -r build test spec/support');
+  execSync('rm -rf build test spec/support');
 });
 
 var cmd = process.argv[2];
diff --git a/proxy/static/_locales/en_US/messages.json b/proxy/static/_locales/en_US/messages.json
new file mode 100644
index 0000000..f9de9d4
--- /dev/null
+++ b/proxy/static/_locales/en_US/messages.json
@@ -0,0 +1,32 @@
+{
+  "appDesc": {
+    "message": "Snowflake is a WebRTC pluggable transport for Tor."
+  },
+  "popupTurnOn": {
+    "message": "Turn On"
+  },
+  "popupTurnOff": {
+    "message": "Turn Off"
+  },
+  "popupLearnMore": {
+    "message": "Learn more"
+  },
+  "popupStatusOff": {
+    "message": "Snowflake is off"
+  },
+  "popupStatusOn": {
+    "message": "Number of users currently connected: $1"
+  },
+  "popupStatusReady": {
+    "message": "Your Snowflake is ready to help users circumvent censorship!"
+  },
+  "popupWebRTCOff": {
+    "message": "WebRTC feature is not detected."
+  },
+  "popupDescOn": {
+    "message": "Number of users your Snowflake has helped circumvent censorship in the last 24 hours: $1"
+  },
+  "badgeCookiesOff": {
+    "message": "Cookies are not enabled."
+  }
+}
diff --git a/proxy/static/embed.html b/proxy/static/embed.html
index 441241a..eb75c30 100644
--- a/proxy/static/embed.html
+++ b/proxy/static/embed.html
@@ -11,18 +11,18 @@
   <body>
     <div id="active">
       <div id="statusimg"></div>
-      <p id="statustext">Snowflake is off</p>
+      <p id="statustext">__MSG_popupStatusOff__</p>
       <p id="statusdesc"></p>
     </div>
     <div class="b button">
-      <label id="toggle" for="enabled">Turn On</label>
+      <label id="toggle" for="enabled">__MSG_popupTurnOn__</label>
       <label class="switch">
         <input id="enabled" type="checkbox" />
         <span class="slider round"></span>
       </label>
     </div>
     <div class="b learn">
-      <a target="_blank" href="https://snowflake.torproject.org/">Learn more</a>
+      <a target="_blank" href="https://snowflake.torproject.org/">__MSG_popupLearnMore__</a>
     </div>
   </body>
 </html>
diff --git a/proxy/static/index.html b/proxy/static/index.html
index 20fe5c8..5607e07 100644
--- a/proxy/static/index.html
+++ b/proxy/static/index.html
@@ -86,7 +86,7 @@
 
       <p>Which looks like this:</p>
 
-      <iframe src="embed.html" width="320px" height="200px" frameborder="0" scrolling="no"></iframe>
+      <iframe src="embed.html" width="320px" height="240px" frameborder="0" scrolling="no"></iframe>
 
     </div>
   </body>
diff --git a/proxy/static/popup.js b/proxy/static/popup.js
index 9ff8121..c59f842 100644
--- a/proxy/static/popup.js
+++ b/proxy/static/popup.js
@@ -38,4 +38,16 @@ class Popup {
   setToggleText(txt) {
     document.getElementById('toggle').innerText = txt;
   }
+  static fill(n, func) {
+    switch(n.nodeType) {
+      case 3: {  // Node.TEXT_NODE
+        const m = /^__MSG_([^_]*)__$/.exec(n.nodeValue);
+        if (m) { n.nodeValue = func(m[1]); }
+        break;
+      }
+      case 1:  // Node.ELEMENT_NODE
+        n.childNodes.forEach(c => Popup.fill(c, func));
+        break;
+    }
+  }
 }
diff --git a/proxy/webext/embed.js b/proxy/webext/embed.js
index 62c97a5..7e0dac9 100644
--- a/proxy/webext/embed.js
+++ b/proxy/webext/embed.js
@@ -1,5 +1,12 @@
 /* global chrome, Popup */
 
+// Fill i18n in HTML
+window.onload = () => {
+  Popup.fill(document.body, (m) => {
+    return chrome.i18n.getMessage(m);
+  });
+};
+
 const port = chrome.runtime.connect({
   name: "popup"
 });
@@ -11,8 +18,8 @@ port.onMessage.addListener((m) => {
   if (missingFeature) {
     popup.setEnabled(false);
     popup.setActive(false);
-    popup.setStatusText("Snowflake is off");
-    popup.setStatusDesc("WebRTC feature is not detected.", true);
+    popup.setStatusText(chrome.i18n.getMessage('popupStatusOff'));
+    popup.setStatusDesc(chrome.i18n.getMessage('popupWebRTCOff'), true);
     popup.hideButton();
     return;
   }
@@ -21,13 +28,17 @@ port.onMessage.addListener((m) => {
 
   if (enabled) {
     popup.setChecked(true);
-    popup.setToggleText('Turn Off');
-    popup.setStatusText(`${clients} client${(clients !== 1) ? 's' : ''} connected.`);
-    popup.setStatusDesc(`Your snowflake has helped ${total} user${(total !== 1) ? 's' : ''} circumvent censorship in the last 24 hours.`);
+    popup.setToggleText(chrome.i18n.getMessage('popupTurnOff'));
+    if (clients > 0) {
+      popup.setStatusText(chrome.i18n.getMessage('popupStatusOn', String(clients)));
+    } else {
+      popup.setStatusText(chrome.i18n.getMessage('popupStatusReady'));
+    }
+    popup.setStatusDesc((total > 0) ? chrome.i18n.getMessage('popupDescOn', String(total)) : '');
   } else {
     popup.setChecked(false);
-    popup.setToggleText('Turn On');
-    popup.setStatusText("Snowflake is off");
+    popup.setToggleText(chrome.i18n.getMessage('popupTurnOn'));
+    popup.setStatusText(chrome.i18n.getMessage('popupStatusOff'));
     popup.setStatusDesc("");
   }
   popup.setEnabled(enabled);
diff --git a/proxy/webext/manifest.json b/proxy/webext/manifest.json
index 8863dbb..7317c67 100644
--- a/proxy/webext/manifest.json
+++ b/proxy/webext/manifest.json
@@ -2,7 +2,8 @@
   "manifest_version": 2,
   "name": "Snowflake",
   "version": "0.0.9",
-  "description": "Snowflake is a WebRTC pluggable transport for Tor.",
+  "description": "__MSG_appDesc__",
+  "default_locale": "en_US",
   "background": {
     "scripts": ["snowflake.js"],
     "persistent": true



More information about the tor-commits mailing list