diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
index e79b841c9967773c6d5b07b1d12dc1fc505e2fc1..e9180bef40b3539b9c0e66f1739090de18ad205a 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -803,7 +803,7 @@ public class I2PSnarkServlet extends Default {
         
         out.write("<td align=\"center\" class=\"snarkTorrentETA " + rowClass + "\">");
         if(isRunning && remainingSeconds > 0)
-            out.write(DataHelper.formatDuration(remainingSeconds*1000)); // (eta 6h)
+            out.write(DataHelper.formatDuration2(remainingSeconds*1000)); // (eta 6h)
         out.write("</td>\n\t");
         out.write("<td align=\"right\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
         if (remaining > 0)
diff --git a/apps/routerconsole/java/bundle-messages.sh b/apps/routerconsole/java/bundle-messages.sh
index 548f7f768a7b08c5b14fe4e787f5fad36fbeb150..dd422bf064c0251f0838e7acc51a3f7d31a60b3e 100755
--- a/apps/routerconsole/java/bundle-messages.sh
+++ b/apps/routerconsole/java/bundle-messages.sh
@@ -32,8 +32,9 @@ then
 	sed 's/..,\(..*\)/_("\1");/' $CFILE >> $JFILE
 fi
 
-# list specific files in router/ here, so we don't scan the whole tree
+# list specific files in core/ and router/ here, so we don't scan the whole tree
 ROUTERFILES="\
+   ../../../core/java/src/net/i2p/data/DataHelper.java \
    ../../../router/java/src/net/i2p/router/RouterThrottleImpl.java \
    ../../../router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java \
    ../../../router/java/src/net/i2p/router/transport/TransportManager.java \
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java
index aacdfc91a959fd956f724a262d496d7cddbc2fca..ccd96e7a0eb4def70a6dee669d897ea152fc0d12 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java
@@ -62,12 +62,12 @@ public class ConfigRestartBean {
             buf.append("</b></center>");
         } else if (shuttingDown) {
             buf.append("<center><b>");
-            buf.append(_("Shutdown in {0}", DataHelper.formatDuration(timeRemaining), ctx));
+            buf.append(_("Shutdown in {0}", DataHelper.formatDuration2(timeRemaining), ctx));
             buf.append("</b></center><br>");
             buttons(ctx, buf, urlBase, systemNonce, SET1);
         } else if (restarting) {
             buf.append("<center><b>");
-            buf.append(_("Restart in {0}", DataHelper.formatDuration(timeRemaining), ctx));
+            buf.append(_("Restart in {0}", DataHelper.formatDuration2(timeRemaining), ctx));
             buf.append("</b></center><br>");
             buttons(ctx, buf, urlBase, systemNonce, SET2);
         } else {
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java
index d20524bfb568c89c380ba242b85b5460ac7ee0f1..72acbb5404aa56ba05dca95e94d8c22348c5ff0c 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java
@@ -130,7 +130,7 @@ public class ConfigUpdateHandler extends FormHandler {
             try { oldFreq = Long.parseLong(oldFreqStr); } catch (NumberFormatException nfe) {}
         if (_refreshFrequency != oldFreq) {
             _context.router().setConfigSetting(PROP_REFRESH_FREQUENCY, ""+_refreshFrequency);
-            addFormNotice(_("Updating refresh frequency to") + " " + DataHelper.formatDuration(_refreshFrequency));
+            addFormNotice(_("Updating refresh frequency to") + " " + DataHelper.formatDuration2(_refreshFrequency));
         }
 
         if ( (_updatePolicy != null) && (_updatePolicy.length() > 0) ) {
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java
index 520c13104e44cde2a304767a34436d146f21d991..2e03ad93f8bf2942331ed700a758c5c41c9f530b 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHelper.java
@@ -87,7 +87,7 @@ public class ConfigUpdateHelper extends HelperBase {
             if (PERIODS[i] == -1)
                 buf.append("\">" + _("Never") + "</option>\n");
             else
-                buf.append("\">" + _("Every") + " ").append(DataHelper.formatDuration(PERIODS[i])).append("</option>\n");
+                buf.append("\">" + _("Every") + " ").append(DataHelper.formatDuration2(PERIODS[i])).append("</option>\n");
         }
         buf.append("</select>\n");
         return buf.toString();
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java
index 94dc08b98aebcedf84b978bcfcec757751dd00a8..8fc78e7900a6e5e1afaa6d9384ecda9488062380 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/GraphHelper.java
@@ -93,7 +93,7 @@ public class GraphHelper extends HelperBase {
                 SummaryListener lsnr = (SummaryListener)iter.next();
                 Rate r = lsnr.getRate();
                 // e.g. "statname for 60m"
-                String title = _("{0} for {1}", r.getRateStat().getName(), DataHelper.formatDuration(_periodCount * r.getPeriod()));
+                String title = _("{0} for {1}", r.getRateStat().getName(), DataHelper.formatDuration2(_periodCount * r.getPeriod()));
                 _out.write("<a href=\"viewstat.jsp?stat="
                            + r.getRateStat().getName() 
                            + "&amp;showEvents=" + _showEvents
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
index a2c5632fa8e0e4d57e750d3f60447b3469f6d605..d2b0a17e903c17308b8d077904826c3f1f66814f 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java
@@ -150,9 +150,9 @@ public class NetDbRenderer {
             buf.append(")</b><br>\n");
             long exp = ls.getEarliestLeaseDate()-now;
             if (exp > 0)
-                buf.append(_("Expires in {0}", DataHelper.formatDuration(exp))).append("<br>\n");
+                buf.append(_("Expires in {0}", DataHelper.formatDuration2(exp))).append("<br>\n");
             else
-                buf.append(_("Expired {0} ago", DataHelper.formatDuration(0-exp))).append("<br>\n");
+                buf.append(_("Expired {0} ago", DataHelper.formatDuration2(0-exp))).append("<br>\n");
             if (debug) {
                 buf.append("RAP? " + ls.getReceivedAsPublished() + ' ');
                 buf.append("RAR? " + ls.getReceivedAsReply() + ' ');
@@ -352,13 +352,13 @@ public class NetDbRenderer {
         long age = _context.clock().now() - info.getPublished();
         if (isUs && _context.router().isHidden()) {
             buf.append("<b>").append(_("Hidden")).append(", ").append(_("Updated")).append(":</b> ")
-               .append(_("{0} ago", DataHelper.formatDuration(age))).append("<br>\n");
+               .append(_("{0} ago", DataHelper.formatDuration2(age))).append("<br>\n");
         } else if (age > 0) {
             buf.append("<b>").append(_("Published")).append(":</b> ")
-               .append(_("{0} ago", DataHelper.formatDuration(age))).append("<br>\n");
+               .append(_("{0} ago", DataHelper.formatDuration2(age))).append("<br>\n");
         } else {
             // shouldnt happen
-            buf.append("<b>" + _("Published") + ":</b> in ").append(DataHelper.formatDuration(0-age)).append("???<br>\n");
+            buf.append("<b>" + _("Published") + ":</b> in ").append(DataHelper.formatDuration2(0-age)).append("???<br>\n");
         }
         buf.append("<b>" + _("Address(es)") + ":</b> ");
         String country = _context.commSystem().getCountry(info.getIdentity().getHash());
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
index df576254c91e9bdae4e15cd2172dc29d8615899d..71cb982c9ab467ecad11f56b63e277fd00dde056 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java
@@ -80,13 +80,13 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
          long now = _context.clock().now();
          if (_lastUpdated > 0) {
              buf.append(Messages.getString("News last updated {0} ago.",
-                                           DataHelper.formatDuration(now - _lastUpdated),
+                                           DataHelper.formatDuration2(now - _lastUpdated),
                                            _context))
                 .append('\n');
          }
          if (_lastFetch > _lastUpdated) {
              buf.append(Messages.getString("News last checked {0} ago.",
-                                           DataHelper.formatDuration(now - _lastFetch),
+                                           DataHelper.formatDuration2(now - _lastFetch),
                                            _context));
          }
          return buf.toString();
@@ -136,7 +136,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
                 return true;
             } else {
                 if (_log.shouldLog(Log.DEBUG))
-                    _log.debug("Last fetched " + DataHelper.formatDuration(_context.clock().now() - _lastFetch) + " ago");
+                    _log.debug("Last fetched " + DataHelper.formatDuration2(_context.clock().now() - _lastFetch) + " ago");
                 return false;
             }
         } catch (NumberFormatException nfe) {
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java
index f1824930b7df195f531d92d5003196ebf60ba557..7265578730beb42756aede5e933fada97b4b2bc0 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ProfileOrganizerRenderer.java
@@ -217,26 +217,26 @@ class ProfileOrganizerRenderer {
             buf.append("<td align=\"right\">").append(num(prof.getIntegrationValue())).append("</td>");
             long time;
             time = now - prof.getLastHeardAbout();
-            buf.append("<td align=\"right\">").append(DataHelper.formatDuration(time)).append("</td>");
+            buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(time)).append("</td>");
             time = now - prof.getLastHeardFrom();
-            buf.append("<td align=\"right\">").append(DataHelper.formatDuration(time)).append("</td>");
+            buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(time)).append("</td>");
             time = now - prof.getLastSendSuccessful();
-            buf.append("<td align=\"right\">").append(DataHelper.formatDuration(time)).append("</td>");
+            buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(time)).append("</td>");
             time = now - prof.getLastSendFailed();
-            buf.append("<td align=\"right\">").append(DataHelper.formatDuration(time)).append("</td>");
+            buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(time)).append("</td>");
             buf.append("<td align=\"right\">").append(avg(prof, 10*60*1000l)).append("</td>");
             buf.append("<td align=\"right\">").append(avg(prof, 60*60*1000l)).append("</td>");
             buf.append("<td align=\"right\">").append(avg(prof, 24*60*60*1000l)).append("</td>");
             DBHistory dbh = prof.getDBHistory();
             if (dbh != null) {
                 time = now - dbh.getLastLookupSuccessful();
-                buf.append("<td align=\"right\">").append(DataHelper.formatDuration(time)).append("</td>");
+                buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(time)).append("</td>");
                 time = now - dbh.getLastLookupFailed();
-                buf.append("<td align=\"right\">").append(DataHelper.formatDuration(time)).append("</td>");
+                buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(time)).append("</td>");
                 time = now - dbh.getLastStoreSuccessful();
-                buf.append("<td align=\"right\">").append(DataHelper.formatDuration(time)).append("</td>");
+                buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(time)).append("</td>");
                 time = now - dbh.getLastStoreFailed();
-                buf.append("<td align=\"right\">").append(DataHelper.formatDuration(time)).append("</td>");
+                buf.append("<td align=\"right\">").append(DataHelper.formatDuration2(time)).append("</td>");
                 buf.append("<td align=\"right\">").append(davg(dbh, 60*60*1000l)).append("</td>");
                 buf.append("<td align=\"right\">").append(davg(dbh, 24*60*60*1000l)).append("</td>");
             } else {
@@ -323,7 +323,7 @@ class ProfileOrganizerRenderer {
             if (c == 0)
                 return _(NA);
             double d = r.getCurrentTotalValue() + r.getLastTotalValue();
-            return Math.round(d/c) + "ms";
+            return DataHelper.formatDuration2(Math.round(d/c));
     }
 
     private String davg (DBHistory dbh, long rate) {
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ShitlistRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/ShitlistRenderer.java
index 48af67857a049dd109af3a45585371cc73f4c045..f0b9c910bb25b5ab6964b78a6fd6b9f42bc45c2b 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ShitlistRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ShitlistRenderer.java
@@ -52,7 +52,7 @@ public class ShitlistRenderer {
             buf.append("<li>").append(_context.commSystem().renderPeerHTML(key));
             buf.append(' ');
             long expires = entry.expireOn-_context.clock().now();
-            String expireString = DataHelper.formatDuration(expires);
+            String expireString = DataHelper.formatDuration2(expires);
             if (expires < 5l*24*60*60*1000)
                 buf.append(_("Temporary ban expiring in {0}", expireString));
             else
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java
index 8447db4127152e23a95e8f58dd88a0ce5dfecb7b..50fe1f54dfdae1b5962cd6a92d5cd1bf04a6b15c 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java
@@ -366,7 +366,7 @@ public class SummaryBarRenderer {
            .append(_("Used"))
            .append(":</b></td><td align=\"right\">")
            .append(_helper.getInboundTransferred())
-           .append(" / ")
+           .append("&thinsp;/&thinsp;")
            .append(_helper.getOutboundTransferred())
            .append("</td></tr></table>\n" +
 
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
index d5e64a1af41e8861043d4bc36db231a4c98c5fd3..5f2cb891b7e92312dc49ce46a39663b79f7b1faa 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
@@ -60,7 +60,7 @@ public class SummaryHelper extends HelperBase {
         if (router == null) 
             return "[not up]";
         else
-            return DataHelper.formatDuration(router.getUptime());
+            return DataHelper.formatDuration2(router.getUptime());
     }
     
 /**
@@ -74,7 +74,7 @@ public class SummaryHelper extends HelperBase {
         long diff = Math.abs(ms);
         if (diff < 3000)
             return "";
-        return " (" + DataHelper.formatDuration(diff) + " " + _("skew") + ")";
+        return " (" + DataHelper.formatDuration2(diff) + " " + _("skew") + ")";
     }
 **/
     
@@ -105,7 +105,7 @@ public class SummaryHelper extends HelperBase {
         long skew = _context.commSystem().getFramedAveragePeerClockSkew(33);
         // Display the actual skew, not the offset
         if (Math.abs(skew) > 30*1000)
-            return _("ERR-Clock Skew of {0}", DataHelper.formatDuration(Math.abs(skew)));
+            return _("ERR-Clock Skew of {0}", DataHelper.formatDuration2(Math.abs(skew)));
         if (_context.router().isHidden())
             return _("Hidden");
 
@@ -321,7 +321,7 @@ public class SummaryHelper extends HelperBase {
             fmt = new DecimalFormat("#0.0");
         else
             fmt = new DecimalFormat("#0.00");
-        return fmt.format(in) + " / " + fmt.format(out) +
+        return fmt.format(in) + "&thinsp;/&thinsp;" + fmt.format(out) + "&nbsp;" +
                (mega ? 'M' : 'K');
     }
 
@@ -336,7 +336,7 @@ public class SummaryHelper extends HelperBase {
         
         long received = _context.bandwidthLimiter().getTotalAllocatedInboundBytes();
 
-        return DataHelper.formatSize(received) + 'B';
+        return DataHelper.formatSize2(received) + 'B';
     }
     
     /**
@@ -349,7 +349,7 @@ public class SummaryHelper extends HelperBase {
             return "0";
         
         long sent = _context.bandwidthLimiter().getTotalAllocatedOutboundBytes();
-        return DataHelper.formatSize(sent) + 'B';
+        return DataHelper.formatSize2(sent) + 'B';
     }
     
     /**
@@ -389,7 +389,7 @@ public class SummaryHelper extends HelperBase {
                     long timeToExpire = ls.getEarliestLeaseDate() - _context.clock().now();
                     if (timeToExpire < 0) {
                         // red or yellow light                 
-                        buf.append("<td><img src=\"/themes/console/images/local_inprogress.png\" alt=\"").append(_("Rebuilding")).append("&hellip;\" title=\"").append(_("Leases expired")).append(" ").append(DataHelper.formatDuration(0-timeToExpire));
+                        buf.append("<td><img src=\"/themes/console/images/local_inprogress.png\" alt=\"").append(_("Rebuilding")).append("&hellip;\" title=\"").append(_("Leases expired")).append(" ").append(DataHelper.formatDuration2(0-timeToExpire));
                         buf.append(" ").append(_("ago")).append(". ").append(_("Rebuilding")).append("&hellip;\"></td></tr>\n");                    
                     } else {
                         // green light 
@@ -510,10 +510,10 @@ public class SummaryHelper extends HelperBase {
      */
     public String getJobLag() { 
         if (_context == null) 
-            return "0ms";
+            return "0 ms";
         
         Rate lagRate = _context.statManager().getRate("jobQueue.jobLag").getRate(60*1000);
-        return ((int)lagRate.getAverageValue()) + "ms";
+        return DataHelper.formatDuration2((long)lagRate.getAverageValue());
     }
  
     /**
@@ -523,9 +523,9 @@ public class SummaryHelper extends HelperBase {
      */   
     public String getMessageDelay() { 
         if (_context == null) 
-            return "0ms";
+            return "0 ms";
         
-        return _context.throttle().getMessageDelay() + "ms";
+        return DataHelper.formatDuration2(_context.throttle().getMessageDelay());
     }
     
     /**
@@ -535,9 +535,9 @@ public class SummaryHelper extends HelperBase {
      */
     public String getTunnelLag() { 
         if (_context == null) 
-            return "0ms";
+            return "0 ms";
         
-        return _context.throttle().getTunnelLag() + "ms";
+        return DataHelper.formatDuration2(_context.throttle().getTunnelLag());
     }
     
     public String getTunnelStatus() { 
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java
index f26426edca2435b1f62c94280ea6f7a4da96e33c..5e7545ec2485369b05cf24a44de803d08f744391 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryRenderer.java
@@ -78,7 +78,12 @@ class SummaryRenderer {
                 def.setBaseValue(1024);
             if (!hideTitle) {
                 String title;
-                String p = DataHelper.formatDuration(_listener.getRate().getPeriod());
+                String p;
+                // we want the formatting and translation of formatDuration2(), except not zh, and not the &nbsp;
+                if ("zh".equals(Messages.getLanguage(_context)))
+                    p = DataHelper.formatDuration(_listener.getRate().getPeriod());
+                else
+                    p = DataHelper.formatDuration2(_listener.getRate().getPeriod()).replace("&nbsp;", " ");
                 if (showEvents)
                     // Note to translators: all runtime zh translation disabled in this file, no font available in RRD
                     title = name + ' ' + _("events in {0}", p);
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java
index e9d7d1da68033f9e3385b93cbd54d78aa2daa60d..3d52c653fa313ce673995bb7d4df86df3bf2fad4 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/TunnelRenderer.java
@@ -102,7 +102,7 @@ public class TunnelRenderer {
                 out.write("<td class=\"cells\">&nbsp;</td>");
             long timeLeft = cfg.getExpiration()-_context.clock().now();
             if (timeLeft > 0)
-                out.write("<td class=\"cells\" align=\"center\">" + DataHelper.formatDuration(timeLeft) + "</td>");
+                out.write("<td class=\"cells\" align=\"center\">" + DataHelper.formatDuration2(timeLeft) + "</td>");
             else
                 out.write("<td class=\"cells\" align=\"center\">(" + _("grace period") + ")</td>");
             out.write("<td class=\"cells\" align=\"center\">" + cfg.getProcessedMessagesCount() + " KB</td>");
@@ -175,7 +175,7 @@ public class TunnelRenderer {
                 out.write("<tr> <td class=\"cells\" align=\"center\"><img src=\"/themes/console/images/inbound.png\" alt=\"Inbound\" title=\"Inbound\"></td>");
             else
                 out.write("<tr> <td class=\"cells\" align=\"center\"><img src=\"/themes/console/images/outbound.png\" alt=\"Outbound\" title=\"Outbound\"></td>");
-            out.write(" <td class=\"cells\" align=\"center\">" + DataHelper.formatDuration(timeLeft) + "</td>\n");
+            out.write(" <td class=\"cells\" align=\"center\">" + DataHelper.formatDuration2(timeLeft) + "</td>\n");
             out.write(" <td class=\"cells\" align=\"center\">" + info.getProcessedMessagesCount() + " KB</td>\n");
             for (int j = 0; j < info.getLength(); j++) {
                 Hash peer = info.getPeer(j);
diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java
index 5b0ba4b199dda8ad708ae0488744a7678d599e15..5ab6d32fbfa362c802a9f42d0d980d7c79048feb 100644
--- a/core/java/src/net/i2p/data/DataHelper.java
+++ b/core/java/src/net/i2p/data/DataHelper.java
@@ -37,11 +37,13 @@ import java.util.Properties;
 import java.util.TreeMap;
 import java.util.zip.Deflater;
 
+import net.i2p.I2PAppContext;
 import net.i2p.util.ByteCache;
 import net.i2p.util.OrderedProperties;
 import net.i2p.util.ReusableGZIPInputStream;
 import net.i2p.util.ReusableGZIPOutputStream;
 import net.i2p.util.SecureFileOutputStream;
+import net.i2p.util.Translate;
 
 /**
  * Defines some simple IO routines for dealing with marshalling data structures
@@ -543,9 +545,11 @@ public class DataHelper {
         else
             return toLong(DATE_LENGTH, date.getTime());
     }
+
     public static void toDate(byte target[], int offset, long when) throws IllegalArgumentException {
         toLong(target, offset, DATE_LENGTH, when);
     }
+
     public static Date fromDate(byte src[], int offset) throws DataFormatException {
         if ( (src == null) || (offset + DATE_LENGTH > src.length) )
             throw new DataFormatException("Not enough data to read a date");
@@ -1038,6 +1042,9 @@ public class DataHelper {
         return rv;
     }
 
+    /**
+     *  NOTE: formatDuration2() recommended in most cases for readability
+     */
     public static String formatDuration(long ms) {
         if (ms < 5 * 1000) {
             return ms + "ms";
@@ -1054,8 +1061,68 @@ public class DataHelper {
         }
     }
     
+    /**
+     * Like formatDuration but with a non-breaking space after the number,
+     * 0 is unitless, and the unit is translated.
+     * This seems consistent with most style guides out there.
+     * Use only in HTML.
+     * Thresholds are a little lower than in formatDuration() also,
+     * as precision is less important in the GUI than in logging.
+     * @since 0.8.2
+     */
+    public static String formatDuration2(long ms) {
+        String t;
+        if (ms == 0) {
+            return "0";
+        } else if (ms < 3 * 1000) {
+            // NOTE TO TRANSLATORS: Feel free to translate all these as you see fit, there are several options...
+            // spaces or not, '.' or not, plural or not. Try not to make it too long, it is used in
+            // a lot of tables.
+            // milliseconds
+            // Note to translators, may be negative or zero, 2999 maximum.
+            // {0,number,####} prevents 1234 from being output as 1,234 in the English locale.
+            // If you want the digit separator in your locale, translate as {0}.
+            // alternates: msec, msecs
+            t = ngettext("1 ms", "{0,number,####} ms", (int) ms);
+        } else if (ms < 2 * 60 * 1000) {
+            // seconds
+            // Note to translators: quantity will always be greater than one.
+            // alternates: secs, sec. 'seconds' is probably too long.
+            t = ngettext("1 sec", "{0} sec", (int) (ms / 1000));
+        } else if (ms < 120 * 60 * 1000) {
+            // minutes
+            // Note to translators: quantity will always be greater than one.
+            // alternates: mins, min. 'minutes' is probably too long.
+            t = ngettext("1 min", "{0} min", (int) (ms / (60 * 1000)));
+        } else if (ms < 2 * 24 * 60 * 60 * 1000) {
+            // hours
+            // Note to translators: quantity will always be greater than one.
+            // alternates: hrs, hr., hrs.
+            t = ngettext("1 hour", "{0} hours", (int) (ms / (60 * 60 * 1000)));
+        } else if (ms > 1000l * 24l * 60l * 60l * 1000l) {
+            return _("n/a");
+        } else {
+            // days
+            // Note to translators: quantity will always be greater than one.
+            t = ngettext("1 day", "{0} days", (int) (ms / (24 * 60 * 60 * 1000)));
+        }
+        // do it here to keep &nbsp; out of the tags for translator sanity
+        return t.replace(" ", "&nbsp;");
+    }
+    
+    private static final String BUNDLE_NAME = "net.i2p.router.web.messages";
+
+    private static String _(String key) {
+        return Translate.getString(key, I2PAppContext.getGlobalContext(), BUNDLE_NAME);
+    }
+
+    private static String ngettext(String s, String p, int n) {
+        return Translate.getString(n, s, p, I2PAppContext.getGlobalContext(), BUNDLE_NAME);
+    }
+
     /**
      * Caller should append 'B' or 'b' as appropriate
+     * NOTE: formatDuration2() recommended in most cases for readability
      */
     public static String formatSize(long bytes) {
         double val = bytes;
@@ -1079,6 +1146,7 @@ public class DataHelper {
     
     /**
      * Like formatSize but with a non-breaking space after the number
+     * This seems consistent with most style guides out there.
      * Use only in HTML
      * @since 0.7.14
      */
diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
index 0d6f4561d383e5bd14578772564875e007846fbd..3d6d91f5167f1bb3e2509418b44ecf4494289f7e 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java
@@ -709,7 +709,7 @@ public class NTCPTransport extends TransportImpl {
         StringBuilder buf = new StringBuilder(512);
         buf.append("<h3 id=\"ntcpcon\">").append(_("NTCP connections")).append(": ").append(peers.size());
         buf.append(". ").append(_("Limit")).append(": ").append(getMaxConnections());
-        buf.append(". ").append(_("Timeout")).append(": ").append(DataHelper.formatDuration(_pumper.getIdleTimeout()));
+        buf.append(". ").append(_("Timeout")).append(": ").append(DataHelper.formatDuration2(_pumper.getIdleTimeout()));
         buf.append(".</h3>\n" +
                    "<table>\n" +
                    "<tr><th><a href=\"#def.peer\">").append(_("Peer")).append("</a></th>" +
@@ -739,16 +739,16 @@ public class NTCPTransport extends TransportImpl {
             else
                 buf.append("<img src=\"/themes/console/images/outbound.png\" alt=\"Outbound\" title=\"").append(_("Outbound")).append("\"/>");
             buf.append("</td><td class=\"cells\" align=\"right\">");
-            buf.append(con.getTimeSinceReceive()/1000);
-            buf.append("s / ").append(con.getTimeSinceSend()/1000);
-            buf.append("s</td><td class=\"cells\" align=\"right\">");
+            buf.append(DataHelper.formatDuration2(con.getTimeSinceReceive()));
+            buf.append("&thinsp;/&thinsp;").append(DataHelper.formatDuration2(con.getTimeSinceSend()));
+            buf.append("</td><td class=\"cells\" align=\"right\">");
             if (con.getTimeSinceReceive() < 10*1000) {
                 buf.append(formatRate(con.getRecvRate()/1024));
                 bpsRecv += con.getRecvRate();
             } else {
                 buf.append(formatRate(0));
             }
-            buf.append(" / ");
+            buf.append("&thinsp;/&thinsp;");
             if (con.getTimeSinceSend() < 10*1000) {
                 buf.append(formatRate(con.getSendRate()/1024));
                 bpsSend += con.getSendRate();
@@ -756,11 +756,11 @@ public class NTCPTransport extends TransportImpl {
                 buf.append(formatRate(0));
             }
             //buf.append(" K/s");
-            buf.append("</td><td class=\"cells\" align=\"right\">").append(DataHelper.formatDuration(con.getUptime()));
+            buf.append("</td><td class=\"cells\" align=\"right\">").append(DataHelper.formatDuration2(con.getUptime()));
             totalUptime += con.getUptime();
             offsetTotal = offsetTotal + con.getClockSkew();
-            buf.append("</td><td class=\"cells\" align=\"right\">").append(con.getClockSkew());
-            buf.append("s</td><td class=\"cells\" align=\"right\">").append(con.getMessagesSent());
+            buf.append("</td><td class=\"cells\" align=\"right\">").append(DataHelper.formatDuration2(1000 * con.getClockSkew()));
+            buf.append("</td><td class=\"cells\" align=\"right\">").append(con.getMessagesSent());
             totalSend += con.getMessagesSent();
             buf.append("</td><td class=\"cells\" align=\"right\">").append(con.getMessagesReceived());
             totalRecv += con.getMessagesReceived();
@@ -785,9 +785,9 @@ public class NTCPTransport extends TransportImpl {
         if (!peers.isEmpty()) {
 //            buf.append("<tr> <td colspan=\"11\"><hr></td></tr>\n");
             buf.append("<tr class=\"tablefooter\"><td align=\"center\"><b>").append(peers.size()).append(' ').append(_("peers")).append("</b></td><td>&nbsp;</td><td>&nbsp;");
-            buf.append("</td><td align=\"center\"><b>").append(formatRate(bpsRecv/1024)).append("/").append(formatRate(bpsSend/1024)).append("</b>");
-            buf.append("</td><td align=\"center\"><b>").append(DataHelper.formatDuration(totalUptime/peers.size()));
-            buf.append("</b></td><td align=\"center\"><b>").append((!peers.isEmpty()) ? DataHelper.formatDuration(offsetTotal*1000/peers.size()) : "0ms");
+            buf.append("</td><td align=\"center\"><b>").append(formatRate(bpsRecv/1024)).append("&thinsp;/&thinsp;").append(formatRate(bpsSend/1024)).append("</b>");
+            buf.append("</td><td align=\"center\"><b>").append(DataHelper.formatDuration2(totalUptime/peers.size()));
+            buf.append("</b></td><td align=\"center\"><b>").append(DataHelper.formatDuration2(offsetTotal*1000/peers.size()));
             buf.append("</b></td><td align=\"center\"><b>").append(totalSend).append("</b></td><td align=\"center\"><b>").append(totalRecv);
             buf.append("</b></td><td>&nbsp;</td><td>&nbsp;</td></tr>\n");
         }
diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
index 70aaa1ecc9f10d6d2042b9523cbe02d7509c9c5e..f0e9c9e2e921c3c6a0393397ca0b26dee62a3fbc 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
@@ -1882,7 +1882,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
         StringBuilder buf = new StringBuilder(512);
         buf.append("<h3 id=\"udpcon\">").append(_("UDP connections")).append(": ").append(peers.size());
         buf.append(". ").append(_("Limit")).append(": ").append(getMaxConnections());
-        buf.append(". ").append(_("Timeout")).append(": ").append(DataHelper.formatDuration(_expireTimeout));
+        buf.append(". ").append(_("Timeout")).append(": ").append(DataHelper.formatDuration2(_expireTimeout));
         buf.append(".</h3>\n");
         buf.append("<table>\n");
         buf.append("<tr><th class=\"smallhead\" nowrap><a href=\"#def.peer\">").append(_("Peer")).append("</a><br>");
@@ -1984,17 +1984,17 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
             if (idleOut < 0) idleOut = 0;
             
             buf.append("<td class=\"cells\" align=\"right\">");
-            buf.append(idleIn);
-            buf.append("s / ");
-            buf.append(idleOut);
-            buf.append("s</td>");
+            buf.append(DataHelper.formatDuration2(1000 * idleIn));
+            buf.append("&thinsp/&thinsp;");
+            buf.append(DataHelper.formatDuration2(1000 * idleOut));
+            buf.append("</td>");
  
             int recvBps = (idleIn > 2 ? 0 : peer.getReceiveBps());
             int sendBps = (idleOut > 2 ? 0 : peer.getSendBps());
             
             buf.append("<td class=\"cells\" align=\"right\" nowrap>");
             buf.append(formatKBps(recvBps));
-            buf.append(" / ");
+            buf.append("&thinsp;/&thinsp;");
             buf.append(formatKBps(sendBps));
             //buf.append(" K/s");
             //buf.append(formatKBps(peer.getReceiveACKBps()));
@@ -2006,12 +2006,12 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
             long uptime = now - peer.getKeyEstablishedTime();
             
             buf.append("<td class=\"cells\" align=\"right\">");
-            buf.append(DataHelper.formatDuration(uptime));
+            buf.append(DataHelper.formatDuration2(uptime));
             buf.append("</td>");
             
             buf.append("<td class=\"cells\" align=\"right\">");
-            buf.append(peer.getClockSkew() / 1000);
-            buf.append("s</td>");
+            buf.append(DataHelper.formatDuration2(peer.getClockSkew()));
+            buf.append("</td>");
             offsetTotal = offsetTotal + peer.getClockSkew();
 
             long sendWindow = peer.getSendWindowBytes();
@@ -2019,9 +2019,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
             buf.append("<td class=\"cells\" align=\"right\">");
             buf.append(sendWindow/1024);
             buf.append("K");
-            buf.append(" / ").append(peer.getConcurrentSends());
-            buf.append(" / ").append(peer.getConcurrentSendWindow());
-            buf.append(" / ").append(peer.getConsecutiveSendRejections());
+            buf.append("&thinsp;/&thinsp;").append(peer.getConcurrentSends());
+            buf.append("&thinsp;/&thinsp;").append(peer.getConcurrentSendWindow());
+            buf.append("&thinsp;/&thinsp;").append(peer.getConsecutiveSendRejections());
             buf.append("</td>");
 
             buf.append("<td class=\"cells\" align=\"right\">");
@@ -2044,7 +2044,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
             buf.append("</td>");
             
             buf.append("<td class=\"cells\" align=\"right\">");
-            buf.append(peer.getMTU()).append(" / ").append(peer.getReceiveMTU());
+            buf.append(peer.getMTU()).append("&thinsp;/&thinsp;").append(peer.getReceiveMTU());
             
             //.append('/');
             //buf.append(peer.getMTUIncreases()).append('/');
@@ -2104,10 +2104,12 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
 //        buf.append("<tr><td colspan=\"16\"><hr></td></tr>\n");
         buf.append("<tr class=\"tablefooter\"> <td colspan=\"3\" align=\"left\"><b>").append(_("SUMMARY")).append("</b></td>" +
                    "<td align=\"center\" nowrap><b>");
-        buf.append(formatKBps(bpsIn)).append(" / ").append(formatKBps(bpsOut));
+        buf.append(formatKBps(bpsIn)).append("thinsp;/&thinsp;").append(formatKBps(bpsOut));
+        long x = numPeers > 0 ? uptimeMsTotal/numPeers : 0;
         buf.append("</b></td>" +
-                   "<td align=\"center\"><b>").append(numPeers > 0 ? DataHelper.formatDuration(uptimeMsTotal/numPeers) : "0s");
-        buf.append("</b></td><td align=\"center\"><b>").append(numPeers > 0 ? DataHelper.formatDuration(offsetTotal/numPeers) : "0ms").append("</b></td>\n" +
+                   "<td align=\"center\"><b>").append(DataHelper.formatDuration2(x));
+        x = numPeers > 0 ? offsetTotal/numPeers : 0;
+        buf.append("</b></td><td align=\"center\"><b>").append(DataHelper.formatDuration2(x)).append("</b></td>\n" +
                    "<td align=\"center\"><b>");
         buf.append(numPeers > 0 ? cwinTotal/(numPeers*1024) + "K" : "0K");
         buf.append("</b></td><td>&nbsp;</td>\n" +