Index: /trunk/src/org/openstreetmap/josm/actions/AboutAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/AboutAction.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/actions/AboutAction.java	(revision 2308)
@@ -93,5 +93,5 @@
     static public void setUserAgent() {
         Properties sysProp = System.getProperties();
-        sysProp.put("http.agent", "JOSM/1.5 ("+(version.equals(tr("UNKNOWN"))?"UNKNOWN":version)+" "+LanguageInfo.getLanguageCode()+")");
+        sysProp.put("http.agent", "JOSM/1.5 ("+(version.equals(tr("UNKNOWN"))?"UNKNOWN":version)+" "+LanguageInfo.getJOSMLocaleCode()+")");
         System.setProperties(sysProp);
     }
Index: /trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java	(revision 2308)
@@ -107,5 +107,4 @@
         TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways);
 
-
         // try to build a new way which includes all the combined
         // ways
Index: /trunk/src/org/openstreetmap/josm/actions/HelpAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/HelpAction.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/actions/HelpAction.java	(revision 2308)
@@ -9,13 +9,9 @@
 
 import javax.swing.AbstractAction;
-import javax.swing.AbstractButton;
-import javax.swing.Action;
-import javax.swing.JComponent;
-import javax.swing.JMenu;
 import javax.swing.SwingUtilities;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.gui.help.HelpBrowserProxy;
-import org.openstreetmap.josm.gui.help.Helpful;
+import org.openstreetmap.josm.gui.help.HelpUtil;
 import org.openstreetmap.josm.tools.ImageProvider;
 
@@ -23,11 +19,6 @@
  * Open a help browser and displays lightweight online help.
  *
- * @author imi
  */
 public class HelpAction extends AbstractAction {
-
-
-    private String pathhelp = Main.pref.get("help.pathhelp", "Help/");
-    private String pathmenu = Main.pref.get("help.pathmenu", "Menu/");
 
     public HelpAction() {
@@ -43,5 +34,5 @@
                 if (mouse != null) {
                     c = SwingUtilities.getDeepestComponentAt(c, mouse.x, mouse.y);
-                    topic = contextSensitiveHelp(c);
+                    topic = HelpUtil.getContextSpecificHelpTopic(c);
                 } else {
                     topic = null;
@@ -49,51 +40,14 @@
             } else {
                 Point mouse = Main.parent.getMousePosition();
-                topic = contextSensitiveHelp(SwingUtilities.getDeepestComponentAt(Main.parent, mouse.x, mouse.y));
+                topic = HelpUtil.getContextSpecificHelpTopic(SwingUtilities.getDeepestComponentAt(Main.parent, mouse.x, mouse.y));
             }
             if (topic == null) {
-                HelpBrowserProxy.getInstance().setUrlForHelpTopic("Help");
+                HelpBrowserProxy.getInstance().setUrlForHelpTopic("/");
             } else {
-                help(topic);
+                HelpBrowserProxy.getInstance().setUrlForHelpTopic(topic);
             }
         } else {
-            HelpBrowserProxy.getInstance().setUrlForHelpTopic("Help");
+            HelpBrowserProxy.getInstance().setUrlForHelpTopic("/");
         }
     }
-
-    /**
-     * @return The topic of the help. <code>null</code> for "don't know"
-     */
-    private String contextSensitiveHelp(Object c) {
-        if (c == null)
-            return null;
-        if (c instanceof Helpful)
-            return ((Helpful)c).helpTopic();
-        if (c instanceof JMenu) {
-            JMenu b = (JMenu)c;
-            if (b.getClientProperty("help") != null)
-                return (String)b.getClientProperty("help");
-            return pathmenu+b.getText();
-        }
-        if (c instanceof AbstractButton) {
-            AbstractButton b = (AbstractButton)c;
-            if (b.getClientProperty("help") != null)
-                return (String)b.getClientProperty("help");
-            return contextSensitiveHelp(((AbstractButton)c).getAction());
-        }
-        if (c instanceof Action)
-            return (String)((Action)c).getValue("help");
-        if (c instanceof JComponent && ((JComponent)c).getClientProperty("help") != null)
-            return (String)((JComponent)c).getClientProperty("help");
-        if (c instanceof Component)
-            return contextSensitiveHelp(((Component)c).getParent());
-        return null;
-    }
-
-    /**
-     * Displays the help (or browse on the already open help) on the online page
-     * with the given help topic. Use this for larger help descriptions.
-     */
-    public void help(String topic) {
-        HelpBrowserProxy.getInstance().setUrlForHelpTopic(pathhelp + topic);
-    }
 }
Index: /trunk/src/org/openstreetmap/josm/actions/OpenLocationAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/OpenLocationAction.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/actions/OpenLocationAction.java	(revision 2308)
@@ -91,5 +91,5 @@
                 tr("Close dialog and cancel downloading")
         });
-        dialog.configureContextsensitiveHelp("Help/Action/OpenLocation", true /* show help button */);
+        dialog.configureContextsensitiveHelp("/Action/OpenLocation", true /* show help button */);
         dialog.showDialog();
         if (dialog.getValue() != 1) return;
Index: /trunk/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java	(revision 2308)
@@ -142,5 +142,5 @@
             c = DeleteCommand.deleteWithReferences(getEditLayer(),getCurrentDataSet().getSelected());
         } else {
-            c = DeleteCommand.delete(getEditLayer(),getCurrentDataSet().getSelected(), !alt);
+            c = DeleteCommand.delete(getEditLayer(),getCurrentDataSet().getSelected(), !alt /* also delete nodes in way */);
         }
         if (c != null) {
@@ -309,9 +309,9 @@
      * @param e MouseEvent from which modifiers and position are taken
      * @param int modifiers For explanation: @see updateCursor
-     * @param Simulate Set to true if the user should be bugged with additional
+     * @param silet Set to true if the user should not be bugged with additional
      *        dialogs
      * @return
      */
-    private Command buildDeleteCommands(MouseEvent e, int modifiers, boolean simulate) {
+    private Command buildDeleteCommands(MouseEvent e, int modifiers, boolean silent) {
         // Note: CTRL is the only modifier that is checked in MouseMove, don't
         // forget updating it there
@@ -330,5 +330,5 @@
                     c = DeleteCommand.deleteWithReferences(getEditLayer(),Collections.singleton((OsmPrimitive)ws.way),true);
                 } else {
-                    c = DeleteCommand.delete(getEditLayer(),Collections.singleton((OsmPrimitive)ws.way), !alt, simulate);
+                    c = DeleteCommand.delete(getEditLayer(),Collections.singleton((OsmPrimitive)ws.way), !alt, silent);
                 }
             }
@@ -336,5 +336,5 @@
             c = DeleteCommand.deleteWithReferences(getEditLayer(),Collections.singleton(sel));
         } else {
-            c = DeleteCommand.delete(getEditLayer(),Collections.singleton(sel), !alt, simulate);
+            c = DeleteCommand.delete(getEditLayer(),Collections.singleton(sel), !alt, silent);
         }
 
Index: /trunk/src/org/openstreetmap/josm/command/Command.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/Command.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/command/Command.java	(revision 2308)
@@ -3,4 +3,5 @@
 
 import java.util.Collection;
+import static org.openstreetmap.josm.tools.I18n.tr;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -58,7 +59,10 @@
      * Creates a new command in the context of a specific data layer
      *
-     * @param layer the data layer
+     * @param layer the data layer. Must not be null.
+     * @throws IllegalArgumentException thrown if layer is null
      */
-    public Command(OsmDataLayer layer) {
+    public Command(OsmDataLayer layer) throws IllegalArgumentException {
+        if (layer == null)
+            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "layer"));
         this.layer = layer;
     }
Index: /trunk/src/org/openstreetmap/josm/command/DeleteCommand.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/command/DeleteCommand.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/command/DeleteCommand.java	(revision 2308)
@@ -10,5 +10,4 @@
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -24,4 +23,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.BackreferencedDataSet;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
@@ -31,14 +31,18 @@
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.data.osm.BackreferencedDataSet.RelationToChildReference;
 import org.openstreetmap.josm.data.osm.visitor.CollectBackReferencesVisitor;
 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
 import org.openstreetmap.josm.gui.DefaultNameFormatter;
 import org.openstreetmap.josm.gui.ExtendedDialog;
+import org.openstreetmap.josm.gui.actionsupport.DeleteFromRelationConfirmationDialog;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.tools.ImageProvider;
 
+import sun.swing.BakedArrayList;
+
 /**
  * A command to delete a number of primitives from the dataset.
- * @author imi
+
  */
 public class DeleteCommand extends Command {
@@ -49,9 +53,25 @@
 
     /**
-     * Constructor for a collection of data
+     * Constructor. Deletes a collection of primitives in the current edit layer.
+     * 
+     * @param data the primitives to delete
      */
     public DeleteCommand(Collection<? extends OsmPrimitive> data) {
-        super();
+        if (data == null) {
+            data = Collections.emptyList();
+        }
         this.toDelete = data;
+    }
+
+    /**
+     * Constructor. Deletes a single primitive in the current edit layer.
+     * 
+     * @param data  the primitive to delete. Must not be null.
+     * @throws IllegalArgumentException thrown if data is null
+     */
+    public DeleteCommand(OsmPrimitive data) throws IllegalArgumentException {
+        if (data == null)
+            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "data"));
+        this.toDelete = Collections.singleton(data);
     }
 
@@ -59,18 +79,14 @@
      * Constructor for a single data item. Use the collection constructor to delete multiple
      * objects.
-     */
-    public DeleteCommand(OsmPrimitive data) {
-        this.toDelete = Collections.singleton(data);
-    }
-
-    /**
-     * Constructor for a single data item. Use the collection constructor to delete multiple
-     * objects.
-     *
-     * @param layer the layer context for deleting this primitive
-     * @param data the primitive to delete
-     */
-    public DeleteCommand(OsmDataLayer layer, OsmPrimitive data) {
+     *
+     * @param layer the layer context for deleting this primitive. Must not be null.
+     * @param data the primitive to delete. Must not be null.
+     * @throws IllegalArgumentException thrown if data is null
+     * @throws IllegalArgumentException thrown if layer is null
+     */
+    public DeleteCommand(OsmDataLayer layer, OsmPrimitive data) throws IllegalArgumentException {
         super(layer);
+        if (data == null)
+            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "data"));
         this.toDelete = Collections.singleton(data);
     }
@@ -80,10 +96,42 @@
      * a specific layer
      *
-     * @param layer the layer context for deleting these primitives
+     * @param layer the layer context for deleting these primitives. Must not be null.
      * @param data the primitives to delete
