Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(revision 15672)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java	(revision 15673)
@@ -78,4 +78,5 @@
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
+import org.openstreetmap.josm.gui.util.AbstractTag2LinkPopupListener;
 import org.openstreetmap.josm.gui.util.HighlightHelper;
 import org.openstreetmap.josm.gui.widgets.CompileSearchTextDecorator;
@@ -662,4 +663,14 @@
             }
         });
+
+        popupMenuHandler.addListener(new AbstractTag2LinkPopupListener() {
+            @Override
+            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+                final IRelation<?> selection = getSelected();
+                if (selection != null) {
+                    selection.visitKeys((primitive, key, value) -> addLinks(popupMenu, key, value));
+                }
+            }
+        });
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java	(revision 15672)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java	(revision 15673)
@@ -36,4 +36,5 @@
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
+import javax.swing.event.PopupMenuEvent;
 
 import org.openstreetmap.josm.actions.AbstractSelectAction;
@@ -74,4 +75,5 @@
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
+import org.openstreetmap.josm.gui.util.AbstractTag2LinkPopupListener;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.util.HighlightHelper;
@@ -231,4 +233,13 @@
         handler.addAction(actZoomToListSelection);
         handler.addSeparator();
+        handler.addListener(new AbstractTag2LinkPopupListener() {
+            @Override
+            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+                final Collection<OsmPrimitive> selection = getSelectedPrimitives();
+                if (!selection.isEmpty()) {
+                    selection.iterator().next().visitKeys((primitive, key, value) -> addLinks(popupMenu, key, value));
+                }
+            }
+        });
         return RelationPopupMenus.setupHandler(handler);
     }
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java	(revision 15672)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java	(revision 15673)
@@ -41,4 +41,5 @@
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
+import javax.swing.event.PopupMenuEvent;
 import javax.swing.event.RowSorterEvent;
 import javax.swing.event.RowSorterListener;
@@ -97,4 +98,5 @@
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler;
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType;
+import org.openstreetmap.josm.gui.util.AbstractTag2LinkPopupListener;
 import org.openstreetmap.josm.gui.util.HighlightHelper;
 import org.openstreetmap.josm.gui.util.TableHelper;
@@ -391,4 +393,14 @@
         membershipMenu.add(taginfoAction);
 
+        membershipMenu.addPopupMenuListener(new AbstractTag2LinkPopupListener() {
+            @Override
+            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+                final IRelation<?> relation = getSelectedMembershipRelation();
+                if (relation != null) {
+                    relation.visitKeys((primitive, key, value) -> addLinks(membershipMenu, key, value));
+                }
+            }
+        });
+
         membershipTable.addMouseListener(new PopupMenuLauncher(membershipMenu) {
             @Override
@@ -442,4 +454,14 @@
         tagMenu.add(helpTagAction);
         tagMenu.add(taginfoAction);
+        tagMenu.addPopupMenuListener(new AbstractTag2LinkPopupListener() {
+            @Override
+            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+                final Tags tags = getSelectedProperties();
+                if (tags != null) {
+                    tags.getValues().forEach(value -> addLinks(tagMenu, tags.getKey(), value));
+                }
+            }
+        });
+
         tagTable.addMouseListener(new PopupMenuLauncher(tagMenu));
     }
Index: /trunk/src/org/openstreetmap/josm/gui/history/OpenChangesetPopupMenu.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/history/OpenChangesetPopupMenu.java	(revision 15672)
+++ /trunk/src/org/openstreetmap/josm/gui/history/OpenChangesetPopupMenu.java	(revision 15673)
@@ -6,5 +6,4 @@
 import java.awt.Container;
 import java.awt.Rectangle;
-import java.awt.event.ActionEvent;
 import java.text.MessageFormat;
 import java.util.Arrays;
@@ -12,5 +11,4 @@
 import java.util.Objects;
 
-import javax.swing.AbstractAction;
 import javax.swing.Action;
 import javax.swing.JComponent;
@@ -20,7 +18,6 @@
 import org.openstreetmap.josm.data.StructUtils.StructEntry;
 import org.openstreetmap.josm.data.osm.PrimitiveId;
