mirror of
https://github.com/ThisIsBenny/wishlist-app.git
synced 2025-06-07 05:57:41 +00:00
i18n support added
Signed-off-by: Benny Samir Hierl <bennysamir@posteo.de>
This commit is contained in:
parent
6759770002
commit
4200b87139
13 changed files with 275 additions and 11 deletions
128
package-lock.json
generated
128
package-lock.json
generated
|
@ -15,6 +15,7 @@
|
|||
"fastify-helmet": "^7.0.1",
|
||||
"fastify-static": "^4.5.0",
|
||||
"vue": "^3.2.27",
|
||||
"vue-i18n": "^9.2.0-beta.30",
|
||||
"vue-router": "^4.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -284,6 +285,63 @@
|
|||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "9.2.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.0-beta.30.tgz",
|
||||
"integrity": "sha512-tnOuI8gs4S7vv4WjG8oFL7vbZ4PM7Is/Ld3lRHQlBO7UjpnCVcQ94AgP/4F0cUPFn9JSPMQRN0aOOahW1BXvSA==",
|
||||
"dependencies": {
|
||||
"@intlify/devtools-if": "9.2.0-beta.30",
|
||||
"@intlify/message-compiler": "9.2.0-beta.30",
|
||||
"@intlify/shared": "9.2.0-beta.30",
|
||||
"@intlify/vue-devtools": "9.2.0-beta.30"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/devtools-if": {
|
||||
"version": "9.2.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.0-beta.30.tgz",
|
||||
"integrity": "sha512-3OxGFi6ooya9DFqX/JsxFjrj9nGYcDoo4CRGYSDqnC+xv4bnsyB5ekmaYBiVZtagCdZdSUMxbTFphl1WbtgNLQ==",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "9.2.0-beta.30"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/message-compiler": {
|
||||
"version": "9.2.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.0-beta.30.tgz",
|
||||
"integrity": "sha512-2kj/0nLIFrgiO86f9VifcUUcV8LdzXt4YYPIujx/LkTEQOuSFUo/bNiMaG1hyfiU/8mfq6tsaWKjoOZjeao1eQ==",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "9.2.0-beta.30",
|
||||
"source-map": "0.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/shared": {
|
||||
"version": "9.2.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.0-beta.30.tgz",
|
||||
"integrity": "sha512-E1WHRTIlUEse3d/6t1pAagSXRxmeVeNIhx5kT80dfpYxw8lOnCWV9wLve2bq9Fkv+3TD2I5j+CdN7jvSl3LdsA==",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/vue-devtools": {
|
||||
"version": "9.2.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.0-beta.30.tgz",
|
||||
"integrity": "sha512-hcqDfwP/oXVmVCaJ0RA+uv1WSCcd42/Y13S0bySmWZv2KamLcxiD7wYxp/MaECG/D4KZcSLkq/wDHTG7lhYf5Q==",
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "9.2.0-beta.30",
|
||||
"@intlify/shared": "9.2.0-beta.30"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/@istanbuljs/schema": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
|
||||
|
@ -6620,6 +6678,23 @@
|
|||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-i18n": {
|
||||
"version": "9.2.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.0-beta.30.tgz",
|
||||
"integrity": "sha512-5DqrgG9ffgC7j3RRAfViC0WUcdz0C3Ix1qq1AyQItpF7UkSB6iSJGEjBG6KdspbRQq/8t1YzDx4JRXbL05l6ow==",
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "9.2.0-beta.30",
|
||||
"@intlify/shared": "9.2.0-beta.30",
|
||||
"@intlify/vue-devtools": "9.2.0-beta.30",
|
||||
"@vue/devtools-api": "^6.0.0-beta.13"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.0.12",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.12.tgz",
|
||||
|
@ -7117,6 +7192,48 @@
|
|||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||
"dev": true
|
||||
},
|
||||
"@intlify/core-base": {
|
||||
"version": "9.2.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.0-beta.30.tgz",
|
||||
"integrity": "sha512-tnOuI8gs4S7vv4WjG8oFL7vbZ4PM7Is/Ld3lRHQlBO7UjpnCVcQ94AgP/4F0cUPFn9JSPMQRN0aOOahW1BXvSA==",
|
||||
"requires": {
|
||||
"@intlify/devtools-if": "9.2.0-beta.30",
|
||||
"@intlify/message-compiler": "9.2.0-beta.30",
|
||||
"@intlify/shared": "9.2.0-beta.30",
|
||||
"@intlify/vue-devtools": "9.2.0-beta.30"
|
||||
}
|
||||
},
|
||||
"@intlify/devtools-if": {
|
||||
"version": "9.2.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.0-beta.30.tgz",
|
||||
"integrity": "sha512-3OxGFi6ooya9DFqX/JsxFjrj9nGYcDoo4CRGYSDqnC+xv4bnsyB5ekmaYBiVZtagCdZdSUMxbTFphl1WbtgNLQ==",
|
||||
"requires": {
|
||||
"@intlify/shared": "9.2.0-beta.30"
|
||||
}
|
||||
},
|
||||
"@intlify/message-compiler": {
|
||||
"version": "9.2.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.0-beta.30.tgz",
|
||||
"integrity": "sha512-2kj/0nLIFrgiO86f9VifcUUcV8LdzXt4YYPIujx/LkTEQOuSFUo/bNiMaG1hyfiU/8mfq6tsaWKjoOZjeao1eQ==",
|
||||
"requires": {
|
||||
"@intlify/shared": "9.2.0-beta.30",
|
||||
"source-map": "0.6.1"
|
||||
}
|
||||
},
|
||||
"@intlify/shared": {
|
||||
"version": "9.2.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.0-beta.30.tgz",
|
||||
"integrity": "sha512-E1WHRTIlUEse3d/6t1pAagSXRxmeVeNIhx5kT80dfpYxw8lOnCWV9wLve2bq9Fkv+3TD2I5j+CdN7jvSl3LdsA=="
|
||||
},
|
||||
"@intlify/vue-devtools": {
|
||||
"version": "9.2.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.0-beta.30.tgz",
|
||||
"integrity": "sha512-hcqDfwP/oXVmVCaJ0RA+uv1WSCcd42/Y13S0bySmWZv2KamLcxiD7wYxp/MaECG/D4KZcSLkq/wDHTG7lhYf5Q==",
|
||||
"requires": {
|
||||
"@intlify/core-base": "9.2.0-beta.30",
|
||||
"@intlify/shared": "9.2.0-beta.30"
|
||||
}
|
||||
},
|
||||
"@istanbuljs/schema": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
|
||||
|
@ -11904,6 +12021,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"vue-i18n": {
|
||||
"version": "9.2.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.0-beta.30.tgz",
|
||||
"integrity": "sha512-5DqrgG9ffgC7j3RRAfViC0WUcdz0C3Ix1qq1AyQItpF7UkSB6iSJGEjBG6KdspbRQq/8t1YzDx4JRXbL05l6ow==",
|
||||
"requires": {
|
||||
"@intlify/core-base": "9.2.0-beta.30",
|
||||
"@intlify/shared": "9.2.0-beta.30",
|
||||
"@intlify/vue-devtools": "9.2.0-beta.30",
|
||||
"@vue/devtools-api": "^6.0.0-beta.13"
|
||||
}
|
||||
},
|
||||
"vue-router": {
|
||||
"version": "4.0.12",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.12.tgz",
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"fastify-helmet": "^7.0.1",
|
||||
"fastify-static": "^4.5.0",
|
||||
"vue": "^3.2.27",
|
||||
"vue-i18n": "^9.2.0-beta.30",
|
||||
"vue-router": "^4.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
class="flex flex-row space-x-2 items-center content-center justify-center m-20 text-red-500"
|
||||
>
|
||||
<IconError class="w-4 h-4" />
|
||||
<span>Es ist ein Fehler aufgetreten...</span>
|
||||
<span>{{ t('errors.generic.text') }}</span>
|
||||
</div>
|
||||
<suspense v-else>
|
||||
<template #default>
|
||||
|
@ -20,7 +20,7 @@
|
|||
class="flex flex-row space-x-2 items-center content-center justify-center m-20"
|
||||
>
|
||||
<IconSpinner class="w-4 h-4" />
|
||||
<span> Lade... </span>
|
||||
<span> {{ t('common.loading.text') }} </span>
|
||||
</div>
|
||||
</template>
|
||||
</suspense>
|
||||
|
@ -34,6 +34,8 @@
|
|||
<script setup lang="ts">
|
||||
import { onErrorCaptured, ref } from 'vue'
|
||||
import { IconSpinner, IconError } from '@/components/icons'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
|
||||
const error = ref(null)
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import IconLink from './icons/IconLink.vue'
|
||||
import IconImagePlaceholder from './icons/IconImagePlaceholder.vue'
|
||||
import BaseButton from './BaseButton.vue'
|
||||
|
@ -10,6 +11,7 @@ defineProps<{
|
|||
description: string
|
||||
comment?: string
|
||||
}>()
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -41,14 +43,16 @@ defineProps<{
|
|||
class="text-sm mt-1 text-stone-500 flex flex-row items-center w-fit"
|
||||
>
|
||||
<IconLink class="mr-1 w-4 h-4" />
|
||||
<span>Produktseite öffnen</span>
|
||||
<span>{{
|
||||
t('components.wishlist-item.external-product-page-link.text')
|
||||
}}</span>
|
||||
</a>
|
||||
</div>
|
||||
<BaseButton
|
||||
class="mt-4 sm:mt-2 text-xs"
|
||||
:icon="IconCart"
|
||||
@click="$emit('bought')"
|
||||
>Gekauft</BaseButton
|
||||
>{{ t('components.wishlist-item.bought-button.text') }}</BaseButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,8 +7,15 @@ const callback = (confirmed: boolean) => {
|
|||
_resolve(confirmed)
|
||||
}
|
||||
|
||||
const show = (title: string, text = '') => {
|
||||
const show = (
|
||||
title: string,
|
||||
confirmText: string,
|
||||
cancelText: string,
|
||||
text = ''
|
||||
) => {
|
||||
data.title = title
|
||||
data.confirmText = confirmText
|
||||
data.cancelText = cancelText
|
||||
data.text = text
|
||||
data.isShown = true
|
||||
return new Promise((resolve) => {
|
||||
|
@ -21,9 +28,9 @@ const data = reactive({
|
|||
show,
|
||||
title: '',
|
||||
text: '',
|
||||
confirmText: 'Ja',
|
||||
confirmText: '',
|
||||
confirm: () => callback(true),
|
||||
cancelText: 'Nein',
|
||||
cancelText: '',
|
||||
cancel: () => callback(false),
|
||||
})
|
||||
|
||||
|
|
15
src/config/i18n.ts
Normal file
15
src/config/i18n.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { createI18n } from 'vue-i18n'
|
||||
import enUS from './locales/en-US.json'
|
||||
import deDE from './locales/de-DE.json'
|
||||
|
||||
export default createI18n({
|
||||
legacy: false,
|
||||
locale: navigator.language || 'en',
|
||||
fallbackLocale: 'en-US',
|
||||
messages: {
|
||||
'en-US': enUS,
|
||||
en: enUS,
|
||||
'de-DE': deDE,
|
||||
de: deDE,
|
||||
},
|
||||
})
|
|
@ -1 +1,2 @@
|
|||
export { default as apiConfig } from './apiConfig'
|
||||
export { default as i18n } from './i18n'
|
||||
|
|
46
src/config/locales/de-DE.json
Normal file
46
src/config/locales/de-DE.json
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"common": {
|
||||
"app-title": {
|
||||
"text": "Wunschlisten"
|
||||
},
|
||||
"loading": {
|
||||
"text": "Lade..."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"not-found": {
|
||||
"text": "Ups, es sieht so aus, als ob die Seite, die du suchst, nicht existiert."
|
||||
},
|
||||
"generic": {
|
||||
"text": "Es ist ein Fehler aufgetreten..."
|
||||
}
|
||||
},
|
||||
"pages": {
|
||||
"detail-view": {
|
||||
"confirmation-modal": {
|
||||
"title": {
|
||||
"text": "Möchten Sie den Gegenstand von der Liste nehmen?"
|
||||
},
|
||||
"body": {
|
||||
"text": "Durch das das runternehmen von der Liste ist dieser Gegenstand nicht mehr andere sichtbar."
|
||||
},
|
||||
"confirm-button": {
|
||||
"text": "Ja"
|
||||
},
|
||||
"cancel-button": {
|
||||
"text": "Nein"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"wishlist-item": {
|
||||
"external-product-page-link": {
|
||||
"text": "Produktseite öffnen"
|
||||
},
|
||||
"bought-button": {
|
||||
"text": "Gekauft"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
src/config/locales/en-US.json
Normal file
46
src/config/locales/en-US.json
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"common": {
|
||||
"app-title": {
|
||||
"text": "Wishlists"
|
||||
},
|
||||
"loading": {
|
||||
"text": "Loading..."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"not-found": {
|
||||
"text": "Oops, it looks like the page you're looking for doesn't exist."
|
||||
},
|
||||
"generic": {
|
||||
"text": "An error has occurred..."
|
||||
}
|
||||
},
|
||||
"pages": {
|
||||
"detail-view": {
|
||||
"confirmation-modal": {
|
||||
"title": {
|
||||
"text": "Do you want to remove the item from the list?"
|
||||
},
|
||||
"body": {
|
||||
"text": "By taking it down from the list, this item is no longer visible to others."
|
||||
},
|
||||
"confirm-button": {
|
||||
"text": "Yes"
|
||||
},
|
||||
"cancel-button": {
|
||||
"text": "No"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"wishlist-item": {
|
||||
"external-product-page-link": {
|
||||
"text": "Open product page"
|
||||
},
|
||||
"bought-button": {
|
||||
"text": "Bought"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,10 +4,12 @@ import './assets/tailwind.css'
|
|||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import Modal from '@/components/Modal.vue'
|
||||
import { i18n } from '@/config'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
app.use(i18n)
|
||||
app.component('modalOverlay', Modal)
|
||||
|
||||
app.mount('#app')
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { WishlistItem as WishlistItemType } from '@/types'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
@ -9,6 +10,8 @@ import WishlistItem from '@/components/WishlistItem.vue'
|
|||
const route = useRoute()
|
||||
const modal = useModal()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { list, fetch, updateItem } = useWishlistStore()
|
||||
await fetch(route.params.slug as string)
|
||||
|
||||
|
@ -20,8 +23,10 @@ const notBoughtItems = computed(() => {
|
|||
|
||||
const bought = async (item: WishlistItemType): Promise<void> => {
|
||||
const confirmed = await modal.show(
|
||||
'Möchten Sie den Gegenstand von der Liste nehmen?',
|
||||
'Durch das das runternehmen von der Liste ist dieser Gegenstand nicht mehr andere sichtbar.'
|
||||
t('pages.detail-view.confirmation-modal.title.text'),
|
||||
t('pages.detail-view.confirmation-modal.confirm-button.text'),
|
||||
t('pages.detail-view.confirmation-modal.cancel-button.text'),
|
||||
t('pages.detail-view.confirmation-modal.body.text')
|
||||
)
|
||||
if (confirmed) {
|
||||
item.bought = true
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Tile from '@/components/Tile.vue'
|
||||
import { useWishlistsStore } from '@/composables'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { lists, fetch } = useWishlistsStore()
|
||||
await fetch()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1 class="text-3xl text-center">Wunschlisten</h1>
|
||||
<h1 class="text-3xl text-center">{{ t('common.app-title.text') }}</h1>
|
||||
<div v-if="lists" class="flex flex-row flex-wrap justify-around p-10">
|
||||
<router-link
|
||||
v-for="(item, index) in lists"
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
<template>
|
||||
<h1>Oops, it looks like the page you're looking for doesn't exist.</h1>
|
||||
<h1>{{ t('errors.not-found.text') }}</h1>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
|
Loading…
Add table
Reference in a new issue