diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4c37e96..4857a22 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to the KoalaSync browser extension and relay server. --- +## [v3.0.1] — 2026-07-26 + +### Added +- **Extension: Google Drive workaround** — Detects and controls videos opened on `drive.google.com` through the site-specific embedded-player path. +- **Extension: Chat event messages** — Optionally shows timestamped room and playback events in chat while keeping chat-message notifications enabled by default and event notifications disabled by default. + +### Fixed +- **Extension: Chat interactions** — Prevents video-player keyboard shortcuts while typing, makes the floating chat draggable, restores left/right docking, updates unread notifications, and keeps chat timestamps visible. +- **Extension: Embedded-player selection** — Avoids selecting a stale hidden media frame when multiple nested frames are temporarily ambiguous. +- **Extension: Popup layout** — Moves the video-selection hint lower and reduces its glow. +- **Website and extension copy** — Corrects malformed translations, separates chat and playback notification labels, removes stale version references and unsupported comparison claims, and accurately documents encrypted live chat and operational logging. + ## [v3.0.0] — 2026-07-26 ### Added diff --git a/extension/background.js b/extension/background.js index 50ada81..7e9424b 100644 --- a/extension/background.js +++ b/extension/background.js @@ -398,7 +398,7 @@ async function getSettings() { // (username) must NEVER come from storage.sync — syncing them across devices // both leaks them and resurrects dead rooms on reinstall (a fresh install // has empty local storage but sync survives in the user's Google account). - const data = await chrome.storage.local.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'chatKey', 'chatEnabled', 'username', 'sendTabTitle', 'mediaTitlePrivacyMode', 'titlePrivacyMode']); + const data = await chrome.storage.local.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'chatKey', 'chatEnabled', 'chatNotifications', 'username', 'sendTabTitle', 'mediaTitlePrivacyMode', 'titlePrivacyMode']); let username = data.username; if (!username) { username = generateUsername(); @@ -417,6 +417,7 @@ async function getSettings() { password: data.password || '', chatKey, chatEnabled: data.chatEnabled === true, + chatNotifications: data.chatNotifications !== false, username, sendTabTitle: normalizeSendTabTitle(data.sendTabTitle, legacyTitlePrivacyMode), mediaTitlePrivacyMode @@ -453,7 +454,7 @@ function emitEpisodeLobbyForCurrentPrivacy() { // otherwise be redistributed across devices and resurrected on reinstall). const LEGACY_SYNC_KEYS = [ 'serverUrl', 'useCustomServer', 'roomId', 'password', 'chatKey', - 'chatEnabled', 'chatPosition', 'chatSize', 'chatStartMode', 'username', + 'chatEnabled', 'chatNotifications', 'chatPosition', 'chatSize', 'chatStartMode', 'username', 'filterNoise', 'autoSyncNextEpisode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite', 'locale', 'audioSettings', 'titlePrivacyMode', 'sendTabTitle', 'mediaTitlePrivacyMode' @@ -907,7 +908,11 @@ function showNotification(senderName, action) { displayName = getMessage('LABEL_YOU') || 'YOU'; } - const message = getMessage('TOAST_PEER_ACTION', { name: displayName, action: label }) + '.'; + const message = action === 'joined' + ? getMessage('TOAST_PEER_JOINED', { name: displayName }) + : action === 'left' + ? getMessage('TOAST_PEER_LEFT', { name: displayName }) + : getMessage('TOAST_PEER_ACTION', { name: displayName, action: label }) + '.'; chrome.notifications.create(`sync_${Date.now()}`, { type: 'basic', @@ -919,6 +924,43 @@ function showNotification(senderName, action) { }); } +function showChatNotification(displayName, text) { + chrome.storage.local.get(['chatNotifications', 'locale'], async (settings) => { + if (settings.chatNotifications === false) return; + + await loadLocale(settings.locale || getSystemLanguage()); + chrome.notifications.create(`chat_${Date.now()}`, { + type: 'basic', + iconUrl: 'icons/icon128.png', + title: getMessage('CHAT_TITLE') || 'KoalaSync', + message: `${displayName || getMessage('CHAT_TITLE') || 'Room Chat'}: ${text}`, + priority: 1 + }); + }); +} + +function chatActivityDisplayName(senderId) { + if (senderId === peerId) return ''; + const peer = currentRoom?.peers?.find(candidate => + (typeof candidate === 'object' ? candidate.peerId : candidate) === senderId + ); + return typeof peer === 'object' ? peer.username || senderId : senderId; +} + +function sendChatActivity(action, senderId, timestamp = Date.now()) { + if (!currentTabId || !serverSupportsChat()) return; + if (![EVENTS.PLAY, EVENTS.PAUSE, EVENTS.SEEK, EVENTS.FORCE_SYNC_PREPARE, EVENTS.FORCE_SYNC_EXECUTE, 'joined', 'left'].includes(action)) return; + chrome.tabs.sendMessage(Number(currentTabId), { + type: 'CHAT_EVENT', + event: { + action, + senderId, + username: chatActivityDisplayName(senderId), + timestamp: Number.isFinite(timestamp) ? timestamp : Date.now() + } + }).catch(() => {}); +} + function scheduleReconnect() { if (reconnectTimer) return; @@ -1235,6 +1277,12 @@ async function handleServerEvent(event, data) { } }).catch(() => {}); } + if (received.senderId !== peerId) { + showChatNotification( + typeof senderPeer === 'object' ? senderPeer.username : received.senderId, + text + ); + } } catch (_) { if (isCurrentSession()) addLog('Discarded chat message that failed authentication', 'warn'); } @@ -1291,6 +1339,7 @@ async function handleServerEvent(event, data) { if (data.senderId) { addToHistory(event, data.senderId); showNotification(data.senderId, event); + sendChatActivity(event, data.senderId, data.actionTimestamp); updateLastAction(event, data.senderId); lastActionState.targetTime = data.targetTime !== undefined ? data.targetTime : data.currentTime; if (storageInitialized) chrome.storage.session.set({ lastActionState }); @@ -1348,6 +1397,7 @@ async function handleServerEvent(event, data) { if (data?.senderId) { addToHistory(event, data.senderId); showNotification(data.senderId, event); + sendChatActivity(event, data.senderId, data.actionTimestamp); // (The sender's state is updated below with everyone else) } @@ -1403,6 +1453,8 @@ async function handleServerEvent(event, data) { currentRoom.peers.push(createPeerData(data)); if (storageInitialized) chrome.storage.session.set({ currentRoom }); chrome.runtime.sendMessage({ type: 'PEER_UPDATE', peers: currentRoom.peers }).catch(() => {}); + sendChatActivity('joined', data.peerId, Date.now()); + showNotification(data.username || data.peerId, 'joined'); // We were alone and now we're not — proactively push our // current playback state so the newcomer syncs immediately @@ -1416,6 +1468,9 @@ async function handleServerEvent(event, data) { } } } else if (data.status === 'left') { + const departedDisplayName = chatActivityDisplayName(data.peerId); + sendChatActivity('left', data.peerId, Date.now()); + showNotification(departedDisplayName, 'left'); currentRoom.peers = currentRoom.peers.filter(p => (p.peerId || p) !== data.peerId); if (storageInitialized) chrome.storage.session.set({ currentRoom }); chrome.runtime.sendMessage({ type: 'PEER_UPDATE', peers: currentRoom.peers }).catch(() => {}); @@ -1577,6 +1632,7 @@ function executeForceSync() { emit(EVENTS.FORCE_SYNC_EXECUTE, { actionTimestamp: executionTimestamp, seq: localSeq }); routeToContent(EVENTS.FORCE_SYNC_EXECUTE, { actionTimestamp: executionTimestamp, seq: localSeq }); + sendChatActivity(EVENTS.FORCE_SYNC_EXECUTE, peerId, executionTimestamp); addLog('Force Sync Executed', 'success'); } @@ -2554,7 +2610,11 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { }); chrome.storage.onChanged.addListener((changes, area) => { - if (area !== 'local' || (!changes.roomId && !changes.chatKey && !changes.chatEnabled)) return; + if (area !== 'local') return; + if (changes.browserNotifications && currentTabId) { + chrome.tabs.sendMessage(Number(currentTabId), { type: 'CHAT_CONTEXT_UPDATE' }).catch(() => {}); + } + if (!changes.roomId && !changes.chatKey && !changes.chatEnabled) return; if (changes.chatKey) chatSecretGuard = validateChatSecret(changes.chatKey.newValue); invalidateChatSession(); if (currentTabId) chrome.tabs.sendMessage(Number(currentTabId), { type: 'CHAT_CONTEXT_UPDATE' }).catch(() => {}); @@ -2661,7 +2721,7 @@ async function handleAsyncMessage(message, sender, sendResponse) { return; } const settings = await getSettings(); - const localeData = await chrome.storage.local.get(['locale']); + const localeData = await chrome.storage.local.get(['locale', 'browserNotifications']); await loadLocale(localeData.locale || getSystemLanguage()); const translated = key => { const value = getMessage(key); @@ -2672,6 +2732,7 @@ async function handleAsyncMessage(message, sender, sendResponse) { enabled: settings.chatEnabled, hasKey: !!settings.chatKey, connected: !!(socket && socket.readyState === WebSocket.OPEN && isNamespaceJoined), + eventNotifications: localeData.browserNotifications === true, peerId, roomId: currentRoom.roomId, strings: { @@ -2688,7 +2749,15 @@ async function handleAsyncMessage(message, sender, sendResponse) { tooLong: translated('CHAT_TOO_LONG'), sendFailed: translated('CHAT_SEND_FAILED'), empty: translated('CHAT_EMPTY'), - you: translated('LABEL_YOU') + you: translated('LABEL_YOU'), + eventPlay: translated('NOTIF_PLAY'), + eventPause: translated('NOTIF_PAUSE'), + eventSeek: translated('NOTIF_SEEK'), + eventForcePrepare: translated('NOTIF_FORCE_PREPARE'), + eventForceExecute: translated('NOTIF_FORCE_EXECUTE'), + eventAction: translated('TOAST_PEER_ACTION'), + eventJoined: translated('TOAST_PEER_JOINED'), + eventLeft: translated('TOAST_PEER_LEFT') } }); } else if (message.type === 'CHAT_SEND') { @@ -3127,6 +3196,7 @@ async function handleAsyncMessage(message, sender, sendResponse) { }, FORCE_SYNC_TIMEOUT); } addToHistory(message.action, 'You'); + sendChatActivity(message.action, peerId, timestamp); const isNonEssentialEvent = message.action === EVENTS.PLAY || message.action === EVENTS.PAUSE || message.action === EVENTS.SEEK; if (isNonEssentialEvent && !hasOtherPeers) { diff --git a/extension/chat-overlay-contract.test.mjs b/extension/chat-overlay-contract.test.mjs index 8559857..782b5bc 100644 --- a/extension/chat-overlay-contract.test.mjs +++ b/extension/chat-overlay-contract.test.mjs @@ -52,6 +52,54 @@ describe('chat overlay contract', () => { expect(overlaySource).toContain('min(${MIN_WIDTH}px, calc(100vw - 16px))'); }); + it('blocks page playback shortcuts while the chat composer owns keyboard input', () => { + expect(overlaySource).toContain("for (const eventName of ['keydown', 'keyup', 'keypress'])"); + expect(overlaySource).toContain('window.addEventListener(eventName, stopPageKeyboardShortcut, true)'); + expect(overlaySource).toContain('path.includes(textarea)'); + expect(overlaySource).toContain('event.stopImmediatePropagation()'); + expect(overlaySource).toContain('window.removeEventListener(eventName, stopPageKeyboardShortcut, true)'); + }); + + it('keeps the launcher draggable independently from dock mode', () => { + expect(overlaySource).toContain("launcher.addEventListener('pointerdown'"); + expect(overlaySource).toContain("launcher.addEventListener('pointermove'"); + expect(overlaySource).toContain('layout.launcherX = launcherDrag.x + deltaX'); + expect(overlaySource).toContain('layout.launcherY = launcherDrag.y + deltaY'); + expect(overlaySource).toContain('suppressLauncherClick = launcherDrag.moved'); + expect(overlaySource).not.toMatch(/launcher\.style\.left = `\$\{layout\.x\}px`/); + }); + + it('reserves page space for real left and right dock modes', () => { + expect(overlaySource).toContain("const PAGE_DOCK_ATTRIBUTE = 'data-koalasync-chat-dock'"); + expect(overlaySource).toContain('padding-left: var(${PAGE_DOCK_WIDTH}) !important'); + expect(overlaySource).toContain('padding-right: var(${PAGE_DOCK_WIDTH}) !important'); + expect(overlaySource).toContain('document.documentElement.setAttribute(PAGE_DOCK_ATTRIBUTE, side)'); + expect(overlaySource).toContain('applyPageDock(layout.mode, dockWidth)'); + expect(overlaySource).toContain('clearPageDock()'); + }); + + it('counts unread remote messages on the bubble and clears the badge when opened', () => { + expect(overlaySource).toContain("const unreadBadge = element('span', 'unread')"); + expect(overlaySource).toContain('if (!opened && !own) setUnreadCount(unreadCount + 1)'); + expect(overlaySource).toMatch(/if \(opened\) \{\s*setUnreadCount\(0\)/); + expect(overlaySource).toContain("unreadBadge.classList.toggle('visible', unreadCount > 0)"); + expect(backgroundSource).toMatch(/received\.senderId !== peerId[\s\S]*showChatNotification\([\s\S]*senderPeer\.username[\s\S]*received\.senderId/); + expect(backgroundSource).toContain("chrome.notifications.create(`chat_${Date.now()}`"); + expect(backgroundSource).toContain("if (settings.chatNotifications === false) return"); + }); + + it('renders timestamped room activity and keeps activity notifications opt-in', () => { + expect(overlaySource).toContain("if (message?.type === 'CHAT_EVENT') appendEvent(message.event)"); + expect(overlaySource).toContain("const time = element('time', 'time')"); + expect(overlaySource).toContain("date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })"); + expect(overlaySource).toContain('if (!opened && context.eventNotifications) setUnreadCount(unreadCount + 1)'); + expect(backgroundSource).toContain("eventNotifications: localeData.browserNotifications === true"); + expect(backgroundSource).toContain("sendChatActivity(message.action, peerId, timestamp)"); + expect(backgroundSource).toContain("sendChatActivity(event, data.senderId, data.actionTimestamp)"); + expect(backgroundSource).toContain("sendChatActivity('joined', data.peerId, Date.now())"); + expect(backgroundSource).toContain("sendChatActivity('left', data.peerId, Date.now())"); + }); + it('guards async refresh/send work and clears all composer state on room reset', () => { expect(overlaySource).toContain('generation !== refreshGeneration'); expect(overlaySource).toContain('if (sending || !context?.enabled) return'); diff --git a/extension/chat-overlay.js b/extension/chat-overlay.js index b97ce12..8dbbf03 100644 --- a/extension/chat-overlay.js +++ b/extension/chat-overlay.js @@ -9,6 +9,11 @@ const MIN_HEIGHT = 320; const DEFAULT_WIDTH = 360; const DEFAULT_HEIGHT = 520; + const LAUNCHER_SIZE = 48; + const EDGE_INSET = 8; + const DOCK_MIN_PAGE_WIDTH = 480; + const PAGE_DOCK_ATTRIBUTE = 'data-koalasync-chat-dock'; + const PAGE_DOCK_WIDTH = '--koalasync-chat-dock-width'; const SIZE_PRESETS = Object.freeze({ compact: Object.freeze({ width: 320, height: 400 }), standard: Object.freeze({ width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT }), @@ -26,6 +31,7 @@ let refreshGeneration = 0; let sendGeneration = 0; let sending = false; + let unreadCount = 0; let layout = { mode: 'right', x: 24, @@ -34,7 +40,10 @@ height: DEFAULT_HEIGHT, customWidth: DEFAULT_WIDTH, customHeight: DEFAULT_HEIGHT, - detachedInitialized: false + detachedInitialized: false, + launcherX: null, + launcherY: null, + launcherInitialized: false }; let chatPosition = 'right'; let chatSize = 'standard'; @@ -103,21 +112,31 @@ button, textarea { font: inherit; } button { color: inherit; } .launcher { - position: fixed; width: 48px; height: 48px; border: 1px solid var(--border-strong); - border-radius: 16px; background: var(--card); color: var(--text); cursor: pointer; - pointer-events: auto; box-shadow: 0 10px 28px rgb(0 0 0 / .32); font-size: 22px; - } - .launcher:hover:not([aria-disabled="true"]) { border-color: var(--accent); transform: translateY(-1px); } - .launcher[aria-disabled="true"] { cursor: not-allowed; opacity: .58; } - .panel { + position: fixed; width: 48px; height: 48px; border: 1px solid var(--border-strong); + border-radius: 16px; background: var(--card); color: var(--text); cursor: pointer; + pointer-events: auto; box-shadow: 0 10px 28px rgb(0 0 0 / .32); font-size: 22px; + touch-action: none; user-select: none; + } + .launcher:hover:not([aria-disabled="true"]) { border-color: var(--accent); transform: translateY(-1px); } + .launcher[aria-disabled="true"] { cursor: not-allowed; opacity: .58; } + .launcher-icon { pointer-events: none; } + .unread { + position: absolute; top: -7px; right: -7px; display: none; min-width: 22px; height: 22px; + align-items: center; justify-content: center; padding: 0 6px; border: 2px solid var(--card); + border-radius: 999px; background: var(--danger); color: white; font-size: 11px; font-weight: 800; + line-height: 1; pointer-events: none; + } + .unread.visible { display: flex; } + .panel { position: fixed; display: none; flex-direction: column; overflow: hidden; min-width: min(${MIN_WIDTH}px, calc(100vw - 16px)); min-height: min(${MIN_HEIGHT}px, calc(100vh - 16px)); max-width: calc(100vw - 16px); max-height: calc(100vh - 16px); pointer-events: auto; color: var(--text); background: var(--card); border: 1px solid var(--border-strong); border-radius: 18px; box-shadow: 0 18px 55px rgb(0 0 0 / .38); transform: translateZ(0); - } - .panel.open { display: flex; } - .header { display: flex; align-items: center; gap: 10px; padding: 12px; border-bottom: 1px solid var(--border-soft); user-select: none; } + } + .panel.open { display: flex; } + .panel.docked { border-radius: 0; max-height: 100vh; } + .header { display: flex; align-items: center; gap: 10px; padding: 12px; border-bottom: 1px solid var(--border-soft); user-select: none; } .header.detached { cursor: move; } .heading { min-width: 0; flex: 1; } .title { font-size: 14px; font-weight: 760; } @@ -131,7 +150,13 @@ .meta { display: flex; align-items: baseline; gap: 7px; margin-bottom: 2px; } .sender { color: var(--accent); font-weight: 700; font-size: 12px; } .time { color: var(--text-muted); font-size: 10px; } - .body { white-space: pre-wrap; color: var(--text); } + .body { white-space: pre-wrap; color: var(--text); } + .event-message { + display: flex; align-items: center; justify-content: center; gap: 7px; margin: 8px 0; + color: var(--text-muted); font-size: 11px; text-align: center; + } + .event-message::before, .event-message::after { content: ""; flex: 1; height: 1px; background: var(--border-soft); } + .event-text { max-width: 78%; } .composer { padding: 10px; border-top: 1px solid var(--border-soft); background: var(--card); } textarea { width: 100%; min-height: 62px; max-height: 132px; resize: vertical; border: 1px solid var(--border-strong); border-radius: 11px; padding: 9px; background: var(--surface-deep); color: var(--text); outline: none; } textarea:focus { border-color: var(--accent); } @@ -156,8 +181,12 @@ const app = element('div'); app.id = 'app'; - const launcher = element('button', 'launcher', '💬'); + const launcher = element('button', 'launcher'); launcher.type = 'button'; + const launcherIcon = element('span', 'launcher-icon', '💬'); + const unreadBadge = element('span', 'unread'); + unreadBadge.setAttribute('aria-hidden', 'true'); + launcher.append(launcherIcon, unreadBadge); const launcherHint = element('span', 'visually-hidden'); launcherHint.id = 'chat-launcher-hint'; const panel = element('section', 'panel'); @@ -169,7 +198,7 @@ heading.append(title, subtitle); const modes = element('div', 'modes'); const leftButton = element('button', 'icon-button', '←'); - const detachedButton = element('button', 'icon-button', '↗'); + const detachedButton = element('button', 'icon-button', '✥'); const rightButton = element('button', 'icon-button', '→'); const closeButton = element('button', 'icon-button', '×'); for (const button of [leftButton, detachedButton, rightButton, closeButton]) button.type = 'button'; @@ -199,6 +228,19 @@ app.append(launcherHint, launcher, panel); shadow.append(style, app); document.documentElement.append(host); + const pageDockStyle = document.createElement('style'); + pageDockStyle.id = 'koalasync-chat-page-dock-style'; + pageDockStyle.textContent = ` + html[${PAGE_DOCK_ATTRIBUTE}="left"] { + box-sizing: border-box !important; + padding-left: var(${PAGE_DOCK_WIDTH}) !important; + } + html[${PAGE_DOCK_ATTRIBUTE}="right"] { + box-sizing: border-box !important; + padding-right: var(${PAGE_DOCK_WIDTH}) !important; + } + `; + document.documentElement.append(pageDockStyle); function messageRuntime(payload, timeoutMs = 5000) { return new Promise(resolve => { @@ -250,6 +292,7 @@ detachedButton.setAttribute('aria-label', text.detached || ''); rightButton.setAttribute('aria-label', text.dockRight || ''); closeButton.setAttribute('aria-label', text.close || ''); + applyUnreadCount(); } function applyTheme() { @@ -277,6 +320,52 @@ }; } + function clampLauncher() { + const viewport = viewportBounds(); + const maxX = Math.max(0, viewport.width - LAUNCHER_SIZE - EDGE_INSET); + const maxY = Math.max(0, viewport.height - LAUNCHER_SIZE - EDGE_INSET); + layout.launcherX = Math.min(Math.max(Number(layout.launcherX) || EDGE_INSET, EDGE_INSET), maxX); + layout.launcherY = Math.min(Math.max(Number(layout.launcherY) || EDGE_INSET, EDGE_INSET), maxY); + } + + function initializeLauncher() { + if (layout.launcherInitialized) return; + const viewport = viewportBounds(); + layout.launcherX = chatPosition === 'left' + ? Math.min(16, Math.max(EDGE_INSET, viewport.width - LAUNCHER_SIZE - EDGE_INSET)) + : Math.max(EDGE_INSET, viewport.width - LAUNCHER_SIZE - 16); + layout.launcherY = Math.max(EDGE_INSET, Math.round(viewport.height / 2 - LAUNCHER_SIZE / 2)); + layout.launcherInitialized = true; + clampLauncher(); + } + + function clearPageDock() { + document.documentElement.removeAttribute(PAGE_DOCK_ATTRIBUTE); + document.documentElement.style.removeProperty(PAGE_DOCK_WIDTH); + } + + function applyPageDock(side, width) { + const viewport = viewportBounds(); + if (!opened || document.fullscreenElement || viewport.width - width < DOCK_MIN_PAGE_WIDTH) { + clearPageDock(); + return; + } + document.documentElement.setAttribute(PAGE_DOCK_ATTRIBUTE, side); + document.documentElement.style.setProperty(PAGE_DOCK_WIDTH, `${width}px`); + } + + function applyUnreadCount() { + unreadBadge.textContent = unreadCount > 99 ? '99+' : String(unreadCount); + unreadBadge.classList.toggle('visible', unreadCount > 0); + const openLabel = strings().open || ''; + launcher.setAttribute('aria-label', unreadCount > 0 ? `${openLabel} (${unreadCount})` : openLabel); + } + + function setUnreadCount(next) { + unreadCount = Math.max(0, Number.isFinite(next) ? Math.trunc(next) : 0); + applyUnreadCount(); + } + function clampDetached() { const viewport = viewportBounds(); const maxWidth = Math.max(1, Math.min(600, viewport.width - 16)); @@ -300,35 +389,37 @@ function applyLayout() { applyingLayout = true; const size = preferredSize(); + initializeLauncher(); + clampLauncher(); + launcher.style.left = `${layout.launcherX}px`; + launcher.style.right = 'auto'; + launcher.style.top = `${layout.launcherY}px`; panel.style.right = 'auto'; panel.style.left = 'auto'; panel.style.bottom = 'auto'; panel.style.resize = 'none'; + panel.classList.toggle('docked', layout.mode !== 'detached'); header.classList.toggle('detached', layout.mode === 'detached'); leftButton.classList.toggle('active', layout.mode === 'left'); detachedButton.classList.toggle('active', layout.mode === 'detached'); rightButton.classList.toggle('active', layout.mode === 'right'); if (layout.mode === 'detached') { + clearPageDock(); clampDetached(); panel.style.left = `${layout.x}px`; panel.style.top = `${layout.y}px`; panel.style.width = `${layout.width}px`; panel.style.height = `${layout.height}px`; panel.style.resize = 'both'; - launcher.style.left = `${layout.x}px`; - launcher.style.right = 'auto'; - launcher.style.top = `${layout.y}px`; } else { const viewport = viewportBounds(); - const gutter = Math.min(16, Math.max(0, Math.floor((viewport.width - 1) / 2))); - const panelTop = viewport.height < MIN_HEIGHT + 80 ? Math.min(8, Math.max(0, viewport.height - 1)) : 64; - panel.style.top = `${panelTop}px`; - panel.style.width = `${Math.max(1, Math.min(size.width, viewport.width - gutter * 2))}px`; - panel.style.height = `${Math.max(1, Math.min(size.height, viewport.height - panelTop - Math.min(16, Math.max(0, viewport.height - panelTop - 1))))}px`; - panel.style[layout.mode] = `${gutter}px`; - launcher.style.top = `${Math.max(0, Math.min(viewport.height - 48, viewport.height / 2 - 24))}px`; - launcher.style.left = layout.mode === 'left' ? `${gutter}px` : 'auto'; - launcher.style.right = layout.mode === 'right' ? `${gutter}px` : 'auto'; + const dockWidth = Math.max(1, Math.min(size.width, viewport.width - 16)); + panel.style.top = '0'; + panel.style.bottom = '0'; + panel.style.width = `${dockWidth}px`; + panel.style.height = `${viewport.height}px`; + panel.style[layout.mode] = '0'; + applyPageDock(layout.mode, dockWidth); } globalThis.queueMicrotask(() => { applyingLayout = false; }); } @@ -369,9 +460,11 @@ panel.classList.toggle('open', opened); launcher.style.display = opened ? 'none' : ''; if (opened) { + setUnreadCount(0); textarea.focus(); messages.scrollTop = messages.scrollHeight; } + applyLayout(); } function applyContext(next) { @@ -413,6 +506,7 @@ const wrapper = element('article', 'message'); const meta = element('div', 'meta'); const own = message.senderId === context.peerId; + if (!opened && !own) setUnreadCount(unreadCount + 1); const sender = element('span', 'sender', own ? (strings().you || '') : (message.username || message.senderId || '')); const time = element('time', 'time'); const date = new Date(message.timestamp); @@ -431,8 +525,44 @@ messages.scrollTop = messages.scrollHeight; } + function appendEvent(event) { + if (!context?.enabled || !event || typeof event.action !== 'string') return; + const own = event.senderId === context.peerId; + const displayName = own ? (strings().you || '') : (event.username || event.senderId || ''); + const labels = { + play: strings().eventPlay, + pause: strings().eventPause, + seek: strings().eventSeek, + force_sync_prepare: strings().eventForcePrepare, + force_sync_execute: strings().eventForceExecute + }; + let eventText = ''; + if (event.action === 'joined' || event.action === 'left') { + const template = event.action === 'joined' ? strings().eventJoined : strings().eventLeft; + eventText = (template || '{name}').replace('{name}', displayName); + } else if (labels[event.action]) { + eventText = (strings().eventAction || '{name} {action}') + .replace('{name}', displayName) + .replace('{action}', labels[event.action]); + } + if (!eventText) return; + + if (empty.isConnected) empty.remove(); + if (!opened && context.eventNotifications) setUnreadCount(unreadCount + 1); + const wrapper = element('article', 'message event-message'); + const body = element('span', 'event-text', eventText); + const time = element('time', 'time'); + const date = new Date(event.timestamp); + time.textContent = Number.isFinite(date.getTime()) ? date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : ''; + wrapper.append(body, time); + messages.append(wrapper); + while (messages.querySelectorAll('.message').length > MAX_MESSAGES) messages.querySelector('.message')?.remove(); + messages.scrollTop = messages.scrollHeight; + } + function clearMessages() { messages.replaceChildren(empty); + setUnreadCount(0); } function resetComposer() { @@ -445,7 +575,48 @@ sendButton.disabled = false; } - launcher.addEventListener('click', () => setOpened(true)); + let launcherDrag = null; + let suppressLauncherClick = false; + launcher.addEventListener('click', event => { + if (suppressLauncherClick && event.detail !== 0) { + suppressLauncherClick = false; + return; + } + suppressLauncherClick = false; + setOpened(true); + }); + launcher.addEventListener('pointerdown', event => { + launcherDrag = { + pointerId: event.pointerId, + startX: event.clientX, + startY: event.clientY, + x: layout.launcherX, + y: layout.launcherY, + moved: false + }; + launcher.setPointerCapture(event.pointerId); + }); + launcher.addEventListener('pointermove', event => { + if (!launcherDrag || event.pointerId !== launcherDrag.pointerId) return; + const deltaX = event.clientX - launcherDrag.startX; + const deltaY = event.clientY - launcherDrag.startY; + if (Math.hypot(deltaX, deltaY) >= 4) launcherDrag.moved = true; + layout.launcherX = launcherDrag.x + deltaX; + layout.launcherY = launcherDrag.y + deltaY; + clampLauncher(); + launcher.style.left = `${layout.launcherX}px`; + launcher.style.top = `${layout.launcherY}px`; + }); + launcher.addEventListener('pointerup', event => { + if (!launcherDrag || event.pointerId !== launcherDrag.pointerId) return; + suppressLauncherClick = launcherDrag.moved; + launcherDrag = null; + saveLayout(); + }); + launcher.addEventListener('pointercancel', () => { + launcherDrag = null; + suppressLauncherClick = false; + }); closeButton.addEventListener('click', () => setOpened(false)); leftButton.addEventListener('click', () => setMode('left')); detachedButton.addEventListener('click', () => setMode('detached')); @@ -457,12 +628,18 @@ count.style.color = length > 500 ? 'var(--danger)' : ''; status.textContent = length > 500 ? (strings().tooLong || '') : ''; }); - textarea.addEventListener('keydown', event => { - if (event.key === 'Enter' && !event.shiftKey) { + const stopPageKeyboardShortcut = event => { + const path = typeof event.composedPath === 'function' ? event.composedPath() : []; + if (!path.includes(textarea) && !path.includes(composer)) return; + if (event.type === 'keydown' && event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); if (!sending) composer.requestSubmit(); } - }); + event.stopImmediatePropagation(); + }; + for (const eventName of ['keydown', 'keyup', 'keypress']) { + window.addEventListener(eventName, stopPageKeyboardShortcut, true); + } composer.addEventListener('submit', async event => { event.preventDefault(); if (sending || !context?.enabled) return; @@ -535,10 +712,11 @@ } function handleResize() { + clampLauncher(); + applyLayout(); + saveLayout(); if (layout.mode === 'detached') { clampDetached(); - applyLayout(); - saveLayout(); } } @@ -565,6 +743,7 @@ function handleRuntime(message) { if (message?.type === 'CHAT_MESSAGE') appendMessage(message.message); + if (message?.type === 'CHAT_EVENT') appendEvent(message.event); if (message?.type === 'CHAT_CONTEXT_UPDATE' || message?.type === 'CONNECTION_STATUS') refresh(); if (message?.type === 'CHAT_RESET') { clearMessages(); @@ -581,6 +760,11 @@ sendGeneration++; if (saveTimer) clearTimeout(saveTimer); resizeObserver.disconnect(); + clearPageDock(); + pageDockStyle.remove(); + for (const eventName of ['keydown', 'keyup', 'keypress']) { + window.removeEventListener(eventName, stopPageKeyboardShortcut, true); + } document.removeEventListener('fullscreenchange', moveIntoFullscreen); window.removeEventListener('resize', handleResize); systemTheme.removeEventListener('change', handleSystemTheme); diff --git a/extension/locales/de.json b/extension/locales/de.json index 6cc5489..ebc0d21 100644 --- a/extension/locales/de.json +++ b/extension/locales/de.json @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "Weiter", "BTN_ONBOARDING_NEXT_TOOLTIP": "Zum nächsten Schritt gehen", "ONBOARDING_1_TITLE": "Willkommen bei KoalaSync!", - "ONBOARDING_1_TEXT": "Schau Videos in perfekter Synchronität zusammen — egal wo ihr seid. Machen wir eine kurze Tour!", + "ONBOARDING_1_TEXT": "Schaut Videos gemeinsam, egal wo ihr seid. Hier ist eine kurze Einführung.", "ONBOARDING_2_TITLE": "1. Raum erstellen", "ONBOARDING_2_TEXT": "Hier geht's los. Erstelle einen Raum und teile den Einladungslink mit deinen Freunden.", "ONBOARDING_3_TITLE": "2. Video auswählen", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Zeitüberschreitung — nicht alle Teilnehmer haben die Episode geladen", "LOBBY_CANCEL_USER": "Vom Benutzer abgebrochen", "NOTIF_ERROR_TITLE": "KoalaSync-Fehler", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "KoalaSync unterstützen", "FOOTER_REVIEW": "★ Bewerten", "FOOTER_SUPPORT_PROMPT": "Gefällt dir KoalaSync? Hinterlasse eine Bewertung!", "LABEL_AUDIO_PROCESSING": "Audio-Verarbeitung", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "Chat ist nur über einen aktuellen Einladungslink verfügbar.", "CHAT_TOO_LONG": "Maximal 500 Zeichen.", "CHAT_SEND_FAILED": "Nachricht konnte nicht gesendet werden.", - "CHAT_EMPTY": "Noch keine Nachrichten. Der Chat ist nur live." + "CHAT_EMPTY": "Noch keine Nachrichten. Der Chat ist nur live.", + "LABEL_CHAT_NOTIFICATIONS": "Chat-Benachrichtigungen", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Zeigt neue Raum-Chatnachrichten als Systembenachrichtigung, wenn der Video-Tab nicht fokussiert ist.", + "LABEL_EVENT_NOTIFICATIONS": "Wiedergabe- & Raumbenachrichtigungen", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Zeigt Systembenachrichtigungen, wenn jemand beitritt, den Raum verlässt, abspielt oder pausiert.", + "DEBUG_SECTION_VIDEO_DETECTION": "Videoerkennung", + "DEBUG_VIDEO_NOT_FOUND": "KEIN VIDEOELEMENT GEFUNDEN", + "DEBUG_SECTION_MEDIA_SESSION": "Media Session API", + "DEBUG_VIDEO_HINT": "Läuft gerade ein Video? KoalaSync erkennt Browser-Videoelemente, nachdem das Medium geladen wurde.", + "DEBUG_SECTION_PLAYBACK": "Wiedergabe", + "DEBUG_STATE_PAUSED": "PAUSIERT", + "DEBUG_STATE_PLAYING": "LÄUFT", + "DEBUG_SECTION_PROPERTIES": "Eigenschaften", + "DEBUG_SECTION_DIMENSIONS": "Abmessungen", + "DEBUG_DIMENSIONS_HINT": "0×0 = Videoelement ist ausgeblendet oder noch nicht geladen", + "DEBUG_SECTION_MEDIA_ERROR": "Medienfehler", + "DEBUG_SECTION_DETECTION": "Erkennung", + "DEBUG_SECTION_IDENTIFICATION": "Identifikation", + "DEBUG_SECTION_MEDIA_SOURCE": "Medienquelle", + "DEBUG_SECTION_DATA_ATTRIBUTES": "Datenattribute", + "DEBUG_YES": "JA", + "DEBUG_NO": "NEIN", + "DEBUG_YES_CHECKED": "JA (geprüft)", + "DEBUG_NA": "k. A." } diff --git a/extension/locales/en.json b/extension/locales/en.json index e1d23bc..03f769a 100644 --- a/extension/locales/en.json +++ b/extension/locales/en.json @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "Next", "BTN_ONBOARDING_NEXT_TOOLTIP": "Go to next step", "ONBOARDING_1_TITLE": "Welcome to KoalaSync!", - "ONBOARDING_1_TEXT": "Watch videos together in perfect sync — no matter where you are. Let's take a quick tour!", + "ONBOARDING_1_TEXT": "Watch videos together wherever you are. Here is a quick tour.", "ONBOARDING_2_TITLE": "1. Create a Room", "ONBOARDING_2_TEXT": "Start here. Create a room and share the invite link with your friends.", "ONBOARDING_3_TITLE": "2. Select Video", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "Chat is available only through a current invite link.", "CHAT_TOO_LONG": "Maximum 500 characters.", "CHAT_SEND_FAILED": "Message could not be sent.", - "CHAT_EMPTY": "No messages yet. Chat is live only." + "CHAT_EMPTY": "No messages yet. Chat is live only.", + "LABEL_CHAT_NOTIFICATIONS": "Chat Notifications", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Shows a system notification for new room-chat messages when the video tab is not focused.", + "LABEL_EVENT_NOTIFICATIONS": "Playback & Room Notifications", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Shows system notifications when someone joins, leaves, plays, or pauses.", + "DEBUG_SECTION_VIDEO_DETECTION": "Video Detection", + "DEBUG_VIDEO_NOT_FOUND": "NO VIDEO ELEMENT FOUND", + "DEBUG_SECTION_MEDIA_SESSION": "Media Session API", + "DEBUG_VIDEO_HINT": "Is a video currently playing? KoalaSync detects browser video elements after the media has loaded.", + "DEBUG_SECTION_PLAYBACK": "Playback", + "DEBUG_STATE_PAUSED": "PAUSED", + "DEBUG_STATE_PLAYING": "PLAYING", + "DEBUG_SECTION_PROPERTIES": "Properties", + "DEBUG_SECTION_DIMENSIONS": "Dimensions", + "DEBUG_DIMENSIONS_HINT": "0×0 = video element is hidden or not loaded yet", + "DEBUG_SECTION_MEDIA_ERROR": "Media Error", + "DEBUG_SECTION_DETECTION": "Detection", + "DEBUG_SECTION_IDENTIFICATION": "Identification", + "DEBUG_SECTION_MEDIA_SOURCE": "Media Source", + "DEBUG_SECTION_DATA_ATTRIBUTES": "Data Attributes", + "DEBUG_YES": "YES", + "DEBUG_NO": "NO", + "DEBUG_YES_CHECKED": "YES (checked)", + "DEBUG_NA": "n/a" } diff --git a/extension/locales/es.json b/extension/locales/es.json index 37b2317..ff4c92b 100644 --- a/extension/locales/es.json +++ b/extension/locales/es.json @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "Siguiente", "BTN_ONBOARDING_NEXT_TOOLTIP": "Ir al siguiente paso", "ONBOARDING_1_TITLE": "¡Bienvenido a KoalaSync!", - "ONBOARDING_1_TEXT": "Mira videos junto a tus amigos en perfecta sincronización, sin importar dónde estén. ¡Hagamos un recorrido rápido!", + "ONBOARDING_1_TEXT": "Ved vídeos juntos estéis donde estéis. Aquí tienes un recorrido rápido.", "ONBOARDING_2_TITLE": "1. Crear una sala", "ONBOARDING_2_TEXT": "Comienza aquí. Crea una sala y comparte el enlace de invitación con tus amigos.", "ONBOARDING_3_TITLE": "2. Seleccionar video", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Tiempo de espera agotado: no todos los participantes cargaron el episodio", "LOBBY_CANCEL_USER": "Cancelado por el usuario", "NOTIF_ERROR_TITLE": "Error de KoalaSync", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "Apoyar KoalaSync", "FOOTER_REVIEW": "★ Valorar", "FOOTER_SUPPORT_PROMPT": "¿Te gusta KoalaSync? ¡Deja una reseña!", "LABEL_AUDIO_PROCESSING": "Procesamiento de audio", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "El chat solo está disponible mediante un enlace de invitación actual.", "CHAT_TOO_LONG": "Máximo 500 caracteres.", "CHAT_SEND_FAILED": "No se pudo enviar el mensaje.", - "CHAT_EMPTY": "Aún no hay mensajes. El chat solo es en directo." + "CHAT_EMPTY": "Aún no hay mensajes. El chat solo es en directo.", + "LABEL_CHAT_NOTIFICATIONS": "Notificaciones del chat", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Muestra una notificación del sistema para mensajes nuevos cuando la pestaña de vídeo no está activa.", + "LABEL_EVENT_NOTIFICATIONS": "Notificaciones de reproducción y sala", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Muestra notificaciones cuando alguien entra, sale, reproduce o pausa.", + "DEBUG_SECTION_VIDEO_DETECTION": "Detección de vídeo", + "DEBUG_VIDEO_NOT_FOUND": "NO SE ENCONTRÓ NINGÚN ELEMENTO DE VÍDEO", + "DEBUG_SECTION_MEDIA_SESSION": "API Media Session", + "DEBUG_VIDEO_HINT": "¿Hay un vídeo reproduciéndose? KoalaSync detecta elementos de vídeo cuando el contenido está cargado.", + "DEBUG_SECTION_PLAYBACK": "Reproducción", + "DEBUG_STATE_PAUSED": "EN PAUSA", + "DEBUG_STATE_PLAYING": "REPRODUCIENDO", + "DEBUG_SECTION_PROPERTIES": "Propiedades", + "DEBUG_SECTION_DIMENSIONS": "Dimensiones", + "DEBUG_DIMENSIONS_HINT": "0×0 = el elemento de vídeo está oculto o aún no se ha cargado", + "DEBUG_SECTION_MEDIA_ERROR": "Error multimedia", + "DEBUG_SECTION_DETECTION": "Detección", + "DEBUG_SECTION_IDENTIFICATION": "Identificación", + "DEBUG_SECTION_MEDIA_SOURCE": "Fuente multimedia", + "DEBUG_SECTION_DATA_ATTRIBUTES": "Atributos de datos", + "DEBUG_YES": "SÍ", + "DEBUG_NO": "NO", + "DEBUG_YES_CHECKED": "SÍ (comprobado)", + "DEBUG_NA": "n/d" } diff --git a/extension/locales/fr.json b/extension/locales/fr.json index be95e7a..1dd3ea5 100644 --- a/extension/locales/fr.json +++ b/extension/locales/fr.json @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "Suivant", "BTN_ONBOARDING_NEXT_TOOLTIP": "Étape suivante", "ONBOARDING_1_TITLE": "Bienvenue sur KoalaSync !", - "ONBOARDING_1_TEXT": "Regardez des vidéos en parfaite synchronisation — où que vous soyez. Faisons un petit tour !", + "ONBOARDING_1_TEXT": "Regardez des vidéos ensemble où que vous soyez. Voici une présentation rapide.", "ONBOARDING_2_TITLE": "1. Créer un salon", "ONBOARDING_2_TEXT": "Commencez ici. Créez un salon et partagez le lien d'invitation avec vos amis.", "ONBOARDING_3_TITLE": "2. Choisir une vidéo", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Délai dépassé — tous les membres n'ont pas chargé l'épisode", "LOBBY_CANCEL_USER": "Annulé par l'utilisateur", "NOTIF_ERROR_TITLE": "Erreur KoalaSync", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "Soutenir KoalaSync", "FOOTER_REVIEW": "★ Évaluer", "FOOTER_SUPPORT_PROMPT": "Tu aimes KoalaSync? Laisse un avis!", "LABEL_AUDIO_PROCESSING": "Traitement audio", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "Le chat est disponible uniquement via un lien d'invitation actuel.", "CHAT_TOO_LONG": "500 caractères maximum.", "CHAT_SEND_FAILED": "Le message n'a pas pu être envoyé.", - "CHAT_EMPTY": "Aucun message. Le chat est uniquement en direct." + "CHAT_EMPTY": "Aucun message. Le chat est uniquement en direct.", + "LABEL_CHAT_NOTIFICATIONS": "Notifications du chat", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Affiche une notification système pour les nouveaux messages lorsque l’onglet vidéo n’est pas actif.", + "LABEL_EVENT_NOTIFICATIONS": "Notifications de lecture et de salon", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Affiche des notifications quand une personne arrive, part, lance ou met en pause.", + "DEBUG_SECTION_VIDEO_DETECTION": "Détection vidéo", + "DEBUG_VIDEO_NOT_FOUND": "AUCUN ÉLÉMENT VIDÉO DÉTECTÉ", + "DEBUG_SECTION_MEDIA_SESSION": "API Media Session", + "DEBUG_VIDEO_HINT": "Une vidéo est-elle en cours de lecture ? KoalaSync détecte les éléments vidéo une fois le média chargé.", + "DEBUG_SECTION_PLAYBACK": "Lecture", + "DEBUG_STATE_PAUSED": "EN PAUSE", + "DEBUG_STATE_PLAYING": "EN LECTURE", + "DEBUG_SECTION_PROPERTIES": "Propriétés", + "DEBUG_SECTION_DIMENSIONS": "Dimensions", + "DEBUG_DIMENSIONS_HINT": "0×0 = l’élément vidéo est masqué ou pas encore chargé", + "DEBUG_SECTION_MEDIA_ERROR": "Erreur multimédia", + "DEBUG_SECTION_DETECTION": "Détection", + "DEBUG_SECTION_IDENTIFICATION": "Identification", + "DEBUG_SECTION_MEDIA_SOURCE": "Source multimédia", + "DEBUG_SECTION_DATA_ATTRIBUTES": "Attributs de données", + "DEBUG_YES": "OUI", + "DEBUG_NO": "NON", + "DEBUG_YES_CHECKED": "OUI (vérifié)", + "DEBUG_NA": "n/d" } diff --git a/extension/locales/it.json b/extension/locales/it.json index d09157f..5311c9c 100644 --- a/extension/locales/it.json +++ b/extension/locales/it.json @@ -34,7 +34,7 @@ "BTN_REFRESH_TOOLTIP": "Aggiorna l'elenco delle stanze", "PUBLIC_ROOMS_REFRESHING": "Aggiornamento...", "BTN_REFRESH_COOLDOWN": "ATTENDI {seconds}s", - "BTN_REFRESH_COOLDOWN_TOOLTIP": "Riprova tra {seconds}s.", + "BTN_REFRESH_COOLDOWN_TOOLTIP": "L’elenco delle stanze è in attesa. Riprova tra {seconds}s.", "PUBLIC_ROOMS_REFRESHING_COOLDOWN": "Aggiornamento in corso. Disponibile tra {seconds}s.", "LABEL_ACTIVE_ROOM": "Stanza Attiva", "LABEL_ACTIVE_ROOM_TOOLTIP": "La stanza a cui sei connesso", @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "Avanti", "BTN_ONBOARDING_NEXT_TOOLTIP": "Vai al passaggio successivo", "ONBOARDING_1_TITLE": "Benvenuto in KoalaSync!", - "ONBOARDING_1_TEXT": "Guarda i video insieme ai tuoi amici in perfetta sincronia. Facciamo un breve tour!", + "ONBOARDING_1_TEXT": "Guarda video insieme ovunque vi troviate. Ecco una breve panoramica.", "ONBOARDING_2_TITLE": "1. Crea una Stanza", "ONBOARDING_2_TEXT": "Inizia da qui. Crea una stanza e condividi il link con i tuoi amici.", "ONBOARDING_3_TITLE": "2. Seleziona Video", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Tempo scaduto: caricamento incompleto per alcuni partecipanti", "LOBBY_CANCEL_USER": "Annullato dall'utente", "NOTIF_ERROR_TITLE": "Errore KoalaSync", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "Sostieni KoalaSync", "FOOTER_REVIEW": "★ Valutaci", "FOOTER_SUPPORT_PROMPT": "Ti piace KoalaSync? Lascia una recensione!", "LABEL_AUDIO_PROCESSING": "Elaborazione Audio", @@ -260,7 +260,7 @@ "OPTION_CHAT_SIZE_CUSTOM": "Ultima dimensione personalizzata", "LABEL_CHAT_START_MODE": "Avvio come", "LABEL_CHAT_START_MODE_TOOLTIP": "Scegli se la chat si avvia come bolla mobile o overlay aperto.", - "OPTION_CHAT_START_BUBBLE": "Bolla mobile", + "OPTION_CHAT_START_BUBBLE": "Bolla fluttuante", "OPTION_CHAT_START_OPEN": "Overlay aperto", "CHAT_TITLE": "Chat della stanza", "CHAT_LIVE_ONLY": "Solo in diretta. Nessuna cronologia.", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "La chat è disponibile solo tramite un link di invito attuale.", "CHAT_TOO_LONG": "Massimo 500 caratteri.", "CHAT_SEND_FAILED": "Impossibile inviare il messaggio.", - "CHAT_EMPTY": "Nessun messaggio. La chat è solo in diretta." + "CHAT_EMPTY": "Nessun messaggio. La chat è solo in diretta.", + "LABEL_CHAT_NOTIFICATIONS": "Notifiche chat", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Mostra una notifica di sistema per i nuovi messaggi quando la scheda video non è attiva.", + "LABEL_EVENT_NOTIFICATIONS": "Notifiche di riproduzione e stanza", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Mostra notifiche quando qualcuno entra, esce, avvia o mette in pausa.", + "DEBUG_SECTION_VIDEO_DETECTION": "Rilevamento video", + "DEBUG_VIDEO_NOT_FOUND": "NESSUN ELEMENTO VIDEO TROVATO", + "DEBUG_SECTION_MEDIA_SESSION": "API Media Session", + "DEBUG_VIDEO_HINT": "È in riproduzione un video? KoalaSync rileva gli elementi video dopo il caricamento del contenuto.", + "DEBUG_SECTION_PLAYBACK": "Riproduzione", + "DEBUG_STATE_PAUSED": "IN PAUSA", + "DEBUG_STATE_PLAYING": "IN RIPRODUZIONE", + "DEBUG_SECTION_PROPERTIES": "Proprietà", + "DEBUG_SECTION_DIMENSIONS": "Dimensioni", + "DEBUG_DIMENSIONS_HINT": "0×0 = elemento video nascosto o non ancora caricato", + "DEBUG_SECTION_MEDIA_ERROR": "Errore multimediale", + "DEBUG_SECTION_DETECTION": "Rilevamento", + "DEBUG_SECTION_IDENTIFICATION": "Identificazione", + "DEBUG_SECTION_MEDIA_SOURCE": "Sorgente multimediale", + "DEBUG_SECTION_DATA_ATTRIBUTES": "Attributi dati", + "DEBUG_YES": "SÌ", + "DEBUG_NO": "NO", + "DEBUG_YES_CHECKED": "SÌ (verificato)", + "DEBUG_NA": "n/d" } diff --git a/extension/locales/ja.json b/extension/locales/ja.json index 70b1ce6..879d1dc 100644 --- a/extension/locales/ja.json +++ b/extension/locales/ja.json @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "次へ", "BTN_ONBOARDING_NEXT_TOOLTIP": "次のステップに進む", "ONBOARDING_1_TITLE": "KoalaSyncへようこそ!", - "ONBOARDING_1_TEXT": "どこにいても、完璧に同期してビデオを一緒に楽しめます。簡単なツアーを始めましょう!", + "ONBOARDING_1_TEXT": "離れた場所から一緒に動画を視聴できます。簡単に機能を紹介します。", "ONBOARDING_2_TITLE": "1. ルームの作成", "ONBOARDING_2_TEXT": "ここからスタート。ルームを作成し、招待リンクを友達に共有します。", "ONBOARDING_3_TITLE": "2. ビデオの選択", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "タイムアウト — 一部のメンバーがエピソードを読み込めませんでした", "LOBBY_CANCEL_USER": "ユーザーによってキャンセルされました", "NOTIF_ERROR_TITLE": "KoalaSyncエラー", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "KoalaSyncを支援", "FOOTER_REVIEW": "★ 評価する", "FOOTER_SUPPORT_PROMPT": "KoalaSyncはいかが?レビューを書いてください!", "LABEL_AUDIO_PROCESSING": "オーディオ処理", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "チャットは最新の招待リンクからのみ利用できます。", "CHAT_TOO_LONG": "最大500文字です。", "CHAT_SEND_FAILED": "メッセージを送信できませんでした。", - "CHAT_EMPTY": "まだメッセージはありません。チャットはライブのみです。" + "CHAT_EMPTY": "まだメッセージはありません。チャットはライブのみです。", + "LABEL_CHAT_NOTIFICATIONS": "チャット通知", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "動画タブがアクティブでないとき、新しいルームチャットをシステム通知で表示します。", + "LABEL_EVENT_NOTIFICATIONS": "再生とルームの通知", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "参加、退出、再生、一時停止をシステム通知で表示します。", + "DEBUG_SECTION_VIDEO_DETECTION": "動画検出", + "DEBUG_VIDEO_NOT_FOUND": "動画要素が見つかりません", + "DEBUG_SECTION_MEDIA_SESSION": "Media Session API", + "DEBUG_VIDEO_HINT": "動画を再生していますか?メディアの読み込み後にKoalaSyncが動画要素を検出します。", + "DEBUG_SECTION_PLAYBACK": "再生", + "DEBUG_STATE_PAUSED": "一時停止", + "DEBUG_STATE_PLAYING": "再生中", + "DEBUG_SECTION_PROPERTIES": "プロパティ", + "DEBUG_SECTION_DIMENSIONS": "サイズ", + "DEBUG_DIMENSIONS_HINT": "0×0 = 動画要素が非表示、または未読み込みです", + "DEBUG_SECTION_MEDIA_ERROR": "メディアエラー", + "DEBUG_SECTION_DETECTION": "検出", + "DEBUG_SECTION_IDENTIFICATION": "識別情報", + "DEBUG_SECTION_MEDIA_SOURCE": "メディアソース", + "DEBUG_SECTION_DATA_ATTRIBUTES": "データ属性", + "DEBUG_YES": "はい", + "DEBUG_NO": "いいえ", + "DEBUG_YES_CHECKED": "はい(確認済み)", + "DEBUG_NA": "なし" } diff --git a/extension/locales/ko.json b/extension/locales/ko.json index e7cf680..36e7a28 100644 --- a/extension/locales/ko.json +++ b/extension/locales/ko.json @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "다음", "BTN_ONBOARDING_NEXT_TOOLTIP": "다음 단계로 이동", "ONBOARDING_1_TITLE": "KoalaSync에 오신 것을 환영합니다!", - "ONBOARDING_1_TEXT": "어디에 있든 완벽하게 동기화된 비디오를 함께 시청하세요. 간단히 둘러볼까요!", + "ONBOARDING_1_TEXT": "어디서든 함께 비디오를 시청하세요. 주요 기능을 간단히 살펴봅니다.", "ONBOARDING_2_TITLE": "1. 방 만들기", "ONBOARDING_2_TEXT": "여기서 시작하세요. 방을 만들고 친구들에게 초대 링크를 공유하세요.", "ONBOARDING_3_TITLE": "2. 비디오 선택", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "시간 초과 — 모든 피어가 에피소드를 로드하지 못했습니다", "LOBBY_CANCEL_USER": "사용자에 의해 취소됨", "NOTIF_ERROR_TITLE": "KoalaSync 오류", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "KoalaSync 후원", "FOOTER_REVIEW": "★ 평가하기", "FOOTER_SUPPORT_PROMPT": "KoalaSync가 마음에 드세요? 리뷰를 남겨주세요!", "LABEL_AUDIO_PROCESSING": "오디오 처리", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "채팅은 최신 초대 링크를 통해서만 사용할 수 있습니다.", "CHAT_TOO_LONG": "최대 500자입니다.", "CHAT_SEND_FAILED": "메시지를 보낼 수 없습니다.", - "CHAT_EMPTY": "아직 메시지가 없습니다. 채팅은 실시간 전용입니다." + "CHAT_EMPTY": "아직 메시지가 없습니다. 채팅은 실시간 전용입니다.", + "LABEL_CHAT_NOTIFICATIONS": "채팅 알림", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "비디오 탭이 활성 상태가 아닐 때 새 방 채팅 메시지를 시스템 알림으로 표시합니다.", + "LABEL_EVENT_NOTIFICATIONS": "재생 및 방 알림", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "참여, 퇴장, 재생 또는 일시정지를 시스템 알림으로 표시합니다.", + "DEBUG_SECTION_VIDEO_DETECTION": "비디오 감지", + "DEBUG_VIDEO_NOT_FOUND": "비디오 요소를 찾지 못했습니다", + "DEBUG_SECTION_MEDIA_SESSION": "Media Session API", + "DEBUG_VIDEO_HINT": "비디오가 재생 중인가요? 미디어가 로드된 후 KoalaSync가 비디오 요소를 감지합니다.", + "DEBUG_SECTION_PLAYBACK": "재생", + "DEBUG_STATE_PAUSED": "일시정지", + "DEBUG_STATE_PLAYING": "재생 중", + "DEBUG_SECTION_PROPERTIES": "속성", + "DEBUG_SECTION_DIMENSIONS": "크기", + "DEBUG_DIMENSIONS_HINT": "0×0 = 비디오 요소가 숨겨져 있거나 아직 로드되지 않음", + "DEBUG_SECTION_MEDIA_ERROR": "미디어 오류", + "DEBUG_SECTION_DETECTION": "감지", + "DEBUG_SECTION_IDENTIFICATION": "식별 정보", + "DEBUG_SECTION_MEDIA_SOURCE": "미디어 소스", + "DEBUG_SECTION_DATA_ATTRIBUTES": "데이터 속성", + "DEBUG_YES": "예", + "DEBUG_NO": "아니요", + "DEBUG_YES_CHECKED": "예(확인됨)", + "DEBUG_NA": "없음" } diff --git a/extension/locales/nl.json b/extension/locales/nl.json index b1acfd4..d635430 100644 --- a/extension/locales/nl.json +++ b/extension/locales/nl.json @@ -82,7 +82,7 @@ "LABEL_LAST_ACTIVITY": "Laatste activiteitsstatus", "LABEL_LAST_ACTIVITY_TOOLTIP": "Toont de meest recente afspeel-, pauzeer- of zoekopdracht", "NO_RECENT_COMMANDS": "Geen recente commando's", - "LOBBY_HEADER": "EPISODE LOBBY", + "LOBBY_HEADER": "AFLEVERINGSLOBBY", "LOBBY_WAITING_FOR": "🎬 Wachten op: \"{title}\"", "LOBBY_WAITING_PEERS": "Wachten op deelnemers...", "BTN_SKIP_PLAY": "Overslaan & toch afspelen", @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "Volgende", "BTN_ONBOARDING_NEXT_TOOLTIP": "Ga naar de volgende stap", "ONBOARDING_1_TITLE": "Welkom bij KoalaSync!", - "ONBOARDING_1_TEXT": "Bekijk video's samen in perfecte synchronisatie — waar u ook bent. Laten we een snelle rondleiding nemen!", + "ONBOARDING_1_TEXT": "Bekijk samen video’s, waar je ook bent. Hier volgt een korte rondleiding.", "ONBOARDING_2_TITLE": "1. Kamer maken", "ONBOARDING_2_TEXT": "Begin hier. Maak een kamer en deel de uitnodigingslink met uw vrienden.", "ONBOARDING_3_TITLE": "2. Selecteer video", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Time-out — niet alle deelnemers hebben de aflevering geladen", "LOBBY_CANCEL_USER": "Geannuleerd door gebruiker", "NOTIF_ERROR_TITLE": "KoalaSync-fout", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "Steun KoalaSync", "FOOTER_REVIEW": "★ Beoordelen", "FOOTER_SUPPORT_PROMPT": "Vind je KoalaSync leuk? Laat een review achter!", "LABEL_AUDIO_PROCESSING": "Audioverwerking", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "Chat is alleen beschikbaar via een actuele uitnodigingslink.", "CHAT_TOO_LONG": "Maximaal 500 tekens.", "CHAT_SEND_FAILED": "Bericht kon niet worden verzonden.", - "CHAT_EMPTY": "Nog geen berichten. Chat is alleen live." + "CHAT_EMPTY": "Nog geen berichten. Chat is alleen live.", + "LABEL_CHAT_NOTIFICATIONS": "Chatmeldingen", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Toont een systeemmelding voor nieuwe chatberichten wanneer het videotabblad niet actief is.", + "LABEL_EVENT_NOTIFICATIONS": "Afspeel- en kamermeldingen", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Toont meldingen wanneer iemand deelneemt, vertrekt, afspeelt of pauzeert.", + "DEBUG_SECTION_VIDEO_DETECTION": "Videodetectie", + "DEBUG_VIDEO_NOT_FOUND": "GEEN VIDEO-ELEMENT GEVONDEN", + "DEBUG_SECTION_MEDIA_SESSION": "Media Session API", + "DEBUG_VIDEO_HINT": "Wordt er een video afgespeeld? KoalaSync detecteert video-elementen nadat de media is geladen.", + "DEBUG_SECTION_PLAYBACK": "Afspelen", + "DEBUG_STATE_PAUSED": "GEPAUZEERD", + "DEBUG_STATE_PLAYING": "WORDT AFGESPEELD", + "DEBUG_SECTION_PROPERTIES": "Eigenschappen", + "DEBUG_SECTION_DIMENSIONS": "Afmetingen", + "DEBUG_DIMENSIONS_HINT": "0×0 = video-element is verborgen of nog niet geladen", + "DEBUG_SECTION_MEDIA_ERROR": "Mediafout", + "DEBUG_SECTION_DETECTION": "Detectie", + "DEBUG_SECTION_IDENTIFICATION": "Identificatie", + "DEBUG_SECTION_MEDIA_SOURCE": "Mediabron", + "DEBUG_SECTION_DATA_ATTRIBUTES": "Data-attributen", + "DEBUG_YES": "JA", + "DEBUG_NO": "NEE", + "DEBUG_YES_CHECKED": "JA (gecontroleerd)", + "DEBUG_NA": "n.v.t." } diff --git a/extension/locales/pl.json b/extension/locales/pl.json index 4fea8df..e34c7ef 100644 --- a/extension/locales/pl.json +++ b/extension/locales/pl.json @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "Dalej", "BTN_ONBOARDING_NEXT_TOOLTIP": "Przejdź do następnego kroku", "ONBOARDING_1_TITLE": "Witaj w KoalaSync!", - "ONBOARDING_1_TEXT": "Oglądaj filmy w idealnej synchronizacji — bez względu na to, gdzie jesteś. Zróbmy szybką wycieczkę!", + "ONBOARDING_1_TEXT": "Oglądajcie filmy razem, gdziekolwiek jesteście. Oto krótkie wprowadzenie.", "ONBOARDING_2_TITLE": "1. Utwórz pokój", "ONBOARDING_2_TEXT": "Zacznij tutaj. Utwórz pokój i udostępnij link zaproszenia znajomym.", "ONBOARDING_3_TITLE": "2. Wybierz wideo", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Limit czasu — nie wszyscy uczestnicy załadowali odcinek", "LOBBY_CANCEL_USER": "Anulowane przez użytkownika", "NOTIF_ERROR_TITLE": "Błąd KoalaSync", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "Wesprzyj KoalaSync", "FOOTER_REVIEW": "★ Oceń", "FOOTER_SUPPORT_PROMPT": "Podoba Ci się KoalaSync? Zostaw recenzję!", "LABEL_AUDIO_PROCESSING": "Przetwarzanie dźwięku", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "Czat jest dostępny tylko przez aktualny link zaproszenia.", "CHAT_TOO_LONG": "Maksymalnie 500 znaków.", "CHAT_SEND_FAILED": "Nie udało się wysłać wiadomości.", - "CHAT_EMPTY": "Brak wiadomości. Czat działa tylko na żywo." + "CHAT_EMPTY": "Brak wiadomości. Czat działa tylko na żywo.", + "LABEL_CHAT_NOTIFICATIONS": "Powiadomienia czatu", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Pokazuje powiadomienie systemowe o nowej wiadomości, gdy karta wideo nie jest aktywna.", + "LABEL_EVENT_NOTIFICATIONS": "Powiadomienia o odtwarzaniu i pokoju", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Pokazuje powiadomienia, gdy ktoś dołącza, wychodzi, odtwarza lub wstrzymuje.", + "DEBUG_SECTION_VIDEO_DETECTION": "Wykrywanie wideo", + "DEBUG_VIDEO_NOT_FOUND": "NIE ZNALEZIONO ELEMENTU WIDEO", + "DEBUG_SECTION_MEDIA_SESSION": "API Media Session", + "DEBUG_VIDEO_HINT": "Czy wideo jest odtwarzane? KoalaSync wykrywa elementy wideo po załadowaniu multimediów.", + "DEBUG_SECTION_PLAYBACK": "Odtwarzanie", + "DEBUG_STATE_PAUSED": "WSTRZYMANE", + "DEBUG_STATE_PLAYING": "ODTWARZANIE", + "DEBUG_SECTION_PROPERTIES": "Właściwości", + "DEBUG_SECTION_DIMENSIONS": "Wymiary", + "DEBUG_DIMENSIONS_HINT": "0×0 = element wideo jest ukryty lub jeszcze niezaładowany", + "DEBUG_SECTION_MEDIA_ERROR": "Błąd multimediów", + "DEBUG_SECTION_DETECTION": "Wykrywanie", + "DEBUG_SECTION_IDENTIFICATION": "Identyfikacja", + "DEBUG_SECTION_MEDIA_SOURCE": "Źródło multimediów", + "DEBUG_SECTION_DATA_ATTRIBUTES": "Atrybuty danych", + "DEBUG_YES": "TAK", + "DEBUG_NO": "NIE", + "DEBUG_YES_CHECKED": "TAK (sprawdzono)", + "DEBUG_NA": "brak" } diff --git a/extension/locales/pt-BR.json b/extension/locales/pt-BR.json index 7861967..e335b48 100644 --- a/extension/locales/pt-BR.json +++ b/extension/locales/pt-BR.json @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "Próximo", "BTN_ONBOARDING_NEXT_TOOLTIP": "Ir para o próximo passo", "ONBOARDING_1_TITLE": "Bem-vindo ao KoalaSync!", - "ONBOARDING_1_TEXT": "Assista a vídeos com seus amigos em perfeita sincronia, não importa onde estejam. Vamos fazer um tour rápido!", + "ONBOARDING_1_TEXT": "Assista a vídeos com outras pessoas, onde vocês estiverem. Veja uma apresentação rápida.", "ONBOARDING_2_TITLE": "1. Criar uma sala", "ONBOARDING_2_TEXT": "Comece aqui. Crie uma sala e compartilhe o link de convite com seus amigos.", "ONBOARDING_3_TITLE": "2. Selecionar vídeo", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Tempo limite — nem todos carregaram o episódio", "LOBBY_CANCEL_USER": "Cancelado pelo usuário", "NOTIF_ERROR_TITLE": "Erro no KoalaSync", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "Apoie o KoalaSync", "FOOTER_REVIEW": "★ Avaliar", "FOOTER_SUPPORT_PROMPT": "Gostou do KoalaSync? Deixe uma avaliação!", "LABEL_AUDIO_PROCESSING": "Processamento de áudio", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "O chat está disponível apenas por um link de convite atual.", "CHAT_TOO_LONG": "Máximo de 500 caracteres.", "CHAT_SEND_FAILED": "Não foi possível enviar a mensagem.", - "CHAT_EMPTY": "Ainda não há mensagens. O chat é somente ao vivo." + "CHAT_EMPTY": "Ainda não há mensagens. O chat é somente ao vivo.", + "LABEL_CHAT_NOTIFICATIONS": "Notificações do chat", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Mostra uma notificação do sistema para novas mensagens quando a aba do vídeo não está ativa.", + "LABEL_EVENT_NOTIFICATIONS": "Notificações de reprodução e sala", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Mostra notificações quando alguém entra, sai, reproduz ou pausa.", + "DEBUG_SECTION_VIDEO_DETECTION": "Detecção de vídeo", + "DEBUG_VIDEO_NOT_FOUND": "NENHUM ELEMENTO DE VÍDEO ENCONTRADO", + "DEBUG_SECTION_MEDIA_SESSION": "API Media Session", + "DEBUG_VIDEO_HINT": "Há um vídeo sendo reproduzido? O KoalaSync detecta elementos de vídeo após o carregamento da mídia.", + "DEBUG_SECTION_PLAYBACK": "Reprodução", + "DEBUG_STATE_PAUSED": "PAUSADO", + "DEBUG_STATE_PLAYING": "REPRODUZINDO", + "DEBUG_SECTION_PROPERTIES": "Propriedades", + "DEBUG_SECTION_DIMENSIONS": "Dimensões", + "DEBUG_DIMENSIONS_HINT": "0×0 = elemento de vídeo oculto ou ainda não carregado", + "DEBUG_SECTION_MEDIA_ERROR": "Erro de mídia", + "DEBUG_SECTION_DETECTION": "Detecção", + "DEBUG_SECTION_IDENTIFICATION": "Identificação", + "DEBUG_SECTION_MEDIA_SOURCE": "Fonte de mídia", + "DEBUG_SECTION_DATA_ATTRIBUTES": "Atributos de dados", + "DEBUG_YES": "SIM", + "DEBUG_NO": "NÃO", + "DEBUG_YES_CHECKED": "SIM (verificado)", + "DEBUG_NA": "n/d" } diff --git a/extension/locales/pt.json b/extension/locales/pt.json index 539eb43..a3bfb67 100644 --- a/extension/locales/pt.json +++ b/extension/locales/pt.json @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "Seguinte", "BTN_ONBOARDING_NEXT_TOOLTIP": "Ir para o passo seguinte", "ONBOARDING_1_TITLE": "Bem-vindo ao KoalaSync!", - "ONBOARDING_1_TEXT": "Veja vídeos em conjunto com os seus amigos em sincronia perfeita. Vamos fazer uma breve visita guiada!", + "ONBOARDING_1_TEXT": "Vê vídeos com outras pessoas, onde quer que estejam. Aqui tens uma breve apresentação.", "ONBOARDING_2_TITLE": "1. Criar uma Sala", "ONBOARDING_2_TEXT": "Comece aqui. Crie uma sala e partilhe o link de convite com os seus amigos.", "ONBOARDING_3_TITLE": "2. Selecionar Vídeo", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Tempo limite — alguns participantes não carregaram o episódio", "LOBBY_CANCEL_USER": "Cancelado pelo utilizador", "NOTIF_ERROR_TITLE": "Erro no KoalaSync", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "Apoiar o KoalaSync", "FOOTER_REVIEW": "★ Avaliar", "FOOTER_SUPPORT_PROMPT": "Gostas do KoalaSync? Deixa uma avaliação!", "LABEL_AUDIO_PROCESSING": "Processamento de Áudio", @@ -249,7 +249,7 @@ "LABEL_CHAT_ENABLED": "Ativar chat da sala", "LABEL_CHAT_ENABLED_TOOLTIP": "Mostrar o chat encriptado da sala no separador de vídeo selecionado.", "LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Configure o aspeto e a posição do chat encriptado da sala.", - "CHAT_SETTINGS_NOTE": "Opcional e encriptado ponto a ponto. As mensagens são apenas em direto e não são guardadas pelo relay.", + "CHAT_SETTINGS_NOTE": "Opcional e encriptado de ponta a ponta. As mensagens são apenas em direto e não são guardadas pelo relay.", "LABEL_CHAT_POSITION": "Posição", "LABEL_CHAT_POSITION_TOOLTIP": "Escolha onde o chat fica fixado no leitor selecionado.", "LABEL_CHAT_SIZE": "Tamanho da sobreposição", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "O chat está disponível apenas através de um link de convite atual.", "CHAT_TOO_LONG": "Máximo de 500 caracteres.", "CHAT_SEND_FAILED": "Não foi possível enviar a mensagem.", - "CHAT_EMPTY": "Ainda não há mensagens. O chat funciona apenas em direto." + "CHAT_EMPTY": "Ainda não há mensagens. O chat funciona apenas em direto.", + "LABEL_CHAT_NOTIFICATIONS": "Notificações do chat", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Mostra uma notificação do sistema para novas mensagens quando o separador do vídeo não está ativo.", + "LABEL_EVENT_NOTIFICATIONS": "Notificações de reprodução e sala", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Mostra notificações quando alguém entra, sai, reproduz ou pausa.", + "DEBUG_SECTION_VIDEO_DETECTION": "Deteção de vídeo", + "DEBUG_VIDEO_NOT_FOUND": "NENHUM ELEMENTO DE VÍDEO ENCONTRADO", + "DEBUG_SECTION_MEDIA_SESSION": "API Media Session", + "DEBUG_VIDEO_HINT": "Está algum vídeo em reprodução? O KoalaSync deteta elementos de vídeo após o carregamento do conteúdo.", + "DEBUG_SECTION_PLAYBACK": "Reprodução", + "DEBUG_STATE_PAUSED": "EM PAUSA", + "DEBUG_STATE_PLAYING": "A REPRODUZIR", + "DEBUG_SECTION_PROPERTIES": "Propriedades", + "DEBUG_SECTION_DIMENSIONS": "Dimensões", + "DEBUG_DIMENSIONS_HINT": "0×0 = elemento de vídeo oculto ou ainda não carregado", + "DEBUG_SECTION_MEDIA_ERROR": "Erro multimédia", + "DEBUG_SECTION_DETECTION": "Deteção", + "DEBUG_SECTION_IDENTIFICATION": "Identificação", + "DEBUG_SECTION_MEDIA_SOURCE": "Fonte multimédia", + "DEBUG_SECTION_DATA_ATTRIBUTES": "Atributos de dados", + "DEBUG_YES": "SIM", + "DEBUG_NO": "NÃO", + "DEBUG_YES_CHECKED": "SIM (verificado)", + "DEBUG_NA": "n/d" } diff --git a/extension/locales/ru.json b/extension/locales/ru.json index d51f982..48d1bfe 100644 --- a/extension/locales/ru.json +++ b/extension/locales/ru.json @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "Далее", "BTN_ONBOARDING_NEXT_TOOLTIP": "Перейти к следующему шагу", "ONBOARDING_1_TITLE": "Добро пожаловать в KoalaSync!", - "ONBOARDING_1_TEXT": "Смотрите видео в идеальной синхронизации вместе — где бы вы ни находились. Давайте проведем небольшую экскурсию!", + "ONBOARDING_1_TEXT": "Смотрите видео вместе, где бы вы ни находились. Вот краткий обзор.", "ONBOARDING_2_TITLE": "1. Создайте комнату", "ONBOARDING_2_TEXT": "Начните здесь. Создайте комнату и поделитесь ссылкой-приглашением со своими друзьями.", "ONBOARDING_3_TITLE": "2. Выберите видео", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Время ожидания истекло — не все участники загрузили серию", "LOBBY_CANCEL_USER": "Отменено пользователем", "NOTIF_ERROR_TITLE": "Ошибка KoalaSync", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "Поддержать KoalaSync", "FOOTER_REVIEW": "★ Оценить", "FOOTER_SUPPORT_PROMPT": "Нравится KoalaSync? Оставьте отзыв!", "LABEL_AUDIO_PROCESSING": "Обработка звука", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "Чат доступен только по актуальной ссылке-приглашению.", "CHAT_TOO_LONG": "Не более 500 символов.", "CHAT_SEND_FAILED": "Не удалось отправить сообщение.", - "CHAT_EMPTY": "Сообщений пока нет. Чат работает только в реальном времени." + "CHAT_EMPTY": "Сообщений пока нет. Чат работает только в реальном времени.", + "LABEL_CHAT_NOTIFICATIONS": "Уведомления чата", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Показывает системное уведомление о новых сообщениях, когда вкладка с видео не активна.", + "LABEL_EVENT_NOTIFICATIONS": "Уведомления о воспроизведении и комнате", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Показывает уведомления, когда кто-то входит, выходит, запускает или приостанавливает видео.", + "DEBUG_SECTION_VIDEO_DETECTION": "Обнаружение видео", + "DEBUG_VIDEO_NOT_FOUND": "ВИДЕОЭЛЕМЕНТ НЕ НАЙДЕН", + "DEBUG_SECTION_MEDIA_SESSION": "API Media Session", + "DEBUG_VIDEO_HINT": "Видео сейчас воспроизводится? KoalaSync обнаруживает видеоэлементы после загрузки контента.", + "DEBUG_SECTION_PLAYBACK": "Воспроизведение", + "DEBUG_STATE_PAUSED": "ПАУЗА", + "DEBUG_STATE_PLAYING": "ВОСПРОИЗВЕДЕНИЕ", + "DEBUG_SECTION_PROPERTIES": "Свойства", + "DEBUG_SECTION_DIMENSIONS": "Размеры", + "DEBUG_DIMENSIONS_HINT": "0×0 = видеоэлемент скрыт или ещё не загружен", + "DEBUG_SECTION_MEDIA_ERROR": "Ошибка медиа", + "DEBUG_SECTION_DETECTION": "Обнаружение", + "DEBUG_SECTION_IDENTIFICATION": "Идентификация", + "DEBUG_SECTION_MEDIA_SOURCE": "Источник медиа", + "DEBUG_SECTION_DATA_ATTRIBUTES": "Атрибуты данных", + "DEBUG_YES": "ДА", + "DEBUG_NO": "НЕТ", + "DEBUG_YES_CHECKED": "ДА (проверено)", + "DEBUG_NA": "н/д" } diff --git a/extension/locales/tr.json b/extension/locales/tr.json index 8f49b35..5cafd13 100644 --- a/extension/locales/tr.json +++ b/extension/locales/tr.json @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "İleri", "BTN_ONBOARDING_NEXT_TOOLTIP": "Bir sonraki adıma git", "ONBOARDING_1_TITLE": "KoalaSync'e Hoş Geldiniz!", - "ONBOARDING_1_TEXT": "Nerede olursanız olun videoları mükemmel bir senkronizasyonla birlikte izleyin. Hızlı bir tura çıkalım!", + "ONBOARDING_1_TEXT": "Nerede olursanız olun videoları birlikte izleyin. İşte kısa bir tanıtım.", "ONBOARDING_2_TITLE": "1. Oda Oluştur", "ONBOARDING_2_TEXT": "Buradan başlayın. Bir oda oluşturun ve davet bağlantısını arkadaşlarınızla paylaşın.", "ONBOARDING_3_TITLE": "2. Video Seç", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Zaman aşımı — tüm bağlantılar bölümü yüklemedi", "LOBBY_CANCEL_USER": "Kullanıcı tarafından iptal edildi", "NOTIF_ERROR_TITLE": "KoalaSync Hatası", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "KoalaSync’i destekle", "FOOTER_REVIEW": "★ Değerlendir", "FOOTER_SUPPORT_PROMPT": "KoalaSync'i beğendin mi? Bir yorum bırak!", "LABEL_AUDIO_PROCESSING": "Ses işleme", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "Sohbet yalnızca güncel bir davet bağlantısıyla kullanılabilir.", "CHAT_TOO_LONG": "En fazla 500 karakter.", "CHAT_SEND_FAILED": "Mesaj gönderilemedi.", - "CHAT_EMPTY": "Henüz mesaj yok. Sohbet yalnızca canlıdır." + "CHAT_EMPTY": "Henüz mesaj yok. Sohbet yalnızca canlıdır.", + "LABEL_CHAT_NOTIFICATIONS": "Sohbet bildirimleri", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Video sekmesi etkin değilken yeni oda mesajlarını sistem bildirimi olarak gösterir.", + "LABEL_EVENT_NOTIFICATIONS": "Oynatma ve oda bildirimleri", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Biri katıldığında, ayrıldığında, oynattığında veya duraklattığında bildirim gösterir.", + "DEBUG_SECTION_VIDEO_DETECTION": "Video algılama", + "DEBUG_VIDEO_NOT_FOUND": "VİDEO ÖĞESİ BULUNAMADI", + "DEBUG_SECTION_MEDIA_SESSION": "Media Session API", + "DEBUG_VIDEO_HINT": "Şu anda video oynatılıyor mu? KoalaSync medya yüklendikten sonra video öğelerini algılar.", + "DEBUG_SECTION_PLAYBACK": "Oynatma", + "DEBUG_STATE_PAUSED": "DURAKLATILDI", + "DEBUG_STATE_PLAYING": "OYNATILIYOR", + "DEBUG_SECTION_PROPERTIES": "Özellikler", + "DEBUG_SECTION_DIMENSIONS": "Boyutlar", + "DEBUG_DIMENSIONS_HINT": "0×0 = video öğesi gizli veya henüz yüklenmedi", + "DEBUG_SECTION_MEDIA_ERROR": "Medya hatası", + "DEBUG_SECTION_DETECTION": "Algılama", + "DEBUG_SECTION_IDENTIFICATION": "Tanımlama", + "DEBUG_SECTION_MEDIA_SOURCE": "Medya kaynağı", + "DEBUG_SECTION_DATA_ATTRIBUTES": "Veri öznitelikleri", + "DEBUG_YES": "EVET", + "DEBUG_NO": "HAYIR", + "DEBUG_YES_CHECKED": "EVET (kontrol edildi)", + "DEBUG_NA": "yok" } diff --git a/extension/locales/uk.json b/extension/locales/uk.json index af15714..4d0e62b 100644 --- a/extension/locales/uk.json +++ b/extension/locales/uk.json @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "Далі", "BTN_ONBOARDING_NEXT_TOOLTIP": "Перейти до наступного кроку", "ONBOARDING_1_TITLE": "Ласкаво просимо до KoalaSync!", - "ONBOARDING_1_TEXT": "Дивіться відео разом у ідеальній синхронізації — де б ви не були. Давайте зробимо короткий тур!", + "ONBOARDING_1_TEXT": "Дивіться відео разом, де б ви не були. Ось короткий огляд.", "ONBOARDING_2_TITLE": "1. Створіть кімнату", "ONBOARDING_2_TEXT": "Почніть тут. Створіть кімнату та поділіться посиланням запрошення з друзями.", "ONBOARDING_3_TITLE": "2. Виберіть Відео", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Тайм-аут — не всі вузли завантажили епізод", "LOBBY_CANCEL_USER": "Скасовано користувачем", "NOTIF_ERROR_TITLE": "Помилка KoalaSync", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "Підтримати KoalaSync", "FOOTER_REVIEW": "★ Оцініть нас", "FOOTER_SUPPORT_PROMPT": "Подобається KoalaSync? Залиште відгук!", "LABEL_AUDIO_PROCESSING": "Обробка аудіо", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "Чат доступний лише за актуальним посиланням-запрошенням.", "CHAT_TOO_LONG": "Не більше 500 символів.", "CHAT_SEND_FAILED": "Не вдалося надіслати повідомлення.", - "CHAT_EMPTY": "Повідомлень ще немає. Чат працює лише наживо." + "CHAT_EMPTY": "Повідомлень ще немає. Чат працює лише наживо.", + "LABEL_CHAT_NOTIFICATIONS": "Сповіщення чату", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Показує системне сповіщення про нові повідомлення, коли вкладка з відео неактивна.", + "LABEL_EVENT_NOTIFICATIONS": "Сповіщення про відтворення та кімнату", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Показує сповіщення, коли хтось приєднується, виходить, запускає або призупиняє відео.", + "DEBUG_SECTION_VIDEO_DETECTION": "Виявлення відео", + "DEBUG_VIDEO_NOT_FOUND": "ВІДЕОЕЛЕМЕНТ НЕ ЗНАЙДЕНО", + "DEBUG_SECTION_MEDIA_SESSION": "API Media Session", + "DEBUG_VIDEO_HINT": "Відео зараз відтворюється? KoalaSync виявляє відеоелементи після завантаження медіа.", + "DEBUG_SECTION_PLAYBACK": "Відтворення", + "DEBUG_STATE_PAUSED": "ПРИЗУПИНЕНО", + "DEBUG_STATE_PLAYING": "ВІДТВОРЮЄТЬСЯ", + "DEBUG_SECTION_PROPERTIES": "Властивості", + "DEBUG_SECTION_DIMENSIONS": "Розміри", + "DEBUG_DIMENSIONS_HINT": "0×0 = відеоелемент прихований або ще не завантажений", + "DEBUG_SECTION_MEDIA_ERROR": "Помилка медіа", + "DEBUG_SECTION_DETECTION": "Виявлення", + "DEBUG_SECTION_IDENTIFICATION": "Ідентифікація", + "DEBUG_SECTION_MEDIA_SOURCE": "Джерело медіа", + "DEBUG_SECTION_DATA_ATTRIBUTES": "Атрибути даних", + "DEBUG_YES": "ТАК", + "DEBUG_NO": "НІ", + "DEBUG_YES_CHECKED": "ТАК (перевірено)", + "DEBUG_NA": "н/д" } diff --git a/extension/locales/zh.json b/extension/locales/zh.json index 92a6d70..3004688 100644 --- a/extension/locales/zh.json +++ b/extension/locales/zh.json @@ -134,7 +134,7 @@ "BTN_ONBOARDING_NEXT": "下一个", "BTN_ONBOARDING_NEXT_TOOLTIP": "进入下一步", "ONBOARDING_1_TITLE": "欢迎来到KoalaSync!", - "ONBOARDING_1_TEXT": "无论您身在何处,都能完美同步地一起观看视频。让我们快速浏览一下吧!", + "ONBOARDING_1_TEXT": "无论身在何处,都可以一起看视频。下面是快速介绍。", "ONBOARDING_2_TITLE": "1. 创建房间", "ONBOARDING_2_TEXT": "从这里开始。创建一个房间并与您的朋友分享邀请链接。", "ONBOARDING_3_TITLE": "2. 选择视频", @@ -204,7 +204,7 @@ "LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "超时——并非所有同行都加载了该剧集", "LOBBY_CANCEL_USER": "已被用户取消", "NOTIF_ERROR_TITLE": "KoalaSync 错误", - "FOOTER_SUPPORT": "Support KoalaSync", + "FOOTER_SUPPORT": "支持 KoalaSync", "FOOTER_REVIEW": "★ 评价我们", "FOOTER_SUPPORT_PROMPT": "喜欢 KoalaSync 吗?留下评论!", "LABEL_AUDIO_PROCESSING": "音频处理", @@ -274,5 +274,28 @@ "CHAT_MISSING_KEY": "聊天仅可通过当前邀请链接使用。", "CHAT_TOO_LONG": "最多500个字符。", "CHAT_SEND_FAILED": "无法发送消息。", - "CHAT_EMPTY": "暂无消息。聊天仅实时显示。" + "CHAT_EMPTY": "暂无消息。聊天仅实时显示。", + "LABEL_CHAT_NOTIFICATIONS": "聊天通知", + "LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "视频标签页未激活时,以系统通知显示新的房间聊天消息。", + "LABEL_EVENT_NOTIFICATIONS": "播放与房间通知", + "LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "有人加入、离开、播放或暂停时显示系统通知。", + "DEBUG_SECTION_VIDEO_DETECTION": "视频检测", + "DEBUG_VIDEO_NOT_FOUND": "未找到视频元素", + "DEBUG_SECTION_MEDIA_SESSION": "Media Session API", + "DEBUG_VIDEO_HINT": "当前是否正在播放视频?媒体加载后,KoalaSync 才能检测视频元素。", + "DEBUG_SECTION_PLAYBACK": "播放", + "DEBUG_STATE_PAUSED": "已暂停", + "DEBUG_STATE_PLAYING": "播放中", + "DEBUG_SECTION_PROPERTIES": "属性", + "DEBUG_SECTION_DIMENSIONS": "尺寸", + "DEBUG_DIMENSIONS_HINT": "0×0 = 视频元素已隐藏或尚未加载", + "DEBUG_SECTION_MEDIA_ERROR": "媒体错误", + "DEBUG_SECTION_DETECTION": "检测", + "DEBUG_SECTION_IDENTIFICATION": "标识", + "DEBUG_SECTION_MEDIA_SOURCE": "媒体来源", + "DEBUG_SECTION_DATA_ATTRIBUTES": "数据属性", + "DEBUG_YES": "是", + "DEBUG_NO": "否", + "DEBUG_YES_CHECKED": "是(已检查)", + "DEBUG_NA": "无" } diff --git a/extension/popup.html b/extension/popup.html index 0ed6cc2..afaea23 100644 --- a/extension/popup.html +++ b/extension/popup.html @@ -1632,6 +1632,13 @@ +