-     */
-    public DeleteCommand(OsmDataLayer layer, Collection<? extends OsmPrimitive> data) {
+     * @throws IllegalArgumentException thrown if layer is null
+     */
+    public DeleteCommand(OsmDataLayer layer, Collection<? extends OsmPrimitive> data) throws IllegalArgumentException{
         super(layer);
+        if (data == null) {
+            data = Collections.emptyList();
+        }
         this.toDelete = data;
+    }
+
+    protected void removeNewNodesFromDeletedWay(Way w) {
+        // #2707: ways to be deleted can include new nodes (with node.id == 0).
+        // Remove them from the way before the way is deleted. Otherwise the
+        // deleted way is saved (or sent to the API) with a dangling reference to a node
+        // Example:
+        // <node id='2' action='delete' visible='true' version='1' ... />
+        // <node id='1' action='delete' visible='true' version='1' ... />
+        // <!-- missing node with id -1 because new deleted nodes are not persisted -->
+        // <way id='3' action='delete' visible='true' version='1'>
+        // <nd ref='1' />
+        // <nd ref='-1' /> <!-- here's the problem -->
+        // <nd ref='2' />
+        // </way>
+        if (w.isNew())
+            return; // process existing ways only
+        List<Node> nodesToKeep = new ArrayList<Node>();
+        // lookup new nodes which have been added to the set of deleted
+        // nodes ...
+        Iterator<Node> it = nodesToKeep.iterator();
+        while(it.hasNext()) {
+            Node n = it.next();
+            if (n.isNew()) {
+                it.remove();
+            }
+        }
+        w.setNodes(nodesToKeep);
     }
 
@@ -93,4 +141,7 @@
         for (OsmPrimitive osm : toDelete) {
             osm.setDeleted(true);
+            if (osm instanceof Way) {
+                removeNewNodesFromDeletedWay((Way)osm);
+            }
         }
         return true;
@@ -109,7 +160,7 @@
             String msg = "";
             switch(OsmPrimitiveType.from(primitive)) {
-            case NODE: msg = "Delete node {0}"; break;
-            case WAY: msg = "Delete way {0}"; break;
-            case RELATION:msg = "Delete relation {0}"; break;
+                case NODE: msg = "Delete node {0}"; break;
+                case WAY: msg = "Delete way {0}"; break;
+                case RELATION:msg = "Delete relation {0}"; break;
             }
 
@@ -130,7 +181,7 @@
             apiname = t.getAPIName();
             switch(t) {
-            case NODE: msg = trn("Delete {0} node", "Delete {0} nodes", toDelete.size(), toDelete.size()); break;
-            case WAY: msg = trn("Delete {0} way", "Delete {0} ways", toDelete.size(), toDelete.size()); break;
-            case RELATION: msg = trn("Delete {0} relation", "Delete {0} relations", toDelete.size(), toDelete.size()); break;
+                case NODE: msg = trn("Delete {0} node", "Delete {0} nodes", toDelete.size(), toDelete.size()); break;
+                case WAY: msg = trn("Delete {0} way", "Delete {0} ways", toDelete.size(), toDelete.size()); break;
+                case RELATION: msg = trn("Delete {0} relation", "Delete {0} relations", toDelete.size(), toDelete.size()); break;
             }
         }
@@ -156,10 +207,14 @@
      * If a way is deleted, only the way and no nodes are deleted.
      *
-     * @param layer
+     * @param layer the {@see OsmDataLayer} in whose context primitives are deleted. Must not be null.
      * @param selection The list of all object to be deleted.
-     * @param simulate  Set to true if the user should not be bugged with additional dialogs
+     * @param silent  Set to true if the user should not be bugged with additional dialogs
      * @return command A command to perform the deletions, or null of there is nothing to delete.
-     */
-    public static Command deleteWithReferences(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection, boolean simulate) {
+     * @throws IllegalArgumentException thrown if layer is null
+     */
+    public static Command deleteWithReferences(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection, boolean silent) throws IllegalArgumentException {
+        if (layer == null)
+            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "layer"));
+        if (selection == null || selection.isEmpty()) return null;
         CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data);
         v.initialize();
@@ -170,5 +225,5 @@
         if (v.getData().isEmpty())
             return null;
-        if (!checkAndConfirmOutlyingDeletes(layer,v.getData()) && !simulate)
+        if (!checkAndConfirmOutlyingDeletes(layer,v.getData()) && !silent)
             return null;
         return new DeleteCommand(layer,v.getData());
@@ -233,4 +288,5 @@
      *    <li>it is not referred to by other non-deleted primitives outside of  <code>primitivesToDelete</code></li>
      * <ul>
+     * @param backreferences backreference data structure
      * @param layer  the layer in whose context primitives are deleted
      * @param primitivesToDelete  the primitives to delete
@@ -238,18 +294,15 @@
      * can be deleted too
      */
