This adds error reporting to loading/saving configurations.

It also adds support for reading/writing VPN configs, and fixes a
bunch of bugs.

BUG=none
TEST=ran in browser.

Change-Id: Ife9d532af38e2586cebd6bae4e897ecaabdef423
diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 246dee7..c9b5b41 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -1,37 +1,217 @@
 {
-  "extName": {
-    "message": "Spigots",
-    "description": "Name of extension"
+  "acronymEapMd5": {
+    "message": "EAP-MD5"
   },
-  "extDesc": {
-    "message": "Network configuration tool for Chrome OS"
+  "acronymEapMschapV2": {
+    "message": "EAP-MSCHAPv2"
   },
-  "wifiSettingsTab": {
-    "message": "Wi-Fi Settings"
+  "acronymEapPap": {
+    "message": "EAP-PAP"
+  },
+  "acronymEapTls": {
+    "message": "EAP-TLS"
+  },
+  "acronymEapTtls": {
+    "message": "EAP-TTLS"
+  },
+  "acronymLeap": {
+    "message": "LEAP"
+  },
+  "acronymPeap": {
+    "message": "PEAP"
   },
   "acronymSsid": {
     "message": "Service set identifier (SSID):"
   },
-  "identificationOfTheWirelessNetwork": {
-    "message": "Identification of the wireless network"
-  },
-  "thisSsidIsNotBroadcast": {
-    "message": "This SSID is not broadcast"
+  "automatic": {
+    "description": "Indicating that a choice is made automatically without further specification",
+    "message": "Automatic"
   },
   "automaticallyConnect": {
     "message": "Automatically connect"
   },
-  "securityType": {
-    "message": "Security type:"
+  "certificateEmpty": {
+    "message": "Empty"
+  },
+  "certificateList": {
+    "message": "Certificate list"
+  },
+  "certificateListClear": {
+    "message": "Remove All Certificates"
+  },
+  "certificatesAndTrustTab": {
+    "message": "Certificates / Trust"
+  },
+  "clientCertificateAuthority": {
+    "message": "Client Certificate Authority"
+  },
+  "doNotCheckCA": {
+    "message": "Do not check (insecure)"
+  },
+  "enrollmentUri": {
+    "message": "Client Enrollment URI"
+  },
+  "errorJSONStringify": {
+    "description": "This is displayed when an internal error during conversion to JSON makes it impossible to generate an output file.  The positional arg is the system error message from json.Stringify.",
+    "message": "Internal error converting to JSON: $1"
+  },
+  "errorLoadParsingJSON": {
+    "message": "JSON parsing error during load: $1"
+  },
+  "errorLoadRequiredObjectMissing": {
+    "message": "Network configuration is missing required '$1' object."
+  },
+  "errorLoadUnhandledEapType": {
+    "message": "Unhandled EAP Type '$1' in WiFi network '$2'"
+  },
+  "errorLoadUnhandledSecurityType": {
+    "message": "Unhandled Security Type '$1' in WiFi network '$2'"
+  },
+  "errorLoadUnknownNetworkConfigType": {
+    "message": "Unknown NetworkConfiguration type '$1'."
+  },
+  "errorMissingCert": {
+    "description": "This is displayed when a network requiring a certificate doesn't have one associated with it.  The first positional arg is the type of network. The second positional arg is the name of the network.",
+    "message": "Missing certificate authority in $2 network '$1'."
+  },
+  "errorMissingEAPIdentity": {
+    "message": "You supplied a passphrase for the network '$1', but no identity to go with it."
+  },
+  "errorEAPIdentityEmpty": {
+    "message": "You supplied an empty identity for the network '$1'.  An empty identity is not allowed."
+  },
+  "errorMissingEAPPassword": {
+    "message": "You supplied the identity '$1' for the Wi-Fi network '$2', but no password."
+  },
+  "errorMissingVPNIdentity": {
+    "message": "You supplied a passphrase for the VPN '$1', but no identity to go with it."
+  },
+  "errorMissingVPNPreSharedKey": {
+    "message": "You selected '$1' for the VPN security for the network '$2', but didn't supply a pre-shared key."
+  },
+  "errorWEPKeyInvalidLength": {
+    "message": "Your WEP ASCII passphrase for the network '$1' is not a valid length.  You supplied $2 characters, and a WEP passphrase must be either 5, 13, 16, or 29 characters for an ASCII passphrase, or 10, 26, 32 or 58 characters for a hexadecimal passphrase, depending on the bit length of the key desired."
+  },
+  "errorWEPKeyNonHexCharacters": {
+    "message": "The hexadecimal WEP password for the network '$1' contains invalid hex characters.  Hexadecimal passwords can only contain the letters 'a' through 'f' and the numbers 0 through 9.  If you didn't intend to enter a hex password, then you must use an ASCII password of the correct length (5, 13, 16 or 29 characters, depending on the length of the key desired)."
+  },
+  "extDesc": {
+    "message": "Network configuration tool for Chrome OS"
+  },
+  "extName": {
+    "description": "Name of extension",
+    "message": "Spigots"
+  },
+  "extensibleAuthenticationProtocol": {
+    "message": "Extensible Authentication Protocol"
+  },
+  "fileNotSupported": {
+    "message": "File $1 is not supported"
+  },
+  "helpCertificateList": {
+    "message": "List of certificates to install.  Drag and drop root certificate authorities and certificates to install on Chrome OS. You may drag and drop .pem and .der files."
+  },
+  "helpClientCertificateAuthority": {
+    "message": "Select the certificate authority that will issue the client certificate.  This information is used to determine which of the user's certificates is appropriate to use to identify them to the network.  Add new certificate authorities under the Certificates tab."
+  },
+  "helpEnrollmentUri": {
+    "message": "If the user does not have a suitable client certificate, upon attempting to connect to this network, direct the user to this URI to guide them through certificate enrollment.  Note that this URI should not be an internal network resource as the user may not have internal network access.  This may be an extension or data URI."
+  },
+  "helpInnerProtocol": {
+    "message": "Select the inner protocol to use for authentication (or Automatic to use any)"
+  },
+  "helpLoadConfigurationFromFile": {
+    "message": "Choose a file to load."
+  },
+  "helpPassword": {
+    "message": "Enter password for logging into network"
+  },
+  "helpPresharedKey": {
+    "message": "Enter the pre-shared key used by the IPsec layer"
+  },
+  "helpProxyUrl": {
+    "message": "Set the proxy configuration URL while connected to this network."
+  },
+  "helpRemoteHost": {
+    "message": "Enter the hostname or IP of the virtual private network server"
+  },
+  "helpSaveConfigurationToFile": {
+    "message": "Right-click on the link below and save the link with an '.onc' suffix."
   },
   "helpSecurityType": {
     "message": "Security and authentication settings for network"
   },
+  "helpServerCertificateAuthority": {
+    "message": "Select the certificate authority to trust when authenticating to this network.  Add new certificate authorities under the Certificates tab."
+  },
+  "helpUniqueIdentifier": {
+    "message": "This random unique identifier (GUID) identifies this network connection in case you want to update or delete it in the future."
+  },
+  "helpUsername": {
+    "message": "Enter username for logging into network"
+  },
+  "helpVpnType": {
+    "message": "Choose the type of Virtual Private Network connection"
+  },
+  "helpWirelessPassphrase": {
+    "description": "Prompt to ask the user for their network passphrase",
+    "message": "Passphrase required to connect to network"
+  },
+  "identificationOfTheWirelessNetwork": {
+    "message": "Identification of the wireless network"
+  },
+  "innerProtocol": {
+    "message": "Inner protocol"
+  },
+  "l2tpIpsecCert": {
+    "message": "L2TP over IPsec with Certificates"
+  },
+  "l2tpIpsecPsk": {
+    "description": "L2TP over IPsec is a kind of VPN.  Pre-shared key is a particular configuration of that VPN.",
+    "message": "L2TP over IPsec with Pre-Shared Key"
+  },
+  "loadConfigurationFromFile": {
+    "message": "Load configuration from file"
+  },
+  "loadConfigurationTab": {
+    "message": "Load configuration"
+  },
+  "loadSucceeded": {
+    "message": "Configuration loaded successfully."
+  },
+  "password": {
+    "message": "Password"
+  },
+  "presharedKey": {
+    "message": "Pre-shared key"
+  },
+  "proxyUrl": {
+    "message": "Proxy URL"
+  },
+  "remoteHost": {
+    "message": "Remote host"
+  },
+  "saveConfigurationTab": {
+    "message": "Save configuration"
+  },
+  "saveConfigurationToFile": {
+    "message": "Save configuration to file"
+  },
+  "saveLinkText": {
+    "description": "This is the text of the link that starts the download of the newly creation configuration.",
+    "message": "Download Configuration"
+  },
+  "saveSucceeded": {
+    "message": "Save succeeded!"
+  },
   "securityNone": {
     "message": "None"
   },
+  "securityType": {
+    "message": "Security type:"
+  },
   "securityWep": {
-    "message": "WEP"
+    "message": "WEP (insecure)"
   },
   "securityWpa": {
     "message": "WPA"
@@ -42,170 +222,54 @@
   "securityWpa2Enterprise": {
     "message": "WPA2 Enterprise (802.1X)"
   },
-  "wirelessPassphrase": {
-    "message": "Passphrase"
-  },
-  "helpWirelessPassphrase": {
-    "message": "Passphrase required to connect to network",
-    "description": "Prompt to ask the user for their network passphrase"
-  },
-  "extensibleAuthenticationProtocol": {
-    "message": "Extensible Authentication Protocol"
-  },
   "selectTheEapToPermit": {
     "message": "Select the Extensible Authentication Protocol (EAP) to permit"
   },
-  "acronymPeap": {
-    "message": "PEAP"
-  },
-  "acronymEapTtls": {
-    "message": "EAP-TTLS"
-  },
-  "acronymEapTls": {
-    "message": "EAP-TLS"
-  },
-  "acronymLeap": {
-    "message": "LEAP"
-  },
-  "innerProtocol": {
-    "message": "Inner protocol"
-  },
-  "helpInnerProtocol": {
-    "message": "Select the inner protocol to use for authentication (or Automatic to use any)"
-  },
-  "automatic": {
-    "message": "Automatic",
-    "description": "Indicating that a choice is made automatically without further specification"
-  },
-  "acronymEapMschapV2": {
-    "message": "EAP-MSCHAPv2"
-  },
-  "acronymEapMd5": {
-    "message": "EAP-MD5"
-  },
-  "acronymEapPap": {
-    "message": "EAP-PAP"
-  },
-  "specifyUsernameAndPassword": {
-    "message": "Specify username and password"
-  },
-  "username": {
-    "message": "Username"
-  },
-  "helpUsername": {
-    "message": "Enter username for logging into network"
-  },
-  "password": {
-    "message": "Password"
-  },
-  "helpPassword": {
-    "message": "Enter password for logging into network"
-  },
   "serverCertificateAuthority": {
     "message": "Server Certificate Authority"
   },
-  "helpServerCertificateAuthority": {
-    "message": "Select the certificate authority to trust when authenticating to this network.  Add new certificate authorities under the Certificates tab."
+  "specifyUsernameAndPassword": {
+    "message": "Specify username and password"
   },
-  "clientCertificateAuthority": {
-    "message": "Client Certificate Authority"
-  },
-  "helpClientCertificateAuthority": {
-    "message": "Select the certificate authority that will issue the client certificate.  This information is used to determine which of the user's certificates is appropriate to use to identify them to the network.  Add new certificate authorities under the Certificates tab."
-  },
-  "enrollmentUri": {
-    "message": "Client Enrollment URI"
-  },
-  "helpEnrollmentUri": {
-    "message": "If the user does not have a suitable client certificate, upon attempting to connect to this network, direct the user to this URI to guide them through certificate enrollment.  Note that this URI should not be an internal network resource as the user may not have internal network access.  This may be an extension or data URI."
-  },
-  "proxyUrl": {
-    "message": "Proxy URL"
-  },
-  "helpProxyUrl": {
-    "message": "Set the proxy configuration URL while connected to this network."
+  "thisSsidIsNotBroadcast": {
+    "message": "This SSID is not broadcast"
   },
   "uniqueIdentifier": {
     "message": "Unique identifier"
   },
-  "helpUniqueIdentifier": {
-    "message": "This random unique identifier (GUID) identifies this network connection in case you want to update or delete it in the future."
+  "useAnyDefaultCA": {
+    "message": "Use any default Certificate Authority"
   },
-  "vpnTab": {
-    "message": "Virtual private networks"
+  "username": {
+    "message": "Username"
   },
   "vpnSettings": {
     "message": "Virtual Private Network (VPN) settings"
   },
-  "remoteHost": {
-    "message": "Remote host"
-  },
-  "helpRemoteHost": {
-    "message": "Enter the hostname or IP of the virtual private network server"
+  "vpnTab": {
+    "message": "Virtual private networks"
   },
   "vpnType": {
     "message": "VPN Type"
   },
-  "helpVpnType": {
-    "message": "Choose the type of Virtual Private Network connection"
+  "warningMissingNetworkName": {
+    "description": "This is displayed when a network has no network name.  The positional arg is the type of the network.",
+    "message": "Warning: Missing network name for $1 network."
   },
-  "l2tpIpsecPsk": {
-    "message": "L2TP over IPsec with Pre-Shared Key",
-    "description": "L2TP over IPsec is a kind of VPN.  Pre-shared key is a particular configuration of that VPN."
+  "warningMissingSSIDName": {
+    "description": "This is displayed when a network has no network name.  The first positional arg is the type of the network, the second is the name.",
+    "message": "Warning: Missing SSID name for $1 network '$2'."
   },
-  "l2tpIpsecCert": {
-    "message": "L2TP over IPsec with Certificates"
+  "warningShortWPAPassphraseUnsafe": {
+    "message": "Warning: Your $1 password for the network named '$2' is only $3 characters long.  This is too short to be a secure password, and is easily compromised."
   },
-  "presharedKey": {
-    "message": "Pre-shared key"
+  "warningWEPInherentlyUnsafe": {
+    "message": "Warning: You are using WEP to secure the Wi-Fi network named '$1'.  WEP networks are inherently insecure.  Consider using WPA or WPA2 if possible."
   },
-  "helpPresharedKey": {
-    "message": "Enter the pre-shared key used by the IPsec layer"
+  "wifiSettingsTab": {
+    "message": "Wi-Fi Settings"
   },
-  "certificatesAndTrustTab": {
-    "message": "Certificates / Trust"
-  },
-  "certificateList": {
-    "message": "Certificate list"
-  },
-  "certificateListClear": {
-    "message": "Remove All Certificates"
-  },
-  "helpCertificateList": {
-    "message": "List of certificates to install.  Drag and drop root certificate authorities and certificates to install on Chrome OS. You may drag and drop .pem and .der files."
-  },
-  "loadConfigurationTab": {
-    "message": "Load configuration"
-  },
-  "loadConfigurationFromFile": {
-    "message": "Load configuration from file"
-  },
-  "helpLoadConfigurationFromFile": {
-    "message": "Choose a file to load."
-  },
-  "saveConfigurationTab": {
-    "message": "Save configuration"
-  },
-  "saveConfigurationToFile": {
-    "message": "Save configuration to file"
-  },
-  "helpSaveConfigurationToFile": {
-    "message": "Right click the following link and choose save as. Save the file as a .onc file."
-  },
-  "useAnyDefaultCA": {
-    "message": "Use any default Certificate Authority"
-  },
-  "doNotCheckCA": {
-    "message": "Do not check (insecure)"
-  },
-  "certificateNone": {
-    "message": "None"
-  },
-  "fileNotSupported": {
-    "message": "File $1 is not supported",
-    "description": "This error message is displayed when the given file could not be loaded"
-  },
-  "errorDuringLoad": {
-    "message": "Error during load: $1"
+  "wirelessPassphrase": {
+    "message": "Passphrase"
   }
 }
diff --git a/main.html b/main.html
index 62dc3c5..4e63792 100644
--- a/main.html
+++ b/main.html
@@ -111,6 +111,21 @@
     #load-errors {
       color: #ff0000
     }
+    #load-warnings {
+      color: #807000
+    }
+    #load-header {
+      color: #008000
+    }
+    #save-errors {
+      color: #ff0000
+    }
+    #save-header {
+      color: #008000
+    }
+    #save-warnings {
+      color: #807000
+    }
     h1 {
       padding-top:20px;
       font-size:16pt;
@@ -207,13 +222,13 @@
     </div>
     <div id="phase2-auth">
       <div class="checkable">
-        <input type="checkbox" id="specify-credentials"><span
-               i18n="specifyUsernameAndPassword"></span></input>
+        <input type="checkbox" id="specify-credentials"></input>
+        <span i18n="specifyUsernameAndPassword"></span>
       </div>
       <div id="phase2-auth-cred">
         <h2 i18n="username"></h2>
         <p class="help" i18n="helpUsername"></p>
-        <input type="text" id="wifi-username">
+        <input type="text" id="wifi-identity">
         <h2 i18n="password"></h2>
         <p class="help" i18n="helpPassword"></p>
         <input type="password" id="wifi-password">
@@ -234,7 +249,7 @@
       </select>
       <h2 i18n="enrollmentUri"></h2>
       <p class="help" i18n="helpEnrollmentUri"></p>
-      <input type="text" name="wifi-enrollment-uri"></input>
+      <input id="wifi-enrollment-uri" type="text"></input>
       </div>
   </div>
   <h2 i18n="proxyUrl"></h2>
@@ -248,12 +263,12 @@
   <h1 i18n="vpnSettings"></h1>
   <h2 i18n="remoteHost"></h2>
   <p class="help" i18n="helpRemoteHost"></p>
-  <input type="text" id="vpn-server">
+  <input type="text" id="vpn-host">
   <h2 i18n="vpnType"></h2>
   <p class="help" i18n="helpVpnType"></p>
   <select id="vpn-type">
-    <option i18n="l2tpIpsecPsk" value="L2TP-IPSEC-PSK"></option>
-    <option i18n="l2tpIpsecCert" value="L2TP-IPSEC-CERT"></option>
+    <option i18n="l2tpIpsecPsk" value="L2TP-IPsec-PSK"></option>
+    <option i18n="l2tpIpsecCert" value="L2TP-IPsec-cert"></option>
   </select>
   <div class="checkable">
     <input type="checkbox" id="l2tp-specify-credentials"></input>
@@ -275,15 +290,15 @@
     </select>
     <h2 i18n="enrollmentUri"></h2>
     <p class="help" i18n="helpEnrollmentUri"></p>
-    <input name="ipsec-enrollment-uri"></input>
+    <input id="ipsec-enrollment-uri" type="text"></input>
   </div>
   <div id="l2tp-cred">
     <h2 i18n="username"></h2>
     <p class="help" i18n="helpUsername"></p>
-    <input type="text" name="l2tp-user"></input>
+    <input id="vpn-username" type="text"></input>
     <h2 i18n="password"></h2>
     <p class="help" i18n="helpPassword"></p>
-    <input name="l2tp-password" type="password"></input>
+    <input id="vpn-password" type="password"></input>
   </div>
   <h2 i18n="proxyUrl"></h2>
   <p class="help" i18n="helpProxyUrl"></p>
@@ -308,14 +323,27 @@
   <h1 i18n="loadConfigurationTab"></h1>
   <h2 i18n="loadConfigurationFromFile"></h2>
   <p class="help" i18n="helpLoadConfigurationFromFile"></p>
-  <input type="file" id="load-file" name="loadFiles" multiple />
+  <br>
+  <form id="load-file-form">
+    <input type="file" id="load-file" name="loadFiles" multiple />
+  </form>
+  <div id="load-header"></div>
   <div id="load-errors"></div>
+  <br>
+  <div id="load-warnings"></div>
 </div>
 <div class="right-pane" id="save-pane">
   <h1 i18n="saveConfigurationTab"></h1>
   <h2 i18n="saveConfigurationToFile"></h2>
-  <p class="help" i18n="helpSaveConfigurationToFile"></p>
   <br>
-  <a id="save-link" href="">Link</a>
+  <div id="save-header"></div>
+  <div id="save-errors"></div>
+  <br>
+  <div id="save-warnings"></div>
+  <br>
+  <div id="save-link-div">
+    <p class="help" i18n="helpSaveConfigurationToFile"></p>
+    <a id="save-link" href="about:blank" i18n="saveLinkText"></a>
+  </div>
 </div>
 <script type="text/javascript" src="main.js"></script>
diff --git a/main.js b/main.js
index 4412e24..e93d2ac 100644
--- a/main.js
+++ b/main.js
@@ -154,9 +154,9 @@
   }
 
   if (main.wifiRequiresClientCertficate()) {
-    $('#eap-client-ca').show();
+    $('#eap-client-cert').show();
   } else {
-    $('#eap-client-ca').hide();
+    $('#eap-client-cert').hide();
   }
 };
 
@@ -167,11 +167,7 @@
   var setting = $('#vpn-type').val();
   var save = $('#l2tp-specify-credentials').is(':checked');
   if (setting == 'L2TP-IPsec-PSK') {
-    if (save) {
-      $('#l2tpipsec-psk-div').show();
-    } else {
-      $('#l2tpipsec-psk-div').hide();
-    }
+    $('#l2tpipsec-psk-div').show();
     $('#l2tpipsec-cert-div').hide();
   } else {
     $('#l2tpipsec-psk-div').hide();
@@ -195,16 +191,6 @@
 };
 
 /**
- * Given a selection in the UI for the certificate, create a valid reference.
- * @param {String} uiValue  Setting in the UI.
- * @return {String}  ONC-specific reference to the UI.
- */
-main.createCertReference = function(uiValue) {
-  // TODO: actually do sha256 and find the cert...
-  return 'sha256(' + uiValue + ')';
-};
-
-/**
  * Create GUID.
  * @return {String}  Returns a GUID string of format
  *                   {XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX}.
@@ -230,11 +216,115 @@
   return guid;
 };
 
+main.toHex = function(str) {
+  var result = '';
+  for (var i = 0; i < str.length; i++) {
+    var byte = str.charCodeAt(i).toString(16);
+    if (byte.length == 1)
+      byte = '0' + byte;
+    result += byte;
+  }
+  return result;
+};
+
+main.isAllHex = function(str) {
+  var allHex = true;
+  var validHexChars = ['a', 'b', 'c', 'd', 'e', 'f',
+                       '0', '1', '2', '3', '4', '5',
+                       '6', '7', '8', '9'];
+  var lowercaseString = str.toLowerCase();
+  for (var i = 0; i < str.length; i++) {
+    if (validHexChars.indexOf(lowercaseString[i]) == -1) {
+      allHex = false;
+      break;
+    }
+  }
+  return allHex;
+};
+
+main.checkNetworkValidity = function(network, result) {
+  if (!network.Name) {
+    result.warnings.push(['warningMissingNetworkName', network.Type]);
+  }
+  if (network.Type == 'WiFi' && 'WiFi' in network) {
+    if (!('SSID' in network.WiFi))
+      result.warnings.push(['warningMissingSSIDName', network.Type,
+                            network.Name]);
+    if (network.WiFi.Security == 'WEP') {
+      result.warnings.push(['warningWEPInherentlyUnsafe', network.Name]);
+    }
+    if (network.WiFi.Security == 'WEP' &&
+        'Passphrase' in network.WiFi) {
+      // 5/13/16/29 characters are needed for 64/128/152/256-bit WEP ascii keys
+      // 10/26/32/58 characters are needed for 64/128/152/256-bit WEP hex keys
+      // Note that the actual bits supplied here are only
+      // 40/104/128/232 bits, respectively, but WEP adds some
+      // randomness to make up the rest of the bits.
+      var asciiLengths = [5, 13, 16, 29];
+      var hexLengths = [10, 26, 32, 58];
+      if (asciiLengths.indexOf(network.WiFi.Passphrase.length) != -1) {
+        // Always store the passphrase as hex, to avoid possible
+        // encoding issues.
+        network.WiFi.Passphrase =
+            '0x' + main.toHex(network.WiFi.Passphrase.toLowerCase());
+      } else if (hexLengths.indexOf(network.WiFi.Passphrase.length) != -1) {
+        if (!main.isAllHex(network.WiFi.Passphrase))
+          result.errors.push(['errorWEPKeyNonHexCharacters',
+                              network.WiFi.Passphrase, network.Name]);
+        network.WiFi.Passphrase = '0x' + network.WiFi.Passphrase.toLowerCase();
+      } else {
+        result.errors.push(['errorWEPKeyInvalidLength',
+                            network.Name,
+                            network.WiFi.Passphrase.length]);
+      }
+    }
+    if ((network.WiFi.Security == 'WPA' ||
+         network.WiFi.Security == 'WPA2') &&
+        'Passphrase' in network.WiFi &&
+        network.WiFi.Passphrase.length < 8) {
+      result.warnings.push(['warningShortWPAPassphraseUnsafe',
+                            network.WiFi.Security,
+                            network.Name,
+                            network.WiFi.Passphrase.length]);
+    }
+    if ('EAP' in network.WiFi) {
+      // Only check if we have password and identity for non-TLS
+      // protocols, since that uses a certificate instead.
+      if (network.WiFi.EAP.Outer != 'EAP-TLS') {
+        // We only check to see if one is specified without the other:
+        // if neither are specified, then we assume that the user
+        // didn't want to supply any credentials.  If both, then we
+        // use them.
+        if ('Identity' in network.WiFi.EAP) {
+          if (network.WiFi.EAP.Identity.length == 0)
+            result.errors.push(['errorEAPIdentityEmpty', network.Name]);
+          if (!('Password' in network.WiFi.EAP))
+            result.errors.push(['errorMissingEAPPassword',
+                                network.WiFi.EAP.Identity, network.Name]);
+        }
+        if ('Password' in network.WiFi.EAP && !('Identity' in network.WiFi.EAP))
+          result.errors.push(['errorMissingEAPIdentity', network.Name]);
+      }
+    }
+  }
+  if (network.Type == 'VPN') {
+    if (network.VPN.L2TPIPsec.Password && !network.VPN.L2TPIPsec.Username) {
+      result.errors.push(['errorMissingVPNIdentity', network.Name]);
+    }
+    if (network.VPN.Type == 'L2TP-IPsec-PSK' && !network.VPN.L2TPIPsec.PSK) {
+      result.errors.push(['errorMissingVPNPreSharedKey',
+                          network.VPN.Type, network.Name]);
+    }
+  }
+  return result;
+}
+
 /**
  * Save all the configuration currently specified in the UI.
  */
 main.saveConfig = function() {
   var config = {};
+  var result = { 'errors': [], 'warnings': [] };
   config.NetworkConfigurations = [];
   var network = {
     'GUID': $('#wifi-guid').val(),
@@ -258,81 +348,89 @@
       network.WiFi.Security = 'WPA2';
       network.WiFi.EAP = {};
       network.WiFi.EAP.Outer = $('#eap').val();
-      network.WiFi.EAP.UseSystemCAs =
-          main.getSelectedI18n('#wifi-server-ca') != 'doNotCheckCA';
+      network.WiFi.EAP.UseSystemCAs = $('#wifi-server-ca').val() != 'ignore';
       if ($('#specify-credentials').is(':checked')) {
-        network.WiFi.EAP.Identity = $('#wifi-username').val();
+        network.WiFi.EAP.Identity = $('#wifi-identity').val();
         network.WiFi.EAP.Password = $('#wifi-password').val();
       }
       if (main.wifiRequiresServerCertificate()) {
-        if (main.getSelectedI18n('#wifi-server-ca') != 'doNotCheckCA' &&
-            main.getSelectedI18n('#wifi-server-ca') != 'useAnyDefaultCA') {
-          network.WiFi.EAP.ServerCARef = main.createCertReference(
-              $('#wifi-server-ca').val());
-        } else if (main.getSelectedI18n('#wifi-server-ca') == 'doNotCheckCA') {
-          network.WiFi.EAP.ServerCARef = 'none';
+        if ($('#wifi-server-ca').val() != 'empty') {
+          network.WiFi.EAP.ServerCARef = $('#wifi-server-ca').val();
         } else {
-          network.WiFi.EAP.ServerCARef = 'default';
+          result.errors.push(['errorMissingServerCert',
+                              network.Name, network.Type]);
         }
       }
       if (main.wifiRequiresClientCertficate()) {
-        network.Wifi.EAP.ClientCertPattern = {};
-        if (main.getSelectedI18n('#wifi-client-ca') != 'certificateNone') {
-          network.Wifi.EAP.ClientCertPattern.IssuerRef =
-              main.createCertReference($('#wifi-client-ca').val());
+        network.WiFi.EAP.ClientCertPattern = {};
+        if ($('#wifi-client-ca').val() != 'empty') {
+          network.WiFi.EAP.ClientCertPattern.IssuerRef =
+              $('#wifi-client-ca').val();
+        } else {
+          result.errors.push(['errorMissingClientCert',
+                              network.Name, network.Type]);
         }
-        network.Wifi.EAP.ClientCertPattern.EnrollmentUri =
+        network.WiFi.EAP.ClientCertPattern.EnrollmentUri =
             $('#wifi-enrollment-uri').val();
       }
       break;
   }
   network.WiFi.AutoConnect = $('#auto-connect').is(':checked');
+  result = main.checkNetworkValidity(network, result);
   config.NetworkConfigurations.push(network);
   // Save VPN settings.
-  save_credentials = $('#l2tp-specify-credentials').is(':checked');
+  saveCredentials = $('#l2tp-specify-credentials').is(':checked');
   network = {
     'GUID': $('#vpn-guid').val(),
-    'Name': $('#vpn-server').val(),
+    'Name': $('#vpn-host').val(),
     'Type': 'VPN',
     'VPN': {
       'Type': $('#vpn-type').val(),
-      'Host': $('#vpn-server').val()
+      'Host': $('#vpn-host').val(),
+      'L2TPIPsec' : {}
     }
   };
   if ($('#vpn-proxy-url').val())
     network.ProxyURL = $('#vpn-proxy-url').val();
-  if (save_credentials) {
-    network.VPN.User = $('#l2tp-user').val();
-    network.VPN.Password = $('#l2tp-password').val();
+  if (saveCredentials) {
+    network.VPN.L2TPIPsec.Username = $('#vpn-username').val();
+    network.VPN.L2TPIPsec.Password = $('#vpn-password').val();
   }
-  if ($('#vpn-type').val() == 'L2TP-IPsec-RSA') {
-    if (main.getSelectedI18n('#ipsec-server-ca') != 'certificateNone') {
-      network.VPN.ServerCARef = createCertReference(
-          $('#ipsec-server-ca').val());
+  if ($('#vpn-type').val() == 'L2TP-IPsec-cert') {
+    var serverCa = $('#ipsec-server-ca').val();
+    if (serverCa != 'empty') {
+      network.VPN.L2TPIPsec.ServerCARef = serverCa;
     } else {
-      // TODO(gspencer): This is really an error case, and it really
-      // should tell the user why (i.e. because there's no valid
-      // server CA specified for a network that requires one).
-      return '';
+      result.errors.push(['errorMissingServerCert',
+                          network.Name, network.Type]);
     }
-    if (main.getSelectedI18n('#ipsec-client-ca') != 'certificateNone') {
-      network.VPN.ClientCertPattern = {
-        'IssuerRef': createCertReference($('#ipsec-client-ca').val())
+    var clientCa = $('#ipsec-client-ca').val();
+    if (clientCa != 'empty') {
+      network.VPN.L2TPIPsec.ClientCertPattern = {
+        'IssuerRef': clientCa
       };
-    } else {
-      network.VPN.ClientCertPattern = { 'IssuerRef': ''}
     }
   }
-  config.NetworkConfigurations.push(network);
-  config.Certificates = [];
-  for (var i = 0; i < certList.length; ++i) {
-    var cert = certList[i];
-    config.Certificates.push({
-      'GUID': cert.guid,
-      'X509': cert.x509
-    });
+  if ($('#vpn-type').val() == 'L2TP-IPsec-PSK') {
+    if ($('#ipsec-psk').val())
+      network.VPN.L2TPIPsec.PSK = $('#ipsec-psk').val();
   }
-  return JSON.stringify(config);
+  result = main.checkNetworkValidity(network, result);
+  config.NetworkConfigurations.push(network);
+  if (certList.length > 0) {
+    config.Certificates = [];
+    for (var i = 0; i < certList.length; ++i) {
+      var cert = certList[i];
+      config.Certificates.push({'GUID': cert.guid, 'X509': cert.x509});
+    }
+  }
+  try {
+    result.config = JSON.stringify(config);
+  }
+  catch (e) {
+    result.errors.push(['errorJSONStringify', e]);
+  }
+  return result;
 };
 
 /**
@@ -349,19 +447,49 @@
 };
 
 /**
- * Update the save link to the current UI configuration.
+ * Converts the given message list into localized HTML to display.
+ * @param {Array.<Array.<String>>} messageList array of messages to
+ *   be converted.
+ */
+main.convertMessagesToHtml = function(messageList) {
+  messages = [];
+  for (var i = 0; i < messageList.length; i++) {
+    var message = messageList[i];
+    if (message.length > 1) {
+      messages.push(chrome.i18n.getMessage(message[0], message.slice(1)));
+    } else {
+      messages.push(chrome.i18n.getMessage(message[0]));
+    }
+  }
+  return '<p>' + messages.join('</p><p>') + '<\p>';
+}
+
+/**
+ * Update the save button to the current UI configuration.
  */
 main.updateSaveLink = function() {
-  $('#save-link').attr('href','about:blank');  // In case something fails.
-  var rawConfig = main.saveConfig();
-  if (!rawConfig)
-    return;
-  var configArray = raw_config.split('').map(function(c) {
-      return c.charCodeAt(0);
-    });
-  config = Base64.encode(main.arrayToUint8Array(configArray));
-  $('#save-link').attr('href',
-                       'data:application/octet-stream;base64,' + config);
+  var saveLink = $('#save-link');
+  var saveLinkDiv = $('#save-link-div');
+  saveLinkDiv.hide();  // In case something fails.
+  var configResult = main.saveConfig();
+  if (configResult.errors.length == 0) {
+    $('#save-errors-header').html(chrome.i18n.getMessage('saveSucceeded'));
+  } else {
+    $('#save-errors-header').html('');
+  }
+  $('#save-errors').html(main.convertMessagesToHtml(configResult.errors));
+  $('#save-warnings').html(main.convertMessagesToHtml(configResult.warnings));
+  if (configResult.errors.length == 0) {
+    var configArray = configResult.config.split('').map(function(c) {
+        return c.charCodeAt(0);
+      });
+    config = Base64.encode(main.arrayToUint8Array(configArray));
+    saveLinkDiv.show();
+    saveLink.attr('href', 'data:application/octet-stream;base64,' + config);
+  } else {
+    saveLinkDiv.hide();
+    saveLink.attr('href', 'about:blank');
+  }
 };
 
 /**
@@ -373,17 +501,31 @@
   if (clearFirst) optionList.options.length = 0;
   if (certList.length == 0 && optionList.options.length == 0) {
     optionList.options.add(new Option(
-        chrome.i18n.getMessage('certificateNone')));
+        chrome.i18n.getMessage('certificateEmpty'), 'empty'));
     optionList.disabled = true;
   } else {
     optionList.disabled = false;
     for (var i = 0; i < certList.length; ++i) {
-      optionList.options.add(new Option(certList[i].subject.commonName));
+      optionList.options.add(new Option(certList[i].subject.organizationName +
+                                        ' / ' + certList[i].subject.commonName,
+                                        certList[i].guid));
     }
   }
 };
 
 /**
+ * Find a cert in the cert list by GUID.
+ */
+main.findCertInList = function(guid) {
+  for (var i = 0; i < certList.length; ++i) {
+    if (certList[i].guid == guid)
+      return certList[i];
+  }
+  return null;
+}
+
+
+/**
  * Update the certificate lists across all tabs.
  */
 main.updateCertLists = function() {
@@ -398,10 +540,10 @@
   var serverCaDom = $('#wifi-server-ca')[0];
   serverCaDom.options.length = 0;
   serverCaDom.options.add(new Option(
-      chrome.i18n.getMessage('useAnyDefaultCA')));
+      chrome.i18n.getMessage('useAnyDefaultCA'), 'default'));
   main.addCertificates(serverCaDom, false);
   serverCaDom.options.add(new Option(
-      chrome.i18n.getMessage('doNotCheckCA')));
+      chrome.i18n.getMessage('doNotCheckCA'), 'ignore'));
   main.addCertificates($('#wifi-client-ca')[0], true);
   main.addCertificates($('#ipsec-server-ca')[0], true);
   main.addCertificates($('#ipsec-client-ca')[0], true);
@@ -511,7 +653,12 @@
  * to prepare for a load.
  */
 main.resetUI = function() {
+  $('#load-header').html('');
   $('#load-errors').html('');
+  $('#load-warnings').html('');
+  $('#save-errors').html('');
+  $('#save-errors-header').html('');
+  $('#save-warnings').html('');
   $('#ssid').val('');
   $('#hidden-ssid')[0].checked = false;
   $('#auto-connect')[0].checked = false;
@@ -520,6 +667,8 @@
   $('#wifi-proxy-url').val('');
   main.setSelectedI18n('#eap', 'acronymPeap');
   main.setSelectedI18n('#phase2', 'automatic');
+  $('#l2tp-specify-credentials')[0].checked = false;
+  $('#specify-credentials')[0].checked = false;
   $('#username').val('');
   $('#password').val('');
   $('#wifi-server-ca').val('');
@@ -533,111 +682,17 @@
 };
 
 /**
- * Set an error during loading.  errorText is assumed to already
- * be translated or too difficult to translate to be worthwhile.
- * @param {String} errorText  text to display.
- */
-main.setLoadError = function(errorText) {
-  $('#load-errors').html(chrome.i18n.getMessage('errorDuringLoad',
-                                                [errorText]));
-}
-
-/**
  * Load the given ONC configuration and show any errors in errorDom.
  * @param {String} config  ONC formatted string.
  */
 main.loadConfig = function(config) {
+  var result = { 'errors': [], 'warnings': [] };
   main.resetUI();
   try {
     config = JSON.parse(config);
   } catch(e) {
-    main.setLoadError(e.toString());
-    return null;
-  }
-  if ('NetworkConfigurations' in config) {
-    var netConfigs = config.NetworkConfigurations;
-    networkConfigurationsLoop:
-    for (var i = 0; i < netConfigs.length; ++i) {
-      var netConfig = netConfigs[i];
-      var requiredNetworkConfigurationKeys = [
-        'GUID',
-        'Type'
-      ];
-      for (var i = 0; i < requiredNetworkConfigurationKeys.length; ++i) {
-        var key = requiredNetworkConfigurationKeys[i];
-        if (!(key in netConfig)) {
-          main.setLoadError('NetworkConfiguration missing ' + key);
-          continue networkConfigurationsLoop;
-        }
-      }
-      if (netConfig.Type == 'WiFi') {
-        if (!('WiFi' in netConfig)) {
-          main.setLoadError('WiFi object missing');
-          continue networkConfigurationsLoop;
-        }
-        var wifiConfig = netConfig.WiFi;
-        if (!('SSID' in wifiConfig)) {
-          main.setLoadError('SSID missing from WiFi object');
-          continue networkConfigurationsLoop;
-        }
-        if (!('Security' in wifiConfig)) {
-          main.setLoadError('Security kind missing from WiFi object');
-          continue networkConfigurationsLoop;
-        }
-        $('#wifi-guid').val(netConfig.GUID);
-        $('#ssid').val(wifiConfig.SSID);
-        if ('AutoConnect' in wifiConfig)
-          $('#auto-connect').val(wifiConfig.AutoConnect != false);
-        if ('HiddenSSID' in wifiConfig)
-          $('#hidden-ssid').val(wifiConfig.HiddenSSID != false);
-        if ('Passphrase' in wifiConfig)
-          $('#passphrase').val(wifiConfig.Passphrase);
-        $('#security').val(wifiConfig.Security);
-        if ('EAP' in netConfig.WiFi && wifiConfig.Security == 'WPA2') {
-          // TODO: potentially handle Dynamic WEP / 802.1X.
-          var eapConfig = netConfig.WiFi.EAP;
-          if (!('Outer' in eapConfig)) {
-            main.setLoadError('Missing Outer protocol kind');
-            continue networkConfigurationsLoop;
-          }
-          switch (eapConfig.Outer) {
-            case 'PEAP':
-              main.setSelectedI18n('#eap', 'acronymPeap');
-              break;
-            case 'EAP-TTLS':
-              main.setSelectedI18n('#eap', 'acronymEapTtls');
-              break;
-            case 'EAP-TLS':
-              main.setSelectedI18n('#eap', 'acronymEapTls');
-              break;
-            case 'LEAP':
-              main.setSelectedI18n('#eap', 'acronymLeap');
-              break;
-            default:
-              main.setLoadError('Unhandled EAP type');
-              continue networkConfigurationsLoop;
-          }
-          if ('UseSystemCAs' in eapConfig && !eapConfig.UseSystemCAs) {
-            main.setSelectedI18n('#wifi-server-ca', 'useAnyDefaultCA');
-          } else if (!('ServerCARef' in eapConfig)) {
-            main.setSelectedI18n('#wifi-server-ca', 'doNotCheckCA');
-          } else {
-            // TODO: Find matching server CA certificate from given reference.
-          }
-          // TODO: handle client certificate references and patterns.
-        }
-        if ('ProxyURL' in netConfig)
-          $('#wifi-proxy-url').val(netConfig.ProxyURL);
-        // TODO: handle unrecognized/vendor fields.
-      } else if (netConfig.Type == 'VPN') {
-        $('#vpn-guid').val(netConfig.GUID);
-        // TODO: handle loading VPN data
-      } else {
-        main.setLoadError('Unknown networkConfiguration Type ' +
-                          netConfig.Type);
-        continue networkConfigurationsLoop;
-      }
-    }
+    result.errors.push(['errorDuringLoad', e.toString()]);
+    return result;
   }
   if ('Certificates' in config) {
     var certificates = config.Certificates;
@@ -675,7 +730,160 @@
     }
     main.updateCertLists();
   }
+  if ('NetworkConfigurations' in config) {
+    var netConfigs = config.NetworkConfigurations;
+    var requiredNetworkConfigurationKeys = [ 'GUID', 'Type' ];
+    networkConfigurationsLoop:
+    for (var i = 0; i < netConfigs.length; ++i) {
+      var netConfig = netConfigs[i];
+      for (var j = 0; j < requiredNetworkConfigurationKeys.length; ++j) {
+        var key = requiredNetworkConfigurationKeys[j];
+        if (!(key in netConfig)) {
+          result.errors.push(['errorLoadRequiredObjectMissing', key]);
+          continue networkConfigurationsLoop;
+        }
+      }
+      if (netConfig.Type == 'WiFi') {
+        if (!('WiFi' in netConfig)) {
+          result.errors.push(['errorLoadRequiredObjectMissing', 'WiFi']);
+          continue networkConfigurationsLoop;
+        }
+        var wifiConfig = netConfig.WiFi;
+        if (!('SSID' in wifiConfig)) {
+          result.errors.push(['errorLoadRequiredObjectMissing', 'SSID']);
+          continue networkConfigurationsLoop;
+        }
+        if (!('Security' in wifiConfig)) {
+          result.errors.push(['errorLoadRequiredObjectMissing', 'Security']);
+          continue networkConfigurationsLoop;
+        }
+        $('#wifi-guid').val(netConfig.GUID);
+        $('#ssid').val(wifiConfig.SSID);
+        if ('AutoConnect' in wifiConfig)
+          $('#auto-connect').val(wifiConfig.AutoConnect != false);
+        if ('HiddenSSID' in wifiConfig)
+          $('#hidden-ssid').val(wifiConfig.HiddenSSID != false);
+        if ('Passphrase' in wifiConfig) {
+          // Strip off any '0x' from hex passphrases.  We'll correctly
+          // interpret it as a hex passphrase when we save and add the
+          // '0x' back on.
+          if (wifiConfig.Security == 'WEP' &&
+              wifiConfig.Passphrase.substr(0,2) == '0x')
+            wifiConfig.Passphrase = wifiConfig.Passphrase.substr(2);
+          $('#passphrase').val(wifiConfig.Passphrase);
+        }
+        switch (wifiConfig.Security) {
+          case 'WEP':
+          case 'WPA':
+          case 'WPA2':
+            $('#security').val(wifiConfig.Security);
+            break;
+          default:
+            result.errors.push(['errorLoadUnhandledSecurityType',
+                                wifiConfig.Security, netConfig.Name]);
+            continue networkConfigurationsLoop;
+        }
+        if ('EAP' in netConfig.WiFi && wifiConfig.Security == 'WPA2') {
+          $('#security').val('WPA2Enterprise');
+          // TODO: potentially handle Dynamic WEP / 802.1X.
+          var eapConfig = netConfig.WiFi.EAP;
+          if (!('Outer' in eapConfig)) {
+            result.errors.push(['errorLoadRequiredObjectMissing',
+                                'WiFi.EAP.Outer']);
+            continue networkConfigurationsLoop;
+          }
+          switch (eapConfig.Outer) {
+            case 'PEAP':
+            case 'EAP-TTLS':
+            case 'EAP-TLS':
+            case 'LEAP':
+              $('#eap').val(eapConfig.Outer);
+              break;
+            default:
+              result.errors.push(['errorLoadUnhandledEapType',
+                                  eapConfig.Outer, netConfig.Name]);
+              continue networkConfigurationsLoop;
+          }
+          if ('Identity' in eapConfig) {
+            $('#wifi-identity').val(eapConfig.Identity);
+          }
+          if ('Password' in eapConfig) {
+            $('#wifi-password').val(eapConfig.Password);
+          }
+          if ('Identity' in eapConfig || 'Password' in eapConfig)
+            $('#specify-credentials')[0].checked = true;
+          if ('UseSystemCAs' in eapConfig && !eapConfig.UseSystemCAs) {
+            $('#wifi-server-ca').val('default');
+          } else if (!('ServerCARef' in eapConfig)) {
+            $('#wifi-server-ca').val('ignore');
+          } else {
+            if (main.findCertInList(eapConfig.ServerCARef) != null)
+              $('#wifi-server-ca').val(eapConfig.ServerCARef);
+          }
+          if ('ClientCertPattern' in eapConfig) {
+            // TODO: handle more complex client cert patterns.
+            var certPattern = eapConfig.ClientCertPattern;
+            if ('IssuerRef' in certPattern)
+              $('#wifi-client-ca').val(certPattern.IssuerRef);
+            if ('EnrollmentUri' in certPattern)
+              $('#wifi-enrollment-uri').val(certPattern.EnrollmentUri);
+          }
+        }
+        if ('ProxyURL' in netConfig)
+          $('#wifi-proxy-url').val(netConfig.ProxyURL);
+        // TODO: handle unrecognized/vendor fields.
+      } else if (netConfig.Type == 'VPN') {
+        $('#vpn-guid').val(netConfig.GUID);
+        if (!('VPN' in netConfig)) {
+          result.errors.push(['errorLoadRequiredObjectMissing', 'VPN']);
+          continue networkConfigurationsLoop;
+        }
+        var vpnConfig = netConfig.VPN;
+        if (!('Type' in vpnConfig)) {
+          result.errors.push(['errorLoadRequiredObjectMissing', 'VPN.Type']);
+          continue networkConfigurationsLoop;
+        }
+        $('#vpn-type').val(vpnConfig.Type);
+        if (!('Host' in vpnConfig)) {
+          result.errors.push(['errorLoadRequiredObjectMissing', 'VPN.Host']);
+          continue networkConfigurationsLoop;
+        }
+        $('#vpn-host').val(vpnConfig.Host);
+        if (!('L2TPIPsec' in vpnConfig)) {
+          result.errors.push(['errorLoadRequiredObjectMissing',
+                              'VPN.L2TPIPsec']);
+          continue networkConfigurationsLoop;
+        }
+        var l2tpConfig = vpnConfig.L2TPIPsec;
+        if ('PSK' in l2tpConfig)
+          $('#ipsec-psk').val(l2tpConfig.PSK);
+        if ('Username' in l2tpConfig)
+          $('#vpn-username').val(l2tpConfig.Username);
+        if ('Password' in l2tpConfig)
+          $('#vpn-password').val(l2tpConfig.Password);
+        if ('Username' in l2tpConfig || 'Password' in l2tpConfig)
+          $('#l2tp-specify-credentials')[0].checked = true;
+        if ('ProxyURL' in netConfig)
+          $('#vpn-proxy-url').val(netConfig.ProxyURL);
+      } else {
+        result.errors.push(['errorLoadUnknownNetworkConfigType',
+                            netConfig.Type]);
+        continue networkConfigurationsLoop;
+      }
+    }
+  }
   main.updateAllVisibility();
+
+  // Display the errors we found (if any)
+  if (result.errors.length == 0) {
+    $('#load-header').html(chrome.i18n.getMessage('loadSucceeded'));
+  } else {
+    // Clear everything out if we had errors.
+    main.resetUI();
+    $('#load-file-form')[0].reset();
+  }
+  $('#load-errors').html(main.convertMessagesToHtml(result.errors));
+  $('#load-warnings').html(main.convertMessagesToHtml(result.warnings));
 };
 
 /**
@@ -712,10 +920,10 @@
 main.translateText = function() {
   var i18nNodes = document.querySelectorAll('[i18n]');
   for (var i = 0; i < i18nNodes.length; ++i) {
-    var i18n_id = i18nNodes[i].getAttribute('i18n');
-    var translation = chrome.i18n.getMessage(i18n_id);
+    var i18nId = i18nNodes[i].getAttribute('i18n');
+    var translation = chrome.i18n.getMessage(i18nId);
     if (translation == '') {
-      translation = 'NO TRANSLATION FOR: ' + i18n_id;
+      translation = 'NO TRANSLATION FOR: ' + i18nId;
     }
     i18nNodes[i].textContent = translation;
   }