i18n support added

Signed-off-by: Benny Samir Hierl <bennysamir@posteo.de>
This commit is contained in:
Benny Samir Hierl 2022-02-03 21:01:53 +01:00
parent 6759770002
commit 4200b87139
13 changed files with 275 additions and 11 deletions

128
package-lock.json generated
View file

@ -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",

View file

@ -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": {

View file

@ -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)

View file

@ -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>

View file

@ -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
View 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,
},
})

View file

@ -1 +1,2 @@
export { default as apiConfig } from './apiConfig'
export { default as i18n } from './i18n'

View 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"
}
}
}
}

View 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"
}
}
}
}

View file

@ -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')

View file

@ -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

View file

@ -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"

View file

@ -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>