diff --git a/src/lib/i18n/de.ts b/src/lib/i18n/de.ts
index 162e29c28a2029f8e3bacda4065ddf98bfa1f969..971425166710efbd158b8b7699522bf14d7aa4fd 100644
--- a/src/lib/i18n/de.ts
+++ b/src/lib/i18n/de.ts
@@ -15,19 +15,19 @@ export default {
 		"/admin/eswe": "ESWE-Einstellungen",
 		"/admin/flyer": "Flyer verwalten",
 		"/admin/rabatte": "Rabatte verwalten",
-		"/admin/rabatte/[id]": "Rabatt bearbeiten: {discount.title}",
+		"/admin/rabatte/[id=number]": "Rabatt bearbeiten: {discount.title}",
 		"/admin/rallye/points": "Punkte verwalten",
 		"/admin/rallye/station": "Rallyestationen verwalten",
 		"/admin/rallye/station/[id=uuid]": "Rallyestation bearbeiten: {rallyStation.name}",
 		"/admin/rallye/station/new": "Neue Rallyestation erstellen",
 		"/admin/schedule": "Stundenpläne verwalten",
-		"/admin/schedule/[id]": "Stundenplan bearbeiten: {schedule.studyProgram.name.de}",
+		"/admin/schedule/[id=number]": "Stundenplan bearbeiten: {schedule.studyProgram.name.de}",
 		"/admin/schedule/fonts": "Schriftarten verwalten",
 		"/admin/setup": "Instanz einrichten",
 		"/admin/studyprogram": "Studiengänge verwalten",
 		"/admin/templates": "E-Mail-Vorlagen verwalten",
 		"/admin/tutor": "Tutor:innen verwalten",
-		"/admin/tutor/[id]": "Tutor:in bearbeiten: {tutor.firstname} {tutor.lastname}",
+		"/admin/tutor/[id=number]": "Tutor:in bearbeiten: {tutor.firstname} {tutor.lastname}",
 		"/admin/tutor/mail": "E-Mail senden",
 		"/admin/tutor/training": "Tutschulungen verwalten",
 		"/admin/tutor/training/[id=number]": "Tutschulung bearbeiten: {training.date}",
@@ -193,6 +193,17 @@ export default {
 	Flyer: {
 		Notice: "Einige Flyer haben mehr als eine Seite. Wenn du auf das Bild klickst, kommst du zum vollständigen PDF.",
 	},
+	ShareMoments: { // Text auf /upload
+		Headline: "Momente teilen",
+		Description: "Du hast Fotos von der Ersti-Woche gemacht und möchtest sie mit uns teilen? Dann bist du hier richtig! Lade deine Dateien hoch und wir sorgen dafür, dass sie an die richtige Stelle kommen.",
+		Disclaimer: "Mit dem Upload stimmst Du zu, dass die Bilder von der Fachschaft für Öffentlichkeitsarbeit verwendet und z.B. auf Instagram oder anderen Social Media-Plattformen veröffentlicht werden dürfen.",
+		Disclaimer2: "Bitte lade nur Bilder hoch, die Du selbst gemacht hast und für deren Veröffentlichung Du und alle erkennbaren Personen auf dem Bild einverstanden sind.",
+		LookingForwards: "Wir freuen uns auf deine Impressionen!",
+		Compliance: "Ich bestätige, dass ich die Rechte an diesem Bild oder diesen Bildern besitze und dass alle erkennbaren Personen der Veröffentlichung zugestimmt haben. Ich erlaube der Fachschaft I/1, das Bild für Öffentlichkeitsarbeit zu verwenden und auf Social Media zu veröffentlichen.",
+		Upload: "Hochladen",
+		UploadSuccess: "Deine Dateien wurden erfolgreich hochgeladen. Vielen Dank!",
+		UploadError: "Beim Hochladen deiner Dateien ist ein Fehler aufgetreten",
+	},
 	Legal: {
 		Headline: "Impressum",
 		Address: "Adresse",
diff --git a/src/lib/i18n/en.ts b/src/lib/i18n/en.ts
index 9a8f48c115e489833eee67b0ca774436129abd16..48d0b60ed6ee0ca93fa9951c8a91abe21e9e01fa 100644
--- a/src/lib/i18n/en.ts
+++ b/src/lib/i18n/en.ts
@@ -17,19 +17,19 @@ export default {
 		"/admin/eswe": "ESWE Settings",
 		"/admin/flyer": "Manage Flyers",
 		"/admin/rabatte": "Manage Discounts",
-		"/admin/rabatte/[id]": "Edit Discount: {discount.title}",
+		"/admin/rabatte/[id=number]": "Edit Discount: {discount.title}",
 		"/admin/rallye/points": "Manage Rally Points",
 		"/admin/rallye/station": "Manage Rally Stations",
 		"/admin/rallye/station/[id=uuid]": "Edit rally station: {rallyStation.name}",
 		"/admin/rallye/station/new": "Create new rally station",
 		"/admin/schedule": "Manage Schedules",
-		"/admin/schedule/[id]": "Edit Schedule: {schedule.studyProgram.name.en}",
+		"/admin/schedule/[id=number]": "Edit Schedule: {schedule.studyProgram.name.en}",
 		"/admin/schedule/fonts": "Manage Fonts",
 		"/admin/setup": "Setup Instance",
 		"/admin/studyprogram": "Manage Study Programs",
 		"/admin/templates": "Manage Mail Templates",
 		"/admin/tutor": "Manage Tutors",
-		"/admin/tutor/[id]": "Edit Tutor: {tutor.firstname} {tutor.lastname}",
+		"/admin/tutor/[id=number]": "Edit Tutor: {tutor.firstname} {tutor.lastname}",
 		"/admin/tutor/mail": "Send Email",
 		"/admin/tutor/training": "Manage Tutor Trainings",
 		"/admin/tutor/training/[id=number]": "Edit tutor training: {training.date}",
@@ -195,6 +195,17 @@ export default {
 	Flyer: {
 		Notice: "Some flyers have more than one page. You can get to the full PDF by clicking on the image.",
 	},
+	ShareMoments: { // Text auf /upload
+		Headline: "Share your moments",
+		Description: "You have taken some great pictures during the freshers' week and want to share them with us? Then you are exactly right here! Upload your pictures here and we will share them on our social media channels.",
+		Disclaimer: "By uploading, you agree that the images may be used by the student council for public relations and published on social media platforms.",
+		Disclaimer2: "Please only upload pictures that you have taken yourself and for which you and all recognizable persons in the picture agree to publication.",
+		LookingForwards: "We are looking forward to your impressions!",
+		Compliance: "I confirm that I own the rights to this image or these images and that all recognizable persons have agreed to the publication. I allow the student council I/1 to use the image for public relations and to publish it on social media.",
+		Upload: "Upload",
+		UploadSuccess: "Your files have been uploaded successfully. Thank you!",
+		UploadError: "An error occurred while uploading your files",
+	},
 	Legal: { // Text auf /impressum
 		Headline: "Legal Notice",
 		Address: "Address",
diff --git a/src/lib/server/mail.ts b/src/lib/server/mail.ts
index 6c6ceb8ba00ccd35d52d73a6e0bfc5bdce8b9a93..d0cab1df748b2cf5274883f63e4c4e936aa588eb 100644
--- a/src/lib/server/mail.ts
+++ b/src/lib/server/mail.ts
@@ -6,6 +6,7 @@ import { compileTemplate, parseTemplate, renderTemplate, type MailTemplate } fro
 import { Config } from "./database/entities/Config.entity";
 import ical from "ical-generator";
 import type SMTPTransport from "nodemailer/lib/smtp-transport";
+import type { TutorTraining } from "./database/entities/TutorTraining.entity";
 
 const transporter = nodemailer.createTransport({
 	host: env.MAIL_HOST,
@@ -52,7 +53,7 @@ export async function sendTrainingMails(){
 								description: "Verpflichtende Schulung für alle Ersti-Tuts / Mandatory training for all fresher tutors",
 								location: tutors[0].training!.location,
 								url: "https://esa.fsmpi.rwth-aachen.de/",
-							}
+							},
 						],
 					}).toString(),
 				},
@@ -65,6 +66,39 @@ export async function sendTrainingMails(){
 	}
 }
 
+export async function sendTrainingMail(tutor: Tutor): Promise<SendMailResult|void> {
+	if(!tutor.training) return;
+	const { trainingsStart, trainingsEnd, mailTemplates: { trainingInformation: template } } = Config.get();
+	const [startHour, startMinute] = trainingsStart.split(":").map(t => parseInt(t));
+	const [endHour, endMinute] = trainingsEnd.split(":").map(t => parseInt(t));
+	const start = new Date(tutor.training.date);
+	start.setHours(startHour, startMinute, 0, 0);
+	const end = new Date(start);
+	end.setHours(endHour, endMinute, 0, 0);
+	const result = await sendMail({
+		template,
+		tutors: [tutor],
+		from: env.MAIL_FROM,
+		icalEvent: {
+			filename: "training.ics",
+			method: "request",
+			content: ical({
+				events: [
+					{
+						start,
+						end,
+						summary: "Tutschulung / Tutor training",
+						description: "Verpflichtende Schulung für alle Ersti-Tuts / Mandatory training for all fresher tutors",
+						location: tutor.training.location,
+						url: "https://esa.fsmpi.rwth-aachen.de/",
+					},
+				],
+			}).toString(),
+		},
+	});
+	if(result.successes.length > 0) await Tutor.update({id: tutor.id, sentTrainingMail: true});
+}
+
 export async function sendTutorRegisteredMail(tutor: Tutor){
 	const { mailTemplates: { tutorRegistered: template } } = Config.get();
 	return sendMail({
@@ -77,13 +111,14 @@ export async function sendTutorRegisteredMail(tutor: Tutor){
 type SuccessType = {status: "success", response: SMTPTransport.SentMessageInfo, tutorId: number};
 type RejectedType = {status: "rejected", response: SMTPTransport.SentMessageInfo, tutorId: number};
 type ErrorType = {status: "error", error: Error, tutorId: number};
+export type SendMailResult = {successes: SuccessType[], rejected: RejectedType[], errors: ErrorType[]};
 export function sendMail({ template, tutors, attachments = [], from, icalEvent }: {
 	template: MailTemplate,
 	tutors: Tutor[],
 	attachments?: Mail.Attachment[],
 	from?: string | Mail.Address,
 	icalEvent?: Mail.IcalAttachment,
-}): Promise<{successes: SuccessType[], rejected: RejectedType[], errors: ErrorType[]}> {
+}): Promise<SendMailResult> {
 	const replyTo = template.replyTo === "-" ? undefined : template.replyTo;
 	const textParts = parseTemplate(template.subject);
 	const subjectParts = parseTemplate(template.subject);
diff --git a/src/routes/(non-admin)/upload/+page.svelte b/src/routes/(non-admin)/upload/+page.svelte
index 5687b55ff2bbfb7dc5ed4ec2bcb99514e5be19ce..fb04694d11370179fd27c2e8211ca8ad34cd8158 100644
--- a/src/routes/(non-admin)/upload/+page.svelte
+++ b/src/routes/(non-admin)/upload/+page.svelte
@@ -1,35 +1,28 @@
 <script lang="ts">
 	import { enhance } from '$app/forms';
-    import { addMessage } from '$lib/messages';
+	import { addMessage } from '$lib/messages';
 	import { Heading, P, Fileupload, Button, Checkbox } from 'flowbite-svelte';
+	import { LL } from "$lib/i18n/i18n";
 </script>
 
 <!-- TODO i18n -->
-<Heading tag="h1" customSize="text-4xl font-bold" class="mb-6 text-center">Momente teilen</Heading>
+<Heading tag="h1" customSize="text-4xl font-bold" class="mb-6 text-center">{$LL.ShareMoments.Headline()}</Heading>
 
-<P class="mb-3">
-	Lade hier Deine Fotos von der Erstiwoche hoch und hilf uns, die besten Momente festzuhalten.
-	Mit dem Upload stimmst Du zu, dass die Bilder von der Fachschaft für Öffentlichkeitsarbeit verwendet
-	und z.B. auf Instagram oder anderen Social Media-Plattformen veröffentlicht werden dürfen.
-</P>
-<P class="mb-3">
-	Bitte lade nur Bilder hoch, die Du selbst gemacht hast und für deren Veröffentlichung Du und alle
-	erkennbaren Personen auf dem Bild einverstanden sind.
-</P>
-<P class="mb-3">
-	Wir freuen uns auf Deine Impressionen!
-</P>
+<P class="mb-3">{$LL.ShareMoments.Description()}</P>
+<P class="mb-3">{$LL.ShareMoments.Disclaimer()}</P>
+<P class="mb-3">{$LL.ShareMoments.Disclaimer2()}</P>
+<P class="mb-3">{$LL.ShareMoments.LookingForwards()}</P>
 
 <form action="?/upload" method="post" enctype="multipart/form-data" use:enhance={()=>({result, update})=>{
 	if(result.type === "success"){
-		addMessage({ type: "success", text: "Deine Bilder wurden erfolgreich hochgeladen. Vielen Dank!" });
+		addMessage({ type: "success", text: $LL.ShareMoments.UploadSuccess() });
 		update();
 	}else{
-		addMessage({ type: "error", text: "Beim Hochladen der Bilder ist ein Fehler aufgetreten. Bitte versuche es erneut." });
+		addMessage({ type: "error", text: $LL.ShareMoments.UploadError() });
 		console.error(result);
 	}
 }}>
 	<Fileupload name="files" accept="image/*" multiple required class="mb-2" />
-	<Checkbox class="mb-2" required>Ich bestätige, dass ich die Rechte an diesem Bild oder diesen Bildern besitze und dass alle erkennbaren Personen der Veröffentlichung zugestimmt haben. Ich erlaube der Fachschaft I/1, das Bild für Öffentlichkeitsarbeit zu verwenden und auf Social Media zu veröffentlichen.</Checkbox>
-	<Button type="submit">Hochladen</Button>
+	<Checkbox class="mb-2" required>{$LL.ShareMoments.Compliance()}</Checkbox>
+	<Button type="submit">{$LL.ShareMoments.Upload()}</Button>
 </form>
diff --git a/src/routes/admin/eswe/+page.svelte b/src/routes/admin/eswe/+page.svelte
index b76cfc91fcd6c266b0c91d3bcf7c6d4cc4ce0c96..bdd265a5fd6f25d20091bb2e8e3efdb8b8d6ab84 100644
--- a/src/routes/admin/eswe/+page.svelte
+++ b/src/routes/admin/eswe/+page.svelte
@@ -74,7 +74,9 @@
 		</TableBodyRow>
 		{/each}
 		<TableBodyRow>
-			<TableBodyCell class="max-w-min"><Input type="file" name="image" form="new" class="max-w-min" accept={data.extensions.map(ext=>`.${ext}`).join(",")} /></TableBodyCell>
+			<TableBodyCell class="max-w-min">
+				<Input type="file" name="image" form="new" class="w-30" accept={data.extensions.map(ext=>`.${ext}`).join(",")} />
+			</TableBodyCell>
 			<TableBodyCell>
 				<Label>
 					Sortierung
diff --git a/src/routes/admin/rabatte/[id]/+page.server.ts b/src/routes/admin/rabatte/[id=number]/+page.server.ts
similarity index 100%
rename from src/routes/admin/rabatte/[id]/+page.server.ts
rename to src/routes/admin/rabatte/[id=number]/+page.server.ts
diff --git a/src/routes/admin/rabatte/[id]/+page.svelte b/src/routes/admin/rabatte/[id=number]/+page.svelte
similarity index 98%
rename from src/routes/admin/rabatte/[id]/+page.svelte
rename to src/routes/admin/rabatte/[id=number]/+page.svelte
index 81070ea9e826602e02a38694b3fd6638b7f28eb0..319a46b1c74d18ad9bd580121e51ee87754cd4e4 100644
--- a/src/routes/admin/rabatte/[id]/+page.svelte
+++ b/src/routes/admin/rabatte/[id=number]/+page.svelte
@@ -96,7 +96,7 @@
 
 <Breadcrumb class="mb-4">
 	<BreadcrumbItem href="/admin" home>Admin</BreadcrumbItem>
-	<BreadcrumbItem href="/admin/rabatte">Rallye</BreadcrumbItem>
+	<BreadcrumbItem href="/admin/rabatte">Rabatte</BreadcrumbItem>
 	<BreadcrumbItem href="/admin/rallye/{data.discount.id}">{data.discount.title}</BreadcrumbItem>
 </Breadcrumb>
 
diff --git a/src/routes/admin/rabatte/[id]/+server.ts b/src/routes/admin/rabatte/[id=number]/+server.ts
similarity index 100%
rename from src/routes/admin/rabatte/[id]/+server.ts
rename to src/routes/admin/rabatte/[id=number]/+server.ts
diff --git a/src/routes/admin/rabatte/[id]/OpeningHoursInput.svelte b/src/routes/admin/rabatte/[id=number]/OpeningHoursInput.svelte
similarity index 100%
rename from src/routes/admin/rabatte/[id]/OpeningHoursInput.svelte
rename to src/routes/admin/rabatte/[id=number]/OpeningHoursInput.svelte
diff --git a/src/routes/admin/rallye/points/+page.server.ts b/src/routes/admin/rallye/points/+page.server.ts
index b558fc69831ec71d209df7d6b638a6bc1c34ce1d..e10f142c04e2009cb33cad46ed9f115fe312f86d 100644
--- a/src/routes/admin/rallye/points/+page.server.ts
+++ b/src/routes/admin/rallye/points/+page.server.ts
@@ -8,8 +8,8 @@ export const load = (async () => {
 	const stations = await RallyStation.getAll();
 	const points = await RallyPoints.getAll();
 	return {
-		tutorials: tutorials.map(t=>({id: t.id, name: t.name})),
-		stations: stations.map(s=>({id: s.id, name: s.name, pointsDescription: s.pointsDescription})),
+		tutorials: tutorials.map(t=>({id: t.id, name: t.name, rallyQuestionnairePoints: t.rallyQuestionnairePoints})),
+		stations: stations.map(s=>({id: s.id, name: s.name, pointsDescription: s.pointsDescription, shouldMaximize: s.shouldMaximize})),
 		points: points.map(p=>({station: p.rallyStation.id, tutorial: p.tutorial, points: p.points, bribe: p.bribe})),
 	};
 }) satisfies PageServerLoad;
diff --git a/src/routes/admin/rallye/points/+page.svelte b/src/routes/admin/rallye/points/+page.svelte
index 34cdba3723746fce520148bc0af90e31de728fe1..601b18d8c9fcebbdfedf40d7d03ac7856d17b6e4 100644
--- a/src/routes/admin/rallye/points/+page.svelte
+++ b/src/routes/admin/rallye/points/+page.svelte
@@ -5,10 +5,54 @@
 	
 	let tutorialIds = data.tutorials.map(tutorial => tutorial.id);
 	let stationIds = data.stations.map(station => station.id);
-	let points = Array.from({ length: stationIds.length }).map(()=>Array.from({ length: tutorialIds.length }).map(()=>({points: 0, bribe: 0})));
+	let points = Array.from({ length: stationIds.length }).map(()=>Array.from({ length: tutorialIds.length }).map(()=>null as {points: number, bribe: number}|null));
 	for(const point of data.points){
 		points[stationIds.indexOf(point.station)][tutorialIds.indexOf(point.tutorial)] = { points: point.points, bribe: point.bribe };
 	}
+	
+	function normalizeQuestionnaires(){
+		const points = data.tutorials.map(tutorial => tutorial.rallyQuestionnairePoints ?? 0);
+		const max = Math.max(...points);
+		const maxPoints = 30;
+		return points.map(p => p / max * maxPoints);
+	}
+	
+	function normalize(){
+		return points.map((_, stationIndex) => normalizeStation(stationIndex));
+	}
+	
+	function normalizeStation(stationIndex: number){
+		const normalized = points[stationIndex].map(p => ({points: 0, bribe: p?.bribe ?? 0}));
+		let station = data.stations[stationIndex];
+		let [min, max] = points[stationIndex].reduce((acc, p) => p ? [Math.min(acc[0], p.points), Math.max(acc[1], p.points)] : acc, [Infinity, -Infinity]);
+		if(!Number.isFinite(min) || !Number.isFinite(max)) return normalized;
+		let range = max - min;
+		const maxPoints = 20;
+		for(let tutorialIndex = 0; tutorialIndex < normalized.length; tutorialIndex++){
+			let point = points[stationIndex][tutorialIndex];
+			if(!point) continue;
+			let normalizedPoints = (point.points - min) / range * (maxPoints - 1) + 1;
+			if(!station.shouldMaximize){
+				normalizedPoints = maxPoints - normalizedPoints + 1;
+			}
+			point.points = normalizedPoints;
+		}
+		return normalized;
+	}
+	
+	function evaluate(){
+		const normalized = normalize();
+		const transposed = normalized[0].map((_, i) => normalized.map(row => row[i]));
+		const numberOfStationsToGrade = Math.min(6, stationIds.length);
+		for(let tutorialIndex = 0; tutorialIndex < transposed.length; tutorialIndex++){
+			transposed[tutorialIndex].sort((a, b) => (b.points+b.bribe) - (a.points+a.bribe)).splice(numberOfStationsToGrade);
+		}
+		const normalizedQuestionnaires = normalizeQuestionnaires();
+		return transposed.map((points, tutorialIndex) => ({
+			tutorialId: tutorialIds[tutorialIndex],
+			points: points.reduce((acc, point) => acc + point.points + point.bribe + normalizedQuestionnaires[tutorialIndex], 0)
+		})).sort((a, b) => b.points - a.points);
+	}
 </script>
 
 <Breadcrumb class="mb-4">
@@ -17,6 +61,18 @@
 	<BreadcrumbItem href="/admin/rallye/points">Punkte</BreadcrumbItem>
 </Breadcrumb>
 
+{#each data.tutorials as tutorial}
+{#each data.stations as station}
+<form id="form-{station.id}/{tutorial.id}" method="post" action="?/updateStation">
+	<input type="hidden" name="stationId" value={station.id} />
+	<input type="hidden" name="tutorialId" value={tutorial.id} />
+</form>
+{/each}
+<form id="form-{tutorial.id}" method="post" action="?/updateQuestionnaire">
+	<input type="hidden" name="tutorialId" value={tutorial.id} />
+</form>
+{/each}
+
 <Table>
 	<TableHead>
 		<TableHeadCell></TableHeadCell>
@@ -32,15 +88,26 @@
 			<TableBodyCell>
 				<Label>
 					{station.pointsDescription}
-					<Input type="number" bind:value={points[stationIndex][tutorialIndex].points} class="w-24" name="points" form="form-{station.id}/{tutorial.id}" />
+					<Input type="number" value={points[stationIndex][tutorialIndex]?.points} class="w-24" name="points" form="form-{station.id}/{tutorial.id}" />
 				</Label>
 				<Label>
 					Bestechung
-					<Input type="number" bind:value={points[stationIndex][tutorialIndex].bribe} class="w-24" name="bribe" form="form-{station.id}/{tutorial.id}" />
+					<Input type="number" value={points[stationIndex][tutorialIndex]?.bribe} class="w-24" name="bribe" form="form-{station.id}/{tutorial.id}" />
 				</Label>
 			</TableBodyCell>
 			{/each}
 		</TableBodyRow>
 		{/each}
+		<TableBodyRow>
+			<TableBodyCell>Laufzettel</TableBodyCell>
+			{#each data.tutorials as tutorial}
+			<TableBodyCell>
+				<Label>
+					Punkte
+					<Input type="number" value={tutorial.rallyQuestionnairePoints} class="w-24" name="rallyQuestionnairePoints" form="form-{tutorial.id}" />
+				</Label>
+			</TableBodyCell>
+			{/each}
+		</TableBodyRow>
 	</TableBody>
 </Table>
diff --git a/src/routes/admin/schedule/+page.svelte b/src/routes/admin/schedule/+page.svelte
index 48c0ac3c38540fff2b0b2bd2383ac39a6390f4c9..0d680d740c97d85cd1aa1be48425597a5c2b2cf2 100644
--- a/src/routes/admin/schedule/+page.svelte
+++ b/src/routes/admin/schedule/+page.svelte
@@ -4,7 +4,7 @@
 	import { enhance } from "$app/forms";
 	import { Permission } from "$lib/perms";
 	import { invalidateAll } from "$app/navigation";
-	import { locale, locales } from "$lib/i18n/i18n";
+	import { locale, locales, LL } from "$lib/i18n/i18n";
 	import { addMessage } from "$lib/messages.js";
 	
 	export let data;
@@ -265,8 +265,8 @@
 				<TableBodyRow>
 					<TableBodyCell>{schedule.studyProgram.name[$locale]}</TableBodyCell>
 					<TableBodyCell>
-						<img src="/stundenplaene/{schedule.id}/{$locale}.dark.svg" class="dark:block hidden h-64" />
-						<img src="/stundenplaene/{schedule.id}/{$locale}.light.svg" class="block dark:hidden h-64" />
+						<img src="/stundenplaene/{schedule.id}/{$locale}.dark.svg" class="dark:block hidden h-64" alt={$LL.Information.ScheduleAlt({semester: data.semester, studyProgram: schedule.studyProgram.name[$locale]})} />
+						<img src="/stundenplaene/{schedule.id}/{$locale}.light.svg" class="block dark:hidden h-64" alt={$LL.Information.ScheduleAlt({semester: data.semester, studyProgram: schedule.studyProgram.name[$locale]})} />
 					</TableBodyCell>
 					{#if data.user.permissions.has(Permission.UPDATE_SCHEDULES)}
 					<TableBodyCell class="max-w-min">
diff --git a/src/routes/admin/schedule/[id]/+page.server.ts b/src/routes/admin/schedule/[id=number]/+page.server.ts
similarity index 100%
rename from src/routes/admin/schedule/[id]/+page.server.ts
rename to src/routes/admin/schedule/[id=number]/+page.server.ts
diff --git a/src/routes/admin/schedule/[id]/+page.svelte b/src/routes/admin/schedule/[id=number]/+page.svelte
similarity index 95%
rename from src/routes/admin/schedule/[id]/+page.svelte
rename to src/routes/admin/schedule/[id=number]/+page.svelte
index b61f66b26c4f126d3e7f18ae135995102f367556..b164378427e56dee7d92bdcfa446c9871ffeaa23 100644
--- a/src/routes/admin/schedule/[id]/+page.svelte
+++ b/src/routes/admin/schedule/[id=number]/+page.svelte
@@ -1,7 +1,7 @@
 <script lang="ts">
 	import { Breadcrumb, BreadcrumbItem, Button, Heading, Input, Label, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell, Textarea } from "flowbite-svelte";
 	import { enhance } from "$app/forms";
-	import { locale, locales } from "$lib/i18n/i18n";
+	import { locale, locales, LL } from "$lib/i18n/i18n";
 	import type { Timeslot } from "$lib/server/database/entities/Schedule.entity.js";
 	import { addMessage } from "$lib/messages.js";
 
@@ -28,7 +28,7 @@
 
 <Breadcrumb class="mb-4">
 	<BreadcrumbItem href="/admin" home>Admin</BreadcrumbItem>
-	<BreadcrumbItem href="/admin/schedule">Rallye</BreadcrumbItem>
+	<BreadcrumbItem href="/admin/schedule">Stundenpläne</BreadcrumbItem>
 	<BreadcrumbItem href="/admin/schedule/{data.schedule.id}">{data.schedule.studyProgram.name[$locale]}</BreadcrumbItem>
 </Breadcrumb>
 
@@ -38,8 +38,8 @@
 
 <Heading tag="h2" customSize="text-2xl font-bold" class="mb-2">Vorschau</Heading>
 <div>
-	<img src="/stundenplaene/{schedule.id}/{$locale}.dark.svg" class="hidden dark:block" />
-	<img src="/stundenplaene/{schedule.id}/{$locale}.light.svg" class="block dark:hidden" />
+	<img src="/stundenplaene/{schedule.id}/{$locale}.dark.svg" class="hidden dark:block" alt={$LL.Information.ScheduleAlt({semester: data.semester, studyProgram: schedule.studyProgram.name[$locale]})} />
+	<img src="/stundenplaene/{schedule.id}/{$locale}.light.svg" class="block dark:hidden" alt={$LL.Information.ScheduleAlt({semester: data.semester, studyProgram: schedule.studyProgram.name[$locale]})} />
 </div>
 <Button size="sm" on:click={rerender}>Neu rendern</Button>
 
diff --git a/src/routes/admin/schedule/[id]/+server.ts b/src/routes/admin/schedule/[id=number]/+server.ts
similarity index 100%
rename from src/routes/admin/schedule/[id]/+server.ts
rename to src/routes/admin/schedule/[id=number]/+server.ts
diff --git a/src/routes/admin/schedule/[id]/[timeslot]/+server.ts b/src/routes/admin/schedule/[id=number]/[timeslot]/+server.ts
similarity index 100%
rename from src/routes/admin/schedule/[id]/[timeslot]/+server.ts
rename to src/routes/admin/schedule/[id=number]/[timeslot]/+server.ts
diff --git a/src/routes/admin/schedule/fonts/+page.svelte b/src/routes/admin/schedule/fonts/+page.svelte
index 4a805031dc019c50e2ae9e1bdf7014bd169c8057..051a881294ace29220f242028326661e3f9e68fe 100644
--- a/src/routes/admin/schedule/fonts/+page.svelte
+++ b/src/routes/admin/schedule/fonts/+page.svelte
@@ -22,7 +22,7 @@
 
 <Breadcrumb class="mb-4">
 	<BreadcrumbItem href="/admin" home>Admin</BreadcrumbItem>
-	<BreadcrumbItem href="/admin/schedule">Rallye</BreadcrumbItem>
+	<BreadcrumbItem href="/admin/schedule">Stundenpläne</BreadcrumbItem>
 	<BreadcrumbItem href="/admin/schedule/fonts">Schriftarten</BreadcrumbItem>
 </Breadcrumb>
 
diff --git a/src/routes/admin/tutor/[id]/+page.server.ts b/src/routes/admin/tutor/[id=number]/+page.server.ts
similarity index 100%
rename from src/routes/admin/tutor/[id]/+page.server.ts
rename to src/routes/admin/tutor/[id=number]/+page.server.ts
diff --git a/src/routes/admin/tutor/[id]/+page.svelte b/src/routes/admin/tutor/[id=number]/+page.svelte
similarity index 100%
rename from src/routes/admin/tutor/[id]/+page.svelte
rename to src/routes/admin/tutor/[id=number]/+page.svelte
diff --git a/src/routes/admin/tutor/training/[id=number]/+page.server.ts b/src/routes/admin/tutor/training/[id=number]/+page.server.ts
index ed0a0d8d8f07d4438aeb415481bf9c5e68d3499b..a5fdb9970eb5c4782a7bf3e8c486f0a04eeeb5a7 100644
--- a/src/routes/admin/tutor/training/[id=number]/+page.server.ts
+++ b/src/routes/admin/tutor/training/[id=number]/+page.server.ts
@@ -11,3 +11,33 @@ export const load = (async event => {
 		training: {...training, participants: training.participants.length},
 	};
 }) satisfies PageServerLoad;
+
+export const actions = {
+	update: async event => {
+		const id = Number(event.params.id);
+		if(!Number.isInteger(id) || id < 1) error(404, 'Not Found');
+		const training = await TutorTraining.getById(id);
+		if(!training) error(404, 'Not Found');
+		const data = await event.request.formData();
+		const dateStr = data.get("date");
+		const location = data.get("location");
+		const maxParticipantsStr = data.get("maxParticipants");
+		const notes = data.get("notes");
+		const internal = data.get("internal") === "on";
+		if(typeof dateStr !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) error(400, 'Invalid date');
+		const dateNum = Date.parse(dateStr);
+		if(isNaN(dateNum)) error(400, 'Invalid date');
+		if(typeof location !== "string" || location.length < 1) error(400, 'Invalid location');
+		if(typeof maxParticipantsStr !== "string" || !/^\d+$/.test(maxParticipantsStr)) error(400, 'Invalid maxParticipants');
+		const maxParticipants = Number(maxParticipantsStr);
+		if(!Number.isInteger(maxParticipants) || maxParticipants < 1) error(400, 'Invalid maxParticipants');
+		if(typeof notes !== "string") error(400, 'Invalid notes');
+		await TutorTraining.update(id, {
+			date: dateStr,
+			location,
+			maxParticipants,
+			notes,
+			internal,
+		});
+	},
+};