ONOS-6730: Topo View i18n:
- augmented UiMessageHandler base class to allow injection of
  localization bundles, so that the handler can look up localized
  text when composing data to ship to the client.
- i18n'd the Summary Panel in Topo view.

Change-Id: I15010d1e2fcce72e3133a9ce40e51510c8f5146f
diff --git a/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java b/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
index c2582c9..53125a7 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
@@ -22,6 +22,7 @@
 import org.onosproject.codec.CodecContext;
 import org.onosproject.codec.CodecService;
 import org.onosproject.codec.JsonCodec;
+import org.onosproject.ui.lion.LionBundle;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -64,6 +65,7 @@
 
     private final Logger log = LoggerFactory.getLogger(getClass());
     private final Map<String, RequestHandler> handlerMap = new HashMap<>();
+    private final Map<String, LionBundle> cachedLionBundles = new HashMap<>();
 
     private final ObjectMapper mapper = new ObjectMapper();
 
@@ -181,6 +183,44 @@
     }
 
     /**
+     * Returns the set of identifiers for localization bundles that the
+     * message handler would like injected into itself, so that it can use
+     * those bundles in composing localized data to ship to the client.
+     * <p>
+     * This default implementation returns an empty set.
+     * <p>
+     * Subclasses that wish to have localization bundles injected should
+     * override this method and return the set of bundle identifiers.
+     *
+     * @return the set of identifiers of required localization bundles
+     */
+    public Set<String> requiredLionBundles() {
+        return Collections.emptySet();
+    }
+
+    /**
+     * Invoked during initialization to cache any requested localization
+     * bundles in the handler's context, so that it may subsequently look
+     * up localization strings when composing data for the client.
+     *
+     * @param bundle the bundle to cache
+     */
+    public void cacheLionBundle(LionBundle bundle) {
+        cachedLionBundles.put(bundle.id(), bundle);
+    }
+
+    /**
+     * Returns the localization bundle with the given identifier, if we
+     * requested to have it cached during initialization; null otherwise.
+     *
+     * @param id the lion bundle identifier
+     * @return the associated lion bundle
+     */
+    protected LionBundle getLionBundle(String id) {
+        return cachedLionBundles.get(id);
+    }
+
+    /**
      * Returns a freshly minted object node.
      *
      * @return new object node
diff --git a/core/api/src/main/java/org/onosproject/ui/lion/LionBundle.java b/core/api/src/main/java/org/onosproject/ui/lion/LionBundle.java
index e60ef14..780dea8 100644
--- a/core/api/src/main/java/org/onosproject/ui/lion/LionBundle.java
+++ b/core/api/src/main/java/org/onosproject/ui/lion/LionBundle.java
@@ -85,6 +85,18 @@
     }
 
     /**
+     * Returns the localized value for the given key, or, if no such mapping
+     * exists, returns the key wrapped in '%' characters.
+     *
+     * @param key the key
+     * @return the localized value (or a wrapped key placeholder)
+     */
+    public String getSafe(String key) {
+        String value = mapped.get(key);
+        return value == null ? "%" + key + "%" : value;
+    }
+
+    /**
      * Returns an immutable set of the items in this bundle.
      *
      * @return the items in this bundle
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java b/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java
index 8ce23d3..561fa76 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/PropertyPanel.java
@@ -97,11 +97,12 @@
      * Adds a property to the panel data.
      *
      * @param key   property key
+     * @param label property label (localized)
      * @param value property value
      * @return self, for chaining
      */
