Added Bootstrap and Vue I18N; added header and footer components; added language switcher
This commit is contained in:
@@ -2,5 +2,7 @@
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100
|
||||
"printWidth": 100,
|
||||
"useTabs": true,
|
||||
"tabWidth": 4
|
||||
}
|
||||
|
||||
66
README.md
66
README.md
@@ -1,6 +1,22 @@
|
||||
# glossary_admin_client
|
||||
# NATO Glossary, Admin Client
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
This is the repo for the admin client of the NATO Glossary project. This README will explain all basic commands for running, developing, testing, and building the app.
|
||||
|
||||
## Stack
|
||||
|
||||
- [Vue 3](https://vuejs.org/) + [Vite](https://vite.dev/)
|
||||
- [TypeScript](https://www.typescriptlang.org/)
|
||||
- [Bootstrap](https://getbootstrap.com/) for UI and styling
|
||||
- [Vue I18N](https://vue-i18n.intlify.dev/) for multi-language options
|
||||
- [Pinia](https://pinia.vuejs.org/) for state management
|
||||
- [Vitest](https://vitest.dev/) for unit testing
|
||||
- [Playwright](https://playwright.dev/) for end-to-end (e2e) testing
|
||||
- [ESLint](https://eslint.org/) for code linting
|
||||
- [Prettier](https://prettier.io/) for code formatting
|
||||
|
||||
## File Structure
|
||||
|
||||
Given the small scope of this project, we're beginning with a [Flat Structure](https://alexop.dev/posts/how-to-structure-vue-projects/#1-flat-structure-perfect-for-small-projects) to organize the files.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
@@ -17,30 +33,36 @@ This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
## First-Time Setup
|
||||
|
||||
```sh
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
## Development Environment
|
||||
|
||||
```sh
|
||||
To properly run on and test this application, you'll need the following pieces to be running:
|
||||
|
||||
- Development Server (the web app)
|
||||
- Local Database (a dev database that provides dummy data to testing)
|
||||
- Development API (the API that serves the dummy data from the database to the web app)
|
||||
|
||||
### Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||
|
||||
```sh
|
||||
@@ -71,3 +93,21 @@ npm run test:e2e -- --debug
|
||||
```sh
|
||||
npm run lint
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
130
package-lock.json
generated
130
package-lock.json
generated
@@ -8,8 +8,11 @@
|
||||
"name": "glossary_admin_client",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.8",
|
||||
"bootstrap-vue-next": "^0.42.0",
|
||||
"pinia": "^3.0.4",
|
||||
"vue": "^3.5.26",
|
||||
"vue-i18n": "^11.2.8",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -1403,6 +1406,50 @@
|
||||
"url": "https://github.com/sponsors/nzakas"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "11.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.2.8.tgz",
|
||||
"integrity": "sha512-nBq6Y1tVkjIUsLsdOjDSJj4AsjvD0UG3zsg9Fyc+OivwlA/oMHSKooUy9tpKj0HqZ+NWFifweHavdljlBLTwdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/message-compiler": "11.2.8",
|
||||
"@intlify/shared": "11.2.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/message-compiler": {
|
||||
"version": "11.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.2.8.tgz",
|
||||
"integrity": "sha512-A5n33doOjmHsBtCN421386cG1tWp5rpOjOYPNsnpjIJbQ4POF0QY2ezhZR9kr0boKwaHjbOifvyQvHj2UTrDFQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "11.2.8",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/shared": {
|
||||
"version": "11.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.2.8.tgz",
|
||||
"integrity": "sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
@@ -1562,6 +1609,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.53",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz",
|
||||
@@ -2965,6 +3023,52 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "5.3.8",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
|
||||
"integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/twbs"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.8"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap-vue-next": {
|
||||
"version": "0.42.0",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap-vue-next/-/bootstrap-vue-next-0.42.0.tgz",
|
||||
"integrity": "sha512-CSkk2jL8Y2U9zgdutxjbBx4DRhKFmxTmaTQhhMYXVvczkVksw2mzRu1MU//950z0lvUwSqJo3R7y0opoyEA8zg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap-vue-next"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@floating-ui/vue": "*",
|
||||
"@vueuse/core": "*",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@floating-ui/vue": {
|
||||
"optional": true
|
||||
},
|
||||
"@vueuse/core": {
|
||||
"optional": true
|
||||
},
|
||||
"vue-router": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
@@ -6293,6 +6397,32 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-i18n": {
|
||||
"version": "11.2.8",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.2.8.tgz",
|
||||
"integrity": "sha512-vJ123v/PXCZntd6Qj5Jumy7UBmIuE92VrtdX+AXr+1WzdBHojiBxnAxdfctUFL+/JIN+VQH4BhsfTtiGsvVObg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "11.2.8",
|
||||
"@intlify/shared": "11.2.8",
|
||||
"@vue/devtools-api": "^6.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-i18n/node_modules/@vue/devtools-api": {
|
||||
"version": "6.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
|
||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.6.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
|
||||
|
||||
@@ -18,8 +18,11 @@
|
||||
"format": "prettier --write --experimental-cli src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.8",
|
||||
"bootstrap-vue-next": "^0.42.0",
|
||||
"pinia": "^3.0.4",
|
||||
"vue": "^3.5.26",
|
||||
"vue-i18n": "^11.2.8",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
12
src/App.vue
12
src/App.vue
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import { BApp } from 'bootstrap-vue-next'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>You did it!</h1>
|
||||
<p>
|
||||
Visit <a href="https://vuejs.org/" target="_blank" rel="noopener">vuejs.org</a> to read the
|
||||
documentation
|
||||
</p>
|
||||
<BApp>
|
||||
<RouterView />
|
||||
</BApp>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
11
src/components/AppFooter.vue
Normal file
11
src/components/AppFooter.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { BContainer } from 'bootstrap-vue-next';
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- <div class="bg-dark text-white text-center py-3 fixed-bottom">Footer</div> -->
|
||||
<BContainer fluid class="bg-dark text-white fixed-bottom py-3">
|
||||
Footer
|
||||
</BContainer>
|
||||
</template>
|
||||
64
src/components/AppHeader.vue
Normal file
64
src/components/AppHeader.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
BButton,
|
||||
BCollapse,
|
||||
BDropdownItem,
|
||||
BNavbar,
|
||||
BNavbarBrand,
|
||||
BNavbarNav,
|
||||
BNavbarToggle,
|
||||
BNavItem,
|
||||
BNavItemDropdown,
|
||||
vBColorMode
|
||||
} from 'bootstrap-vue-next'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// use global scope
|
||||
const { t } = useI18n({
|
||||
useScope: 'global',
|
||||
inheritLocale: true,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<b-navbar v-b-color-mode="'dark'" toggleable="lg" variant="primary">
|
||||
<b-navbar-brand>{{ t('navbar.natoGlossary') }}</b-navbar-brand>
|
||||
<b-navbar-toggle target="nav-collapse" />
|
||||
<b-collapse id="nav-collapse" is-nav>
|
||||
<b-navbar-nav>
|
||||
<b-nav-item href="#">
|
||||
<RouterLink
|
||||
:to="{ name: 'Home' }"
|
||||
class="nav-link active"
|
||||
aria-current="page"
|
||||
href="#"
|
||||
>{{ t('navbar.home') }}</RouterLink
|
||||
>
|
||||
</b-nav-item>
|
||||
<b-nav-item href="#">
|
||||
<RouterLink
|
||||
:to="{ name: 'List' }"
|
||||
class="nav-link active"
|
||||
aria-current="page"
|
||||
href="#"
|
||||
>{{ t('navbar.list') }}</RouterLink
|
||||
>
|
||||
</b-nav-item>
|
||||
</b-navbar-nav>
|
||||
<!-- Right aligned nav items -->
|
||||
<b-navbar-nav class="ms-auto mb-2 mb-lg-0">
|
||||
<b-nav-item-dropdown text="Lang" right>
|
||||
<b-dropdown-item
|
||||
v-for="locale in $i18n.availableLocales"
|
||||
:key="`locale-${locale}`"
|
||||
v-on:click="$i18n.locale = locale"
|
||||
>{{ locale }}</b-dropdown-item
|
||||
>
|
||||
</b-nav-item-dropdown>
|
||||
</b-navbar-nav>
|
||||
<b-button type="button">{{ t('navbar.login') }}</b-button>
|
||||
</b-collapse>
|
||||
</b-navbar>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
14
src/layout/DefaultLayout.vue
Normal file
14
src/layout/DefaultLayout.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import AppHeader from '../components/AppHeader.vue'
|
||||
import AppFooter from '../components/AppFooter.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<AppHeader />
|
||||
<RouterView />
|
||||
<AppFooter />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
12
src/locales/en.json
Normal file
12
src/locales/en.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"hello": "hello world!",
|
||||
"errors": ["Error!"],
|
||||
"navbar": {
|
||||
"natoGlossary": "NATO GLossary",
|
||||
"home": "Home",
|
||||
"list": "List",
|
||||
"language": "Language",
|
||||
"logout": "logout",
|
||||
"login": "login"
|
||||
}
|
||||
}
|
||||
12
src/locales/fr.json
Normal file
12
src/locales/fr.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"hello": "bonjour monde !",
|
||||
"errors": ["Erreur !"],
|
||||
"navbar": {
|
||||
"natoGlossary": "NATO GLossary",
|
||||
"home": "Home",
|
||||
"list": "Liste",
|
||||
"language": "Language",
|
||||
"logout": "se déconnecter",
|
||||
"login": "se connecter"
|
||||
}
|
||||
}
|
||||
44
src/main.ts
44
src/main.ts
@@ -1,12 +1,56 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import App from './App.vue'
|
||||
import 'bootstrap/dist/css/bootstrap.css'
|
||||
import 'bootstrap-vue-next/dist/bootstrap-vue-next.css'
|
||||
import router from './router'
|
||||
|
||||
/**
|
||||
* import locale messages resource from json for global scope
|
||||
*/
|
||||
import en from './locales/en.json'
|
||||
import fr from './locales/fr.json'
|
||||
|
||||
/**
|
||||
* setup vue-i18n with i18n resources with global type definition.
|
||||
* The defined i18n resource schema in the `*.d.ts` file is checked with typeScript.
|
||||
* You can check global type definition at `./vue-i18n.d.ts`.
|
||||
*/
|
||||
const i18n = createI18n({
|
||||
locale: 'en',
|
||||
fallbackLocale: 'fr',
|
||||
messages: {
|
||||
en: en,
|
||||
fr: fr,
|
||||
},
|
||||
// datetimeFormats: {
|
||||
// 'en': {
|
||||
// short: {
|
||||
// hour: 'numeric',
|
||||
// minute: 'numeric',
|
||||
// second: 'numeric',
|
||||
// timeZoneName: 'short',
|
||||
// timezone: 'America/Montreal'
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// numberFormats: {
|
||||
// 'en': {
|
||||
// currency: {
|
||||
// style: 'currency',
|
||||
// currencyDisplay: 'symbol',
|
||||
// currency: 'CAD'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
})
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(i18n)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
@@ -1,8 +1,30 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import Home from '@/views/Home.vue'
|
||||
import List from '@/views/List.vue'
|
||||
|
||||
import DefaultLayout from '@/layout/DefaultLayout.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [],
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Public',
|
||||
component: DefaultLayout,
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: Home,
|
||||
},
|
||||
{
|
||||
path: '/list',
|
||||
name: 'List',
|
||||
component: List,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
7
src/views/Home.vue
Normal file
7
src/views/Home.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>This is Home</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
8
src/views/List.vue
Normal file
8
src/views/List.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>This is List</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
45
src/vue-i18n.d.ts
vendored
Normal file
45
src/vue-i18n.d.ts
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* global type definitions
|
||||
* using the typescript interface, you can define the i18n resources that is type-safed!
|
||||
*/
|
||||
|
||||
/**
|
||||
* you need to import the some interfaces
|
||||
*/
|
||||
import { DefineLocaleMessage, DefineDateTimeFormat, DefineNumberFormat } from 'vue-i18n'
|
||||
|
||||
declare module 'vue-i18n' {
|
||||
// define the locale messages schema
|
||||
export interface DefineLocaleMessage {
|
||||
hello: string
|
||||
errors: string[]
|
||||
navbar: {
|
||||
natoGlossary: string
|
||||
home: string
|
||||
list: string
|
||||
language: string
|
||||
logout: string
|
||||
login: string
|
||||
}
|
||||
}
|
||||
|
||||
// // define the datetime format schema
|
||||
// export interface DefineDateTimeFormat {
|
||||
// short: {
|
||||
// hour: 'numeric'
|
||||
// minute: 'numeric'
|
||||
// second: 'numeric'
|
||||
// timeZoneName: 'short'
|
||||
// timezone: string
|
||||
// }
|
||||
// }
|
||||
|
||||
// // define the number format schema
|
||||
// export interface DefineNumberFormat {
|
||||
// currency: {
|
||||
// style: 'currency'
|
||||
// currencyDisplay: 'symbol'
|
||||
// currency: string
|
||||
// }
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user