diff --git a/assets/cn.svg b/assets/cn.svg new file mode 100644 index 0000000..aca1409 --- /dev/null +++ b/assets/cn.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/launch.html b/launch.html index c0daf3b..3e49872 100644 --- a/launch.html +++ b/launch.html @@ -7,7 +7,11 @@ @@ -306,8 +318,18 @@ + +
@@ -831,6 +853,89 @@ organizerRequired: "Veuillez entrer un email d'organisateur", organizerInvalid: "Veuillez entrer une adresse email d'organisateur valide" } + }, + + zh: { + pageTitle: "Jitsi 会议", + impressum: "法律声明", + + launcher: { + title: "Jitsi 会议", + meetingPrefix: "会议", + meetingSuffix: "即将打开。", + openButton: "加入会议" + }, + + starting: { + title: "正在启动 Jitsi Meet …", + status: "正在打开 Jitsi Meet 桌面客户端。" + }, + + fallback: { + title: "无法打开 Jitsi Meet", + description: "Jitsi Meet 桌面客户端可能尚未启动。", + retryButton: "重试" + }, + + downloads: { + windows: { + title: "适用于 Windows 的 Jitsi Meet", + description: "请安装 Jitsi Meet 桌面客户端,然后再次尝试打开会议。", + button: "下载 Windows 版 Jitsi" + }, + + macos: { + title: "适用于 macOS 的 Jitsi Meet", + description: "请安装 Jitsi Meet 桌面客户端,然后再次尝试打开会议。", + button: "下载 macOS 版 Jitsi" + }, + + linux: { + title: "适用于 Linux 的 Jitsi Meet", + description: "请安装 Jitsi Meet 桌面客户端,然后再次尝试打开会议。", + button: "下载 Linux 版 Jitsi" + }, + + other: { + title: "Jitsi Meet 桌面版", + description: "没有为您的操作系统配置桌面版下载。" + } + }, + + browser: { + title: "在浏览器中加入", + description: "您也可以直接通过浏览器加入会议。", + button: "在浏览器中加入" + }, + + error: { + title: "未指定会议", + description: "请使用参数指定会议", + descriptionSuffix: "。", + invalidTitle: "会议名称无效", + invalidDescription: "仅允许使用字母、数字和连字符:" + }, + + creator: { + title: "创建新会议", + meetingNameLabel: "会议名称", + meetingDescriptionLabel: "描述", + meetingStartLabel: "开始日期和时间", + meetingEndLabel: "结束日期和时间", + meetingOrganizerLabel: "组织者邮箱", + createButton: "创建会议", + cancelButton: "取消", + successMessage: "会议已创建!请下载您的日历文件。", + downloadButton: "下载 ICS 文件", + createNewButton: "创建新会议", + meetingNameRequired: "请输入会议名称", + meetingNameInvalid: "会议名称只能包含字母、数字和连字符", + startTimeRequired: "请选择开始时间", + endTimeRequired: "请选择结束时间", + endAfterStart: "结束时间必须晚于开始时间", + organizerRequired: "请输入组织者邮箱", + organizerInvalid: "请输入有效的组织者邮箱地址" + } } }; @@ -940,13 +1045,15 @@ const LANGUAGE_NAMES = { en: "English", de: "Deutsch", - fr: "Français" + fr: "Français", + zh: "中文" }; const LANGUAGE_FLAGS = { en: "assets/uk.svg", de: "assets/de.svg", - fr: "assets/fr.svg" + fr: "assets/fr.svg", + zh: "assets/cn.svg" }; function updateLanguageMenu() { @@ -997,6 +1104,66 @@ langButton.setAttribute("aria-expanded", "false"); } + /* ============================================================ + * THEME + * ============================================================ + */ + + const THEME_KEY = "jitsiLauncherTheme"; + + function savedTheme() { + try { + const saved = localStorage.getItem(THEME_KEY); + + if (saved === "dark" || saved === "light") { + return saved; + } + } catch (error) { + /* + * localStorage kann z.B. im Privat-Modus blockiert sein. + */ + } + + return null; + } + + function systemIsDark() { + return window.matchMedia("(prefers-color-scheme: dark)").matches; + } + + function detectTheme() { + /* + * Gespeicherte Wahl hat Vorrang, sonst System-Einstellung. + */ + return savedTheme() || (systemIsDark() ? "dark" : "light"); + } + + let currentTheme = detectTheme(); + + function updateThemeToggle() { + themeIconSun.classList.toggle("hidden", currentTheme !== "dark"); + themeIconMoon.classList.toggle("hidden", currentTheme === "dark"); + } + + function applyTheme() { + document.documentElement.classList.toggle("dark", currentTheme === "dark"); + updateThemeToggle(); + } + + function toggleTheme() { + currentTheme = currentTheme === "dark" ? "light" : "dark"; + + try { + localStorage.setItem(THEME_KEY, currentTheme); + } catch (error) { + /* + * localStorage kann z.B. im Privat-Modus blockiert sein. + */ + } + + applyTheme(); + } + /* ============================================================ * BACKGROUND IMAGE * ============================================================ @@ -1079,6 +1246,11 @@ const logoBar = document.getElementById("logoBar"); const logoImage = document.getElementById("logoImage"); + /* Theme DOM elements */ + const themeButton = document.getElementById("themeButton"); + const themeIconSun = document.getElementById("themeIconSun"); + const themeIconMoon = document.getElementById("themeIconMoon"); + /* ============================================================ * GET ROOM FROM QUERY-PARAMETER * ============================================================ @@ -1683,10 +1855,6 @@ END:VCALENDAR`; * Kein room-Parameter: Meldungen ausblenden, * nur den Create-Button anzeigen. */ - /* - * Kein room-Parameter: keine Fehlermeldung, - * nur den Create-Button anzeigen. - */ if (room === null) { missingRoomTitle.classList.add("hidden"); missingRoomError.classList.add("hidden"); @@ -1827,12 +1995,31 @@ END:VCALENDAR`; } ); + /* Theme events */ + themeButton.addEventListener( + "click", + toggleTheme + ); + + /* + * Ohne gespeicherte Wahl dem System folgen. + */ + window + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener("change", () => { + if (savedTheme() === null) { + currentTheme = systemIsDark() ? "dark" : "light"; + applyTheme(); + } + }); + /* ============================================================ * START * ============================================================ */ applyTranslations(); + applyTheme(); applyBackgroundImage(); applyLogo(); updateLanguageMenu();