-    protected static Collection<Node> computeNodesToDelete(OsmDataLayer layer, Collection<OsmPrimitive> primitivesToDelete) {
+    protected static Collection<Node> computeNodesToDelete(BackreferencedDataSet backreferences, OsmDataLayer layer, Collection<OsmPrimitive> primitivesToDelete) {
         Collection<Node> nodesToDelete = new HashSet<Node>();
-        CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data, false);
-        for (OsmPrimitive osm : primitivesToDelete) {
-            if (! (osm instanceof Way) ) {
-                continue;
-            }
-            for (Node n : ((Way) osm).getNodes()) {
+        //CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data, false);
+        for (Way way : OsmPrimitive.getFilteredList(primitivesToDelete, Way.class)) {
+            for (Node n : way.getNodes()) {
                 if (n.isTagged()) {
                     continue;
                 }
-                v.initialize();
-                n.visit(v);
-                Collection<OsmPrimitive> referringPrimitives = v.getData();
+                //v.initialize();
+                //n.visit(v);
+                Collection<OsmPrimitive> referringPrimitives = backreferences.getParents(n);
                 referringPrimitives.removeAll(primitivesToDelete);
                 int count = 0;
@@ -276,58 +329,51 @@
      * they are part of a relation, inform the user and do not delete.
      *
-     * @param layer the {@see OsmDataLayer} in whose context a primitive the primitives are deleted
-     * @param selection The objects to delete.
+     * @param layer the {@see OsmDataLayer} in whose context the primitives are deleted
+     * @param selection the objects to delete.
      * @param alsoDeleteNodesInWay <code>true</code> if nodes should be deleted as well
-     * @param simulate Set to true if the user should not be bugged with additional questions
      * @return command a command to perform the deletions, or null if there is nothing to delete.
      */
     public static Command delete(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection,
             boolean alsoDeleteNodesInWay) {
-        return delete(layer, selection, alsoDeleteNodesInWay, false);
-    }
-
+        return delete(layer, selection, alsoDeleteNodesInWay, false /* not silent */);
+    }
+
+    /**
+     * Try to delete all given primitives.
+     *
+     * If a node is used by a way, it's removed from that way. If a node or a way is used by a
+     * relation, inform the user and do not delete.
+     *
+     * If this would cause ways with less than 2 nodes to be created, delete these ways instead. If
+     * they are part of a relation, inform the user and do not delete.
+     *
+     * @param layer the {@see OsmDataLayer} in whose context the primitives are deleted
+     * @param selection the objects to delete.
+     * @param alsoDeleteNodesInWay <code>true</code> if nodes should be deleted as well
+     * @param silent set to true if the user should not be bugged with additional questions
+     * @return command a command to perform the deletions, or null if there is nothing to delete.
+     */
     public static Command delete(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection,
-            boolean alsoDeleteNodesInWay, boolean simulate) {
-        if (selection.isEmpty())
+            boolean alsoDeleteNodesInWay, boolean silent) {
+        if (selection == null || selection.isEmpty())
             return null;
 
-        Collection<OsmPrimitive> primitivesToDelete = new HashSet<OsmPrimitive>(selection);
+        BackreferencedDataSet backreferences = new BackreferencedDataSet(layer.data);
+        backreferences.build();
+
+        Set<OsmPrimitive> primitivesToDelete = new HashSet<OsmPrimitive>(selection);
         Collection<Way> waysToBeChanged = new HashSet<Way>();
-        HashMap<OsmPrimitive, Collection<OsmPrimitive>> relationsToBeChanged = new HashMap<OsmPrimitive, Collection<OsmPrimitive>>();
 
         if (alsoDeleteNodesInWay) {
             // delete untagged nodes only referenced by primitives in primitivesToDelete,
             // too
-            Collection<Node> nodesToDelete = computeNodesToDelete(layer, primitivesToDelete);
+            Collection<Node> nodesToDelete = computeNodesToDelete(backreferences, layer, primitivesToDelete);
             primitivesToDelete.addAll(nodesToDelete);
         }
 
-        if (!simulate && !checkAndConfirmOutlyingDeletes(layer,primitivesToDelete))
+        if (!silent && !checkAndConfirmOutlyingDeletes(layer,primitivesToDelete))
             return null;
 
-        CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data, false);
-        for (OsmPrimitive osm : primitivesToDelete) {
-            v.initialize();
-            osm.visit(v);
-            for (OsmPrimitive ref : v.getData()) {
-                if (primitivesToDelete.contains(ref)) {
-                    continue;
-                }
-                if (ref instanceof Way) {
-                    waysToBeChanged.add((Way) ref);
-                } else if (ref instanceof Relation) {
-                    if (testRelation((Relation) ref, osm, simulate) == 1) {
-                        Collection<OsmPrimitive> relset = relationsToBeChanged.get(ref);
-                        if (relset == null) {
-                            relset = new HashSet<OsmPrimitive>();
-                        }
-                        relset.add(osm);
-                        relationsToBeChanged.put(ref, relset);
-                    } else
-                        return null;
-                } else
-                    return null;
-            }
-        }
+        waysToBeChanged.addAll(OsmPrimitive.getFilteredSet(backreferences.getParents(primitivesToDelete), Way.class));
 
         Collection<Command> cmds = new LinkedList<Command>();
@@ -337,34 +383,4 @@
             if (wnew.getNodesCount() < 2) {
                 primitivesToDelete.add(w);
-
-                v.initialize();
-                w.visit(v);
-                for (OsmPrimitive ref : v.getData()) {
-                    if (primitivesToDelete.contains(ref)) {
-                        continue;
-                    }
-                    if (ref instanceof Relation) {
-                        Boolean found = false;
-                        Collection<OsmPrimitive> relset = relationsToBeChanged.get(ref);
-                        if (relset == null) {
-                            relset = new HashSet<OsmPrimitive>();
-                        } else {
-                            for (OsmPrimitive m : relset) {
-                                if (m == w) {
-                                    found = true;
-                                    break;
-                                }
-                            }
-                        }
-                        if (!found) {
-                            if (testRelation((Relation) ref, w, simulate) == 1) {
-                                relset.add(w);
-                                relationsToBeChanged.put(ref, relset);
-                            } else
-                                return null;
-                        }
-                    } else
-                        return null;
-                }
             } else {
                 cmds.add(new ChangeCommand(w, wnew));
@@ -372,51 +388,37 @@
         }
 
-        Iterator<OsmPrimitive> iterator = relationsToBeChanged.keySet().iterator();
+        // get a confirmation that the objects to delete can be removed from their parent
+        // relations
+        //
+        if (!silent) {
+            Set<RelationToChildReference> references = backreferences.getRelationToChildReferences(primitivesToDelete);
+            Iterator<RelationToChildReference> it = references.iterator();
+            while(it.hasNext()) {
+                RelationToChildReference ref = it.next();
+                if (ref.getParent().isDeleted()) {
+                    it.remove();
+                }
+            }
+            if (!references.isEmpty()) {
+                DeleteFromRelationConfirmationDialog dialog = DeleteFromRelationConfirmationDialog.getInstance();
+                dialog.getModel().populate(references);
+                dialog.setVisible(true);
+                if (dialog.isCanceled())
+                    return null;
+            }
+        }
+
+        // remove the objects from their parent relations
+        //
+        Iterator<Relation> iterator = OsmPrimitive.getFilteredSet(backreferences.getParents(primitivesToDelete), Relation.class).iterator();
         while (iterator.hasNext()) {
-            Relation cur = (Relation) iterator.next();
+            Relation cur = iterator.next();
             Relation rel = new Relation(cur);
-            for (OsmPrimitive osm : relationsToBeChanged.get(cur)) {
-                rel.removeMembersFor(osm);
-            }
+            rel.removeMembersFor(primitivesToDelete);
             cmds.add(new ChangeCommand(cur, rel));
         }
 
-        // #2707: ways to be deleted can include new nodes (with node.id == 0).
-        // Remove them from the way before the way is deleted. Otherwise the
-        // deleted way is saved (or sent to the API) with a dangling reference to a node
-        // Example:
-        // <node id='2' action='delete' visible='true' version='1' ... />
-        // <node id='1' action='delete' visible='true' version='1' ... />
-        // <!-- missing node with id -1 because new deleted nodes are not persisted -->
-        // <way id='3' action='delete' visible='true' version='1'>
-        // <nd ref='1' />
-        // <nd ref='-1' /> <!-- heres the problem -->
-        // <nd ref='2' />
-        // </way>
-        for (OsmPrimitive primitive : primitivesToDelete) {
-            if (!(primitive instanceof Way)) {
-                continue;
-            }
-            Way w = (Way) primitive;
-            if (w.isNew()) { // new ways with id == 0 are fine,
-                continue; // process existing ways only
-            }
-            Way wnew = new Way(w);
-            List<Node> nodesToKeep = new ArrayList<Node>();
-            // lookup new nodes which have been added to the set of deleted
-            // nodes ...
-            for (Node n : wnew.getNodes()) {
-                if (!n.isNew() || !primitivesToDelete.contains(n)) {
-                    nodesToKeep.add(n);
-                }
-            }
-            // .. and remove them from the way
-            //
-            wnew.setNodes(nodesToKeep);
-            if (nodesToKeep.size() < w.getNodesCount()) {
-                cmds.add(new ChangeCommand(w, wnew));
-            }
-        }
-
+        // build the delete command
+        //
         if (!primitivesToDelete.isEmpty()) {
             cmds.add(new DeleteCommand(layer,primitivesToDelete));
@@ -427,10 +429,6 @@
 
     public static Command deleteWaySegment(OsmDataLayer layer, WaySegment ws) {
-        if (ws.way.getNodesCount() < 3) {
-            // If the way contains less than three nodes, it can't have more
-            // than one segment, so the way should be deleted.
-
+        if (ws.way.getNodesCount() < 3)
             return new DeleteCommand(layer, Collections.singleton(ws.way));
-        }
 
         if (ws.way.firstNode() == ws.way.lastNode()) {
Index: /trunk/src/org/openstreetmap/josm/data/osm/Relation.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/Relation.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/data/osm/Relation.java	(revision 2308)
@@ -2,4 +2,5 @@
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -170,13 +171,13 @@
         for (RelationMemberData member:relationData.getMembers()) {
             switch (member.getMemberType()) {
-            case NODE:
-                nodes.put(member.getMemberId(), nodeMarker);
-                break;
-            case WAY:
-                ways.put(member.getMemberId(), wayMarker);
-                break;
-            case RELATION:
-                relations.put(member.getMemberId(), relationMarker);
-                break;
+                case NODE:
+                    nodes.put(member.getMemberId(), nodeMarker);
+                    break;
+                case WAY:
+                    ways.put(member.getMemberId(), wayMarker);
+                    break;
+                case RELATION:
+                    relations.put(member.getMemberId(), relationMarker);
+                    break;
             }
         }
@@ -202,22 +203,19 @@
             OsmPrimitive foundMember = null;
             switch (member.getMemberType()) {
-            case NODE:
-                foundMember = nodes.get(member.getMemberId());
-                if (foundMember == nodeMarker) {
-                    throw new AssertionError("Data consistency problem - relation with missing member detected");
-                }
-                break;
-            case WAY:
-                foundMember = ways.get(member.getMemberId());
-                if (foundMember == wayMarker) {
-                    throw new AssertionError("Data consistency problem - relation with missing member detected");
-                }
-                break;
-            case RELATION:
-                foundMember = relations.get(member.getMemberId());
-                if (foundMember == relationMarker) {
-                    throw new AssertionError("Data consistency problem - relation with missing member detected");
-                }
-                break;
+                case NODE:
+                    foundMember = nodes.get(member.getMemberId());
+                    if (foundMember == nodeMarker)
+                        throw new AssertionError("Data consistency problem - relation with missing member detected");
+                    break;
+                case WAY:
+                    foundMember = ways.get(member.getMemberId());
+                    if (foundMember == wayMarker)
+                        throw new AssertionError("Data consistency problem - relation with missing member detected");
+                    break;
+                case RELATION:
+                    foundMember = relations.get(member.getMemberId());
+                    if (foundMember == relationMarker)
+                        throw new AssertionError("Data consistency problem - relation with missing member detected");
+                    break;
             }
             newMembers.add(new RelationMember(member.getRole(), foundMember));
@@ -290,4 +288,22 @@
     }
 
+    /**
+     * removes all members with member.member == primitive
+     *
+     * @param primitives the primitives to check for
+     */
+    public void removeMembersFor(Collection<OsmPrimitive> primitives) {
+        if (primitives == null || primitives.isEmpty())
+            return;
+
+        ArrayList<RelationMember> todelete = new ArrayList<RelationMember>();
+        for (RelationMember member: members) {
+            if (primitives.contains(member.getMember())) {
+                todelete.add(member);
+            }
+        }
+        members.removeAll(todelete);
+    }
+
     @Override
     public String getDisplayName(NameFormatter formatter) {
Index: /trunk/src/org/openstreetmap/josm/data/osm/visitor/MapPaintVisitor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/visitor/MapPaintVisitor.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/data/osm/visitor/MapPaintVisitor.java	(revision 2308)
@@ -1370,5 +1370,5 @@
         leftHandTraffic = Main.pref.getBoolean("mappaint.lefthandtraffic",false);
         orderFont = new Font(Main.pref.get("mappaint.font","Helvetica"), Font.PLAIN, Main.pref.getInteger("mappaint.fontsize", 8));
-        String[] names = {"name:"+LanguageInfo.getLanguageCode(), "name", "int_name", "ref", "operator", "brand","addr:housenumber"};
+        String[] names = {"name:"+LanguageInfo.getJOSMLocaleCode(), "name", "int_name", "ref", "operator", "brand","addr:housenumber"};
         regionalNameOrder = Main.pref.getCollection("mappaint.nameOrder", Arrays.asList(names));
         minEN = nc.getEastNorth(0,nc.getHeight()-1);
Index: /trunk/src/org/openstreetmap/josm/gui/GettingStarted.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/GettingStarted.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/gui/GettingStarted.java	(revision 2308)
@@ -52,5 +52,5 @@
 
         final private int myVersion = AboutAction.getVersionNumber();
-        final private String myLang = LanguageInfo.getLanguageCodeWiki();
+        final private String myLang = LanguageInfo.getWikiLanguagePrefix();
 
         /**
Index: /trunk/src/org/openstreetmap/josm/gui/actionsupport/DeleteFromRelationConfirmationDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/actionsupport/DeleteFromRelationConfirmationDialog.java	(revision 2308)
+++ /trunk/src/org/openstreetmap/josm/gui/actionsupport/DeleteFromRelationConfirmationDialog.java	(revision 2308)
@@ -0,0 +1,361 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.actionsupport;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableColumn;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.osm.NameFormatter;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.BackreferencedDataSet.RelationToChildReference;
+import org.openstreetmap.josm.gui.DefaultNameFormatter;
+import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
+import org.openstreetmap.josm.gui.help.HelpUtil;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.WindowGeometry;
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+
+/**
+ * This dialog is used to get a user confirmation that a collection of primitives can be removed
+ * from their parent relations.
+ * 
+ */
+public class DeleteFromRelationConfirmationDialog extends JDialog implements TableModelListener {
+    /** the unique instance of this dialog */
+    static private DeleteFromRelationConfirmationDialog instance;
+
+    /**
+     * Replies the unique instance of this dialog
+     * 
+     * @return
+     */
+    static public DeleteFromRelationConfirmationDialog getInstance() {
+        if (instance == null) {
+            instance = new DeleteFromRelationConfirmationDialog();
+        }
+        return instance;
+    }
+
+    /** the data model */
+    private RelationMemberTableModel model;
+    private JLabel lblMessage;
+    private boolean canceled;
+    private SideButton btnOK;
+
+    protected JPanel buildMessagePanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new GridBagLayout());
+        GridBagConstraints gc = new GridBagConstraints();
+        gc.anchor = GridBagConstraints.NORTHWEST;
+        gc.fill = GridBagConstraints.BOTH;
+        pnl.add(lblMessage = new JLabel(), gc);
+        lblMessage.setAlignmentX(JComponent.LEFT_ALIGNMENT);
+        pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+        return pnl;
+    }
+
+    protected JPanel buildRelationMemberTablePanel() {
+        JTable table = new JTable(model, new RelationMemberTableColumnModel());
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new BorderLayout());
+        pnl.add(new JScrollPane(table));
+        return pnl;
+    }
+
+    protected JPanel buildButtonPanel() {
+        JPanel pnl = new JPanel();
+        pnl.setLayout(new FlowLayout());
+        pnl.add(btnOK = new SideButton(new OKAction()));
+        btnOK.setFocusable(true);
+        pnl.add(new SideButton(new CancelAction()));
+        pnl.add(new SideButton(new ContextSensitiveHelpAction(ht("/Action/Delete#DeleteFromRelations"))));
+        return pnl;
+    }
+
+    protected void build() {
+        model = new RelationMemberTableModel();
+        model.addTableModelListener(this);
+        getContentPane().setLayout(new BorderLayout());
+        getContentPane().add(buildMessagePanel(), BorderLayout.NORTH);
+        getContentPane().add(buildRelationMemberTablePanel(), BorderLayout.CENTER);
+        getContentPane().add(buildButtonPanel(), BorderLayout.SOUTH);
+
+        HelpUtil.setHelpContext(this.getRootPane(), ht("/Action/Delete#DeleteFromRelations"));
+
+        addWindowListener(new WindowEventHandler());
+    }
+
+    protected void updateMessage() {
+        int numObjectsToDelete = model.getNumObjectsToDelete();
+        int numParentRelations = model.getNumParentRelations();
+        String msg;
+        if (numObjectsToDelete == 1 && numParentRelations == 1) {
+            msg = tr("<html>Please confirm to remove <strong>1 object</strong> from <strong>1 relation</strong>.</html>");
+        } else if (numObjectsToDelete == 1 && numParentRelations > 1) {
+            msg = tr("<html>Please confirm to remove <strong>1 object</strong> from <strong>{0} relations</strong>.</html>", numParentRelations);
+        } else if (numObjectsToDelete > 1 && numParentRelations == 1) {
+            msg = tr("<html>Please confirm to remove <strong>1 object</strong> from <strong>{0} relations</strong>.</html>", numParentRelations);
+        } else {
+            msg = tr("<html>Please confirm to remove <strong>{0} objects</strong> from <strong>{1} relations</strong>.</html>", numObjectsToDelete,numParentRelations);
+        }
+        lblMessage.setText(msg);
+        invalidate();
+    }
+
+    protected void updateTitle() {
+        int numObjectsToDelete = model.getNumObjectsToDelete();
+        if (numObjectsToDelete > 0) {
+            setTitle(trn("Deleting {0} object", "Deleting {0} objects", numObjectsToDelete, numObjectsToDelete));
+        } else {
+            setTitle(tr("Delete objects"));
+        }
+    }
+
+    public DeleteFromRelationConfirmationDialog() {
+        super(JOptionPane.getFrameForComponent(Main.parent), "", true /* modal */);
+        build();
+    }
+
+    /**
+     * Replies the data model used in this dialog
+     * 
+     * @return the data model
+     */
+    public RelationMemberTableModel getModel() {
+        return model;
+    }
+
+    /**
+     * Replies true if the dialog was canceled
+     * 
+     * @return true if the dialog was canceled
+     */
+    public boolean isCanceled() {
+        return canceled;
+    }
+
+    protected void setCanceled(boolean canceled) {
+        this.canceled = canceled;
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        if (visible) {
+            new WindowGeometry(
+                    getClass().getName()  + ".geometry",
+                    WindowGeometry.centerInWindow(
+                            Main.parent,
+                            new Dimension(400,200)
+                    )
+            ).applySafe(this);
+            setCanceled(false);
+        } else {
+            new WindowGeometry(this).remember(getClass().getName() + ".geometry");
+        }
+        super.setVisible(visible);
+    }
+
+    public void tableChanged(TableModelEvent e) {
+        updateMessage();
+        updateTitle();
+    }
+
+    /**
+     * The table model which manages the list of relation-to-child references
+     *
+     */
+    public static class RelationMemberTableModel extends DefaultTableModel {
+        private ArrayList<RelationToChildReference> data;
+
+        public RelationMemberTableModel() {
+            data = new ArrayList<RelationToChildReference>();
+        }
+
+        @Override
+        public int getRowCount() {
+            if (data == null) return 0;
+            return data.size();
+        }
+
+        protected void sort() {
+            Collections.sort(
+                    data,
+                    new Comparator<RelationToChildReference>() {
+                        private NameFormatter nf = DefaultNameFormatter.getInstance();
+                        public int compare(RelationToChildReference o1, RelationToChildReference o2) {
+                            int cmp = o1.getChild().getDisplayName(nf).compareTo(o2.getChild().getDisplayName(nf));
+                            if (cmp != 0) return cmp;
+                            cmp = o1.getParent().getDisplayName(nf).compareTo(o2.getParent().getDisplayName(nf));
+                            if (cmp != 0) return cmp;
+                            return new Integer(o1.getPosition()).compareTo(o2.getPosition());
+                        }
+                    }
+            );
+        }
+
+        public void populate(Collection<RelationToChildReference> references) {
+            data.clear();
+            if (references != null) {
+                data.addAll(references);
+            }
+            sort();
+            fireTableDataChanged();
+        }
+
+        public Set<OsmPrimitive> getObjectsToDelete() {
+            HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
+            for (RelationToChildReference ref: data) {
+                ret.add(ref.getChild());
+            }
+            return ret;
+        }
+
+        public int getNumObjectsToDelete() {
+            return getObjectsToDelete().size();
+        }
+
+        public Set<OsmPrimitive> getParentRelations() {
+            HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
+            for (RelationToChildReference ref: data) {
+                ret.add(ref.getParent());
+            }
+            return ret;
+        }
+
+        public int getNumParentRelations() {
+            return getParentRelations().size();
+        }
+
+        @Override
+        public Object getValueAt(int rowIndex, int columnIndex) {
+            if (data == null) return null;
+            RelationToChildReference ref = data.get(rowIndex);
+            switch(columnIndex) {
+                case 0: return ref.getChild();
+                case 1: return ref.getParent();
+                case 2: return ref.getPosition();
+                case 3: return ref.getRole();
+                default:
+                    assert false: "Illegal column index";
+            }
+            return null;
+        }
+
+    }
+
+    private static class RelationMemberTableColumnModel extends DefaultTableColumnModel{
+
+        protected void createColumns() {
+            TableColumn col = null;
+
+            // column 0 - To Delete
+            col = new TableColumn(0);
+            col.setHeaderValue(tr("To delete"));
+            col.setResizable(true);
+            col.setWidth(100);
+            col.setPreferredWidth(100);
+            col.setCellRenderer(new OsmPrimitivRenderer());
+            addColumn(col);
+
+            // column 0 - From Relation
+            col = new TableColumn(1);
+            col.setHeaderValue(tr("From Relation"));
+            col.setResizable(true);
+            col.setWidth(100);
+            col.setPreferredWidth(100);
+            col.setCellRenderer(new OsmPrimitivRenderer());
+            addColumn(col);
+
+            // column 1 - Pos.
+            col = new TableColumn(2);
+            col.setHeaderValue(tr("Pos."));
+            col.setResizable(true);
+            col.setWidth(30);
+            col.setPreferredWidth(30);
+            addColumn(col);
+
+            // column 2 - Role
+            col = new TableColumn(3);
+            col.setHeaderValue(tr("Role"));
+            col.setResizable(true);
+            col.setWidth(50);
+            col.setPreferredWidth(50);
+            addColumn(col);
+        }
+
+        public RelationMemberTableColumnModel() {
+            createColumns();
+        }
+    }
+
+    class OKAction extends AbstractAction {
+        public OKAction() {
+            putValue(NAME, tr("OK"));
+            putValue(SMALL_ICON, ImageProvider.get("ok"));
+            putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and remove the object from the relations"));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            setCanceled(false);
+            setVisible(false);
+        }
+    }
+
+    class CancelAction extends AbstractAction {
+        public CancelAction() {
+            putValue(NAME, tr("Cancel"));
+            putValue(SMALL_ICON, ImageProvider.get("cancel"));
+            putValue(SHORT_DESCRIPTION, tr("Click to close the dialog and to abort deleting the objects"));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            setCanceled(true);
+            setVisible(false);
+        }
+    }
+
+    class WindowEventHandler extends WindowAdapter {
+
+        @Override
+        public void windowClosing(WindowEvent e) {
+            setCanceled(true);
+        }
+
+        @Override
+        public void windowOpened(WindowEvent e) {
+            btnOK.requestFocusInWindow();
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java	(revision 2308)
@@ -192,5 +192,5 @@
 
         public void actionPerformed(ActionEvent arg0) {
-            HelpBrowserProxy.getInstance().setUrlForHelpTopic("/Help/Dialog/ConflictDialog");
+            HelpBrowserProxy.getInstance().setUrlForHelpTopic("/Dialog/ConflictDialog");
         }
     }
Index: /trunk/src/org/openstreetmap/josm/gui/help/ContextSensitiveHelpAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/help/ContextSensitiveHelpAction.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/gui/help/ContextSensitiveHelpAction.java	(revision 2308)
@@ -19,4 +19,8 @@
     private String helpTopic;
 
+    /**
+     * 
+     * @param helpTopic
+     */
     public ContextSensitiveHelpAction(String helpTopic) {
         putValue(SHORT_DESCRIPTION, tr("Show help information"));
Index: /trunk/src/org/openstreetmap/josm/gui/help/HelpApplication.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/help/HelpApplication.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/gui/help/HelpApplication.java	(revision 2308)
@@ -6,4 +6,8 @@
 import java.awt.Rectangle;
 import java.awt.Toolkit;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.Thread.UncaughtExceptionHandler;
 import java.util.Arrays;
 import java.util.Collection;
@@ -12,4 +16,11 @@
 import java.util.List;
 import java.util.Map;
+import java.util.logging.FileHandler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;
+
+import javax.swing.JOptionPane;
 
 import org.openstreetmap.josm.Main;
@@ -23,4 +34,5 @@
  */
 public class HelpApplication {
+    static private final Logger logger = Logger.getLogger(HelpApplication.class.getName());
     private HelpBrowser browser;
     private HelpBrowserCommandProcessor commandProcessor;
@@ -82,4 +94,13 @@
 
         MainApplication.preConstructorInit(args);
+        Thread.setDefaultUncaughtExceptionHandler(
+                new UncaughtExceptionHandler() {
+                    public void uncaughtException(Thread t, Throwable e) {
+                        StringWriter sw = new StringWriter();
+                        e.printStackTrace(new PrintWriter(sw));
+                        logger.log(Level.SEVERE, sw.getBuffer().toString());
+                    }
+                }
+        );
 
         new HelpApplication().start();
Index: /trunk/src/org/openstreetmap/josm/gui/help/HelpBrowser.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/help/HelpBrowser.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/gui/help/HelpBrowser.java	(revision 2308)
@@ -2,7 +2,10 @@
 package org.openstreetmap.josm.gui.help;
 
+import static org.openstreetmap.josm.gui.help.HelpUtil.buildAbsoluteHelpTopic;
+import static org.openstreetmap.josm.gui.help.HelpUtil.getHelpTopicEditUrl;
 import static org.openstreetmap.josm.tools.I18n.tr;
 
 import java.awt.BorderLayout;
+import java.awt.Rectangle;
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
@@ -12,6 +15,8 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.util.Locale;
 import java.util.Observable;
 import java.util.Observer;
+import java.util.logging.Logger;
 
 import javax.swing.AbstractAction;
@@ -22,4 +27,5 @@
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.JScrollBar;
 import javax.swing.JScrollPane;
 import javax.swing.JSeparator;
@@ -28,15 +34,23 @@
 import javax.swing.event.HyperlinkEvent;
 import javax.swing.event.HyperlinkListener;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.html.HTMLDocument;
 import javax.swing.text.html.HTMLEditorKit;
 import javax.swing.text.html.StyleSheet;
+import javax.swing.text.html.HTML.Tag;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
 import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.LanguageInfo;
 import org.openstreetmap.josm.tools.OpenBrowser;
-import org.openstreetmap.josm.tools.WikiReader;
 
 public class HelpBrowser extends JFrame {
-
+    static private final Logger logger = Logger.getLogger(HelpBrowser.class.getName());
+
+    /** the unique instance */
     private static HelpBrowser instance;
 
@@ -61,5 +75,5 @@
     static public void launchBrowser(String helpTopic) {
         HelpBrowser browser = getInstance();
-        browser.setUrlForHelpTopic(helpTopic);
+        browser.openHelpTopic(helpTopic);
         browser.setVisible(true);
         browser.toFront();
@@ -68,4 +82,6 @@
     /** the help browser */
     private JEditorPane help;
+    private JScrollPane spHelp;
+
     /** the help browser history */
     private HelpBrowserHistory history;
@@ -74,8 +90,5 @@
     private String url;
 
-    private String languageCode = LanguageInfo.getLanguageCodeWiki();
-    private String baseurl = Main.pref.get("help.baseurl", "http://josm.openstreetmap.de");
-    private String pathbase = Main.pref.get("help.pathbase", "/wiki/");
-    private WikiReader reader = new WikiReader(baseurl);
+    private HelpContentReader reader;
 
     /**
@@ -126,21 +139,6 @@
         help.setEditorKit(kit);
         help.setEditable(false);
-        help.addHyperlinkListener(new HyperlinkListener(){
-            public void hyperlinkUpdate(HyperlinkEvent e) {
-                if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED)
-                    return;
-                if (e.getURL() == null) {
-                    help.setText("<html>404 not found</html>");
-                } else if (e.getURL().toString().endsWith("action=edit")) {
-                    OpenBrowser.displayUrl(e.getURL().toString());
-                } else {
-                    url = e.getURL().toString();
-                    setUrl(e.getURL().toString());
-                }
-            }
-        });
+        help.addHyperlinkListener(new HyperlinkHandler());
         help.setContentType("text/html");
-
-
         history = new HelpBrowserHistory(this);
 
@@ -148,5 +146,5 @@
         setContentPane(p);
 
-        p.add(new JScrollPane(help), BorderLayout.CENTER);
+        p.add(spHelp = new JScrollPane(help), BorderLayout.CENTER);
 
         addWindowListener(new WindowAdapter(){
@@ -168,35 +166,132 @@
 
     public HelpBrowser() {
+        reader = new HelpContentReader(HelpUtil.getWikiBaseUrl());
         build();
     }
 
+    /**
+     * Replies the current URL
+     * 
+     * @return the current URL
+     */
     public String getUrl() {
         return url;
     }
 
-    protected void loadUrl(String url) {
-        String langurl = url;
-        if(url.startsWith(baseurl+pathbase)){
-            int i = pathbase.length()+baseurl.length();
-            String title = url.substring(i);
-            if(languageCode.length() != 0 && !title.startsWith(languageCode)) {
-                title = languageCode + title;
-            }
-            langurl = url.substring(0, i) + title;
-        }
-        boolean loaded = false;
-        if(!langurl.equals(this.url) && !langurl.equals(url)){
-            loaded = loadHelpUrl(url, langurl, true);
-        }
-        if(!loaded) {
-            loaded = loadHelpUrl(url, langurl, false);
-        }
-        if(!loaded) {
-            help.setText(tr("Error while loading page {0}",url));
-        }
-    }
-
-    public void setUrl(String url) {
-        loadUrl(url);
+    /**
+     * Displays a warning page when a help topic doesn't exist yet.
+     * 
+     * @param relativeHelpTopic the help topic
+     */
+    protected void handleMissingHelpContent(String relativeHelpTopic) {
+        String message = tr("<html><p class=\"warning-header\">Help content for help topic missing</p>"
+                + "<p class=\"warning-body\">Help content for the help topic <strong>{0}</strong> is "
+                + "not available yet. It is missing both in your local language ({1}) and in english.<br><br>"
+                + "Please help to improve the JOSM help system and fill in the missing information."
+                + "You can both edit the <a href=\"{2}\">help topic in your local language ({1})</a> and "
+                + "the <a href=\"{3}\">help topic in english</a>."
+                + "</p></html>",
+                relativeHelpTopic,
+                Locale.getDefault().getDisplayName(),
+                getHelpTopicEditUrl(buildAbsoluteHelpTopic(relativeHelpTopic)),
+                getHelpTopicEditUrl(buildAbsoluteHelpTopic(relativeHelpTopic, Locale.ENGLISH))
+        );
+        help.setText(message);
+    }
+
+    /**
+     * Displays a error page if a help topic couldn't be loaded because of network or IO error.
+     * 
+     * @param relativeHelpTopic the help topic
+     * @param e the exception
+     */
+    protected void handleHelpContentReaderException(String relativeHelpTopic, HelpContentReaderException e) {
+        String message = tr("<html><p class=\"error-header\">Error when retrieving help information</p>"
+                + "<p class=\"error-body\">The content for the help topic <strong>{0}</strong> could "
+                + "not be loaded. The error message is (untranslated):<br>"
+                + "<tt>{1}</tt>"
+                + "</p></html>",
+                relativeHelpTopic,
+                e.toString()
+        );
+        help.setText(message);
+    }
+
+    protected void scrollToTop() {
+        JScrollBar sb = spHelp.getVerticalScrollBar();
+        sb.setValue(sb.getMinimum());
+    }
+
+    /**
+     * Loads a help topic given by a relative help topic name (i.e. "/Action/New")
+     * 
+     * First tries to load the language specific help topic. If it is missing, tries to
+     * load the topic in english.
+     * 
+     * @param relativeHelpTopic the relative help topic
+     */
+    protected void loadRelativeHelpTopic(String relativeHelpTopic) {
+        String url = HelpUtil.getHelpTopicUrl(HelpUtil.buildAbsoluteHelpTopic(relativeHelpTopic));
+        String content = null;
+        try {
+            content = reader.fetchHelpTopicContent(url);
+        } catch(MissingHelpContentException e) {
+            url = HelpUtil.getHelpTopicUrl(HelpUtil.buildAbsoluteHelpTopic(relativeHelpTopic, Locale.ENGLISH));
+            try {
+                logger.info("fetching url: " + url);
+                content = reader.fetchHelpTopicContent(url);
+            } catch(MissingHelpContentException e1) {
+                handleMissingHelpContent(relativeHelpTopic);
+                return;
+            } catch(HelpContentReaderException e1) {
+                e1.printStackTrace();
+                handleHelpContentReaderException(relativeHelpTopic,e1);
+                return;
+            }
+        } catch(HelpContentReaderException e) {
+            e.printStackTrace();
+            handleHelpContentReaderException(relativeHelpTopic, e);
+            return;
+        }
+        help.setText(content);
+        history.setCurrentUrl(url);
+        this.url = url;
+        scrollToTop();
+    }
+
+    /**
+     * Loads a help topic given by an absolute help topic name, i.e.
+     * "/De:Help/Action/New"
+     * 
+     * @param absoluteHelpTopic the absolute help topic name
+     */
+    protected void loadAbsoluteHelpTopic(String absoluteHelpTopic) {
+        String url = HelpUtil.getHelpTopicUrl(absoluteHelpTopic);
+        String content = null;
+        try {
+            content = reader.fetchHelpTopicContent(url);
+        } catch(MissingHelpContentException e) {
+            handleMissingHelpContent(absoluteHelpTopic);
+            return;
+        } catch(HelpContentReaderException e) {
+            e.printStackTrace();
+            handleHelpContentReaderException(absoluteHelpTopic, e);
+            return;
+        }
+        help.setText(content);
+        history.setCurrentUrl(url);
+        this.url = url;
+        scrollToTop();
+    }
+
+    /**
+     * Opens an URL and displays the content.
+     * 
+     *  If the URL is the locator of an absolute help topic, help content is loaded from
+     *  the JOSM wiki. Otherwise, the help browser loads the page from the given URL
+     * 
+     * @param url the url
+     */
+    public void openUrl(String url) {
         if (!isVisible()) {
             setVisible(true);
@@ -205,33 +300,46 @@
             toFront();
         }
-        history.setCurrentUrl(url);
-    }
-
-    public void setUrlForHelpTopic(String topic) {
-        setUrl(baseurl+pathbase+ topic);
-    }
-
-    protected boolean loadHelpUrl(String url, String localizedUrl, boolean useLocalizedUrl){
-        this.url = useLocalizedUrl ? localizedUrl : url;
-        boolean loaded = false;
-        try {
-            String txt = reader.read(this.url);
-            if(txt.length() == 0){
-                if(useLocalizedUrl)
-                    throw new IOException();
-                if(url.equals(localizedUrl)){
-                    txt = ("<HTML>"+tr("Help page missing. Create it in <A HREF=\"{0}\">English</A>.",
-                            url+"?action=edit")+"</HTML>");
-                } else{
-                    txt = ("<HTML>"+tr("Help page missing. Create it in <A HREF=\"{0}\">English</A> or <A HREF=\"{1}\">your language</A>.",
-                            url+"?action=edit", localizedUrl+"?action=edit")+"</HTML>");
-                }
-            }
-            help.setText(txt);
-            help.setCaretPosition(0);
-            loaded = true;
-        } catch (IOException ex) {
-        }
-        return loaded;
+        String helpTopic = HelpUtil.extractAbsoluteHelpTopic(url);
+        if (helpTopic == null) {
+            try {
+                this.url = url;
+                help.setPage(url);
+            } catch(IOException e) {
+                HelpAwareOptionPane.showOptionDialog(
+                        Main.parent,
+                        tr(
+                                "<html>Failed to open help page for url {0}.<br>"
+                                + "This is most likely due to a network problem, please check your<br>"
+                                + "your internet connection</html>",
+                                url.toString()
+                        ),
+                        tr("Failed to open URL"),
+                        JOptionPane.ERROR_MESSAGE,
+                        null, /* no icon */
+                        null, /* standard options, just OK button */
+                        null, /* default is standard */
+                        null /* no help context */
+                );
+            }
+            history.setCurrentUrl(url);
+        } else {
+            loadAbsoluteHelpTopic(helpTopic);
+        }
+    }
+
+    /**
+     * Loads and displays the help information for a help topic given
+     * by a relative help topic name, i.e. "/Action/New"
+     * 
+     * @param relativeHelpTopic the relative help topic
+     */
+    public void openHelpTopic(String relativeHelpTopic) {
+        if (!isVisible()) {
+            setVisible(true);
+            toFront();
+        } else {
+            toFront();
+        }
+        loadRelativeHelpTopic(relativeHelpTopic);
     }
 
@@ -256,8 +364,15 @@
 
         public void actionPerformed(ActionEvent e) {
-            if (!getUrl().startsWith(baseurl)) {
+            if (!getUrl().startsWith(HelpUtil.getWikiBaseHelpUrl())) {
+                String message = tr(
+                        "<html>The current URL <tt>{0}</tt><br>"
+                        + "is an external URL. Editing is only possible for help topics<br>"
+                        + "on the help server <tt>{1}</tt>.</html>",
+                        getUrl(),
+                        HelpUtil.getWikiBaseUrl()
+                );
                 JOptionPane.showMessageDialog(
                         Main.parent,
-                        tr("Can only edit help pages from JOSM Online Help"),
+                        message,
                         tr("Warning"),
                         JOptionPane.WARNING_MESSAGE
@@ -265,4 +380,6 @@
                 return;
             }
+            String url = getUrl();
+            url = url.replaceAll("#[^#]*$", "");
             OpenBrowser.displayUrl(url+"?action=edit");
         }
@@ -277,5 +394,5 @@
 
         public void actionPerformed(ActionEvent e) {
-            setUrl(url);
+            openUrl(getUrl());
         }
     }
@@ -328,5 +445,95 @@
 
         public void actionPerformed(ActionEvent e) {
-            setUrlForHelpTopic("Help");
+            openHelpTopic("/");
+        }
+    }
+
+    class HyperlinkHandler implements HyperlinkListener {
+
+        /**
+         * Scrolls the help browser to the element with id <code>id</code>
+         * 
+         * @param id the id
+         * @return true, if an element with this id was found and scrolling was successful; false, otherwise
+         */
+        protected boolean scrollToElementWithId(String id) {
+            Document d = help.getDocument();
+            if (d instanceof HTMLDocument) {
+                HTMLDocument doc = (HTMLDocument) d;
+                Element element = doc.getElement(id);
+                try {
+                    Rectangle r = help.modelToView(element.getStartOffset());
+                    if (r != null) {
+                        Rectangle vis = help.getVisibleRect();
+                        r.height = vis.height;
+                        help.scrollRectToVisible(r);
+                        return true;
+                    }
+                } catch(BadLocationException e) {
+                    System.err.println(tr("Warning: bad location in HTML document. Exception was: " + e.toString()));
+                    e.printStackTrace();
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Checks whether the hyperlink event originated on a <a ...> element with
+         * a relative href consisting of a URL fragment only, i.e.
+         * <a href="#thisIsALocalFragment">. If so, replies the fragment, i.e.
+         * "thisIsALocalFragment".
+         * 
+         * Otherwise, replies null
+         * 
+         * @param e the hyperlink event
+         * @return the local fragment
+         */
+        protected String getUrlFragment(HyperlinkEvent e) {
+            AttributeSet set = e.getSourceElement().getAttributes();
+            Object value = set.getAttribute(Tag.A);
+            if (value == null || ! (value instanceof SimpleAttributeSet)) return null;
+            SimpleAttributeSet atts = (SimpleAttributeSet)value;
+            value = atts.getAttribute(javax.swing.text.html.HTML.Attribute.HREF);
+            if (value == null) return null;
+            String s = (String)value;
+            if (s.matches("#.*"))
+                return s.substring(1);
+            return null;
+        }
+
+        public void hyperlinkUpdate(HyperlinkEvent e) {
+            if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED)
+                return;
+            if (e.getURL() == null) {
+                // Probably hyperlink event on a an A-element with a href consisting of
+                // a fragment only, i.e. "#ALocalFragment".
+                //
+                String fragment = getUrlFragment(e);
+                if (fragment != null) {
+                    // first try to scroll to an element with id==fragment. This is the way
+                    // table of contents are built in the JOSM wiki. If this fails, try to
+                    // scroll to a <A name="..."> element.
+                    //
+                    if (!scrollToElementWithId(fragment)) {
+                        help.scrollToReference(fragment);
+                    }
+                } else {
+                    HelpAwareOptionPane.showOptionDialog(
+                            Main.parent,
+                            tr("Failed to open help page. The target URL is empty."),
+                            tr("Failed to open help page"),
+                            JOptionPane.ERROR_MESSAGE,
+                            null, /* no icon */
+                            null, /* standard options, just OK button */
+                            null, /* default is standard */
+                            null /* no help context */
+                    );
+                }
+            } else if (e.getURL().toString().endsWith("action=edit")) {
+                OpenBrowser.displayUrl(e.getURL().toString());
+            } else {
+                url = e.getURL().toString();
+                openUrl(e.getURL().toString());
+            }
         }
     }
Index: /trunk/src/org/openstreetmap/josm/gui/help/HelpBrowserCommandProcessor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/help/HelpBrowserCommandProcessor.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/gui/help/HelpBrowserCommandProcessor.java	(revision 2308)
@@ -5,4 +5,6 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 import javax.swing.SwingUtilities;
@@ -16,4 +18,5 @@
  */
 public class HelpBrowserCommandProcessor implements Runnable {
+    private static final Logger logger = Logger.getLogger(HelpBrowserCommandProcessor.class.getName());
 
     /** the controlled help browser*/
@@ -31,10 +34,10 @@
      * Show the help page for help topic <code>helpTopic</code>.
      * 
-     * @param helpTopics the help topic
+     * @param helpTopic the help topic
      */
-    protected void setUrlForHelpTopics(final String helpTopics) {
+    protected void setUrlForHelpTopic(final String helpTopic) {
         Runnable r = new Runnable() {
             public void run() {
-                browser.setUrlForHelpTopic(helpTopics);
+                browser.openHelpTopic(helpTopic);
                 browser.setVisible(true);
                 browser.toFront();
@@ -65,7 +68,10 @@
         while(true) {
             String cmd = null;
+
             try {
                 cmd = reader.readLine();
+                logger.info("got command: " + cmd);
             } catch(IOException e) {
+                logger.log(Level.SEVERE,e.toString());
                 System.out.println(tr("Failed to read command. Exiting help browser. Exception was:" + e.toString()));
                 System.exit(1);
@@ -73,7 +79,7 @@
             if (cmd.startsWith("exit")) {
                 exit();
-            } else if (cmd.startsWith("setUrlForHelpTopics ")) {
-                String helpTopics = cmd.substring("setUrlForHelpTopics ".length());
-                setUrlForHelpTopics(helpTopics);
+            } else if (cmd.startsWith("setUrlForHelpTopic ")) {
+                String helpTopic = cmd.substring("setUrlForHelpTopic ".length());
+                setUrlForHelpTopic(helpTopic);
             }
         }
Index: /trunk/src/org/openstreetmap/josm/gui/help/HelpBrowserHistory.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/help/HelpBrowserHistory.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/gui/help/HelpBrowserHistory.java	(revision 2308)
@@ -35,5 +35,5 @@
         if (historyPos < 0) return;
         String url = history.get(historyPos);
-        browser.loadUrl(url);
+        browser.openUrl(url);
         setChanged();
         notifyObservers();
@@ -44,5 +44,5 @@
         if (historyPos >= history.size()) return;
         String url = history.get(historyPos);
-        browser.loadUrl(url);
+        browser.openUrl(url);
         setChanged();
         notifyObservers();
Index: /trunk/src/org/openstreetmap/josm/gui/help/HelpBrowserProxy.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/help/HelpBrowserProxy.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/gui/help/HelpBrowserProxy.java	(revision 2308)
@@ -77,9 +77,9 @@
     /**
      * Direct the help browser to the help page for help topic
-     * <code>helpTopic</code>
+     * <code>relativeHelpTopic</code>
      * 
-     * @param helpTopic the help topic
+     * @param relativeHelpTopic the help topic
      */
-    public void setUrlForHelpTopic(String helpTopic) {
+    public void setUrlForHelpTopic(String relativeHelpTopic) {
         if (helpBrowserProcess == null) {
             launch();
@@ -95,5 +95,5 @@
             return;
         }
-        pw.println("setUrlForHelpTopics " + helpTopic);
+        pw.println("setUrlForHelpTopic " + relativeHelpTopic);
         pw.flush();
     }
Index: /trunk/src/org/openstreetmap/josm/gui/help/HelpContentReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/help/HelpContentReader.java	(revision 2308)
+++ /trunk/src/org/openstreetmap/josm/gui/help/HelpContentReader.java	(revision 2308)
@@ -0,0 +1,138 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.help;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.openstreetmap.josm.tools.WikiReader;
+
+/**
+ * Reads help content from the JOSM Wiki and prepares it for rendering in the internal
+ * help browser.
+ * 
+ * The help content has to be <strong>filtered</strong> because only the main content <tt>&lt;div&gt;</tt>
+ * of a Wiki help page is displayed in the internal help browser.
+ * 
+ * It also has to be <strong>transformed</strong> because the internal help browser required slightly
+ * different HTML than what is provided by the Wiki.
+ * 
+ * @see WikiReader
+ */
+public class HelpContentReader {
+
+    /** the base url */
+    private String baseUrl;
+
+    /**
+     * constructor
+     * 
+     * @param baseUrl the base url of the JOSM help wiki, i.e. http://josm.openstreetmap.org
+     */
+    public HelpContentReader(String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
+    /**
+     * Fetches the content of a help topic from the JOSM wiki.
+     * 
+     * @param helpTopicUrl  the absolute help topic URL
+     * @return the content, filtered and transformed for being displayed in the internal help browser
+     * @throws HelpContentReaderException thrown if problem occurs
+     * @throws MissingHelpContentException thrown if this helpTopicUrl doesn't point to an existing Wiki help page
+     */
+    public String fetchHelpTopicContent(String helpTopicUrl) throws HelpContentReaderException {
+        URL url = null;
+        HttpURLConnection con = null;
+        BufferedReader in = null;
+        try {
+            url = new URL(helpTopicUrl);
+            con = (HttpURLConnection)url.openConnection();
+            con.connect();
+            in = new BufferedReader(new InputStreamReader(con.getInputStream()));
+            return prepareHelpContent(in);
+        } catch(MalformedURLException e) {
+            throw new HelpContentReaderException(e);
+        } catch(IOException e) {
+            HelpContentReaderException ex = new HelpContentReaderException(e);
+            if (con != null) {
+                try {
+                    ex.setResponseCode(con.getResponseCode());
+                } catch(IOException e1) {
+                    // ignore
+                }
+            }
+            throw ex;
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch(IOException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+    /**
+     * Reads help content from the input stream and prepares it to be rendered later
+     * in the internal help browser.
+     * 
+     * Throws a {@see MissingHelpContentException} if the content read from the stream
+     * most likely represents a stub help page.
+     * 
+     * @param in the input stream
+     * @return the content
+     * @throws HelpContentReaderException thrown if an exception occurs
+     * @throws MissingHelpContentException thrown, if the content read isn't a help page
+     */
+    protected String prepareHelpContent(BufferedReader in) throws HelpContentReaderException {
+        boolean isInContent = false;
+        boolean isInTranslationsSideBar = false;
+        boolean isExistingHelpPage = false;
+        StringBuffer sb = new StringBuffer();
+        try {
+            for (String line = in.readLine(); line != null; line = in.readLine()) {
+                if (line.contains("<div id=\"searchable\">")) {
+                    isInContent = true;
+                } else if (line.contains("<div class=\"wiki-toc trac-nav\"")) {
+                    isInTranslationsSideBar = true;
+                } else if (line.contains("<div class=\"wikipage searchable\">")) {
+                    isInContent = true;
+                } else if (line.contains("<div class=\"buttons\">")) {
+                    isInContent = false;
+                } else if (line.contains("<h3>Attachments</h3>")) {
+                    isInContent = false;
+                } else if (line.contains("<input type=\"submit\" name=\"attachfilebutton\"")) {
+                    // heuristic: if we find a button for uploading images we are in an
+                    // existing pages. Otherwise this is probably the stub page for a not yet
+                    // existing help page
+                    isExistingHelpPage = true;
+                }
+                if (isInContent && !isInTranslationsSideBar) {
+                    // add a border="0" attribute to images, otherwise the internal help browser
+                    // will render a thick  border around images inside an <a> element
+                    //
+                    // Also make sure image URLs are absolute
+                    //
+                    line = line.replaceAll("<img src=\"/", "<img border=\"0\" src=\"" + baseUrl + "/").replaceAll("href=\"/",
+                            "href=\"" + baseUrl + "/").replaceAll(" />", ">");
+                    sb.append(line);
+                    sb.append("\n");
+                } else if (isInTranslationsSideBar && line.contains("</div>")) {
+                    isInTranslationsSideBar = false;
+                }
+            }
+        } catch(IOException e) {
+            throw new HelpContentReaderException(e);
+        }
+        sb.insert(0, "<html>");
+        sb.append("<html>");
+        if (! isExistingHelpPage)
+            throw new MissingHelpContentException();
+        return sb.toString();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/help/HelpContentReaderException.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/help/HelpContentReaderException.java	(revision 2308)
+++ /trunk/src/org/openstreetmap/josm/gui/help/HelpContentReaderException.java	(revision 2308)
@@ -0,0 +1,47 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.help;
+
+public class HelpContentReaderException extends Exception {
+    private int responseCode;
+
+    public HelpContentReaderException() {
+        super();
+        // TODO Auto-generated constructor stub
+    }
+
+    public HelpContentReaderException(String message, Throwable cause) {
+        super(message, cause);
+        // TODO Auto-generated constructor stub
+    }
+
+    public HelpContentReaderException(String message) {
+        super(message);
+        // TODO Auto-generated constructor stub
+    }
+
+    public HelpContentReaderException(Throwable cause) {
+        super(cause);
+        // TODO Auto-generated constructor stub
+    }
+
+    /**
+     * Replies the HTTP response code related to the wiki access exception.
+     * If no HTTP response code is available, 0 is replied.
+     * 
+     * @return the http response code
+     */
+    public int getResponseCode() {
+        return responseCode;
+    }
+
+    /**
+     * Sets the HTTP response code
+     * 
+     * @param responseCode the response code
+     */
+    public void setResponseCode(int responseCode) {
+        this.responseCode = responseCode;
+    }
+
+
+}
Index: /trunk/src/org/openstreetmap/josm/gui/help/HelpUtil.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/help/HelpUtil.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/gui/help/HelpUtil.java	(revision 2308)
@@ -2,24 +2,244 @@
 package org.openstreetmap.josm.gui.help;
 
+import java.awt.Component;
 import java.awt.event.KeyEvent;
-
+import java.util.Locale;
+
+import javax.swing.AbstractButton;
+import javax.swing.Action;
 import javax.swing.JComponent;
+import javax.swing.JMenu;
 import javax.swing.KeyStroke;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.tools.LanguageInfo;
 
 public class HelpUtil {
 
     /**
+     * Replies the base wiki URL.
+     * 
+     * @return the base wiki URL
+     */
+    static public String getWikiBaseUrl() {
+        return Main.pref.get("help.baseurl", "http://josm.openstreetmap.de");
+    }
+
+    /**
+     * Replies the base wiki URL for help pages
+     * 
+     * @return the base wiki URL for help pages
+     */
+    static public String getWikiBaseHelpUrl() {
+        return getWikiBaseUrl() + "/wiki";
+    }
+
+    /**
+     * Replies the URL on the wiki for an absolute help topic. The URL is encoded in UTF-8.
+     * 
+     * @param absoluteHelpTopic the absolute help topic
+     * @return the url
+     * @see #buildAbsoluteHelpTopic(String)
+     * @see #buildAbsoluteHelpTopic(String, Locale)
+     */
+    static public String getHelpTopicUrl(String absoluteHelpTopic) {
+        String ret = getWikiBaseHelpUrl();
+        ret = ret.replaceAll("\\/+$", "");
+        absoluteHelpTopic  =absoluteHelpTopic.replace(" ", "%20");
+        absoluteHelpTopic = absoluteHelpTopic.replaceAll("^\\/+", "/");
+        return ret + absoluteHelpTopic;
+    }
+
+    /**
+     * Replies the URL to the edit page for the absolute help topic.
+     * 
+     * @param absoluteHelpTopic the absolute help topic
+     * @return the URL to the edit page
+     */
+    static public String getHelpTopicEditUrl(String absoluteHelpTopic) {
+        String topicUrl = getHelpTopicUrl(absoluteHelpTopic);
+        topicUrl = topicUrl.replaceAll("#[^#]*$", ""); // remove optional fragment
+        return topicUrl + "?action=edit";
+    }
+
+    /**
+     * Extracts the relative help topic from an URL. Replies null, if
+     * no relative help topic is found.
+     * 
+     * @param url the url
+     * @return the relative help topic in the URL, i.e. "/Action/New"
+     */
+    static public String extractRelativeHelpTopic(String url) {
+        String topic = extractAbsoluteHelpTopic(url);
+        if (topic == null) return null;
+        String pattern = "/[A-Z][a-z]:" + getHelpTopicPrefix(Locale.ENGLISH).replaceAll("^\\/+", "");
+        if (url.matches(pattern))
+            return topic.substring(pattern.length());
+        return null;
+    }
+
+    /**
+     * Extracts the absolute help topic from an URL. Replies null, if
+     * no absolute help topic is found.
+     * 
+     * @param url the url
+     * @return the absolute help topic in the URL, i.e. "/De:Help/Action/New"
+     */
+    static public String extractAbsoluteHelpTopic(String url) {
+        if (!url.startsWith(getWikiBaseHelpUrl())) return null;
+        url = url.substring(getWikiBaseHelpUrl().length());
+        String prefix = getHelpTopicPrefix(Locale.ENGLISH);
+        if (url.startsWith(prefix))
+            return url;
+
+        String pattern = "/[A-Z][a-z]:" + prefix.replaceAll("^\\/+", "");
+        if (url.matches(pattern))
+            return url;
+
+        return null;
+    }
+
+    /**
+     * Replies the help topic prefix for the current locale. Examples:
+     * <ul>
+     *   <li>/Help if the current locale is a locale with language "en"</li>
+     *   <li>/De:Help if the current locale is a locale with language "de"</li>
+     * </ul>
+     * 
+     * @return the help topic prefix
+     * @see #getHelpTopicPrefix(Locale)
+     */
+    static public String getHelpTopicPrefix() {
+        return getHelpTopicPrefix(Locale.getDefault());
+    }
+
+    /**
+     * Replies the help topic prefix for the given locale. Examples:
+     * <ul>
+     *   <li>/Help if the  locale is a locale with language "en"</li>
+     *   <li>/De:Help if the  locale is a locale with language "de"</li>
+     * </ul>
+     * 
+     * @param locale the locale. {@see Locale#ENGLISH} assumed, if null.
+     * @return the help topic prefix
+     * @see #getHelpTopicPrefix(Locale)
+     */
+    static public String getHelpTopicPrefix(Locale locale) {
+        if (locale == null) {
+            locale = Locale.ENGLISH;
+        }
+        String ret = Main.pref.get("help.pathhelp", "/Help");
+        ret = ret.replaceAll("^\\/+", ""); // remove leading /
+        ret = "/" + LanguageInfo.getWikiLanguagePrefix(locale) + ret;
+        return ret;
+    }
+
+    /**
+     * Replies the absolute, localized help topic for the given topic.
+     * 
+     * Example: for a topic "/Dialog/RelationEditor" and the locale "de", this method
+     * replies "/De:Help/Dialog/RelationEditor"
+     * 
+     * @param topic the relative help topic. Home help topic assumed, if null.
+     * @param locale the locale. {@see Locale#ENGLISH} assumed, if null.
+     * @return the absolute, localized help topic
+     */
+    static public String buildAbsoluteHelpTopic(String topic, Locale locale) {
+        if (locale == null) {
+            locale = Locale.ENGLISH;
+        }
+        if (topic == null || topic.trim().length() == 0 || topic.trim().equals("/"))
+            return getHelpTopicPrefix(locale);
+        String ret = getHelpTopicPrefix(locale);
+        if (topic.startsWith("/")) {
+            ret += topic;
+        } else {
+            ret += "/" + topic;
+        }
+        ret.replaceAll("\\/+", "\\/"); // just in case, collapse sequences of //
+        return ret;
+    }
+
+    /**
+     * Replies the absolute, localized help topic for the given topic and the
+     * current locale.
+     * 
+     * @param topic the relative help topic. Home help topic assumed, if null.
+     * @return the absolute, localized help topic
+     * @see Locale#getDefault()
+     * @see #buildAbsoluteHelpTopic(String, Locale)
+     */
+    static public String buildAbsoluteHelpTopic(String topic) {
+        return buildAbsoluteHelpTopic(topic, Locale.getDefault());
+    }
+
+    /**
+     * Replies the context specific help topic configured for <code>context</code>.
+     * 
+     * @return the help topic. null, if no context specific help topic is found
+     */
+    static public String getContextSpecificHelpTopic(Object context) {
+        if (context == null)
+            return null;
+        if (context instanceof Helpful)
+            return ((Helpful)context).helpTopic();
+        if (context instanceof JMenu) {
+            JMenu b = (JMenu)context;
+            if (b.getClientProperty("help") != null)
+                return (String)b.getClientProperty("help");
+            return null;
+        }
+        if (context instanceof AbstractButton) {
+            AbstractButton b = (AbstractButton)context;
+            if (b.getClientProperty("help") != null)
+                return (String)b.getClientProperty("help");
+            return getContextSpecificHelpTopic(b.getAction());
+        }
+        if (context instanceof Action)
+            return (String)((Action)context).getValue("help");
+        if (context instanceof JComponent && ((JComponent)context).getClientProperty("help") != null)
+            return (String)((JComponent)context).getClientProperty("help");
+        if (context instanceof Component)
+            return getContextSpecificHelpTopic(((Component)context).getParent());
+        return null;
+    }
+
+    /**
      * Makes a component aware of context sensitive help.
      * 
-     * @param component the component
-     * @param topic the help topic
-     */
-    static public void setHelpContext(JComponent component, String topic) {
+     * A relative help topic doesn't start with /Help and doesn't include a locale
+     * code. Example: /Dialog/RelationEditor is a relative help topic, /De:Help/Dialog/RelationEditor
+     * is not.
+     * 
+     * 
+     * @param component the component  the component
+     * @param topic the help topic. Set to the default help topic if null.
+     */
+    static public void setHelpContext(JComponent component, String relativeHelpTopic) {
+        if (relativeHelpTopic == null) {
+            relativeHelpTopic = "";
+        }
         component.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F1,0), "help");
         component.getActionMap().put("help", Main.main.menu.help);
-        component.putClientProperty("help", topic);
-    }
-
+        component.putClientProperty("help", relativeHelpTopic);
+    }
+
+    /**
+     * This is a simple marker method for help topic literals. If you declare a help
+     * topic literal in the source you should enclose it in ht(...).
+     * 
+     *  <strong>Example</strong>
+     *  <pre>
+     *     String helpTopic = ht("/Dialog/RelationEditor");
+     *  or
+     *     putValue("help", ht("/Dialog/RelationEditor"));
+     *  </pre>
+     * 
+     * 
+     * @param helpTopic
+     */
+    static public String ht(String helpTopic) {
+        // this is just a marker method
+        return helpTopic;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/help/MissingHelpContentException.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/help/MissingHelpContentException.java	(revision 2308)
+++ /trunk/src/org/openstreetmap/josm/gui/help/MissingHelpContentException.java	(revision 2308)
@@ -0,0 +1,25 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.help;
+
+public class MissingHelpContentException extends HelpContentReaderException {
+
+    public MissingHelpContentException() {
+        super();
+        // TODO Auto-generated constructor stub
+    }
+
+    public MissingHelpContentException(String message, Throwable cause) {
+        super(message, cause);
+        // TODO Auto-generated constructor stub
+    }
+
+    public MissingHelpContentException(String message) {
+        super(message);
+        // TODO Auto-generated constructor stub
+    }
+
+    public MissingHelpContentException(Throwable cause) {
+        super(cause);
+        // TODO Auto-generated constructor stub
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/help/help-browser.css
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/help/help-browser.css	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/gui/help/help-browser.css	(revision 2308)
@@ -19,2 +19,27 @@
 tt {font-family: Courier New}
 pre {font-family: Courier New}
+.warning-header {
+	font-family: Arial, sans-serif; 
+	font-size:24pt; 
+	font-weight:bold
+}
+.warning-body {
+	background-color:rgb(253,255,221);
+	padding: 10pt; 
+	border-color:rgb(128,128,128);
+	border-style: solid; 
+	border-width: 1px;
+}
+
+.error-header {
+	font-family: Arial, sans-serif; 
+	font-size:24pt; 
+	font-weight:bold
+}
+.error-body {
+	background-color:rgb(254,195,190);
+	padding: 10pt; 
+	border-color:rgb(128,128,128);
+	border-style: solid; 
+	border-width: 1px;
+}
Index: /trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java	(revision 2308)
@@ -192,7 +192,6 @@
                 JComponent.WHEN_IN_FOCUSED_WINDOW
         );
-
-        pnl.add(new SideButton(new ContextSensitiveHelpAction("/Help/Dialogs/UploadDialog")));
-        HelpUtil.setHelpContext(getRootPane(),"/Help/Dialogs/UploadDialog");
+        pnl.add(new SideButton(new ContextSensitiveHelpAction("/Dialogs/UploadDialog")));
+        HelpUtil.setHelpContext(getRootPane(),"/Dialogs/UploadDialog");
         return pnl;
     }
Index: /trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 2308)
@@ -376,5 +376,5 @@
                         public void actionPerformed(ActionEvent e) {
                             HelpBrowser b = new HelpBrowser();
-                            b.setUrlForHelpTopic("Help/Concepts/Conflict");
+                            b.openHelpTopic("Help/Concepts/Conflict");
                             b.setVisible(true);
                         }
@@ -383,5 +383,5 @@
             dialog.setContentPane(pane);
             dialog.pack();
-            HelpUtil.setHelpContext(dialog.getRootPane(), "Concepts/Conflict");
+            HelpUtil.setHelpContext(dialog.getRootPane(), "/Concepts/Conflict");
             WindowGeometry.centerOnScreen(dialog.getSize()).applySafe(dialog);
             dialog.setVisible(true);
Index: /trunk/src/org/openstreetmap/josm/tools/LanguageInfo.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/LanguageInfo.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/tools/LanguageInfo.java	(revision 2308)
@@ -3,18 +3,63 @@
 
 import java.util.Locale;
+import static org.openstreetmap.josm.tools.I18n.tr;
 
 public class LanguageInfo {
-    static public String getLanguageCodeWiki()
-    {
-        String languageCode = getLanguageCode();
-        if(languageCode.equals("en"))
+
+    /**
+     * Replies the wiki language prefix for the given locale. The wiki language
+     * prefix has the form 'Xy:' where 'Xy' is a ISO 639 language code in title
+     * case.
+     * 
+     * @param locale  the locale
+     * @return the wiki language prefix
+     */
+    static public String getWikiLanguagePrefix(Locale locale) {
+        String code = getJOSMLocaleCode(locale);
+        if (code.length() == 2) {
+            if (code.equals("en")) return "";
+        } else if (code.matches("[^_]+_[^_]+")) {
+            code = code.substring(0,2);
+        } else {
+            System.err.println(tr("Warning: failed to derive wiki language prefix from JOSM locale code ''{0}''. Using default code ''en''.", code));
             return "";
-        else if(languageCode.equals("pt_BR"))
-            return "Pt:";
-        return languageCode.substring(0,1).toUpperCase() + languageCode.substring(1) + ":";
+        }
+        return code.substring(0,1).toUpperCase() + code.substring(1) + ":";
     }
-    static public String getLanguageCode()
-    {
-        String full = Locale.getDefault().toString();
+
+    /**
+     * Replies the wiki language prefix for the current locale.
+     * 
+     * @return the wiki language prefix
+     * @see Locale#getDefault()
+     * @see #getWikiLanguagePrefix(Locale)
+     */
+    static public String getWikiLanguagePrefix() {
+        return getWikiLanguagePrefix(Locale.getDefault());
+    }
+
+    /**
+     * Replies the JOSM locale code for the default locale.
+     * 
+     * @return the JOSM locale code for the default locale
+     * @see #getJOSMLocaleCode(Locale)
+     */
+    static public String getJOSMLocaleCode() {
+        return getJOSMLocaleCode(Locale.getDefault());
+    }
+
+    /**
+     * Replies the local code used by JOSM for a given locale.
+     * 
+     * In most cases JOSM uses the 2-character ISO 639 language code ({@see Locale#getLanguage()}
+     * to identify the locale of a localized resource, but in some cases it may use the
+     * programmatic name for locales, as replied by {@see Locale#toString()}.
+     * 
+     * @param locale the locale. Replies "en" if null.
+     * @return the JOSM code for the given locale
+     */
+    static public String getJOSMLocaleCode(Locale locale) {
+        if (locale == null) return "en";
+        String full = locale.toString();
         if (full.equals("iw_IL"))
             return "he";
@@ -22,13 +67,16 @@
         else if (full.equals("en_GB"))
             return full;
-        return Locale.getDefault().getLanguage();
+
+        return locale.getLanguage();
     }
+
+
     static public String getLanguageCodeXML()
     {
-        return getLanguageCode()+".";
+        return getJOSMLocaleCode()+".";
     }
     static public String getLanguageCodeManifest()
     {
-        return getLanguageCode()+"_";
+        return getJOSMLocaleCode()+"_";
     }
 }
Index: /trunk/src/org/openstreetmap/josm/tools/WikiReader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/WikiReader.java	(revision 2307)
+++ /trunk/src/org/openstreetmap/josm/tools/WikiReader.java	(revision 2308)
@@ -35,5 +35,5 @@
      * pathes etc..
      * 
-     * @return Either the string of the content of the wiki page.
+     * @return
      * @throws IOException Throws, if the page could not be loaded.
      */
@@ -46,5 +46,5 @@
 
     public String readLang(String text) {
-        String languageCode = LanguageInfo.getLanguageCodeWiki();
+        String languageCode = LanguageInfo.getWikiLanguagePrefix();
         String url = baseurl + "/wiki/" + languageCode + text;
         String res = "";
