diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..dd84ea7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,38 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+ - Version [e.g. 22]
+
+**Smartphone (please complete the following information):**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS8.1]
+ - Browser [e.g. stock browser, safari]
+ - Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..bbcbbe7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.gitignore b/.gitignore
index 2ba9c4f..278bf8a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.idea/
.vscode/
.DS_Store
-
+workspace.xml
+**/.idea/
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 9ed1b03..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,9 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-/jsLibraryMappings.xml
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
deleted file mode 100644
index f986f2f..0000000
--- a/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index 79ee123..0000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/htwk-planner.iml b/.idea/htwk-planner.iml
deleted file mode 100644
index 5e764c4..0000000
--- a/.idea/htwk-planner.iml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
deleted file mode 100644
index d23208f..0000000
--- a/.idea/jsLibraryMappings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 99c7e98..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/prettier.xml b/.idea/prettier.xml
deleted file mode 100644
index b0c1c68..0000000
--- a/.idea/prettier.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/swagger-settings.xml b/.idea/swagger-settings.xml
deleted file mode 100644
index 01d844c..0000000
--- a/.idea/swagger-settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
deleted file mode 100644
index 2b15ac5..0000000
--- a/.idea/workspace.xml
+++ /dev/null
@@ -1,224 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- "associatedIndex": 2
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1697463118186
-
-
- 1697463118186
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backend/.idea/dataSources.xml b/backend/.idea/dataSources.xml
deleted file mode 100644
index be7ca6d..0000000
--- a/backend/.idea/dataSources.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
- sqlite.xerial
- true
- org.sqlite.JDBC
- jdbc:sqlite:$PROJECT_DIR$/pb_data/data.db
- $ProjectFileDir$
-
-
-
\ No newline at end of file
diff --git a/backend/.idea/jpa-buddy.xml b/backend/.idea/jpa-buddy.xml
deleted file mode 100644
index 966d5f5..0000000
--- a/backend/.idea/jpa-buddy.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backend/.idea/misc.xml b/backend/.idea/misc.xml
deleted file mode 100644
index d79bd4e..0000000
--- a/backend/.idea/misc.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backend/.idea/modules.xml b/backend/.idea/modules.xml
deleted file mode 100644
index 0dc77c9..0000000
--- a/backend/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backend/.idea/vcs.xml b/backend/.idea/vcs.xml
deleted file mode 100644
index 6c0b863..0000000
--- a/backend/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backend/openapi.yml b/backend/openapi.yml
index d1aee05..7f51ae3 100644
--- a/backend/openapi.yml
+++ b/backend/openapi.yml
@@ -3,7 +3,7 @@ info:
title: HTWKalendar API
version: 1.0.1
servers:
- - url: https://cal.ekresse.de
+ - url: https://htwkalendar.de
description: Production server
- url: http://localhost:8090
description: Local server
diff --git a/backend/service/fetch/fetchSeminarEventService.go b/backend/service/fetch/fetchSeminarEventService.go
index 81521ae..bf18258 100644
--- a/backend/service/fetch/fetchSeminarEventService.go
+++ b/backend/service/fetch/fetchSeminarEventService.go
@@ -82,7 +82,7 @@ func GetSeminarGroupsEventsFromHTML(seminarGroupsLabel []string) []model.Seminar
func splitEventType(events []model.Event) []model.Event {
for i, event := range events {
- matched, _ := regexp.Match("^(V|P|S)(w|p)$", []byte(event.EventType))
+ matched, _ := regexp.Match("^([VPS])([wp])$", []byte(event.EventType))
if matched {
eventType := event.EventType
event.EventType = eventType[0:1]
@@ -136,17 +136,27 @@ func generateUUIDs(events []model.Event, course string) []model.Event {
}
+// convertWeeksToDates converts the week and year to a date
+// The date is calculated based on the week and the year
+// The time is unset and 23:00 is used as default
+// Additionally the semester is added to the event
+
func convertWeeksToDates(events []model.Event, semester string, year string) []model.Event {
var newEvents []model.Event
eventYear, _ := strconv.Atoi(year)
// for each event we need to calculate the start and end date based on the week and the year
for _, event := range events {
-
eventWeek, _ := strconv.Atoi(event.Week)
eventDay, _ := date.GetDateFromWeekNumber(eventYear, eventWeek, event.Day)
start := replaceTimeForDate(eventDay, event.Start.Time())
end := replaceTimeForDate(eventDay, event.End.Time())
+
+ //Check if end is before start
+ if end.Before(start) {
+ end = end.AddDate(0, 0, 1)
+ }
+
newEvent := event
newEvent.Start, _ = types.ParseDateTime(start.In(time.UTC))
newEvent.End, _ = types.ParseDateTime(end.In(time.UTC))
diff --git a/backend/service/fetch/fetchSeminarEventService_test.go b/backend/service/fetch/fetchSeminarEventService_test.go
index dde66b4..e1b6948 100644
--- a/backend/service/fetch/fetchSeminarEventService_test.go
+++ b/backend/service/fetch/fetchSeminarEventService_test.go
@@ -1,6 +1,8 @@
package fetch
import (
+ "fmt"
+ "github.com/pocketbase/pocketbase/tools/types"
"htwkalender/model"
"reflect"
"testing"
@@ -340,3 +342,155 @@ func Test_replaceTimeInDate(t *testing.T) {
})
}
}
+
+func Test_convertWeeksToDates(t *testing.T) {
+ type args struct {
+ events []model.Event
+ semester string
+ year string
+ }
+ returnDateTime := func(date time.Time) types.DateTime {
+ dateTime, err := types.ParseDateTime(date)
+ if err != nil {
+ fmt.Println(err)
+ }
+ return dateTime
+ }
+ tests := []struct {
+ name string
+ args args
+ want []model.Event
+ }{
+ {
+ name: "Test Wintertime",
+ args: args{
+ events: []model.Event{
+ {
+ Week: "1",
+ Day: "Montag",
+ Start: returnDateTime(time.Date(0, 0, 0, 7, 30, 0, 0, time.UTC)),
+ End: returnDateTime(time.Date(0, 0, 0, 9, 0, 0, 0, time.UTC)),
+ },
+ },
+ semester: "ws",
+ year: "2021",
+ },
+ want: []model.Event{
+ {
+ Week: "1",
+ Day: "Montag",
+ Start: returnDateTime(time.Date(2021, 1, 4, 6, 30, 0, 0, time.UTC)),
+ End: returnDateTime(time.Date(2021, 1, 4, 8, 0, 0, 0, time.UTC)),
+ Semester: "ws",
+ },
+ },
+ },
+ {
+ name: "Test Summertime",
+ args: args{
+ events: []model.Event{
+ {
+ Week: "30",
+ Day: "Donnerstag",
+ Start: returnDateTime(time.Date(0, 0, 0, 7, 30, 0, 0, time.UTC)),
+ End: returnDateTime(time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC)),
+ },
+ },
+ semester: "ws",
+ year: "2023",
+ },
+ want: []model.Event{
+ {
+ Week: "30",
+ Day: "Donnerstag",
+ Start: returnDateTime(time.Date(2023, 7, 27, 5, 30, 0, 0, time.UTC)),
+ End: returnDateTime(time.Date(2023, 7, 27, 22, 0, 0, 0, time.UTC)),
+ Semester: "ws",
+ },
+ },
+ },
+ {
+ name: "Test NextDay",
+ args: args{
+ events: []model.Event{
+ {
+ Week: "45",
+ Day: "Donnerstag",
+ Start: returnDateTime(time.Date(0, 0, 0, 7, 30, 0, 0, time.UTC)),
+ End: returnDateTime(time.Date(0, 0, 0, 4, 0, 0, 0, time.UTC)),
+ },
+ },
+ semester: "ws",
+ year: "2023",
+ },
+ want: []model.Event{
+ {
+ Week: "45",
+ Day: "Donnerstag",
+ Start: returnDateTime(time.Date(2023, 11, 9, 6, 30, 0, 0, time.UTC)),
+ End: returnDateTime(time.Date(2023, 11, 10, 3, 0, 0, 0, time.UTC)),
+ Semester: "ws",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := convertWeeksToDates(tt.args.events, tt.args.semester, tt.args.year); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("convertWeeksToDates() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_replaceTimeForDate(t *testing.T) {
+ type args struct {
+ date time.Time
+ replacementTime time.Time
+ }
+ tests := []struct {
+ name string
+ args args
+ want time.Time
+ }{
+ {
+ name: "Replace Hour",
+ args: args{
+ date: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
+ replacementTime: time.Date(0, 0, 0, 8, 0, 0, 0, time.UTC),
+ },
+ want: time.Date(2021, 1, 1, 8, 0, 0, 0, time.UTC),
+ },
+ {
+ name: "Replace Hour and Minute",
+ args: args{
+ date: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
+ replacementTime: time.Date(0, 0, 0, 8, 15, 0, 0, time.UTC),
+ },
+ want: time.Date(2021, 1, 1, 8, 15, 0, 0, time.UTC),
+ },
+ {
+ name: "Replace Hour and Minute",
+ args: args{
+ date: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
+ replacementTime: time.Date(0, 0, 0, 8, 30, 0, 0, time.UTC),
+ },
+ want: time.Date(2021, 1, 1, 8, 30, 0, 0, time.UTC),
+ },
+ {
+ name: "Replace Hour and Minute without Year, Month, Day",
+ args: args{
+ date: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
+ replacementTime: time.Date(2023, 10, 3, 8, 30, 0, 0, time.UTC),
+ },
+ want: time.Date(2021, 1, 1, 8, 30, 0, 0, time.UTC),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := replaceTimeForDate(tt.args.date, tt.args.replacementTime); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("replaceTimeForDate() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/backend/service/ical/icalFileGeneration.go b/backend/service/ical/icalFileGeneration.go
index 47f41d9..cee12d2 100644
--- a/backend/service/ical/icalFileGeneration.go
+++ b/backend/service/ical/icalFileGeneration.go
@@ -4,6 +4,7 @@ import (
"htwkalender/model"
"htwkalender/service/functions"
"htwkalender/service/names"
+ "strconv"
"time"
"github.com/jordic/goics"
@@ -20,21 +21,29 @@ func (icalModel IcalModel) EmitICal() goics.Componenter {
europeTime, _ := time.LoadLocation("Europe/Berlin")
c := goics.NewComponent()
c.SetType("VCALENDAR")
+ // PRODID is required by the standard
+ c.AddProperty("PRODID", "-//HTWK Kalender//htwkalender.de//DE")
+
c.AddProperty("VERSION", "2.0")
- c.AddProperty("CALSCAL", "GREGORIAN")
- c.AddProperty("TZID", "Europe/Berlin")
+ c.AddProperty("CALSCALE", "GREGORIAN")
+ c.AddProperty("TZID", "EUROPE/BERLIN")
c.AddProperty("X-WR-CALNAME", "HTWK Kalender")
- c.AddProperty("X-WR-TIMEZONE", "Europe/Berlin")
- c.AddProperty("X-LIC-LOCATION", "Europe/Berlin")
- for _, event := range icalModel.Events {
+ c.AddProperty("X-WR-TIMEZONE", "EUROPE/BERLIN")
+ //add v time zone
+ icalModel.vtimezone(c)
+
+ var timeStamp = time.Now().Local().In(europeTime).Format("20060102T150405")
+
+ for i, event := range icalModel.Events {
mapEntry, mappingFound := icalModel.Mapping[event.UUID]
s := goics.NewComponent()
s.SetType("VEVENT")
- k, v := goics.FormatDateTime("DTEND;TZID=Europe/Berlin", event.End.Time().Local().In(europeTime))
- s.AddProperty(k, v)
- k, v = goics.FormatDateTime("DTSTART;TZID=Europe/Berlin", event.Start.Time().Local().In(europeTime))
- s.AddProperty(k, v)
+
+ s.AddProperty(goics.FormatDateTime("DTSTAMP", time.Now().Local().In(europeTime)))
+ s.AddProperty("UID", strconv.FormatInt(int64(i), 16)+"-"+timeStamp+"@htwkalender.de")
+ s.AddProperty(goics.FormatDateTime("DTEND", event.End.Time().Local().In(europeTime)))
+ s.AddProperty(goics.FormatDateTime("DTSTART", event.Start.Time().Local().In(europeTime)))
if mappingFound {
addPropertyIfNotEmpty(s, "SUMMARY", replaceNameIfUserDefined(&event, mapEntry))
@@ -50,6 +59,43 @@ func (icalModel IcalModel) EmitICal() goics.Componenter {
return c
}
+func (icalModel IcalModel) vtimezone(c *goics.Component) {
+ tz := goics.NewComponent()
+ tz.SetType("VTIMEZONE")
+ tz.AddProperty("TZID", "EUROPE/BERLIN")
+ //add standard time
+ icalModel.standard(tz)
+ //add daylight time
+ icalModel.daylight(tz)
+
+ c.AddComponent(tz)
+}
+
+func (icalModel IcalModel) standard(tz *goics.Component) {
+ st := NewHtwkalenderComponent()
+ st.SetType("STANDARD")
+ st.AddProperty("DTSTART", "19701025T030000")
+ st.AddProperty("RRULE", "FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU")
+ st.AddProperty("TZOFFSETFROM", "+0200")
+ st.AddProperty("TZOFFSETTO", "+0100")
+ st.AddProperty("TZNAME", "CET")
+ tz.AddComponent(st)
+}
+
+// create an override for goics component function Write
+// to add the RRULE property
+
+func (icalModel IcalModel) daylight(tz *goics.Component) {
+ dt := NewHtwkalenderComponent()
+ dt.SetType("DAYLIGHT")
+ dt.AddProperty("DTSTART", "19700329T020000")
+ dt.AddProperty("TZOFFSETFROM", "+0100")
+ dt.AddProperty("TZOFFSETTO", "+0200")
+ dt.AddProperty("TZNAME", "CEST")
+ dt.AddProperty("RRULE", "FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU")
+ tz.AddComponent(dt)
+}
+
// if reminder is specified in the configuration for this event, an alarm will be added to the event
func addAlarmIfSpecified(s *goics.Component, event model.Event, mapping model.FeedCollection) {
if mapping.Reminder {
diff --git a/backend/service/ical/icsComponenter.go b/backend/service/ical/icsComponenter.go
new file mode 100644
index 0000000..b44c74a
--- /dev/null
+++ b/backend/service/ical/icsComponenter.go
@@ -0,0 +1,46 @@
+package ical
+
+import (
+ "github.com/jordic/goics"
+ "sort"
+ "strings"
+)
+
+type HtwkalenderComponent struct {
+ *goics.Component
+}
+
+func NewHtwkalenderComponent() *HtwkalenderComponent {
+ return &HtwkalenderComponent{
+ Component: goics.NewComponent(),
+ }
+}
+
+// Writes the component to the Writer
+func (c *HtwkalenderComponent) Write(w *goics.ICalEncode) {
+ w.WriteLine("BEGIN:" + c.Tipo + goics.CRLF)
+
+ // Iterate over component properties
+ var keys []string
+ for k := range c.Properties {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, key := range keys {
+ vals := c.Properties[key]
+ for _, val := range vals {
+ w.WriteLine(WriteStringField(key, val))
+ }
+ }
+
+ for _, xc := range c.Elements {
+ xc.Write(w)
+ }
+
+ w.WriteLine("END:" + c.Tipo + goics.CRLF)
+}
+
+// WriteStringField UID:asdfasdfаs@dfasdf.com
+func WriteStringField(key string, val string) string {
+ return strings.ToUpper(key) + ":" + (val) + goics.CRLF
+}
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index 937d52a..4eb781c 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:latest
+FROM node:lts-alpine3.18
WORKDIR /app
COPY package*.json ./
diff --git a/frontend/Dockerfile_prod b/frontend/Dockerfile_prod
index 43139ef..0d4cb85 100644
--- a/frontend/Dockerfile_prod
+++ b/frontend/Dockerfile_prod
@@ -1,5 +1,5 @@
# build stage
-FROM node:latest as build-stage
+FROM node:lts-alpine3.18 as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm ci
diff --git a/frontend/index.html b/frontend/index.html
index a4c3e3f..4aad534 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -2,7 +2,7 @@
-
+
HTWKalender
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 1434fd0..1a3f9e4 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -13,11 +13,13 @@
"@fullcalendar/interaction": "^6.1.9",
"@fullcalendar/timegrid": "^6.1.9",
"@fullcalendar/vue3": "^6.1.9",
+ "@vueuse/core": "^10.6.1",
"pinia": "^2.1.6",
"primeflex": "^3.3.1",
"primeicons": "^6.0.1",
"primevue": "^3.32.2",
"vue": "^3.3.4",
+ "vue-i18n": "^9.4.1",
"vue-router": "^4.2.4"
},
"devDependencies": {
@@ -27,6 +29,7 @@
"eslint": "^8.48.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-vue": "^9.17.0",
+ "moment-timezone": "^0.5.43",
"prettier": "3.0.2",
"sass": "^1.69.5",
"sass-loader": "^13.3.2",
@@ -584,6 +587,47 @@
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true
},
+ "node_modules/@intlify/core-base": {
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.4.1.tgz",
+ "integrity": "sha512-WIwx+elsZbxSMxRG5+LC+utRohFvmZMoDevfKOfnYMLbpCjCSavqTfHJAtfsY6ruowzqXeKkeLhRHbYbjoJx5g==",
+ "dependencies": {
+ "@intlify/message-compiler": "9.4.1",
+ "@intlify/shared": "9.4.1"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/message-compiler": {
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.4.1.tgz",
+ "integrity": "sha512-aN2N+dUx320108QhH51Ycd2LEpZ+NKbzyQ2kjjhqMcxhHdxtOnkgdx+MDBhOy/CObwBmhC3Nygzc6hNlfKvPNw==",
+ "dependencies": {
+ "@intlify/shared": "9.4.1",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/shared": {
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.4.1.tgz",
+ "integrity": "sha512-A51elBmZWf1FS80inf/32diO9DeXoqg9GR9aUDHFcfHoNDuT46Q+fpPOdj8jiJnSHSBh8E1E+6qWRhAZXdK3Ng==",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
@@ -728,6 +772,11 @@
"integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==",
"dev": true
},
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.20",
+ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
+ "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.0.tgz",
@@ -1122,6 +1171,89 @@
"@vue/language-core": "1.8.8"
}
},
+ "node_modules/@vueuse/core": {
+ "version": "10.6.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.6.1.tgz",
+ "integrity": "sha512-Pc26IJbqgC9VG1u6VY/xrXXfxD33hnvxBnKrLlA2LJlyHII+BSrRoTPJgGYq7qZOu61itITFUnm6QbacwZ4H8Q==",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.20",
+ "@vueuse/metadata": "10.6.1",
+ "@vueuse/shared": "10.6.1",
+ "vue-demi": ">=0.14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/core/node_modules/vue-demi": {
+ "version": "0.14.6",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
+ "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
+ "hasInstallScript": true,
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "10.6.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.6.1.tgz",
+ "integrity": "sha512-qhdwPI65Bgcj23e5lpGfQsxcy0bMjCAsUGoXkJ7DsoeDUdasbZ2DBa4dinFCOER3lF4gwUv+UD2AlA11zdzMFw==",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "10.6.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.6.1.tgz",
+ "integrity": "sha512-TECVDTIedFlL0NUfHWncf3zF9Gc4VfdxfQc8JFwoVZQmxpONhLxFrlm0eHQeidHj4rdTPL3KXJa0TZCk1wnc5Q==",
+ "dependencies": {
+ "vue-demi": ">=0.14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared/node_modules/vue-demi": {
+ "version": "0.14.6",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
+ "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
+ "hasInstallScript": true,
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@webassemblyjs/ast": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
@@ -2588,6 +2720,27 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/moment": {
+ "version": "2.29.4",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/moment-timezone": {
+ "version": "0.5.43",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz",
+ "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==",
+ "dev": true,
+ "dependencies": {
+ "moment": "^2.29.4"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -2829,9 +2982,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.28",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz",
- "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==",
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"funding": [
{
"type": "opencollective",
@@ -3525,6 +3678,25 @@
"eslint": ">=6.0.0"
}
},
+ "node_modules/vue-i18n": {
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.4.1.tgz",
+ "integrity": "sha512-vnQyYE9LBuNOqPpETIcCaGnAyLEqfeIvDcyZ9T+WBCWFTqWw1J8FuF1jfeDwpHBi5JKgAwgXyq1mt8jp/x/GPA==",
+ "dependencies": {
+ "@intlify/core-base": "9.4.1",
+ "@intlify/shared": "9.4.1",
+ "@vue/devtools-api": "^6.5.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ },
+ "peerDependencies": {
+ "vue": "^3.0.0"
+ }
+ },
"node_modules/vue-router": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.4.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 12358ed..3c1e442 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -16,11 +16,13 @@
"@fullcalendar/interaction": "^6.1.9",
"@fullcalendar/timegrid": "^6.1.9",
"@fullcalendar/vue3": "^6.1.9",
+ "@vueuse/core": "^10.6.1",
"pinia": "^2.1.6",
"primeflex": "^3.3.1",
"primeicons": "^6.0.1",
"primevue": "^3.32.2",
"vue": "^3.3.4",
+ "vue-i18n": "^9.4.1",
"vue-router": "^4.2.4"
},
"devDependencies": {
@@ -30,6 +32,7 @@
"eslint": "^8.48.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-vue": "^9.17.0",
+ "moment-timezone": "^0.5.43",
"prettier": "3.0.2",
"sass": "^1.69.5",
"sass-loader": "^13.3.2",
diff --git a/frontend/public/htwk.svg b/frontend/public/htwk.svg
new file mode 100644
index 0000000..cef9566
--- /dev/null
+++ b/frontend/public/htwk.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/frontend/public/htwkalender.svg b/frontend/public/htwkalender.svg
new file mode 100644
index 0000000..e0c98e5
--- /dev/null
+++ b/frontend/public/htwkalender.svg
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index 7a564bb..4562c96 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -1,11 +1,12 @@
-
+
diff --git a/frontend/src/api/fetchCourse.ts b/frontend/src/api/fetchCourse.ts
index 00974e4..b1e164b 100644
--- a/frontend/src/api/fetchCourse.ts
+++ b/frontend/src/api/fetchCourse.ts
@@ -6,7 +6,13 @@ export async function fetchCourse(): Promise {
const courses: string[] = [];
await fetch("/api/courses")
.then((response) => {
- return response.json();
+ //check if response type is json
+ const contentType = response.headers.get("content-type");
+ if (contentType && contentType.indexOf("application/json") !== -1) {
+ return response.json();
+ } else {
+ return [];
+ }
})
.then((coursesResponse) => {
coursesResponse.forEach((course: string) => courses.push(course));
diff --git a/frontend/src/components/CalendarLink.vue b/frontend/src/components/CalendarLink.vue
index fda780d..2507ca8 100644
--- a/frontend/src/components/CalendarLink.vue
+++ b/frontend/src/components/CalendarLink.vue
@@ -3,15 +3,21 @@ import tokenStore from "../store/tokenStore.ts";
import { useToast } from "primevue/usetoast";
import { onMounted } from "vue";
import router from "../router";
+import { useI18n } from "vue-i18n";
+const { t } = useI18n({ useScope: "global" });
+
const toast = useToast();
const domain = window.location.hostname;
+const getLink = () =>
+ "https://" + domain + "/api/feed?token=" + tokenStore().token;
+
const show = () => {
toast.add({
severity: "info",
- summary: "Info",
- detail: "Link copied to clipboard",
+ summary: t("calendarLink.copyToastSummary"),
+ detail: t("calendarLink.copyToastNotification"),
life: 3000,
});
};
@@ -27,11 +33,49 @@ function rerouteIfTokenIsEmpty() {
}
function copyToClipboard() {
- const text = "https://" + domain + "/api/feed?token=" + tokenStore().token;
// Copy the text inside the text field
- navigator.clipboard.writeText(text);
- show();
+ navigator.clipboard.writeText(getLink()).then(show, (err) => {
+ console.error("Could not copy text: ", err);
+ toast.add({
+ severity: "error",
+ summary: t("calendarLink.copyToastError"),
+ detail: t("calendarLink.copyToastErrorDetail"),
+ life: 3000,
+ });
+ });
}
+
+const forwardToGoogle = () => {
+ window.open(
+ "https://calendar.google.com/calendar/u/0/r?cid=" +
+ encodeURI(getLink().replace("https://", "http://")),
+ );
+};
+
+const forwardToMicrosoft = () => {
+ window.open(
+ "https://outlook.live.com/owa?path=/calendar/action/compose&rru=addsubscription&name=HTWK%20Kalender&url=" +
+ encodeURI(getLink()),
+ );
+};
+
+const actions = [
+ {
+ label: t("calendarLink.copyToClipboard"),
+ icon: "pi pi-copy",
+ command: copyToClipboard,
+ },
+ {
+ label: t("calendarLink.toGoogleCalendar"),
+ icon: "pi pi-google",
+ command: forwardToGoogle,
+ },
+ {
+ label: t("calendarLink.toMicrosoftCalendar"),
+ icon: "pi pi-microsoft",
+ command: forwardToMicrosoft,
+ },
+];
@@ -39,11 +83,11 @@ function copyToClipboard() {
- {{ "https://" + domain + "/api/feed?token=" + tokenStore().token }}
+ {{ getLink() }}
-
diff --git a/frontend/src/components/CourseSelection.vue b/frontend/src/components/CourseSelection.vue
index adbe155..88b9134 100644
--- a/frontend/src/components/CourseSelection.vue
+++ b/frontend/src/components/CourseSelection.vue
@@ -1,11 +1,13 @@
+
-
FAQ
+ {{$t('faqView.headline')}}
- Wie funktioniert das Kalender erstellen mit dem HTWKalender?
+ {{$t('faqView.firstQuestion')}}
- Die Webseite ermöglicht es deinen HTWK-Stundenplan in eines deiner
- bevorzugten Kalender-Verwaltungs-Programme (Outlook, Google Kalender,
- etc.) einzubinden.
+ {{$t('faqView.firstAnswer')}}
+
+
+
+
{{$t('faqView.secondQuestion')}}
+
+ {{$t('faqView.secondAnswer')}}
-
Wie genau funktioniert das alles?
-
- Du wählst deinen Studiengang und das gewünschte Semester aus, danach
- kannst du aus den dazugehörigen Modulen wählen. Dabei kannst du
- einzelne Module an/abwählen wie "Feiertage" oder Wahlpflichtmodule,
- die du nicht belegst.
- Im letzten Schritt wird dir der Link mit dem entsprechenden Token für
- den von dir erstellenten Kalenders angezeigt. Mit diesem Link kannst
- du den Kalender abonnieren oder herunterladen.
-
-
-
-
-
Wie kann ich den Kalender abonnieren?
+
{{$t('faqView.thirdQuestion')}}
-
+
- Erstelle deinen Kalender und kopiere den Link.
+ {{$t('faqView.thirdAnswer.google.first')}}
- Bei Google Kalender selbst hast du in der linken Seitenleiste
- eine Sektion namens "Weitere Kalender" . Dort klickst
- du das kleine Pfeil-Icon rechts neben dem Schritzug. Im
- daraufhin erscheinenden Menü gibt es einen Punkt
- "Über URL hinzufügen" , den du anklicken musst.
+ {{$t('faqView.thirdAnswer.google.second')}}
- Füge den kopierten Kalenderlink ein, klicke auf
- "Kalender hinzufügen" und du bist fertig.
+ {{$t('faqView.thirdAnswer.google.third')}}
-
- Unter Outlook 2010:
+
+ {{$t('faqView.thirdAnswer.microsoft_outlook.outlook_2010.title')}}
- Erstelle deinen Kalender und kopiere den Link.
- Klicke auf den Reiter “Start” .
-
- Dort befindest sich in der Sektion
- “Kalender verwalten” der Button
- “Kalender öffnen” , auf den du kicken musst.
-
-
- Es öffnet sich ein Kontextmenü, in dem du den Punkt
- “Aus dem Internet” anklickst.
-
-
- Im daraufhin erscheinenden Fenster kannst du den Link einfügen
- und mit “OK” bestätigen. Eventuell musst du das
- Abonnement in einem weiteren Schritt noch bestätigen. Dann
- bist du fertig.
-
+ {{$t('faqView.thirdAnswer.microsoft_outlook.outlook_2010.first')}}
+ {{$t('faqView.thirdAnswer.microsoft_outlook.outlook_2010.second')}}
+ {{$t('faqView.thirdAnswer.microsoft_outlook.outlook_2010.third')}}
+ {{$t('faqView.thirdAnswer.microsoft_outlook.outlook_2010.fourth')}}
+ {{$t('faqView.thirdAnswer.microsoft_outlook.outlook_2010.fifth')}}
- Unter Outlook 2007:
+ {{$t('faqView.thirdAnswer.microsoft_outlook.outlook_2007.title')}}
- Erstelle deinen Kalender und kopiere den Link.
-
- Unter “Extras” findest du die
- “Kontoeinstellungen” .
-
-
- Dort auf den Reiter “Internetkalender” klicken.
-
- Auf “Neu” klicken.
-
- Im sich öffnenden Fenster musst du den Link einfügen.
- Allerdings musst du “http://” durch
- “webcal://” austauschen.
-
-
- Spätestens beim nächsten Start sollte der Kalender
- aktualisiert werden und dir fortan zur Verfügung stehen.
-
+ {{$t('faqView.thirdAnswer.microsoft_outlook.outlook_2007.first')}}
+ {{$t('faqView.thirdAnswer.microsoft_outlook.outlook_2007.second')}}
+ {{$t('faqView.thirdAnswer.microsoft_outlook.outlook_2007.third')}}
+ {{$t('faqView.thirdAnswer.microsoft_outlook.outlook_2007.fourth')}}
+ {{$t('faqView.thirdAnswer.microsoft_outlook.outlook_2007.fifth')}}
+ {{$t('faqView.thirdAnswer.microsoft_outlook.outlook_2007.sixth')}}
-
+
- Erstelle deinen Kalender und kopiere den Link.
-
- Unter “Ablage” findest du den Punkt
- “Neues Kalenderabonnement” (oder unter Snow Leopard
- “Abonnieren” ).
-
-
- Im daraufhin erscheinenden Fenster musst du den kopierten Link
- einfügen und “abonnieren” klicken.
-
-
- Anschließend kannst du dem Kalender noch einen Namen geben und
- bestimmen, wie oft er aktualisiert werden soll. Falls du
- iCloud auf deinem iPhone o.ä. verwendest, empfehle ich dir bei
- “Ort” unbedingt “iCloud” zu wählen. So hast
- du deinen Stundenplan ohne weiteres zutun auch unterwegs immer
- parat.
-
+ {{$t('faqView.thirdAnswer.apple_osx.first')}}
+ {{$t('faqView.thirdAnswer.apple_osx.second')}}
+ {{$t('faqView.thirdAnswer.apple_osx.third')}}
+ {{$t('faqView.thirdAnswer.apple_osx.fourth')}}
-
+
- Erstelle deinen Kalender und kopiere den Link.
-
- Im Menü “Termine und Aufgaben” den Punkt
- “Kalender” wählen.
-
-
- Links siehst du die Kalenderübersicht. In diesem Bereich über
- die rechte Maustaste klicken und im darauf erscheinenden
- Kontextmenü “Neuer Kalender” anklicken.
-
-
- Du hast die Wahl zwischen “Auf meinem Computer” und
- “Im Netzwerk” . Bitte letzteres wählen und
- “Fortsetzen” klicken.
-
-
- Im folgenden Fenster lässt du das “Format” wie es ist
- (“iCalender” ).
-
-
- Unter "Adresse" den kopierten Kalenderlink einfügen.
-
-
- Anschließend kannst du noch einen Namen vergeben und weitere
- Einstellungen nach Belieben vornehmen.
-
+ {{ $t('faqView.thirdAnswer.thunderbird.one') }}
+ {{ $t('faqView.thirdAnswer.thunderbird.two') }}
+ {{ $t('faqView.thirdAnswer.thunderbird.three') }}
+ {{ $t('faqView.thirdAnswer.thunderbird.four') }}
+ {{ $t('faqView.thirdAnswer.thunderbird.five') }}
+ {{ $t('faqView.thirdAnswer.thunderbird.six') }}
+ {{ $t('faqView.thirdAnswer.thunderbird.seven') }}
-
-
- Der einfachste Weg unter iOS ist der iCloud-Sync (siehe
- Anleitung für OS X Kalender). Hier ist der andere Weg:
-
+
+ {{$t('faqView.thirdAnswer.iphone.description')}}
- Erstelle deinen Kalender und kopiere den Link.
- Gehe in die “Systemeinstellungen” .
- Dort wählst du “Mail, Kontakte, Kalender” .
- “Account hinzufügen” auswählen.
- Ganz unten tippst du auf “Andere” .
-
- Der letzte Punkt ist “Kalenderabo hinzufügen” . Dort
- drauftippen.
-
-
- Im daraufhin erscheinenden Textfeld fügst du den Kalenderlink
- ein und drückst oben auf “weiter” .
-
-
- Du kannst noch eine “Beschreibung” vergeben. Den Rest
- solltest du lassen, wie er ist.
-
-
- Nach kurzer Zeit taucht der abonnierte Kalender in der
- Kalender-App auf.
-
+ {{$t('faqView.thirdAnswer.iphone.one')}}
+ {{$t('faqView.thirdAnswer.iphone.two')}}
+ {{$t('faqView.thirdAnswer.iphone.three')}}
+ {{$t('faqView.thirdAnswer.iphone.four')}}
+ {{$t('faqView.thirdAnswer.iphone.five')}}
+ {{$t('faqView.thirdAnswer.iphone.six')}}
+ {{$t('faqView.thirdAnswer.iphone.seven')}}
+ {{$t('faqView.thirdAnswer.iphone.eight')}}
+ {{$t('faqView.thirdAnswer.iphone.nine')}}
-
- Unter Android ist die Synchronisierung mit dem Google Kalender
- die einfachste Variante. Schaue bitte in die Google Kalender
- Anleitung um zu erfahren, wie du den Kalender dort abonnierst.
-
+ {{$t('faqView.thirdAnswer.android.description')}}
-
- Am einfachsten ist unter Windows Phone die Synchronisierung über
- Outlook.com:
-
-
+ {{$t('faqView.thirdAnswer.windows_phone.description')}}
- Erstelle deinen Kalender und kopiere den Link.
- Bei Outlook.com anmelden und in den Kalender wechseln.
- Auf “Subscribe” klicken.
- “Subscribe to a public calendar” auswählen.
- Bei “Calendar URL” den Kalenderlink einfügen.
- Sonstige Einstellungen nach Belieben vornehmen.
- Auf “Subscribe to calendar” klicken.
-
- Das Windows-Phone-Gerät muss mit dem gleichen
- Outlook.com-Benutzerkonto angemeldet sein. Fortan sollte die
- Synchronisierung des Kalenders automatisch erfolgen.
-
+ {{$t('faqView.thirdAnswer.windows_phone.one')}}
+ {{$t('faqView.thirdAnswer.windows_phone.two')}}
+ {{$t('faqView.thirdAnswer.windows_phone.three')}}
+ {{$t('faqView.thirdAnswer.windows_phone.four')}}
+ {{$t('faqView.thirdAnswer.windows_phone.five')}}
+ {{$t('faqView.thirdAnswer.windows_phone.six')}}
+ {{$t('faqView.thirdAnswer.windows_phone.seven')}}
+ {{$t('faqView.thirdAnswer.windows_phone.eight')}}
-
- Kalender abonnieren? Ich will den downloaden !
-
-
- Das kannst du gern tun. Nachdem dein persönlicher Stundenplan erstellt
- wurde, hast du die Möglichkeit ihn herunterzuladen. Außerdem kannst du
- ihn jederzeit herunterladen, wenn du den generierten Link einfach in
- deinem Browser aufrufst.
- Bedenke hierbei, dass heruntergeladene Kalender bzw. Stundenpläne sich
- nicht aktualisieren werden. Das ist nur möglich, wenn du den Kalender
- abonnierst.
-
+
{{$t('faqView.fourthQuestion')}}
+
{{$t('faqView.fourthAnswer')}}
-
-
- Ich belege zusätzlich Module aus anderen Studiengängen und möchte
- diese auch in meinem Stundenplan haben.
-
-
- Nachdem du die Möglichkeit hattest, die für deinen Studiengang
- vorgesehenen Module aus dem Modulhandbuch auszuwählen, wirst du auf
- eine zweite Seite weitergeleitet. Dort hast du die Möglichkeit,
- weitere Module aus anderen Studiengängen in deinen Studienplan
- einzufügen.
-
+
{{$t('faqView.fifthQuestion')}}
+
{{$t('faqView.fifthAnswer')}}
-
-
Aktualisierung Probleme mit dem Kalender?
-
- Das liegt vermutlich daran, dass du ihn heruntergeladen statt
- abonniert hast. Automatisch aktualisieren können sich nur Kalender,
- die du abonniert hast. Eine Aktualisierung des Kalenders auf dem
- Server wird täglich um 4:00 Uhr durchgeführt. So wird gewährleistet
- das alle Veränderungen seitens der HTWK übernommen werden.
-
+
{{$t('faqView.sixthQuestion')}}
+
{{$t('faqView.sixthAnswer')}}
-
-
- Wie lange ist mein Stundenplan bzw. der Link dorthin gültig?
-
-
- Studenpläne sind erstmal nur für die ausgewählten Semester gültig. Da
- durch Wahlpflichtmodule oder deine Planung sich Veränderungen ergeben
- können.
-
+
{{$t('faqView.seventhQuestion')}}
+
{{$t('faqView.seventhAnswer')}}
-
-
Preis und Entwicklung?
-
- Die Kosten können durch das selbständiges Hosting vollständig
- ausgelagert werden. Die Entwicklung soll als aktives Git Projekt auch
- durch die Community verwaltet werden.
-
+
{{$t('faqView.eighthQuestion')}}
+
{{$t('faqView.eighthAnswer')}}
- Nicht gefunden, wonach du suchst?
- Kontakt aufnehmen
+ {{$t('faqView.notFound')}}
+ {{$t('faqView.contact')}}
diff --git a/frontend/src/components/LocaleSwitcher.vue b/frontend/src/components/LocaleSwitcher.vue
new file mode 100644
index 0000000..ea614e2
--- /dev/null
+++ b/frontend/src/components/LocaleSwitcher.vue
@@ -0,0 +1,49 @@
+
+
+
+
+
+
{{ displayIcon(slotProps.value) }}
+
{{ displayCountry(slotProps.value) }}
+
+
+ {{ slotProps.placeholder }}
+
+
+
+
+
{{ displayIcon(slotProps.option) }}
+
{{ displayCountry(slotProps.option) }}
+
+
+
+
diff --git a/frontend/src/components/MenuBar.vue b/frontend/src/components/MenuBar.vue
index a3b0ec7..d701634 100644
--- a/frontend/src/components/MenuBar.vue
+++ b/frontend/src/components/MenuBar.vue
@@ -1,43 +1,69 @@
-
diff --git a/frontend/src/components/ModuleInformation.vue b/frontend/src/components/ModuleInformation.vue
index e5789c4..893a917 100644
--- a/frontend/src/components/ModuleInformation.vue
+++ b/frontend/src/components/ModuleInformation.vue
@@ -1,10 +1,30 @@
@@ -12,24 +32,32 @@ const module = dialogRef.value.data.module as Module;
{{ module.name }}
- Course: {{ module.course }}
+ {{ $t("moduleInformation.course") }}: {{ module.course }}
- Person: {{ module.prof }}
+ {{ $t("moduleInformation.person") }}: {{ module.prof }}
- Semester: {{ module.semester }}
+ {{ $t("moduleInformation.semester") }}: {{ module.semester }}
-
-
-
-
-
-
-
+
+
+
+
+ {{ formatTimestamp(slotProps.data.start) }}
+
+
+
+
+ {{formatTimestamp( slotProps.data.end) }}
+
+
+
+
+
diff --git a/frontend/src/components/ModuleSelection.vue b/frontend/src/components/ModuleSelection.vue
index 7ddb751..650e908 100644
--- a/frontend/src/components/ModuleSelection.vue
+++ b/frontend/src/components/ModuleSelection.vue
@@ -64,7 +64,7 @@ function nextStep() {
:disabled="selectedModules.length < 1"
class="col-4 justify-content-center"
@click="nextStep()"
- >Next Step
+ >{{ $t("moduleSelection.nextStep") }}
@@ -72,16 +72,19 @@ function nextStep() {
-
Modules - {{ selectedModules.length }}
+
+ {{ $t("moduleSelection.modules") }} -
+ {{ selectedModules.length }}
+
@@ -89,7 +92,7 @@ function nextStep() {
- No Modules found for this course
+ {{ $t("moduleSelection.noModulesAvailable") }}
@@ -112,9 +115,9 @@ function nextStep() {
v-model="modulesWithSelection[slotProps.index].selected"
class="w-9rem"
off-icon="pi pi-times"
- off-label="Unselected"
+ :off-label="$t('moduleSelection.unselected')"
on-icon="pi pi-check"
- on-label="Selected"
+ :on-label="$t('moduleSelection.selected')"
/>
diff --git a/frontend/src/components/ModuleTemplateDialog.vue b/frontend/src/components/ModuleTemplateDialog.vue
index 9769c2d..4b7eb61 100644
--- a/frontend/src/components/ModuleTemplateDialog.vue
+++ b/frontend/src/components/ModuleTemplateDialog.vue
@@ -1,18 +1,30 @@
@@ -28,20 +40,30 @@ const placeholders = ref([
aria-label="Help"
@click="helpVisible = true"
/>
-
+
- Here you can rename your modules to your liking. This will be the name of
- the event in your calendar.
+ {{ t("moduleTemplateDialog.explanationOne") }}
- You can use the following placeholders in your module names:
+ {{ t("moduleTemplateDialog.tableDescription") }}
-
-
-
+
+
+
- Additionally, you can toggle notifications for each module. If you do so,
- you will be notified 15 minutes before the event starts.
+ {{ t("moduleTemplateDialog.explanationTwo") }}
diff --git a/frontend/src/components/RenameModules.vue b/frontend/src/components/RenameModules.vue
index 34d3ec7..d900abf 100644
--- a/frontend/src/components/RenameModules.vue
+++ b/frontend/src/components/RenameModules.vue
@@ -3,8 +3,11 @@ import moduleStore from "../store/moduleStore.ts";
import { createIndividualFeed } from "../api/createFeed.ts";
import router from "../router";
import tokenStore from "../store/tokenStore.ts";
-import { ref } from "vue";
+import { computed, ref } from "vue";
import ModuleTemplateDialog from "./ModuleTemplateDialog.vue";
+import { onlyWhitespace } from "../helpers/strings.ts";
+import { useI18n } from "vue-i18n";
+const { t } = useI18n({ useScope: "global" });
const store = moduleStore();
const tableData = ref(
@@ -16,10 +19,10 @@ const tableData = ref(
}),
);
-const columns = ref([
- { field: "Course", header: "Course" },
- { field: "Module", header: "Module" },
- { field: "Reminder", header: "Reminder" },
+const columns = computed(() => [
+ { field: "Course", header: t("moduleInformation.course") },
+ { field: "Module", header: t("moduleInformation.module") },
+ { field: "Reminder", header: t("renameModules.reminder") },
]);
async function finalStep() {
@@ -32,7 +35,7 @@ async function finalStep() {
-
Configure your selected Modules to your liking.
+ {{ $t("renameModules.subTitle") }}
@@ -44,7 +47,7 @@ async function finalStep() {
>
- Enable all notifications:
+ {{ $t("renameModules.enableAllNotifications") }}
- {{ data[field].userDefinedName }}
+ {{
+ onlyWhitespace(data[field].userDefinedName)
+ ? data[field].name
+ : data[field].userDefinedName
+ }}
-
- Next Step
+ {{ $t("renameModules.nextStep") }}
diff --git a/frontend/src/components/RoomOccupation.vue b/frontend/src/components/RoomOccupation.vue
index 883233a..207bc79 100644
--- a/frontend/src/components/RoomOccupation.vue
+++ b/frontend/src/components/RoomOccupation.vue
@@ -3,9 +3,11 @@ import FullCalendar from "@fullcalendar/vue3";
import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from "@fullcalendar/interaction";
import timeGridPlugin from "@fullcalendar/timegrid";
-import { computed, ref, Ref, watch } from "vue";
+import { computed, ComputedRef, ref, Ref, watch } from "vue";
import { CalendarOptions, EventInput } from "@fullcalendar/core";
import { fetchEventsByRoomAndDuration } from "../api/fetchRoom.ts";
+import { useI18n } from "vue-i18n";
+const { t } = useI18n({ useScope: "global" });
const props = defineProps({
room: {
@@ -53,84 +55,92 @@ async function getOccupation() {
calendar?.refetchEvents();
}
-const calendarOptions: CalendarOptions = {
- plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
- initialView: "week",
- dayHeaderFormat: { weekday: "short", omitCommas: true },
- slotDuration: "00:15:00",
- eventTimeFormat: {
- hour: "2-digit",
- minute: "2-digit",
- hour12: false,
- },
- views: {
- week: {
- type: "timeGrid",
- slotLabelFormat: {
- hour: "numeric",
- minute: "2-digit",
- omitZeroMinute: false,
- meridiem: false,
- hour12: false,
- },
- dateAlignment: "week",
- titleFormat: { month: "short", day: "numeric" },
- slotMinTime: "06:00:00",
- slotMaxTime: "22:00:00",
+import allLocales from "@fullcalendar/core/locales-all";
- duration: { days: 7 },
- firstDay: 1,
- allDaySlot: false,
- hiddenDays: [0],
+const calendarOptions: ComputedRef
= computed(() => {
+ return {
+ locales: allLocales,
+ locale: t("languageCode"),
+ plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
+ initialView: "week",
+ dayHeaderFormat: { weekday: "short", omitCommas: true },
+ slotDuration: "00:15:00",
+ eventTimeFormat: {
+ hour: "2-digit",
+ minute: "2-digit",
+ hour12: false,
},
- Day: {
- type: "timeGrid",
- slotLabelFormat: {
- hour: "numeric",
- minute: "2-digit",
- omitZeroMinute: false,
- meridiem: false,
- hour12: false,
+ views: {
+ week: {
+ description: "Wochenansicht",
+ type: "timeGrid",
+ slotLabelFormat: {
+ hour: "numeric",
+ minute: "2-digit",
+ omitZeroMinute: false,
+ meridiem: false,
+ hour12: false,
+ },
+ dateAlignment: "week",
+ titleFormat: { month: "short", day: "numeric" },
+ slotMinTime: "06:00:00",
+ slotMaxTime: "22:00:00",
+ duration: { days: 7 },
+ firstDay: 1,
+ allDaySlot: false,
+ hiddenDays: [0],
+ },
+ Day: {
+ type: "timeGrid",
+ slotLabelFormat: {
+ hour: "numeric",
+ minute: "2-digit",
+ omitZeroMinute: false,
+ meridiem: false,
+ hour12: false,
+ },
+ titleFormat: { month: "short", day: "numeric" },
+ slotMinTime: "06:00:00",
+ slotMaxTime: "22:00:00",
+ duration: { days: 1 },
+ allDaySlot: false,
+ hiddenDays: [0],
},
- titleFormat: { month: "short", day: "numeric" },
- slotMinTime: "06:00:00",
- slotMaxTime: "22:00:00",
- duration: { days: 1 },
- allDaySlot: false,
- hiddenDays: [0],
},
- },
- headerToolbar: {
- end: "prev,next today",
- center: "title",
- start: "week,Day",
- },
+ headerToolbar: {
+ end: "prev,next today",
+ center: "title",
+ start: "week,Day",
+ },
- datesSet: function (dateInfo) {
- const view = dateInfo.view;
- const offset = new Date().getTimezoneOffset();
- const startDate = new Date(view.activeStart.getTime() - offset * 60 * 1000);
- const endDate = new Date(view.activeEnd.getTime() - offset * 60 * 1000);
- currentDateFrom.value = startDate.toISOString().split("T")[0];
- currentDateTo.value = endDate.toISOString().split("T")[0];
- getOccupation();
- },
- events: function (_info, successCallback, failureCallback) {
- if (occupations.value.length === 0) {
- failureCallback(new Error("no events"));
- } else {
- successCallback(
- occupations.value.map((event) => {
- return {
- id: event.id.toString(),
- start: event.start,
- end: event.end,
- } as EventInput;
- }),
+ datesSet: function (dateInfo: any) {
+ const view = dateInfo.view;
+ const offset = new Date().getTimezoneOffset();
+ const startDate = new Date(
+ view.activeStart.getTime() - offset * 60 * 1000,
);
- }
- },
-};
+ const endDate = new Date(view.activeEnd.getTime() - offset * 60 * 1000);
+ currentDateFrom.value = startDate.toISOString().split("T")[0];
+ currentDateTo.value = endDate.toISOString().split("T")[0];
+ getOccupation();
+ },
+ events: function (_info: any, successCallback: any, failureCallback: any) {
+ if (occupations.value.length === 0) {
+ failureCallback(new Error("no events"));
+ } else {
+ successCallback(
+ occupations.value.map((event) => {
+ return {
+ id: event.id.toString(),
+ start: event.start,
+ end: event.end,
+ } as EventInput;
+ }),
+ );
+ }
+ },
+ };
+});
diff --git a/frontend/src/components/editCalendar/EditAdditionalModules.vue b/frontend/src/components/editCalendar/EditAdditionalModules.vue
index db3f22d..fb968fc 100644
--- a/frontend/src/components/editCalendar/EditAdditionalModules.vue
+++ b/frontend/src/components/editCalendar/EditAdditionalModules.vue
@@ -91,6 +91,7 @@ function selectChange() {
class="custom-multiselect"
filter
placeholder="Select additional modules"
+ :auto-filter-focus="true"
@change="selectChange()"
@selectall-change="onSelectAllChange($event)"
>
diff --git a/frontend/src/components/editCalendar/EditModules.vue b/frontend/src/components/editCalendar/EditModules.vue
index c079cce..7852657 100644
--- a/frontend/src/components/editCalendar/EditModules.vue
+++ b/frontend/src/components/editCalendar/EditModules.vue
@@ -7,6 +7,7 @@ import { saveIndividualFeed } from "../../api/createFeed";
import tokenStore from "../../store/tokenStore";
import router from "../../router";
import ModuleTemplateDialog from "../ModuleTemplateDialog.vue";
+import { onlyWhitespace } from "../../helpers/strings.ts";
const store = moduleStore();
const tableData = computed(() =>
@@ -90,7 +91,11 @@ async function finalStep() {
- {{ data[field].userDefinedName }}
+ {{
+ onlyWhitespace(data[field].userDefinedName)
+ ? data[field].name
+ : data[field].userDefinedName
+ }}
+
+ {{ data[field] }}
+
diff --git a/frontend/src/helpers/strings.ts b/frontend/src/helpers/strings.ts
new file mode 100644
index 0000000..15ac4c8
--- /dev/null
+++ b/frontend/src/helpers/strings.ts
@@ -0,0 +1,8 @@
+/**
+ * Returns false only if the input argument is a string which contains characters that are not considered a whitespace
+ * @param str - The string to check for whitespace
+ * @returns boolean - true if the string contains only whitespace, false otherwise
+ */
+export function onlyWhitespace(str: string): boolean {
+ return !str || str.length === 0 || /^\s*$/.test(str);
+}
diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts
new file mode 100644
index 0000000..3414b0f
--- /dev/null
+++ b/frontend/src/i18n/index.ts
@@ -0,0 +1,46 @@
+import { createI18n } from "vue-i18n";
+import en from "./translations/en.json";
+import de from "./translations/de.json";
+import localeStore from "../store/localeStore.ts";
+
+export const supportedLocales = {
+ en: { name: "English" },
+ de: { name: "Deutsch" },
+};
+// Private instance of VueI18n object
+let _i18n: any;
+// Initializer
+function setup() {
+ _i18n = createI18n({
+ legacy: false,
+ locale: localeStore().locale,
+ fallbackLocale: "en",
+ messages: {
+ en,
+ de,
+ },
+ });
+ return _i18n;
+}
+
+// Sets the active locale.
+function setLocale(newLocale: any) {
+ _i18n.global.locale = newLocale;
+ setDocumentAttributesFor(newLocale);
+}
+
+function setDocumentAttributesFor(locale: any) {
+ const htmlElement = document.querySelector("html");
+
+ htmlElement?.setAttribute("lang", locale);
+}
+
+// Public interface
+export default {
+ // Expose the VueI18n instance via a getter
+ get vueI18n() {
+ return _i18n;
+ },
+ setup,
+ setLocale,
+};
diff --git a/frontend/src/i18n/messages.ts b/frontend/src/i18n/messages.ts
new file mode 100644
index 0000000..b21be5e
--- /dev/null
+++ b/frontend/src/i18n/messages.ts
@@ -0,0 +1,18 @@
+export default {
+ en: {
+ createCalendar: "Create Calendar",
+ editCalendar: "Edit Calendar",
+ roomFinder: "Room Finder",
+ faq: "FAQ",
+ imprint: "Imprint",
+ privacy: "Privacy Policy",
+ },
+ de: {
+ createCalendar: "Kalender erstellen",
+ editCalendar: "Kalender bearbeiten",
+ roomFinder: "Raumfinder",
+ faq: "FAQ",
+ imprint: "Impressum",
+ privacy: "Datenschutz",
+ },
+};
diff --git a/frontend/src/i18n/translations/de.json b/frontend/src/i18n/translations/de.json
new file mode 100644
index 0000000..4dd8efa
--- /dev/null
+++ b/frontend/src/i18n/translations/de.json
@@ -0,0 +1,184 @@
+{
+ "languageCode": "de",
+ "createCalendar": "Kalender erstellen",
+ "editCalendar": "Kalender bearbeiten",
+ "roomFinder": "Raumfinder",
+ "faq": "FAQ",
+ "imprint": "Impressum",
+ "privacy": "Datenschutz",
+ "english": "Englisch",
+ "german": "Deutsch",
+ "courseSelection": {
+ "headline": "Willkommen beim HTWKalender",
+ "winterSemester": "Wintersemester",
+ "summerSemester": "Sommersemester",
+ "subTitle": "Bitte wähle eine Gruppe und das Semester aus.",
+ "courseDropDown": "Gruppe",
+ "noCoursesAvailable": "Keine Gruppen verfügbar",
+ "semesterDropDown": "Semester"
+ },
+ "roomFinderPage": {
+ "headline": "Raumfinder",
+ "detail": "Bitte wähle einen Raum aus, um die Belegung einzusehen",
+ "dropDownSelect": "Bitte wähle einen Raum aus",
+ "noRoomsAvailable": "Keine Räume verfügbar"
+ },
+ "moduleSelection": {
+ "nextStep": "Weiter",
+ "selectAll": "Alle anwählen",
+ "deselectAll": "Alle abwählen",
+ "selected": "angewählt",
+ "unselected": "abgewählt",
+ "noModulesAvailable": "Keine Module verfügbar",
+ "modules": "Module"
+ },
+ "moduleInformation": {
+ "course": "Kurs",
+ "person": "Dozent",
+ "semester": "Semester",
+ "module": "Modul",
+ "day": "Tag",
+ "start": "Begin",
+ "end": "Ende",
+ "room": "Raum",
+ "type": "Art",
+ "week": "Woche"
+ },
+ "editCalendarView": {
+ "error": "Fehler",
+ "invalidToken": "Ungültiger Token",
+ "headline": "Bearbeite deinen HTWKalender",
+ "subTitle": "Füge deinen Link oder Token ein um den Kalender zu bearbeiten",
+ "loadCalendar": "Kalender laden"
+ },
+ "additionalModules": {
+ "subTitle": "Füge weitere Module hinzu die nicht in deinem Studiengang enthalten sind.",
+ "dropDown": "Wähle weitere Module aus",
+ "module": "Modul",
+ "modules": "Module",
+ "dropDownFooterSelected": "ausgewählt",
+ "nextStep": "Weiter"
+ },
+ "renameModules": {
+ "reminder": "Erinnerung",
+ "enableAllNotifications": "Alle Benachrichtigungen aktivieren",
+ "subTitle": "Konfigurieren Sie die ausgewählten Module nach Ihren Wünschen.",
+ "nextStep": "Weiter"
+ },
+ "moduleTemplateDialog": {
+ "explanationOne": "Hier können Module nach Wunsch umbenannt werden, welche dann als Anzeigename im Kalender dargestellt werden.",
+ "explanationTwo": "Zusätzlich können Sie Benachrichtigungen für jedes Modul einschalten, damit für jeden Termin 15 Minuten vor Beginn einen Erinnerung geschalten wird.",
+ "tableDescription": "Im Modulnamen stehen folgende Platzhalter zur Verfügung:",
+ "placeholder": "Platzhalter",
+ "description": "Beschreibung",
+ "examples": "Beispiele",
+ "moduleConfiguration": "Modulkonfiguration",
+ "mandatory": "Verpflichtend",
+ "optional": "Optional",
+ "lecture": "Vorlesung",
+ "seminar": "Seminar",
+ "exam": "Prüfung/Praktikum",
+ "eventTyp": "Ereignistyp"
+ },
+ "calendarLink": {
+ "copyToastNotification": "Link in Zwischenablage kopiert",
+ "copyToastSummary": "Information",
+ "copyToastError": "Fehler",
+ "copyToastErrorDetail": "Link konnte nicht in Zwischenablage kopiert werden",
+ "copyToClipboard": "Link kopieren",
+ "toGoogleCalendar": "Google Kalender",
+ "toMicrosoftCalendar": "Microsoft Kalender"
+ },
+ "faqView": {
+ "headline": "Fragen und Antworten",
+ "firstQuestion": "Wie funktioniert das Kalender erstellen mit dem HTWKalender?",
+ "firstAnswer": "Die Webseite ermöglicht es deinen HTWK-Stundenplan in eines deiner bevorzugten Kalender-Verwaltungs-Programme (Outlook, Google Kalender, etc.) einzubinden. ",
+ "secondQuestion": "Wie genau funktioniert das alles?",
+ "secondAnswer": "Du wählst deinen Studiengang und das gewünschte Semester aus, danach kannst du aus den dazugehörigen Modulen wählen. Dabei kannst du einzelne Module an/abwählen wie \"Feiertage\" oder Wahlpflichtmodule, die du nicht belegst. Im letzten Schritt wird dir der Link mit dem entsprechenden Token für den von dir erstellenten Kalenders angezeigt. Mit diesem Link kannst du den Kalender abonnieren oder herunterladen. ",
+ "thirdQuestion": "Wie kann ich den Kalender abonnieren?",
+ "thirdAnswer": {
+ "tabTitle": "Google Calendar",
+ "google": {
+ "first": "Erstelle deinen Kalender und kopiere den Link.",
+ "second": "Bei Google Kalender selbst hast du in der linken Seitenleiste eine Sektion namens \"Weitere Kalender\". Dort klickst du das kleine Pfeil-Icon rechts neben dem Schritzug. Im daraufhin erscheinenden Menü gibt es einen Punkt \"Über URL hinzufügen\", den du anklicken musst. ",
+ "third": "Füge den kopierten Kalenderlink ein, klicke auf \"Kalender hinzufügen\" und du bist fertig. "
+ },
+ "microsoft_outlook": {
+ "title": "Mircosoft Outlook",
+ "outlook_2010": {
+ "title": "Unter Outlook 2010:",
+ "first": "Erstelle deinen Kalender und kopiere den Link.",
+ "second": "Klicke auf den Reiter “Start”.",
+ "third": "Dort befindest sich in der Sektion “Kalender verwalten” der Button “Kalender öffnen”, auf den du kicken musst.",
+ "fourth": "Es öffnet sich ein Kontextmenü, in dem du den Punkt “Aus dem Internet” anklickst.",
+ "fifth": "Im daraufhin erscheinenden Fenster kannst du den Link einfügen und mit “OK” bestätigen. Eventuell musst du das Abonnement in einem weiteren Schritt noch bestätigen. Dann bist du fertig."
+ },
+ "outlook_2007": {
+ "title": "Unter Outlook 2007:",
+ "first": "Erstelle deinen Kalender und kopiere den Link.",
+ "second": "Unter “Extras” findest du die “Kontoeinstellungen”.",
+ "third": "Dort auf den Reiter “Internetkalender” klicken.",
+ "fourth": "Auf “Neu” klicken.",
+ "fifth": "Im sich öffnenden Fenster musst du den Link einfügen. Allerdings musst du “http://” durch “webcal://” austauschen.",
+ "sixth": "Spätestens beim nächsten Start sollte der Kalender aktualisiert werden und dir fortan zur Verfügung stehen."
+ }
+ },
+ "apple_osx": {
+ "title": "Calendar (OS X)",
+ "first": "Erstelle deinen Kalender und kopiere den Link.",
+ "second": "Unter “Ablage” findest du den Punkt “Neues Kalenderabonnement” (oder unter Snow Leopard “Abonnieren”).",
+ "third": "Im daraufhin erscheinenden Fenster musst du den kopierten Link einfügen und “abonnieren” klicken.",
+ "fourth": "Anschließend kannst du dem Kalender noch einen Namen geben und bestimmen, wie oft er aktualisiert werden soll. Falls du iCloud auf deinem iPhone o.ä. verwendest, empfehle ich dir bei “Ort” unbedingt “iCloud” zu wählen. So hast du deinen Stundenplan ohne weiteres zutun auch unterwegs immer parat."
+ },
+ "thunderbird": {
+ "title": "Thunderbird",
+ "one" : "Erstelle deinen Kalender und kopiere den Link.",
+ "two" : "Im Menü “Termine und Aufgaben” den Punkt “Kalender” wählen. ",
+ "three" : "Links siehst du die Kalenderübersicht. In diesem Bereich über die rechte Maustaste klicken und im darauf erscheinenden Kontextmenü “Neuer Kalender” anklicken.",
+ "four" : "Du hast die Wahl zwischen “Auf meinem Computer” und “Im Netzwerk”. Bitte letzteres wählen und “Fortsetzen” klicken.",
+ "five" : "Im folgenden Fenster lässt du das “Format” wie es ist (“iCalender”).",
+ "six" : "Unter \"Adresse\" den kopierten Kalenderlink einfügen.",
+ "seven" : "Anschließend kannst du noch einen Namen vergeben und weitere Einstellungen nach Belieben vornehmen."
+ },
+ "iphone": {
+ "title": "iPhone",
+ "description": "Der einfachste Weg unter iOS ist der iCloud-Sync (siehe Anleitung für OS X Kalender). Hier ist der andere Weg: ",
+ "one": "Erstelle deinen Kalender und kopiere den Link.",
+ "two": "Gehe in die “Systemeinstellungen”.",
+ "three": "Dort wählst du “Mail, Kontakte, Kalender”.",
+ "four": "“Account hinzufügen” auswählen.",
+ "five": "Ganz unten tippst du auf “Andere”.",
+ "six": "Der letzte Punkt ist “Kalenderabo hinzufügen”. Dort drauftippen.",
+ "seven": "Im daraufhin erscheinenden Textfeld fügst du den Kalenderlink ein und drückst oben auf “weiter”.",
+ "eight": "Du kannst noch eine “Beschreibung” vergeben. Den Rest solltest du lassen, wie er ist.",
+ "nine": "Nach kurzer Zeit taucht der abonnierte Kalender in der Kalender-App auf."
+ },
+ "android": {
+ "description": "Unter Android ist die Synchronisierung mit dem Google Kalender die einfachste Variante. Schaue bitte in die Google Kalender Anleitung um zu erfahren, wie du den Kalender dort abonnierst. "
+ },
+ "windows_phone": {
+ "description": "Am einfachsten ist unter Windows Phone die Synchronisierung über Outlook.com:",
+ "one": "Erstelle deinen Kalender und kopiere den Link.",
+ "two": "Bei Outlook.com anmelden und in den Kalender wechseln.",
+ "three": "Auf “Subscribe” klicken.",
+ "four": "“Subscribe to a public calendar” auswählen.",
+ "five": "Bei “Calendar URL” den Kalenderlink einfügen.",
+ "six": "Sonstige Einstellungen nach Belieben vornehmen.",
+ "seven": "Auf “Subscribe to calendar” klicken.",
+ "eight": "Das Windows-Phone-Gerät muss mit dem gleichen Outlook.com-Benutzerkonto angemeldet sein. Fortan sollte die Synchronisierung des Kalenders automatisch erfolgen."
+ }
+ },
+ "fourthQuestion": "Kalender abonnieren? Ich will den downloaden!",
+ "fourthAnswer": "Das kannst du gern tun. Nachdem dein persönlicher Stundenplan erstellt wurde, hast du die Möglichkeit ihn herunterzuladen. Außerdem kannst du ihn jederzeit herunterladen, wenn du den generierten Link einfach in deinem Browser aufrufst. Bedenke hierbei, dass heruntergeladene Kalender bzw. Stundenpläne sich nicht aktualisieren werden. Das ist nur möglich, wenn du den Kalender abonnierst. ",
+ "fifthQuestion": "Ich belege zusätzlich Module aus anderen Studiengängen und möchte diese auch in meinem Stundenplan haben. ",
+ "fifthAnswer": "Nachdem du die Möglichkeit hattest, die für deinen Studiengang vorgesehenen Module aus dem Modulhandbuch auszuwählen, wirst du auf eine zweite Seite weitergeleitet. Dort hast du die Möglichkeit, weitere Module aus anderen Studiengängen in deinen Studienplan einzufügen. ",
+ "sixthQuestion": "Aktualisierungs Probleme mit dem Kalender?",
+ "sixthAnswer": " Das liegt vermutlich daran, dass du ihn heruntergeladen statt abonniert hast. Automatisch aktualisieren können sich nur Kalender, die du abonniert hast. Eine Aktualisierung des Kalenders auf dem Server wird alle 3h ab Mitternacht durchgeführt. So wird gewährleistet das alle Veränderungen seitens der HTWK übernommen werden.",
+ "seventhQuestion": "Wie lange ist mein Stundenplan bzw. der Link dorthin gültig?",
+ "seventhAnswer": "Studenpläne sind erstmal nur für die ausgewählten Semester gültig. Da durch Wahlpflichtmodule oder deine Planung sich Veränderungen ergeben können.",
+ "eighthQuestion": "Preis und Entwicklung?",
+ "eighthAnswer": "Die Kosten können durch das selbständiges Hosting vollständig ausgelagert werden. Die Entwicklung soll als aktives Git Projekt auch durch die Community verwaltet werden.",
+ "notFound": "Nicht gefunden, wonach du suchst?",
+ "contact": "Kontakt aufnehmen"
+ }
+}
diff --git a/frontend/src/i18n/translations/en.json b/frontend/src/i18n/translations/en.json
new file mode 100644
index 0000000..641d5b9
--- /dev/null
+++ b/frontend/src/i18n/translations/en.json
@@ -0,0 +1,184 @@
+{
+ "languageCode": "en",
+ "createCalendar": "create calendar",
+ "editCalendar": "edit calendar",
+ "roomFinder": "room finder",
+ "faq": "faq",
+ "imprint": "imprint",
+ "privacy": "privacy",
+ "english": "English",
+ "german": "German",
+ "courseSelection": {
+ "headline": "Welcome to HTWKalender",
+ "winterSemester": "winter semester",
+ "summerSemester": "summer semester",
+ "subTitle": "Please select a course and semester",
+ "courseDropDown": "Please select a course",
+ "noCoursesAvailable": "No courses listed",
+ "semesterDropDown": "Please select a semester"
+ },
+ "roomFinderPage": {
+ "headline": "room finder",
+ "detail": "Please select a room to view the occupancy",
+ "dropDownSelect": "Please select a room",
+ "noRoomsAvailable": "No rooms listed"
+ },
+ "moduleSelection": {
+ "nextStep": "next step",
+ "selectAll": "select all",
+ "deselectAll": "deselect all",
+ "selected": "selected",
+ "unselected": "unselected",
+ "noModulesAvailable": "no modules available",
+ "modules": "modules"
+ },
+ "moduleInformation": {
+ "course": "course",
+ "person": "lecturer",
+ "semester": "semester",
+ "module": "module",
+ "day": "day",
+ "start": "start",
+ "end": "end",
+ "room": "room",
+ "type": "type",
+ "week": "week"
+ },
+ "editCalendarView": {
+ "error": "error",
+ "invalidToken": "invalid token",
+ "headline": "edit your HTWKalender",
+ "subTitle": "please enter your link or calendar token",
+ "loadCalendar": "load calendar"
+ },
+ "additionalModules": {
+ "subTitle": "Select additional Modules that are not listed in the regular semester for your Course",
+ "dropDown": "Select additional modules",
+ "module": "module",
+ "modules": "modules",
+ "dropDownFooterSelected": "selected",
+ "nextStep": "next step"
+ },
+ "renameModules": {
+ "reminder": "reminder",
+ "enableAllNotifications": "enable all notifications",
+ "subTitle": "Configure your selected Modules to your liking.",
+ "nextStep": "next step"
+ },
+ "moduleTemplateDialog": {
+ "explanationOne": "Here you can rename your modules to your liking. This will be the name of the event in your calendar.",
+ "explanationTwo": "Additionally, you can toggle notifications for each module. If you do so, you will be notified 15 minutes before the event starts.",
+ "tableDescription": "You can use the following placeholders in your module names:",
+ "placeholder": "placeholder",
+ "description": "description",
+ "examples": "examples",
+ "moduleConfiguration": "module configuration",
+ "mandatory": "mandatory",
+ "optional": "optional",
+ "lecture": "lecture",
+ "seminar": "seminar",
+ "exam": "exam/internship project",
+ "eventTyp": "event type"
+ },
+ "calendarLink": {
+ "copyToastNotification": "Link copied to clipboard",
+ "copyToastSummary": "information",
+ "copyToastError": "error",
+ "copyToastErrorDetail": "could not copy link to clipboard",
+ "copyToClipboard": "copy to clipboard",
+ "toGoogleCalendar": "to Google Calendar",
+ "toMicrosoftCalendar": "to Microsoft Calendar"
+ },
+ "faqView": {
+ "headline": "faq",
+ "firstQuestion": "How does calendar creation work with HTWKalender?",
+ "firstAnswer": "The website allows you to integrate your HTWK timetable into one of your preferred calendar management programs (Outlook, Google Calendar, etc.).",
+ "secondQuestion": "How does it all work exactly?",
+ "secondAnswer": "You choose your course of study and the desired semester; then, you can select modules associated with it. You can individually select or deselect modules such as 'Holidays' or elective modules you are not taking. In the last step, the link with the corresponding token for your created calendar is displayed. With this link, you can subscribe to or download the calendar.",
+ "thirdQuestion": "How can I subscribe to the calendar?",
+ "thirdAnswer": {
+ "tabTitle": "Google Calendar",
+ "google": {
+ "first": "Create your calendar and copy the link.",
+ "second": "In Google Calendar itself, in the left sidebar, there is a section called 'Other calendars.' There, click the small arrow icon to the right of the text. In the menu that appears, there is an option 'Add by URL' that you need to click.",
+ "third": "Paste the copied calendar link, click 'Add Calendar,' and you're done."
+ },
+ "microsoft_outlook": {
+ "title": "Microsoft Outlook",
+ "outlook_2010": {
+ "title": "Under Outlook 2010:",
+ "first": "Create your calendar and copy the link.",
+ "second": "Click on the 'Home' tab.",
+ "third": "In the 'Manage Calendars' section, there is a 'Open Calendar' button that you need to click.",
+ "fourth": "A context menu opens, where you click on 'From Internet'.",
+ "fifth": "In the window that appears, you can paste the link and confirm with 'OK'. You may need to confirm the subscription in an additional step. Then you're done."
+ },
+ "outlook_2007": {
+ "title": "Under Outlook 2007:",
+ "first": "Create your calendar and copy the link.",
+ "second": "Under 'Tools', you will find 'Account Settings'.",
+ "third": "There, click on the 'Internet Calendars' tab.",
+ "fourth": "Click on 'New.'",
+ "fifth": "In the opening window, you must paste the link. However, you need to replace 'http://' with 'webcal://'.",
+ "sixth": "At the latest, the calendar should be updated on the next start and be available to you from then on."
+ }
+ },
+ "apple_osx": {
+ "title": "Calendar (OS X)",
+ "first": "Create your calendar and copy the link.",
+ "second": "Under 'File', you will find the option 'New Calendar Subscription' (or 'Subscribe' under Snow Leopard).",
+ "third": "In the window that appears, paste the copied link and click 'Subscribe.'",
+ "fourth": "You can then give the calendar a name and determine how often it should be updated. If you use iCloud on your iPhone or similar, I recommend selecting 'iCloud' under 'Location.' This way, your timetable is always available on the go without further action."
+ },
+ "thunderbird": {
+ "title": "Thunderbird",
+ "one": "Create your calendar and copy the link.",
+ "two": "In the 'Events and Tasks' menu, select 'Calendar.'",
+ "three": "On the left, you will see the calendar overview. Click with the right mouse button in this area, and in the context menu that appears, click 'New Calendar.'",
+ "four": "You have the choice between 'On My Computer' and 'On the Network.' Please choose the latter and click 'Continue.'",
+ "five": "In the following window, leave the 'Format' as it is ('iCalendar').",
+ "six": "Paste the copied calendar link under 'Location'.",
+ "seven": "You can then give it a name and make additional settings as desired."
+ },
+ "iphone": {
+ "title": "iPhone",
+ "description": "The easiest way on iOS is through iCloud sync (see instructions for OS X Calendar). Here is another way:",
+ "one": "Create your calendar and copy the link.",
+ "two": "Go to 'Settings'.",
+ "three": "There, select 'Mail, Contacts, Calendars'.",
+ "four": "Select 'Add Account.'",
+ "five": "At the bottom, tap 'Other.'",
+ "six": "The last option is 'Add Subscribed Calendar.' Tap on it.",
+ "seven": "In the text field that appears, paste the calendar link and press 'Next' at the top.",
+ "eight": "You can give it a 'Description.' Leave the rest as it is.",
+ "nine": "After a short time, the subscribed calendar will appear in the Calendar app."
+ },
+ "android": {
+ "description": "Under Android, synchronizing with Google Calendar is the easiest option. Please refer to the Google Calendar instructions to learn how to subscribe to the calendar there."
+ },
+ "windows_phone": {
+ "description": "The easiest way on Windows Phone is synchronization via Outlook.com:",
+ "one": "Create your calendar and copy the link.",
+ "two": "Log in to Outlook.com and switch to the calendar.",
+ "three": "Click on 'Subscribe.'",
+ "four": "Select 'Subscribe to a public calendar.'",
+ "five": "Paste the calendar link under 'Calendar URL.'",
+ "six": "Make other settings as desired.",
+ "seven": "Click on 'Subscribe to calendar.'",
+ "eight": "The Windows Phone device must be logged in with the same Outlook.com user account. From now on, calendar synchronization should occur automatically."
+ }
+ },
+ "fourthQuestion": "Subscribe to the calendar? I want to download it!",
+ "fourthAnswer": "You can certainly do that. After your personal timetable has been created, you have the option to download it. Additionally, you can download it anytime by simply opening the generated link in your browser. Keep in mind that downloaded calendars or timetables will not update. This is only possible if you subscribe to the calendar.",
+ "fifthQuestion": "I am taking additional modules from other courses of study and want them in my timetable.",
+ "fifthAnswer": "After you have had the opportunity to select the modules provided for your course of study from the module handbook, you will be redirected to a second page. There, you have the option to add additional modules from other courses of study to your study plan.",
+ "sixthQuestion": "Updating issues with the calendar?",
+ "sixthAnswer": "This is probably because you downloaded it instead of subscribing. Only calendars that you have subscribed to can update automatically. A server update of the calendar occurs every 3 hours starting from midnight. This ensures that all changes from HTWK are incorporated.",
+ "seventhQuestion": "How long is my timetable or the link to it valid?",
+ "seventhAnswer": "Timetables are initially only valid for the selected semesters, as changes can occur due to elective modules or your planning.",
+ "eighthQuestion": "Cost and development?",
+ "eighthAnswer": "Costs can be completely outsourced through self-hosting. Development is intended to be managed by the community as an active Git project.",
+ "notFound": "Not finding what you're looking for?",
+ "contact": "Get in touch"
+ }
+}
diff --git a/frontend/src/main.ts b/frontend/src/main.ts
index 3c2c61f..48d5974 100644
--- a/frontend/src/main.ts
+++ b/frontend/src/main.ts
@@ -4,6 +4,7 @@ import App from "./App.vue";
import PrimeVue from "primevue/config";
import Button from "primevue/button";
import Dropdown from "primevue/dropdown";
+import Menu from "primevue/menu";
import Menubar from "primevue/menubar";
import InputText from "primevue/inputtext";
import InputSwitch from "primevue/inputswitch";
@@ -30,6 +31,7 @@ import Column from "primevue/column";
import DynamicDialog from "primevue/dynamicdialog";
import DialogService from "primevue/dialogservice";
import ProgressSpinner from "primevue/progressspinner";
+import i18n from "./i18n";
const app = createApp(App);
const pinia = createPinia();
@@ -39,7 +41,10 @@ app.use(router);
app.use(ToastService);
app.use(pinia);
app.use(DialogService);
+i18n.setup();
+app.use(i18n.vueI18n);
app.component("Button", Button);
+app.component("Menu", Menu);
app.component("Menubar", Menubar);
app.component("Dialog", Dialog);
app.component("Dropdown", Dropdown);
diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts
index 95c4864..92cabe8 100644
--- a/frontend/src/router/index.ts
+++ b/frontend/src/router/index.ts
@@ -1,15 +1,16 @@
import { createRouter, createWebHistory } from "vue-router";
import Faq from "../components/FaqPage.vue";
-import CourseSelection from "../components/CourseSelection.vue";
-import AdditionalModules from "../components/AdditionalModules.vue";
+import AdditionalModules from "../view/AdditionalModules.vue";
import CalendarLink from "../components/CalendarLink.vue";
-import Imprint from "../components/ImprintPage.vue";
-import PrivacyPolicy from "../components/PrivacyPolicy.vue";
+import Imprint from "../view/ImprintPage.vue";
+import PrivacyPolicy from "../view/PrivacyPolicy.vue";
import RenameModules from "../components/RenameModules.vue";
-import RoomFinder from "../components/RoomFinder.vue";
-import EditCalendarView from "../view/editCalendarView.vue";
+import RoomFinder from "../view/RoomFinder.vue";
+import EditCalendarView from "../view/EditCalendarView.vue";
import EditAdditionalModules from "../components/editCalendar/EditAdditionalModules.vue";
import EditModules from "../components/editCalendar/EditModules.vue";
+import CourseSelection from "../components/CourseSelection.vue";
+import i18n from "../i18n";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@@ -69,11 +70,17 @@ const router = createRouter({
name: "rename-modules",
component: RenameModules,
},
- {
- path: "/:catchAll(.*)",
- redirect: "/",
- },
],
});
+router.beforeEach(async (to, from) => {
+ const newLocale = to.params.locale;
+ const prevLocale = from.params.locale;
+ // If the locale hasn't changed, do nothing
+ if (newLocale === prevLocale) {
+ return;
+ }
+ i18n.setLocale(newLocale);
+});
+
export default router;
diff --git a/frontend/src/store/localeStore.ts b/frontend/src/store/localeStore.ts
new file mode 100644
index 0000000..121bfdb
--- /dev/null
+++ b/frontend/src/store/localeStore.ts
@@ -0,0 +1,17 @@
+import { defineStore } from "pinia";
+import { useLocalStorage } from "@vueuse/core";
+
+const localeStore = defineStore("localeStore", {
+ state: () => {
+ return {
+ locale: useLocalStorage("locale", "en"), //useLocalStorage takes in a key of 'count' and default value of 0
+ };
+ },
+ actions: {
+ setLocale(locale: string) {
+ this.locale = locale;
+ },
+ },
+});
+
+export default localeStore;
diff --git a/frontend/src/components/AdditionalModules.vue b/frontend/src/view/AdditionalModules.vue
similarity index 84%
rename from frontend/src/components/AdditionalModules.vue
rename to frontend/src/view/AdditionalModules.vue
index bf08122..f5569c7 100644
--- a/frontend/src/components/AdditionalModules.vue
+++ b/frontend/src/view/AdditionalModules.vue
@@ -1,15 +1,18 @@
@@ -117,8 +128,7 @@ function selectChange(event : MultiSelectChangeEvent) {
- Select additional Modules that are not listed in the regular semester
- for your Course
+ {{ $t("additionalModules.subTitle") }}
@@ -139,7 +149,7 @@ function selectChange(event : MultiSelectChangeEvent) {
:striped-rows="true"
class="w-10"
>
-
+
@@ -202,7 +212,7 @@ function selectChange(event : MultiSelectChangeEvent) {
@click.stop="showInfo(slotProps.data)"
>
-
+
@@ -226,9 +236,14 @@ function selectChange(event : MultiSelectChangeEvent) {
:virtual-scroller-options="{ itemSize: 70 }"
class="custom-multiselect"
filter
+ :placeholder="$t('additionalModules.dropDown')"
+ :auto-filter-focus="true"
+ :show-toggle-all="false"
+ @change="selectChange()"
placeholder="Select additional modules"
@change="selectChange($event)"
@selectall-change="onSelectAllChange($event)"
+ :selectedItemsLabel="itemsLabelWithNumber(selectedModules)"
>
@@ -254,17 +269,17 @@ function selectChange(event : MultiSelectChangeEvent) {
{{ selectedModules ? selectedModules.length : 0 }}
- item{{
- (selectedModules ? selectedModules.length : 0) > 1 ? "s" : ""
- }}
- selected.
+ {{ itemsLabel(selectedModules) }}
+ {{ $t("additionalModules.dropDownFooterSelected") }}
-->
- Next Step
+ {{
+ $t("additionalModules.nextStep")
+ }}
diff --git a/frontend/src/view/editCalendarView.vue b/frontend/src/view/EditCalendarView.vue
similarity index 74%
rename from frontend/src/view/editCalendarView.vue
rename to frontend/src/view/EditCalendarView.vue
index cd848d8..e0fa2dc 100644
--- a/frontend/src/view/editCalendarView.vue
+++ b/frontend/src/view/EditCalendarView.vue
@@ -6,6 +6,9 @@ import { getCalender } from "../api/loadCalendar";
import router from "../router";
import tokenStore from "../store/tokenStore";
import { useToast } from "primevue/usetoast";
+import { useI18n } from "vue-i18n";
+const { t } = useI18n({ useScope: "global" });
+
const toast = useToast();
const token: Ref
= ref("");
@@ -13,7 +16,7 @@ const modules: Ref = ref(moduleStore().modules);
function extractToken(token: string): string {
const tokenRegex = /^[a-z0-9]{15}$/;
- const tokenUriRegex = /(?:\?|&)token=([a-z0-9]{15})(?:&|$)/;
+ const tokenUriRegex = /[?&]token=([a-z0-9]{15})(?:&|$)/;
if (tokenRegex.test(token)) {
return token;
@@ -33,8 +36,8 @@ function loadCalendar(): void {
} catch (e) {
toast.add({
severity: "error",
- summary: "Error",
- detail: "Invalid token",
+ summary: t("editCalendarView.error"),
+ detail: t("editCalendarView.invalidToken"),
life: 3000,
});
return;
@@ -58,10 +61,10 @@ function loadCalendar(): void {
-
- Edit your HTWKalender
+
+ {{ $t("editCalendarView.headline") }}
@@ -69,17 +72,17 @@ function loadCalendar(): void {
-
Please enter your existing calendar token
+
{{ $t("editCalendarView.subTitle") }}
-
+
-
+
diff --git a/frontend/src/components/ImprintPage.vue b/frontend/src/view/ImprintPage.vue
similarity index 95%
rename from frontend/src/components/ImprintPage.vue
rename to frontend/src/view/ImprintPage.vue
index d8ff591..0d6c69b 100644
--- a/frontend/src/components/ImprintPage.vue
+++ b/frontend/src/view/ImprintPage.vue
@@ -3,14 +3,15 @@
-
Imprint
+ {{$t("imprint")}}
nach dem Telemediengesetz (TMG) der Bundesrepublik Deutschland.
Kontakt
- Per Email: support@ekresse.de
+ Per Email:
+ support@htwkalender.de
Adresse
diff --git a/frontend/src/components/PrivacyPolicy.vue b/frontend/src/view/PrivacyPolicy.vue
similarity index 99%
rename from frontend/src/components/PrivacyPolicy.vue
rename to frontend/src/view/PrivacyPolicy.vue
index 3c9b4f6..0d44f8c 100644
--- a/frontend/src/components/PrivacyPolicy.vue
+++ b/frontend/src/view/PrivacyPolicy.vue
@@ -3,7 +3,7 @@
-
Privacy policy
+ {{$t("privacy")}}
Datenschutzerklärung
@@ -55,7 +55,7 @@
Deutschland
E-Mail-Adresse:
-
support@ekresse.de
+
support@htwkalender.de
Übersicht der Verarbeitungen
diff --git a/frontend/src/components/RoomFinder.vue b/frontend/src/view/RoomFinder.vue
similarity index 68%
rename from frontend/src/components/RoomFinder.vue
rename to frontend/src/view/RoomFinder.vue
index 35cba3b..98c661c 100644
--- a/frontend/src/components/RoomFinder.vue
+++ b/frontend/src/view/RoomFinder.vue
@@ -1,7 +1,7 @@