diff --git a/assets/i18n.json b/assets/i18n.json index 7488f13..1e00d9b 100644 --- a/assets/i18n.json +++ b/assets/i18n.json @@ -1,7 +1,7 @@ { "en": { "pageTitle": "Jitsi Meeting", - "impressum": "Legal Notice", + "legal_notice": "Legal Notice", "source": "Source", "launcher": { "title": "Jitsi Meeting", @@ -91,7 +91,7 @@ }, "de": { "pageTitle": "Jitsi Meeting öffnen", - "impressum": "Impressum", + "legal_notice": "Impressum", "source": "Quellcode", "launcher": { "title": "Jitsi Meeting", @@ -181,7 +181,7 @@ }, "fr": { "pageTitle": "Réunion Jitsi", - "impressum": "Mentions légales", + "legal_notice": "Mentions légales", "source": "Code source", "launcher": { "title": "Réunion Jitsi", @@ -271,7 +271,7 @@ }, "zh": { "pageTitle": "Jitsi 会议", - "impressum": "法律声明", + "legal_notice": "法律声明", "source": "源代码", "launcher": { "title": "Jitsi 会议", diff --git a/assets/launch.js b/assets/launch.js index af4f63d..fdaf642 100644 --- a/assets/launch.js +++ b/assets/launch.js @@ -1,13 +1,13 @@ "use strict"; /* ============================================================ - * KONFIGURATION + * CONFIGURATION * ============================================================ */ /* - * Konfiguration liegt in assets/config.json und wird beim - * Start asynchron geladen (siehe START). + * Configuration lives in assets/config.json and is loaded + * asynchronously at startup (see START). */ let CONFIG = {}; const CONFIG_URL = "assets/config.json"; @@ -18,8 +18,8 @@ const CONFIG_URL = "assets/config.json"; */ /* - * Sprachen liegen in assets/i18n.json und werden beim - * Start asynchron geladen (siehe START). + * Translations live in assets/i18n.json and are loaded + * asynchronously at startup (see START). */ let TRANSLATIONS = {}; const I18N_URL = "assets/i18n.json"; @@ -31,7 +31,7 @@ const I18N_URL = "assets/i18n.json"; function detectLanguage() { /* - * Gespeicherte Sprachwahl hat Vorrang. + * Saved language choice takes precedence. */ try { const saved = localStorage.getItem("jitsiLauncherLanguage"); @@ -41,7 +41,7 @@ function detectLanguage() { } } catch (error) { /* - * localStorage kann z.B. im Privat-Modus blockiert sein. + * localStorage can be blocked, e.g. in private mode. */ } @@ -62,7 +62,7 @@ function detectLanguage() { } /* - * Englisch ist der Standard/Fallback. + * English is the default/fallback. */ return "en"; } @@ -93,7 +93,7 @@ function applyTranslations() { }); /* - * Platzhalter von Eingabefeldern übersetzen. + * Translate placeholders of input fields. */ document .querySelectorAll("[data-i18n-placeholder]") @@ -107,13 +107,13 @@ function applyTranslations() { } /* - * Übersetzung anhand eines Pfades lesen. + * Read a translation by path. * - * Beispiel: + * Example: * * "downloads.windows.title" * - * ergibt: + * resolves to: * * translation.downloads.windows.title */ @@ -182,7 +182,7 @@ function setLanguage(language) { localStorage.setItem("jitsiLauncherLanguage", language); } catch (error) { /* - * localStorage kann z.B. im Privat-Modus blockiert sein. + * localStorage can be blocked, e.g. in private mode. */ } @@ -236,7 +236,7 @@ function savedTheme() { } } catch (error) { /* - * localStorage kann z.B. im Privat-Modus blockiert sein. + * localStorage can be blocked, e.g. in private mode. */ } @@ -249,7 +249,7 @@ function systemIsDark() { function detectTheme() { /* - * Gespeicherte Wahl hat Vorrang, sonst System-Einstellung. + * Saved choice takes precedence, otherwise system setting. */ return savedTheme() || (systemIsDark() ? "dark" : "light"); } @@ -273,7 +273,7 @@ function toggleTheme() { localStorage.setItem(THEME_KEY, currentTheme); } catch (error) { /* - * localStorage kann z.B. im Privat-Modus blockiert sein. + * localStorage can be blocked, e.g. in private mode. */ } @@ -315,8 +315,8 @@ function applyLogo() { */ /* - * Nur anzeigen, wenn eine Nextcloud-Instanz konfiguriert - * ist (CONFIG.nextcloudBase in assets/config.json). + * Only show if a Nextcloud instance is configured + * (CONFIG.nextcloudBase in assets/config.json). */ function applyNextcloudButton() { if (!CONFIG.nextcloudBase || !createNextcloudButton) { @@ -332,7 +332,7 @@ function applyNextcloudButton() { */ /* - * Nur anzeigen, wenn explizit aktiviert + * Only show if explicitly enabled * (CONFIG.allowAdhocMeetings in assets/config.json). */ function applyAdhocButton() { @@ -421,11 +421,11 @@ const themeIconMoon = document.getElementById("themeIconMoon"); * GET ROOM FROM QUERY-PARAMETER * ============================================================ * - * Beispiel: + * Example: * * https:///launch.html?room=abc-defg-hij * - * ergibt: + * results in: * * abc-defg-hij * @@ -438,16 +438,16 @@ function getRoom() { ); /* - * Parameter nicht vorhanden. + * Parameter missing. */ if (!params.has("room")) { return null; } /* - * Leerzeichen etc. verhindern. + * Prevent whitespace etc. * - * "?room=" ohne Wert ergibt einen leeren String. + * "?room=" without a value yields an empty string. */ return params.get("room").trim(); } @@ -456,11 +456,11 @@ function getRoom() { * VALIDATE ROOM * ============================================================ * - * Jitsi-Roomnamen können je nach Konfiguration relativ - * unterschiedliche Zeichen enthalten. + * Jitsi room names can contain rather different + * characters depending on the configuration. * - * Wir verhindern hier lediglich Steuerzeichen und - * offensichtliche URL-Manipulation. + * We only prevent control characters and obvious + * URL manipulation here. * * ============================================================ */ @@ -473,7 +473,7 @@ function isValidRoom(room) { } /* - * Nur Buchstaben, Zahlen und Bindestriche. + * Only letters, numbers and hyphens. */ return ROOM_PATTERN.test(room); } @@ -511,10 +511,10 @@ function createAppUrl(room) { * ============================================================ */ /* - * Die Android-App registriert "org.jitsi.meet", nicht das - * Desktop-Schema. Chrome & Co. verlangen intent://, ohne App - * bietet Chrome den Play-Store-Eintrag aus "package=" an. - * Firefox ignoriert intent://, kennt aber das Custom-Schema. + * The Android app registers "org.jitsi.meet", not the + * desktop scheme. Chrome & co. require intent://; without + * the app Chrome offers the Play Store entry from "package=". + * Firefox ignores intent:// but knows the custom scheme. */ function createAndroidIntentUrl(room) { @@ -528,8 +528,8 @@ function createAndroidIntentUrl(room) { } /* - * Die Mobile-Apps (iOS, Firefox auf Android) registrieren - * "org.jitsi.meet", nicht das Desktop-Schema. + * The mobile apps (iOS, Firefox on Android) register + * "org.jitsi.meet", not the desktop scheme. */ const APP_SCHEME = "org.jitsi.meet://"; @@ -543,14 +543,14 @@ function createAppSchemeUrl(room) { } /* ============================================================ - * URL NACH PLATTFORM/BROWSER + * URL BY PLATFORM/BROWSER * ============================================================ */ function selectProtocolUrl(room) { const os = detectOperatingSystem(); /* - * iOS-App kennt nur das App-Schema. + * The iOS app only knows the app scheme. */ if (os === "ios") { return createAppSchemeUrl(room); @@ -571,8 +571,8 @@ function selectProtocolUrl(room) { */ /* - * Einmal parsen (assets/lib/ua-parser.min.js). - * Fällt die Bibliothek aus, bleibt die Seite bedienbar. + * Parse once (assets/lib/ua-parser.min.js). + * If the library is missing, the page stays usable. */ const uaResult = typeof UAParser === "function" @@ -580,8 +580,8 @@ const uaResult = : { os: {}, browser: {} }; /* - * UAParser-Namen -> interne Kürzel. - * iPadOS meldet sich als macOS – Touchpunkte gelten als iOS. + * UAParser names -> internal shortcuts. + * iPadOS reports as macOS – touch points count as iOS. */ const OS_NAMES = { Android: "android", @@ -614,9 +614,9 @@ const BROWSER_NAMES = { function detectOperatingSystem() { /* - * Chrome im "Desktop-Modus" auf Android sendet einen - * Desktop-UA (z.B. X11; Linux). Client Hints verraten - * das echte System. Firefox/Safari kennen sie nicht. + * Chrome in "desktop mode" on Android sends a desktop + * UA (e.g. X11; Linux). Client hints reveal the real + * system. Firefox/Safari do not support them. */ const hints = navigator.userAgentData; if (hints && hints.platform === "Android") { @@ -624,8 +624,9 @@ function detectOperatingSystem() { } /* - * iPadOS tarnt sich als macOS – Touchpunkte verraten das iPad. - * Der Plattform-Check greift auch ohne geladenen ua-parser. + * iPadOS masquerades as macOS – touch points give the + * iPad away. The platform check also works without the + * ua-parser loaded. */ if ( navigator.maxTouchPoints > 1 && @@ -638,7 +639,7 @@ function detectOperatingSystem() { } /* ============================================================ - * BROWSER ERKENNEN + * DETECT BROWSER * ============================================================ */ @@ -696,12 +697,12 @@ function configureDownloads() { return; } - /* Alles andere */ + /* Everything else */ otherDownload.classList.remove("hidden"); } /* ============================================================ - * FALLBACK ANZEIGEN + * SHOW FALLBACK * ============================================================ */ @@ -714,7 +715,7 @@ function showFallback() { } /* ============================================================ - * JITSI DESKTOP STARTEN + * START JITSI DESKTOP * ============================================================ */ @@ -729,12 +730,12 @@ function launchJitsi() { const browserUrl = createBrowserUrl(room); /* - * Browser-Fallback vorbereiten. + * Prepare the browser fallback. */ browserLink.href = browserUrl; /* - * UI auf "Startvorgang" setzen. + * Set the UI to the "starting" state. */ launcher.classList.add("hidden"); fallback.classList.add("hidden"); @@ -744,13 +745,13 @@ function launchJitsi() { let timeout = null; /* - * Unsichtbarer iframe für den Protocol Handler. + * Invisible iframe for the protocol handler. */ const iframe = document.createElement("iframe"); iframe.style.display = "none"; /* - * Aufräumen. + * Clean up. */ function cleanup() { if (completed) { @@ -768,25 +769,25 @@ function launchJitsi() { /* * -------------------------------------------------------- - * WICHTIG: + * IMPORTANT: * - * Kein "blur" mehr als Erfolgskriterium verwenden. + * Do not use "blur" as a success criterion anymore. * - * Der Browser kann beim Protocol-Dialog ebenfalls - * blur auslösen. + * The browser can also trigger blur when showing + * the protocol dialog. * - * Wenn der Benutzer den Dialog abbricht, bleibt die - * Seite sichtbar und unser Timeout läuft weiter. + * If the user cancels the dialog, the page stays + * visible and our timeout keeps running. * -------------------------------------------------------- */ document.body.appendChild(iframe); /* - * Protocol Handler starten. + * Start the protocol handler. * - * Firefox ignoriert externe Protokolle aus iframes – - * das Custom-Schema direkt in der Hauptnavigation starten. + * Firefox ignores external protocols from iframes – + * start the custom scheme directly in the main navigation. */ try { if (protocolUrl.startsWith(APP_SCHEME)) { @@ -811,19 +812,19 @@ function launchJitsi() { * FALLBACK * -------------------------------------------------------- * - * Wir warten unabhängig davon, ob der Browser kurz - * den Fokus verliert. + * We wait regardless of whether the browser briefly + * loses focus. * - * Wenn Jitsi erfolgreich startet: + * If Jitsi starts successfully: * - * -> Jitsi kommt in den Vordergrund - * -> Benutzer sieht diese Seite nicht mehr + * -> Jitsi comes to the foreground + * -> the user no longer sees this page * - * Wenn der Benutzer den Browser-Dialog abbricht: + * If the user cancels the browser dialog: * - * -> diese Seite bleibt sichtbar - * -> Timeout läuft ab - * -> Fallback wird angezeigt + * -> this page stays visible + * -> the timeout expires + * -> the fallback is shown * -------------------------------------------------------- */ @@ -843,13 +844,13 @@ function launchJitsi() { */ /* - * Sonderzeichen in das nächstliegende ASCII-Zeichen - * umwandeln. + * Convert special characters to the closest ASCII + * character. * - * Beispiel: + * Example: * - * "Grundstück" ergibt "Grundstueck" - * "François" ergibt "Francois" + * "Grundstück" becomes "Grundstueck" + * "François" becomes "Francois" */ function transliterate(text) { const extras = { @@ -870,11 +871,11 @@ function transliterate(text) { } /* - * Aus dem Meeting-Namen einen gültigen Room-Namen ableiten. + * Derive a valid room name from the meeting name. * - * Beispiel: + * Example: * - * "Grund Stück Planung" ergibt "grund-stuck-planung" + * "Grund Stück Planung" becomes "grund-stuck-planung" */ function createRoomName(meetingName) { return transliterate(meetingName) @@ -954,12 +955,12 @@ const MINUTES_STEP = 15; const DEFAULT_DURATION_MINUTES = 30; /* - * Minuten auf das 15-Minuten-Raster aufrunden. + * Round minutes up to the 15-minute grid. * - * Beispiel: + * Example: * - * 14:07 ergibt 14:15 - * 14:50 ergibt 15:00 + * 14:07 becomes 14:15 + * 14:50 becomes 15:00 */ function roundMinutes(date) { const rounded = new Date(date); @@ -973,11 +974,11 @@ function roundMinutes(date) { } /* - * Lokale Zeit für datetime-local formatieren. + * Format local time for datetime-local. * - * Beispiel: + * Example: * - * 1. Januar 2026, 14:15 ergibt "2026-01-01T14:15" + * January 1, 2026, 14:15 becomes "2026-01-01T14:15" */ function formatLocal(date) { const pad = (num) => num.toString().padStart(2, "0"); @@ -1066,8 +1067,8 @@ function openNextcloudCalendarCreator() { } /* - * Nextcloud validiert die E-Mail serverseitig und - * bricht sonst mit einer Fehlerseite ab. + * Nextcloud validates the email server-side and + * aborts with an error page otherwise. */ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; @@ -1177,17 +1178,17 @@ function createMeeting() { */ /* - * Zeitspanne, nach der der "Kopiert!"-Text zum - * normalen Button-Label zurückkehrt. + * Time span after which the "Copied!" text returns + * to the normal button label. */ const COPY_RESET_MS = 2000; /* - * Launch-URL, die das Meeting im Jitsi-Client öffnet. + * Launch URL that opens the meeting in the Jitsi client. * - * Beispiel: + * Example: * - * "projekt-kickoff" ergibt + * "projekt-kickoff" becomes * https://meet.bnvb.eu/launch.html?room=projekt-kickoff */ function createLaunchUrl(roomName) { @@ -1221,7 +1222,7 @@ function createAdhocLink() { async function copyAdhocLink() { const url = adhocLink.href; - /* Clipboard-API, mit Fallback für ältere Browser */ + /* Clipboard API, with a fallback for older browsers */ try { await navigator.clipboard.writeText(url); } catch (error) { @@ -1233,7 +1234,7 @@ async function copyAdhocLink() { document.body.removeChild(input); } - /* Kurzes Feedback, dann zurück zum normalen Label */ + /* Brief feedback, then back to the normal label */ copyAdhocLinkButton.textContent = getTranslation('creator.copiedLabel') || 'Copied!'; setTimeout(() => { copyAdhocLinkButton.textContent = getTranslation('creator.copyLinkButton') || 'Copy Link'; @@ -1255,7 +1256,7 @@ function cancelCreateMeeting() { } /* ============================================================ - * INITIALISIERUNG + * INITIALIZATION * ============================================================ */ @@ -1263,8 +1264,8 @@ function initialize() { const room = getRoom(); /* - * Kein room-Parameter: Meldungen ausblenden, - * nur den Create-Button anzeigen. + * No room parameter: hide the messages, + * only show the Create button. */ if (room === null) { missingRoomTitle.classList.add("hidden"); @@ -1277,8 +1278,8 @@ function initialize() { } /* - * Leerer room-Parameter ("?room="): - * "No meeting specified" anzeigen. + * Empty room parameter ("?room="): + * show "No meeting specified". */ if (room === "") { missingRoomTitle.classList.remove("hidden"); @@ -1291,8 +1292,8 @@ function initialize() { } /* - * Ungültiger room-Parameter: nur die - * Invalid-Name-Meldung anzeigen. + * Invalid room parameter: only show + * the invalid-name message. */ if (!isValidRoom(room)) { missingRoomTitle.classList.add("hidden"); @@ -1306,17 +1307,17 @@ function initialize() { } /* - * Room anzeigen. + * Show the room. */ roomName.textContent = room; /* - * Browser-Fallback vorbereiten. + * Prepare the browser fallback. */ browserLink.href = createBrowserUrl(room); /* - * Launcher anzeigen. + * Show the launcher. */ launcher.classList.remove("hidden"); @@ -1326,7 +1327,7 @@ function initialize() { createNewFromLauncherButton.classList.add("hidden"); /* - * Debug-Informationen. + * Debug information. */ console.log( "Jitsi Launcher", @@ -1441,7 +1442,7 @@ navToggle.addEventListener( ); /* - * Ohne gespeicherte Wahl dem System folgen. + * Follow the system if no choice is saved. */ window .matchMedia("(prefers-color-scheme: dark)") @@ -1463,8 +1464,8 @@ async function loadConfig() { CONFIG = await response.json(); } catch (error) { /* - * Ohne Datei bleiben Launcher unbrauchbar – Fehler - * melden und mit leerer Konfiguration fortfahren. + * Without the file the launcher stays unusable – + * report the error and continue with an empty config. */ console.error("Error loading config:", error); } @@ -1476,7 +1477,7 @@ async function loadTranslations() { TRANSLATIONS = await response.json(); } catch (error) { /* - * Ohne Datei bleibt der englische Text aus dem HTML stehen. + * Without the file the English text from the HTML stays. */ console.error("Error loading translations:", error); } @@ -1486,8 +1487,8 @@ async function loadTranslations() { } /* - * Theme/Logo sofort anwenden, damit nichts aufblinkt – - * Text erst nach dem Laden der Übersetzungen. + * Apply theme/logo immediately so nothing flickers – + * text only after the translations have loaded. */ async function start() { applyTheme(); diff --git a/launch.html b/launch.html index 08076f7..7414f69 100644 --- a/launch.html +++ b/launch.html @@ -31,8 +31,8 @@