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:
co-authored by
jepp9350
parent
19be7ef797
commit
63a10dd5c4
Generated
+98
-3
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user