From e8f6eb0d59f68d14b90858ccd858661916d62022 Mon Sep 17 00:00:00 2001
From: Jonathan Wilkes <jon.w.wilkes@gmail.com>
Date: Mon, 2 Mar 2015 01:11:16 -0500
Subject: [PATCH] First draft of i18n, plus cleanup of iemgui properties
 dialogs

---
 pd/nw/index.html                  | 142 +++++----
 pd/nw/locales/en/translation.json | 214 +++++++++++++
 pd/nw/pdcanvas.html               | 311 ++++++++++--------
 pd/nw/pdgui.js                    |   9 +-
 pd/nw/pdlang.js                   |  17 +
 pd/nw/pdproperties.css            |  93 +++++-
 pd/nw/pdproperties.html           | 511 ++++++++++++++++++++----------
 7 files changed, 926 insertions(+), 371 deletions(-)
 create mode 100644 pd/nw/locales/en/translation.json
 create mode 100644 pd/nw/pdlang.js

diff --git a/pd/nw/index.html b/pd/nw/index.html
index 8d64aaf25..46a70896f 100644
--- a/pd/nw/index.html
+++ b/pd/nw/index.html
@@ -22,6 +22,9 @@
         pdgui.set_pd_window(this);
         pdgui.set_app_quitfn(app_quit);
 
