diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 1b80337..918cd48 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,10 +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/LocaleSwitcher.vue b/frontend/src/components/LocaleSwitcher.vue new file mode 100644 index 0000000..c2e0eaf --- /dev/null +++ b/frontend/src/components/LocaleSwitcher.vue @@ -0,0 +1,39 @@ + + \ No newline at end of file diff --git a/frontend/src/components/MenuBar.vue b/frontend/src/components/MenuBar.vue index fb8263c..be33bd1 100644 --- a/frontend/src/components/MenuBar.vue +++ b/frontend/src/components/MenuBar.vue @@ -1,45 +1,59 @@ @@ -48,4 +62,4 @@ const items = ref([ background-color: transparent; border: none; } - + \ No newline at end of file diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts index cf409a0..a048b1a 100644 --- a/frontend/src/i18n/index.ts +++ b/frontend/src/i18n/index.ts @@ -1,11 +1,55 @@ -import { createI18n } from 'vue-i18n' -import messages from "./messages.ts"; +import { createI18n } from "vue-i18n"; +import { nextTick } from "vue"; +import defaultMessages from './translations/en.json' -const i18n = createI18n({ - legacy: false, - globalInjection: true, - locale: 'en', - messages, -}) +export const supportedLocales= { + 'en': { name: 'English'}, + 'de': { name: 'Deutsch'}, +} +export let defaultLocale = 'en' +// Private instance of VueI18n object +let _i18n: any +// Initializer +function setup(options = { locale: defaultLocale }) { + _i18n = createI18n({ + legacy: false, + locale: options.locale, + fallbackLocale: defaultLocale, + messages: { [defaultLocale]: defaultMessages }, + }) + setLocale(options.locale) + return _i18n +} -export default i18n \ No newline at end of file +// Sets the active locale. +function setLocale(newLocale : any) { + _i18n.global.locale = newLocale + setDocumentAttributesFor(newLocale) +} + +async function loadMessagesFor(locale: any) { + const messages = await import( + `./translations/${locale}.json` + ) + + _i18n.global.setLocaleMessage(locale, messages.default) + + return nextTick() +} + +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, + loadMessagesFor, +} \ No newline at end of file diff --git a/frontend/src/i18n/messages.ts b/frontend/src/i18n/messages.ts index 3325aac..2f07201 100644 --- a/frontend/src/i18n/messages.ts +++ b/frontend/src/i18n/messages.ts @@ -1,18 +1,18 @@ export default { - en: { - createCalendar: 'Create Calendar', - editCalendar: 'Edit Calendar', - roomFinder: 'Room Finder', - faq: 'FAQ', - imprint: 'Imprint', - privacy: 'Privacy', + 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', + de : { + createCalendar: "Kalender erstellen", + editCalendar: "Kalender bearbeiten", + roomFinder: "Raumfinder", + faq: "FAQ", + imprint: "Impressum", + privacy: "Datenschutz" } } \ No newline at end of file diff --git a/frontend/src/i18n/translations/de.json b/frontend/src/i18n/translations/de.json new file mode 100644 index 0000000..93783bc --- /dev/null +++ b/frontend/src/i18n/translations/de.json @@ -0,0 +1,8 @@ +{ + "createCalendar": "Kalender erstellen", + "editCalendar": "Kalender bearbeiten", + "roomFinder": "Raumfinder", + "faq": "FAQ", + "imprint": "Impressum", + "privacy": "Datenschutz" +} \ No newline at end of file diff --git a/frontend/src/i18n/translations/en.json b/frontend/src/i18n/translations/en.json new file mode 100644 index 0000000..d9a1164 --- /dev/null +++ b/frontend/src/i18n/translations/en.json @@ -0,0 +1,8 @@ +{ + "createCalendar": "Create Calendar", + "editCalendar": "Edit Calendar", + "roomFinder": "Room Finder", + "faq": "FAQ", + "imprint": "Imprint", + "privacy": "Privacy" +} \ No newline at end of file diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 75c9915..328181a 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -35,12 +35,13 @@ import i18n from "./i18n"; const app = createApp(App); const pinia = createPinia(); +i18n.setup(); +app.use(i18n.vueI18n); app.use(PrimeVue); app.use(router); app.use(ToastService); app.use(pinia); app.use(DialogService); -app.use(i18n); app.component("Button", Button); app.component("Menu", Menu); app.component("Menubar", Menubar); diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 95c4864..8e6ce2d 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -1,6 +1,5 @@ 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 CalendarLink from "../components/CalendarLink.vue"; import Imprint from "../components/ImprintPage.vue"; @@ -10,70 +9,89 @@ import RoomFinder from "../components/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, { defaultLocale } from "../i18n"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: "/", - name: "course-selection", - component: CourseSelection, + redirect: `/${defaultLocale}`, }, { - path: "/rooms", - name: "room-finder", - component: RoomFinder, - }, - { - path: "/faq", - name: "faq", - component: Faq, - }, - { - path: "/additional-modules", - name: "additional-modules", - component: AdditionalModules, - }, - { - path: "/edit-additional-modules", - name: "edit-additional-modules", - component: EditAdditionalModules, - }, - { - path: "/edit-calendar", - name: "edit-calendar", - component: EditModules, - }, - { - path: "/calendar-link", - name: "calendar-link", - component: CalendarLink, - }, - { - path: "/edit", - name: "edit", - component: EditCalendarView, - }, - { - path: "/privacy-policy", - name: "privacy-policy", - component: PrivacyPolicy, - }, - { - path: "/imprint", - name: "imprint", - component: Imprint, - }, - { - path: "/rename-modules", - name: "rename-modules", - component: RenameModules, - }, - { - path: "/:catchAll(.*)", - redirect: "/", + path: "/:locale", + children: [ + { + path: "", + name: "course-selection", + component: CourseSelection, + }, + { + path: "rooms", + name: "room-finder", + component: RoomFinder, + }, + { + path: "faq", + name: "faq", + component: Faq, + }, + { + path: "additional-modules", + name: "additional-modules", + component: AdditionalModules, + }, + { + path: "edit-additional-modules", + name: "edit-additional-modules", + component: EditAdditionalModules, + }, + { + path: "edit-calendar", + name: "edit-calendar", + component: EditModules, + }, + { + path: "calendar-link", + name: "calendar-link", + component: CalendarLink, + }, + { + path: "edit", + name: "edit", + component: EditCalendarView, + }, + { + path: "privacy-policy", + name: "privacy-policy", + component: PrivacyPolicy, + }, + { + path: "imprint", + name: "imprint", + component: Imprint, + }, + { + path: "rename-modules", + name: "rename-modules", + component: RenameModules, + }, + ], }, ], }); +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 + } + + await i18n.loadMessagesFor(newLocale) + i18n.setLocale(newLocale) +}) + export default router; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 3353390..59a9a80 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,9 +1,15 @@ import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; +import { fileURLToPath } from "node:url"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, server: { host: true, port: 8000,