aboutsummaryrefslogtreecommitdiff
path: root/client-web
diff options
context:
space:
mode:
Diffstat (limited to 'client-web')
-rw-r--r--client-web/source/chat.ts15
-rw-r--r--client-web/source/download_stream.ts3
-rw-r--r--client-web/source/helper.ts3
-rw-r--r--client-web/source/index.ts18
-rw-r--r--client-web/source/keybinds.ts3
-rw-r--r--client-web/source/locale/en.ts63
-rw-r--r--client-web/source/locale/mod.ts72
-rw-r--r--client-web/source/logger.ts2
-rw-r--r--client-web/source/menu.ts25
-rw-r--r--client-web/source/preferences/decl.ts9
-rw-r--r--client-web/source/preferences/ui.ts11
-rw-r--r--client-web/source/resource/file.ts21
-rw-r--r--client-web/source/resource/track.ts13
-rw-r--r--client-web/source/room_watches.ts17
-rw-r--r--client-web/source/user/mod.ts5
-rw-r--r--client-web/source/user/remote.ts19
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])