diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
index f73874d91a11ea81eba3ea65585205b6787b0632..76b8ca08d2abc282955d1fec7f7cbb97745455f6 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java
@@ -1428,16 +1428,20 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
         }
     }
 
-    /** @since 0.8.7 */
+    /**
+     * @param destination the hostname
+     * @since 0.8.7
+     */
     private void writeHelperSaveForm(OutputStream outs, String destination, String ahelperKey,
                                      String targetRequest, String referer) throws IOException {
         if(outs == null)
             return;
+        String idn = decodeIDNHost(destination);
         Writer out = new BufferedWriter(new OutputStreamWriter(outs, "UTF-8"));
         String header = getErrorPage("ahelper-new", ERR_AHELPER_NEW);
         out.write(header);
         out.write("<table id=\"proxyNewHost\">\n<tr><td align=\"right\">" + _t("Host") +
-                "</td><td>" + destination + "</td></tr>\n");
+                "</td><td>" + idn + "</td></tr>\n");
         try {
             String b32 = Base32.encode(SHA256Generator.getInstance().calculateHash(Base64.decode(ahelperKey)).getData());
             out.write("<tr><td align=\"right\">" + _t("Base32") + "</td>" +
@@ -1450,7 +1454,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
 
                 // FIXME if there is a query remaining it is lost
                 "<form method=\"GET\" action=\"" + targetRequest + "\">\n" +
-                "<h4>" + _t("Continue to {0} without saving", destination) + "</h4>\n<p>" +
+                "<h4>" + _t("Continue to {0} without saving", idn) + "</h4>\n<p>" +
                 _t("You can browse to the site without saving it to the address book. The address will be remembered until you restart your I2P router.") +
                 "</p>\n<div class=\"formaction\"><button type=\"submit\" class=\"go\">" + _t("Continue without saving") + "</button></div>" + "\n</form>\n" +
 
@@ -1459,7 +1463,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
                 "<input type=\"hidden\" name=\"dest\" value=\"" + ahelperKey + "\">\n" +
                 "<input type=\"hidden\" name=\"nonce\" value=\"" + _proxyNonce + "\">\n" +
 
-                "<h4>" + _t("Save {0} to router address book and continue to website", destination) + "</h4>\n<p>" +
+                "<h4>" + _t("Save {0} to router address book and continue to website", idn) + "</h4>\n<p>" +
                 _t("This address will be saved to your Router address book where your subscription-based addresses are stored."));
         if(_context.namingService().getName().equals("BlockfileNamingService")) {
             out.write(" " + _t("If you want to keep track of sites you have added manually, add to your Local or Private address book instead."));
@@ -1472,12 +1476,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
         if(_context.namingService().getName().equals("BlockfileNamingService")) {
             // only blockfile supports multiple books
 
-            out.write("<h4>" + _t("Save {0} to local address book and continue to website", destination) + "</h4>\n<p>" +
+            out.write("<h4>" + _t("Save {0} to local address book and continue to website", idn) + "</h4>\n<p>" +
             _t("This address will be saved to your Local address book. Select this option for addresses you wish to keep separate from the main router address book, but don't mind publishing.") +
             "</p>\n<div class=\"formaction\"><button type=\"submit\" class=\"accept\" name=\"local\" value=\"local\">" +
             label + "</button></div>\n");
 
-            out.write("<h4>" + _t("Save {0} to private address book and continue to website", destination) + "</h4>\n<p>" +
+            out.write("<h4>" + _t("Save {0} to private address book and continue to website", idn) + "</h4>\n<p>" +
             _t("This address will be saved to your Private address book, ensuring it is never published.") +
             "</p>\n<div class=\"formaction\"><button type=\"submit\" class=\"accept\" name=\"private\" value=\"private\">" +
             label + "</button></div>\n");
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java
index 54c7762062ff2ae37589cfc71c6195ce5dd65663..814145b623f0b99f91888e9afd71a8214b8d9163 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientBase.java
@@ -15,6 +15,7 @@ import java.io.Reader;
 import java.io.StringReader;
 import java.io.UnsupportedEncodingException;
 import java.io.Writer;
+import java.net.IDN;
 import java.net.Socket;
 import java.net.SocketTimeoutException;
 import java.net.URI;
@@ -144,6 +145,19 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
     // very simple, remember last-failed only
     private String _lastFailedSSLProxy;
 
+    /** available as of Java 6 and Android API 9 */
+    private static final boolean _haveIDN;
+    static {
+        boolean h;
+        try {
+            Class.forName("java.net.IDN", false, ClassLoader.getSystemClassLoader());
+            h = true;
+        } catch (ClassNotFoundException cnfe) {
+            h = false;
+        }
+        _haveIDN = h;
+    }
+
     protected String getPrefix(long requestId) {
         return "HTTPClient[" + _clientId + '/' + requestId + "]: ";
     }
@@ -929,7 +943,7 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
             out.write(uri);
             out.write("\">");
             // Long URLs are handled in CSS
-            out.write(uri);
+            out.write(decodeIDNURI(uri));
             out.write("</a>");
             if (usingWWWProxy) {
                 out.write("<br><br><b>");
@@ -996,6 +1010,45 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
         writeFooter(out);
     }
 
+    /**
+     *  Decode the host part of a URI for display.
+     *  Returns original string on any error.
+     *
+     *  @since 0.9.50
+     */
+    private static String decodeIDNURI(String uri) {
+        if (!_haveIDN)
+             return uri;
+        if (!uri.contains("xn--"))
+             return uri;
+        try {
+            URI u = new URI(uri);
+            String h = u.getHost();
+            String hu = IDN.toUnicode(h);
+            if (hu == null || h.equals(hu))
+                return uri;
+            int idx = uri.indexOf(h);
+            if (idx < 0)
+                return uri;
+            return uri.substring(0, idx) + hu + uri.substring(idx + h.length(), uri.length());
+         } catch(URISyntaxException use) {}
+         return uri;
+    }
+
+    /**
+     *  Decode a hostname for display.
+     *  Returns original string on any error.
+     *
+     *  @since 0.9.50
+     */
+    public static String decodeIDNHost(String host) {
+        if (!_haveIDN)
+             return host;
+        if (!host.contains("xn--"))
+             return host;
+        return IDN.toUnicode(host);
+    }
+
     /**
      *  Flushes.
      *
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java
index 5368b1538ba3fef4bd8a8fe287ce2ee22339c043..2ab1059a9b198dbe6e2867f20a0fd69c69842ab6 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java
@@ -365,6 +365,7 @@ public abstract class LocalHTTPServer {
 
         PortMapper pm = I2PAppContext.getGlobalContext().portMapper();
         String conURL = pm.getConsoleURL();
+        String idn = I2PTunnelHTTPClientBase.decodeIDNHost(host);
         out.write(("HTTP/1.1 200 OK\r\n"+
                   "Content-Type: text/html; charset=UTF-8\r\n"+
                   "Referrer-Policy: no-referrer\r\n"+
@@ -372,7 +373,7 @@ public abstract class LocalHTTPServer {
                   "Proxy-Connection: close\r\n"+
                   "\r\n"+
                   "<html><head>"+
-                  "<title>" + _t("Redirecting to {0}", host) + "</title>\n" +
+                  "<title>" + _t("Redirecting to {0}", idn) + "</title>\n" +
                   "<link rel=\"shortcut icon\" href=\"http://proxy.i2p/themes/console/images/favicon.ico\" >\n" +
                   "<link href=\"http://proxy.i2p/themes/console/default/console.css\" rel=\"stylesheet\" type=\"text/css\" >\n" +
                   "<meta http-equiv=\"Refresh\" content=\"1; url=" + url + "\">\n" +
@@ -386,8 +387,8 @@ public abstract class LocalHTTPServer {
                   "<div class=warning id=warning>\n" +
                   "<h3>" +
                   (success ?
-                           _t("Saved {0} to the {1} address book, redirecting now.", host, tbook) :
-                           _t("Failed to save {0} to the {1} address book, redirecting now.", host, tbook)) +
+                           _t("Saved {0} to the {1} address book, redirecting now.", idn, tbook) :
+                           _t("Failed to save {0} to the {1} address book, redirecting now.", idn, tbook)) +
                   "</h3>\n<p><a href=\"" + url + "\">" +
                   _t("Click here if you are not redirected automatically.") +
                   "</a></p></div>").getBytes("UTF-8"));
@@ -399,6 +400,7 @@ public abstract class LocalHTTPServer {
     private static void writeB32RedirectPage(OutputStream out, String host, String url) throws IOException {
         PortMapper pm = I2PAppContext.getGlobalContext().portMapper();
         String conURL = pm.getConsoleURL();
+        String idn = I2PTunnelHTTPClientBase.decodeIDNHost(host);
         out.write(("HTTP/1.1 200 OK\r\n"+
                   "Content-Type: text/html; charset=UTF-8\r\n"+
                   "Referrer-Policy: no-referrer\r\n"+
@@ -406,7 +408,7 @@ public abstract class LocalHTTPServer {
                   "Proxy-Connection: close\r\n"+
                   "\r\n"+
                   "<html><head>"+
-                  "<title>" + _t("Redirecting to {0}", host) + "</title>\n" +
+                  "<title>" + _t("Redirecting to {0}", idn) + "</title>\n" +
                   "<link rel=\"shortcut icon\" href=\"http://proxy.i2p/themes/console/images/favicon.ico\" >\n" +
                   "<link href=\"http://proxy.i2p/themes/console/default/console.css\" rel=\"stylesheet\" type=\"text/css\" >\n" +
                   "<meta http-equiv=\"Refresh\" content=\"1; url=" + url + "\">\n" +
@@ -419,7 +421,7 @@ public abstract class LocalHTTPServer {
         out.write(("</div>" +
                   "<div class=warning id=warning>\n" +
                   "<h3>" +
-                  _t("Saved the authentication for {0}, redirecting now.", host) +
+                  _t("Saved the authentication for {0}, redirecting now.", idn) +
                   "</h3>\n<p><a href=\"" + url + "\">" +
                   _t("Click here if you are not redirected automatically.") +
                   "</a></p></div>").getBytes("UTF-8"));