Init v1.0.0
Some checks failed
Build & Push Docker Image / build (push) Failing after 53s

This commit is contained in:
2026-02-24 18:14:16 +01:00
commit 54d6ee553a
49 changed files with 10410 additions and 0 deletions

244
src/i18n/de.json Normal file
View File

@@ -0,0 +1,244 @@
{
"common": {
"appName": "Redlight",
"loading": "Laden...",
"save": "Speichern",
"cancel": "Abbrechen",
"delete": "Löschen",
"edit": "Bearbeiten",
"create": "Erstellen",
"search": "Suchen...",
"close": "Schließen",
"confirm": "Bestätigen",
"back": "Zurück",
"yes": "Ja",
"no": "Nein",
"or": "oder",
"optional": "Optional",
"unlimited": "Unbegrenzt",
"none": "Keiner",
"offline": "Offline",
"active": "Aktiv",
"inactive": "Inaktiv",
"protected": "Geschützt",
"live": "Live",
"error": "Fehler",
"success": "Erfolg"
},
"nav": {
"dashboard": "Dashboard",
"settings": "Einstellungen",
"admin": "Administration",
"appearance": "Darstellung",
"changeTheme": "Theme ändern",
"navigation": "Navigation"
},
"auth": {
"login": "Anmelden",
"register": "Registrieren",
"logout": "Abmelden",
"email": "E-Mail",
"password": "Passwort",
"name": "Name",
"welcomeBack": "Willkommen zurück",
"loginSubtitle": "Melden Sie sich an, um auf Ihre Räume zuzugreifen.",
"createAccount": "Konto erstellen",
"registerSubtitle": "Registrieren Sie sich, um Räume zu erstellen und Meetings zu starten.",
"noAccount": "Noch kein Konto?",
"hasAccount": "Bereits ein Konto?",
"signUpNow": "Jetzt registrieren",
"signInNow": "Jetzt anmelden",
"backToHome": "← Zurück zur Startseite",
"emailPlaceholder": "name@beispiel.de",
"passwordPlaceholder": "••••••••",
"namePlaceholder": "Max Mustermann",
"minPassword": "Mindestens 6 Zeichen",
"confirmPassword": "Passwort bestätigen",
"repeatPassword": "Passwort wiederholen",
"passwordMismatch": "Passwörter stimmen nicht überein",
"passwordTooShort": "Passwort muss mindestens 6 Zeichen lang sein",
"loginSuccess": "Willkommen zurück!",
"registerSuccess": "Registrierung erfolgreich!",
"loginFailed": "Anmeldung fehlgeschlagen",
"registerFailed": "Registrierung fehlgeschlagen",
"allFieldsRequired": "Alle Felder sind erforderlich"
},
"home": {
"poweredBy": "Powered by BigBlueButton",
"heroTitle": "Meetings neu ",
"heroTitleHighlight": "definiert",
"heroSubtitle": "Das moderne, selbst gehostete BigBlueButton-Frontend. Erstellen Sie Räume, verwalten Sie Aufnahmen und genießen Sie ein wunderschönes Interface mit über 15 Themes.",
"getStarted": "Jetzt starten",
"features": "Alles was Sie brauchen",
"featuresSubtitle": "Redlight bietet alle Funktionen, die Sie für professionelle Videokonferenzen benötigen.",
"featureVideoTitle": "Videokonferenzen",
"featureVideoDesc": "Erstellen und verwalten Sie Meetings direkt über BigBlueButton.",
"featureRoomsTitle": "Raumverwaltung",
"featureRoomsDesc": "Unbegrenzte Räume mit individuellen Einstellungen und Zugangscodes.",
"featureUsersTitle": "Benutzerverwaltung",
"featureUsersDesc": "Registrierung, Login und Rollenverwaltung für Ihre Organisation.",
"featureThemesTitle": "15+ Themes",
"featureThemesDesc": "Dracula, Nord, Catppuccin, Rosé Pine, Gruvbox und viele mehr.",
"featureRecordingsTitle": "Aufnahmen",
"featureRecordingsDesc": "Alle Aufnahmen pro Raum einsehen, veröffentlichen oder löschen.",
"featureOpenSourceTitle": "Open Source",
"featureOpenSourceDesc": "Vollständig quelloffen und selbst gehostet. Ihre Daten bleiben bei Ihnen.",
"statThemes": "Themes",
"statRooms": "Räume",
"statOpenSource": "Open Source",
"footer": "© {year} Redlight. Ein Open-Source BigBlueButton Frontend."
},
"dashboard": {
"myRooms": "Meine Räume",
"roomCount": "{count} Raum erstellt | {count} Räume erstellt",
"newRoom": "Neuer Raum",
"noRooms": "Noch keine Räume",
"noRoomsSubtitle": "Erstellen Sie Ihren ersten Raum, um Meetings zu starten.",
"createFirst": "Ersten Raum erstellen",
"createRoom": "Neuen Raum erstellen",
"roomName": "Raumname",
"roomNamePlaceholder": "z.B. Team Meeting",
"roomNameRequired": "Raumname ist erforderlich",
"welcomeMessage": "Willkommensnachricht",
"welcomeMessageDefault": "Willkommen im Meeting!",
"maxParticipants": "Max. Teilnehmer",
"maxParticipantsHint": "0 = unbegrenzt",
"accessCode": "Zugangscode",
"muteOnJoin": "Teilnehmer beim Beitritt stummschalten",
"allowRecording": "Aufnahme erlauben",
"roomCreated": "Raum erstellt!",
"roomCreateFailed": "Raum konnte nicht erstellt werden",
"roomDeleted": "Raum gelöscht",
"roomDeleteFailed": "Raum konnte nicht gelöscht werden",
"roomDeleteConfirm": "Raum \"{name}\" wirklich löschen?",
"loadFailed": "Räume konnten nicht geladen werden"
},
"room": {
"backToDashboard": "Zurück zum Dashboard",
"start": "Starten",
"startMeeting": "Meeting starten",
"join": "Beitreten",
"end": "Beenden",
"openDetails": "Details öffnen",
"overview": "Übersicht",
"recordings": "Aufnahmen",
"settings": "Einstellungen",
"participants": "{count} Teilnehmer",
"copyLink": "Link kopieren",
"linkCopied": "Link kopiert!",
"meetingDetails": "Meeting-Details",
"meetingId": "Meeting ID",
"status": "Status",
"maxParticipants": "Max. Teilnehmer",
"accessCode": "Zugangscode",
"roomSettings": "Raumeinstellungen",
"mutedOnJoin": "Beim Beitritt stummgeschaltet",
"micActiveOnJoin": "Mikrofon aktiv beim Beitritt",
"approvalRequired": "Genehmigung erforderlich",
"freeJoin": "Freier Beitritt",
"allModerators": "Alle als Moderator",
"rolesAssigned": "Rollen werden zugewiesen",
"recordingAllowed": "Aufnahme erlaubt",
"recordingDisabled": "Aufnahme deaktiviert",
"welcomeMsg": "Willkommensnachricht",
"muteOnJoin": "Beim Beitritt stummschalten",
"requireApproval": "Moderator-Genehmigung erforderlich",
"anyoneCanStart": "Jeder kann das Meeting starten",
"allJoinModerator": "Alle Teilnehmer als Moderator",
"allowRecording": "Aufnahme erlauben",
"noAccessCode": "Kein Zugangscode",
"emptyNoCode": "Leer = kein Code",
"settingsSaved": "Einstellungen gespeichert",
"settingsSaveFailed": "Einstellungen konnten nicht gespeichert werden",
"meetingStarted": "Meeting gestartet!",
"meetingStartFailed": "Meeting konnte nicht gestartet werden",
"meetingEnded": "Meeting beendet",
"meetingEndFailed": "Meeting konnte nicht beendet werden",
"joinFailed": "Beitritt fehlgeschlagen",
"endConfirm": "Meeting wirklich beenden?",
"enterAccessCode": "Zugangscode eingeben:",
"notFound": "Raum nicht gefunden"
},
"recordings": {
"title": "Aufnahmen",
"noRecordings": "Keine Aufnahmen vorhanden",
"published": "Veröffentlicht",
"unpublished": "Nicht veröffentlicht",
"presentation": "Präsentation",
"deleted": "Aufnahme gelöscht",
"deleteFailed": "Fehler beim Löschen",
"deleteConfirm": "Aufnahme wirklich löschen?",
"publishSuccess": "Aufnahme veröffentlicht",
"unpublishSuccess": "Aufnahme versteckt",
"publishFailed": "Fehler beim Aktualisieren",
"hide": "Verstecken",
"publish": "Veröffentlichen",
"loadFailed": "Aufnahmen konnten nicht geladen werden"
},
"settings": {
"title": "Einstellungen",
"subtitle": "Verwalten Sie Ihr Profil und Ihre Einstellungen",
"profile": "Profil",
"password": "Passwort",
"themes": "Themes",
"language": "Sprache",
"editProfile": "Profil bearbeiten",
"avatar": "Profilbild",
"avatarColor": "Avatar-Farbe",
"avatarColorHint": "Wird als Fallback verwendet, wenn kein Bild hochgeladen ist.",
"uploadImage": "Bild hochladen",
"removeImage": "Bild entfernen",
"avatarHint": "JPG, PNG, GIF oder WebP. Max. 2 MB.",
"avatarUploaded": "Profilbild aktualisiert",
"avatarUploadFailed": "Fehler beim Hochladen",
"avatarRemoved": "Profilbild entfernt",
"avatarRemoveFailed": "Fehler beim Entfernen",
"avatarInvalidType": "Nur Bilddateien sind erlaubt",
"avatarTooLarge": "Bild darf maximal 2 MB groß sein",
"changePassword": "Passwort ändern",
"currentPassword": "Aktuelles Passwort",
"newPassword": "Neues Passwort",
"confirmNewPassword": "Neues Passwort bestätigen",
"profileSaved": "Profil gespeichert",
"profileSaveFailed": "Fehler beim Speichern",
"passwordChanged": "Passwort geändert",
"passwordChangeFailed": "Fehler beim Ändern",
"passwordMismatch": "Passwörter stimmen nicht überein",
"selectLanguage": "Sprache auswählen"
},
"themes": {
"selectTheme": "Theme auswählen",
"selectThemeSubtitle": "Wähle dein bevorzugtes Farbschema",
"light": "Hell",
"dark": "Dunkel"
},
"admin": {
"title": "Administration",
"userCount": "{count} Benutzer registriert | {count} Benutzer registriert",
"searchUsers": "Benutzer suchen...",
"user": "Benutzer",
"role": "Rolle",
"rooms": "Räume",
"registered": "Registriert",
"actions": "Aktionen",
"admin": "Admin",
"makeAdmin": "Zum Admin machen",
"makeUser": "Zum Benutzer machen",
"resetPassword": "Passwort zurücksetzen",
"deleteUser": "Löschen",
"createUser": "Benutzer erstellen",
"createUserTitle": "Neuen Benutzer erstellen",
"userCreated": "Benutzer erstellt",
"userCreateFailed": "Benutzer konnte nicht erstellt werden",
"newPasswordLabel": "Neues Passwort",
"resetPasswordTitle": "Passwort zurücksetzen",
"noUsersFound": "Keine Benutzer gefunden",
"roleUpdated": "Rolle aktualisiert",
"roleUpdateFailed": "Fehler beim Aktualisieren",
"userDeleted": "Benutzer gelöscht",
"userDeleteFailed": "Fehler beim Löschen",
"passwordReset": "Passwort zurückgesetzt",
"passwordResetFailed": "Fehler beim Zurücksetzen",
"deleteUserConfirm": "Benutzer \"{name}\" wirklich löschen? Alle Räume werden ebenfalls gelöscht."
}
}