+        // For translations
+        var l = pdgui.get_local_string;
+
         console.log("Hello from " + pwd);
 
         function app_quit () {
@@ -154,22 +157,24 @@ function nw_create_pd_window_menus () {
 
     // Add to window menu
     windowMenu.append(new nw.MenuItem({
-        label: 'File',
+        label: l('menu.file'),
         submenu: fileMenu
     }));
 
     // File sub-entries
     fileMenu.append(new nw.MenuItem({
-        label: 'New',
+        label: l('menu.new'),
         click: pdgui.menu_new,
         key: 'n',
-        modifiers: "ctrl"
+        modifiers: 'ctrl',
+        tooltip: l('menu.new.tt')
     }));
 
     fileMenu.append(new nw.MenuItem({
-        label: 'Open',
+        label: l('menu.open'),
         key: 'o',
         modifiers: "ctrl",
+        tooltip: l('menu.open.tt'),
         click: function (){
             var chooser = document.querySelector('#fileDialog');
             chooser.click();
@@ -178,7 +183,8 @@ function nw_create_pd_window_menus () {
 
     if (pdgui.k12_mode == 1) {
         fileMenu.append(new nw.MenuItem({
-        label: 'K12 Demos',
+        label: l('menu.k12.demos'),
+        tooltip: l('menu.k12.demos_tt'),
         click: pdgui.menu_k12_open_demos
         }));
     }
@@ -189,18 +195,20 @@ function nw_create_pd_window_menus () {
 
     // Note: this must be different for the main Pd window
     fileMenu.append(new nw.MenuItem({
-        label: 'Save',
+        label: l('menu.save'),
             click: function () {},
             enabled: false,
         key: 's',
+        tooltip: l('menu.save.tt'),
         modifiers: "ctrl"
     }));
 
     fileMenu.append(new nw.MenuItem({
-        label: 'Save as...',
+        label: l('menu.saveas'),
         click: function (){},
         enabled: false,
         key: 'S',
+        tooltip: l('menu.saveas_tt'),
         modifiers: "ctrl"
     }));
 
@@ -211,10 +219,11 @@ function nw_create_pd_window_menus () {
     }
 
     fileMenu.append(new nw.MenuItem({
-        label: 'Message...',
+        label: l('menu.message'),
         click: pdgui.menu_send,
         key: 'm',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.message_tt')
     }));
 
     if (pdgui.k12_mode == 0) {
@@ -229,17 +238,18 @@ function nw_create_pd_window_menus () {
 
     // Note: there's no good reason to have this here
     fileMenu.append(new nw.MenuItem({
-        label: 'Close',
+        label: l('menu.close'),
         click: function () {},
         enabled: false,
     }));
 
     // Quit Pd
     fileMenu.append(new nw.MenuItem({
-        label: 'Quit',
+        label: l('menu.quit'),
         click: pdgui.menu_quit,
         key: 'q',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.quit_tt')
     }));
 
 
@@ -248,25 +258,27 @@ function nw_create_pd_window_menus () {
 
     // Add to window menu
     windowMenu.append(new nw.MenuItem({
-    label: 'Edit',
+    label: l('menu.edit'),
     submenu: editMenu
     }));
 
     // Edit sub-entries
     editMenu.append(new nw.MenuItem({
-        label: 'Copy',
+        label: l('menu.copy'),
         click: pdmenu_copy,
         key: 'c',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.copy_tt')
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Select All',
+        label: l('menu.selectall'),
         click: function () {
             document.selectAllChildren(document);
         },
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.selectall_tt')
     }));
 
     editMenu.append(new nw.MenuItem({
@@ -274,30 +286,33 @@ function nw_create_pd_window_menus () {
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Zoom In',
+        label: l('menu.zoomin'),
         click: function () {
             nw.Window.get().zoomLevel += 1;
             pdgui.gui_post("zoom level is " + nw.Window.get().zoomLevel);
         },
         key: '=',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.zoomin_tt')
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Zoom Out',
+        label: l('menu.zoomout'),
         click: function () {
             nw.Window.get().zoomLevel -= 1;
             pdgui.gui_post("zoom level is " + nw.Window.get().zoomLevel);
         },
         key: '-',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.zoomout_tt')
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Preferences...',
+        label: l('menu.preferences'),
         click: pdmenu_preferences,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.preferences_tt')
     }));
 
 
@@ -306,23 +321,25 @@ function nw_create_pd_window_menus () {
 
     // Add to windows menu
     windowMenu.append(new nw.MenuItem({
-    label: 'Windows',
+    label: l('menu.windows'),
     submenu: winmanMenu
     }));
 
     // Winman sub-entries
     winmanMenu.append(new nw.MenuItem({
-        label: 'Next Window',
+        label: l('menu.nextwin'),
         click: pdmenu_next_win,
         key: 'c',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.nextwin_tt')
     }));
 
     winmanMenu.append(new nw.MenuItem({
-        label: 'Previous Window',
+        label: l('menu.prevwin'),
         click: pdmenu_previous_win,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.prevwin_tt')
     }));
 
     winmanMenu.append(new nw.MenuItem({
@@ -330,17 +347,19 @@ function nw_create_pd_window_menus () {
     }));
 
     winmanMenu.append(new nw.MenuItem({
-        label: 'Parent Window',
+        label: l('menu.parentwin'),
         click: pdmenu_parent_win,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.parentwin_tt')
     }));
 
     winmanMenu.append(new nw.MenuItem({
-        label: 'Pd & Console',
+        label: l('menu.pdwin'),
         click: pdmenu_console_win,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.pdwin_tt')
     }));
 
     // Media menu
@@ -348,27 +367,29 @@ function nw_create_pd_window_menus () {
 
     // Add to window menu
     windowMenu.append(new nw.MenuItem({
-    label: 'Media',
+    label: l('menu.media'),
     submenu: mediaMenu
     }));
 
     // Media sub-entries
     mediaMenu.append(new nw.MenuItem({
-        label: 'Audio On',
+        label: l('menu.audio_on'),
         click: function() {
             pdgui.pdsend("pd dsp 1");
         },
         key: 'c',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.audio_on_tt')
     }));
 
     mediaMenu.append(new nw.MenuItem({
-        label: 'Audio Off',
+        label: l('menu.audio_off'),
         click: function() {
             pdgui.pdsend("pd dsp 0");
         },
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.audio_off_tt')
     }));
 
     mediaMenu.append(new nw.MenuItem({
@@ -376,17 +397,19 @@ function nw_create_pd_window_menus () {
     }));
 
     mediaMenu.append(new nw.MenuItem({
-        label: 'Test Audio and Midi',
+        label: l('menu.test'),
         click: pdmenu_test_audio,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.test_tt')
     }));
 
     mediaMenu.append(new nw.MenuItem({
-        label: 'Load Meter',
+        label: l('menu.loadmeter'),
         click: pdmenu_load_meter,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.loadmeter_tt')
     }));
 
     // Help menu
@@ -394,30 +417,33 @@ function nw_create_pd_window_menus () {
 
     // Add to window menu
     windowMenu.append(new nw.MenuItem({
-    label: 'Help',
+    label: l('menu.help'),
     submenu: helpMenu
     }));
 
     // Help sub-entries
     helpMenu.append(new nw.MenuItem({
-        label: 'About Pd-L2ork',
+        label: l('menu.about'),
         click: pdmenu_about_pd,
         key: 'c',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.about_tt')
     }));
 
     helpMenu.append(new nw.MenuItem({
-        label: 'Manual',
+        label: l('menu.manual'),
         click: pdmenu_manual,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.manual_tt')
     }));
 
     helpMenu.append(new nw.MenuItem({
-        label: 'Help Browser',
+        label: l('menu.browser'),
         click: pdmenu_help_browser,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.browser_tt')
     }));
 
     helpMenu.append(new nw.MenuItem({
@@ -425,31 +451,35 @@ function nw_create_pd_window_menus () {
     }));
 
     helpMenu.append(new nw.MenuItem({
-        label: 'Pd-L2ork Mailing List',
+        label: l('menu.l2ork_list'),
         click: pdmenu_l2ork_mailinglist,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.l2ork_list_tt')
     }));
 
     helpMenu.append(new nw.MenuItem({
-        label: 'Pure Data Mailing Lists',
+        label: l('menu.pd_list'),
         click: pdmenu_pd_mailinglists,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.pd_list_tt')
     }));
 
     helpMenu.append(new nw.MenuItem({
-        label: 'Forums',
+        label: l('menu.forums'),
         click: pdmenu_forums,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.forums_tt')
     }));
 
     helpMenu.append(new nw.MenuItem({
-        label: 'IRC Chat',
+        label: l('menu.irc'),
         click: pdmenu_irc,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.irc_tt')
     }));
 
     // Assign to window
diff --git a/pd/nw/locales/en/translation.json b/pd/nw/locales/en/translation.json
new file mode 100644
index 000000000..0962fd7e2
--- /dev/null
+++ b/pd/nw/locales/en/translation.json
@@ -0,0 +1,214 @@
+{
+  "hello": "world",
+  "iem": {
+    "prop": {
+      "heading": {
+          "size":    "size and behavior",
+          "messages": "messages",
+          "label":   "label",
+          "colors":  "colors"
+      },
+      "size_tt": "size of the iemgui",
+      "size": "size",
+      "select_size": "selection size",
+      "select_size_tt": "size of the selection rectangle used to select and drag the object",
+      "visible_width": "width",
+      "visible_width_tt": "width of the rectangle",
+      "visible_height": "height",
+      "visible_height_tt": "height of the rectangle",
+      "nonzero_value": "nonzero value",
+      "nonzero_value_tt": "value to output when the toggle shows an 'x'",
+      "flash_interrupt": "flash interrupt",
+      "flash_interrupt_tt": "the amount of time (in milliseconds) that Pd will wait before interrupting the flashing of the button",
+      "flash_hold":  "hold",
+      "flash_hold_tt": "the amount of time (in milliseconds) that Pd will display the flash animation",
+      "vu_scale":    "scale",
+      "vu_scale_tt": "display scale (numbers) next to the meter",
+      "width":       "width",
+      "width_tt":     "width of the iemgui in pixels",
+      "height":       "height",
+      "height_tt":    "height of the iemgui in pixels",
+      "minimum":      "minimum",
+      "minimum_tt":   "smallest number to output. Anything lower will be replaced by this number",
+      "maximum":      "maximum",
+      "maximum_tt":   "largest number to output. Anything bigger will be replaced by this number",
+      "number":       "number",
+      "number_tt":    "total number of buttons",
+      "init":         "init",
+      "init_tt":      "save the state of the iemgui with the patch, and output a saved value when loaded (as if you had a [loadbang] connected to the input)",
+      "log_scale":          "logarithmic scaling",
+      "log_scale_tt":       "use logarithmic scale for values along the slider",
+      "log_height":   "log height",
+      "log_height_tt":   "the framus intersects with the ramistan approximately at the podernoster",
+      "steady":       "steady on click",
+      "steady_tt":    "don't move the slider when clicked. Only move it when dragging the mouse",
+      "send":         "send symbol",
+      "send_tt":      "symbol to send wireless messages to other iemguis or objects",
+      "receive":      "receive symbol",
+      "receive_tt":   "symbol to receive wireless messages from other iemguis or objects",
+      "label":        "text",
+      "label_tt":     "text to display next to this iemgui",
+      "xoffset":      "x",
+      "xoffset_tt":  "horizontal offset of the text relative to the top-left corner of the iemgui",
+      "yoffset":      "y",
+      "yoffset_tt":   "vertical offset for the text relative to the top-left corner of the iemgui",
+      "font":         "font",
+      "font_tt":      "which font to use when displaying the label text",
+      "fontsize":     "size",
+      "fontsize_tt":  "size of the font for the label",
+      "bgcolor":      "background",
+      "bgcolor_tt":   "background fill color for the iemgui",
+      "fgcolor":      "foreground",
+      "fgcolor_tt":   "foreground color for the iemgui",
+      "label_color":        "label",
+      "label_color_tt":     "color for the text label",
+      "ok":           "Ok",
+      "ok_tt":        "Apply the settings and close this dialog window",
+      "apply":        "Apply",
+      "apply_tt":     "Apply the settings without closing the dialog",
+      "cancel":       "Cancel",
+      "cancel_tt":    "Close the dialog window"
+    }
+  },
+  "menu": {
+    "file": "File",
+    "new": "New",
+    "new_tt": "Create an empty Pd patch",
+    "open": "Open",
+    "open_tt": "Open one or more Pd files",
+    "k12_demos": "K12 Demos",
+    "k12_demos_tt": "Demo patches for use with K12 Mode",
+    "save": "Save",
+    "save_tt": "Save a Pd patch to disk",
+    "saveas": "Save as...",
+    "saveas_tt": "Save a Pd patch by manually choosing a filename",
+    "message": "Message...",
+    "message_tt": "Send a message directly to the running Pd instance",
+
+    "close": "Close",
+    "close_tt": "Close patch",
+
+    "quit": "Quit",
+    "quit_tt": "Close all patches and quit the program",
+
+    "edit": "Edit",
+
+    "undo": "Undo",
+    "undo_tt": "Undo the last action performed on this patch",
+    "redo": "Redo",
+    "redo_tt": "if you clicked undo, this will restore the action that you performed",
+    "cut": "Cut",
+    "cut_tt": "Remove the currently selected object or objects on the canvas",
+    "copy": "Copy",
+    "copy_tt": "Copy selected objects to the clipboard",
+    "paste": "Paste",
+    "paste_tt": "Add any objects to the canvas which wer previously cut or copied",
+    "duplicate": "Duplicate",
+    "duplicate_tt": "Paste a copy of the current selection on the canvas (doesn't use clipboard)",
+    "selectall": "Select All",
+    "selectall_tt": "Select all objects in a patch",
+    "reselect": "Reselect",
+    "reselect_tt": "Restore the previous selection",
+    "zoomin": "Zoom In",
+    "zoomin_tt": "Make the patch visually larger",
+    "zoomout": "Zoom Out",
+    "zoomout_tt": "Make the patch visually smaller",
+    "tidyup": "Tidy Up",
+    "tidyup_tt": "Line up the selected objects in straight rows and columns",
+    "tofront": "Bring to Front",
+    "tofront_tt": "Bring the selected object visually in front of all other objects",
+    "toback": "Send to Back",
+    "toback_tt": "Send the selected object visually behind all other objects",
+    "font": "Font",
+    "font_tt": "Font settings for this patch",
+    "cordinspector": "Cord Inspector",
+    "cordinspector_tt": "Move the mouse over cords to inspect the data moving between objects",
+    "find": "Find",
+    "find_tt": "Search for an object in this patch",
+    "findagain": "Find Again",
+    "findagain_tt": "Search for the next object matching the string you typed",
+    "finderror": "Find Last Error",
+    "finderror_tt": "If possible, find the last object which caused an error",
+    "autotips": "Autotips",
+    "autotips_tt": "Turn on tooltips in the patch",
+    "editmode": "Editmode",
+    "autotips_tt": "Toggle Editmode",
+    "preferences": "Preferences",
+    "preferences_tt": "Open a dialog window to configure the running instance of Pd",
+
+    "put": "Put",
+
+    "object": "Object",
+    "object_tt": "Add an empty object box to the canvas",
+    "msgbox": "Message",
+    "msg_tt": "Add a message box to the canvas",
+    "number": "Number",
+    "number_tt": "Add a box to type, scroll, and display a number on the canvas",
+    "symbol": "Symbol",
+    "symbol_tt": "Add a box to type and display a symbol on the canvas",
+    "comment": "Comment",
+    "comment_tt": "Write a comment on the canvas",
+    "bang": "Bang",
+    "bang_tt": "Add a graphical button to the canvas for sending bang messages",
+    "bang": "Toggle",
+    "bang_tt": "Add a graphical checkbox to the canvas for toggling between two values",
+    "number2": "Number2",
+    "number2_tt": "Add a fancy graphical box to the canvas for displaying and scrolling numbers",
+    "vslider": "Vslider",
+    "vslider_tt": "Add a vertical slider to the canvas for scrolling numbers",
+    "hslider": "Hslider",
+    "hslider_tt": "Add a horizontal slider to the canvas for scrolling numbers",
+    "vradio": "Vradio",
+    "vradio_tt": "Add a vertical group of radio buttons to the canvas for selecting a value",
+    "hradio": "Hradio",
+    "hradio_tt": "Add horizontal group of radio buttons to the canvas for selecting a value",
+    "vu": "VU",
+    "vu_tt": "Add a Vu Meter to the canvas",
+    "cnv": "Canvas Rectangle",
+    "cnv_tt": "Add a boring rectangle to the canvas for displaying a rectangle",
+
+    "graph": "Graph",
+    "graph_tt": "Add an empty graph to the canvas",
+    "array": "Array",
+    "array_tt": "Add a visual array object to the canvas (with dialog for settings)",
+
+    "windows": "Windows",
+
+    "nextwin": "Next Window",
+    "nextwin_tt": "Give focus to the next open window in the stacking order",
+    "prevwin": "Previous Window",
+    "prevwin_tt": "Give focus to the previous window in the stacking order",
+    "parentwin": "Parent Window",
+    "parentwin_tt": "give focus to the parent window of the current window",
+    "pdwin": "Pd & Console",
+    "pdwin_tt": "Give focus to the main Pd window",
+
+    "media": "Media",
+
+    "audio_on": "Audio On",
+    "audio_on_tt": "Turn audio on",
+    "audio_off": "Audio Off",
+    "audio_off_tt": "Turn audio off",
+    "test": "Test Audio and Midi",
+    "test_tt": "Open a patch to test your audio and midi are configured and functioning correctly",
+    "loadmeter": "Load Meter",
+    "loadmeter_tt": "Open a patch to monitor the CPU load of Pd (note: doesn't include the GUI)",
+
+    "help": "Help",
+
+    "about": "About Pd-L2ork",
+    "about_tt": "Get information about this version of Pd",
+    "manual": "Manual",
+    "manual_tt": "Open the HTML manual for Pd",
+    "browser": "Help Browser",
+    "browser_tt": "Open a help browser to search for documentation and objects",
+    "l2ork_list": "Pd-L2ork Mailing List",
+    "l2ork_list_tt": "Open a link in a browser for Pd-L2ork Mailing List",
+    "pd_list": "Pure Data Mailing Lists",
+    "pd_list_tt": "Open a link in a browser for Pure Data Mailing Lists",
+    "forums": "Forums",
+    "forums_tt": "Open a link in a browser for the Pd Forum",
+    "irc": "IRC Chat",
+    "irc_tt": "Open a link in a browser for IRC Chat"
+  }
+}
diff --git a/pd/nw/pdcanvas.html b/pd/nw/pdcanvas.html
index 6642ac467..a0b3c2707 100644
--- a/pd/nw/pdcanvas.html
+++ b/pd/nw/pdcanvas.html
@@ -21,6 +21,8 @@
 
     //var name = pdgui.last_loaded();
     
+    var l = pdgui.get_local_string;
+
     console.log("my working dire is " + pdgui.get_pwd());
     document.getElementById("saveDialog").setAttribute("nwworkingdir", pdgui.get_pwd());
 
@@ -309,22 +311,24 @@ function nw_create_patch_window_menus (name) {
 
     // Add to window menu
     windowMenu.append(new nw.MenuItem({
-        label: 'File',
+        label: l('menu.file'),
         submenu: fileMenu
     }));
 
     // File sub-entries
     fileMenu.append(new nw.MenuItem({
-        label: 'New',
+        label: l('menu.new'),
         click: pdgui.menu_new,
         key: 'n',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.new_tt')
     }));
 
     fileMenu.append(new nw.MenuItem({
-        label: 'Open',
+        label: l('menu.open'),
         key: 'o',
         modifiers: "ctrl",
+        tooltip: l('menu.open_tt'),
         click: function() {
             var chooser = document.querySelector('#fileDialog');
             chooser.click();
@@ -333,7 +337,8 @@ function nw_create_patch_window_menus (name) {
 
     if (pdgui.k12_mode == 1) {
         fileMenu.append(new nw.MenuItem({
-        label: 'K12 Demos',
+        label: l('menu.k12_demos'),
+        tooltip: l('menu.k12_demos_tt'),
         click: pdgui.menu_k12_open_demos
         }));
     }
@@ -344,21 +349,23 @@ function nw_create_patch_window_menus (name) {
 
     // Note: this must be different for the main Pd window
     fileMenu.append(new nw.MenuItem({
-        label: 'Save',
+        label: l('menu.save'),
         click: function () {
             pdgui.menu_save(name);
         },
         key: 's',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.save_tt')
     }));
 
     fileMenu.append(new nw.MenuItem({
-        label: 'Save as...',
+        label: l('menu.saveas'),
         click: function (){
             pdgui.menu_saveas(name);
         },
         key: 's',
-        modifiers: "ctrl+shift"
+        modifiers: "ctrl+shift",
+        tooltip: l('menu.saveas_tt')
     }));
 
     if (pdgui.k12_mode == 0) {
@@ -368,10 +375,11 @@ function nw_create_patch_window_menus (name) {
     }
 
     fileMenu.append(new nw.MenuItem({
-        label: 'Message...',
+        label: l('menu.message'),
         click: pdgui.menu_send,
         key: 'm',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.message_tt')
     }));
 
     if (pdgui.k12_mode == 0) {
@@ -385,7 +393,8 @@ function nw_create_patch_window_menus (name) {
     // anther separator goes here if there are any recent files
 
     fileMenu.append(new nw.MenuItem({
-        label: 'Close',
+        label: l('menu.close'),
+        tooltip: l('menu.close_tt'),
         click: function() {
             pdgui.menu_close(name);
         }
@@ -393,10 +402,11 @@ function nw_create_patch_window_menus (name) {
 
     // Quit Pd
     fileMenu.append(new nw.MenuItem({
-        label: 'Quit',
+        label: l('menu.quit'),
         click: pdgui.menu_quit,
         key: 'q',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.quit_tt')
     }));
 
 
@@ -405,23 +415,25 @@ function nw_create_patch_window_menus (name) {
 
     // Add to window menu
     windowMenu.append(new nw.MenuItem({
-    label: 'Edit',
+    label: l('menu.edit'),
     submenu: editMenu
     }));
 
     // Edit sub-entries
     editMenu.append(new nw.MenuItem({
-        label: 'Undo',
+        label: l('menu.undo'),
         click: menu_generic,
         key: 'c',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.undo_tt')
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Redo',
+        label: l('menu.redo'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.redo_tt')
     }));
 
     editMenu.append(new nw.MenuItem({
@@ -429,53 +441,58 @@ function nw_create_patch_window_menus (name) {
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Cut',
+        label: l('menu.cut'),
         click: function () {
             pdgui.pdsend(name + " cut");
         },
         key: 'c',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.cut_tt')
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Copy',
+        label: l('menu.copy'),
         click: function () {
             pdgui.pdsend(name + " copy");
         },
         key: 'x',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.copy_tt')
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Paste',
+        label: l('menu.paste'),
         click: function () {
             pdgui.pdsend(name + " paste");
         },
         key: 'v',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.paste_tt')
     }));
 
 
     editMenu.append(new nw.MenuItem({
-        label: 'Duplicate',
+        label:  l('menu.duplicate'),
         click: function () {
             pdgui.pdsend(name + " duplicate");
         },
         key: 'd',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.duplicate_tt')
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Select All',
+        label: l('menu.selectall'),
         click: function () {
             pdgui.pdsend(name + " selectall");
         },
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.selectall_tt'),
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Reselect',
+        label: l('menu.reselect'),
         // Unfortunately nw.js doesn't allow
         // key: "Return" or key: "Enter", so we
         // can't bind to ctrl-Enter here. (Even
@@ -484,7 +501,8 @@ function nw_create_patch_window_menus (name) {
             pdgui.pdsend(name + " reselect");
         },
         key: String.fromCharCode(10),
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.reselect_tt')
     }));
 
     editMenu.append(new nw.MenuItem({
@@ -492,7 +510,7 @@ function nw_create_patch_window_menus (name) {
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Zoom In',
+        label: l('menu.zoomin'),
         click: function () {
             var z = nw.Window.get().zoomLevel;
             if (z < 8) { z++; }
@@ -500,11 +518,12 @@ function nw_create_patch_window_menus (name) {
             pdgui.gui_post("zoom level is " + nw.Window.get().zoomLevel);
         },
         key: '=',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.zoomin')
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Zoom Out',
+        label: l('menu.zoomout'),
         click: function () {
             var z = nw.Window.get().zoomLevel;
             if (z > -7) { z--; } 
@@ -512,35 +531,36 @@ function nw_create_patch_window_menus (name) {
             pdgui.gui_post("zoom level is " + nw.Window.get().zoomLevel);
         },
         key: '-',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.zoomout_tt'),
     }));
 
-
-
-
     editMenu.append(new nw.MenuItem({
         type: 'separator'
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Tidy Up',
+        label: l('menu.tidyup'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.tidyup_tt')
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Bring to Front',
+        label: l('menu.tofront'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.tofront_tt'),
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Send to Back',
+        label: l('menu.toback'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.toback_tt'),
     }));
 
     editMenu.append(new nw.MenuItem({
@@ -548,19 +568,21 @@ function nw_create_patch_window_menus (name) {
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Font',
+        label: l('menu.font'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.font_tt'),
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Cord Inspector',
+        label: l('menu.cordinspector'),
         click: function() {
             pdgui.pdsend(name + " magicglass 0");
         },
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.cordinspector_tt'),
     }));
 
     editMenu.append(new nw.MenuItem({
@@ -568,24 +590,27 @@ function nw_create_patch_window_menus (name) {
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Find',
+        label: l('menu.find'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.find_tt'),
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Find Again',
+        label: l('menu.findagain'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.findagain')
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Find Last Error',
+        label: l('menu.finderror'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.finderror_tt'),
     }));
 
     editMenu.append(new nw.MenuItem({
@@ -593,17 +618,19 @@ function nw_create_patch_window_menus (name) {
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Autotips',
+        label: l('menu.autotips'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.autotips_tt'),
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Editmode',
+        label: l('menu.editmode'),
         click: function() { pdgui.pdsend(name + " editmode 0"); },
         key: 'e',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.editmode_tt')
     }));
 
     editMenu.append(new nw.MenuItem({
@@ -611,10 +638,11 @@ function nw_create_patch_window_menus (name) {
     }));
 
     editMenu.append(new nw.MenuItem({
-        label: 'Preferences...',
+        label: l('menu.preferences'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.preferences_tt')
     }));
 
     // Put menu
@@ -622,19 +650,20 @@ function nw_create_patch_window_menus (name) {
 
     // Add to window menu
     windowMenu.append(new nw.MenuItem({
-    label: 'Put',
+    label: l('menu.put'),
     submenu: putMenu
     }));
 
     // Put menu sub-entries
     putMenu.append(new nw.MenuItem({
-        label: 'Object',
+        label: l('menu.object'),
         click: function() {
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " obj 0");
         },
         key: '1',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.object_tt'),
     }));
 
 /*
@@ -671,43 +700,47 @@ proc menu_array {name} {
 */
 
     putMenu.append(new nw.MenuItem({
-        label: 'Message',
+        label: l('menu.msgbox'),
         click: function() {
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " msg 0");
         },
         key: '2',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.msgbox_tt'),
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'Number',
+        label: l('menu.number'),
         click: function() { 
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " floatatom 0");
         },
         key: '3',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.number_tt')
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'Symbol',
+        label: l('menu.symbol'),
         click: function() {
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " symbolatom 0");
         },
         key: '4',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.symbol_tt')
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'Comment',
+        label: l('menu.comment'),
         click: function() {
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " text 0");
         },
         key: '5',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.comment_tt')
     }));
 
     putMenu.append(new nw.MenuItem({
@@ -715,93 +748,102 @@ proc menu_array {name} {
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'Bang',
+        label: l('menu.bang'),
         click: function(e) {
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " bng 0");
         },
         key: 'b',
-        modifiers: "ctrl-shift"
+        modifiers: "ctrl-shift",
+        tooltip: l('menu.bang_tt')
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'Toggle',
+        label: l('menu.toggle'),
         click: function() {
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " toggle 0");
         },
         key: 't',
-        modifiers: "ctrl-shift"
+        modifiers: "ctrl-shift",
+        tooltip: l('menu.toggle_tt')
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'Number2',
+        label: l('menu.number2'),
         click: function() {
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " numbox 0");
         },
         key: 'n',
-        modifiers: "ctrl-shift"
+        modifiers: "ctrl-shift",
+        tooltip: l('menu.number2')
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'Vslider',
+        label: l('menu.vslider'),
         click: function() {
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " vslider 0");
         },
         key: 'v',
-        modifiers: "ctrl-shift"
+        modifiers: "ctrl-shift",
+        tooltip: l('menu.vslider_tt'),
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'Hslider',
+        label: l('menu.hslider'),
         click: function() {
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " hslider 0");
         },
         key: 'h',
-        modifiers: "ctrl-shift"
+        modifiers: "ctrl-shift",
+        tooltip: l('menu.hslider_tt'),
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'Vradio',
+        label: l('menu.vradio'),
         click: function() {
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " vradio 0");
         },
         key: 'd',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.vradio_tt'),
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'Hradio',
+        label: l('menu.hradio'),
         click: function() {
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " hradio 0");
         },
         key: 'i',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.hradio_tt'),
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'VU',
+        label: l('menu.vu'),
         click: function() {
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " vumeter 0");
         },
         key: 'u',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.vu_tt'),
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'Canvas',
+        label: l('menu.cnv'),
         click: function() {
                    pdgui.pdsend(name + " dirty 1");
                    pdgui.pdsend(name + " mycnv 0");
         },
         key: 'c',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.cnv_tt')
     }));
 
     putMenu.append(new nw.MenuItem({
@@ -809,21 +851,23 @@ proc menu_array {name} {
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'Graph',
+        label: l('menu.graph'),
         click: function() {
             pdgui.pdsend(name + " dirty 1");
             // leaving out some placement logic... see pd.tk menu_graph
             pdgui.pdsend(name + " graph NULL 0 0 0 0 30 30 0 30");
         },
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.graph_tt'),
     }));
 
     putMenu.append(new nw.MenuItem({
-        label: 'Array',
+        label: l('menu.array'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.array_tt'),
     }));
 
 
@@ -832,23 +876,25 @@ proc menu_array {name} {
 
     // Add to windows menu
     windowMenu.append(new nw.MenuItem({
-    label: 'Windows',
+    label: l('menu.windows'),
     submenu: winmanMenu
     }));
 
     // Winman sub-entries
     winmanMenu.append(new nw.MenuItem({
-        label: 'Next Window',
+        label: l('menu.nextwin'),
         click: menu_generic,
         key: 'c',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.nextwin_tt'),
     }));
 
     winmanMenu.append(new nw.MenuItem({
-        label: 'Previous Window',
+        label: l('menu.prevwin'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.prevwin_tt'),
     }));
 
     winmanMenu.append(new nw.MenuItem({
@@ -856,17 +902,19 @@ proc menu_array {name} {
     }));
 
     winmanMenu.append(new nw.MenuItem({
-        label: 'Parent Window',
+        label: l('menu.parentwin'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.parentwin_tt'),
     }));
 
     winmanMenu.append(new nw.MenuItem({
-        label: 'Pd & Console',
+        label: l('menu.pdwin'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.pdwin_tt'),
     }));
 
     // Media menu
@@ -874,23 +922,25 @@ proc menu_array {name} {
 
     // Add to window menu
     windowMenu.append(new nw.MenuItem({
-    label: 'Media',
+    label: l('menu.media'),
     submenu: mediaMenu
     }));
 
     // Media sub-entries
     mediaMenu.append(new nw.MenuItem({
-        label: 'Audio On',
+        label: l('menu.audio_on'),
         click: menu_generic,
         key: 'c',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.audio_on_tt'),
     }));
 
     mediaMenu.append(new nw.MenuItem({
-        label: 'Audio Off',
+        label: l('menu.audio_off'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.audio_off_tt'),
     }));
 
     mediaMenu.append(new nw.MenuItem({
@@ -898,17 +948,19 @@ proc menu_array {name} {
     }));
 
     mediaMenu.append(new nw.MenuItem({
-        label: 'Test Audio and Midi',
+        label: l('menu.test'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.test_tt'),
     }));
 
     mediaMenu.append(new nw.MenuItem({
-        label: 'Load Meter',
+        label: l('menu.loadmeter'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.loadmeter_tt'),
     }));
 
     // Help menu
@@ -916,30 +968,33 @@ proc menu_array {name} {
 
     // Add to window menu
     windowMenu.append(new nw.MenuItem({
-    label: 'Help',
+    label: l('menu.help'),
     submenu: helpMenu
     }));
 
     // Help sub-entries
     helpMenu.append(new nw.MenuItem({
-        label: 'About Pd-L2ork',
+        label: l('menu.about'),
         click: menu_generic,
         key: 'c',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.about_tt'),
     }));
 
     helpMenu.append(new nw.MenuItem({
-        label: 'Manual',
+        label: l('menu.manual'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.manual'),
     }));
 
     helpMenu.append(new nw.MenuItem({
-        label: 'Help Browser',
+        label: l('menu.browser'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.browser_tt'),
     }));
 
     helpMenu.append(new nw.MenuItem({
@@ -947,31 +1002,35 @@ proc menu_array {name} {
     }));
 
     helpMenu.append(new nw.MenuItem({
-        label: 'Pd-L2ork Mailing List',
+        label: l('menu.l2ork_list'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.l2ork_list_tt'),
     }));
 
     helpMenu.append(new nw.MenuItem({
-        label: 'Pure Data Mailing Lists',
+        label: l('menu.pd_list'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.pd_list_tt'),
     }));
 
     helpMenu.append(new nw.MenuItem({
-        label: 'Forums',
+        label: l('menu.forums'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.forums_tt'),
     }));
 
     helpMenu.append(new nw.MenuItem({
-        label: 'IRC Chat',
+        label: l('menu.irc'),
         click: menu_generic,
         key: 'a',
-        modifiers: "ctrl"
+        modifiers: "ctrl",
+        tooltip: l('menu.irc_tt'),
     }));
 
     // Assign to window
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index 3129b5c23..bb11d3e94 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -15,11 +15,10 @@ exports.get_pwd = function() {
 var fs = require('fs');     // for fs.existsSync
 var path = require('path'); // for path.dirname path.extname
 
-var flub = { flash_interrupt_label: "this thing does stuff" };
-exports.tr = function(arg) {
-    return flub[arg];
-}
+// local strings
+var lang = require('./pdlang.js');
 
+exports.get_local_string = lang.get_local_string;
 
 var pd_window; 
 exports.pd_window;
@@ -2926,7 +2925,7 @@ function gui_iemgui_dialog(did, attr_array) {
     for (var i = 0; i < attr_array.length; i++) {
         attr_array[i] = '"' + attr_array[i] + '"';
     }
-    dialogwin[did] = nw_create_window(did, 'pd-properties', 235, 430, 20, 20, 0,
+    dialogwin[did] = nw_create_window(did, 'pd-properties', 265, 540, 20, 20, 0,
         0, 1, 'white', 'Properties', '', 0, null, attr_array);
 
 }
diff --git a/pd/nw/pdlang.js b/pd/nw/pdlang.js
new file mode 100644
index 000000000..9bdbae1cf
--- /dev/null
+++ b/pd/nw/pdlang.js
@@ -0,0 +1,17 @@
+var lang = require('./locales/en/translation.json');
+
+exports.lang = lang;
+
+exports.get_local_string = function(key) {
+    return recursive_key_splitter(key, lang);
+}
+
+function recursive_key_splitter(key, object) {
+    var subkeys = key.split(".");
+    if (subkeys.length > 1) {
+        return recursive_key_splitter(subkeys.slice(1).join("."), object[subkeys[0]]);
+    }
+    else {
+        return object[subkeys[0]];
+    }
+}
diff --git a/pd/nw/pdproperties.css b/pd/nw/pdproperties.css
index 88baa317e..b62808614 100644
--- a/pd/nw/pdproperties.css
+++ b/pd/nw/pdproperties.css
@@ -4,8 +4,43 @@
 }
 
 body {
+    font-family:Verdana;
     margin: 0px;
-    font-family: "DejaVu Sans Mono";
+}
+
+fieldset {
+    font-family:Georgia;
+    background-color:#eeeeee;
+    border-radius:3px;
+    border:2px solid black;
+    margin-left:auto;
+    margin-right:auto;
+    padding: 10px;
+}
+
+input[type="text"]{
+    width:3em;
+}
+
+input[type="number"]{
+    width:3em;
+}
+
+
+.prop{
+}
+
+.hidden {
+    display: none;
+}
+
+.container{
+    display: none;
+}
+
+
+
+body {
 }
 
 .noselect {
@@ -28,23 +63,51 @@ text {
     stroke-width: 1;
 }
 
-.selected_line {
-    stroke: blue;
+label {
+    text-align: right;
+//    margin-right: auto;
+//    margin-left: auto;
 }
 
-.broken_border {
-    fill: #f7f7f7;
-    stroke: red;
-    stroke-dasharray: 3 2;
+.pair {
+    width: 75%;
+    text-align: left;
+    align: left;
 }
 
-.xlet {
-    stroke: black;
-    fill: gray;
-    stroke-width: 1;
-    shape-rendering: optimizeSpeed;
+.item1 {
+    width: 50%;
+}
+
+.item2 {
+    width: 50%;
+}
+
+input[name="x-offset"] {
+    width: 2em;
+}
+
+input[name="y-offset"] {
+    width: 2em;
 }
 
-//.xlet:hover {
-//    stroke: red;
-//}
+input[name="send-symbol"] {
+    width: 8em;
+}
+
+input[name="receive-symbol"] {
+    width: 8em;
+}
+
+input[name="label"] {
+    width: 8em;
+}
+
+input[name="font-size"] {
+    width: 3em;
+}
+
+.submit_buttons {
+    text-align: center;
+    padding: 8px;
+}
diff --git a/pd/nw/pdproperties.html b/pd/nw/pdproperties.html
index 0b78458e0..b266a91b6 100644
--- a/pd/nw/pdproperties.html
+++ b/pd/nw/pdproperties.html
@@ -1,181 +1,310 @@
 <!DOCTYPE html>
 <html>
   <head>
-  <link rel="stylesheet" type="text/css" href="pdproperties.css">
-
-<style>body{font-family:Verdana;}
-fieldset{font-family:Georgia; background-color:#eeeeee;
-		 border-radius:3px; border:2px solid black;
-		 margin:5px; padding:8px; }
-
-input[type="text"]{width:3em;}
-
-.prop{display: none;}
-
-</style>
-
-
-
+    <link rel="stylesheet" type="text/css" href="pdproperties.css">
   </head>
   <body>
-
- <form> 
-     <fieldset> 
-      <legend>size and behavior</legend> 
-
-      <span class="size prop">
-        <label title="size of the iemgui">
-          size <input type="text" name="size"></label><br>
-      </span>
-
-      <span class="selection-size prop">
-        <label title="size of the selection rectangle used to select and drag the object">
-          selection size <input type="text" name="selection-size"></label><br>
-      </span>
-
-      <span class="width prop">
-        <label title="width of the iemgui">
-          width <input type="text" name="width"></label>
-        <label title="height of the iemgui">
-          height <input type="text" name="height"></label><br>
-      </span>
-
-      <span class="visible-width prop">
-        <label title="width of the rectangle">
-          width <input type="text" name="visible-width"></label>
-        <label title="height of the rectangle">
-          height <input type="text" name="visible-height"></label><br>
-      </span>
-
-      <span class="nonzero-value prop">
-        <label title="value to output when the toggle shows an 'x'">
-          nonzero value <input type="text" name="nonzero-value"></label><br>
-      </span>
-
-      <span class="number prop">
-        <label title="number of buttons">
-          number <input type="text" name="number"></label><br>
-      </span>
-
-      <span class="flash-interrupt prop">
-        <label title="the amount of time (in milliseconds) that Pd will wait before interrupting the flashing of the button">
-          interrupt <input type="text" name="flash-interrupt"></label>
-      </span>
-
-      <span class="flash-hold prop">
-        <label title="the amount of time (in milliseconds) that Pd will display the flash animation">
-          hold <input type="text" name="flash-hold"></label>  <br>
-      </span>
-
-      <span class="minimum-range prop">
-        <label title="the lowest number to output. Anything lower will be replaced by this number">
-          minimum <input type="text" name="minimum-range"></label>
-        <label title="the largest number to output. Anything higher will be replaced by this number">
-          maximum <input type="text" name="maximum-range"></label>  <br>
-      </span>
-
-      <span class="init prop">
-        <label title="when checked, this will save the state of the iemgui with the patch, and output a value when the patch is loaded (as if you had an invisible [loadbang] connected to the input).">
-          init <input type="checkbox" name="init" value="on"></label><br>
-      </span>
-
-      <span class="vu-scale prop">
-        <label title="display scale (numbers) next to the meter">
-          scale <input type="checkbox" name="vu-scale" value="on"></label><br>
-      </span>
-
-
-      <span class="log-scaling prop">
-        <label title="logarithmic scale for values along the slider">
-          logarithmic scaling <input type="checkbox" name="log-scaling" value="on"></label><br>
-      </span>
-
-      <span class="log-height prop">
-        <label title="logarithmic scale for values along the slider">
-          log height<input type="text" name="log-height"></label><br>
-      </span>
-
-      <span class="steady-on-click prop">
-        <label title="don't move the slider on click, only on dragging.">
-          steady on click <input type="checkbox" name="steady-on-click" value="on"></label><br>
-      </span>
-     </fieldset> 
-
-     <fieldset> 
-      <legend>messages</legend> 
-
-      <span class="send-symbol prop">
-        <label title="symbol you can use to send wireless messages to other iemguis (or a [receive] object)">
-          send-symbol <input type="text" name="send-symbol" ></label> <br>
-      </span>
-
-      <span class="receive-symbol prop">
-        <label title="symbol you can use to receive wireless messages from other iemguis (or from a [send] object)">
-          receive-symbol <input type="text" name="receive-symbol" ></label> <br>
-      </span>
-     </fieldset> 
-
-     <fieldset> 
-      <legend>label</legend> 
-
-      <span class="label prop">
-        <label title="a label displayed next to this iemgui">
-          label <input type="text" name="label" > <br>
-      </span>
-
-      <span class="x-offset prop">
-        <label title="horizontal offset (from the top-left corner of the object) for the label">
-          x offset <input type="text" name="x-offset" ></label>
-        <label title="vertical offset (from the top-left corner of the object) for the label">
-          y offset <input type="text" name="y-offset"></label> <br>
-      </span>
-
-      <span class="font-style prop">
-        <label title="which font to use to display the label">
-          font <input type="text" name="font-style"></label>
-        <label title="size of the label">size <input type="text" name="font-size"><label> <br>
-      </span>
-     </fieldset> 
-
-     <fieldset> 
-      <legend>colors</legend> 
-
-      <span class="background-color prop">
-        <label title="background color for the object">
-          background <input type="color" name="background-color"></label> <br>
-      </span>
-
-      <span class="foreground-color prop">
-        <label title="foreground color for the object">
-          foreground <input type="color" name="foreground-color"></label> <br>
-      </span>
-
-      <span class="label-color prop">
-        <label title="color of the label text">
-          label <input type="color" name="label-color"></title> <br>
-      </span>
-     </fieldset> 
-
-     <span class="prop">
-       <input type="hidden" name="minimum-size">
-       <input type="hidden" name="range-schedule">
-       <input type="hidden" name="hide-frame">
-     </span>
-
-     <div style="text-align: center">
-      <button type="button" onClick="ok()" title="Apply these settings and close the window">Ok</button>
-      <button type="button" onClick="apply()" title="Apply these settings without closing the window">Apply</button>
-      <button type="button" onClick="cancel()" title="Close window without applying any changes">Cancel</button>
-     </div>
-
- </form> 
-        
+    <div class="container">
+    <form> 
+      <fieldset> 
+        <legend data-i18n="iem.prop.heading.size"></legend> 
+
+        <table class="pairs">
+          <tr class="size prop hidden">
+            <td>
+              <label data-i18n="[title]iem.prop.size_tt">
+                <span data-i18n="iem.prop.size"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.size_tt">
+              <input type="text" name="size">
+            </td>
+          </tr>
+          <tr class="selection-size prop hidden">
+            <td>
+              <label data-i18n="[title]iem.select_size_tt">
+                <span data-i18n="iem.prop.select_size"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.select_size_tt">
+              <input type="text" name="selection-size">
+            </td>
+          </tr>
+          <tr class="number prop hidden">
+            <td>
+              <label data-i18n="[title]iem.prop.number_tt">
+                <span data-i18n="iem.prop.number"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.number_tt">
+              <input type="number" name="number">
+            </td>
+          </tr>
+          <tr class="nonzero-value prop hidden">
+            <td>
+              <label data-i18n="[title]iem.prop.nonzero_value_tt">
+                <span data-i18n="iem.prop.nonzero_value"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.nonzero_value_tt">
+              <input type="text" name="nonzero-value">
+            </td>
+          </tr>
+          <tr class="width prop hidden">
+            <td>
+              <label data-i18n="[title]iem.prop.width_tt"> 
+                <span data-i18n="iem.prop.width"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.width_tt">
+              <input type="text" name="width">
+            </td>
+            <td>
+              <label data-i18n="[title]iem.prop.height_tt">
+                <span data-i18n="iem.prop.height"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.height_tt">
+              <input type="text" name="height">
+            </td>
+          </tr>
+          <tr class="visible-width prop hidden">
+            <td>
+              <label data-i18n="[title]iem.prop.visible_width_tt">
+                <span data-i18n="iem.prop.visible_width"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.visible_width_tt">
+              <input type="text" name="visible-width">
+            </td>
+            <td>
+              <label data-i18n="iem.prop.visible_height">
+                <span data-i18n="iem.prop.visible_height"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.visible_height_tt">
+              <input type="text" name="visible-height">
+            </td>
+          </tr>
+          <tr class="minimum-range prop pair hidden">
+            <td>
+              <label data-i18n="[title]iem.prop.minimum_tt">
+                <span data-i18n="iem.prop.minimum"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.minimum_tt">
+              <input type="text" name="minimum-range">
+            </td>
+            <td>
+              <label data-i18n="[title]iem.prop.maximum_tt">
+                <span data-i18n="iem.prop.maximum"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.maximum_tt">
+              <input type="text" name="maximum-range">
+            </td>
+          </tr>
+          <tr class="flash-interrupt prop hidden">
+            <td>
+              <label data-i18n="[title]iem.prop.flash_interrupt_tt">
+                <span data-i18n="iem.prop.flash_interrupt"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.flash_interrupt_tt">
+              <input type="text" name="flash-interrupt">
+            </td>
+            <td>
+              <label data-i18n="[title]iem.prop.flash_hold_tt">
+                <span data-i18n="iem.prop.flash_hold"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.flash_hold_tt">
+              <input type="text" name="flash-hold">
+            </td>
+          </tr>
+          <tr class="log-height prop hidden">
+            <td></td><td></td>
+            <td>
+              <label data-i18n="[title]iem.prop.log_height_tt">
+                <span data-i18n="iem.prop.log_height"></span>
+              </label>
+            </td>
+            <td>
+              <input type="text" name="log-height">
+            </td>
+          </tr>
+        </table>
+
+        <div class="init prop hidden">
+          <label data-i18n="[title]iem.prop.init_tt">
+            <input type="checkbox" name="init" value="on">
+            <span data-i18n="iem.prop.init"></span>
+          </label>
+          <br>
+        </div>
+
+        <div class="vu-scale prop hidden">
+          <label data-i18n="[title]iem.prop.vu_scale_tt">
+            <span data-i18n="iem.prop.vu_scale"></span>
+            <input type="checkbox" name="vu-scale" value="on">
+          </label>
+          <br>
+        </div>
+
+        <div class="log-scaling prop hidden">
+          <label data-i18n="[title]iem.prop.log_scale_tt">
+            <input type="checkbox" name="log-scaling" value="on">
+            <span data-i18n="iem.prop.log_scale"></span>
+          </label>
+          <br>
+        </div>
+
+        <div class="steady-on-click prop hidden">
+          <label data-i18n="[title]iem.prop.steady_tt">
+            <input type="checkbox" name="steady-on-click" value="on">
+            <span data-i18n="iem.prop.steady"></span>
+          </label>
+          <br>
+        </div>
+      </fieldset> 
+
+      <fieldset> 
+        <legend data-i18n="iem.prop.heading.messages"></legend> 
+
+        <table>
+          <tr class="send-symbol prop hidden">
+            <td>
+              <label data-i18n="[title]iem.prop.send_tt">
+                <span data-i18n="iem.prop.send"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.send_tt">
+              <input type="text" name="send-symbol">
+            </td>
+            <td>
+          <tr class="receive-symbol prop hidden">
+            <td>
+              <label data-i18n="[title]iem.prop.receive_tt">
+                <span data-i18n="iem.prop.receive"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.receive_tt">
+              <input type="text" name="receive-symbol">
+            </td>
+            <td>
+          </tr>
+        </table>
+      </fieldset> 
+
+      <fieldset> 
+        <legend data-i18n="iem.prop.heading.label">wrong stuff</legend> 
+
+        <table class="pairs">
+          <tr class="label prop hidden">
+            <td>
+              <label data-i18n="[title]iem.prop.label_tt">
+                <span data-i18n="iem.prop.label"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.label_tt">
+              <input type="text" name="label">
+            </td>
+            <td>
+              <label data-i18n="[title]iem.prop.xoffset_tt">
+                <span data-i18n="iem.prop.xoffset"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.xoffset_tt">
+              <input type="text" name="x-offset">
+            </td>
+            <td>
+              <label data-i18n="[title]iem.prop.yoffset_tt">
+                <span data-i18n="iem.prop.yoffset"></span>
+              </label>
+            </td>
+            <td data-i18n="[title]iem.prop.yoffset_tt">
+              <input type="text" name="y-offset">
+            </td>
+          </tr>
+          <tr class="font-style prop hidden">
+            <td>
+              <label data-i18n="[title]iem.prop.font_tt">
+                <span data-i18n="iem.prop.font"></span>
+            </td>
+            <td data-i18n="[title]iem.prop.font_tt">
+              <select name="font-style">
+                <option>Font Number 0</option>
+                <option>Font #1</option>
+                <option>Font 2</option>
+              </select>
+            </td>
+            <td colspan="4">
+              <label data-i18n="[title]iem.prop.fontsize_tt">
+                <span data-i18n="iem.prop.fontsize"></span>
+                <input type="text" name="font-size">
+              <label>
+            </td>
+          </tr>
+        </table>
+      </fieldset> 
+
+      <fieldset> 
+      <legend data-i18n="iem.prop.heading.colors"></legend> 
+
+      <div class="background-color prop hidden">
+        <label data-i18n="[title]iem.prop.bgcolor_tt">
+          <input type="color" name="background-color">
+          <span data-i18n="iem.prop.bgcolor"></span>
+        </label>
+        <br>
+      </div>
+
+      <div class="foreground-color prop hidden">
+        <label data-i18n="[title]iem.prop.fgcolor_tt">
+          <input type="color" name="foreground-color">
+          <span data-i18n="iem.prop.fgcolor"></span>
+        </label>
+        <br>
+      </div>
+
+      <div class="label-color prop hidden">
+        <label data-i18n="[title]iem.prop.label_color_tt">
+          <input type="color" name="label-color">
+          <span data-i18n="iem.prop.label_color"></span>
+        </label>
+        <br>
+      </div>
+    </fieldset> 
+
+    <div class="prop hidden">
+      <input type="hidden" name="minimum-size">
+      <input type="hidden" name="range-schedule">
+      <input type="hidden" name="hide-frame">
+    </div>
+
+    <div class="submit_buttons">
+      <button type="button" onClick="ok()" data-i18n="[title]iem.prop.ok_tt">
+        <span data-i18n="iem.prop.ok"></span>
+      </button>
+      <button type="button" onClick="apply()" data-i18n="[title]iem.prop.apply_tt">
+        <span data-i18n="iem.prop.apply"></span>
+      </button>
+      <button type="button" onClick="cancel()" data-i18n="[title]iem.prop.cancel_tt">
+        <span data-i18n="iem.prop.cancel"></span>
+      </button>
+    </div>
+
+  </form> 
+  </div>      
 
   <script>
     'use strict';
     var nw = require('nw.gui'); 
     var pdgui = require('./pdgui.js');
 
+    // For translations
+    var l = pdgui.get_local_string;
+
     console.log("my working dire is " + pdgui.get_pwd());
 
     var pd_object_callback;
@@ -213,7 +342,9 @@ input[type="text"]{width:3em;}
             iemgui_verify_rng $id
             iemgui_sched_rng $id
             iemgui_clip_fontsize $id
-        */	
+        */
+
+
 
         var send_symbol = document.getElementsByName('send-symbol')[0].value;
         var receive_symbol = document.getElementsByName('receive-symbol')[0].value;
@@ -375,17 +506,59 @@ input[type="text"]{width:3em;}
         add_events(gfxstub);
         // not sure that we need this for properties windows
 //        pdgui.canvas_map(gfxstub);
+        translate_form();
         populate_form(attr_array);
+        // We don't turn on rendering of the "container" div until
+        // We've finished displaying all the spans and populating the
+        // labels and form elements.  That makes it more efficient and
+        // snappier, at least on older machines.
+        document.getElementsByClassName('container')[0].style.setProperty('display', 'inline');
 //        document.getElementsByClass("fumbles")[0].setAttribute('style', 'display: inline;');
     }
 
+function tr_text(id) {
+    var elem = document.getElementById('iem.prop.' + id);
+    elem.textContent = l('iem.prop.' + id);
+}
+
+// Stop-gap translator
+function translate_form() {
+    var i
+    var elements = document.querySelectorAll('[data-i18n]');
+    for (i = 0; i < elements.length; i++) {
+        var data = elements[i].dataset.i18n;
+        if (data.slice(0,7) === '[title]') {
+            elements[i].title = l(data.slice(7));
+        } else {
+            elements[i].textContent = l(data);
+        }
+    }
+}
+
 function populate_form(attr_array) {
+
+    // First, let's put the translated text for the form labels:
+
+//    tr_text('heading.size');
+//    tr_text('heading.messages');
+//    tr_text('heading.label');
+//    tr_text('heading.colors');
+//    tr_prop('width');
+//    tr_tooltip('width');
+
+//    var headings = ["size", "messages", "label", "colors"];
+//    for (var i = 0; i < headings.length; i++) {
+//        var str = "iem.prop.heading." + headings[i];
+//        var heading = document.getElementById(str);
+//        heading.textContent = l(str);
+//    }
+
     for(var i = 0; i < attr_array.length; i+=2) {
         // Unhide the span with the class with the same name as the id
         var prop_group = document.getElementsByClassName(attr_array[i])[0];
         if (prop_group !== undefined) {
             console.log("the thing here is " + attr_array[i]);
-            prop_group.style.setProperty('display', 'inline');
+            prop_group.classList.remove('hidden');
         } else {
             pdgui.gui_post("Error: couldn't find iemgui prop group for " + attr_array[i]);
         }
-- 
GitLab