Add i18n setup with vue-i18n, locale files, and LanguageSwitcher component

Co-authored-by: jepp9350 <17977216+jepp9350@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-17 10:57:31 +00:00
co-authored by jepp9350
parent 19be7ef797
commit 63a10dd5c4
10 changed files with 377 additions and 12 deletions
+98 -3
View File
@@ -47,6 +47,7 @@
"vue": "^3.5.12",
"vue-cal": "^5.0.1-rc.20",
"vue-chartjs": "^5.3.2",
"vue-i18n": "^9.14.5",
"vue-popperjs": "^2.3.0",
"vue-qrcode-reader": "^5.7.3",
"vue-router": "^4.5.0",
@@ -903,6 +904,7 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz",
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -2897,6 +2899,7 @@
"version": "6.1.17",
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.17.tgz",
"integrity": "sha512-0W7lnIrv18ruJ5zeWBeNZXO8qCWlzxDdp9COFEsZnyNjiEhUVnrW/dPbjRKYpL0edGG0/Lhs0ghp1z/5ekt8ZA==",
"peer": true,
"dependencies": {
"preact": "~10.12.1"
}
@@ -3777,6 +3780,50 @@
}
}
},
"node_modules/@intlify/core-base": {
"version": "9.14.5",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz",
"integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==",
"license": "MIT",
"dependencies": {
"@intlify/message-compiler": "9.14.5",
"@intlify/shared": "9.14.5"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/message-compiler": {
"version": "9.14.5",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz",
"integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==",
"license": "MIT",
"dependencies": {
"@intlify/shared": "9.14.5",
"source-map-js": "^1.0.2"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/shared": {
"version": "9.14.5",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz",
"integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@isaacs/balanced-match": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
@@ -4013,6 +4060,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz",
"integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==",
"peer": true,
"dependencies": {
"@jimp/core": "^0.22.12"
}
@@ -4541,6 +4589,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz",
"integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12"
},
@@ -4552,6 +4601,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.12.tgz",
"integrity": "sha512-S0vJADTuh1Q9F+cXAwFPlrKWzDj2F9t/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uw==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12"
},
@@ -4574,6 +4624,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz",
"integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12",
"tinycolor2": "^1.6.0"
@@ -4614,6 +4665,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz",
"integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12"
},
@@ -4854,6 +4906,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz",
"integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12"
},
@@ -4865,6 +4918,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz",
"integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12"
},
@@ -4879,6 +4933,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz",
"integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==",
"peer": true,
"dependencies": {
"@jimp/utils": "^0.22.12"
},
@@ -8250,6 +8305,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"devOptional": true,
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -8273,6 +8329,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -14255,6 +14312,7 @@
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73",
@@ -14339,7 +14397,8 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/bulma/-/bulma-1.0.4.tgz",
"integrity": "sha512-Ffb6YGXDiZYX3cqvSbHWqQ8+LkX6tVoTcZuVB3lm93sbAVXlO0D6QlOTMnV6g18gILpAXqkG2z9hf9z4hCjz2g==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/bulma-block-list": {
"version": "1.1.0",
@@ -14554,7 +14613,8 @@
"resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
"integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
"devOptional": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/chardet": {
"version": "0.7.0",
@@ -14565,6 +14625,7 @@
"version": "4.4.7",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.7.tgz",
"integrity": "sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw==",
"peer": true,
"dependencies": {
"@kurkle/color": "^0.3.0"
},
@@ -15441,6 +15502,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -15606,6 +15668,7 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz",
"integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==",
"deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.",
"peer": true,
"dependencies": {
"@vue/compiler-sfc": "2.7.16",
"csstype": "^3.1.0"
@@ -16575,6 +16638,7 @@
"integrity": "sha512-/XxRRR90gNSuNf++w1jOQjhC5LE9Ixf/iAQctVb/miEI3dwzPZTuG27/omoh5REfSLDoPXofM84vAH/ULtz35g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vitest/snapshot": "^3.2.4",
"deep-eql": "^5.0.2",
@@ -18069,7 +18133,8 @@
"node_modules/idb-keyval": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz",
"integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg=="
"integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==",
"peer": true
},
"node_modules/ieee754": {
"version": "1.2.1",
@@ -18932,6 +18997,7 @@
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
@@ -19941,6 +20007,7 @@
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"peer": true,
"engines": {
"node": "*"
}
@@ -21246,6 +21313,7 @@
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
"license": "MIT",
"peer": true,
"dependencies": {
"dijkstrajs": "^1.0.1",
"pngjs": "^5.0.0",
@@ -21930,6 +21998,7 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -22142,6 +22211,7 @@
"version": "1.81.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.81.1.tgz",
"integrity": "sha512-VNLgf4FC5yFyKwAumAAwwNh8X4SevlVREq3Y8aDZIkm0lI/zO1feycMXQ4hn+eB6FVhRbleSQ1Yb/q8juSldTA==",
"peer": true,
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
@@ -22162,6 +22232,7 @@
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.81.0.tgz",
"integrity": "sha512-uZQ2Faxb1oWBHpeSSzjxnhClbMb3QadN0ql0ZFNuqWOLUxwaVhrMlMhPq6TDPbbfDUjihuwrMCuy695Bgna5RA==",
"devOptional": true,
"peer": true,
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
"buffer-builder": "^0.2.0",
@@ -24861,6 +24932,7 @@
"version": "5.4.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
"integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
"peer": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
@@ -25048,6 +25120,7 @@
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz",
"integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.13",
"@vue/compiler-sfc": "3.5.13",
@@ -25084,6 +25157,27 @@
"vue": "^3.0.0-0 || ^2.7.0"
}
},
"node_modules/vue-i18n": {
"version": "9.14.5",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz",
"integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==",
"deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html",
"license": "MIT",
"dependencies": {
"@intlify/core-base": "9.14.5",
"@intlify/shared": "9.14.5",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-popperjs": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/vue-popperjs/-/vue-popperjs-2.3.0.tgz",
@@ -26385,6 +26479,7 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
"dev": true,
"peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
+1
View File
@@ -54,6 +54,7 @@
"vue": "^3.5.12",
"vue-cal": "^5.0.1-rc.20",
"vue-chartjs": "^5.3.2",
"vue-i18n": "^9.14.5",
"vue-popperjs": "^2.3.0",
"vue-qrcode-reader": "^5.7.3",
"vue-router": "^4.5.0",
+155
View File
@@ -0,0 +1,155 @@
<script setup>
import { useI18n } from 'vue-i18n'
import { ref, watch } from 'vue'
const { locale } = useI18n()
const isOpen = ref(false)
// Language options
const languages = [
{ code: 'da', name: 'Dansk', flag: '🇩🇰' },
{ code: 'en', name: 'English', flag: '🇬🇧' }
]
// Get current language
const currentLanguage = ref(languages.find(lang => lang.code === locale.value) || languages[0])
// Toggle dropdown
const toggleDropdown = () => {
isOpen.value = !isOpen.value
}
// Close dropdown when clicking outside
const closeDropdown = () => {
isOpen.value = false
}
// Change language
const changeLanguage = (lang) => {
locale.value = lang.code
currentLanguage.value = lang
localStorage.setItem('locale', lang.code)
isOpen.value = false
}
// Watch locale changes
watch(locale, (newLocale) => {
currentLanguage.value = languages.find(lang => lang.code === newLocale) || languages[0]
})
</script>
<template>
<div class="language-switcher" @blur="closeDropdown" tabindex="0">
<button class="language-button" @click="toggleDropdown" type="button">
<span class="flag">{{ currentLanguage.flag }}</span>
<span class="name">{{ currentLanguage.name }}</span>
<span class="arrow" :class="{ 'open': isOpen }"></span>
</button>
<div v-if="isOpen" class="language-dropdown">
<button
v-for="lang in languages"
:key="lang.code"
@click="changeLanguage(lang)"
:class="{ 'active': lang.code === locale }"
class="language-option"
type="button"
>
<span class="flag">{{ lang.flag }}</span>
<span class="name">{{ lang.name }}</span>
</button>
</div>
</div>
</template>
<style scoped>
.language-switcher {
position: relative;
display: inline-block;
}
.language-button {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background-color: transparent;
border: 1.5px solid rgba(255, 255, 255, 0.3);
border-radius: 8px;
color: #fff;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
}
.language-button:hover {
background-color: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
.language-button .flag {
font-size: 18px;
line-height: 1;
}
.language-button .name {
line-height: 1;
}
.language-button .arrow {
font-size: 10px;
transition: transform 0.3s ease;
line-height: 1;
}
.language-button .arrow.open {
transform: rotate(180deg);
}
.language-dropdown {
position: absolute;
top: calc(100% + 8px);
right: 0;
background-color: #fff;
border: 1.5px solid #e0e0e0;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
overflow: hidden;
z-index: 1000;
min-width: 150px;
}
.language-option {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 12px 16px;
background-color: transparent;
border: none;
color: #13324C;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background-color 0.2s ease;
text-align: left;
}
.language-option:hover {
background-color: #f5f5f5;
}
.language-option.active {
background-color: #e8f4fd;
color: #1976d2;
}
.language-option .flag {
font-size: 18px;
line-height: 1;
}
.language-option .name {
line-height: 1;
}
</style>
+2 -2
View File
@@ -88,10 +88,10 @@ SessionUser.auth.reCAPTCHA.preCheck.get().then((response) => {
</p>
</div>
<div class="form-field">
<label><input type="checkbox" id="remember" name="remember">Husk mig</label>
<label><input type="checkbox" id="remember" name="remember">{{ $t('auth.remember_me') }}</label>
</div>
<router-link :to="{name: 'password-reset'}" class="">Glemt adgangskode</router-link>
<router-link :to="{name: 'password-reset'}" class="">{{ $t('auth.forgot_password') }}</router-link>
<LoadButtonWhileAwait :loadFunction="login" class="submit" id="login-button">{{ SessionUser.functions.ucFirst(SessionUser.objects.global.language.login) }}</LoadButtonWhileAwait>
<router-link :to="{name: 'loginqr'}" class="submit">{{ SessionUser.functions.ucFirst(SessionUser.objects.global.language.login_with_qr_code) }}</router-link>
</form>
+7 -5
View File
@@ -1,5 +1,6 @@
<script setup>
import logo from "@/assets/img/truckwash.png";
import LanguageSwitcher from "@/components/LanguageSwitcher.vue";
import {onBeforeUnmount, onMounted} from "vue";
onMounted(() => {
const headerWrapper = document.querySelector('.header-wrapper');
@@ -91,7 +92,8 @@ onMounted(() => {
</ul>
<nav>
<ul>
<li><router-link :to="{ name: 'adminlogin' }">Operatør login</router-link></li>
<li><router-link :to="{ name: 'adminlogin' }">{{ $t('header.operator_login') }}</router-link></li>
<li><LanguageSwitcher /></li>
</ul>
</nav>
</div>
@@ -101,10 +103,10 @@ onMounted(() => {
</router-link>
<nav class="nav-menu">
<ul>
<li><router-link :to="{ name: 'customer-creation' }">Kundeoprettelse</router-link></li>
<li><router-link :to="{ name: 'login' }">Kundelogin</router-link></li>
<li><router-link :to="{ name: 'about-us' }">Om Os</router-link></li>
<li class="on-mobile"><router-link :to="{ name: 'adminlogin' }">Operatør login</router-link></li>
<li><router-link :to="{ name: 'customer-creation' }">{{ $t('header.customer_creation') }}</router-link></li>
<li><router-link :to="{ name: 'login' }">{{ $t('header.customer_login') }}</router-link></li>
<li><router-link :to="{ name: 'about-us' }">{{ $t('header.about_us') }}</router-link></li>
<li class="on-mobile"><router-link :to="{ name: 'adminlogin' }">{{ $t('header.operator_login') }}</router-link></li>
</ul>
</nav>
<div class="menu-toggle__icon"></div>
+20
View File
@@ -0,0 +1,20 @@
import { createI18n } from 'vue-i18n'
import da from './locales/da.json'
import en from './locales/en.json'
// Get saved locale from localStorage or use 'da' as default
const savedLocale = localStorage.getItem('locale') || 'da'
const i18n = createI18n({
legacy: false, // Use Composition API mode
locale: savedLocale, // Set locale from localStorage or default
fallbackLocale: 'da', // Fallback to Danish if translation is missing
messages: {
da,
en
},
// Global injections for $t, $te, etc.
globalInjection: true
})
export default i18n
+45
View File
@@ -0,0 +1,45 @@
{
"header": {
"operator_login": "Operatør login",
"customer_creation": "Kundeoprettelse",
"customer_login": "Kundelogin",
"about_us": "Om Os"
},
"auth": {
"customer_login_title": "Kundelogin",
"customer_number": "Kundenummer",
"password": "Adgangskode",
"remember_me": "Husk mig",
"forgot_password": "Glemt adgangskode",
"login": "Log ind",
"login_with_qr_code": "Log ind med QR-kode",
"terms_acceptance": "Ved at trykke \"Log ind\" accepterer du truckwash.io privatlivspolitik og betingelser"
},
"profile": {
"title": "Profil",
"email": {
"label": "E-mailadresse",
"enter_password": "Indtast din adgangskode",
"password_prompt": "Indtast venligst din adgangskode",
"new_email_prompt": "Hvilken e-mailadresse vil du bruge?",
"new_email_text": "Indtast venligst din nye e-mailadresse.",
"enter_email": "Indtast venligst din e-mailadresse",
"invalid_email": "Indtast venligst en gyldig e-mailadresse",
"saved": "E-mailadresse gemt",
"saved_text": "Din e-mailadresse er blevet gemt."
},
"error": {
"title": "Fejl",
"validation_error": "Der opstod en fejl under validering af adgangskoden.",
"generic_error": "Der opstod en fejl"
}
},
"common": {
"save": "Gem",
"cancel": "Annuller",
"close": "Luk",
"loading": "Indlæser...",
"error": "Fejl",
"success": "Succes"
}
}
+45
View File
@@ -0,0 +1,45 @@
{
"header": {
"operator_login": "Operator Login",
"customer_creation": "Customer Creation",
"customer_login": "Customer Login",
"about_us": "About Us"
},
"auth": {
"customer_login_title": "Customer Login",
"customer_number": "Customer Number",
"password": "Password",
"remember_me": "Remember Me",
"forgot_password": "Forgot Password",
"login": "Login",
"login_with_qr_code": "Login with QR Code",
"terms_acceptance": "By clicking \"Login\" you accept truckwash.io privacy policy and terms"
},
"profile": {
"title": "Profile",
"email": {
"label": "Email Address",
"enter_password": "Enter your password",
"password_prompt": "Please enter your password",
"new_email_prompt": "Which email address would you like to use?",
"new_email_text": "Please enter your new email address.",
"enter_email": "Please enter your email address",
"invalid_email": "Please enter a valid email address",
"saved": "Email Address Saved",
"saved_text": "Your email address has been saved."
},
"error": {
"title": "Error",
"validation_error": "An error occurred while validating the password.",
"generic_error": "An error occurred"
}
},
"common": {
"save": "Save",
"cancel": "Cancel",
"close": "Close",
"loading": "Loading...",
"error": "Error",
"success": "Success"
}
}
+2
View File
@@ -12,6 +12,7 @@ import Router from "@/router";
import App from "@/App.vue";
import Buefy from 'buefy'
import 'buefy/dist/css/buefy.css'
import i18n from '@/i18n'
import { API_URL, IS_DEV, POS_STEP_1_VERSION } from './config';
// export { API_URL, IS_DEV, POS_STEP_1_VERSION };
@@ -33,6 +34,7 @@ createApp(App)
.use(store)
.use(ToastPlugin)
.use(Buefy)
.use(i18n)
.provide('Colors', Colors)
.provide('IS_DEV', IS_DEV)
.provide('API_URL', API_URL)
+2 -2
View File
@@ -12,14 +12,14 @@ import logo from "@/assets/img/truckwash-banner.png";
<section class="login-section">
<div class="wrapper">
<div class="sidebar">
<h1>Kundelogin</h1>
<h1>{{ $t('auth.customer_login_title') }}</h1>
</div>
<div class="main">
<router-link :to="{ name: 'landing' }" class="logo">
<img :src="logo" alt="Truck Wash">
</router-link>
<LoginForm />
<p>Ved at trykke Log ind accepterer du truckwash.io privatlivspolitik og betingelser</p>
<p>{{ $t('auth.terms_acceptance') }}</p>
</div>
</div>
</section>