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();