diff options
author | metamuffin <metamuffin@disroot.org> | 2024-04-01 16:48:57 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-04-01 16:48:57 +0200 |
commit | 45529def628fd1562bef262312649b428bbdb0f7 (patch) | |
tree | 4d7e54e9d45bbf91c1fe2247bf379e0e879cd89b /client-web | |
parent | 5cd88136c70dc3aae12778180b7f1c2c568a00fc (diff) | |
download | keks-meet-45529def628fd1562bef262312649b428bbdb0f7.tar keks-meet-45529def628fd1562bef262312649b428bbdb0f7.tar.bz2 keks-meet-45529def628fd1562bef262312649b428bbdb0f7.tar.zst |
localization
Diffstat (limited to 'client-web')
-rw-r--r-- | client-web/source/chat.ts | 15 | ||||
-rw-r--r-- | client-web/source/download_stream.ts | 3 | ||||
-rw-r--r-- | client-web/source/helper.ts | 3 | ||||
-rw-r--r-- | client-web/source/index.ts | 18 | ||||
-rw-r--r-- | client-web/source/keybinds.ts | 3 | ||||
-rw-r--r-- | client-web/source/locale/en.ts | 63 | ||||
-rw-r--r-- | client-web/source/locale/mod.ts | 72 | ||||
-rw-r--r-- | client-web/source/logger.ts | 2 | ||||
-rw-r--r-- | client-web/source/menu.ts | 25 | ||||
-rw-r--r-- | client-web/source/preferences/decl.ts | 9 | ||||
-rw-r--r-- | client-web/source/preferences/ui.ts | 11 | ||||
-rw-r--r-- | client-web/source/resource/file.ts | 21 | ||||
-rw-r--r-- | client-web/source/resource/track.ts | 13 | ||||
-rw-r--r-- | client-web/source/room_watches.ts | 17 | ||||
-rw-r--r-- | client-web/source/user/mod.ts | 5 | ||||
-rw-r--r-- | client-web/source/user/remote.ts | 19 |
16 files changed, 227 insertions, 72 deletions
diff --git a/client-web/source/chat.ts b/client-web/source/chat.ts index 5d911c6..1eb9fa5 100644 --- a/client-web/source/chat.ts +++ b/client-web/source/chat.ts @@ -7,6 +7,7 @@ import { ChatMessage } from "../../common/packets.d.ts"; import { e, image_view, notify } from "./helper.ts"; +import { PO } from "./locale/mod.ts"; import { log } from "./logger.ts"; import { chat_control } from "./menu.ts"; import { PREFS } from "./preferences/mod.ts"; @@ -30,15 +31,15 @@ export class Chat { constructor() { const send = document.createElement("input") - send.ariaLabel = "send message" + send.ariaLabel = PO.chatbox_label send.type = "text" - send.placeholder = "Type a message" + send.placeholder = PO.chatbox_placeholder const messages = e("div", { class: "messages", aria_live: "polite" }) const controls = e("div", { class: "controls" }) controls.append(send) - this.element = e("section", { class: "chat", aria_label: "chat", role: "dialog" }, messages, controls) + this.element = e("section", { class: "chat", aria_label: PO.chat, role: "dialog" }, messages, controls) this.messages = messages this.controls = controls this.send_el = send @@ -85,7 +86,9 @@ export class Chat { } add_control_message(m: ControlMessage) { - const el = e("div", { class: ["message", "control-message"] }, e("span", { class: "author" }, m.join?.display_name ?? m.leave?.display_name ?? ""), ` ${m.join ? "joined" : "left"} the room.`) + const el = e("div", { class: ["message", "control-message"] }, + ...(m.join ? PO.join_message : PO.leave_message)(e("span", { class: "author" }, m.join?.display_name ?? m.leave?.display_name ?? "")) + ) this.messages.append(el) el.scrollIntoView({ block: "end", behavior: "smooth", inline: "end" }) } @@ -100,9 +103,9 @@ export class Chat { this.messages.append(el) el.scrollIntoView({ block: "end", behavior: "smooth", inline: "end" }) - let body_str = "(empty message)" + let body_str = PO.summary_empty_message if (message.text) body_str = message.text - if (message.image) body_str = "(image)" + if (message.image) body_str = PO.summery_image if (!(sender instanceof LocalUser) && PREFS.notify_chat) notify(body_str, sender.display_name) } } diff --git a/client-web/source/download_stream.ts b/client-web/source/download_stream.ts index 7159725..330fcb2 100644 --- a/client-web/source/download_stream.ts +++ b/client-web/source/download_stream.ts @@ -4,11 +4,12 @@ Copyright (C) 2023 metamuffin <metamuffin.org> */ /// <reference lib="dom" /> +import { PO } from "./locale/mod.ts"; import { log } from "./logger.ts" import { send_sw_message, SW_ENABLED } from "./sw/client.ts" function FallbackStreamDownload(size: number, filename?: string, progress?: (position: number) => void) { - log({ scope: "*", warn: true }, "downloading to memory because serviceworker is not available") + log({ scope: "*", warn: true }, PO.warn_mem_download) let position = 0 let buffer = new Uint8Array(size) return { diff --git a/client-web/source/helper.ts b/client-web/source/helper.ts index 3676ca3..70b7c28 100644 --- a/client-web/source/helper.ts +++ b/client-web/source/helper.ts @@ -5,6 +5,7 @@ */ /// <reference lib="dom" /> +import { PO } from "./locale/mod.ts"; import { PREFS } from "./preferences/mod.ts"; interface Opts<E> { @@ -63,7 +64,7 @@ export function image_view(url: string, opts?: Opts<HTMLElement>): HTMLElement { const img = document.createElement("img") apply_opts(img, opts ?? {}) img.src = url - img.alt = `Image (click to open)` + img.alt = PO.image_alt img.addEventListener("click", () => { globalThis.open(url, "_blank", `noreferrer=true,noopener=true,popup=${PREFS.image_view_popup}`) }) diff --git a/client-web/source/index.ts b/client-web/source/index.ts index 237db60..b4dd11c 100644 --- a/client-web/source/index.ts +++ b/client-web/source/index.ts @@ -14,6 +14,8 @@ import { SignalingConnection } from "./protocol/mod.ts"; import { Room } from "./room.ts" import { control_bar, info_br } from "./menu.ts"; import { Chat } from "./chat.ts" +import { init_locale } from "./locale/mod.ts"; +import { PO } from "./locale/mod.ts"; export const VERSION = "1.0.3" globalThis.onload = () => main() @@ -52,7 +54,7 @@ function set_room(state: AppState, secret: string, rtc_config: RTCConfiguration) state.center.removeChild(state.room.element) state.room.destroy() } - if (secret.length < 8) log({ scope: "crypto", warn: true }, "Room name is very short. E2EE is insecure!") + if (secret.length < 8) log({ scope: "crypto", warn: true }, PO.warn_short_secret) if (secret.split("#").length > 1) document.title = `${secret.split("#")[0]} | keks-meet` state.room = new Room(state.conn, state.chat, rtc_config) state.chat.room = state.room @@ -70,13 +72,15 @@ export async function main() { const config: ClientConfig = await config_res.json() log("*", "config loaded. starting") + init_locale(PREFS.language ?? "en-US") + document.body.querySelectorAll(".loading").forEach(e => e.remove()) - if (!globalThis.isSecureContext) log({ scope: "*", warn: true }, "This page is not a 'Secure Context'") - if (!globalThis.RTCPeerConnection) return log({ scope: "webrtc", error: true }, "WebRTC not supported.") - if (!globalThis.crypto.subtle) return log({ scope: "crypto", error: true }, "SubtleCrypto not availible") - if (!globalThis.navigator.serviceWorker) log({ scope: "*", warn: true }, "Your browser does not support the Service Worker API, forced automatic updates are unavoidable.") - if (PREFS.warn_redirect) log({ scope: "crypto", warn: true }, "You were redirected from the old URL format. The server knows the room secret now - E2EE is insecure!") + if (!globalThis.isSecureContext) log({ scope: "*", warn: true }, PO.warn_secure_context) + if (!globalThis.RTCPeerConnection) return log({ scope: "webrtc", error: true }, PO.warn_no_webrtc) + if (!globalThis.crypto.subtle) return log({ scope: "crypto", error: true }, PO.warn_no_crypto) + if (!globalThis.navigator.serviceWorker) log({ scope: "*", warn: true }, PO.warn_no_sw) + if (PREFS.warn_redirect) log({ scope: "crypto", warn: true }, PO.warn_old_url) const sud = e("div", { class: "side-ui" }) const state: AppState = { @@ -108,7 +112,7 @@ export async function main() { globalThis.onbeforeunload = ev => { if (state.room && state.room.local_user.resources.size != 0 && PREFS.enable_onbeforeunload) { ev.preventDefault() - return "You have local resources shared. Really quit?" + return PO.confirm_quit } } diff --git a/client-web/source/keybinds.ts b/client-web/source/keybinds.ts index 047fe97..9284b92 100644 --- a/client-web/source/keybinds.ts +++ b/client-web/source/keybinds.ts @@ -6,6 +6,7 @@ /// <reference lib="dom" /> import { AppState } from "./index.ts"; +import { PO } from "./locale/mod.ts"; import { chat_control } from "./menu.ts"; import { create_camera_res, create_mic_res, create_screencast_res } from "./resource/track.ts"; import { update_serviceworker } from "./sw/client.ts"; @@ -24,7 +25,7 @@ export function setup_keybinds(state: AppState) { if (ev.code == "KeyS") state.room?.local_user.await_add_resource(create_screencast_res()) if (ev.code == "KeyC" && !ev.ctrlKey) state.room?.local_user.await_add_resource(create_camera_res()) if (ev.code == "KeyC" && ev.ctrlKey) state.room?.local_user.resources.forEach(t => t.destroy()) - if (ev.code == "KeyU") if (globalThis.confirm("really update?")) update_serviceworker() + if (ev.code == "KeyU") if (globalThis.confirm(PO.confirm_update)) update_serviceworker() if (ev.code == "KeyV") state.chat?.remove_oldest_message() } }) diff --git a/client-web/source/locale/en.ts b/client-web/source/locale/en.ts new file mode 100644 index 0000000..da9d949 --- /dev/null +++ b/client-web/source/locale/en.ts @@ -0,0 +1,63 @@ +import { LanguageStrings } from "./mod.ts"; + +export const PO_EN_US: LanguageStrings = { + microphone: "Microphone", + chatbox_placeholder: "Type a message", + chatbox_label: "send message", + join_message: author => [author, " joined."], + leave_message: author => [author, " left."], + summary_empty_message: "(empty message)", + summery_image: "(image)", + camera: "Camera", + file: "File", + leave: "Leave", + screen: "Screen", + image_alt: "Image (click to open)", + warn_mem_download: "Downloading to memory because serviceworker is not available.", + confirm_update: "Really update?", + warn_short_secret: "Room name is very short. E2EE is insecure!", + warn_secure_context: "This page is not a 'Secure Context'", + warn_no_webrtc: "WebRTC not supported.", + warn_no_crypto: "SubtleCrypto not availible", + warn_no_sw: "Your browser does not support the Service Worker API, forced automatic updates are unavoidable.", + warn_old_url: "You were redirected from the old URL format. The server knows the room secret now - E2EE is insecure!", + confirm_quit: "You have local resources shared. Really quit?", + controls: "Controls", + license: "License", + source_code: "Source code", + stop_sharing: "Stop sharing", + documentation: "Documentation", + known_rooms: "Known Rooms", + chat: "Chat", + settings: "Settings", + edit: "Edit", + finish_edit: "Finish edit", + add_current_room: "Add current room", + add: "Add", + move_down: "Move down", + move_up: "Move up", + unknown_user: "Unknown user", + status_checking: "Checking...", + status_connected: "Connected", + status_failed: "Connection failed", + status_disconnected: "Disconnected", + status_no_conn: "Not connected", + status_await_channel_open: "Waiting for data channel to open…", + status_await_channel_close: "Waiting for data channel to close…", + downloading: "Downloading…", + download_again: "Download again", + download: "Download", + status_drain_buffer: amount => `Draining buffers… (buffer: ${amount})`, + status_buffering: "Buffering…", + status_closing: "Channel closing…", + mute: "Mute", + video_stream: "video stream", + audio_stream: "video stream", + disable: "Disable", + enable: "Enable", + status_await_track: "Awaiting track…", + notification_perm_explain: "For keks-meet to send notifications, it needs you to grant permission: ", + grant: "Grant", + clear_prefs: "Want to clear all settings? Use this:", + +} diff --git a/client-web/source/locale/mod.ts b/client-web/source/locale/mod.ts new file mode 100644 index 0000000..4febcec --- /dev/null +++ b/client-web/source/locale/mod.ts @@ -0,0 +1,72 @@ +import { PO_EN_US } from "./en.ts"; + +export let PO: LanguageStrings; + +export function init_locale(lang: string) { + PO = LOCALES[lang] +} + +export const LOCALES: { [key: string]: LanguageStrings } = { + "en-US": PO_EN_US +} + +export interface LanguageStrings { + microphone: string, + camera: string, + screen: string, + file: string, + warn_short_secret: string, + warn_no_webrtc: string, + warn_secure_context: string, + warn_no_crypto: string, + warn_no_sw: string, + warn_old_url: string, + warn_mem_download: string, + chatbox_placeholder: string, + chatbox_label: string, + confirm_quit: string, + controls: string, + license: string, + source_code: string, + documentation: string, + chat: string, + settings: string, + known_rooms: string, + leave: string, + confirm_update: string, + image_alt: string, + join_message(author: HTMLElement | string): (HTMLElement | string)[], + leave_message(author: HTMLElement | string): (HTMLElement | string)[], + summary_empty_message: string, + summery_image: string, + edit: string, + finish_edit: string, + add_current_room: string, + add: string, + move_up: string, + move_down: string, + unknown_user: string, + status_connected: string, + status_no_conn: string, + status_checking: string, + status_disconnected: string, + status_failed: string, + downloading: string, + download: string, + download_again: string, + stop_sharing: string, + status_await_channel_open: string, + status_await_channel_close: string, + status_drain_buffer(amount: number): string, + status_buffering: string, + status_closing: string, + mute: string, + video_stream: string, + audio_stream: string, + enable: string, + disable: string, + notification_perm_explain: string, + grant: string, + status_await_track: string, + clear_prefs: string, +} diff --git a/client-web/source/logger.ts b/client-web/source/logger.ts index 8ff4cab..210644a 100644 --- a/client-web/source/logger.ts +++ b/client-web/source/logger.ts @@ -60,4 +60,4 @@ globalThis.addEventListener("load", () => { globalThis.onerror = (_ev, source, line, col, err) => { log({ scope: "*", error: true }, `${err?.name} ${err?.message}`, err) log({ scope: "*", error: true }, `on ${source}:${line}:${col}`, err) -}
\ No newline at end of file +} diff --git a/client-web/source/menu.ts b/client-web/source/menu.ts index 5cd9e7a..dd9e4d4 100644 --- a/client-web/source/menu.ts +++ b/client-web/source/menu.ts @@ -8,6 +8,7 @@ import { e, sleep } from "./helper.ts" import { AppState } from "./index.ts"; import { VERSION } from "./index.ts" +import { PO } from "./locale/mod.ts"; import { ui_preferences } from "./preferences/ui.ts" import { create_file_res } from "./resource/file.ts"; import { create_camera_res, create_mic_res, create_screencast_res } from "./resource/track.ts"; @@ -28,30 +29,30 @@ export function info_br() { return e("footer", { class: "info-br" }, e("p", { class: "version" }, `keks-meet ${VERSION}`), - item("License", "https://codeberg.org/metamuffin/keks-meet/raw/branch/master/COPYING"), - item("Source code", "https://codeberg.org/metamuffin/keks-meet"), - item("Documentation", "https://codeberg.org/metamuffin/keks-meet/src/branch/master/readme.md"), + item(PO.license, "https://codeberg.org/metamuffin/keks-meet/raw/branch/master/COPYING"), + item(PO.source_code, "https://codeberg.org/metamuffin/keks-meet"), + item(PO.documentation, "https://codeberg.org/metamuffin/keks-meet/src/branch/master/readme.md"), ) } export let chat_control: (s?: boolean) => void; export function control_bar(state: AppState, side_ui_container: HTMLElement): HTMLElement { - const leave = e("button", { icon: "leave", class: "abort", onclick() { window.location.href = "/" } }, "Leave") - const chat = side_ui(side_ui_container, state.chat.element, "chat", "Chat", state.chat) - const prefs = side_ui(side_ui_container, ui_preferences(), "settings", "Settings") - const rwatches = side_ui(side_ui_container, ui_room_watches(state.conn), "room", "Known Rooms") + const leave = e("button", { icon: "leave", class: "abort", onclick() { window.location.href = "/" } },PO.leave) + const chat = side_ui(side_ui_container, state.chat.element, "chat", PO.chat, state.chat) + const prefs = side_ui(side_ui_container, ui_preferences(), "settings", PO.settings) + const rwatches = side_ui(side_ui_container, ui_room_watches(state.conn), "room", PO.known_rooms) const local_controls = [ - e("button", { icon: "microphone", onclick: () => state.room?.local_user.await_add_resource(create_mic_res()) }, "Microphone"), - e("button", { icon: "camera", onclick: () => state.room?.local_user.await_add_resource(create_camera_res()) }, "Camera"), - e("button", { icon: "screen", onclick: () => state.room?.local_user.await_add_resource(create_screencast_res()) }, "Screen"), - e("button", { icon: "file", onclick: () => state.room?.local_user.await_add_resource(create_file_res()) }, "File"), + e("button", { icon: "microphone", onclick: () => state.room?.local_user.await_add_resource(create_mic_res()) }, PO.microphone), + e("button", { icon: "camera", onclick: () => state.room?.local_user.await_add_resource(create_camera_res()) }, PO.camera), + e("button", { icon: "screen", onclick: () => state.room?.local_user.await_add_resource(create_screencast_res()) }, PO.screen), + e("button", { icon: "file", onclick: () => state.room?.local_user.await_add_resource(create_file_res()) }, PO.file), ] chat_control = chat.set_state; return e("div", { class: "control-bar", role: "toolbar", - aria_label: "Controls", + aria_label: PO.controls, onkeydown: (_el, ev) => { if (ev.code == "ArrowLeft") { let n = document.activeElement?.previousElementSibling diff --git a/client-web/source/preferences/decl.ts b/client-web/source/preferences/decl.ts index 36ed1a1..a75f1d9 100644 --- a/client-web/source/preferences/decl.ts +++ b/client-web/source/preferences/decl.ts @@ -5,6 +5,8 @@ */ // there should be no deps to dom APIs in this file for the tablegen to work +import { LOCALES } from "../locale/mod.ts"; + export function hex_id(len = 8): string { if (len > 8) return hex_id() + hex_id(len - 8) return Math.floor(Math.random() * 16 ** len).toString(16).padStart(len, "0") @@ -16,6 +18,7 @@ const optional = <T>(a: T): T | undefined => a export const PREF_DECLS = { username: { type: string, default: "guest-" + hex_id(), description: "Username", allow_url: true }, + language: { type: optional(string), possible_values: Object.keys(LOCALES), description: "Interface Language", allow_url: true }, warn_redirect: { type: bool, hidden: true, default: false, description: "Internal option that is set by a server redirect.", allow_url: true }, image_view_popup: { type: bool, default: true, description: "Open image in popup instead of new tab" }, webrtc_debug: { type: bool, default: false, description: "Show additional information for WebRTC related stuff" }, @@ -38,9 +41,9 @@ export const PREF_DECLS = { optional_audio_default_enable: { type: bool, default: true, description: "Enable audio tracks by default" }, optional_video_default_enable: { type: bool, default: false, description: "Enable video tracks by default" }, - notify_chat: { type: bool, default: true, description: "Send notifications for incoming chat messages" }, - notify_join: { type: bool, default: true, description: "Send notifications when users join" }, - notify_leave: { type: bool, default: true, description: "Send notifications when users leave" }, + notify_chat: { type: bool, default: true, description: "Send notifications for incoming chat messages", allow_url: true }, + notify_join: { type: bool, default: true, description: "Send notifications when users join", allow_url: true }, + notify_leave: { type: bool, default: true, description: "Send notifications when users leave", allow_url: true }, enable_onbeforeunload: { type: bool, default: true, description: "Prompt for confirmation when leaving the site while local resources are active" }, room_watches: { type: string, default: "[]", hidden: true, description: "Known rooms (as semicolon seperated list of name=secret pairs)" } diff --git a/client-web/source/preferences/ui.ts b/client-web/source/preferences/ui.ts index 44b3e4e..47f3a4b 100644 --- a/client-web/source/preferences/ui.ts +++ b/client-web/source/preferences/ui.ts @@ -6,6 +6,7 @@ /// <reference lib="dom" /> import { e } from "../helper.ts"; +import { PO } from "../locale/mod.ts"; import { PREF_DECLS } from "./decl.ts"; import { change_pref, on_pref_changed, PrefDecl, PREFS } from "./mod.ts"; @@ -83,19 +84,19 @@ export function ui_preferences(): HTMLElement { }) const notification_perm = Notification.permission == "granted" ? e("div", {}) : e("div", {}, - e("span", {}, "For keks-meet to send notifications, it needs you to grant permission: "), - e("button", { onclick: () => Notification.requestPermission() }, "Grant"), + e("span", {}, PO.notification_perm_explain), + e("button", { onclick: () => Notification.requestPermission() }, PO.grant), ) const reset = e("div", {}, - e("span", {}, "Want to clear all settings? Use this:"), + e("span", {}, PO.clear_prefs), e("button", { onclick: () => { if (confirm("really clear all preferences?")) { localStorage.clear(); window.location.reload() } } }, "RESET"), ) const table = document.createElement("table") table.append(...rows) - return e("div", { class: "preferences", role: "dialog", aria_label: "settings" }, - e("h2", {}, "Settings"), + return e("div", { class: "preferences", role: "dialog", aria_label: PO.settings }, + e("h2", {}, PO.settings), notification_perm, e("br", {}), table, e("br", {}), reset diff --git a/client-web/source/resource/file.ts b/client-web/source/resource/file.ts index ad09ad2..0554e2f 100644 --- a/client-web/source/resource/file.ts +++ b/client-web/source/resource/file.ts @@ -10,6 +10,7 @@ import { log } from "../logger.ts"; import { StreamDownload } from "../download_stream.ts"; import { RemoteUser } from "../user/remote.ts"; import { LocalResource, ResourceHandlerDecl } from "./mod.ts"; +import { PO } from "../locale/mod.ts"; const MAX_CHUNK_SIZE = 1 << 15; @@ -19,14 +20,14 @@ export const resource_file: ResourceHandlerDecl = { const download_button = e("button", { onclick: self => { enable() - self.textContent = "Downloading…" + self.textContent = PO.downloading self.disabled = true } - }, "Download") + }, PO.download) return { info, el: e("div", {}, - e("span", {}, `File: ${JSON.stringify(info.label)} (${display_filesize(info.size!)})`), + e("span", {}, `${PO.file}: ${JSON.stringify(info.label)} (${display_filesize(info.size!)})`), download_button, ), on_statechange(_s) { }, @@ -37,7 +38,7 @@ export const resource_file: ResourceHandlerDecl = { this.el.appendChild(display.el) const reset = () => { download_button.disabled = false - download_button.textContent = "Download again" + download_button.textContent = PO.download_again this.el.removeChild(display.el) disable() } @@ -123,7 +124,7 @@ function file_res_inner(file: File): LocalResource { transfers_abort.forEach(abort => abort()) }, el: e("div", { class: "file" }, - e("button", { class: "abort", onclick(_) { destroy() } }, "Stop sharing"), + e("button", { class: "abort", onclick(_) { destroy() } }, PO.stop_sharing), e("span", {}, `Sharing file: ${JSON.stringify(file.name)}`), transfers_el ), @@ -136,16 +137,16 @@ function file_res_inner(file: File): LocalResource { log("dc", `${user.display_name} started transfer`); const display = transfer_status_el(user) transfers_el.appendChild(display.el) - display.status = "Waiting for data channel to open…" + display.status = PO.status_await_channel_open let position = 0 const finish = async () => { channel.send("end") while (channel.bufferedAmount) { - display.status = `Draining buffers… (buffer: ${channel.bufferedAmount})` + display.status = PO.status_drain_buffer(channel.bufferedAmount) await sleep(10) } - display.status = "Waiting for the channel to close…" + display.status = PO.status_await_channel_close } const feed = async () => { const { value: chunk, done }: { value?: Uint8Array, done: boolean } = await reader.read() @@ -170,7 +171,7 @@ function file_res_inner(file: File): LocalResource { const abort_cb = () => { channel.close(); } channel.onbufferedamountlow = () => feed_until_full() channel.onopen = _ev => { - display.status = "Buffering…" + display.status = PO.status_buffering log("dc", `${user.display_name}: channel open`); feed_until_full() } @@ -178,7 +179,7 @@ function file_res_inner(file: File): LocalResource { log("dc", `${user.display_name}: channel error`); } channel.onclosing = _ev => { - display.status = "Channel closing…" + display.status = PO.status_closing } channel.onclose = _ev => { log("dc", `${user.display_name}: channel closed`); diff --git a/client-web/source/resource/track.ts b/client-web/source/resource/track.ts index 9ba650a..5db07dc 100644 --- a/client-web/source/resource/track.ts +++ b/client-web/source/resource/track.ts @@ -6,6 +6,7 @@ /// <reference lib="dom" /> import { ProvideInfo } from "../../../common/packets.d.ts"; import { e } from "../helper.ts"; +import { PO } from "../locale/mod.ts"; import { log } from "../logger.ts"; import { on_pref_changed, PREFS } from "../preferences/mod.ts"; import { get_rnnoise_node } from "../rnnoise.ts"; @@ -15,14 +16,14 @@ import { LocalResource, ResourceHandlerDecl } from "./mod.ts"; export const resource_track: ResourceHandlerDecl = { kind: "track", new_remote: (info, _user, enable) => { - let enable_label = `Enable ${info.track_kind}` + let enable_label = PO.enable if (info.label) enable_label += ` "${info.label}"` const enable_button = e("button", { class: "center", onclick: self => { self.disabled = true; - self.textContent = "Awaiting track…"; + self.textContent = PO.status_await_track; enable() } }, enable_label) @@ -43,7 +44,7 @@ export const resource_track: ResourceHandlerDecl = { enable_button.textContent = enable_label; self.remove() } - }, "Disable")) + }, PO.disable)) create_track_display(this.el, track) } } @@ -57,7 +58,7 @@ export function new_local_track(info: ProvideInfo, track: TrackHandle, ...extra_ info, el: create_track_display( e("div", { class: `media-${track.kind}` }, - e("button", { class: ["abort", "topright"], onclick: () => destroy() }, "Stop sharing"), + e("button", { class: ["abort", "topright"], onclick: () => destroy() }, PO.stop_sharing), ...extra_controls ), track @@ -81,7 +82,7 @@ function create_track_display(target: HTMLElement, track: TrackHandle): HTMLElem media_el.srcObject = stream media_el.autoplay = true media_el.controls = true - media_el.ariaLabel = `${track.kind} stream` + media_el.ariaLabel = is_video ? PO.video_stream : PO.audio_stream media_el.addEventListener("pause", () => media_el.play()) if (track.local) media_el.muted = true @@ -181,7 +182,7 @@ export async function create_mic_res() { const mute = document.createElement("input") mute.type = "checkbox" - const mute_label = e("label", { class: "check-button" }, "Mute") + const mute_label = e("label", { class: "check-button" }, PO.mute) mute_label.prepend(mute) const res = new_local_track({ id: t.id, kind: "track", track_kind: "audio", label: "Microphone" }, t, mute_label) diff --git a/client-web/source/room_watches.ts b/client-web/source/room_watches.ts index f9d8c1b..d738359 100644 --- a/client-web/source/room_watches.ts +++ b/client-web/source/room_watches.ts @@ -5,6 +5,7 @@ */ /// <reference lib="dom" /> import { array_swap, e } from "./helper.ts"; +import { PO } from "./locale/mod.ts"; import { PREFS, change_pref } from "./preferences/mod.ts"; import { room_hash } from "./protocol/crypto.ts"; import { SignalingConnection } from "./protocol/mod.ts"; @@ -41,7 +42,7 @@ export function ui_room_watches(conn: SignalingConnection): HTMLElement { }) let edit = false; - + const update_listing = () => { listing.innerHTML = "" for (let wi = 0; wi < watches.length; wi++) { @@ -60,8 +61,8 @@ export function ui_room_watches(conn: SignalingConnection): HTMLElement { e("div", { class: "users" }, ...ucont), )) if (edit) el.append(e("button", { onclick(_) { watches = watches.filter(e => e != w); update_listing() } }, "X")) - if (edit && wi > 0) el.append(e("button", { onclick(_) { array_swap(watches, wi, wi - 1); update_listing() } }, "Move up")) - if (edit && wi < watches.length - 1) el.append(e("button", { onclick(_) { array_swap(watches, wi, wi + 1); update_listing() } }, "Move down")) + if (edit && wi > 0) el.append(e("button", { onclick(_) { array_swap(watches, wi, wi - 1); update_listing() } }, PO.move_up)) + if (edit && wi < watches.length - 1) el.append(e("button", { onclick(_) { array_swap(watches, wi, wi + 1); update_listing() } }, PO.move_down)) listing.append(el) } @@ -76,14 +77,14 @@ export function ui_room_watches(conn: SignalingConnection): HTMLElement { update_watches() input.value = "" } - }, "Add"), + }, PO.add), e("button", { async onclick() { if (!conn.room) return await add_watch(conn.room) update_watches() } - }, "Add current room") + }, PO.add_current_room) )) } } @@ -97,7 +98,7 @@ export function ui_room_watches(conn: SignalingConnection): HTMLElement { edit = e; } return e("div", { class: "room-watches", role: "dialog", aria_label: "known rooms" }, - e("h2", {}, "Known Rooms"), + e("h2", {}, PO.known_rooms), listing, button_edit = e("button", { icon: "edit", @@ -105,7 +106,7 @@ export function ui_room_watches(conn: SignalingConnection): HTMLElement { set_edit(true) update_listing() } - }, "Edit"), + }, PO.edit), button_finish = e("button", { icon: "check", hidden: true, @@ -115,6 +116,6 @@ export function ui_room_watches(conn: SignalingConnection): HTMLElement { update_watches() update_listing() } - }, "Finish edit"), + }, PO.finish_edit), ) } diff --git a/client-web/source/user/mod.ts b/client-web/source/user/mod.ts index 37eba73..daf3772 100644 --- a/client-web/source/user/mod.ts +++ b/client-web/source/user/mod.ts @@ -6,18 +6,19 @@ /// <reference lib="dom" /> import { e } from "../helper.ts"; +import { PO } from "../locale/mod.ts"; import { Room } from "../room.ts"; export class User { private _name?: string set name(v: string | undefined) { this._name = v; this.name_el.textContent = this.display_name; this.el.ariaLabel = "user " + this.display_name } get name() { return this._name } - get display_name() { return this.name ?? "Unknown" } + get display_name() { return this.name ?? PO.unknown_user } name_el = e("span", {}, this.display_name) status_el = e("span", { class: ["connection-status", "status-neutral"] }, "") stats_el = e("pre", {}) - el = e("div", { class: "user", role: "group", aria_label: `unknown user`, aria_live: "polite" }) + el = e("div", { class: "user", role: "group", aria_label: PO.unknown_user, aria_live: "polite" }) constructor(public room: Room, public id: number) { const info_el = e("div", { class: "info" }) diff --git a/client-web/source/user/remote.ts b/client-web/source/user/remote.ts index 262efcd..313ddc2 100644 --- a/client-web/source/user/remote.ts +++ b/client-web/source/user/remote.ts @@ -7,6 +7,7 @@ import { RelayMessage } from "../../../common/packets.d.ts"; import { notify } from "../helper.ts"; +import { PO } from "../locale/mod.ts"; import { log } from "../logger.ts" import { PREFS } from "../preferences/mod.ts"; import { new_remote_resource, RemoteResource } from "../resource/mod.ts"; @@ -79,7 +80,7 @@ export class RemoteUser extends User { this.pc.close() this.room.remote_users.delete(this.id) this.room.element.removeChild(this.el) - if (PREFS.notify_leave) notify(`${this.display_name} left`) + if (PREFS.notify_leave) notify(PO.leave_message(this.display_name).join("")) this.room.chat.add_control_message({ leave: this }) } on_relay(message: RelayMessage) { @@ -89,7 +90,7 @@ export class RemoteUser extends User { if (message.answer) this.on_answer(message.answer) if (message.identify) { this.name = message.identify.username - if (PREFS.notify_join) notify(`${this.display_name} joined`) + if (PREFS.notify_join) notify(PO.join_message(this.display_name).join("")) this.room.chat.add_control_message({ join: this }) } if (message.provide) { @@ -167,13 +168,13 @@ export class RemoteUser extends User { async update_status() { const states: { [key in RTCIceConnectionState]: [string, string] } = { - new: ["Not connected", "neutral"], - checking: ["Checking...", "neutral"], - failed: ["Connection failed", "fail"], - closed: ["Disconnected", "neutral"], - completed: ["Connected", "good"], - connected: ["Connected", "good"], - disconnected: ["Disconnected", "neutral"] + new: [PO.status_no_conn, "neutral"], + checking: [PO.status_checking, "neutral"], + failed: [PO.status_failed, "fail"], + closed: [PO.status_disconnected, "neutral"], + completed: [PO.status_connected, "good"], + connected: [PO.status_connected, "good"], + disconnected: [PO.status_disconnected, "neutral"] } this.status_el.classList.value = "" this.status_el.classList.add("connection-status", "status-" + states[this.pc.iceConnectionState][1]) |