244
src/i18n/en.json Normal file
View File

@@ -0,0 +1,244 @@
{
"common": {
"appName": "Redlight",
"loading": "Loading...",
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"edit": "Edit",
"create": "Create",
"search": "Search...",
"close": "Close",
"confirm": "Confirm",
"back": "Back",
"yes": "Yes",
"no": "No",
"or": "or",
"optional": "Optional",
"unlimited": "Unlimited",
"none": "None",
"offline": "Offline",
"active": "Active",
"inactive": "Inactive",
"protected": "Protected",
"live": "Live",
"error": "Error",
"success": "Success"
},
"nav": {
"dashboard": "Dashboard",
"settings": "Settings",
"admin": "Administration",
"appearance": "Appearance",
"changeTheme": "Change theme",
"navigation": "Navigation"
},
"auth": {
"login": "Sign in",
"register": "Sign up",
"logout": "Sign out",
"email": "Email",
"password": "Password",
"name": "Name",
"welcomeBack": "Welcome back",
"loginSubtitle": "Sign in to access your rooms.",
"createAccount": "Create account",
"registerSubtitle": "Sign up to create rooms and start meetings.",
"noAccount": "Don't have an account?",
"hasAccount": "Already have an account?",
"signUpNow": "Sign up now",
"signInNow": "Sign in now",
"backToHome": "← Back to homepage",
"emailPlaceholder": "name@example.com",
"passwordPlaceholder": "••••••••",
"namePlaceholder": "John Doe",
"minPassword": "At least 6 characters",
"confirmPassword": "Confirm password",
"repeatPassword": "Repeat password",
"passwordMismatch": "Passwords do not match",
"passwordTooShort": "Password must be at least 6 characters",
"loginSuccess": "Welcome back!",
"registerSuccess": "Registration successful!",
"loginFailed": "Login failed",
"registerFailed": "Registration failed",
"allFieldsRequired": "All fields are required"
},
"home": {
"poweredBy": "Powered by BigBlueButton",
"heroTitle": "Meetings re",
"heroTitleHighlight": "defined",
"heroSubtitle": "The modern, self-hosted BigBlueButton frontend. Create rooms, manage recordings and enjoy a beautiful interface with over 15 themes.",
"getStarted": "Get started",
"features": "Everything you need",
"featuresSubtitle": "Redlight provides all the features you need for professional video conferencing.",
"featureVideoTitle": "Video Conferencing",
"featureVideoDesc": "Create and manage meetings directly via BigBlueButton.",
"featureRoomsTitle": "Room Management",
"featureRoomsDesc": "Unlimited rooms with individual settings and access codes.",
"featureUsersTitle": "User Management",
"featureUsersDesc": "Registration, login and role management for your organization.",
"featureThemesTitle": "15+ Themes",
"featureThemesDesc": "Dracula, Nord, Catppuccin, Rosé Pine, Gruvbox and many more.",
"featureRecordingsTitle": "Recordings",
"featureRecordingsDesc": "View, publish or delete all recordings per room.",
"featureOpenSourceTitle": "Open Source",
"featureOpenSourceDesc": "Fully open source and self-hosted. Your data stays with you.",
"statThemes": "Themes",
"statRooms": "Rooms",
"statOpenSource": "Open Source",
"footer": "© {year} Redlight. An open source BigBlueButton frontend."
},
"dashboard": {
"myRooms": "My Rooms",
"roomCount": "{count} room created | {count} rooms created",
"newRoom": "New Room",
"noRooms": "No rooms yet",
"noRoomsSubtitle": "Create your first room to start meetings.",
"createFirst": "Create first room",
"createRoom": "Create new room",
"roomName": "Room name",
"roomNamePlaceholder": "e.g. Team Meeting",
"roomNameRequired": "Room name is required",
"welcomeMessage": "Welcome message",
"welcomeMessageDefault": "Welcome to the meeting!",
"maxParticipants": "Max. participants",
"maxParticipantsHint": "0 = unlimited",
"accessCode": "Access code",
"muteOnJoin": "Mute participants on join",
"allowRecording": "Allow recording",
"roomCreated": "Room created!",
"roomCreateFailed": "Room could not be created",
"roomDeleted": "Room deleted",
"roomDeleteFailed": "Room could not be deleted",
"roomDeleteConfirm": "Really delete room \"{name}\"?",
"loadFailed": "Rooms could not be loaded"
},
"room": {
"backToDashboard": "Back to Dashboard",
"start": "Start",
"startMeeting": "Start meeting",
"join": "Join",
"end": "End",
"openDetails": "Open details",
"overview": "Overview",
"recordings": "Recordings",
"settings": "Settings",
"participants": "{count} participants",
"copyLink": "Copy link",
"linkCopied": "Link copied!",
"meetingDetails": "Meeting details",
"meetingId": "Meeting ID",
"status": "Status",
"maxParticipants": "Max. participants",
"accessCode": "Access code",
"roomSettings": "Room settings",
"mutedOnJoin": "Muted on join",
"micActiveOnJoin": "Microphone active on join",
"approvalRequired": "Approval required",
"freeJoin": "Free join",
"allModerators": "All as moderator",
"rolesAssigned": "Roles are assigned",
"recordingAllowed": "Recording allowed",
"recordingDisabled": "Recording disabled",
"welcomeMsg": "Welcome message",
"muteOnJoin": "Mute on join",
"requireApproval": "Moderator approval required",
"anyoneCanStart": "Anyone can start the meeting",
"allJoinModerator": "All participants as moderator",
"allowRecording": "Allow recording",
"noAccessCode": "No access code",
"emptyNoCode": "Empty = no code",
"settingsSaved": "Settings saved",
"settingsSaveFailed": "Settings could not be saved",
"meetingStarted": "Meeting started!",
"meetingStartFailed": "Meeting could not be started",
"meetingEnded": "Meeting ended",
"meetingEndFailed": "Meeting could not be ended",
"joinFailed": "Join failed",
"endConfirm": "Really end meeting?",
"enterAccessCode": "Enter access code:",
"notFound": "Room not found"
},
"recordings": {
"title": "Recordings",
"noRecordings": "No recordings available",
"published": "Published",
"unpublished": "Unpublished",
"presentation": "Presentation",
"deleted": "Recording deleted",
"deleteFailed": "Error deleting recording",
"deleteConfirm": "Really delete recording?",
"publishSuccess": "Recording published",
"unpublishSuccess": "Recording unpublished",
"publishFailed": "Error updating recording",
"hide": "Hide",
"publish": "Publish",
"loadFailed": "Recordings could not be loaded"
},
"settings": {
"title": "Settings",
"subtitle": "Manage your profile and settings",
"profile": "Profile",
"password": "Password",
"themes": "Themes",
"language": "Language",
"editProfile": "Edit profile",
"avatar": "Profile picture",
"avatarColor": "Avatar color",
"avatarColorHint": "Used as fallback when no image is uploaded.",
"uploadImage": "Upload image",
"removeImage": "Remove image",
"avatarHint": "JPG, PNG, GIF or WebP. Max. 2 MB.",
"avatarUploaded": "Profile picture updated",
"avatarUploadFailed": "Error uploading image",
"avatarRemoved": "Profile picture removed",
"avatarRemoveFailed": "Error removing image",
"avatarInvalidType": "Only image files are allowed",
"avatarTooLarge": "Image must be less than 2 MB",
"changePassword": "Change password",
"currentPassword": "Current password",
"newPassword": "New password",
"confirmNewPassword": "Confirm new password",
"profileSaved": "Profile saved",
"profileSaveFailed": "Error saving profile",
"passwordChanged": "Password changed",
"passwordChangeFailed": "Error changing password",
"passwordMismatch": "Passwords do not match",
"selectLanguage": "Select language"
},
"themes": {
"selectTheme": "Select theme",
"selectThemeSubtitle": "Choose your preferred color scheme",
"light": "Light",
"dark": "Dark"
},
"admin": {
"title": "Administration",
"userCount": "{count} user registered | {count} users registered",
"searchUsers": "Search users...",
"user": "User",
"role": "Role",
"rooms": "Rooms",
"registered": "Registered",
"actions": "Actions",
"admin": "Admin",
"makeAdmin": "Make admin",
"makeUser": "Make user",
"resetPassword": "Reset password",
"deleteUser": "Delete",
"createUser": "Create user",
"createUserTitle": "Create new user",
"userCreated": "User created",
"userCreateFailed": "User could not be created",
"newPasswordLabel": "New password",
"resetPasswordTitle": "Reset password",
"noUsersFound": "No users found",
"roleUpdated": "Role updated",
"roleUpdateFailed": "Error updating role",
"userDeleted": "User deleted",
"userDeleteFailed": "Error deleting user",
"passwordReset": "Password reset",
"passwordResetFailed": "Error resetting password",
"deleteUserConfirm": "Really delete user \"{name}\"? All rooms will also be deleted."
}
}

42
src/i18n/index.js Normal file
View File

@@ -0,0 +1,42 @@
import en from './en.json';
import de from './de.json';
export const languages = {
en: { name: 'English', flag: '🇬🇧', translations: en },
de: { name: 'Deutsch', flag: '🇩🇪', translations: de },
};
export const defaultLanguage = 'en';
export function getTranslation(lang, key) {
const keys = key.split('.');
const translations = languages[lang]?.translations || languages[defaultLanguage].translations;
let value = translations;
for (const k of keys) {
value = value?.[k];
if (value === undefined) {
// Fallback to default language
let fallback = languages[defaultLanguage].translations;
for (const fk of keys) {
fallback = fallback?.[fk];
}
return fallback || key;
}
}
return value || key;
}
export function interpolate(template, params = {}) {
if (!template) return '';
return template.replace(/\{(\w+)\}/g, (_, key) => {
return params[key] !== undefined ? params[key] : `{${key}}`;
});
}
// Handle plural: "singular | plural" format
export function pluralize(template, count) {
if (!template) return '';
const parts = template.split(' | ');
if (parts.length === 1) return interpolate(template, { count });
return interpolate(count === 1 ? parts[0] : parts[1], { count });
}