Set up initial language translation

- Add i18n internationalization plugin
- Create json files for group 0 English and Spanish
- Uses $t method to set up initial translations on login page
- Meta title is translated using i18n in App.vue and PageTitle.Vue

Signed-off-by: Dixsie Wolmers <dixsie@ibm.com>
Change-Id: Ifce9f5e54d96f8b2a13239ad6178892f99fc4537
diff --git a/src/App.vue b/src/App.vue
index a5a768a..30de752 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -13,7 +13,7 @@
   name: 'App',
   watch: {
     $route: function(to) {
-      document.title = to.meta.title || 'Page is Missing Title';
+      document.title = this.$t(to.meta.title) || 'Page is missing title';
     }
   }
 };
diff --git a/src/components/Global/PageTitle.vue b/src/components/Global/PageTitle.vue
index 5c64f0d..59bb6a1 100644
--- a/src/components/Global/PageTitle.vue
+++ b/src/components/Global/PageTitle.vue
@@ -14,10 +14,10 @@
       default: ''
     }
   },
-  data() {
-    return {
-      title: this.$route.meta.title
-    };
+  computed: {
+    title() {
+      return this.$t(this.$route.meta.title);
+    }
   }
 };
 </script>
diff --git a/src/i18n.js b/src/i18n.js
new file mode 100644
index 0000000..09b3f4c
--- /dev/null
+++ b/src/i18n.js
@@ -0,0 +1,29 @@
+import Vue from 'vue';
+import VueI18n from 'vue-i18n';
+
+Vue.use(VueI18n);
+
+function loadLocaleMessages() {
+  const locales = require.context(
+    './locales',
+    true,
+    /[A-Za-z0-9-_,\s]+\.json$/i
+  );
+  const messages = {};
+  locales.keys().forEach(key => {
+    const matched = key.match(/([A-Za-z0-9-_]+)\./i);
+    if (matched && matched.length > 1) {
+      const locale = matched[1];
+      messages[locale] = locales(key);
+    }
+  });
+  return messages;
+}
+
+export default new VueI18n({
+  // default language is English
+  locale: 'en',
+  // locale messages with a message key that doesn't exist will fallback to English
+  fallbackLocale: 'en',
+  messages: loadLocaleMessages()
+});
diff --git a/src/locales/en.json b/src/locales/en.json
new file mode 100644
index 0000000..8464ff4
--- /dev/null
+++ b/src/locales/en.json
@@ -0,0 +1,38 @@
+{
+  "global": {
+    "formField": {
+      "validator": "Field required"
+    }
+  },
+  "login": {
+    "language": {
+      "label": "Language"
+    },
+    "languages": {
+      "select": "Select an option",
+      "english": "English",
+      "spanish": "Spanish"
+    },
+    "logIn": {
+      "label": "Log in"
+    },
+    "errorMsg": {
+      "title": "Invalid username or password.",
+      "action": "Please try again."
+    },
+    "password": {
+      "label": "Password",
+      "validator": "@:global.formField.validator"
+    },
+    "username": {
+      "label": "Username",
+      "validator": "@:global.formField.validator"
+    }
+  },
+  "pageTitle": {
+    "localUserMgmt": "Local user management",
+    "login": "Login",
+    "overview": "Overview",
+    "unauthorized": "Unauthorized"
+  }
+}
\ No newline at end of file
diff --git a/src/locales/es.json b/src/locales/es.json
new file mode 100644
index 0000000..30d1fd1
--- /dev/null
+++ b/src/locales/es.json
@@ -0,0 +1,38 @@
+{
+  "global": {
+    "formField": {
+      "validator": "Campo requerido"
+    }
+  },
+  "login": {
+    "language": {
+      "label": "Idioma"
+    },
+    "languages": {
+      "select": "Seleccione una opción",
+      "english": "Inglés",
+      "spanish": "Español"
+    },
+    "logIn": {
+      "label": "Iniciar sesión"
+    },
+    "errorMsg": {
+      "title": "Usuario o contraseña invalido.",
+      "action": "Inténtalo de nuevo."
+    },
+    "password": {
+      "label": "Contraseña",
+      "validator": "@:global.formField.validator"
+    },
+    "username": {
+      "label": "Nombre de usuario",
+      "validator": "@:global.formField.validator"
+    }
+  },
+  "pageTitle": {
+    "localUserMgmt": "Administración de usuarios locales",
+    "login": "Inicio de sesión",
+    "overview": "Información general",
+    "unauthorized": "No autorizado"
+  }
+}
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
index d80d201..7216751 100644
--- a/src/main.js
+++ b/src/main.js
@@ -25,6 +25,7 @@
   ToastPlugin
 } from 'bootstrap-vue';
 import Vuelidate from 'vuelidate';
+import i18n from './i18n';
 
 Vue.filter('date', dateFilter);
 
