Translation

This commit is contained in:
2024-09-09 20:55:09 +02:00
parent 7ebc3c1c77
commit f10c0ef4e9
15 changed files with 226 additions and 60 deletions

View File

@@ -22,6 +22,7 @@
"sqlite3": "^5.1.7", "sqlite3": "^5.1.7",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"vue": "^3.4.29", "vue": "^3.4.29",
"vue-i18n": "^9.14.0",
"vue-router": "^4.4.3", "vue-router": "^4.4.3",
"vuetify": "^3.7.1", "vuetify": "^3.7.1",
"wait-on": "^8.0.0" "wait-on": "^8.0.0"
@@ -519,6 +520,50 @@
"@hapi/hoek": "^9.0.0" "@hapi/hoek": "^9.0.0"
} }
}, },
"node_modules/@intlify/core-base": {
"version": "9.14.0",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.0.tgz",
"integrity": "sha512-zJn0imh9HIsZZUtt9v8T16PeVstPv6bP2YzlrYJwoF8F30gs4brZBwW2KK6EI5WYKFi3NeqX6+UU4gniz5TkGg==",
"license": "MIT",
"dependencies": {
"@intlify/message-compiler": "9.14.0",
"@intlify/shared": "9.14.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/message-compiler": {
"version": "9.14.0",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.0.tgz",
"integrity": "sha512-sXNsoMI0YsipSXW8SR75drmVK56tnJHoYbPXUv2Cf9lz6FzvwsosFm6JtC1oQZI/kU+n7qx0qRrEWkeYFTgETA==",
"license": "MIT",
"dependencies": {
"@intlify/shared": "9.14.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/shared": {
"version": "9.14.0",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.0.tgz",
"integrity": "sha512-r+N8KRQL7LgN1TMTs1A2svfuAU0J94Wu9wWdJVJqYsoMMLIeJxrPjazihfHpmJqfgZq0ah3Y9Q4pgWV2O90Fyg==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@jridgewell/resolve-uri": { "node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -4650,6 +4695,26 @@
} }
} }
}, },
"node_modules/vue-i18n": {
"version": "9.14.0",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.0.tgz",
"integrity": "sha512-LxmpRuCt2rI8gqU+kxeflRZMQn4D5+4M3oP3PWZdowW/ePJraHqhF7p4CuaME52mUxdw3Mmy2yAUKgfZYgCRjA==",
"license": "MIT",
"dependencies": {
"@intlify/core-base": "9.14.0",
"@intlify/shared": "9.14.0",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-router": { "node_modules/vue-router": {
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.3.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.3.tgz",

View File

@@ -31,6 +31,7 @@
"sqlite3": "^5.1.7", "sqlite3": "^5.1.7",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"vue": "^3.4.29", "vue": "^3.4.29",
"vue-i18n": "^9.14.0",
"vue-router": "^4.4.3", "vue-router": "^4.4.3",
"vuetify": "^3.7.1", "vuetify": "^3.7.1",
"wait-on": "^8.0.0" "wait-on": "^8.0.0"

View File

@@ -1,27 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import axios from 'axios';
import { useTheme } from 'vuetify/lib/framework.mjs'; import { useTheme } from 'vuetify/lib/framework.mjs';
import { useUserStore } from './data/stores/userStore'; import { useUserStore } from './data/stores/userStore';
import vuetify from './plugins/vuetify'; import vuetify from './plugins/vuetify';
import { useBasketStore } from './data/stores/basketStore'; import { useBasketStore } from './data/stores/basketStore';
import { i18n } from './plugins/i18n';
const userStore = useUserStore() const userStore = useUserStore()
const basketStore = useBasketStore() const basketStore = useBasketStore()
const categories = ref([])
const theme = useTheme() const theme = useTheme()
const navRail = ref(vuetify.display.mobile) const navRail = ref(vuetify.display.mobile)
theme.global.name.value = userStore.theme theme.global.name.value = userStore.theme
i18n.global.locale = userStore.language
function requestAllCategories() {
axios.get('http://127.0.0.1:3000/categories')
.then(function (response) {
categories.value = response.data
})
}
requestAllCategories()
</script> </script>
<template> <template>
@@ -35,11 +26,11 @@ requestAllCategories()
<v-navigation-drawer :rail="navRail" permanent> <v-navigation-drawer :rail="navRail" permanent>
<v-list> <v-list>
<v-list-subheader> <v-list-subheader>
<div v-if="!navRail">Einkaufen</div> <div v-if="!navRail">{{ $t('menu.shopping') }}</div>
<div v-else></div> <div v-else></div>
</v-list-subheader> </v-list-subheader>
<v-list-item title="Produkte" prepend-icon="mdi-store" to="/products" link /> <v-list-item :title="$t('menu.products')" prepend-icon="mdi-store" to="/products" link />
<v-list-item to="/basket" link title="Warenkorb"> <v-list-item :title="$t('menu.basket')" to="/basket" link >
<template v-slot:prepend> <template v-slot:prepend>
<v-badge color="primary" :content="basketStore.itemsInBasket.length"> <v-badge color="primary" :content="basketStore.itemsInBasket.length">
<v-icon icon="mdi-cart" /> <v-icon icon="mdi-cart" />
@@ -50,21 +41,21 @@ requestAllCategories()
<v-divider /> <v-divider />
<v-list-subheader> <v-list-subheader>
<div v-if="!navRail">Account</div> <div v-if="!navRail">{{ $t('menu.account') }}</div>
<div v-else></div> <div v-else></div>
</v-list-subheader> </v-list-subheader>
<v-list-item title="Login" prepend-icon="mdi-login" to="/login" link /> <v-list-item :title="$t('menu.login')" prepend-icon="mdi-login" to="/login" link />
<v-list-item title="Account" prepend-icon="mdi-account" to="/account" link /> <v-list-item :title="$t('menu.account')" prepend-icon="mdi-account" to="/account" link />
<v-list-item title="Bestellungen" prepend-icon="mdi-cart-check" to="/orders" link /> <v-list-item :title="$t('menu.orders')" prepend-icon="mdi-cart-check" to="/orders" link />
<v-divider /> <v-divider />
<v-list-subheader> <v-list-subheader>
<div v-if="!navRail">System & Hilfe</div> <div v-if="!navRail">{{ $t('menu.systemAndHelp') }}</div>
<div v-else></div> <div v-else></div>
</v-list-subheader> </v-list-subheader>
<v-list-item title="Hilfestellung" prepend-icon="mdi-chat-question" to="/help" link /> <v-list-item :title="$t('menu.helpInstructions')" prepend-icon="mdi-chat-question" to="/help" link />
<v-list-item title="Einstellungen" prepend-icon="mdi-cog" to="/preferences" link /> <v-list-item :title="$t('menu.preferences')" prepend-icon="mdi-cog" to="/preferences" link />
</v-list> </v-list>
</v-navigation-drawer> </v-navigation-drawer>

View File

@@ -1,4 +1,4 @@
export enum LanguageEnum { export enum LanguageEnum {
GERMAN = "Deutsch", GERMAN = "de",
ENGLISH = "English" ENGLISH = "en"
} }

View File

@@ -0,0 +1,40 @@
{
"menu":
{
"shopping": "Shopping",
"products": "Products",
"basket": "Basket",
"login": "Login",
"account": "Account",
"orders": "Orders",
"systemAndHelp": "System & Help",
"helpInstructions": "Help instructions",
"preferences": "Preferences"
},
"preferences": {
"pageSetup": "Page setup",
"selectedTheme": "Selected theme",
"language": "Language",
"systemSetup": "System setup",
"resetDatabase": "Reset database",
"resetPreferences": "Reset preferences"
},
"products": "Products",
"product": "Product",
"offers": "Offers",
"categories": "Categories",
"sortBy": "Sort by",
"quantity": "Quantity",
"addToBasket": "Add to basket",
"emptyBasketTitle": "The basket is empty...",
"emptyBasketText": "Go to our products and add some of them to the basket",
"totalPrice": "Total price",
"orderNow": "Order now",
"category": "Category",
"brand": "Brand",
"productPrice": "Unit price",
"username": "Username",
"password": "Password",
"login": "Login",
"noAccountRegister": "No Account? Register now!"
}

View File

@@ -0,0 +1,40 @@
{
"menu":
{
"shopping": "Einkaufen",
"products": "Produkte",
"basket": "Warenkorb",
"login": "Login",
"account": "Account",
"orders": "Bestellungen",
"systemAndHelp": "System & Hilfe",
"helpInstructions": "Hilfestellung",
"preferences": "Einstellungen"
},
"preferences": {
"pageSetup": "Seiteneinstellungen",
"selectedTheme": "Ausgewähltes Theme",
"language": "Sprache",
"systemSetup": "Systemeinstellungen",
"resetDatabase": "Datenbank zurücksetzen",
"resetPreferences": "Einstellungen zurücksetzen"
},
"products": "Produkte",
"product": "Produkt",
"offers": "Angebote",
"categories": "Kategorien",
"sortBy": "Sortieren nach",
"quantity": "Anzahl",
"addToBasket": "Zum Warenkorb hinzufügen",
"emptyBasketTitle": "Keine Artikel im Warenkorb",
"emptyBasketText": "Gehe zu unseren Produkten und lege Artikel in den Warenkorb",
"totalPrice": "Gesamtpreis",
"orderNow": "Jetzt bestellen",
"category": "Kategorie",
"brand": "Marke",
"productPrice": "Einzelpreis",
"username": "Username",
"password": "Passwort",
"login": "Login",
"noAccountRegister": "Noch keinen Account? Jetzt anmelden!"
}

View File

@@ -4,9 +4,11 @@ import App from './App.vue'
import vuetify from './plugins/vuetify' import vuetify from './plugins/vuetify'
import router from './plugins/router' import router from './plugins/router'
import pinia from './plugins/pinia' import pinia from './plugins/pinia'
import { i18n } from './plugins/i18n'
createApp(App) createApp(App)
.use(vuetify) .use(vuetify)
.use(router) .use(router)
.use(pinia) .use(pinia)
.use(i18n)
.mount('#app') .mount('#app')

View File

@@ -9,21 +9,26 @@ const basketStore = useBasketStore()
<v-container max-width="1000"> <v-container max-width="1000">
<v-row> <v-row>
<v-col> <v-col>
<v-card title="Warenkorb" > <v-card :title="$t('menu.basket')" prepend-icon="mdi-cart">
<v-card-subtitle v-if="basketStore.itemsInBasket.length > 0"> <v-card-subtitle v-if="basketStore.itemsInBasket.length > 0">
{{ basketStore.itemsInBasket.length }} Artikel <div v-if="basketStore.itemsInBasket.length == 1">
{{ basketStore.itemsInBasket.length }} {{ $t('product') }}
</div>
<div v-else>
{{ basketStore.itemsInBasket.length }} {{ $t('products') }}
</div>
</v-card-subtitle> </v-card-subtitle>
<products-table v-if="basketStore.itemsInBasket.length > 0" /> <products-table v-if="basketStore.itemsInBasket.length > 0" />
<v-empty-state v-else <v-empty-state v-else
icon="mdi-basket-off" icon="mdi-basket-off"
title="Keine Artikel im Warenkorb" :title="$t('emptyBasketTitle')"
text="Gehe zu unseren Produkten und lege Artikel in den Warenkorb" :text="$t('emptyBasketText')"
/> />
<v-card-text class="text-right" v-if="basketStore.itemsInBasket.length > 0"> <v-card-text class="text-right" v-if="basketStore.itemsInBasket.length > 0">
Total: {{ basketStore.getTotalPrice }} {{ $t('totalPrice') }}: {{ basketStore.getTotalPrice }}
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
@@ -31,7 +36,7 @@ const basketStore = useBasketStore()
prepend-icon="mdi-basket-check" prepend-icon="mdi-basket-check"
:disabled="basketStore.itemsInBasket.length == 0" :disabled="basketStore.itemsInBasket.length == 0"
> >
Bestellen {{ $t('orderNow') }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>

View File

@@ -15,12 +15,12 @@ function removeFromBasket(basketItem: BasketItemModel) {
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th>Category</th> <th>{{ $t('category') }}</th>
<th>Brand</th> <th>{{ $t('brand') }}</th>
<th>Products</th> <th>{{ $t('products') }}</th>
<th class="text-center">Quantity</th> <th class="text-center">{{ $t('quantity') }}</th>
<th class="text-right">Product price</th> <th class="text-right">{{ $t('productPrice') }}</th>
<th class="text-right">Total price</th> <th class="text-right">{{ $t('totalPrice') }}</th>
</tr> </tr>
</thead> </thead>

View File

@@ -2,24 +2,24 @@
</script> </script>
<template> <template>
<v-card title="Login" prepend-icon="mdi-login" elevation="8"> <v-card :title="$t('menu.login')" prepend-icon="mdi-login" elevation="8">
<v-container> <v-container>
<v-row> <v-row>
<v-col> <v-col>
<v-text-field label="Username" prepend-icon="mdi-account" clearable /> <v-text-field :label="$t('username')" prepend-icon="mdi-account" clearable />
</v-col> </v-col>
</v-row> </v-row>
<v-row> <v-row>
<v-col> <v-col>
<v-text-field label="Passwort" prepend-icon="mdi-key" type="password" clearable /> <v-text-field :label="$t('password')" prepend-icon="mdi-key" type="password" clearable />
</v-col> </v-col>
</v-row> </v-row>
<v-row> <v-row>
<v-col> <v-col>
<!-- todo --> <!-- todo -->
<v-btn prepend-icon="mdi-send" color="primary" block>Login</v-btn> <v-btn prepend-icon="mdi-send" color="primary" block>{{ $t('login') }}</v-btn>
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>
@@ -31,8 +31,7 @@
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
> >
<!-- todo --> {{ $t('noAccountRegister') }} <v-icon icon="mdi-chevron-right"/>
Nicht keinen Account? Jetzt anmelden! <v-icon icon="mdi-chevron-right"></v-icon>
</a> </a>
</v-card-text> </v-card-text>
</v-card> </v-card>

View File

@@ -2,34 +2,40 @@
import { ThemeEnum } from '@/data/enums/themeEnums'; import { ThemeEnum } from '@/data/enums/themeEnums';
import { useTheme } from 'vuetify/lib/framework.mjs'; import { useTheme } from 'vuetify/lib/framework.mjs';
import { useUserStore } from '@/data/stores/userStore'; import { useUserStore } from '@/data/stores/userStore';
import { LanguageEnum } from '@/data/enums/languageEnum'; import { i18n } from '@/plugins/i18n';
const userStore = useUserStore() const userStore = useUserStore()
const theme = useTheme() const theme = useTheme()
const themeEnums = Object.values(ThemeEnum) const themeEnums = Object.values(ThemeEnum)
const languages = Object.values(LanguageEnum)
function changeTheme() { function changeTheme() {
theme.global.name.value = userStore.theme theme.global.name.value = userStore.theme
} }
function changeLanguage() { function changeLanguage() {
// todo i18n.global.locale = userStore.language
} }
</script> </script>
<template> <template>
<v-card title="Page Setup" prepend-icon="mdi-view-dashboard" elevation="8"> <v-card :title="$t('preferences.pageSetup')" prepend-icon="mdi-view-dashboard" elevation="8">
<v-container> <v-container>
<v-row> <v-row>
<v-col> <v-col>
<v-select v-model="userStore.theme" :items="themeEnums" label="Selected theme" @update:model-value="changeTheme" /> <v-select
v-model="userStore.theme"
:items="themeEnums"
:label="$t('preferences.selectedTheme')"
@update:model-value="changeTheme"
/>
</v-col> </v-col>
</v-row> </v-row>
<v-row> <v-row>
<v-col> <v-col>
<v-select v-model="userStore.language" :items="languages" label="Sprache" @update:model-value="changeLanguage" /> <v-select v-model="userStore.language" :items="$i18n.availableLocales" :label="$t('preferences.language')"
@update:model-value="changeLanguage"
/>
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>

View File

@@ -20,14 +20,18 @@ function resetSettings() {
</script> </script>
<template> <template>
<v-card title="System Setup" prepend-icon="mdi-engine" elevation="8"> <v-card :title="$t('preferences.systemSetup')" prepend-icon="mdi-engine" elevation="8">
<v-container> <v-container>
<v-row> <v-row>
<v-col class="d-flex justify-center align-center"> <v-col class="d-flex justify-center align-center">
<v-btn @click="resetDb" color="primary" prepend-icon="mdi-database-refresh">Datenbank zurücksetzen</v-btn> <v-btn @click="resetDb" color="primary" prepend-icon="mdi-database-refresh">
{{ $t('preferences.resetDatabase') }}
</v-btn>
</v-col> </v-col>
<v-col class="d-flex justify-center align-center"> <v-col class="d-flex justify-center align-center">
<v-btn @click="resetDb" color="primary" prepend-icon="mdi-cog-counterclockwise">Einstellungen zurücksetzen</v-btn> <v-btn @click="resetDb" color="primary" prepend-icon="mdi-cog-counterclockwise">
{{ $t('preferences.resetPreferences') }}
</v-btn>
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>

View File

@@ -28,21 +28,21 @@ const onlyDiscounts = defineModel("onlyDiscounts", { required: true, type: Boole
<v-card> <v-card>
<v-card-title> <v-card-title>
<div v-if="numberOfItems == 1"> <div v-if="numberOfItems == 1">
{{ numberOfItems }} Produkt {{ numberOfItems }} {{ $t('product') }}
</div> </div>
<div v-else> <div v-else>
{{ numberOfItems }} Produkte {{ numberOfItems }} {{ $t('products') }}
</div> </div>
</v-card-title> </v-card-title>
<v-container class="pb-0"> <v-container class="pb-0">
<v-row> <v-row>
<v-spacer /> <v-spacer />
<v-col class="d-flex justify-center align-center"> <v-col class="d-flex justify-center align-center">
<v-checkbox label="Angebote" v-model="onlyDiscounts" /> <v-checkbox :label="$t('offers')" v-model="onlyDiscounts" />
</v-col> </v-col>
<v-col class="d-flex justify-left align-center"> <v-col class="d-flex justify-left align-center">
<v-select :items="categories" label="Categories" v-model="selectedCategory" > <v-select :items="categories" :label="$t('categories')" v-model="selectedCategory" >
<template v-slot:item="{ props, item }"> <template v-slot:item="{ props, item }">
<v-list-item v-bind="props" :prepend-icon="item.raw.icon" :title="item.raw.name" /> <v-list-item v-bind="props" :prepend-icon="item.raw.icon" :title="item.raw.name" />
</template> </template>
@@ -54,7 +54,7 @@ const onlyDiscounts = defineModel("onlyDiscounts", { required: true, type: Boole
</v-col> </v-col>
<v-col class="d-flex justify-left align-center"> <v-col class="d-flex justify-left align-center">
<v-select label="Sortieren nach" :items="sortBy" v-model="sortedBy" > <v-select :label="$t('sortBy')" :items="sortBy" v-model="sortedBy" >
<template v-slot:item="{ props, item }"> <template v-slot:item="{ props, item }">
<v-list-item v-bind="props" :prepend-icon="item.raw.icon" :title="item.raw.name" /> <v-list-item v-bind="props" :prepend-icon="item.raw.icon" :title="item.raw.name" />
</template> </template>

View File

@@ -44,7 +44,7 @@ function addProductToBasket() {
<v-number-input <v-number-input
:reverse="false" :reverse="false"
controlVariant="default" controlVariant="default"
label="Anzahl" :label="$t('quantity')"
:hideInput="false" :hideInput="false"
:inset="false" :inset="false"
v-model="nrOfArticles" v-model="nrOfArticles"
@@ -67,7 +67,7 @@ function addProductToBasket() {
prepend-icon="mdi-cart-plus" prepend-icon="mdi-cart-plus"
@click="addProductToBasket" @click="addProductToBasket"
> >
Zum Einkaufswagen hinzufügen {{ $t('addToBasket') }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>

View File

@@ -0,0 +1,13 @@
import { createI18n } from "vue-i18n";
import german from './../locales/german.json'
import english from './../locales/english.json'
type MessageSchema = typeof german
export const i18n = createI18n<[MessageSchema], 'de' | 'en'>({
locale: 'de',
messages: {
'de': german,
'en': english
}
})