+import org.openstreetmap.josm.gui.util.OpenBrowserAction;
 import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.OpenBrowser;
 
 /**
@@ -107,18 +104,3 @@
     }
 
-    static class OpenBrowserAction extends AbstractAction {
-        final String url;
-
-        OpenBrowserAction(String name, String url) {
-            super(name);
-            putValue(SHORT_DESCRIPTION, tr("Open {0}", url));
-            new ImageProvider("help/internet").getResource().attachImageIcon(this, true);
-            this.url = url;
-        }
-
-        @Override
-        public void actionPerformed(ActionEvent e) {
-            OpenBrowser.displayUrl(url);
-        }
-    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/util/AbstractTag2LinkPopupListener.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/util/AbstractTag2LinkPopupListener.java	(revision 15673)
+++ /trunk/src/org/openstreetmap/josm/gui/util/AbstractTag2LinkPopupListener.java	(revision 15673)
@@ -0,0 +1,39 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+
+/**
+ * A popup listener which adds web links based on tags of OSM primitives.
+ *
+ * @since xxx
+ */
+public abstract class AbstractTag2LinkPopupListener implements PopupMenuListener {
+
+    private final List<JMenuItem> itemList;
+
+    protected AbstractTag2LinkPopupListener() {
+        this.itemList = new ArrayList<>();
+    }
+
+    @Override
+    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+        JPopupMenu popup = (JPopupMenu) e.getSource();
+        itemList.forEach(popup::remove);
+        itemList.clear();
+    }
+
+    @Override
+    public void popupMenuCanceled(PopupMenuEvent e) {
+    }
+
+    protected void addLinks(JPopupMenu popup, String key, String value) {
+        Tag2Link.getLinksForTag(key, value, (name, url) -> itemList.add(popup.add(new OpenBrowserAction(name, url))));
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/util/OpenBrowserAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/util/OpenBrowserAction.java	(revision 15673)
+++ /trunk/src/org/openstreetmap/josm/gui/util/OpenBrowserAction.java	(revision 15673)
@@ -0,0 +1,38 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.util;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.OpenBrowser;
+
+/**
+ * Action to open browser on given URL.
+ * @see OpenBrowser
+ * @since xxx
+ */
+public class OpenBrowserAction extends AbstractAction {
+
+    private final String url;
+
+    /**
+     * Constructs a new {@link OpenBrowserAction}.
+     * @param name the name of this action
+     * @param url the URL to launch
+     */
+    public OpenBrowserAction(String name, String url) {
+        super(name);
+        putValue(SHORT_DESCRIPTION, tr("Open {0}", url));
+        new ImageProvider("help/internet").getResource().attachImageIcon(this, true);
+        this.url = url;
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        OpenBrowser.displayUrl(url);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/util/Tag2Link.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/util/Tag2Link.java	(revision 15673)
+++ /trunk/src/org/openstreetmap/josm/gui/util/Tag2Link.java	(revision 15673)
@@ -0,0 +1,93 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.util;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.openstreetmap.josm.data.osm.OsmUtils;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * Extracts web links from OSM tags.
+ *
+ * @since xxx
+ */
+final class Tag2Link {
+
+    // Related implementations:
+    // - https://github.com/openstreetmap/openstreetmap-website/blob/master/app/helpers/browse_tags_helper.rb
+
+    private Tag2Link() {
+        // private constructor for utility class
+    }
+
+    @FunctionalInterface
+    interface LinkConsumer {
+        void acceptLink(String name, String url);
+    }
+
+    static void getLinksForTag(String key, String value, LinkConsumer linkConsumer) {
+        Matcher keyMatcher;
+        Matcher valueMatcher;
+
+        // Search
+        if (key.matches("^(.+[:_])?name([:_].+)?$")) {
+            linkConsumer.acceptLink(tr("Search on DuckDuckGo"), "https://duckduckgo.com/?q=" + value);
+        }
+
+        // Common
+        final boolean valueIsURL = value.matches("^(http:|https:|www\\.).*");
+        if (key.matches("^(.+[:_])?website([:_].+)?$") && valueIsURL) {
+            linkConsumer.acceptLink(tr("View website"), value);
+        }
+        if (key.matches("^(.+[:_])?source([:_].+)?$") && valueIsURL) {
+            linkConsumer.acceptLink(tr("View website"), value);
+        }
+        if (key.matches("^(.+[:_])?url([:_].+)?$") && valueIsURL) {
+            linkConsumer.acceptLink(tr("View URL"), value);
+        }
+        if (key.matches("image") && valueIsURL) {
+            linkConsumer.acceptLink(tr("View image"), value);
+        }
+
+        // Wikimedia
+        if ((keyMatcher = Pattern.compile("wikipedia(:(?<lang>\\p{Lower}{2,}))?").matcher(key)).matches()
+                && (valueMatcher = Pattern.compile("((?<lang>\\p{Lower}{2,}):)?(?<article>.*)").matcher(value)).matches()) {
+            final String lang = Utils.firstNotEmptyString("en", keyMatcher.group("lang"), valueMatcher.group("lang"));
+            linkConsumer.acceptLink(tr("View Wikipedia article"), "https://" + lang + ".wikipedia.org/wiki/" + valueMatcher.group("article"));
+        }
+        if (key.matches("(.*:)?wikidata")) {
+            OsmUtils.splitMultipleValues(value)
+                    .forEach(q -> linkConsumer.acceptLink(tr("View Wikidata item"), "https://www.wikidata.org/wiki/" + q));
+        }
+        if (key.matches("species")) {
+            linkConsumer.acceptLink(tr("View Wikispecies page"), "https://species.wikimedia.org/wiki/" + value);
+        }
+        if (key.matches("wikimedia_commons|image") && value.matches("(?i:File):.*")) {
+            linkConsumer.acceptLink(tr("View image on Wikimedia Commons"), "https://commons.wikimedia.org/wiki/" + value);
+        }
+        if (key.matches("wikimedia_commons|image") && value.matches("(?i:Category):.*")) {
+            linkConsumer.acceptLink(tr("View category on Wikimedia Commons"), "https://commons.wikimedia.org/wiki/" + value);
+        }
+
+        // WHC
+        if (key.matches("ref:whc") && (valueMatcher = Pattern.compile("(?<id>[0-9]+)(-.*)?").matcher(value)).matches()) {
+            linkConsumer.acceptLink(tr("View UNESCO sheet"), "http://whc.unesco.org/en/list/" + valueMatcher.group("id"));
+        }
+
+        // Mapillary
+        if (key.matches("((ref|source):)?mapillary") && value.matches("[0-9a-zA-Z-_]+")) {
+            linkConsumer.acceptLink(tr("View {0} image", "Mapillary"), "https://www.mapillary.com/map/im/" + value);
+        }
+
+        // MMSI
+        if (key.matches("seamark:(virtual_aton|radio_station):mmsi") && value.matches("[0-9]+")) {
+            // https://en.wikipedia.org/wiki/Maritime_Mobile_Service_Identity
+            linkConsumer.acceptLink(tr("View MMSI on MarineTraffic"),
+                    "https://www.marinetraffic.com/en/ais/details/ships/shipid:/mmsi:" + value);
+        }
+    }
+
+}
Index: /trunk/test/unit/org/openstreetmap/josm/gui/util/Tag2LinkTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/util/Tag2LinkTest.java	(revision 15673)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/util/Tag2LinkTest.java	(revision 15673)
@@ -0,0 +1,63 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test {@link Tag2Link}
+ */
+public class Tag2LinkTest {
+
+    List<String> links = new ArrayList<>();
+
+    void addLink(String name, String url) {
+        links.add(name + " // " + url);
+    }
+
+    void checkLinks(String... expected) {
+        Assert.assertEquals(Arrays.asList(expected), links);
+    }
+
+    /**
+     * Unit test of function {@link Tag2Link#getLinksForTag}.
+     */
+    @Test
+    public void testImageCommonsImage() {
+        Tag2Link.getLinksForTag("image", "File:Witten Brücke Gasstraße.jpg", this::addLink);
+        checkLinks("View image on Wikimedia Commons // https://commons.wikimedia.org/wiki/File:Witten Brücke Gasstraße.jpg");
+    }
+
+    /**
+     * Unit test of function {@link Tag2Link#getLinksForTag}.
+     */
+    @Test
+    public void testImageCommonsCategory() {
+        Tag2Link.getLinksForTag("image", "category:JOSM", this::addLink);
+        checkLinks("View category on Wikimedia Commons // https://commons.wikimedia.org/wiki/category:JOSM");
+    }
+
+    /**
+     * Unit test of function {@link Tag2Link#getLinksForTag}.
+     */
+    @Test
+    public void testBrandWikidata() {
+        Tag2Link.getLinksForTag("brand:wikidata", "Q259340", this::addLink);
+        checkLinks("View Wikidata item // https://www.wikidata.org/wiki/Q259340");
+    }
+
+    /**
+     * Unit test of function {@link Tag2Link#getLinksForTag}.
+     */
+    @Test
+    public void testArchipelagoWikidata() {
+        Tag2Link.getLinksForTag("archipelago:wikidata", "Q756987;Q756988", this::addLink);
+        checkLinks("View Wikidata item // https://www.wikidata.org/wiki/Q756987",
+                "View Wikidata item // https://www.wikidata.org/wiki/Q756988");
+    }
+
+}