@@ -59,5 +60,6 @@
 new Vue({
   router,
   store,
+  i18n,
   render: h => h(App)
 }).$mount('#app');
diff --git a/src/router/index.js b/src/router/index.js
index 71b90fb..bec7f54 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -5,6 +5,8 @@
 
 Vue.use(VueRouter);
 
+// Meta title is translated using i18n in App.vue and PageTitle.Vue
+// Example meta: {title: 'pageTitle.overview'}
 const routes = [
   {
     path: '/',
@@ -18,7 +20,7 @@
         path: '',
         component: () => import('@/views/Overview'),
         meta: {
-          title: 'Overview'
+          title: 'pageTitle.overview'
         }
       },
       {
@@ -26,7 +28,7 @@
         name: 'local-users',
         component: () => import('@/views/AccessControl/LocalUserManagement'),
         meta: {
-          title: 'Local user management'
+          title: 'pageTitle.localUserMgmt'
         }
       },
       {
@@ -34,7 +36,7 @@
         name: 'unauthorized',
         component: () => import('@/views/Unauthorized'),
         meta: {
-          title: 'Unauthorized'
+          title: 'pageTitle.unauthorized'
         }
       }
     ]
@@ -44,7 +46,7 @@
     name: 'login',
     component: () => import('@/views/Login'),
     meta: {
-      title: 'Login'
+      title: 'pageTitle.login'
     }
   }
 ];
diff --git a/src/views/Login/Login.vue b/src/views/Login/Login.vue
index 35af76f..d4fde8c 100644
--- a/src/views/Login/Login.vue
+++ b/src/views/Login/Login.vue
@@ -13,17 +13,24 @@
             <h1>OpenBMC</h1>
           </div>
         </b-col>
-
         <b-col md="6">
           <b-form class="login-form" novalidate @submit.prevent="login">
             <b-alert class="login-error" :show="authError" variant="danger">
               <p id="login-error-alert">
-                <strong>{{ errorMsg.title }}</strong>
-                <span>{{ errorMsg.action }}</span>
+                <strong>{{ $t('login.errorMsg.title') }}</strong>
+                <span>{{ $t('login.errorMsg.action') }}</span>
               </p>
             </b-alert>
             <div class="login-form__section">
-              <label for="username">Username</label>
+              <label for="language">{{ $t('login.language.label') }}</label>
+              <b-form-select
+                id="language"
+                v-model="$i18n.locale"
+                :options="languages"
+              ></b-form-select>
+            </div>
+            <div class="login-form__section">
+              <label for="username">{{ $t('login.username.label') }}</label>
               <b-form-input
                 id="username"
                 v-model="userInfo.username"
@@ -36,13 +43,12 @@
               </b-form-input>
               <b-form-invalid-feedback role="alert">
                 <template v-if="!$v.userInfo.username.required">
-                  Field required
+                  {{ $t('login.username.validator') }}
                 </template>
               </b-form-invalid-feedback>
             </div>
-
             <div class="login-form__section">
-              <label for="password">Password</label>
+              <label for="password">{{ $t('login.password.label') }}</label>
               <b-form-input
                 id="password"
                 v-model="userInfo.password"
@@ -54,18 +60,17 @@
               </b-form-input>
               <b-form-invalid-feedback role="alert">
                 <template v-if="!$v.userInfo.password.required">
-                  Field required
+                  {{ $t('login.password.validator') }}
                 </template>
               </b-form-invalid-feedback>
             </div>
-
             <b-button
               block
               class="mt-5"
               type="submit"
               variant="primary"
               :disabled="disableSubmitButton"
-              >Log in</b-button
+              >{{ $t('login.logIn.label') }}</b-button
             >
           </b-form>
         </b-col>
@@ -83,15 +88,22 @@
   mixins: [VuelidateMixin],
   data() {
     return {
-      errorMsg: {
-        title: 'Invalid username or password.',
-        action: 'Please try again.'
-      },
       userInfo: {
         username: null,
         password: null
       },
-      disableSubmitButton: false
+      disableSubmitButton: false,
+      languages: [
+        { value: null, text: this.$t('login.languages.select') },
+        {
+          value: 'en',
+          text: this.$t('login.languages.english')
+        },
+        {
+          value: 'es',
+          text: this.$t('login.languages.spanish')
+        }
+      ]
     };
   },
   computed: {
diff --git a/src/views/Overview/OverviewQuickLinks.vue b/src/views/Overview/OverviewQuickLinks.vue
index d9d86ca..8925397 100644
--- a/src/views/Overview/OverviewQuickLinks.vue
+++ b/src/views/Overview/OverviewQuickLinks.vue
@@ -43,7 +43,7 @@
   },
   data() {
     return {
-      serverLEDChecked: false
+      serverLedChecked: false
     };
   },
   computed: {