-    public PropertyPanel addProp(String key, String value) {
-        properties.add(new Prop(key, value));
+    public PropertyPanel addProp(String key, String label, String value) {
+        properties.add(new Prop(key, label, value));
         return this;
     }
 
@@ -109,11 +110,12 @@
      * Adds a property to the panel data, using a decimal formatter.
      *
      * @param key   property key
+     * @param label property label (localized)
      * @param value property value
      * @return self, for chaining
      */
-    public PropertyPanel addProp(String key, int value) {
-        properties.add(new Prop(key, formatter().format(value)));
+    public PropertyPanel addProp(String key, String label, int value) {
+        properties.add(new Prop(key, label, formatter().format(value)));
         return this;
     }
 
@@ -121,11 +123,12 @@
      * Adds a property to the panel data, using a decimal formatter.
      *
      * @param key   property key
+     * @param label property label (localized)
      * @param value property value
      * @return self, for chaining
      */
-    public PropertyPanel addProp(String key, long value) {
-        properties.add(new Prop(key, formatter().format(value)));
+    public PropertyPanel addProp(String key, String label, long value) {
+        properties.add(new Prop(key, label, formatter().format(value)));
         return this;
     }
 
@@ -135,11 +138,12 @@
      * value to a string.
      *
      * @param key   property key
+     * @param label property label (localized)
      * @param value property value
      * @return self, for chaining
      */
-    public PropertyPanel addProp(String key, Object value) {
-        properties.add(new Prop(key, value.toString()));
+    public PropertyPanel addProp(String key, String label, Object value) {
+        properties.add(new Prop(key, label, value.toString()));
         return this;
     }
 
@@ -150,14 +154,87 @@
      * regular expression string are stripped.
      *
      * @param key     property key
+     * @param label property label (localized)
+     * @param value   property value
+     * @param reStrip regexp characters to strip from value string
+     * @return self, for chaining
+     */
+    public PropertyPanel addProp(String key, String label,
+                                 Object value, String reStrip) {
+        String val = value.toString().replaceAll(reStrip, "");
+        properties.add(new Prop(key, label, val));
+        return this;
+    }
+
+    /*
+     * The following degenerate forms of addProp(...) for backward compatibility.
+     */
+
+    /**
+     * Adds a property to the panel data.
+     * Note that the key is used as the label.
+     *
+     * @param key   property key (also used as display label)
+     * @param value property value
+     * @return self, for chaining
+     * @see #addProp(String, String, String)
+     */
+    public PropertyPanel addProp(String key, String value) {
+        return addProp(key, key, value);
+    }
+
+    /**
+     * Adds a property to the panel data, using a decimal formatter.
+     * Note that the key is used as the label.
+     *
+     * @param key   property key (also used as display label)
+     * @param value property value
+     * @return self, for chaining
+     */
+    public PropertyPanel addProp(String key, int value) {
+        return addProp(key, key, value);
+    }
+
+    /**
+     * Adds a property to the panel data, using a decimal formatter.
+     * Note that the key is used as the label.
+     *
+     * @param key   property key (also used as display label)
+     * @param value property value
+     * @return self, for chaining
+     */
+    public PropertyPanel addProp(String key, long value) {
+        return addProp(key, key, value);
+    }
+
+    /**
+     * Adds a property to the panel data. Note that the value's
+     * {@link Object#toString toString()} method is used to convert the
+     * value to a string.
+     * Note also that the key is used as the label.
+     *
+     * @param key   property key (also used as display label)
+     * @param value property value
+     * @return self, for chaining
+     */
+    public PropertyPanel addProp(String key, Object value) {
+        return addProp(key, key, value);
+    }
+
+    /**
+     * Adds a property to the panel data. Note that the value's
+     * {@link Object#toString toString()} method is used to convert the
+     * value to a string, from which the characters defined in the given
+     * regular expression string are stripped.
+     * Note also that the key is used as the label.
+     *
+     * @param key     property key (also used as display label)
      * @param value   property value
      * @param reStrip regexp characters to strip from value string
      * @return self, for chaining
      */
     public PropertyPanel addProp(String key, Object value, String reStrip) {
-        String val = value.toString().replaceAll(reStrip, "");
-        properties.add(new Prop(key, val));
-        return this;
+        return addProp(key, key, value, reStrip);
     }
 
     /**
@@ -321,20 +398,23 @@
 
 
     /**
-     * Simple data carrier for a property, composed of a key/value pair.
+     * Simple data carrier for a property, composed of a key/label/value trio.
      */
     public static class Prop {
         private final String key;
+        private final String label;
         private final String value;
 
         /**
          * Constructs a property data value.
          *
-         * @param key   property key
+         * @param key   property key (localization key)
+         * @param label property label (localization value)
          * @param value property value
          */
-        public Prop(String key, String value) {
+        public Prop(String key, String label, String value) {
             this.key = key;
+            this.label = label;
             this.value = value;
         }
 
@@ -348,6 +428,15 @@
         }
 
         /**
+         * Returns the property's (localized) label.
+         *
+         * @return the label
+         */
+        public String label() {
+            return label;
+        }
+
+        /**
          * Returns the property's value.
          *
          * @return the value
@@ -356,6 +445,10 @@
             return value;
         }
 
+        /*
+         * NOTE: equals/hashCode are only expressed in terms of key and value.
+         */
+
         @Override
         public boolean equals(Object o) {
             if (this == o) {
@@ -378,7 +471,7 @@
 
         @Override
         public String toString() {
-            return "{" + key + " -> " + value + "}";
+            return "{" + key + "(" + label + ") -> " + value + "}";
         }
     }
 
@@ -387,7 +480,7 @@
      */
     public static class Separator extends Prop {
         public Separator() {
-            super("-", "");
+            super("-", "-", "");
         }
     }
 
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/TopoConstants.java b/core/api/src/main/java/org/onosproject/ui/topo/TopoConstants.java
index 7d52a5a..c5952a1 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/TopoConstants.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/TopoConstants.java
@@ -22,39 +22,40 @@
 public final class TopoConstants {
 
     /**
-     * Defines constants for property names on the default summary and
-     * details panels.
+     * Defines constants for property keys on the default summary and
+     * details panels. Note that display labels should be looked up using
+     * the "core.view.Topo" localization bundle (LionBundle).
      */
     public static final class Properties {
         public static final String SEPARATOR = "-";
 
         // summary panel
-        public static final String DEVICES = "Devices";
-        public static final String LINKS = "Links";
-        public static final String HOSTS = "Hosts";
-        public static final String TOPOLOGY_SSCS = "Topology SCCs";
-        public static final String INTENTS = "Intents";
-        public static final String TUNNELS = "Tunnels";
-        public static final String FLOWS = "Flows";
-        public static final String VERSION = "Version";
+        public static final String DEVICES = "devices";
+        public static final String LINKS = "links";
+        public static final String HOSTS = "hosts";
+        public static final String TOPOLOGY_SSCS = "topology_sccs";
+        public static final String INTENTS = "intents";
+        public static final String TUNNELS = "tunnels";
+        public static final String FLOWS = "flows";
+        public static final String VERSION = "version";
 
         // device details
-        public static final String URI = "URI";
-        public static final String VENDOR = "Vendor";
-        public static final String HW_VERSION = "H/W Version";
-        public static final String SW_VERSION = "S/W Version";
-        public static final String SERIAL_NUMBER = "Serial #";
-        public static final String PROTOCOL = "Protocol";
-        public static final String LATITUDE = "Latitude";
-        public static final String LONGITUDE = "Longitude";
-        public static final String GRID_Y = "Grid Y";
-        public static final String GRID_X = "Grid X";
-        public static final String PORTS = "Ports";
+        public static final String URI = "uri";
+        public static final String VENDOR = "vendor";
+        public static final String HW_VERSION = "hw_version";
+        public static final String SW_VERSION = "sw_version";
+        public static final String SERIAL_NUMBER = "serial_number";
+        public static final String PROTOCOL = "protocol";
+        public static final String LATITUDE = "latitude";
+        public static final String LONGITUDE = "longitude";
+        public static final String GRID_Y = "grid_y";
+        public static final String GRID_X = "grid_x";
+        public static final String PORTS = "ports";
 
         // host details
-        public static final String MAC = "MAC";
-        public static final String IP = "IP";
-        public static final String VLAN = "VLAN";
+        public static final String MAC = "mac";
+        public static final String IP = "ip";
+        public static final String VLAN = "vlan";
     }
 
     /**
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java b/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java
index c6e8079..a0646c7 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/TopoJson.java
@@ -49,7 +49,8 @@
     static final String TYPE = "type";
     static final String NAV_PATH = "navPath";
     static final String PROP_ORDER = "propOrder";
-    static final String PROPS = "props";
+    static final String PROP_LABELS = "propLabels";
+    static final String PROP_VALUES = "propValues";
     static final String BUTTONS = "buttons";
 
 
@@ -185,14 +186,17 @@
             result.put(NAV_PATH, pp.navPath());
         }
 
-        ObjectNode pnode = objectNode();
+        ObjectNode plabels = objectNode();
+        ObjectNode pvalues = objectNode();
         ArrayNode porder = arrayNode();
         for (PropertyPanel.Prop p : pp.properties()) {
             porder.add(p.key());
-            pnode.put(p.key(), p.value());
+            plabels.put(p.key(), p.label());
+            pvalues.put(p.key(), p.value());
         }
         result.set(PROP_ORDER, porder);
-        result.set(PROPS, pnode);
+        result.set(PROP_LABELS, plabels);
+        result.set(PROP_VALUES, pvalues);
 
         ArrayNode buttons = arrayNode();
         for (ButtonId b : pp.buttons()) {
diff --git a/core/api/src/test/java/org/onosproject/ui/topo/PropertyPanelTest.java b/core/api/src/test/java/org/onosproject/ui/topo/PropertyPanelTest.java
index 1c51bf1..6ae6849 100644
--- a/core/api/src/test/java/org/onosproject/ui/topo/PropertyPanelTest.java
+++ b/core/api/src/test/java/org/onosproject/ui/topo/PropertyPanelTest.java
@@ -64,6 +64,11 @@
     private static final String SEP = "-";
     private static final String KEY_Z = "Z";
 
+    private static final String LABEL_A = "labA";
+    private static final String LABEL_B = "labB";
+    private static final String LABEL_C = "labC";
+    private static final String LABEL_Z = "labZ";
+
     private static final String VALUE_A = "Hay";
     private static final String VALUE_B = "Bee";
     private static final String VALUE_C = "Sea";
@@ -89,10 +94,10 @@
 
     @BeforeClass
     public static void setUpClass() {
-        PROP_MAP.put(KEY_A, new Prop(KEY_A, VALUE_A));
-        PROP_MAP.put(KEY_B, new Prop(KEY_B, VALUE_B));
-        PROP_MAP.put(KEY_C, new Prop(KEY_C, VALUE_C));
-        PROP_MAP.put(KEY_Z, new Prop(KEY_Z, VALUE_Z));
+        PROP_MAP.put(KEY_A, new Prop(KEY_A, LABEL_A, VALUE_A));
+        PROP_MAP.put(KEY_B, new Prop(KEY_B, LABEL_B, VALUE_B));
+        PROP_MAP.put(KEY_C, new Prop(KEY_C, LABEL_C, VALUE_C));
+        PROP_MAP.put(KEY_Z, new Prop(KEY_Z, LABEL_Z, VALUE_Z));
         PROP_MAP.put(SEP, new PropertyPanel.Separator());
     }
 
@@ -162,6 +167,27 @@
         validateProps(KEY_A, KEY_B, KEY_C);
     }
 
+
+    @Test
+    public void localizedProp() {
+        basic();
+        pp.addProp(KEY_A, LABEL_A, VALUE_A);
+        Prop p = pp.properties().get(0);
+        assertEquals("wrong key", KEY_A, p.key());
+        assertEquals("wrong label", LABEL_A, p.label());
+        assertEquals("wrong value", VALUE_A, p.value());
+    }
+
+    @Test
+    public void nonLocalizedProp() {
+        basic();
+        pp.addProp(KEY_A, VALUE_A);
+        Prop p = pp.properties().get(0);
+        assertEquals("wrong key", KEY_A, p.key());
+        assertEquals("wrong label", KEY_A, p.label());
+        assertEquals("wrong value", VALUE_A, p.value());
+    }
+
     @Test
     public void separator() {
         props();
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
index 0d7ff82..3cbce06 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
@@ -82,7 +82,9 @@
 import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
 import static org.onosproject.net.DeviceId.deviceId;
 import static org.onosproject.net.HostId.hostId;
-import static org.onosproject.net.device.DeviceEvent.Type.*;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_UPDATED;
+import static org.onosproject.net.device.DeviceEvent.Type.PORT_STATS_UPDATED;
 import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
 import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
 import static org.onosproject.ui.JsonUtils.envelope;
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
index 2437132..c2e290d 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
@@ -18,6 +18,7 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
 import org.onlab.osgi.ServiceDirectory;
 import org.onlab.packet.IpAddress;
 import org.onlab.util.DefaultHashMap;
@@ -50,6 +51,7 @@
 import org.onosproject.ui.UiConnection;
 import org.onosproject.ui.UiMessageHandler;
 import org.onosproject.ui.impl.topo.util.ServicesBundle;
+import org.onosproject.ui.lion.LionBundle;
 import org.onosproject.ui.topo.PropertyPanel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -66,6 +68,14 @@
 import static org.onosproject.net.PortNumber.portNumber;
 import static org.onosproject.ui.topo.TopoConstants.CoreButtons;
 import static org.onosproject.ui.topo.TopoConstants.Properties;
+import static org.onosproject.ui.topo.TopoConstants.Properties.DEVICES;
+import static org.onosproject.ui.topo.TopoConstants.Properties.FLOWS;
+import static org.onosproject.ui.topo.TopoConstants.Properties.HOSTS;
+import static org.onosproject.ui.topo.TopoConstants.Properties.INTENTS;
+import static org.onosproject.ui.topo.TopoConstants.Properties.LINKS;
+import static org.onosproject.ui.topo.TopoConstants.Properties.TOPOLOGY_SSCS;
+import static org.onosproject.ui.topo.TopoConstants.Properties.TUNNELS;
+import static org.onosproject.ui.topo.TopoConstants.Properties.VERSION;
 import static org.onosproject.ui.topo.TopoUtils.compactLinkString;
 
 /**
@@ -125,6 +135,11 @@
         return Collections.unmodifiableMap(metaUi);
     }
 
+    private static final String LION_TOPO = "core.view.Topo";
+
+    private static final Set<String> REQ_LION_BUNDLES = ImmutableSet.of(
+            LION_TOPO
+    );
 
     protected ServicesBundle services;
 
@@ -144,6 +159,11 @@
         version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
     }
 
+    @Override
+    public Set<String> requiredLionBundles() {
+        return REQ_LION_BUNDLES;
+    }
+
     // Returns the first of the given set of IP addresses as a string.
     private String ip(Set<IpAddress> ipAddresses) {
         Iterator<IpAddress> it = ipAddresses.iterator();
@@ -351,18 +371,20 @@
     // Returns property panel model for summary response.
     protected PropertyPanel summmaryMessage() {
         Topology topology = services.topology().currentTopology();
+        LionBundle lion = getLionBundle(LION_TOPO);
+        String panelTitle = lion.getSafe("title_panel_summary");
 
-        return new PropertyPanel("ONOS Summary", "node")
-                .addProp(Properties.VERSION, version)
+        return new PropertyPanel(panelTitle, "node")
+                .addProp(VERSION, lion.getSafe(VERSION), version)
                 .addSeparator()
-                .addProp(Properties.DEVICES, services.device().getDeviceCount())
-                .addProp(Properties.LINKS, topology.linkCount())
-                .addProp(Properties.HOSTS, services.host().getHostCount())
-                .addProp(Properties.TOPOLOGY_SSCS, topology.clusterCount())
+                .addProp(DEVICES, lion.getSafe(DEVICES), services.device().getDeviceCount())
+                .addProp(LINKS, lion.getSafe(LINKS), topology.linkCount())
+                .addProp(HOSTS, lion.getSafe(HOSTS), services.host().getHostCount())
+                .addProp(TOPOLOGY_SSCS, lion.getSafe(TOPOLOGY_SSCS), topology.clusterCount())
                 .addSeparator()
-                .addProp(Properties.INTENTS, services.intent().getIntentCount())
-                .addProp(Properties.TUNNELS, services.tunnel().tunnelCount())
-                .addProp(Properties.FLOWS, services.flow().getFlowRuleCount());
+                .addProp(INTENTS, lion.getSafe(INTENTS), services.intent().getIntentCount())
+                .addProp(TUNNELS, lion.getSafe(TUNNELS), services.tunnel().tunnelCount())
+                .addProp(FLOWS, lion.getSafe(FLOWS), services.flow().getFlowRuleCount());
     }
 
     // Returns property panel model for device details response.
@@ -394,8 +416,8 @@
                 .addSeparator()
 
                 .addProp(Properties.PORTS, portCount)
-                .addProp(Properties.FLOWS, flowCount)
-                .addProp(Properties.TUNNELS, tunnelCount)
+                .addProp(FLOWS, flowCount)
+                .addProp(TUNNELS, tunnelCount)
 
                 .addButton(CoreButtons.SHOW_DEVICE_VIEW)
                 .addButton(CoreButtons.SHOW_FLOW_VIEW)
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
index 55f3183..07a0b0d 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
@@ -40,6 +40,7 @@
 import org.onosproject.ui.impl.topo.Topo2ViewMessageHandler;
 import org.onosproject.ui.impl.topo.UiTopoSession;
 import org.onosproject.ui.impl.topo.model.UiSharedTopologyModel;
+import org.onosproject.ui.lion.LionBundle;
 import org.onosproject.ui.model.topo.UiTopoLayout;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -97,6 +98,8 @@
     private TopoOverlayCache overlayCache;
     private Topo2OverlayCache overlay2Cache;
 
+    private Map<String, LionBundle> lionBundleMap;
+
     private UiSessionToken sessionToken;
 
 
@@ -289,14 +292,16 @@
         overlay2Cache = new Topo2OverlayCache();
 
         Map<Class<?>, UiMessageHandler> handlerInstances = new HashMap<>();
-
         UiExtensionService service = directory.get(UiExtensionService.class);
+        lionBundleMap = generateLionMap(service);
+
         service.getExtensions().forEach(ext -> {
             UiMessageHandlerFactory factory = ext.messageHandlerFactory();
             if (factory != null) {
                 factory.newHandlers().forEach(handler -> {
                     try {
                         handler.init(this, directory);
+                        injectLionBundles(handler, lionBundleMap);
                         handler.messageTypes().forEach(type -> handlers.put(type, handler));
                         handlerInstances.put(handler.getClass(), handler);
 
@@ -305,13 +310,34 @@
                     }
                 });
             }
-
             registerOverlays(ext);
         });
 
         handlerCrossConnects(handlerInstances);
 
-        log.debug("#handlers = {}, #overlays = {}", handlers.size(), overlayCache.size());
+        log.debug("#handlers = {}, #overlays = {}",
+                  handlers.size(), overlayCache.size());
+    }
+
+    private Map<String, LionBundle> generateLionMap(UiExtensionService service) {
+        Map<String, LionBundle> bundles = new HashMap<>();
+        service.getExtensions().forEach(ext -> {
+            ext.lionBundles().forEach(lb -> bundles.put(lb.id(), lb));
+        });
+        return bundles;
+    }
+
+    private void injectLionBundles(UiMessageHandler handler,
+                                   Map<String, LionBundle> lionBundleMap) {
+        handler.requiredLionBundles().forEach(lbid -> {
+            LionBundle lb = lionBundleMap.get(lbid);
+            if (lb != null) {
+                handler.cacheLionBundle(lb);
+            } else {
+                log.warn("handler {}: Lion bundle {} non existent!",
+                         handler.getClass().getName(), lbid);
+            }
+        });
     }
 
     private void authenticate(String type, ObjectNode message) {
@@ -424,9 +450,7 @@
         service.getExtensions().forEach(ext -> {
             ext.lionBundles().forEach(lb -> {
                 ObjectNode lionMap = objectNode();
-                lb.getItems().forEach(item -> {
-                    lionMap.put(item.key(), item.value());
-                });
+                lb.getItems().forEach(item -> lionMap.put(item.key(), item.value()));
                 lion.set(lb.id(), lionMap);
             });
         });
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/_config/core.view.Topo.lioncfg b/web/gui/src/main/resources/org/onosproject/ui/lion/_config/core.view.Topo.lioncfg
index 5d66622..3f4d269 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/_config/core.view.Topo.lioncfg
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/_config/core.view.Topo.lioncfg
@@ -26,6 +26,7 @@
 from cf.QuickHelp import qh_hint_close_detail
 
 from cc.Action import show, hide, enable, disable, select
-from cc.Network import hosts, devices
+from cc.Network import devices, links, hosts, topology_sccs, intents, tunnels, flows, protocol
+from cc.Props import version, vendor, hw_version, sw_version, serial_number
 from cc.State import visible, hidden
 from cc.Ui import click, shift_click, drag, cmd_scroll, cmd_drag, ok, close
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network.properties
index 9064478..3c1bf49 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network.properties
@@ -22,15 +22,24 @@
 device=Device
 host=Host
 link=Link
+intent=Intent
+tunnel=Tunnel
+flow=Flow
+port=Port
 
 # --- Elements (Plural)
 nodes=Nodes
 topologies=Topologies
+topology_sccs=Topology SCCs
 networks=Networks
 regions=Regions
 devices=Devices
 hosts=Hosts
 links=Links
+intents=Intents
+tunnels=Tunnels
+flows=Flows
+ports=Ports
 
 # --- Element IDs
 node_id=Node ID
@@ -38,6 +47,10 @@
 device_id=Device ID
 host_id=Host ID
 link_id=Link ID
+intent_id=Intent ID
+tunnel_id=Tunnel ID
+flow_id=Flow ID
+port_id=Port ID
 
 # --- Protocol terms
 protocol=Protocol
@@ -47,3 +60,4 @@
 mac=MAC
 mac_address=MAC Address
 uri=URI
+vlan=VLAN
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_es.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_es.properties
index df8f8ef..3a3f6df 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_es.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_es.properties
@@ -22,15 +22,24 @@
 device=Dispositivo
 host=Host
 link=Enlace
+intent=Intent (es)
+tunnel=Tunnel (es)
+flow=Flow (es)
+port=Port (es)
 
 # --- Elements (Plural)
 nodes=Nodos
 topologies=Topologías
+topology_sccs=Topology SCCs (es)
 networks=Redes
 regions=Regiones
 devices=Dispositivos
 hosts=Hosts
 links=Enlaces
+intents=Intents (es)
+tunnels=Tunnels (es)
+flows=Flows (es)
+ports=Ports (es)
 
 # --- Element IDs
 node_id=ID del Nodo
@@ -38,6 +47,10 @@
 device_id=ID del Dispositivo
 host_id=ID del Host
 link_id=ID del Enlace
+intent_id=Intent ID (es)
+tunnel_id=Tunnel ID (es)
+flow_id=Flow ID (es)
+port_id=Port ID (es)
 
 # --- Protocol terms
 protocol=Protocolo
@@ -47,3 +60,4 @@
 mac=MAC
 mac_address=Dirección MAC
 uri=URI
+vlan=VLAN (es)
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_it.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_it.properties
index c073672..5cf41eb 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_it.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_it.properties
@@ -22,15 +22,24 @@
 device=Dispositivo
 host=Host
 link=Collegamento
+intent=Intent (it)
+tunnel=Tunnel (it)
+flow=Flow (it)
+port=Port (it)
 
 # --- Elements (Plural)
 nodes=Nodi
 topologies=Topologie
+topology_sccs=Topology SCCs (it)
 networks=Reti
 regions=Regioni
 devices=Dispositivi
 hosts=Hosts
 links=Collegamenti
+intents=Intents (it)
+tunnels=Tunnels (it)
+flows=Flows (it)
+ports=Ports (it)
 
 # --- Element IDs
 node_id=ID del Nodo
@@ -38,6 +47,10 @@
 device_id=ID del Dispositivo
 host_id=ID dell'Host
 link_id=ID del collegamento
+intent_id=Intent ID (it)
+tunnel_id=Tunnel ID (it)
+flow_id=Flow ID (it)
+port_id=Port ID (it)
 
 # --- Protocol terms
 protocol=Protocollo
@@ -47,3 +60,4 @@
 mac=MAC
 mac_address=Indirizzo MAC
 uri=URI
+vlan=VLAN (it)
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_ko.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_ko.properties
index cb30ed7..1190278 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_ko.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_ko.properties
@@ -22,15 +22,24 @@
 device=장치
 host=호스트
 link=링크
+intent=Intent (ko)
+tunnel=Tunnel (ko)
+flow=Flow (ko)
+port=Port (ko)
 
 # --- Elements (Plural)
 nodes=노드
 topologies=토폴로지
+topology_sccs=Topology SCCs (ko)
 networks=네트워크
 regions=리젼
 devices=장치
 hosts=호스트
 links=링크
+intents=Intents (ko)
+tunnels=Tunnels (ko)
+flows=Flows (ko)
+ports=Ports (ko)
 
 # --- Element IDs
 node_id=노드 아이디
@@ -38,6 +47,10 @@
 device_id=장치 아이디
 host_id=호스트 아이디
 link_id=링크 아이디
+intent_id=Intent ID (ko)
+tunnel_id=Tunnel ID (ko)
+flow_id=Flow ID (ko)
+port_id=Port ID (ko)
 
 # --- Protocol terms
 protocol=프로토콜
@@ -47,3 +60,4 @@
 mac=MAC
 mac_address=MAC 주소
 uri=URI
+vlan=VLAN (ko)
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_zh_CN.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_zh_CN.properties
index 2db4017..724bd4d 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_zh_CN.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_zh_CN.properties
@@ -22,15 +22,24 @@
 device=设备
 host=主机
 link=链路
+intent=Intent (zh_CN)
+tunnel=Tunnel (zh_CN)
+flow=Flow (zh_CN)
+port=Port (zh_CN)
 
 # --- Elements (Plural)
 nodes=节点
 topologies=拓扑
+topology_sccs=Topology SCCs (zh_CN)
 networks=网络
 regions=区域
 devices=设备
 hosts=主机
 links=链路
+intents=Intents (zh_CN)
+tunnels=Tunnels (zh_CN)
+flows=Flows (zh_CN)
+ports=Ports (zh_CN)
 
 # --- Element IDs
 node_id=节点 ID
@@ -38,6 +47,10 @@
 device_id=设备 ID
 host_id=主机 ID
 link_id=链路 ID
+intent_id=Intent ID (zh_CN)
+tunnel_id=Tunnel ID (zh_CN)
+flow_id=Flow ID (zh_CN)
+port_id=Port ID (zh_CN)
 
 # --- Protocol terms
 protocol=协议
@@ -47,3 +60,4 @@
 mac=MAC
 mac_address=MAC 地址
 uri=URI
+vlan=VLAN (zh_CN)
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_zh_TW.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_zh_TW.properties
index 22ff27c..30fd81e 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_zh_TW.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Network_zh_TW.properties
@@ -22,15 +22,24 @@
 device=設備
 host=主機
 link=連結
+intent=Intent (zh_TW)
+tunnel=Tunnel (zh_TW)
+flow=Flow (zh_TW)
+port=Port (zh_TW)
 
 # --- Elements (Plural)
 nodes=節點
 topologies=拓撲
+topology_sccs=Topology SCCs (zh_TW)
 networks=網路
 regions=區域
 devices=設備
 hosts=主機
 links=連結
+intents=Intents (zh_TW)
+tunnels=Tunnels (zh_TW)
+flows=Flows (zh_TW)
+ports=Ports (zh_TW)
 
 # --- Element IDs
 node_id=節點 ID
@@ -38,6 +47,10 @@
 device_id=設備 ID
 host_id=主機 ID
 link_id=連結 ID
+intent_id=Intent ID (zh_TW)
+tunnel_id=Tunnel ID (zh_TW)
+flow_id=Flow ID (zh_TW)
+port_id=Port ID (zh_TW)
 
 # --- Protocol terms
 protocol=協定
@@ -47,3 +60,4 @@
 mac=MAC
 mac_address=MAC 地址
 uri=URI
+vlan=VLAN (zh_TW)
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props.properties
index 3dac6c6..86f02dc 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props.properties
@@ -32,3 +32,8 @@
 version=Version
 origin=Origin
 role=Role
+
+latitude=Latitude
+longitude=Longitude
+grid_y=Grid Y
+grid_x=Grid X
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_es.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_es.properties
index b74c36a..5d026b9 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_es.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_es.properties
@@ -32,3 +32,8 @@
 version=Versión
 origin=Origen
 role=Rol
+
+latitude=Latitude (es)
+longitude=Longitude (es)
+grid_y=Grid Y (es)
+grid_x=Grid X (es)
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_it.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_it.properties
index dceeed1..e897aea 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_it.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_it.properties
@@ -32,3 +32,8 @@
 version=Versione
 origin=Origine
 role=Ruolo
+
+latitude=Latitude (it)
+longitude=Longitude (it)
+grid_y=Grid Y (it)
+grid_x=Grid X (it)
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_ko.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_ko.properties
index e7681be..dd4593c 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_ko.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_ko.properties
@@ -32,3 +32,8 @@
 version=버전
 origin=제조사
 role=역할
+
+latitude=Latitude (ko)
+longitude=Longitude (ko)
+grid_y=Grid Y (ko)
+grid_x=Grid X (ko)
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_zh_CN.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_zh_CN.properties
index 57701a6..1c08fa9 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_zh_CN.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_zh_CN.properties
@@ -32,3 +32,8 @@
 version=版本
 origin=源
 role=角色
+
+latitude=Latitude (zh_CN)
+longitude=Longitude (zh_CN)
+grid_y=Grid Y (zh_CN)
+grid_x=Grid X (zh_CN)
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_zh_TW.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_zh_TW.properties
index 1d2e0f4..042bb1d 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_zh_TW.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/common/Props_zh_TW.properties
@@ -32,3 +32,8 @@
 version=版本
 origin=來源
 role=角色
+
+latitude=Latitude (zh_TW)
+longitude=Longitude (zh_TW)
+grid_y=Grid Y (zh_TW)
+grid_x=Grid X (zh_TW)
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
index c6adb3e..c32cd6a 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
@@ -76,6 +76,7 @@
 fl_layer_opt=Optical Layer Shown
 
 fl_panel_instances=Instances Panel
+fl_panel_summary=Summary Panel
 
 fl_port_highlighting=Port Highlighting
 
@@ -91,3 +92,4 @@
 
 # Miscellaneous
 title_select_map=Select Map
+title_panel_summary=ONOS Summary
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_it.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_it.properties
index 23f6460..c32b249 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_it.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_it.properties
@@ -71,6 +71,7 @@
 fl_layer_opt=Optical Layer Shown (it)
 
 fl_panel_instances=Instances Panel (it)
+fl_panel_summary=Summary Panel (it)
 
 fl_port_highlighting=Port Highlighting (it)
 
@@ -86,3 +87,4 @@
 
 # Miscellaneous
 title_select_map=Select Map (it)
+title_panel_summary=ONOS Summary (it)
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.css b/web/gui/src/main/webapp/app/view/topo/topo.css
index 540b480..74fe0c6 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo.css
@@ -124,8 +124,8 @@
     padding: 0;
 }
 
-#topo-p-summary  td.label {
-    width: 50%;
+#topo-p-summary td.label {
+    width: 65%;
 }
 
 #topo-p-detail  div.actionBtns {
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index 3e9a0cc..ba0bc7d 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -728,6 +728,7 @@
                     tfs.setLionBundle(topoLion);
                     tis.setLionBundle(topoLion);
                     tms.setLionBundle(topoLion);
+                    tps.setLionBundle(topoLion);
 
                     // now we have the map projection, we are ready for
                     //  the server to send us device/host data...
diff --git a/web/gui/src/main/webapp/app/view/topo/topoInst.js b/web/gui/src/main/webapp/app/view/topo/topoInst.js
index 4e89822..63d49fa 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoInst.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoInst.js
@@ -47,7 +47,7 @@
 
     // function to be replaced by the localization bundle function
     var topoLion = function (x) {
-        return '#tinst#' + x + '#';
+        return '#tis#' + x + '#';
     };
 
 
@@ -317,11 +317,6 @@
         return on;
     }
 
-    // invoked after the localization bundle has been received from the server
-    function setLionBundle(bundle) {
-        topoLion = bundle;
-    }
-
     // ==========================
 
     angular.module('ovTopo')
@@ -353,7 +348,7 @@
                 hide: hideInsts,
                 toggle: toggleInsts,
                 showMaster: function () { return oiShowMaster; },
-                setLionBundle: setLionBundle,
+                setLionBundle: function (bundle) { topoLion = bundle; },
             };
         }]);
 }());
diff --git a/web/gui/src/main/webapp/app/view/topo/topoPanel.js b/web/gui/src/main/webapp/app/view/topo/topoPanel.js
index b25f02e..eac6a89 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoPanel.js
@@ -25,6 +25,11 @@
     // injected refs
     var $log, $window, $rootScope, fs, ps, gs, flash, wss, bns, mast, ns;
 
+    // function to be replaced by the localization bundle function
+    var topoLion = function (x) {
+        return '#tps#' + x + '#';
+    };
+
     // constants
     var pCls = 'topo-p',
         idSum = 'topo-p-summary',
@@ -175,17 +180,26 @@
     function listProps(tbody, data) {
 
         // Suppress Lat Long in details panel if null
-        if (data.props.Latitude === null ||
-            data.props.Longitude === null) {
-            var idx = data.propOrder.indexOf('Latitude');
+        if (data.propLabels.latitude === null ||
+            data.propLabels.longitude === null) {
+            var idx = data.propOrder.indexOf('latitude');
             data.propOrder.splice(idx, 3);
         }
 
         data.propOrder.forEach(function (p) {
+            // TODO: remove after topo view fully i18n'd
+            var foo = data.props && data.props[p];
+
             if (p === '-') {
                 addSep(tbody);
+
             } else {
-                addProp(tbody, p, data.props[p]);
+                // TODO: remove this if/else once DETAILS panel fixed for i18n
+                if (foo !== undefined) {
+                    addProp(tbody, p, foo);
+                } else {
+                    addProp(tbody, data.propLabels[p], data.propValues[p]);
+                }
             }
         });
     }
@@ -392,7 +406,8 @@
     function toggleSummary(x) {
         var kev = (x === 'keyev'),
             on = kev ? !summary.panel().isVisible() : !!x,
-            verb = on ? 'Show' : 'Hide';
+            verb = on ? topoLion('show') : topoLion('hide'),
+            sumpan = topoLion('fl_panel_summary');
 
         if (on) {
             // ask server to start sending summary data.
@@ -401,7 +416,7 @@
         } else {
             hideSummaryPanel();
         }
-        flash.flash(verb + ' summary panel');
+        flash.flash(verb + ' ' + sumpan);
         return on;
     }
 
@@ -553,6 +568,8 @@
 
                 detailVisible: function () { return detail.panel().isVisible(); },
                 summaryVisible: function () { return summary.panel().isVisible(); },
+
+                setLionBundle: function (bundle) { topoLion = bundle; },
             };
         }]);
 }());
diff --git a/web/gui/src/main/webapp/app/view/topo/topoSelect.js b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
index 1309a66..5d23f1e 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoSelect.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
@@ -256,7 +256,7 @@
     function showDetails(data) {
         var buttons = fs.isA(data.buttons) || [];
         tps.displaySingle(data);
-        tov.installButtons(buttons, data, data.props['URI']);
+        tov.installButtons(buttons, data, data.propValues['uri']);
         tov.hooks.singleSelect(data);
         tps.displaySomething();
     }
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoPanel-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoPanel-spec.js
index 16b67af..e48c27c 100644
--- a/web/gui/src/main/webapp/tests/app/view/topo/topoPanel-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoPanel-spec.js
@@ -76,7 +76,9 @@
             'addAction',
 
             'detailVisible',
-            'summaryVisible'
+            'summaryVisible',
+
+            'setLionBundle',
         ])).toBeTruthy();
     });