✨ 🌐 Add Localization feature
This commit is contained in:
140
package-lock.json
generated
140
package-lock.json
generated
@ -11,6 +11,7 @@
|
|||||||
"dayjs": "^1.11.3",
|
"dayjs": "^1.11.3",
|
||||||
"vite-plugin-git-revision": "^0.1.9",
|
"vite-plugin-git-revision": "^0.1.9",
|
||||||
"vue": "^3.2.37",
|
"vue": "^3.2.37",
|
||||||
|
"vue-i18n": "^9.2.2",
|
||||||
"vue-router": "^4.0.15"
|
"vue-router": "^4.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -1744,6 +1745,63 @@
|
|||||||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@intlify/core-base": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/devtools-if": "9.2.2",
|
||||||
|
"@intlify/message-compiler": "9.2.2",
|
||||||
|
"@intlify/shared": "9.2.2",
|
||||||
|
"@intlify/vue-devtools": "9.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@intlify/devtools-if": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/shared": "9.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@intlify/message-compiler": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/shared": "9.2.2",
|
||||||
|
"source-map": "0.6.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@intlify/shared": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@intlify/vue-devtools": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/core-base": "9.2.2",
|
||||||
|
"@intlify/shared": "9.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
|
||||||
@ -2034,9 +2092,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/devtools-api": {
|
"node_modules/@vue/devtools-api": {
|
||||||
"version": "6.1.4",
|
"version": "6.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.2.1.tgz",
|
||||||
"integrity": "sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ=="
|
"integrity": "sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@vue/eslint-config-prettier": {
|
"node_modules/@vue/eslint-config-prettier": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
@ -5111,6 +5169,23 @@
|
|||||||
"eslint": ">=6.0.0"
|
"eslint": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-i18n": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/core-base": "9.2.2",
|
||||||
|
"@intlify/shared": "9.2.2",
|
||||||
|
"@intlify/vue-devtools": "9.2.2",
|
||||||
|
"@vue/devtools-api": "^6.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-router": {
|
"node_modules/vue-router": {
|
||||||
"version": "4.0.15",
|
"version": "4.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.15.tgz",
|
||||||
@ -6670,6 +6745,48 @@
|
|||||||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@intlify/core-base": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/devtools-if": "9.2.2",
|
||||||
|
"@intlify/message-compiler": "9.2.2",
|
||||||
|
"@intlify/shared": "9.2.2",
|
||||||
|
"@intlify/vue-devtools": "9.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@intlify/devtools-if": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/shared": "9.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@intlify/message-compiler": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/shared": "9.2.2",
|
||||||
|
"source-map": "0.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@intlify/shared": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q=="
|
||||||
|
},
|
||||||
|
"@intlify/vue-devtools": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/core-base": "9.2.2",
|
||||||
|
"@intlify/shared": "9.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@jridgewell/gen-mapping": {
|
"@jridgewell/gen-mapping": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
|
||||||
@ -6909,9 +7026,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/devtools-api": {
|
"@vue/devtools-api": {
|
||||||
"version": "6.1.4",
|
"version": "6.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.2.1.tgz",
|
||||||
"integrity": "sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ=="
|
"integrity": "sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ=="
|
||||||
},
|
},
|
||||||
"@vue/eslint-config-prettier": {
|
"@vue/eslint-config-prettier": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
@ -9041,6 +9158,17 @@
|
|||||||
"semver": "^7.3.5"
|
"semver": "^7.3.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vue-i18n": {
|
||||||
|
"version": "9.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz",
|
||||||
|
"integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/core-base": "9.2.2",
|
||||||
|
"@intlify/shared": "9.2.2",
|
||||||
|
"@intlify/vue-devtools": "9.2.2",
|
||||||
|
"@vue/devtools-api": "^6.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"vue-router": {
|
"vue-router": {
|
||||||
"version": "4.0.15",
|
"version": "4.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.15.tgz",
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"dayjs": "^1.11.3",
|
"dayjs": "^1.11.3",
|
||||||
"vite-plugin-git-revision": "^0.1.9",
|
"vite-plugin-git-revision": "^0.1.9",
|
||||||
"vue": "^3.2.37",
|
"vue": "^3.2.37",
|
||||||
|
"vue-i18n": "^9.2.2",
|
||||||
"vue-router": "^4.0.15"
|
"vue-router": "^4.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -12,16 +12,16 @@ import { loading } from "./store";
|
|||||||
<div class="center">
|
<div class="center">
|
||||||
<main>
|
<main>
|
||||||
<DateSelector
|
<DateSelector
|
||||||
v-show="$route.name != 'Settings' && $route.name != 'Login'"
|
v-show="$route.name != 'title.settings' && $route.name != 'title.login'"
|
||||||
/>
|
/>
|
||||||
<LoadingElement
|
<LoadingElement
|
||||||
:active="loading"
|
:active="loading"
|
||||||
v-show="$route.name != 'Settings' && $route.name != 'Login'"
|
v-show="$route.name != 'title.settings' && $route.name != 'title.login'"
|
||||||
/>
|
/>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<BottomNavbar v-show="$route.name != 'Login'" />
|
<BottomNavbar v-show="$route.name != 'title.login'" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -10,19 +10,19 @@ import { RouterLink } from "vue-router";
|
|||||||
<div class="entry">
|
<div class="entry">
|
||||||
<RouterLink to="/timetable">
|
<RouterLink to="/timetable">
|
||||||
<CalendarIcon class="icon" />
|
<CalendarIcon class="icon" />
|
||||||
<span class="title">Timetable</span>
|
<span class="title">{{ $t("title.timetable") }}</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="entry">
|
<div class="entry">
|
||||||
<RouterLink to="/substitutions">
|
<RouterLink to="/substitutions">
|
||||||
<BellIcon class="icon" />
|
<BellIcon class="icon" />
|
||||||
<span class="title">Substitutions</span>
|
<span class="title">{{ $t("title.substitutions") }}</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="entry">
|
<div class="entry">
|
||||||
<RouterLink to="/history">
|
<RouterLink to="/history">
|
||||||
<ClockIcon class="icon" />
|
<ClockIcon class="icon" />
|
||||||
<span class="title">History</span>
|
<span class="title">{{ $t("title.history") }}</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,10 +6,19 @@ import {
|
|||||||
fetchHistory,
|
fetchHistory,
|
||||||
loading,
|
loading,
|
||||||
} from "../store";
|
} from "../store";
|
||||||
import { dayNames } from "../definitions";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import ArrowIcon from "./icons/arrow-icon.vue";
|
import ArrowIcon from "./icons/arrow-icon.vue";
|
||||||
|
|
||||||
|
const dayNames = [
|
||||||
|
"days.sunday",
|
||||||
|
"days.monday",
|
||||||
|
"days.tuesday",
|
||||||
|
"days.wednesday",
|
||||||
|
"days.thursday",
|
||||||
|
"days.friday",
|
||||||
|
"days.saturday",
|
||||||
|
];
|
||||||
|
|
||||||
function nextDay() {
|
function nextDay() {
|
||||||
var newDate = dayjs(selectedDate.value).add(1, "day");
|
var newDate = dayjs(selectedDate.value).add(1, "day");
|
||||||
// Skip weekend
|
// Skip weekend
|
||||||
@ -35,7 +44,7 @@ async function changeDate(newDate) {
|
|||||||
<div class="selector">
|
<div class="selector">
|
||||||
<ArrowIcon @click="previousDay" />
|
<ArrowIcon @click="previousDay" />
|
||||||
<span class="day" @click="changeDate(dayjs())">
|
<span class="day" @click="changeDate(dayjs())">
|
||||||
{{ dayNames[selectedDay + 1] }},
|
{{ $t(dayNames[selectedDay + 1]) }},
|
||||||
{{ dayjs(selectedDate).format("DD.MM.YYYY") }}
|
{{ dayjs(selectedDate).format("DD.MM.YYYY") }}
|
||||||
</span>
|
</span>
|
||||||
<ArrowIcon style="transform: rotate(180deg)" @click="nextDay" />
|
<ArrowIcon style="transform: rotate(180deg)" @click="nextDay" />
|
||||||
|
@ -2,18 +2,6 @@
|
|||||||
import CalendarIcon from "./icons/calendar-icon.vue";
|
import CalendarIcon from "./icons/calendar-icon.vue";
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
title: {
|
|
||||||
required: true,
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
required: true,
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
selectPrompt: {
|
|
||||||
required: true,
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
options: {
|
options: {
|
||||||
required: true,
|
required: true,
|
||||||
type: Array,
|
type: Array,
|
||||||
@ -28,12 +16,12 @@ defineProps({
|
|||||||
<div class="setup-wrapper">
|
<div class="setup-wrapper">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<CalendarIcon class="icon" />
|
<CalendarIcon class="icon" />
|
||||||
<span class="title">{{ title }}</span>
|
<span class="title">{{ $t("timetable.setup.title") }}</span>
|
||||||
<span class="description">{{ description }}</span>
|
<span class="description">{{ $t("timetable.setup.description") }}</span>
|
||||||
<select
|
<select
|
||||||
@input="(event) => $emit('update:modelValue', event.target.value)"
|
@input="(event) => $emit('update:modelValue', event.target.value)"
|
||||||
>
|
>
|
||||||
<option value="none">{{ selectPrompt }}</option>
|
<option value="none">{{ $t("timetable.setup.prompt") }}</option>
|
||||||
<option v-for="option in options" :value="option" :key="option">
|
<option v-for="option in options" :value="option" :key="option">
|
||||||
{{ option }}
|
{{ option }}
|
||||||
</option>
|
</option>
|
||||||
|
@ -8,7 +8,7 @@ const route = useRoute();
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const routeName = computed(() => route.name);
|
const routeName = computed(() => route.name);
|
||||||
function goBack() {
|
function goBack() {
|
||||||
if (routeName.value != "Settings") return;
|
if (routeName.value != "title.settings") return;
|
||||||
if (lastRoute.value.name) router.go(-1);
|
if (lastRoute.value.name) router.go(-1);
|
||||||
else router.push("/timetable");
|
else router.push("/timetable");
|
||||||
}
|
}
|
||||||
@ -16,11 +16,11 @@ function goBack() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="titlebar">
|
<div class="titlebar">
|
||||||
<span class="title">{{ routeName }}</span>
|
<span class="title">{{ $t(routeName || "") }}</span>
|
||||||
<div class="settings">
|
<div class="settings">
|
||||||
<RouterLink
|
<RouterLink
|
||||||
to="/settings"
|
to="/settings"
|
||||||
v-show="$route.name != 'Login'"
|
v-show="$route.name != 'title.login'"
|
||||||
@click="goBack()"
|
@click="goBack()"
|
||||||
>
|
>
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
export const dayNames = [
|
|
||||||
"Sunday",
|
|
||||||
"Monday",
|
|
||||||
"Tuesday",
|
|
||||||
"Wednesday",
|
|
||||||
"Thursay",
|
|
||||||
"Friday",
|
|
||||||
"Saturday",
|
|
||||||
];
|
|
||||||
export const substitutionTexts = {
|
|
||||||
subjectChange: "Unterrichtsänderung im Fach",
|
|
||||||
teacherChange: "Vertretung mit",
|
|
||||||
teacherChangePartial: "mit",
|
|
||||||
roomChange: "Raumvertretung in Raum",
|
|
||||||
roomChangePartial: "in Raum",
|
|
||||||
cancellation: "Ausfall",
|
|
||||||
};
|
|
||||||
export const uiTexts = {
|
|
||||||
substitutionNotes: "Notes",
|
|
||||||
};
|
|
10
src/main.js
10
src/main.js
@ -2,10 +2,20 @@ import { createApp } from "vue";
|
|||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
import { registerSW } from "virtual:pwa-register";
|
import { registerSW } from "virtual:pwa-register";
|
||||||
|
import { createI18n } from "vue-i18n";
|
||||||
|
import { strings } from "./strings";
|
||||||
|
import { language } from "./store";
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
locale: language.value,
|
||||||
|
fallbackLocale: "en",
|
||||||
|
messages: strings,
|
||||||
|
});
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
app.use(i18n);
|
||||||
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|
||||||
|
@ -15,27 +15,27 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/timetable",
|
path: "/timetable",
|
||||||
name: "Timetable",
|
name: "title.timetable",
|
||||||
component: TimetableView,
|
component: TimetableView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/substitutions",
|
path: "/substitutions",
|
||||||
name: "Substitutions",
|
name: "title.substitutions",
|
||||||
component: SubstitutionView,
|
component: SubstitutionView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/history",
|
path: "/history",
|
||||||
name: "History",
|
name: "title.history",
|
||||||
component: HistoryView,
|
component: HistoryView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
name: "Settings",
|
name: "title.settings",
|
||||||
component: SettingsView,
|
component: SettingsView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/login",
|
path: "/login",
|
||||||
name: "Login",
|
name: "title.login",
|
||||||
component: LoginView,
|
component: LoginView,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -9,6 +9,7 @@ export const classFilter = ref(localStorage.getItem("classFilter") || "none");
|
|||||||
export const timetableGroups = ref(
|
export const timetableGroups = ref(
|
||||||
JSON.parse(localStorage.getItem("timetableGroups") || "[]")
|
JSON.parse(localStorage.getItem("timetableGroups") || "[]")
|
||||||
);
|
);
|
||||||
|
export const language = ref(localStorage.getItem("lang")) || "en";
|
||||||
|
|
||||||
watch(classFilter, (newValue) => {
|
watch(classFilter, (newValue) => {
|
||||||
localStorage.setItem("classFilter", newValue);
|
localStorage.setItem("classFilter", newValue);
|
||||||
@ -18,6 +19,10 @@ watch(timetableGroups, (newValue) => {
|
|||||||
localStorage.setItem("timetableGroups", JSON.stringify(newValue));
|
localStorage.setItem("timetableGroups", JSON.stringify(newValue));
|
||||||
fetchData();
|
fetchData();
|
||||||
});
|
});
|
||||||
|
watch(language, (newValue) => {
|
||||||
|
localStorage.setItem("lang", newValue);
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
export const selectedDate = ref(new Date(new Date().setUTCHours(0, 0, 0, 0)));
|
export const selectedDate = ref(new Date(new Date().setUTCHours(0, 0, 0, 0)));
|
||||||
export const selectedDay = computed(() => selectedDate.value.getDay() - 1);
|
export const selectedDay = computed(() => selectedDate.value.getDay() - 1);
|
||||||
|
71
src/strings.js
Normal file
71
src/strings.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
export const strings = {
|
||||||
|
en: {
|
||||||
|
title: {
|
||||||
|
timetable: "Timetable",
|
||||||
|
substitutions: "Substitutions",
|
||||||
|
history: "History",
|
||||||
|
settings: "Settings",
|
||||||
|
login: "Login",
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
heading: {
|
||||||
|
filtering: "Filtering",
|
||||||
|
timetableGroups: "Timetable Groups",
|
||||||
|
language: "Language",
|
||||||
|
about: "About",
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
filtering:
|
||||||
|
"Select a class here so the correct timetable is used and only relevant substitutions are shown.",
|
||||||
|
timetableGroups:
|
||||||
|
"A timetable group defines, which lesson should be displayed, and which substitution should be shown if there are multiple possibilities for a single lesson within one class.",
|
||||||
|
language: "Change the language of all texts in the application",
|
||||||
|
about:
|
||||||
|
"This Tool queries and parses the latest timetable data every minute. The correctness of the data can in no way be guaranteed, so please check the data against the official plan if something seems wrong! Due to the format of the plan files, it it sometimes not easily possible to extract the correct data, so the plan displayed here may not be correct.",
|
||||||
|
},
|
||||||
|
other: "Other",
|
||||||
|
none: "None",
|
||||||
|
version: "Version",
|
||||||
|
},
|
||||||
|
timetable: {
|
||||||
|
warning: "Warning:",
|
||||||
|
trustWarning:
|
||||||
|
"The Data source of the Timetable data ({source}) is not trustworthy, which means the timetable may be incorrect!",
|
||||||
|
setup: {
|
||||||
|
title: "No class selected",
|
||||||
|
prompt: "Select a class",
|
||||||
|
description:
|
||||||
|
"Please select your class so you can view your timetable and only see substitutions that affect you. You can change this later in the settings.",
|
||||||
|
},
|
||||||
|
notes: "Notes:",
|
||||||
|
},
|
||||||
|
days: {
|
||||||
|
sunday: "Sunday",
|
||||||
|
monday: "Monday",
|
||||||
|
tuesday: "Tuesday",
|
||||||
|
wednesday: "Wednesday",
|
||||||
|
thursday: "Thursday",
|
||||||
|
friday: "Friday",
|
||||||
|
saturday: "Saturday",
|
||||||
|
},
|
||||||
|
substitution: {
|
||||||
|
text: {
|
||||||
|
withClass: {
|
||||||
|
subjectChange:
|
||||||
|
"Lesson changed for {class} in {subject} with {teacher} in room {room}",
|
||||||
|
teacherChange: "Lesson for {class} in {subject} now with {teacher}",
|
||||||
|
roomChange:
|
||||||
|
"Room for lesson {subject} for {class} with {teacher} is now {room}",
|
||||||
|
cancellation: "Lesson for {class} with {teacher} is cancelled",
|
||||||
|
},
|
||||||
|
withoutClass: {
|
||||||
|
subjectChange:
|
||||||
|
"Lesson changed in {subject} with {teacher} in room {room}",
|
||||||
|
teacherChange: "Lesson in {subject} now with {teacher}",
|
||||||
|
roomChange: "Room for lesson {subject} with {teacher} is now {room}",
|
||||||
|
cancellation: "Lesson with {teacher} is cancelled",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
31
src/util.js
31
src/util.js
@ -1,35 +1,16 @@
|
|||||||
import { substitutionTexts } from "./definitions";
|
|
||||||
import { classFilter } from "./store";
|
import { classFilter } from "./store";
|
||||||
|
|
||||||
export function getSubstitutionText(substitution) {
|
export function getSubstitutionText(substitution) {
|
||||||
var text = "";
|
const includeClass = !classFilter.value || classFilter.value == "none";
|
||||||
|
const includeClassValue = includeClass ? "withClass" : "withoutClass";
|
||||||
|
|
||||||
if (!classFilter.value || classFilter.value == "none") {
|
// TODO: implement more texts
|
||||||
text +=
|
|
||||||
substitution.class.join(", ") +
|
|
||||||
(substitution.class.length > 1 ? " haben " : " hat ");
|
|
||||||
}
|
|
||||||
if (substitution.type == "change") {
|
if (substitution.type == "change") {
|
||||||
const change = substitution.change;
|
return `substitution.text.${includeClassValue}.subjectChange`;
|
||||||
if (change.subject) {
|
|
||||||
text += substitutionTexts.subjectChange + " " + change.subject;
|
|
||||||
}
|
|
||||||
if (change.teacher && text == "") {
|
|
||||||
text += substitutionTexts.teacherChange + " " + change.teacher;
|
|
||||||
} else if (change.teacher) {
|
|
||||||
text += " " + substitutionTexts.teacherChangePartial;
|
|
||||||
text += " " + change.teacher;
|
|
||||||
}
|
|
||||||
if (change.room && text == "") {
|
|
||||||
text += substitutionTexts.roomChange + " " + change.room;
|
|
||||||
} else if (change.room) {
|
|
||||||
text += " " + substitutionTexts.roomChangePartial;
|
|
||||||
text += " " + change.room;
|
|
||||||
}
|
|
||||||
} else if (substitution.type == "cancellation") {
|
} else if (substitution.type == "cancellation") {
|
||||||
text += substitutionTexts.cancellation;
|
return `substitution.text.${includeClassValue}.cancellation`;
|
||||||
}
|
}
|
||||||
return text;
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSubstitutionColor(substitution) {
|
export function getSubstitutionColor(substitution) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { historyOfDate, selectedDate } from "../store";
|
import { historyOfDate, selectedDate } from "../store";
|
||||||
import { uiTexts } from "../definitions";
|
|
||||||
import { getSubstitutionText } from "../util";
|
import { getSubstitutionText } from "../util";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
@ -47,11 +46,16 @@ function getColor(type) {
|
|||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<span class="text" v-else>
|
<span class="text" v-else
|
||||||
{{ getSubstitutionText(event.change) }}
|
>{{
|
||||||
<span class="notes">
|
$t(getSubstitutionText(event.change), {
|
||||||
{{ event.change.notes ? uiTexts.substitutionNotes + ": " : "" }}
|
subject: event.change.subject,
|
||||||
{{ event.change.notes }}
|
class: event.change.class.join(", "),
|
||||||
|
teacher: event.change.change.teacher || event.change.teacher,
|
||||||
|
room: event.change.change.room,
|
||||||
|
})
|
||||||
|
}}<span class="notes" v-if="event.change.notes">
|
||||||
|
{{ $t("timetable.notes") }} {{ event.change.notes }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="notes">
|
<span class="notes">
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
classFilter,
|
classFilter,
|
||||||
possibleTimetableGroups,
|
possibleTimetableGroups,
|
||||||
timetableGroups,
|
timetableGroups,
|
||||||
|
language,
|
||||||
} from "../store";
|
} from "../store";
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
@ -12,27 +13,20 @@ const gitHash = GITVERSION;
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="settings">
|
<div class="settings">
|
||||||
<h2>Filtering</h2>
|
<h2>{{ $t("settings.heading.filtering") }}</h2>
|
||||||
<p>
|
<p>{{ $t("settings.text.filtering") }}</p>
|
||||||
Select a class here so the correct timetable is used and only relevant
|
|
||||||
substitutions are shown.
|
|
||||||
</p>
|
|
||||||
<select v-model="classFilter">
|
<select v-model="classFilter">
|
||||||
<option value="none">Select a class</option>
|
<option value="none">{{ $t("timetable.setup.prompt") }}</option>
|
||||||
<option v-for="option in classList" :value="option" :key="option">
|
<option v-for="option in classList" :value="option" :key="option">
|
||||||
{{ option }}
|
{{ option }}
|
||||||
</option>
|
</option>
|
||||||
<option value="other">Other</option>
|
<option value="other">{{ $t("settings.other") }}</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<h2>Timetable Groups</h2>
|
<h2>{{ $t("settings.heading.timetableGroups") }}</h2>
|
||||||
<p>
|
<p>{{ $t("settings.text.timetableGroups") }}</p>
|
||||||
A timetable group defines, which lesson should be displayed, and which
|
|
||||||
substitution should be shown if there are multiple possibilities for a
|
|
||||||
single lesson within one class.
|
|
||||||
</p>
|
|
||||||
<select v-model="timetableGroups" multiple>
|
<select v-model="timetableGroups" multiple>
|
||||||
<option value="none">None</option>
|
<option value="none">{{ $t("settings.none") }}</option>
|
||||||
<option
|
<option
|
||||||
v-for="option in possibleTimetableGroups"
|
v-for="option in possibleTimetableGroups"
|
||||||
:value="option"
|
:value="option"
|
||||||
@ -42,16 +36,22 @@ const gitHash = GITVERSION;
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<h2>About</h2>
|
<h2>{{ $t("settings.heading.language") }}</h2>
|
||||||
<p>
|
<p>{{ $t("settings.text.language") }}</p>
|
||||||
This Tool queries and parses the latest timetable data every minute. The
|
<select v-model="language">
|
||||||
correctness of the data can in no way be guaranteed, so please check the
|
<option
|
||||||
data against the official plan if something seems wrong! Due to the format
|
v-for="locale in $i18n.availableLocales"
|
||||||
of the plan files, it it sometimes not easily possible to extract the
|
:key="`locale-${locale}`"
|
||||||
correct data, so the plan displayed here may not be correct.
|
:value="locale"
|
||||||
</p>
|
>
|
||||||
|
{{ locale }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<p class="gray">Version: {{ gitHash }}</p>
|
<h2>{{ $t("settings.heading.about") }}</h2>
|
||||||
|
<p>{{ $t("settings.text.about") }}</p>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<p class="gray">{{ $t("settings.version") }}: {{ gitHash }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { substitutionsForDate, selectedDate } from "../store";
|
import { substitutionsForDate, selectedDate } from "../store";
|
||||||
import { uiTexts } from "../definitions";
|
|
||||||
import { getSubstitutionText, getSubstitutionColor } from "../util";
|
import { getSubstitutionText, getSubstitutionColor } from "../util";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
@ -15,10 +14,16 @@ const substitutions = computed(() => {
|
|||||||
<div class="substitution" :style="getSubstitutionColor(substitution)">
|
<div class="substitution" :style="getSubstitutionColor(substitution)">
|
||||||
<span class="hour">{{ substitution.lesson }}</span>
|
<span class="hour">{{ substitution.lesson }}</span>
|
||||||
<div class="infos">
|
<div class="infos">
|
||||||
<span class="text">{{ getSubstitutionText(substitution) }}</span>
|
<span class="text">{{
|
||||||
<span class="notes">
|
$t(getSubstitutionText(substitution), {
|
||||||
{{ substitution.notes ? uiTexts.substitutionNotes + ": " : "" }}
|
subject: substitution.change.subject,
|
||||||
{{ substitution.notes }}
|
class: substitution.class.join(", "),
|
||||||
|
teacher: substitution.change.teacher || substitution.teacher,
|
||||||
|
room: substitution.change.room,
|
||||||
|
})
|
||||||
|
}}</span>
|
||||||
|
<span class="notes" v-if="substitution.notes">
|
||||||
|
{{ $t("timetable.notes") }} {{ substitution.notes }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,18 +53,14 @@ function isCancelled(substitution) {
|
|||||||
<template>
|
<template>
|
||||||
<TimetableSetup
|
<TimetableSetup
|
||||||
v-show="!classFilter || classFilter == 'none'"
|
v-show="!classFilter || classFilter == 'none'"
|
||||||
title="No Class Selected"
|
|
||||||
description="Please select your class so you can view your timetable and only see substitutions that affect you. You can change this later in the settings."
|
|
||||||
selectPrompt="Select a class"
|
|
||||||
:options="classList"
|
:options="classList"
|
||||||
v-model="classFilter"
|
v-model="classFilter"
|
||||||
/>
|
/>
|
||||||
<div class="timetable">
|
<div class="timetable">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="trust-warning" v-if="!timetable.trusted">
|
<div class="trust-warning" v-if="!timetable.trusted">
|
||||||
<b>Warning:</b> The Data source of the Timetable data
|
<b>{{ $t("timetable.warning") }}</b>
|
||||||
<b>({{ timetable.source }})</b> is not trustworthy, which means the
|
{{ $t("timetable.trustWarning", { source: timetable.source }) }}
|
||||||
timetable may be incorrect!
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-for="(lesson, index) in linkedTimetable" :key="index">
|
<template v-for="(lesson, index) in linkedTimetable" :key="index">
|
||||||
@ -98,7 +94,7 @@ function isCancelled(substitution) {
|
|||||||
>
|
>
|
||||||
<!-- Show notes if available -->
|
<!-- Show notes if available -->
|
||||||
<span class="info" v-if="getNotes(lesson.substitution)"
|
<span class="info" v-if="getNotes(lesson.substitution)"
|
||||||
>, Notes: {{ getNotes(lesson.substitution) }}
|
>, {{ $t("timetable.notes") }} {{ getNotes(lesson.substitution) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user