Compare commits

...

347 Commits

Author SHA1 Message Date
KoalaDev 788a3331d3 Merge pull request #14 from Shik3i/feature/host-control-mode
Prepare v2.5.0 host control release
2026-06-29 12:55:57 +02:00
Timo 2d9b7bc099 Ignore Claude local files 2026-06-29 12:52:22 +02:00
Timo 717d37c693 Prepare v2.5.0 website release notes 2026-06-29 12:50:49 +02:00
KoalaDev 1f48e9df47 fix(popup): polish custom-server, host-control card & co-host UX
- Manual Connect: split the server picker into its own inset panel,
  visually separated from the Room ID / Password fields (they were
  welded together in one flat list).
- Custom server URL: stop auto-prepending ws:// — default to wss://
  for real hosts, ws:// only for localhost/127.0.0.1 (matches what
  the background already enforces; the displayed URL no longer lies).
- Host Control card: replace the raw native checkbox with the proper
  .toggle-switch slider used in Options; move the toggle row from a
  <label> to a <div> to avoid an illegally nested label.
- Co-host discoverability: owner-only hint in the Host Control card
  pointing to the participant list (new NOTICE_COHOST_HINT across all
  15 locales). Shown only when actionable: owner + co-host-capable
  relay + host-only active.
- Peer row: group right-side badges/actions into one container so they
  no longer scatter via space-between or collide with long names;
  restyle the give/revoke button (padding, weight, hover fill, tooltip).
- Gate promote/demote (and role badges) on host-only mode being ON,
  in addition to the existing owner-privilege check — roles are moot
  in 'everyone' mode, so no badge/button noise there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 02:43:28 +02:00
KoalaDev adab684ed0 perf+fix: debounce seq persistence + role changes, null-clear stale peer state
- server: SET_PEER_ROLE now uses the same per-room 500ms debounce as
  SET_CONTROL_MODE (M-4), preventing a buggy/malicious host from
  thrashing the room's UI with rapid promote/demote bursts.
- server: PEER_STATUS relay treats explicit null as 'clear' for
  tabTitle/mediaTitle/currentTime, so the tab-close heartbeat actually
  zeroes out stale state on other peers instead of being silently
  preserved by the clamp fallback.
- background: _persistLastSeq is now trailing-debounced (500ms),
  cutting storage.session IPC writes from one-per-relayed-event to
  one-per-quiet-window in active rooms.
- locales: add missing TOAST_ID_REGENERATED to all 14 non-English
  locales (was blocking locale coverage test on main).
- test: H-1 force-sync demote test now waits out the debounce window
  to reflect real-world UI timing (a host cannot promote, run a
  force-sync, and demote inside 500ms).
2026-06-29 01:22:41 +02:00
KoalaDev e23f9ab226 fix: wire up Regenerate Peer ID button + remove arbitrary MAX_CONTROLLERS cap
Two pre-existing issues surfaced during audit follow-up:

1. popup.js bound elements.regenId but never attached a click handler —
   the 'Regenerate Peer ID' button has been dead UI since it was added
   (verified across full git history). Wired it up to send REGENERATE_ID;
   the background handler already did the right thing (rotate peerId,
   persist, force reconnect). The button is functional (regen ID on
   duplicate-identity errors), not privacy-theater — username is already
   user-changeable in settings, so it stays out of scope.

2. MAX_CONTROLLERS=10 was arbitrary. Payload math (25 controllers ×
   16-char IDs ≈ 500 bytes) is well under the 4KB maxHttpBufferSize,
   controllers.has() is O(1) on a Set, and 'just use everyone mode' is
   not a real answer when a host wants 12 controllers + 3 guests in a
   15-person room. Removed the cap entirely; the only real upper bound
   is MAX_PEERS_PER_ROOM (25).

Added TOAST_ID_REGENERATED to en.json — the i18n fallback merge
(Object.assign({}, enDict, targetDict)) covers the other 14 locales
automatically with English until they're translated.
2026-06-28 14:03:59 +02:00
KoalaDev 77203fe8fd fix(extension): drop unused roomPassword field from GET_STATUS response
Defense-in-depth: the field was always undefined in practice (no code
path ever sets currentRoom.password — verified across the full git
history), and no GET_STATUS caller in the codebase reads it. But if a
future change ever populated currentRoom.password, any caller of
GET_STATUS would receive it in cleartext.

Threat surface today is narrow (externally_connectable is not set, no
content script sends GET_STATUS, only the popup uses it), but the field
has no reason to exist — removing it eliminates the future-leak vector
for free.

Backward compatible: no caller reads this field, so removing it is a no-op
for every existing code path.
2026-06-28 08:35:05 +02:00
KoalaDev 5379778a20 fix(extension): widen peerId generation to 16 hex chars (64-bit)
8 hex chars (32-bit) gives ~7% collision probability at the relay's
25k-peer capacity and ~1% at 10k peers. A same-room collision silently
triggers the dedup path and kicks the older session with a confusing
'Another session with this ID joined' error.

16 hex chars (64-bit) drops collision probability to ~1e-10 even at one
million peers. The server's JOIN_ROOM sanitizer already clamps peerId to
16 chars, so no server change is needed. Fully backward compatible:

- Existing clients keep their persisted 8-char peerId (only newly
  generated IDs change).
- Mixed 8-char + 16-char ID rooms work — both fit the server's 16-char
  clamp.
- No protocol change on the wire.

Birthday-paradox reference (computed, not estimated):
  32-bit @ 10k peers:  1.16%
  32-bit @ 25k peers:  7.02%
  64-bit @ 25k peers:  ~0.017e-9%
2026-06-28 08:29:59 +02:00
KoalaDev 9fc58dca4b docs(assets): add ready-to-paste marketing copy kit (1-sentence, 3-sentence, full)
Three drop-in lengths for product listings, launch pages, and directory
submissions where the same pitch gets re-typed repeatedly. Bonus taglines
for hero headlines and social bios. All claims verified against the
codebase and docs/TESTED_SERVICES.md.
2026-06-28 06:02:55 +02:00
KoalaDev 17ac5c0a6f fix(host-control-mode): adversarial audit — force-sync stall, desync/lobby, gate parity, BC tests
Audit fixes (each verified against the actual code path):

H-1 (server): track force-sync initiator on PREPARE; let the demoted
initiator's EXECUTE through the host-only gate so mid-sync demotion no
longer strands the whole room paused. Clear on EXECUTE/peer-leave.

M-1 (background): episode-lobby gate now uses !amController() for parity
with CONTENT_EVENT and server gates — co-hosts can drive the room and
initiate lobbies, not just the owner.

M-2/M-3 (popup/content/background): forceSyncReset respects hcmGuestLocked;
desynced guest skips EPISODE_LOBBY so they don't get frozen in pause after
lobby completion, and checkEpisodeLobbyCompletion excludes desynced peers
from the required count so they don't block the lobby.

M-4 (background): getHostSyncTarget clamps extrapolation to 2x heartbeat
interval so a stale host heartbeat can't snap the guest tens of seconds
past the host's real position.

L-1..L-4 (server/content/background/popup): clarify dedup comment re:
network-blip window, enforce desync invariant on SW-restore, add
forceSyncBtn guest-locked backstop, refresh badge text in place.

Backward compatibility (verified by BC-1..BC-4 regression tests):
- Old client ↔ new server: server adds fields only, never requires; old
  heartbeats stripped of desynced; host-only enforced server-side even
  when the client has no awareness.
- New client ↔ old server: empty capabilities → host-control UI hidden,
  all gates default to everyone, behavior byte-identical to pre-HCM.
- Mixed rooms: every pre-HCM event type relays cleanly in both directions.
2026-06-28 05:54:50 +02:00
KoalaDev 1ec2396c41 feat(co-host): popup UI — promote/demote, controller badges, controller-aware lock
- updateHostControlUI takes a state object now; role badge shows Host / Controller
  / Guest; remote-control lock uses amController (a co-host can drive, so their
  buttons aren't locked).
- Peer list: Host/Controller badges and, for the owner, a per-peer Give control /
  Revoke button (SET_PEER_ROLE). Shown only when the feature is active and the room
  is host-only or the viewer is the owner — no role noise for guests in normal rooms.
  Hidden entirely against a relay without the co-host capability.
- i18n: BADGE_CONTROLLER, BTN_GIVE_CONTROL, BTN_REVOKE_CONTROL across all 15 locales.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 03:45:00 +02:00
KoalaDev 7c65fe80cb feat(co-host): client gates use controller-set membership (background + content)
- background: track controllers[] from ROOM_DATA/CONTROL_MODE (restore + reset on
  teardown); add amController() (owner or co-host). Both gates now key on controller
  membership (sender: !amController(); receiver: senderId not in controllers); desync
  invariant uses amController. Thread controllers/amController/coHostSupported through
  GET_STATUS/GET_CONTROL_MODE/CONTROL_MODE. New SET_PEER_ROLE message (owner→server).
- content: gate on amController instead of amHost (a controller is not a gated guest);
  reconcile/HOST_BLOCKED updated accordingly. Drop now-unused hcmAmHost.

Single-host behavior unchanged (owner is always the sole controller until they promote).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 03:41:23 +02:00
KoalaDev a326087ca5 feat(co-host): server-side multi-controller (owner promotes peers to drive)
Generalizes host-only from a single host to a controller SET. The owner (creator)
can promote/demote peers; in host-only mode the relay gate allows any controller,
not just the owner. Single-host is now the special case controllers={owner}, so
existing behavior/tests are unchanged.

- shared: CAPABILITIES.CO_HOST enabled; new SET_PEER_ROLE event; CONTROL_MODE
  payload carries controllers.
- server: room.controllers Set (always incl. owner), advertised in ROOM_DATA;
  gate checks controllers.has(peerId); SET_PEER_ROLE handler (owner-only, target
  must be present, MAX_CONTROLLERS=10 cap, re-sync on reject); controlModePayload
  helper for consistent emits; owner-leave resets controllers + falls back to
  everyone, co-host-leave broadcasts the updated list (dedup/reconnect guarded).
- WS test: promote→drive, plain guest still gated, non-owner can't promote,
  demote→re-gated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 03:36:22 +02:00
KoalaDev de518ac205 docs+test: move KNOWN_LIMITATIONS to docs/, add everyone-mode regression test, co-host plan
- Move KNOWN_LIMITATIONS.md → docs/KNOWN_LIMITATIONS.md (+ fix SECURITY.md link).
- WS test: in default 'everyone' mode a non-host guest can still drive
  play/pause/seek/force-sync/episode-lobby — proves host-control OFF == unchanged
  behavior.
- docs/host-control-mode-COHOST-PLAN.md: plan for multi-controller (owner grants
  drive rights to several co-hosts), built on the capabilities hook; covers the
  gate generalization, roles, backwards-compat, edge cases, and a separate
  large-room (510-peer) scaling track.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 03:16:28 +02:00
KoalaDev ce3cf0ef89 docs: add KNOWN_LIMITATIONS.md with threat model + NOFIX entries
Records the accepted, out-of-threat-model decisions that reviewers/agents keep
re-reporting as bugs: unauthenticated peerId (host impersonation), non-constant-
time room-password compare, public server token, enumerable room IDs, and
pause-revert-not-prevent. Spells out the threat model (ephemeral, account-less
rooms of invited participants watching a video) so the "NOFIX" rationale is clear,
and distinguishes these from genuinely-deferred items (EC-10 host grace, classifier
tuning). Cross-referenced from SECURITY.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 02:53:11 +02:00
KoalaDev 9626948f29 security(server): warn at startup when SERVER_SALT is unset
Room-password hashing falls back to a built-in salt that is public in the repo
when SERVER_SALT is not configured. Low risk (the hash never leaves the server),
but operators should set a unique salt — warn at startup, mirroring the existing
ADMIN_METRICS_TOKEN warning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 02:37:37 +02:00
KoalaDev dfa0a3d03e fix(host-control-mode): audit findings — host dedup demotion, episode-lobby freeze, +cleanups
Findings recovered from the multi-agent audit (verification phase was cut off by a
usage limit; verified inline against the code):

HIGH:
- Host was demoted on peerId dedup (fast reconnect / second tab): the dedup path
  removes the old socket and the kicked socket's 'disconnect' both ran the
  host-leave fallback before the same peerId re-joined → room silently unlocked on
  every host network blip. Now skip the fallback while a join for that peerId is in
  flight (peerJoinLocks). Regression test added. (A long real disconnect still
  falls back — that's the deferred host-grace EC-10.)
- Episode auto-advance froze a gated guest: in host-only the guest's EPISODE_LOBBY
  is dropped server-side, but the guest still self-paused (PAUSE_FOR_LOBBY) waiting
  for readies that never came → 60s freeze. A host-only guest now skips creating a
  lobby (the host drives episode sync).

LOW / cleanup:
- GET_HCM_STRINGS could return a raw key name ("HCM_DIALOG_TITLE") if the locale
  dictionary failed to load (getMessage returns the key on miss) → omit it so
  content keeps its English fallback.
- hcmReset now also clears the snap-back cooldown + buffering grace, so a stale
  cooldown can't swallow the first snap-back after a room/host change.
- Popup play/pause labels reset on lock too (not just unlock), so a button can't
  freeze on "Playing…" when host-only activates mid-click.
- Fix a stale comment (heartbeat now carries 'desynced').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 02:25:11 +02:00
KoalaDev 02c41776b8 fix(host-control-mode): re-query host pos when captured target is unusable
Audit follow-up on the snap-back paths. The immediate involuntary snap used the
captured target directly; if it was null/invalid (host position not known yet,
e.g. before the first host heartbeat) hcmSnapBackToHost no-ops and the guest is
left silently paused with no dialog and no retry. The deferred and "Stay in sync"
paths already re-query with retry — make the immediate path fall back to the same
when the captured target isn't usable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 06:21:27 +02:00
KoalaDev b32e2fa8cc feat(host-control-mode): buffer-aware deferred snap-back (#3)
Implements the snap-back robustly instead of deferring it to device testing —
since the set of video sites is unbounded, "measure which players misbehave, then
fix" doesn't scale; the snap-back must be safe on any player.

On an involuntary pause/seek, if the player isn't ready to play (readyState<3 or
seeking) we no longer snap immediately (which fights the buffer: seek → re-buffer
→ pause → … = stutter). Instead hcmDeferredSnapBack polls until the player can
play (8s cap) and then snaps ONCE to the host's re-queried current position. A
player can't play while buffering anyway, so waiting is also more correct, not
just safer. Ready players still snap immediately (no regression).

Guards: aborts if the user goes solo or is no longer a gated guest; single
pending poll (no stacking); cleared in hcmReset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 06:18:22 +02:00
KoalaDev 9963da2ebc feat(host-control-mode): explicit server capabilities for feature detection
Replace the implicit "hostPeerId present ⇒ feature supported" heuristic with an
explicit capabilities list the relay advertises in ROOM_DATA. Cleaner, self-
documenting, and the extensible hook for the planned co-host feature (owner
promotes guests to extra controllers) — a future 'co-host' capability + events
slot in without a protocol bump.

- shared: CAPABILITIES { HOST_CONTROL }.
- server: SERVER_CAPABILITIES advertised in ROOM_DATA.
- background: track serverCapabilities (empty against older relay), serverSupports(),
  thread hostControlSupported through GET_STATUS / GET_CONTROL_MODE / CONTROL_MODE.
- popup: gate the host-control card on the explicit capability instead of hostPeerId.

Backwards-compatible both ways: old relay omits the field → feature stays hidden
(no errors); old client ignores the field. WS test asserts ROOM_DATA advertises
the capability. Adds docs/host-control-mode-TESTING.md (beta-server setup, wss
caveat, two-new-clients note, verification checklist).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 04:48:45 +02:00
KoalaDev 9feafab617 ci: publish beta relay Docker image for branch testing
Adds a separate workflow that builds the server image and pushes it to GHCR under
non-production tags (beta, branch slug, sha-<commit>, and an optional custom tag
via manual dispatch) so the host-control branch can be deployed to a staging /
backup server and used as a custom server — without ever touching ':latest' that
the official relay tracks (flavor: latest=false).

Runs on push to feature/host-control-mode and on workflow_dispatch. Remove once
the feature is merged and released normally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 01:42:56 +02:00
KoalaDev d26b95985d fix(host-control-mode): only show host-control card when meaningful
The card was rendered in every room with a "Guest" badge by default — confusing
in normal rooms, and outright misleading against a relay that doesn't yet support
host control (older server omits hostPeerId, so amHost is always false and nobody
is ever recognized as host).

Now the card shows only when it's meaningful: the host always sees it (to toggle
host-only), a guest sees it only while host-only is active, and it's hidden in a
normal "everyone" room and whenever the server doesn't advertise hostPeerId
(feature unavailable). Thread hostPeerId through updateHostControlUI to detect
server support.

Note: the feature still requires the relay to run the host-control server code;
this just makes the client degrade cleanly instead of showing a stray "Guest".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 01:29:00 +02:00
KoalaDev bb030a9737 feat(host-control-mode): localize the in-page dialog & badge
The host-control desync dialog and the "watching on your own" badge were
hardcoded English — content.js has no i18n loader. Background does, so it now
resolves the strings (GET_HCM_STRINGS: loadLocale + getMessage) and content
fetches them on init with English fallbacks, re-rendering the badge if it was
already shown. The dialog body is now generic (dropped the paused/jumped verb)
to keep translation clean across languages.

Adds 6 keys (HCM_DIALOG_TITLE/BODY/STAY/SOLO, HCM_BADGE_SOLO/RESYNC) translated
across all 15 popup locales.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 16:26:31 +02:00
KoalaDev 95ff460856 fix(host-control-mode): hold desync invariant + stop Solo-badge flicker
Two issues surfaced by a holistic audit of the combined desync-persist +
reconcile-on-init changes:

1. Stale desync outside gated-guest state. hcmDesynced is persisted, but neither
   ROOM_DATA nor CONTROL_MODE cleared it when the local peer became host or the
   room switched to 'everyone'. Combined with reconcile-on-init, a desynced guest
   promoted to host (host-leave fallback) while content was reloading could
   re-adopt desynced=true — self-labelling "Solo" to all peers and ignoring host
   commands in 'everyone' mode. Enforce the invariant centrally
   (hcmEnforceDesyncInvariant: desynced ⟹ host-only && !host) on every role/mode
   change, and guard the content reconcile to only adopt when actually a gated guest.

2. Solo-badge flicker. The host-side PEER_STATUS handler set peer.desynced
   unconditionally, but the background-driven keepAlive heartbeat omits the field —
   so every ~30s it clobbered desynced to false, flickering the badge. Only update
   when present (matching the sibling fields), and include desynced in the
   background heartbeat so both heartbeat sources are consistent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 16:06:04 +02:00
KoalaDev 96e2207cf3 fix(host-control-mode): retry host-sync on dialog "Stay in sync" too
The Resync badge already retried REQUEST_HOST_SYNC when the host's state wasn't
known yet, but the dialog's "Stay in sync" path did a single no-retry request —
so a stay tap right after the host paused (before a heartbeat propagated) could
silently leave the guest paused/out of sync. Extracted the retry loop into a
shared hcmRequestHostSyncWithRetry() and used it in both paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 15:52:49 +02:00
KoalaDev 2928393b4c fix(host-control-mode): reconcile desync state on content reinjection
Fixes a split-brain where a guest who chose "watch on my own" and then reloaded
the video page would appear stuck as "Solo" to the host while actually being
back in sync.

Root cause: background persists hcmDesynced (survives SW restart) and attaches
it to content-driven heartbeats, but a freshly injected content script started
with hcmDesynced=false and GET_CONTROL_MODE never returned the desync state — so
content re-applied host commands (synced) while background kept relaying
desynced=true to the host.

Fix: GET_CONTROL_MODE now also returns desynced; content adopts it on init
(re-shows the badge) and also adopts hostPeerId so later host-change resets work.
Desync now survives a page reload consistently on both sides, as intended.

Verified the full path: background.js heartbeat (1555 omits / 2018 attaches),
reset paths, CONTENT_BOOT and keepAlive-disconnect (neither reset it), content
sendHeartbeat on inject.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 15:45:33 +02:00
KoalaDev 5863991bfb fix(host-control-mode): remove redundant CONTENT_EVENT guard, persist desync in storage, block stale tab HCM_DESYNC_STATE
Original code at L1957 already validates sender.tab tabId against
currentTabId before calling processEvent() — added guard was
duplicate. HCM_DESYNC_STATE had no such guard; added with same
sender.tab && currentTabId pattern (popup-safe via undefined
short-circuit).

hcmDesynced now persisted in session storage:
- Restored in ensureState()
- Set on HCM_DESYNC_STATE change
- Reset to false in all 3 teardown storage.sets
Survives SW restarts; cleared on leave/switch/idle-room
2026-06-26 13:23:24 +02:00
KoalaDev 1eca3fa5bb fix(host-control-mode): guard against stale tab events, persist desync across SW restart
- CONTENT_EVENT now rejected from non-selected tabs (popup exempted via sender.tab check)
- HCM_DESYNC_STATE same guard
- hcmDesynced persisted in session storage: restored on SW restart, cleared on teardown
2026-06-26 13:18:09 +02:00
KoalaDev 58af258fd3 fix(host-control-mode): harden gates, close UX races, debounce toggles, surface desync state
Bis auf peerId-Auth (akzeptiert) alle Audit-Findings behoben:
- Snap-back spielt nicht mehr blind ab, wenn Host-Status unbekannt
- Teardown-Pfade broadcasten jetzt CONTROL_MODE → kein verwaistes Badge/Dialog
- Dialog-Timer bei Ersetzung sauber gecleart
- Server re-synchronisiert Sender bei nicht-Host-Reject
- Popup-Toggle revertiert bei Server-Ablehnung
- Desync-Zweig sendet keine Phantom-ACKs mehr an Host
- Toggle-Debouncing serverseitig (500ms pro Raum)
- Button-Text auf Unlock zurückgesetzt
- Live-Erkennung defensiver, Resync-Retry bei fehlendem Target, Badge-Retry

Neu: Host sieht 'Solo'-Badge für desyncte Gäste via PEER_STATUS-Heartbeat
(16 Sprachen, neuer i18n-Key BADGE_DESYNCED/TOOLTIP_PEER_DESYNCED)

Tests: H-5-Reject-Unicast, M-4-Debounce, Desync-Heartbeat-Relay
2026-06-26 13:09:34 +02:00
KoalaDev 69f8f4cfd7 fix(extension): trust HOST_BLOCKED as authoritative (join race, EC-5)
hcmHandleBlocked no longer re-checks local control mode before acting. Background
only sends HOST_BLOCKED to a gated guest, so the message itself is authoritative —
adopt host-only/guest role from it. Fixes the join race where a HOST_BLOCKED
arriving before the CONTROL_MODE broadcast was silently ignored.

Also corrects the EC-4 note: "let catch-up re-sync" is invalid (sync is
event-driven, no continuous catch-up); the right fix is a buffer-aware deferred
snap-back.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 10:01:04 +02:00
KoalaDev af9cf34f0b fix(extension): pre-test self-audit fixes for host control mode
- popup: lock remote-control buttons (Play/Pause/SYNC) for guests in host-only
  mode so clicks don't silently get gated and leave the button stuck; backstop
  guards in the handlers and the 2.5s safety reset respects the lock.
- content: rebuild the desync dialog + resync badge with the DOM API inside a
  Shadow DOM. innerHTML inline style="" attributes are stripped by strict
  style-src CSP (Netflix/YouTube/Disney+); CSSOM .style is CSP-safe and the
  shadow root isolates from page CSS.
- content: detect live-DVR in hcmIsLive() via a sliding seekable window
  (seekable.start(0) > 1), not just duration === Infinity (EC-15).

Records remaining audit items (snap-back thrash, join race, dialog i18n) in the
edge-case log for the device-testing pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:54:45 +02:00
KoalaDev b1ea280f0b docs: mark host control mode implementation status
All five layers implemented; automated checks green. Records what still needs
real-device testing (EC matrix) and what was deferred by decision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 04:15:25 +02:00
KoalaDev 9006570bd1 feat(extension): host control toggle + role badge in popup (step 5)
Final wiring of host control mode — the user-facing UI:

- Active-room section gains a Host Control card: a Host/Guest role badge, a
  host-only toggle ("Only I can control playback") shown to the host, and a
  guest notice when the room is host-only.
- Toggle sends SET_CONTROL_MODE; UI refreshes from the server's CONTROL_MODE
  broadcast (with optimistic revert on failure). Card updates on GET_STATUS,
  CONTROL_MODE, and reconnect.
- i18n: 6 new keys (LABEL_HOST_CONTROL[_TOOLTIP], LABEL_HOST_ONLY_TOGGLE,
  NOTICE_HOST_CONTROLS, BADGE_HOST, BADGE_GUEST) translated across all 15
  popup locales.

Completes the feature end to end (server + background + content + popup).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 04:14:55 +02:00
KoalaDev f9dc9d4017 feat(extension): host control mode guest UX in content script (step 4)
Guest-side handling when a room is in host-only mode:

- Intent classifier (EC-9): tells a deliberate guest pause/seek from an
  involuntary one (buffering/seeking/tab-refocus/no recent gesture) via
  readyState, video.seeking, a 'waiting' buffering-grace window, the existing
  visibility grace, and own-tracked keydown/pointerdown gestures.
- HOST_BLOCKED handler: live streams are left alone (EC-15); involuntary events
  snap back silently; deliberate events show a Teleparty-style choice dialog
  (Stay in sync / Watch on my own), defaulting to stay after 8s (EC-18).
- Snap-back reuses tryMediaAction + suppress, with a cooldown to avoid
  pause/play fight loops (EC-4).
- Desync ("watch on my own") mode: persistent resync badge, ignores host
  commands while solo (still ACKs so force-sync doesn't stall), and auto-clears
  on episode change (EC-12). Resync pulls the host's extrapolated position.
- background: GET_CONTROL_MODE (init fetch) + REQUEST_HOST_SYNC (resync target).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 04:10:15 +02:00
KoalaDev 7b37d783b5 feat(extension): host control mode wiring in background (step 3)
Background service worker now understands room control mode (no UI yet):

- Track controlMode + hostPeerId + amHost(); adopt from ROOM_DATA, update on
  CONTROL_MODE, restore from session storage, reset on leave/room-switch.
- Receiver-side gate: ignore room-moving events from non-host peers in host-only
  mode (backstop for old/buggy/modified clients past the server gate).
- Sender-side gate: a guest's play/pause/seek/force-sync is not broadcast;
  instead a HOST_BLOCKED message is sent to the content tab with an extrapolated
  host sync target (hook for step 4's snap-back + desync dialog).
- broadcastControlMode() notifies popup + content tab; GET_STATUS exposes
  controlMode/hostPeerId/amHost; new SET_CONTROL_MODE message lets the host toggle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 04:04:54 +02:00
KoalaDev 9f4be58172 feat(server): host control mode — room state, gate, toggle & host fallback
Step 2 of host control mode (server-side, fully testable without UI):

- Room now tracks hostPeerId (= first joiner) and controlMode ('everyone' default).
- ROOM_DATA carries hostPeerId + controlMode so clients know their role on join.
- SET_CONTROL_MODE handler: host-only authority check, broadcasts CONTROL_MODE.
- Relay gate: in host-only mode, drop room-moving events (play/pause/seek/
  force-sync/episode-lobby) from non-host guests — robust chokepoint that kills
  spam regardless of client. Heartbeats/ACKs/episode-ready still pass.
- removePeerFromRoom: if the host leaves, fall back to 'everyone' and reassign
  host to the earliest remaining peer (v1: immediate, no grace period).

Extends the WS integration test suite to cover all of the above.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:30:57 +02:00
KoalaDev a968f708b3 feat(protocol): add host control mode events & constants
Step 1 of host control mode. Adds the protocol surface only — no behavior yet:
- EVENTS.SET_CONTROL_MODE (client->server: host sets mode)
- EVENTS.CONTROL_MODE   (server->client: mode changed)
- CONTROL_MODES { EVERYONE, HOST_ONLY }

Purely additive; old clients ignore the new events. Propagated to
extension/shared via build-extension. Also records the audited design
decisions in the edge-case log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:27:19 +02:00
KoalaDev 2995afe970 docs: add host control mode branch overview, plan & edge-case log
Planning docs for the optional per-room Host Control Mode (Teleparty-style
host-only playback control) requested via GitHub issue.

- host-control-mode-EDGECASES.md: canonical branch entry-point — goals, scope,
  trust model, architecture summary, 21 edge cases, test matrix, open decisions.
- host-control-mode-plan.md: layered implementation plan with concrete code hooks
  (server room state, three-layer gate, snap-back, guest desync flow, UI/i18n).

Temporary working docs for this branch; to be folded in or removed before merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:22:26 +02:00
KoalaDev 68027a21d5 fix: use absolute /version.json path to work on all page depths
The old logic only handled en='version.json' and non-en='../version.json',
which broke on /alternatives/ and /de/alternatives/ pages.
Absolute path works for root, /de/, /alternatives/, /de/alternatives/ etc.
2026-06-25 04:29:24 +02:00
KoalaDev 4aa6f6d7a6 fix: replace deprecated apple-mobile-web-app-capable with mobile-web-app-capable
Chrome 79+ shows deprecation warning for the apple- prefix.
Main template already uses the correct name; alternatives templates
were missed.
2026-06-25 04:28:41 +02:00
KoalaDev 7602bfdb21 fix: preserve language in cross-links, add og:locale + Organization schema to alternatives
- Fix cross-links on teleparty↔screen-sharing pages: use
  {{LANG_PREFIX}} so German page links stay German, etc.
- Add og:locale meta tag with correct locale code per language
  (e.g. en_US, de_DE, fr_FR, ja_JP, zh_CN, uk_UA)
- Add Organization schema.org JSON-LD block alongside
  existing BreadcrumbList on all 3 alternatives templates
2026-06-25 04:21:02 +02:00
KoalaDev 3ccb967a45 fix(l10n): add missing zh/uk language dropdown options to alternatives pages
- Add 中文 (Chinese) and Українська (Ukrainian) to the language
  selector in index, teleparty, and screen-sharing templates
- Previously only 13 of 15 languages were listed in the dropdown
2026-06-25 04:08:06 +02:00
KoalaDev fe970de39a fix(seo): add missing sitemap locale entries, swap KoalaSearching→RemoteProf on alternatives index
- Add 39 missing language-specific sitemap entries for fr/es/it/.../uk
  alternatives pages (index, teleparty, screen-sharing)
- Replace KoalaSearching image with RemoteProf (professor koala)
  on the Guides & Comparisons overview page
- Update og:image, twitter:image, and img src accordingly
2026-06-25 03:50:00 +02:00
Timo e795339924 Add ALT_* translations for ja, ko, zh + rebuild assets
Replaced English fallback with proper Japanese, Korean, and Chinese
translations for all 27 ALT_* website locale keys. All 15 locale
files now fully translated; npm run verify and build pass cleanly.
2026-06-25 03:00:02 +02:00
KoalaDev 06b5ffbd0b feat(alternatives): add Teleparty & Screen Sharing comparison pages with i18n
- Add article-style comparison pages for Teleparty and Screen Sharing
- Replace comparison tables with honest prose sections
- Add 15-language hreflang alternates and sitemap entries
- Add English locale fallback for untranslated languages
- Replace placeholder Draft copy with real English and German text
- Remove old Draft locale keys from all 13 non-EN/DE locales
- Rebuild outputs with zero TODO/Draft/noindex issues
2026-06-25 00:12:04 +02:00
GitHub Action 24cac52674 chore(release): update versions to v2.4.6 [skip ci] 2026-06-23 16:36:23 +00:00
KoalaDev 6276ee134c fix(ui): Correct false idle state on connection errors
Bump version to 2.4.6
2026-06-23 18:36:05 +02:00
GitHub Action d847389eef chore(release): update versions to v2.4.5 [skip ci] 2026-06-23 15:40:16 +00:00
KoalaDev aa84b63c77 docs: add v2.4.5 entry to CHANGELOG.md
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:38:36 +02:00
KoalaDev bd60a14754 feat(extension): send nothing to the server while alone in a room
When no other peer is present, heartbeats, force-sync, and episode auto-sync
are now fully suppressed (previously the keepAlive heartbeat, force-sync, and
episode lobby were still broadcast to an empty room):
- keepAlive heartbeat gated on a live peer count
- force-sync is a no-op when solo (no pause-then-timeout freeze, no traffic)
- episode auto-sync skips the lobby entirely and just plays the next episode

The solo state is recomputed live from the current peer list on every event
(never cached), so the instant another peer joins syncing resumes. On that
solo->not-solo transition the background also requests an immediate heartbeat
from the content script so the newcomer sees the current position without
waiting for the next interval.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:38:36 +02:00
KoalaDev a1bdcf4325 fix(extension): stop storing room/settings in chrome.storage.sync
Room ID, password, and username were resurrected from synced storage on a
fresh install (sync survives an uninstall in the user's Google account), which
made the extension silently auto-connect to a dead room and appear permanently
connected. getSettings() and all settings reads (popup, content, audio-options,
applyAudioSettingsToTab) are now local-only, and legacy keys are actively
purged from sync on install/update/startup. Only onboardingComplete and
dismissedHints remain in sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:38:36 +02:00
KoalaDev b846803062 Potential fix for code scanning alert no. 14: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-06-23 00:56:28 +02:00
KoalaDev 4bc7ad365d docs: add v2.4.4 entry to CHANGELOG.md
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:49:42 +02:00
GitHub Action 68b2205b0d chore(release): update versions to v2.4.4 [skip ci] 2026-06-22 22:46:03 +00:00
KoalaDev b450845522 fix(extension): harden reconnect, ping, and offline-queue flush
Address client-side rate-limit edge cases that could trip the server's
per-socket event budget or per-IP connection limit:

- Pace the offline event-queue flush after (re)connect in batches
  (10 per 3s) instead of one synchronous burst, so a reconnect after a
  long outage no longer dumps the whole backlog and gets kicked.
- Wrap socket.send() in try/catch and re-queue on failure (race with a
  server-side disconnect) instead of losing the event.
- Ping liveness: tolerate one missed PONG; only force a reconnect after
  2 consecutive misses (~20s) instead of a single hard 5s timeout.
- Reconnect backoff: tune to ~8 attempts/60s (under the connection limit)
  and add +/-20% jitter to de-synchronize reconnect herds.

All magic numbers extracted into named constants.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:45:29 +02:00
KoalaDev 27023ea58e feat(server): raise event rate limit to 50 and name rate-limit constants
Raise the per-socket event budget from 30 to 50 per 10s to give legitimate
bursts (episode joins, force-sync, ACK flurries) more headroom. Extract the
previously inline connection/event/health/admin windows and limits into named
constants (CONNECTION_RATE_LIMIT, EVENT_RATE_LIMIT, *_WINDOW_MS). Tests now
derive their thresholds from the exported constants instead of hardcoding them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:45:19 +02:00
KoalaDev 1a7d23ce93 ci: add PR/push verification workflow and harden release
- Add .github/workflows/ci.yml running `npm run verify` (lint, tests,
  audits, builds) on every push to main and pull request, so regressions
  can't reach main or a release tag unchecked.
- release.yml: use `npm ci` instead of `npm install` for reproducible builds.
- package.json: add `test` script aliasing the verify suite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:45:10 +02:00
KoalaDev d2c8ca1c1e Merge PR #11: extract tab management logic into independent module
3-way merge of Kaia-Alenia's tab-manager refactor (PR #11) onto current
main (v2.4.3). Only the tab-related service-worker listeners move out of
background.js into extension/modules/tab-manager.js via initTabManager({...})
dependency injection; all other recent main changes are preserved untouched.
Behavior-preserving; listeners still register synchronously at top level.

The unrelated website/assets/NewLogoIcon_64.webp change from the PR was
excluded to keep main clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 02:06:38 +02:00
Kaia-Alenia 1c404a7f11 refactor(extension): extract tab management logic into independent module 2026-06-19 14:11:47 -06:00
Timo e2bba04efd docs: update README language count to 15 (add zh, uk)
The localization section still listed 13 languages; Ukrainian (uk) and
Chinese Simplified (zh) were added in v2.4.3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 16:49:34 +02:00
GitHub Action c7b29c277c chore(release): update versions to v2.4.3 [skip ci] 2026-06-19 14:39:06 +00:00
Timo e06965b95c docs: add v2.4.3 entry to CHANGELOG.md
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 16:38:49 +02:00
Timo 23d43741ce chore: replace Ko-Fi with Support links, drop uninstall token
Switch the Ko-Fi support links to https://support.koalastuff.net across the
README badge, the extension footer (popup.html/template.html, label
"Support KoalaSync") and remove the now-unused KOFI_URL constant. Stop
generating/sending the anonymous uninstall token: initUninstallURL() now
registers the feedback URL without the `t` parameter or storage write.
CHANGELOG updated to match.

Also guard the client CMD_ACK path: only ACK a command sender that is still
a known peer in the room, so we don't emit ACKs to peers that already left
(which the server now drops as absent-peer ACKs anyway).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 16:26:54 +02:00
Timo 027d2dbde5 fix(sync): coalesce play/pause bursts and quiet absent-peer ACK logs
Media players fire bursts of native play/pause events (source swaps, ABR,
ads, teardown). Each was relayed as a distinct command, spamming peers and
tripping the relay's event rate limit. Add leading+trailing coalescing in
the content script: emit the first event instantly (zero added latency) and,
on a 150ms window, collapse a burst to its final state. Echo-suppression and
seek-flush stay synchronous on event arrival; the trailing send is never
deduped against the leading one (a remote command may change shared state
mid-window — re-sending is the safe, idempotent choice).

Server: split the EVENT_ACK handler's logging so only a genuine different-room
ACK is logged as [SECURITY]; an ACK to a peer that already left is logged
quietly as [ACKDROP] (verbose-only), so real signals are no longer drowned out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 16:26:31 +02:00
Timo a0c16df664 feat(i18n): add Ukrainian & Chinese, fix zh/uk translation quality
Add two new languages (uk, zh / zh_CN) across the extension and website:
register them in SUPPORTED_LANGUAGES, the Chrome _locales map, the website
build/lang-init/app routing, hreflang/og/schema tags and language selectors.

Fix systematic machine-translation word-sense errors in the new zh/uk
locales (e.g. zh "Status"=地位→状态, "Leave Room"=留出空间→离开房间,
"Play"=玩→播放, "Seek"=寻找→跳转, audio Attack/Release/Knee; uk "Play"=Грати
→Відтворити, "Clear"=ЯСНО→Очистити, "Open"=ВІДЧИНЕНО→Відкрити, peers
"Однолітки"→"Учасники"), translate remaining English leftovers (Room ID,
Custom) and normalise terminology. All 15 locales pass test-locales.cjs.

Note: popup.html and website/template.html also carry the language-selector
additions here; their Ko-Fi→Support label text is part of the branding change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 16:26:20 +02:00
KoalaDev 8fd866f20a feat(website): add canonicals, hreflangs, meta descriptions, and localized schema descriptions 2026-06-19 12:10:08 +02:00
KoalaDev 8f72c238a2 docs: add v2.4.2 entry to CHANGELOG.md 2026-06-19 11:58:06 +02:00
GitHub Action 24ad7c3732 chore(release): update versions to v2.4.2 [skip ci] 2026-06-19 09:54:42 +00:00
KoalaDev 32fd677a31 feat: optimize extension uninstall URL registration with anonymous token and startup hardening 2026-06-19 11:54:07 +02:00
KoalaDev fb22700268 docs: revert completed milestones in ROADMAP.md 2026-06-19 11:39:50 +02:00
KoalaDev 59b675ce17 docs: audit and update script references, supported languages, and completed milestones 2026-06-19 11:35:49 +02:00
KoalaDev 8a9293280b chore: harden version substitution in release workflow and fix supply chain security version in README 2026-06-19 11:12:48 +02:00
KoalaDev 450f715874 ci(release): update actions to Node 24 versions & optimize workflow
- Bump all actions off the deprecated Node 20 runtime: checkout v4->v7,
  upload-artifact v4->v7, setup-node added@v6, action-gh-release v1->v3,
  docker build-push v5->v7, login v3->v4, metadata v5->v6, buildx v3->v4
  (verified latest majors via GitHub API; no breaking input/output changes)
- Add npm dependency caching via setup-node (cache: npm)
- Add GHA layer caching for the multi-arch Docker build (cache-from/to gha)
- Add concurrency group so a release run is never interrupted, only dedupes
  re-pushes of the same tag
- Tighten release-server permissions to contents: read (it never writes to the repo)

Version-injection and commit-back logic unchanged. First exercised on the next v* tag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 10:59:30 +02:00
GitHub Action b4c00f36a0 chore(release): update versions to v2.4.1 [skip ci] 2026-06-19 08:50:27 +00:00
KoalaDev 36f494fa55 feat(extension): one-click invite button in empty peer list + v2.4.1 changelog
- "No peers yet" state now shows a localized "Copy invite link" button (reuses BTN_COPY_INVITE, present in all 13 locales) so users can share without hunting for the field
- Add v2.4.1 changelog entry covering the onboarding tour improvements, empty-state action, and the mobile landing-page fixes from this session

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 10:50:04 +02:00
KoalaDev b5b3885d19 feat(extension): finish onboarding tour with closing step & cleaner welcome
- Add the previously-orphaned 5th onboarding step (ONBOARDING_5) so the tour ends on a closing card instead of stopping at the username field
- Make step 1 a centered welcome card instead of spotlighting the logo; guard querySelector for target-less steps
- Fix stale static placeholders in popup.html (Step 1 of 3 -> 5, progress 33% -> 20%)

Verified ONBOARDING_5/DONE exist across all 13 locales; rendered with the longest (German) copy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 10:40:40 +02:00
KoalaDev 373f2d127a fix(website): mobile comparison cards, hamburger nav, hero symmetry & reveal fallback
- Comparison table: stack into per-feature cards on phones (no more horizontal scroll); bring feature descriptions back
- Re-enable mobile hamburger menu (was hidden via display:none !important); keep logo/language/button uncrowded down to ~320px
- Fix hero asymmetry: extension mockup forced the grid column wider than the container; make it width:100%/max-width:320px and use minmax(0,1fr)
- Add IntersectionObserver guards + <noscript> fallback so reveal-animated content never stays hidden

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 10:27:07 +02:00
KoalaDev 3ebe80aab2 refactor: replace dynamic GitHub API badges with static shields.io badges 2026-06-17 06:39:42 +02:00
Timo e7b59d61ac docs: update all docs for lazy-connect, refactor, and .cjs rename
- ARCHITECTURE.md: startup -> lazy connect, reconnect when in room
- HOW_IT_WORKS.md: on-demand connection, heartbeat while connected
- PRIVACY.md: clarify alarms only during active sessions
- SYNC_GUIDE.md: build-extension.cjs + episode-utils injection
- StoreDescription.md: on-demand relay, no persistent connection
- README.md + extension/README.md: npm run build:extension
- extension/README.md: replace Dual Heartbeat with On-Demand Connection
- website/privacy.html: add lazy-connect privacy note
2026-06-16 14:17:19 +02:00
GitHub Action 71f7ac425a chore(release): update versions to v2.4.0 [skip ci] 2026-06-16 12:00:03 +00:00
Timo 394b9ba474 Merge branch 'refactor/split-monoliths' 2026-06-16 13:58:32 +02:00
Timo 526736b045 fix(ci): update website build path to .cjs after rename 2026-06-16 13:54:57 +02:00
Timo 86112057b4 test: add names generator unit tests (getAvatarForName + generateUsername)
Tests: 20 deterministic avatar assertions (exact match, case insensitive,
longest-match-wins, ZWJ sequences, fallback), 30 randomized username format
checks, and validation that every noun in the list has an emoji mapping.
2026-06-16 13:53:19 +02:00
Timo c56fcfd281 test(server): add 13 WebSocket integration tests covering all server features
Tests: connect, room create/join, password auth, play/pause/seek relay,
force_sync, event_ack, episode_lobby, peer leave, ping/pong, room list,
protocol version check, deduplication, health HTTP endpoint.
All 13 tests pass as part of npm run verify.
2026-06-16 13:48:21 +02:00
Timo 5c43fc6236 docs: add v2.4.0 changelog entries for lazy-connect and refactor 2026-06-16 13:23:53 +02:00
Timo 101761e984 chore: add type:module, fix npm audit, rename CJS files, document module structure
- package.json: add type: module to silence ESM warnings
- server: npm audit fix (0 vulnerabilities, ws vuln resolved)
- Rename 4 CJS files to .cjs: build-extension, test-content-video-finder,
  test-locales, website/build
- Update eslint.config.mjs for .cjs glob patterns
- extension/README.md + server/README.md: document module structure
2026-06-16 13:22:45 +02:00
Timo 3bc68a5713 test: add unit tests for rate-limiter and episode-utils
- test-rate-limiter.mjs: 12 test groups covering all rate-limit
  functions, Maps, cleanup, and double-start guard
- test-episode-utils.mjs: 30+ assertions covering SxxExx patterns,
  6 separator types (dash/dot/slash/colon/comma/space), German/English,
  edge cases (null/undefined/empty), and sameEpisode matching logic
- verify-release.mjs: added 2 test suites + 1 syntax check to pipeline
2026-06-16 13:15:25 +02:00
Timo 6c96dd6344 chore(refactor): remove orphaned comment, add double-start guard to rate-limiter 2026-06-16 13:10:37 +02:00
Timo d7bb8dc97c refactor(extension): extract episode-utils from background.js + content.js
Move extractEpisodeId() and sameEpisode() to shared episode-utils.js.
background.js imports via ES module. content.js (IIFE) receives injected
copy via build-extension.js marker replacement, eliminating duplication.
2026-06-16 12:48:11 +02:00
Timo d38a840114 refactor(server): extract rate-limiter module from index.js
Move 6 rate-limit functions, all rate-limit Maps, cleanup intervals,
and denial counter to server/rate-limiter.js. 149 lines extracted.
Index.js re-exports what tests and ops.js need.
npm run verify passes.
2026-06-16 12:44:43 +02:00
Timo e7d2d76ba3 fix(eslint): add missing browser globals history and location 2026-06-16 10:50:40 +02:00
Timo 5277cd0f21 fix(eslint): add missing browser globals history and location 2026-06-16 10:50:32 +02:00
Timo 4d7cbfe347 Merge branch 'feature/lazy-connect' 2026-06-16 10:47:46 +02:00
Timo 91aa76e842 fix: revert premature isConnecting reset + remove popup disconnected flicker
- background.js: remove isConnecting=false at line 608 (was set before
  WebSocket handshake completed, defeating the guard for the entire
  CONNECTING phase). isConnecting is already properly reset in onclose,
  onopen/40 handler, catch block, and socket guard path.
- background.js: restore isConnecting=false in forceDisconnect() so
  subsequent connect() calls can proceed after intentional disconnect.
- popup.js: remove hardcoded applyConnectionStatus('disconnected') that
  caused a guaranteed red flicker on every popup open. Status is now
  set only by the async GET_STATUS response.
2026-06-16 10:44:57 +02:00
Timo 5f0a189451 fix(popup): trigger CONNECT when popup opens with saved roomId
If user has a room configured but the background is not connected
(e.g. service worker was killed, startup race, or stale state), the
popup now sends CONNECT on open. Previously it only called GET_STATUS
and showed 'disconnected' with 0 logs — the user had no way to know
whether the extension was working.
2026-06-16 10:35:13 +02:00
KoalaDev 312d4f2cf7 chore: update sitemap lastmod dates, automate in release CI
- Update all sitemap <lastmod> entries from 2026-06-09 to 2026-06-16.
- Add sed step in release workflow to auto-update sitemap dates on every
  tagged release, so the sitemap never goes stale.
2026-06-16 06:11:13 +02:00
KoalaDev 884feb982d fix(website): smooth anchor scrolling, language-switch scroll position, lang select UX
- Replace JS scrollIntoView handler with CSS scroll-behavior:smooth for
  native anchor navigation. Fixes #hash-based scroll position surviving
  language switches since the hash stays in the URL.
- Auto-update URL hash via IntersectionObserver as user scrolls through
  sections, so manual scrolling also sets the anchor for language switch.
- Preserve window.location.hash across all language-switch navigations.
- Fix Chrome double-arrow on language select by limiting appearance:base-select
  to ::picker(select) only, keeping the custom SVG chevron.
- Make chevron absolutely positioned over the select with pointer-events:none
  so clicks anywhere on the container open the dropdown.
- Remove max-width constraint on language select so longer names fit.
- Add scroll-margin-top to section[id] and header[id] so anchored sections
  clear the fixed navbar (100px offset).
- Add header[id] to scroll-margin-top for keyboard skip-link accessibility.

Thanks to Taradal from Reddit
2026-06-16 06:05:49 +02:00
KoalaDev b2da17ab62 fix(lazy-connect): harden connection lifecycle against race conditions and edge cases
- Prevent concurrent connect() by removing isConnecting reset from
  forceDisconnect(); the guard in connect() now reliably blocks
  re-entry since no external caller can defeat it.
- Reset isConnecting at end of connect() so subsequent calls can
  proceed without waiting for the async '40' handler.
- Clear connectIntent and cancel reconnect timer on server ERROR
  when no currentRoom exists, preventing infinite reconnect loops
  after failed join attempts (e.g. wrong password via invite link).
- Move connectIntent assignment after !roomId guard in
  WEB_JOIN_REQUEST to avoid clobbering existing intent on invalid
  input.
- Broadcast JOIN_STATUS failure to popup and bridge tabs when
  WEB_JOIN_REQUEST receives an invalid room ID, so the website
  join page receives feedback instead of hanging forever.
- Clear joinBtnTimeout on CONNECTION_STATUS connected/disconnected
  to avoid stale timeout error messages in the popup.
2026-06-16 05:39:17 +02:00
KoalaDev b518685e2c merge: bring main (v2.3.2) changes into lazy-connect 2026-06-16 04:59:13 +02:00
GitHub Action b7a7a14a35 chore(release): update versions to v2.3.2 [skip ci] 2026-06-16 02:29:41 +00:00
KoalaDev 9ee22d985f docs: remove transient _locales fix from changelog (never released) 2026-06-16 04:28:30 +02:00
KoalaDev 2070e701de docs: add v2.3.2 changelog entry (translations, locale fixes, timing leak) 2026-06-16 04:26:07 +02:00
KoalaDev dd8eefe3f9 fix(security): actually prevent timing leak — eagerly evaluate timingSafeEqual
The previous fix had a subtle bug: JavaScript && short-circuits,
so crypto.timingSafeEqual() was skipped when buffer lengths differed
(sameLength was false). The dummy buffer was allocated but never
compared, leaving the original length-based timing leak intact.

Now timingSafeEqual is eagerly assigned to a const before the &&
guard, guaranteeing it runs in constant time on every auth attempt
regardless of whether the length guess was correct.
2026-06-16 04:23:20 +02:00
KoalaDev cc97e0d371 fix(security): prevent admin token length leak via timing side-channel
isAdminMetricsAuthorized() returned early when buffer lengths differed,
allowing an attacker to discover the token length by measuring response
time. Now always calls crypto.timingSafeEqual (using a zeroed dummy
buffer on length mismatch) so every auth attempt takes constant time
regardless of whether the length guess was correct.

Reported-by: Kaia-Alenia
2026-06-16 04:21:15 +02:00
KoalaDev 7571f1986d fix: restore _locales key names, remove orphan pt/ dir, fix locale typos
- _locales/es, pt_BR: renamed appDescription → appDesc (Chrome matches by key name)
- _locales/es, pt_BR: removed extra key contextMenu_syncVideo not in baseline
- _locales/pt_PT: merged Kaia's improved translation, deleted orphan pt/ duplicate
- locales/ko: fixed BTN_REFRESH (Refreschi → 새로고침) and STATUS_CONNECTING corrupted char
- locales/ja: fixed Korean character (의 → の) in LABEL_PUBLIC_ROOMS_TOOLTIP
- locales/nl: fixed typo cmmuniceren → communiceren
2026-06-16 04:19:53 +02:00
KoalaDev bb1a88c085 Merge pull request #8 from Kaia-Alenia/feat/complete-spanish-translation
i18n: complete and refined Spanish translation for extension and website
2026-06-16 03:43:54 +02:00
Kaia-Alenia 07863bdad6 docs: credit Alenia Studios for PT and PT-BR translations 2026-06-15 13:10:55 -06:00
Kaia-Alenia 5e6306432f i18n: complete and refined Portuguese (PT and BR) translations 2026-06-15 13:10:35 -06:00
Kaia-Alenia fa5dfc1cf1 docs: credit Alenia Studios for ES and IT translations 2026-06-15 11:19:52 -06:00
Kaia-Alenia b88b8bbe47 i18n: complete and refined Italian translation 2026-06-15 11:18:46 -06:00
Kaia-Alenia 8aab8ce2f5 docs: shorten Spanish translation context 2026-06-15 11:11:23 -06:00
Kaia-Alenia 95b4608236 docs: update Spanish translation status to 100% manually verified 2026-06-15 11:07:35 -06:00
Kaia-Alenia 6d42ee7d4c i18n: complete and refined Spanish translation for extension and website 2026-06-15 11:03:57 -06:00
Timo 3f8cf33dc9 fix: WEB_JOIN_REQUEST sendResponse leak + popup validation state cleanup
- WEB_JOIN_REQUEST: add missing sendResponse when already in target room
- popup join: reset isProcessingConnection + clear timeout on validation failures
2026-06-15 14:40:15 +02:00
Timo f12bb0a532 fix(extension): reset reconnectAttempts on leave to prevent stale reconnecting status
After LEAVE_ROOM or idle-leave, reconnectAttempts was not reset.
GET_STATUS would return 'reconnecting' instead of 'disconnected',
showing wrong status in popup and leaving Retry button visible.
2026-06-15 14:22:17 +02:00
Timo 1685b6a327 fix(popup): join timeout checks connection status instead of blind 15s timer
Lazy-connect introduces a new state where the user clicks 'Raum erstellen'
and the WebSocket takes 1-2s to establish (previously always pre-connected).
The old 15s blind timeout would re-enable the button while background was
still connecting, causing silent no-op clicks. Now polls GET_STATUS and
extends the window if still connecting.
2026-06-15 14:19:18 +02:00
Timo ec8f56a85d feat(extension): lazy-connect — only maintain WebSocket when actively in room
- Add connectIntent flag to gate all reconnect attempts
- Only auto-connect on startup if roomId exists in storage
- Close socket + stop reconnect after leaveRoom or idleLeave
- Preserve existing behavior 1:1 when actively in a room
- Guard force-sync state and peer list clearing during transient disconnects
- Guard WEB_JOIN_REQUEST against empty sanitized roomId
2026-06-15 14:08:29 +02:00
Timo 38dc923a7c Merge main (v2.3.1) into feature/lazy-connect 2026-06-15 13:30:57 +02:00
KoalaDev 4ca1ef22d2 Revise CHANGELOG for v2.3.1 updates
Updated changelog for version 2.3.1 with fixes and changes.
2026-06-15 13:17:10 +02:00
Timo 27e57862c0 docs: shorten v2.3.1 changelog entry 2026-06-15 13:15:01 +02:00
GitHub Action c391068706 chore(release): update versions to v2.3.1 [skip ci] 2026-06-15 11:14:23 +00:00
Timo d76e9195c4 fix(server): race condition on concurrent peer joins, crash-safe teardown, smart unhandled rejection handling
- Add per-peerId serialization lock (peerJoinLocks) to prevent concurrent dedupe races

- Wrap removePeerFromRoom calls in disconnect/leave/reaper with try/catch

- Replace immediate process.exit on unhandledRejection with rate-limited smart exit

- Optimize buildHealthPayload from 3-pass array ops to single for-of loop

- Reset rateLimitDenied counters in stopServerForTests

Release v2.3.1
2026-06-15 13:14:00 +02:00
KoalaDev 6652a06840 docs: correct rationale for english and german translations 2026-06-15 02:56:37 +02:00
KoalaDev b1a89cff41 docs: restructure TRANSLATION.md to prioritize extension and remove outdated details 2026-06-15 02:55:00 +02:00
KoalaDev e4a77a3ef4 docs: improve contribution and translation guidelines 2026-06-15 02:47:17 +02:00
KoalaDev 5adcce2074 Release v2.3.0 with new onboarding tour and sync tab
Updated CHANGELOG for v2.3.0 release with new features.
2026-06-14 05:31:27 +02:00
GitHub Action a1398ed0e4 chore(release): update versions to v2.3.0 [skip ci] 2026-06-14 03:17:46 +00:00
Timo ed80856803 fix: restore missing Create Room onboarding step and target h1 for welcome step 2026-06-14 05:10:57 +02:00
Timo 102031e0d2 style: swap restart onboarding and regenerate peer ID buttons in settings layout 2026-06-14 05:03:27 +02:00
Timo 503c7d6dc4 chore: update extension uninstall URL and fix missing onboarding restart translations 2026-06-14 05:02:31 +02:00
KoalaDev 77793c8c6e feat(extension): setup cross-browser uninstall url architecture 2026-06-13 21:01:31 +02:00
KoalaDev 4fbf309e5a fix(extension): resolve critical race conditions and infinite seek loop
- Fix infinite seek loop by transitioning to exact expectedSeekTime tracking
- Fix zombie connections by calling forceDisconnect on ping timeouts
- Fix popup Join/Leave race conditions using isProcessingConnection lock
- Fix cross-room join bugs by properly disconnecting old rooms and bypassing same-room joins
- Update onboarding UI to switch to Sync tab automatically
- Prepare CHANGELOG.md for v2.3.0 release
2026-06-13 10:50:46 +02:00
KoalaDev aa61a24351 Add ROADMAP.md with feature tracking and link in README 2026-06-13 06:35:56 +02:00
Timo 2ee5c83ee6 docs: add v2.2.5 unreleased changelog entries 2026-06-13 04:54:57 +02:00
KoalaDev ca1cfdb382 Merge pull request #7 from Shik3i/dependabot/npm_and_yarn/npm_and_yarn-53cbaf2a5b
chore(deps-dev): bump esbuild from 0.28.0 to 0.28.1 in the npm_and_yarn group across 1 directory
2026-06-13 04:53:43 +02:00
Timo ef7b1f2e5f fix: suppress video heartbeat PEER_STATUS when alone in room 2026-06-13 04:52:14 +02:00
Timo 10fdaa23fc fix: propagate audio settings from local storage and add compressor feedback 2026-06-13 04:48:25 +02:00
Timo 6ba5e1b10b Increase failedAuthAttempts eviction limit from 50k to 200k 2026-06-13 04:38:22 +02:00
dependabot[bot] 6e234fb8fd chore(deps-dev): bump esbuild
Bumps the npm_and_yarn group with 1 update in the / directory: [esbuild](https://github.com/evanw/esbuild).


Updates `esbuild` from 0.28.0 to 0.28.1
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.28.0...v0.28.1)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.28.1
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-13 02:07:21 +00:00
GitHub Action 9f553b4f8f chore(release): update versions to v2.2.4 [skip ci] 2026-06-09 23:42:27 +00:00
Timo 131afadc1d v2.2.4: Fix notification setting bypass and misleading reconnect message
- EVENTS.ERROR handler now respects browserNotifications setting
- Graceful shutdown message no longer implies manual reconnect
2026-06-10 01:33:45 +02:00
GitHub Action c62da66b06 chore(release): update versions to v2.2.3 [skip ci] 2026-06-09 22:56:52 +00:00
Timo 34f0c2b265 chore(release): v2.2.3 — artifact attestations, bugfixes, rate limit metrics 2026-06-10 00:56:29 +02:00
Timo af59b4c64c ci(release): add website build + artifact upload on tag 2026-06-09 22:03:38 +02:00
Timo af8184420c chore: stop tracking website/www/ (auto-generated) 2026-06-09 22:02:39 +02:00
Timo 45e1e3defe chore: add website/www/ to gitignore 2026-06-09 22:01:48 +02:00
Timo e67b0c564d fix(website): copy apple-touch-icon files to www root for iOS/Safari 2026-06-09 22:00:31 +02:00
GitHub Action f54a38b656 chore(release): update versions to v2.2.2 [skip ci] 2026-06-09 19:16:15 +00:00
Timo 5216fde641 fix: prevent hero title descender clipping, replace <br> with styled <span> for second line across all locales 2026-06-09 21:14:55 +02:00
Timo 39fe2cbd11 fix: prevent hero title descender clipping by adding padding-bottom to h1 2026-06-09 20:55:18 +02:00
Timo 03d5dbda66 website: add Open Source (MIT) feature card, bump Volume Leveling to bento-large, add footer disclaimers, update translations for all 13 locales, bump version badge to v2.2.1 2026-06-09 20:53:18 +02:00
KoalaDev 0c0cbbc090 feat: add audio compressor to comparison table, fix schema version + languages, update sitemap, add version injection to workflow 2026-06-09 19:26:50 +02:00
KoalaDev 0f4ad39fb4 feat: add Chrome _locales for store i18n, fix copy logs alignment, extend locale test 2026-06-09 19:14:30 +02:00
KoalaDev cfbb070c57 feat: add audio compressor feature to website (Card 7, FAQ Q7/A7) across all 13 locales 2026-06-09 19:09:10 +02:00
KoalaDev 0caf19cf18 docs: fix CHANGELOG v2.2.0 — remove nonexistent makeup gain slider, correct preset count 2026-06-09 18:31:49 +02:00
KoalaDev ff31f56911 Revise KoalaSync description and core features
Updated the description and features of KoalaSync to emphasize privacy, ease of use, and support for various platforms.
2026-06-09 17:37:34 +02:00
KoalaDev 54d03df0af fix: add ping to copy debug report 2026-06-09 07:15:02 +02:00
GitHub Action 4f1016fa87 chore(release): update versions to v2.2.1 [skip ci] 2026-06-09 05:08:32 +00:00
KoalaDev 3e7afd7592 feat: add server ping display with peer ping foundation
Adds application-level ping/pong between extension and relay server.
Extension sends PING every 15s, server echoes PONG. Round-trip time
displayed in Status tab (green/yellow/red color-coded).

Server also forwards PING with target peerId and routes PONG back,
enabling future peer-to-peer ping without server restart.
Extension already responds to incoming peer PINGs.

See CHANGELOG.md for details.
2026-06-09 07:08:14 +02:00
KoalaDev 6c6d4dc2a2 fix status: Netflix no title, Disney+ partial title 2026-06-09 06:52:07 +02:00
KoalaDev 498d5bd91b clean up TESTED_SERVICES notes and fix contradictions 2026-06-09 06:39:53 +02:00
KoalaDev bfd1177a14 Update Netflix, Disney+, and Prime Video sync status 2026-06-09 06:31:33 +02:00
KoalaDev 15f4f5a12e Add rel="noopener" to all target="_blank" links (tabnabbing fix)
- template.html: 10 links fixed (propagates to 13 language variants)
- join.html, privacy.html, imprint.html, impressum-de.html, datenschutz-de.html
- app.js: 2 window.open() calls updated with 'noopener'
- Rebuilt site with node website/build.js

Preserves HTTP Referer — noreferrer intentionally omitted.
2026-06-09 02:40:03 +02:00
KoalaDev fa39be7d40 Remove noindex from legal pages, add them to sitemap 2026-06-09 01:53:22 +02:00
KoalaDev 5fd00f0bda Restore original attribution line in README.md 2026-06-08 23:13:54 +02:00
KoalaDev dca9c6ed07 Revise audio compressor description in README
Updated audio compressor feature description for clarity.
2026-06-08 21:45:23 +02:00
GitHub Action d8634744ea chore(release): update versions to v2.2.0 [skip ci] 2026-06-08 19:31:38 +00:00
Timo 2031f76bee fix: ru.json duplicate AUDIO_BACK, add dupe detection to locale tests 2026-06-08 21:31:06 +02:00
Timo 3757308117 chore: clean up v2.2.0 changelog - nur echte App-Änderungen 2026-06-08 21:21:22 +02:00
Timo e4fae56509 fix: README - New Release unter Beschreibung, Git-Regeln in AI_INIT.md 2026-06-08 21:16:31 +02:00
GitHub Action 766bc1760c chore(release): update versions to v2.2.0 [skip ci] 2026-06-08 19:11:56 +00:00
Timo 4949b04d07 README: restore audio compressor bullet, keep changelog link short 2026-06-08 21:11:31 +02:00
GitHub Action 6ba81d66a4 chore(release): update versions to v2.2.0 [skip ci] 2026-06-08 19:11:17 +00:00
Timo d5ed1083bc Revert README to minimal version bump only 2026-06-08 21:10:48 +02:00
GitHub Action 78d576c7f6 chore(release): update versions to v2.2.0 [skip ci] 2026-06-08 19:08:29 +00:00
Timo 08ce689f4c move all prefs from sync to local storage (audio, filter, autosync, etc.) 2026-06-08 21:07:40 +02:00
GitHub Action bf48d1889b chore(release): update versions to v2.2.0 [skip ci] 2026-06-08 19:01:27 +00:00
Timo 39788e5bd9 README: update for v2.2.0 release 2026-06-08 21:01:06 +02:00
Timo c134e1bfae swap review/coffee order in footers 2026-06-08 20:58:44 +02:00
Timo cad4b4a6db v2.2.0 polish: inline pulse dot, coffee theme, remove toggle, GitHub issue link, beautify audio page, locale cleanup 2026-06-08 20:56:41 +02:00
Timo 9e2abadf5a v2.2.0: audio compressor, Ko-Fi support, 11 new locale translations, website locale test 2026-06-08 20:36:49 +02:00
KoalaDev f5db39b77b Merge pull request #6 from Skrockle/main
Add support badges and audio processing
2026-06-08 19:56:14 +02:00
Skrockle 85d05b85c9 Avoid external support badge asset 2026-06-08 19:46:02 +02:00
Skrockle 6b438f2d37 Merge remote-tracking branch 'skrockle/main' 2026-06-08 19:34:27 +02:00
Skrockle 7bba36c505 Add support badges and audio processing 2026-06-08 19:25:07 +02:00
Timo f93937f5f3 docs: cleanup ROADMAP.md (remove outdated cache item, translate to English) + add store assets 2026-06-08 19:16:28 +02:00
KoalaDev da12dc07a7 move example configs to examples/ dir 2026-06-07 12:55:43 +02:00
KoalaDev 198064b720 docs: move AI_INIT.md, CHANGELOG.md, PRIVACY.md, TESTED_SERVICES.md to docs/ and update all references 2026-06-07 12:54:02 +02:00
KoalaDev def0ad6ded Update GitGem badge format in README.md 2026-06-07 12:31:12 +02:00
KoalaDev 026bbc65b2 Add GitGem badge to README
Added GitGem badge to README for repository visibility.
2026-06-07 12:21:30 +02:00
KoalaDev 854be35474 Add Ko-fi username for funding support 2026-06-07 03:31:50 +02:00
GitHub Action 3506eb0331 chore(release): update versions to v2.1.3 [skip ci] 2026-06-06 22:43:52 +00:00
Timo 0a34d804fb v2.1.3: Fix debug report showing oldest logs instead of newest 2026-06-07 00:43:25 +02:00
GitHub Action a1e882cfa7 chore(release): update versions to v2.1.2 [skip ci] 2026-06-06 20:16:46 +00:00
Timo c88f6fb121 v2.1.2
- Fixed episode guard regex to support Sxx:Exx title format (Jellyfin/Emby)
  and any non-alphanumeric separator between season and episode numbers
- Migrated username, roomId, password, serverUrl, useCustomServer from
  chrome.storage.sync to chrome.storage.local for per-device isolation,
  preventing automatic cross-device room joins with duplicate names
- Added one-time migration from sync to local for existing users
2026-06-06 22:16:30 +02:00
Timo fe047dea2e Marquee promo tile added 2026-06-04 17:52:46 +02:00
Timo 464f5f466a Merge branch 'main' of https://github.com/Shik3i/KoalaSync 2026-06-04 17:40:42 +02:00
Timo 9f00645f58 Updated CTA Store Screenshot 2026-06-04 17:40:34 +02:00
KoalaDev f95095f2cd Update StoreDescription.md with feature details 2026-06-04 17:35:16 +02:00
KoalaDev be91d07c56 Update StoreDescription.md with formatting changes 2026-06-04 17:30:12 +02:00
KoalaDev a6bc7cae48 Revise StoreDescription.md for KoalaSync
Updated the description of KoalaSync to enhance clarity and detail about features, privacy, and usage instructions.
2026-06-04 17:29:49 +02:00
Timo d0c4b3740d New store assets added. 2026-06-04 17:20:02 +02:00
Timo 67a7f6e663 style: remove globe-icon from language selector on website 2026-06-04 15:37:27 +02:00
GitHub Action 7499eafe4e chore(release): update versions to v2.1.1 [skip ci] 2026-06-04 13:33:30 +00:00
Timo 3fe8ca18e4 chore: release v2.1.1 2026-06-04 15:33:06 +02:00
GitHub Action f0ccc0c082 chore(release): update versions to v2.1.0 [skip ci] 2026-06-04 13:20:10 +00:00
Timo d59fc4777d chore: release v2.1.0 with stabilization and audit fixes 2026-06-04 15:18:57 +02:00
Timo 3ad2459558 docs: update TRANSLATION.md with new languages and flags 2026-06-04 15:02:27 +02:00
Timo 839f4d0761 docs: write changelog for v2.1.0 and update README.md 2026-06-04 15:01:14 +02:00
Timo 8838bf20b7 style: add flag emojis to all language selectors 2026-06-04 15:00:10 +02:00
Timo 9d849a2996 feat: add 7 new languages to extension and website, bump version to 2.1.0 2026-06-04 14:59:14 +02:00
Timo 6d5661ea45 feat: update icons for extension and website, automate responsive logo sizes in build pipeline 2026-06-04 14:46:55 +02:00
Koala 886550408a docs: align PRIVACY.md and website datenschutz with the server-only tracking statements 2026-06-03 13:46:11 +02:00
Koala 28694c22bb docs: expand privacy and no-logs statement in main README.md 2026-06-03 13:43:35 +02:00
Koala a2a56f2b17 fix(docs): remove unsupported type: gauge from json_exporter.example.yml 2026-06-03 13:34:19 +02:00
Koala 742876e415 docs: add json_exporter configuration example for prometheus monitoring 2026-06-03 13:08:41 +02:00
KoalaDev bcd956a8db Update README for v2.0.8 release announcement 2026-06-03 12:12:17 +02:00
GitHub Action 1e329c0c52 chore(release): update versions to v2.0.8 [skip ci] 2026-06-03 10:11:57 +00:00
Koala a7481d42a2 fix(extension): resolve language selector UI overwrite and dynamic version reporting 2026-06-03 12:09:39 +02:00
GitHub Action f9577aace9 chore(release): update versions to v2.0.7 [skip ci] 2026-06-03 09:58:23 +00:00
Koala ad33720053 feat(server): add DEBUG_LOGGING environment variable to suppress verbose console output 2026-06-03 11:57:58 +02:00
Koala fa3341cc8e docs(server): quote values in env.example and set empty token 2026-06-03 11:51:49 +02:00
GitHub Action b526e9287b chore(release): update versions to v2.0.6 [skip ci] 2026-06-03 09:45:12 +00:00
Koala a6be6b2670 perf(server): optimize failedAuthAttempts LRU eviction to O(1) 2026-06-03 11:44:06 +02:00
Koala b51e66d824 docs: add modularization point to ROADMAP 2026-06-03 11:40:04 +02:00
Koala e034882975 docs: update release link in README to v2.0.5 2026-06-03 11:38:07 +02:00
Koala e53a93829e docs: enforce no-manual-version-bumping rule in AI_INIT and SYNC_GUIDE 2026-06-03 11:36:09 +02:00
GitHub Action e3dbf2b8a2 chore(release): update versions to v2.0.5 [skip ci] 2026-06-03 09:34:22 +00:00
Koala 595ea297f5 chore(release): release v2.0.5 2026-06-03 11:33:54 +02:00
GitHub Action a948780745 chore(release): update versions to v2.0.4 [skip ci] 2026-06-03 09:07:20 +00:00
Koala 8c899a6469 Update changelog for v2.0.4 2026-06-03 11:06:44 +02:00
Koala 4f4cdcd8c0 Cache health responses server-side 2026-06-03 11:04:45 +02:00
Koala 602be3a724 Tighten health endpoint hardening 2026-06-03 10:59:08 +02:00
Koala bb7bd21102 Update release assets for v2.0.3 2026-06-03 10:51:47 +02:00
GitHub Action 543bfe074d chore(release): update versions to v2.0.3 [skip ci] 2026-06-03 08:49:25 +00:00
Koala 81c50eff16 Harden room discovery and health metrics 2026-06-03 10:48:49 +02:00
Koala bf0fb5741f fix: version.json path for all non-English locales
Changed condition from lang === 'de' to lang === 'en' so that all
non-English language subdirectories (fr, es, pt-BR, ru, de) correctly
resolve version.json via ../version.json instead of only German.
2026-06-02 14:31:13 +02:00
Timo 6d2355f404 Store Assets added 2026-06-02 09:26:20 +02:00
Koala 732d585273 docs: add v2.0.2 changelog and update README badge 2026-06-02 08:23:49 +02:00
GitHub Action 666808f876 chore(release): update versions to v2.0.2 [skip ci] 2026-06-02 06:17:21 +00:00
Koala 3ec9812265 fix: use server-trusted peerId in relay payload to prevent spoofing 2026-06-02 08:17:08 +02:00
Koala 64f3c5eefd fix: use future-proof regex for Amazon domain matching 2026-06-02 08:06:35 +02:00
Koala 3fe074b308 fix: use orteil.dashnet.org instead of cookieclicker.com in blacklist 2026-06-02 08:04:31 +02:00
Koala a06cca8b9d fix: apply boundary-safe hostname matching to remaining platform and blacklist checks 2026-06-02 08:02:47 +02:00
KoalaDev c4fc3ab53b Merge pull request #4 from Shik3i/alert-autofix-3
Potential fix for code scanning alert no. 3: Incomplete URL substring sanitization
2026-06-02 07:55:08 +02:00
KoalaDev 3f3fea58ed Merge pull request #5 from Shik3i/alert-autofix-2
Potential fix for code scanning alert no. 2: DOM text reinterpreted as HTML
2026-06-02 07:54:51 +02:00
KoalaDev 014169f84e Potential fix for code scanning alert no. 2: DOM text reinterpreted as HTML
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-06-02 07:51:01 +02:00
KoalaDev 9c61abee03 Potential fix for code scanning alert no. 3: Incomplete URL substring sanitization
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-06-02 07:50:35 +02:00
KoalaDev 2f11c60307 Merge pull request #2 from Shik3i/dependabot/npm_and_yarn/server/npm_and_yarn-e5a46ec0e1
chore(deps): bump ws from 8.18.3 to 8.20.1 in /server in the npm_and_yarn group across 1 directory
2026-06-02 07:41:19 +02:00
KoalaDev 27301f8746 Merge pull request #3 from Shik3i/dependabot/npm_and_yarn/server/npm_and_yarn-05b1f1d78b
chore(deps): bump qs from 6.15.1 to 6.15.2 in /server in the npm_and_yarn group across 1 directory
2026-06-02 07:40:26 +02:00
dependabot[bot] 030b839b12 chore(deps): bump qs
Bumps the npm_and_yarn group with 1 update in the /server directory: [qs](https://github.com/ljharb/qs).


Updates `qs` from 6.15.1 to 6.15.2
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.15.1...v6.15.2)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.15.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 05:39:05 +00:00
dependabot[bot] 5c805bcafa chore(deps): bump ws
Bumps the npm_and_yarn group with 1 update in the /server directory: [ws](https://github.com/websockets/ws).


Updates `ws` from 8.18.3 to 8.20.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.18.3...8.20.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 8.20.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 05:39:00 +00:00
Koala eb83d9148e Update contact email in CODE_OF_CONDUCT 2026-06-02 07:23:38 +02:00
Koala 290360387f Add CODE_OF_CONDUCT and PULL_REQUEST_TEMPLATE
- Add CODE_OF_CONDUCT.md based on Contributor Covenant 2.1
- Add .github/PULL_REQUEST_TEMPLATE.md with structured sections
- Link CODE_OF_CONDUCT in README.md and CONTRIBUTING.md
2026-06-02 07:22:37 +02:00
Koala c56e404c13 Overhaul issue templates
- Rewrite bug_report.md with professional structure and Copy Logs requirement
- Rewrite feature_request.md to focus on problem, use cases, and benefits
2026-06-02 07:19:49 +02:00
KoalaDev e519ea2302 Update issue templates 2026-06-02 07:16:17 +02:00
Koala 5a75bae8d0 mobile: nav container padding auf 0.5rem reduziert 2026-06-02 06:43:57 +02:00
Koala fc3f4f38ff mobile: Header noch weiter nach links gerückt
- Nav Container Padding auf Mobile reduziert (0.75rem statt 2rem)
- Logo-Area Gap auf 0.25rem reduziert
- Icon auf 40x40 verkleinert
- Schriftgröße auf 1.1rem reduziert
- Vertical margin vom Icon entfernt
2026-06-02 06:41:46 +02:00
Koala f69da7542e mobile: Sprachauswahl immer sichtbar, Hamburger entfernt
- Sprachauswahl aus nav-links in neuen nav-right Container verschoben
- nav-right ist auf Mobile rechtsbündig und immer sichtbar
- Hamburger-Menü entfernt (Links sind Same-Page-Anchors)
- Logo-Bereich auf Mobile kompakter (kleineres Icon, weniger Gap)
- Lang-Selector auf Mobile kompakter (kleinere Icons, weniger Padding)
- Gleiches Restructuring für join.html, impressum.html, datenschutz.html
2026-06-02 06:35:39 +02:00
Timo aba05060b9 feat: add localization comparison row to website comparison table 2026-06-01 17:11:55 +02:00
Timo 0b986e6e13 fix: build-time VERSION injection via build.js instead of JS runtime 2026-06-01 16:27:45 +02:00
Timo b23ce8ee7d fix: dynamic mockup version from version.json instead of hardcoded 1.9.3 2026-06-01 16:25:18 +02:00
Timo 3195bd0089 docs: move older changes into v2.0.0 where they belong 2026-06-01 16:21:35 +02:00
Timo 1c1778d265 docs: remove older changelog entries - attribution unclear 2026-06-01 16:20:36 +02:00
Timo 0262d9c1ad docs: fix changelog dates to match git tag timestamps 2026-06-01 16:19:11 +02:00
Timo f23c329709 docs: move release notice to top of README 2026-06-01 16:18:25 +02:00
Timo 78a165d368 docs: clarify Copy Debug Report description in v2.0.0 2026-06-01 16:16:40 +02:00
Timo 63b5ef0ffd docs: remove website-only entries from changelog, keep extension+server only 2026-06-01 16:16:23 +02:00
Timo a26b07cb5c docs: highlight extension i18n as biggest v2.0.0 feature 2026-06-01 16:15:32 +02:00
Timo a445759dd7 docs: add CHANGELOG.md, link from README, version badge -> clickable changelog 2026-06-01 16:14:30 +02:00
Timo 1e883d8ee4 chore: update assets - add icon folder, remove old SOCIAL_PREVIEW.png 2026-06-01 16:03:40 +02:00
GitHub Action be574e0e25 chore(release): update versions to v2.0.1 [skip ci] 2026-06-01 13:59:31 +00:00
Timo ebf3178e32 feat: multi-video overview in debug report
- GET_VIDEO_STATE now returns allVideos[] with summary per video element
- Copy report shows markdown table when multiple videos on page
- Table includes: resolution, muted, paused, readyState, duration, and marks selected target
- Helps diagnose Prime Video scenarios where preview vs main video both exist
2026-06-01 15:58:31 +02:00
Timo ba96cf2765 fix: prioritize largest video element, fix history action field in debug report
- findVideo() now scores all video elements by size + unmuted + duration
- Fixes Prime Video selecting 0x0 placeholder instead of actual player
- Fix history entries showing '?' by using h.action instead of h.event/h.type
- Update Prime Video status in TESTED_SERVICES.md to partial support
2026-06-01 15:57:11 +02:00
GitHub Action d026ed891a chore(release): update versions to v2.0.0 [skip ci] 2026-06-01 13:50:26 +00:00
Timo d6665fe0f5 feat: v2.0.0 - enhanced debug reports, platform detection, copy report, mockup i18n, new icons
- Copy Logs button now generates full Markdown debug report (system, tab, connection, video, history, logs)
- Enhanced Dev tab with 20+ video state fields (network, dimensions, error, shadow DOM, platform detection)
- Platform auto-detection (YouTube, Netflix, Twitch, Prime Video, Disney+, Hulu, HBO, Vimeo, Dailymotion)
- 'No video found' diagnostic mode with hints
- Convert mockup from inline <span lang> to {{MOCK_*}} template vars (all 6 languages)
- New TwoPointZero branding: icons (16/32/48/96/128), favicons (PNG 16/32 + apple-touch + 192)
- Fix logo vertical centering via display:contents on picture wrappers
- Increase popup logo to 48px, website nav logo to 64px
- AVIF quality 70->80, speed 6->4, remove min 3KB threshold
- Add 32+96 icon sizes to manifest
- Remove amazon. from tab blacklist (unblocks Prime Video)
- Update TESTED_SERVICES.md: mark Prime Video as non-functional
- Update CONTRIBUTING.md with Copy Debug Report workflow
2026-06-01 15:49:55 +02:00
Timo 79025f4d18 feat: new TwoPointZero branding - icons, favicon, mockup i18n
- Replace extension icons with new TwoPointZero design (16/32/48/96/128)
- Update manifest.base.json with 32+96 icon sizes
- Increase popup logo to 48px, website nav logo to 64px
- Generate website webp sizes (64/128/200) + AVIF variants from 600px source
- Add favicon PNGs (16x16, 32x32) + apple-touch-icon + 192x192
- Fix logo vertical centering via display:contents on picture wrappers
- Convert mockup section from inline <span lang> pairs to {{MOCK_*}} template vars
- Add MOCK translations for FR, ES, PT-BR, RU locale files
- Bump AVIF quality 70->80, speed 6->4, remove min 3KB threshold
- Regenerate www/ with build.js
2026-06-01 15:29:33 +02:00
Timo a1f921407c Smart disconnect + human-readable room IDs + expanded word lists
- Add currentServerUrl tracking: only disconnect/reconnect when server URL actually
  changes, otherwise reuse existing connection and just switch rooms
- Add resolveServerUrl() helper for consistent URL resolution across handlers
- forceDisconnect() now resets currentServerUrl to null
- CONNECT and WEB_JOIN_REQUEST handlers: smart comparison before reconnecting
- Add generateRoomId(): human-readable 'ADJECTIVE-NOUN-Number' format (e.g. HAPPY-KOALA-16)
- Replace Math.random() room IDs with generateRoomId() in joinBtn and handleCreateRoom()
- Expand USERNAME_ADJECTIVES: +16 words (Turbo, Zen, Pixel, Cyber, Solar, etc.)
- Expand USERNAME_NOUNS: +12 words (Yeti, Goblin, Pirate, Ninja, Wizard, Storm, etc.)
- Add emoji mappings for all new words
2026-06-01 13:29:02 +02:00
Timo f2669ed769 docs: add relay reachability check hint to readme 2026-06-01 13:27:18 +02:00
Timo 7a6ec8087e Fix WEB_JOIN_REQUEST and joinBtn: custom server invite + password generation
- WEB_JOIN_REQUEST handler: forceDisconnect+connect instead of reusing old socket
  (same bug as CONNECT: JOIN_ROOM was sent to official server instead of custom)
- joinBtn handler: generate random password when roomId is empty and password empty
  (previously created rooms without passwords when using the advanced join button)
- Auto-copy invite link when creating room via joinBtn (consistent with Create Room button)
2026-06-01 13:20:45 +02:00
Timo 8cc622bda0 Fix custom server reconnection and reconnect strategy
- Fix: CONNECT/RETRY_CONNECT always force-disconnect before reconnecting
  (prevents JOIN_ROOM going to official server when custom is selected)
- Add forceDisconnect() helper: clears socket, eventQueue, episodeLobby,
  expectedAcksCount, broadcast status, persists cleanup to storage
- Fix: save useCustomServer to storage on Join Room click (no race condition)
- Fix: trigger RETRY_CONNECT on server mode toggle and custom URL change
- Fix: show error when Custom selected but no URL entered (no silent fallback)
- Fix: button label changed to 'Join / Create Room' in all 6 locales
- Rewrite scheduleReconnect() for two-phase strategy:
  Phase 1 (aggressive): 500ms-5s backoff, max 20 attempts or 5 minutes
  Phase 2 (slow): retry every 5 minutes indefinitely, never give up
- Persist reconnectAttempts/reconnectStartTime/reconnectFailed to session storage
- Remove 'reconnect_failed' status; slow mode shows as 'reconnecting' with retry button
- Add scheduleReconnect() call on offline path to continue retry cycle
2026-06-01 13:06:40 +02:00
KoalaDev 2b5da0dbb7 Modify sync status for YouTube and Twitch
Updated sync status for YouTube and Twitch to indicate episode auto-sync issues.
2026-06-01 12:25:58 +02:00
Timo 1a7ff6b3a7 docs: shorten compose example link text in readme 2026-06-01 12:24:09 +02:00
Timo b1b858d771 docs: replace single docker-compose example with caddy + static ip variants 2026-06-01 12:16:25 +02:00
Timo 608f742f83 fix(i18n): resolve hardcoded strings, missing translation keys, SW notification race conditions, and toast username bugs 2026-06-01 05:47:08 +02:00
Timo b2ff24e155 SEO: add crawlable language navigation links for Google indexing
- Add sr-only CSS class for screen-reader/crawler-visible elements
- Add hidden <nav> with real <a> links to all 6 language versions in template.html
- Googlebot can now discover /de/, /fr/, /es/, /pt-BR/, /ru/ via HTML links
- No visual changes - existing <select> dropdown remains the user-facing switcher
- Rebuild all www/ output with updated CSS hash
2026-06-01 05:36:06 +02:00
Koala 9285e1041e seo: per-locale HowTo schema (8 new keys × 6 languages)
HowTo schema was hardcoded English — now uses {{HOWTO_*}}
placeholders. All 3 rich-result schemas are now locale-aware:
  - FAQPage  (expandable SERP snippets)
  - HowTo    (step-by-step SERP snippets)
  - SoftwareApplication (entity understanding)

SoftwareApplication and Organization use language-neutral
brand name — no locale keys needed.
2026-06-01 04:02:21 +02:00
Koala 3ac110f8f5 seo: update sitemap lastmod + add xhtml:link hreflang alternates
- lastmod bumped to 2026-06-01 for all 6 language roots
- xhtml:link hreflang entries added per URL (Google-recommended
  belt-and-suspenders approach alongside HTML hreflang tags)
2026-06-01 03:58:17 +02:00
Koala cc8c58fac5 seo: per-locale FAQPage schema + Q6 self-hosting mention
- FAQPage schema now uses {{FAQ_Q1}}..{{FAQ_A6}} placeholders
  instead of hardcoded English — each language gets its own
  schema for locale-matched Google Rich Snippets
- Q6 updated across all 6 languages: 'What if I don't trust
  the official server?' + self-hosting Docker mention
2026-06-01 03:56:58 +02:00
Koala 53768980d0 docs: add TESTED_SERVICES.md compatibility matrix; update FAQ Q1
- TESTED_SERVICES.md: table of tested platforms with sync/title/auto-sync columns
- FAQ Q1: explicitly mention Emby + Jellyfin, remove Disney+/Prime mention
- All 6 locale files updated (Q1+A1)
- README links to TESTED_SERVICES.md
2026-06-01 03:54:52 +02:00
Koala 1b04db009a fix: add *.avif to Caddy static cache matcher
PageSpeed Insights flagged AVIF files as uncached because the
@static matcher only included *.webp. Adding *.avif enables
1-year Cache-Control for all AVIF assets.
2026-06-01 03:51:03 +02:00
Koala a2720da5f8 seo: add visible FAQ section with FAQPage schema (6 keyword-targeted Q&As)
Targets high-volume search queries:
- netflix watch2gether / netflix watch in sync
- free teleparty alternative / teleparty alternative
- emby syncplay / jellyfin sync
- watch together extension browsers
- privacy-friendly watch party

Native <details> elements — no JS, Google-visible, Rich-Snippet eligible.
All 6 languages translated (EN/DE/FR/ES/PT-BR/RU).
2026-06-01 03:49:49 +02:00
Koala d2c380d6fc fix: revert store URLs from SoftwareApplication sameAs
Google does not support sameAs for app store links in SoftwareApplication
rich results. sameAs is for identity pages only (Wikipedia, Wikidata, GitHub).
Store pages are independently indexed by Google and need no schema linking.
2026-06-01 03:41:01 +02:00
Koala 1a36b138c0 seo: add Chrome Store and Firefox Add-on URLs to SoftwareApplication sameAs
Google treats SoftwareApplication.sameAs as app listing references
(not social profiles like Organization.sameAs). This connects
KoalaSync's entity graph to both browser stores without
duplication risk.
2026-06-01 03:37:47 +02:00
Koala 47ead27344 docs: improve CONTRIBUTING.md and SECURITY.md readability
- CONTRIBUTING: add ways to contribute table, project structure, setup guide
- CONTRIBUTING: add website testing, translation guide, bug report template
- SECURITY: add scope/out-of-scope, architecture overview, threat model
- SECURITY: structured reporting timeline, responsible disclosure policy
2026-06-01 03:21:59 +02:00
Koala 72180ba817 feat(website): SEO, a11y, and build pipeline overhaul
- SEO: per-locale og:locale, Organization+WebSite schema, FAQPage replacing ItemList
- SEO: BreadcrumbList on legal pages, site.webmanifest, expanded sitemap
- a11y: hamburger ARIA+ESC, skip-to-content, aria-hidden on step-num
- a11y: focus-visible states, prefers-reduced-motion, emoji→SVG icons
- CSS: extract .mock-section-label, replace transition:all, will-change hints
- Build: esbuild JS minifier (-46%), sharp AVIF conversion (26 files, quality 70)
- Build: content hashing (style.XXXX.min.css), SVG minification, picture injection
- Cleanup: remove lightningcss (caused CSS merging bugs)
2026-06-01 03:20:51 +02:00
Koala 9eab699e2a feat(website): output minified files as .min.* for safety and clarity 2026-06-01 02:14:31 +02:00
Koala c95e72b713 docs: document build minifier and warn against editing www/ directly 2026-06-01 02:12:05 +02:00
Koala 14be38fe11 feat(website): add build-time minification and optimize meta descriptions 2026-06-01 02:10:27 +02:00
Koala 1bb2123da2 feat(website): technical SEO and UI/UX accessibility improvements
- Add LCP image preload for hero mascot
- Add HowTo structured data schema (3-step install guide)
- Update SoftwareApplication schema with screenshot + image fields
- Add glassmorphic :focus-visible outlines for keyboard accessibility
- Add content-visibility: auto to heavy offscreen sections for INP
- Expand lang-init.js auto-redirect to all 6 supported languages
- Remove impressum/datenschutz from sitemap.xml (noindex conflict)
2026-06-01 01:57:16 +02:00
Koala e29c6666b6 feat(website): redesign bento tiles in 'Why KoalaSync' section
- Swap last two tiles: Invitation becomes 2nd-last (bento-large),
  Self-Hosting becomes last small tile
- bento-large layout: icon centered above heading, 33/67 grid split
- Fix icon consistency on small tiles (uniform 44x44 rounded squares)
- Improve responsive breakpoints for all viewports
- Delete legacy website/index.html (obsoleted by template + build.js)
2026-06-01 01:42:42 +02:00
Koala b08e8ba06b docs: update advanced caddyfile configuration for syncserver in example and website 2026-06-01 01:20:31 +02:00
Koala 30a4057c99 docs: update documentations to reflect browser extension i18n features 2026-06-01 01:07:41 +02:00
Koala fd47eb82b9 feat(extension): implement full dynamic i18n support and automated consistency checks 2026-06-01 01:05:46 +02:00
Koala 3fcafbd081 fix(eslint): resolve all 6 repository lint errors
- Removed unused html variable from localizeHomeLinks in website/app.js.
- Added website/build.js to ESLint flat config Node files match pattern to define require, process, and __dirname.
2026-06-01 01:04:47 +02:00
Koala d430501b82 feat(website): update sitemap.xml to index clean URLs
- Replaced /impressum.html and /datenschutz.html with clean URLs /impressum and /datenschutz in sitemap.xml.
- Recompiled website to copy clean sitemap to www/ folder.
2026-06-01 00:23:54 +02:00
Koala d738352c90 feat(website): implement Clean URLs and split Caddy configurations
- Removed all .html file extension suffixes from website and invite links (impressum, datenschutz, join).
- Updated app.js dynamic localizer and path switching to target Clean URLs ('./', '../', etc.).
- Refactored Caddyfile.example into simple (lightweight try_files) and advanced (hardened production) options.
- Split Caddy configs into Caddyfile (Simple) and Caddyfile (Advanced) tabs on the landing page.
2026-06-01 00:21:29 +02:00
Koala dbf1b3e81b docs(translation): refine TRANSLATION.md for better clarity and human readability
- Restructured translation guide with clean markdown layout and tables.
- Removed mermaid sequence diagram to improve text-based clarity.
- Audited steps for contributing new language keys.
2026-05-31 23:54:44 +02:00
Koala 5a3f1c7019 docs(website): update Caddyfile configuration and website README for www/ path
- Corrected static server root path to website/www/ in Caddyfile.example and website/README.md.
- Updated core roles to reflect multi-language (EN/DE/FR/ES/PT-BR/RU) support.
- Refactored local development and compilation instructions to target build.js and www/ output.
2026-05-31 23:50:49 +02:00
Koala 57b0dd1632 feat(website): implement custom i18n static compiler & full 6-language expansion
- Added pure Node.js dynamic i18n static site generator (build.js).
- Structured locales for English, German, French, Spanish, Brazilian Portuguese, and Russian.
- Replaced two-state toggle with premium glassmorphic language select dropdown.
- Integrated robust segment-based locale routing with safe dynamic fallbacks for legal and invite pages.
- Audited Core Web Vitals (LCP preloads, CLS dimensions) and SEO structures (robots, sitemap).
- Added dedicated Localization section to README and created contributor TRANSLATION guide.
2026-05-31 23:49:36 +02:00
Koala b6f7c1ccdb refactor(datenschutz): unify email obfuscation to .email-reveal (scraper-safe) 2026-05-31 00:47:39 +02:00
Koala 4bada9533a feat(impressum): add name (Timo/KoalaDev), Mastodon for private msgs, GitHub Issues for concerns 2026-05-31 00:43:58 +02:00
Koala df32385fa6 fix(website): correct Mastodon SVG icon path to official branding 2026-05-31 00:22:47 +02:00
Koala ce9778245d feat(website): add Mastodon profile link (rel=me) to all page footers 2026-05-31 00:16:18 +02:00
Koala 5d6c0cd1fb fix(seo): shorten title (<70 chars), meta description (<160 chars), fix duplicate h1 2026-05-30 21:43:32 +02:00
Koala bbba50f643 fix(website): add responsive srcset to nav logo (40w/80w/200w) 2026-05-30 21:26:36 +02:00
Koala 56293209f8 fix(website): switch from x-descriptors to w-descriptors + sizes for responsive images to satisfy PageSpeed Insights 2026-05-30 21:21:51 +02:00
Koala c55691a535 feat(website): add responsive images with srcset (1x/2x WebP) for all mascot and illustration images 2026-05-30 21:16:09 +02:00
Koala ce3b17f55f feat(website): add hreflang tags, comparison JSON-LD schema, and update sitemap with lastmod 2026-05-30 20:58:35 +02:00
Koala 5e8e9f61c2 fix(website): use Web Animations API for hero button pulse, avoids CSS transition/inline-style conflicts from mouse handlers 2026-05-30 20:50:34 +02:00
Koala 59eb1a6092 fix(website): apple-mobile-web-app-capable deprecated meta tag + download button pulse animation 2026-05-30 20:45:08 +02:00
Timo 00c9ff8cfc feat(website): Add smart browser-badge breathing animation, nav installed indicator, clickable smooth-scroll footnotes, and mockup height layout adjustments 2026-05-30 20:35:48 +02:00
Timo 748ccaa835 Refactor(website): Overhaul Getting Started section with extension-accurate workflows, premium CSS animations, stacked browser badges, and refined German translations 2026-05-30 20:25:36 +02:00
Timo 25b23d083d feat(website,extension): integrate NewLogoIcon as WebP favicon/header logo and convert to PNG extension icons 2026-05-30 20:14:59 +02:00
Timo 74042c3c78 docs(readme): replace header icon with Juggler mascot and feat(website): optimize technical SEO & add mobile theme colors 2026-05-30 19:55:25 +02:00
Timo fb1451022b feat(website): integrate KoalaDeploy mascot, enlarge Juggler mascot by 15%, and add premium interactive hover effects to all main mascots 2026-05-30 19:48:53 +02:00
Timo 54ffbfa4bd feat(website): integrate KoalaSearching, KoalaImprintl, and KoalaPrivacy mascots with custom animations and tight layouts 2026-05-30 19:05:35 +02:00
Timo 0db0b5eea2 feat(website): add KoalaThumbsUp and KoalaThumbsDown status mascots on invite page, hide pulsing ring, and support dev simulation mode 2026-05-30 18:22:05 +02:00
Timo d58041bd4e feat(website): add lookdown koala to hero, questions koala to features, pro-contra koala to comparison, translate learning tile to Gemeinsam Lernen, and refine copy 2026-05-30 17:50:03 +02:00
Timo 1f1b99a1fe feat(website): add koala holding git page next to octocat in bottom CTA and improve LDR couple alt text description 2026-05-30 16:57:31 +02:00
Timo c608d2d9e9 style(website): standardize use-case image container heights to 150px and set object-fit contain to align headings 2026-05-30 16:40:55 +02:00
Timo 06203eb5cb feat(website): replace LDR emoji with hugging koala couple and translate title to Fernbeziehungen 2026-05-30 16:39:03 +02:00
Timo ed599a96ab feat(website): replace remote learning emoji with professor koala mascot in use cases grid 2026-05-30 16:11:44 +02:00
Timo 5d1707a7d7 feat(website): replace movie night emoji with popcorn-sharing koalas mascot and optimize card padding 2026-05-30 15:59:38 +02:00
Timo f4343ca6b1 feat(website): add juggling koala mascot to platforms section and optimize spacing 2026-05-30 15:43:01 +02:00
Koala 3644a7b8ac website: refactor interactive extension mockup and align styling with popup UI
- Update mockup tabs layout, colors, inputs, cards, and spacing to perfectly mirror popup.html 1-to-1.
- Enable full interactivity for toggle switches in Settings mockup screen.
- Link the top mockup version badge directly to the GitHub repository with smooth CSS hover transitions and cursor pointer.
- Port all hover tooltips (title attributes) from popup.html to the mockup index.html.
- Add mandatory caveman communication protocol to AI_INIT.md onboarding instructions.
2026-05-30 13:14:47 +02:00
Koala c621c851af style(website): optimize SEO, UX, accessibility, and navigation icons 2026-05-30 11:49:04 +02:00
Koala 1113fb8bb5 Fix app.js brace syntax error and remove duplicate tbody in index.html 2026-05-30 11:24:13 +02:00
Koala c852826091 Enhance Join page with animated radar status indicator, resolve silent status icon bug, and clean inline script button styles to CSS 2026-05-30 11:20:40 +02:00
Koala cfef79ec1d Refactor comparison table categories to be neutral, consolidate privacy footnotes, and add comparison date stamp 2026-05-30 11:18:06 +02:00
Koala 8fbada8b03 Add clickable proof footnotes to Teleparty comparison table linking to official policy pages 2026-05-30 11:14:04 +02:00
Koala 2261a8133a Refactor landing page use-cases, browser CTA badging, official brand SVGs, and streamlined competitor comparison 2026-05-30 11:13:29 +02:00
GitHub Action d5241b19e8 chore(release): update versions to v1.9.3 [skip ci] 2026-05-30 00:01:24 +00:00
Timo f7096edd30 Refactor: Behebung Force-Sync ACK-Zaehler-Inflation, SHA-256 Hashing, Room-ID-Desinfizierung, Reduzierung MIN_SEEK_DELTA auf 2s und seekDebounce auf 300ms 2026-05-30 02:00:17 +02:00
GitHub Action e2b76b05a1 chore(release): update versions to v1.9.2 [skip ci] 2026-05-29 23:35:36 +00:00
Timo 93acd0b44c fix: resolve F-01 to F-08 audit bugs and QoL improvements 2026-05-30 01:34:48 +02:00
GitHub Action ce7b5c47f2 chore(release): update versions to v1.9.1 [skip ci] 2026-05-29 22:08:39 +00:00
213 changed files with 24091 additions and 2187 deletions
+2
View File
@@ -0,0 +1,2 @@
# These are supported funding model platforms
ko_fi: koaladev
+56
View File
@@ -0,0 +1,56 @@
---
name: 🐛 Bug Report
about: Create a report to help us improve KoalaSync
title: ''
labels: bug
assignees: ''
---
> **⚠️ Required:** Before submitting, open the KoalaSync **Status** tab in the extension popup and click **"Copy Logs"**. Paste the full output below — it contains essential system info, connection state, and debug data needed to diagnose your issue.
<details>
<summary><b>📋 Copy Logs Output</b></summary>
<!-- Paste the copied logs here -->
</details>
---
### Describe the Bug
A clear and concise description of what the bug is.
### To Reproduce
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
### Expected Behavior
A clear and concise description of what you expected to happen.
### Actual Behavior
A clear and concise description of what actually happened.
### Screenshots / Screen Recordings
If applicable, add screenshots or recordings to help explain your problem.
### Environment
- **Browser:** (e.g. Chrome 125, Firefox 128)
- **Extension Version:** (visible at the bottom of the Settings tab)
- **OS:** (e.g. Windows 11, macOS 14.5)
- **Self-Hosted Server?:** (yes / no — if yes, provide server version)
- **Website/Platform:** (e.g. YouTube, Netflix, Twitch, Jellyfin, Emby)
### Additional Context
Add any other context about the problem here (e.g. network setup, VPN usage, multiple monitors, etc.).
+31
View File
@@ -0,0 +1,31 @@
---
name: 🚀 Feature Request
about: Suggest a new feature or enhancement for KoalaSync
title: ''
labels: enhancement
assignees: ''
---
### Problem Description
A clear and concise description of the problem or limitation you're encountering. What's missing or what could be improved?
*As a user, I want to ... so that ...*
### Proposed Solution
Describe the feature you'd like to see. How should it work? Be as specific as possible.
### Use Cases & Benefits
- **When would you use this?** (e.g., specific websites, workflows, setups)
- **What value does it add?** (e.g., saves time, enables new functionality, improves UX)
### Alternatives Considered
What workarounds or alternative approaches have you tried? Are there other ways to achieve a similar result?
### Additional Context
Add any other context, sketches, mockups, or references (e.g., links to similar features in other projects).
+45
View File
@@ -0,0 +1,45 @@
<!--
Thanks for contributing to KoalaSync!
Please read the CONTRIBUTING.md and CODE_OF_CONDUCT.md before submitting.
By submitting this PR, you agree to abide by our code of conduct.
Use conventional commits for the title: feat:, fix:, docs:, refactor:, chore:
-->
### Description
<!-- What does this PR do and why? Link to relevant issue or motivation. -->
Closes #
### Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that alters existing behavior)
- [ ] Refactoring (no functional changes)
- [ ] Documentation update
- [ ] Build, dependencies, or CI
### How Has This Been Tested?
<!-- Describe the tests you ran and the environments (browsers, OS, etc.) -->
- [ ] Tested on Chrome
- [ ] Tested on Firefox
- [ ] `npm run lint` passes with zero errors and zero warnings
- [ ] `node -c` passes on all modified `.js` files
### Checklist
- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review of my code
- [ ] I have added/updated tests if needed
- [ ] I have updated documentation if needed (`docs/`, README, etc.)
- [ ] Protocol changes: I ran `node scripts/build-extension.cjs` and updated relevant docs
- [ ] No new warnings, secrets, or hardcoded credentials introduced
### Additional Context
<!-- Screenshots, migration notes, performance data, etc. -->
+75
View File
@@ -0,0 +1,75 @@
name: Beta Server Image
# Publishes the relay server as a Docker image under NON-production tags so a
# feature branch can be deployed to a staging/backup server and used as a custom
# server, without ever touching the ':latest' tag the official relay tracks.
#
# Tags produced (on ghcr.io/<owner>/<repo>):
# - beta moving channel pointer to the newest build
# - <branch-slug> e.g. feature-host-control-mode
# - sha-<short-commit> immutable, pin to an exact build
# - <custom> only on manual run, e.g. hostcontrol01
# Never ':latest' (flavor: latest=false).
#
# Remove this workflow once the feature is merged & released the normal way.
on:
push:
branches:
- feature/host-control-mode
workflow_dispatch:
inputs:
tag:
description: 'Extra immutable tag for this build (e.g. hostcontrol01). Optional.'
required: false
default: ''
concurrency:
group: beta-image-${{ github.ref }}
cancel-in-progress: true
jobs:
build-beta-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository }}
# Never publish ':latest' from a beta build.
flavor: |
latest=false
tags: |
type=raw,value=beta
type=ref,event=branch
type=sha,prefix=sha-
type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
with:
context: .
file: server/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
+46
View File
@@ -0,0 +1,46 @@
name: CI
# Runs the full verification suite (lint, unit/integration tests, production
# audits, extension + website build) on every push to main and every PR, so a
# regression can never reach main or a release tag unchecked.
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
# Cancel superseded runs on the same ref to save CI minutes. Unlike the release
# workflow, an interrupted CI run has no side effects.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
verify:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
cache-dependency-path: |
package-lock.json
server/package-lock.json
- name: Install root dependencies
run: npm ci
# The server test suite (test-server-ws/routes/ops) imports express,
# socket.io, and dotenv from server/node_modules, so install them too.
- name: Install server dependencies
run: npm ci
working-directory: server
- name: Run verification suite
run: npm run verify
+66 -11
View File
@@ -5,21 +5,29 @@ on:
tags:
- 'v*'
# A release run must never be interrupted (it commits back to main and publishes
# artifacts). Only dedupe accidental re-pushes of the same tag.
concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: false
jobs:
release-server:
runs-on: ubuntu-latest
permissions:
contents: write
contents: read
packages: write
id-token: write
attestations: write
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -27,7 +35,7 @@ jobs:
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository }}
tags: |
@@ -35,7 +43,8 @@ jobs:
type=ref,event=tag
- name: Build and push Docker image
uses: docker/build-push-action@v5
id: build
uses: docker/build-push-action@v7
with:
context: .
file: server/Dockerfile
@@ -43,14 +52,32 @@ jobs:
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Reuse layers across releases to speed up the multi-arch build.
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate artifact attestation
uses: actions/attest@v4
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
release-extension:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
- name: Extract version from tag
id: version
@@ -67,7 +94,7 @@ jobs:
echo " ✓ manifest.base.json -> $VERSION"
# 2. shared/constants.js — APP_VERSION
sed -i "s/export const APP_VERSION = '.*'/export const APP_VERSION = '$VERSION'/" shared/constants.js
sed -i "s/export const APP_VERSION = [\"'].*[\"']/export const APP_VERSION = \"$VERSION\"/" shared/constants.js
echo " ✓ shared/constants.js -> $VERSION"
# 3. package.json
@@ -78,13 +105,26 @@ jobs:
jq -n --arg v "$VERSION" --arg d "$DATE" '{version: $v, date: $d}' > website/version.json
echo " ✓ website/version.json -> version $VERSION, date $DATE"
# 5. website/template.html — SoftwareApplication schema
sed -i "s/\"softwareVersion\": \".*\"/\"softwareVersion\": \"$VERSION\"/" website/template.html
echo " ✓ website/template.html -> softwareVersion $VERSION"
# 6. README.md — version badge & banner
sed -i "s|Release-v[0-9]\+\.[0-9]\+\.[0-9]\+-blue|Release-v$VERSION-blue|g" README.md
sed -i "s/New v[0-9]\+\.[0-9]\+\.[0-9]\+ Release/New v$VERSION Release/g" README.md
echo " ✓ README.md -> v$VERSION"
# 7. website/sitemap.xml — lastmod dates
sed -i "s/<lastmod>[0-9-]*<\/lastmod>/<lastmod>$(date +%Y-%m-%d)<\/lastmod>/g" website/sitemap.xml
echo " ✓ website/sitemap.xml -> lastmod $(date +%Y-%m-%d)"
echo "Version injection complete."
- name: Commit and push version updates back to main
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add extension/manifest.base.json shared/constants.js package.json website/version.json
git add extension/manifest.base.json shared/constants.js package.json website/version.json website/template.html README.md website/sitemap.xml
git commit -m "chore(release): update versions to $GITHUB_REF_NAME [skip ci]" || echo "No changes to commit"
git push origin HEAD:main
env:
@@ -92,11 +132,26 @@ jobs:
- name: Build Extensions
run: |
npm install
npm ci
npm run build:extension
- name: Generate artifact attestation for extensions
uses: actions/attest@v4
with:
subject-path: dist/koalasync-*.zip
- name: Build Website
run: node website/build.cjs
- name: Upload Website Artifacts
uses: actions/upload-artifact@v7
with:
name: website-www
path: website/www/
if-no-files-found: error
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v3
with:
files: |
dist/koalasync-chrome.zip
+4
View File
@@ -27,6 +27,7 @@ Thumbs.db
# IDEs
.vscode/
.idea/
.claude/
*.swp
*.swo
@@ -40,5 +41,8 @@ coverage/
# the root 'shared/' remains the Single Source of Truth.
extension/shared/
# Auto-generated website build output
website/www/
# Temporary scratch files
scratch/
+131
View File
@@ -0,0 +1,131 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Project maintainers are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official email address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the project maintainer at **koaladev@koalamail.rocks**.
All complaints will be reviewed and investigated promptly and fairly.
All project maintainers are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Project maintainers will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from project maintainers, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
+154 -44
View File
@@ -1,58 +1,168 @@
# Contributing to KoalaSync
Thank you for your interest in contributing to KoalaSync! We welcome all contributions, from bug reports to new features.
Thanks for your interest in improving KoalaSync. All contributions are welcome — from bug reports and translations to core protocol changes.
## Development Workflow
Please note that by participating in this project, you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md).
### 1. Prerequisites
- Node.js (v18+)
- Docker (for local server testing)
---
### 2. Setup
1. Clone the repository.
2. Run `npm install` in the root directory to install build dependencies.
3. Run the build script to synchronize protocol constants and generate browser bundles:
```bash
node scripts/build-extension.js
```
## Ways to Contribute
### 3. Testing Locally
1. Load `dist/chrome/` as an "Unpacked Extension" in Chrome (`chrome://extensions/` → Developer Mode → Load Unpacked).
2. For Firefox, load `dist/firefox/` via `about:debugging` → "Load Temporary Add-on".
3. Start the relay server: `docker-compose up --build`.
4. Use **two different browser profiles** (or Chrome + Firefox) to test multi-peer synchronization.
5. Use the extension's **Dev tab** to verify real-time video element metadata (`readyState`, `currentTime`, `paused`).
| Area | Description |
|------|-------------|
| **Bug Reports** | Found a bug? Open an issue with repro steps (see template below). |
| **Code** | Fix bugs, add features, or improve the extension / server / website. |
| **Translations** | Help localize the extension and website into more languages. See [TRANSLATION.md](docs/TRANSLATION.md). |
| **Documentation** | Improve docs, fix typos, or add missing examples. |
| **Security** | Found a vulnerability? See [SECURITY.md](SECURITY.md) — do NOT open a public issue. |
### 4. Protocol Synchronization
KoalaSync uses a "Single Source of Truth" for protocol constants in `shared/constants.js`.
- **CRITICAL**: If you modify the constants, you MUST run the build script:
```bash
node scripts/build-extension.js
```
This will automatically synchronize the changes to the extension and generate the browser-specific bundles in the `dist/` folder.
---
### 5. Code Standards
- **Vanilla JS**: The extension must remain dependency-free. Do not add npm packages to the `extension/` directory.
- **Privacy**: Do not add external requests (CDNs, fonts, analytics, etc.).
- **Comments**: Maintain the existing documentation style, especially for complex sync logic.
- **Room IDs**: Room IDs are restricted to `[a-zA-Z0-9-]` (alphanumeric + hyphens only). Ensure any UI that generates room IDs follows this constraint.
## Development Setup
### Prerequisites
- **Node.js** v18+
- **Docker** (for local relay server testing)
### Quick Start
```bash
git clone https://github.com/Shik3i/KoalaSync.git
cd KoalaSync
npm install
node scripts/build-extension.cjs
```
---
## Project Structure
| Directory | Purpose |
|-----------|---------|
| `extension/` | Browser extension (Manifest V3, Chrome & Firefox) |
| `server/` | Node.js + Socket.IO relay server (Dockerized) |
| `website/` | Landing page, invitation bridge, and marketing site |
| `shared/` | Protocol constants — single source of truth |
| `scripts/` | Build and sync utilities |
| `docs/` | Architecture, sync protocol, and deep-dive guides |
---
## Testing Locally
### Extension
1. Load `dist/chrome/` as an unpacked extension in Chrome (`chrome://extensions/` → Developer Mode → **Load unpacked**).
2. For Firefox: load `dist/firefox/` via `about:debugging`**Load Temporary Add-on**.
3. Start the relay server: `docker compose up --build`.
4. Use **two different browser profiles** (or Chrome + Firefox) to test multi-peer sync.
5. Use the extension's **Dev tab** to inspect real-time video element state (`readyState`, `currentTime`, `paused`).
### Website
```bash
node website/build.cjs # Compile static site → www/
python3 -m http.server 8080 -d website/www # Serve locally
```
Then open `http://localhost:8080`. For multi-language testing: `http://localhost:8080/de/`.
---
## Protocol Constants
KoalaSync uses a **single source of truth** for all protocol constants in `shared/constants.js`.
### 6. Version Numbers
> [!IMPORTANT]
> **Do NOT manually bump version numbers.** The CI pipeline automatically injects the version from the git tag into `manifest.base.json`, `shared/constants.js`, and `package.json` during release builds. Manually changing version numbers in a PR will cause conflicts.
> After modifying `shared/constants.js`, you **must** run the build script to sync changes to the extension:
> ```bash
> node scripts/build-extension.cjs
> ```
> This automatically injects constants into `content.js` and regenerates browser bundles in `dist/`.
## Pull Request Process
1. Create a new branch for your feature or bugfix.
2. Ensure your code is tested locally (Chrome and Firefox).
3. Update relevant documentation (e.g., `docs/ARCHITECTURE.md` if you change the protocol).
4. Submit your PR with a clear description of the changes.
---
## Bug Reports
When reporting a bug, please include:
- **Browser**: Chrome / Firefox / Edge + version number.
- **Extension Version**: Visible in the popup's Dev tab.
- **Dev Tab Output**: Copy the connection status, logs, and video debug info from the Dev tab.
- **Steps to Reproduce**: A clear sequence of actions that triggers the issue.
## Code Standards
- **Vanilla JS**: The extension must remain dependency-free. No npm packages in `extension/`.
- **Privacy-first**: Zero external requests — no CDNs, fonts, analytics, or trackers. All assets self-hosted.
- **System font stack**: `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, ...` — never `@import` external fonts.
- **Room IDs**: Restricted to `[a-zA-Z0-9-]` (alphanumeric + hyphens only). Enforced server-side.
- **Comments**: Document complex sync logic. The codebase uses inline comments for protocol reasoning.
---
## Version Numbers
> [!CAUTION]
> **Never manually bump version numbers.** The CI pipeline injects the version from the git tag into `manifest.base.json`, `shared/constants.js`, and `package.json` during release builds. Manual bumps cause conflicts.
---
## Open Source Workflow & Pull Requests
If you are new to open-source contributions, follow these steps to propose your changes:
1. **Fork the Repository**: Click the "Fork" button at the top right of this repository to create your own copy of KoalaSync.
2. **Clone your Fork**: `git clone https://github.com/YOUR-USERNAME/KoalaSync.git`
3. **Create a Branch**: `git checkout -b my-new-feature` (e.g. `feature/dark-mode` or `fix/translation-de`)
4. **Make your Changes**: Edit the files, then verify them locally.
- *Extension/Server changes*: Test on Chrome/Firefox and check `npm run lint`.
- *Website/Translation changes*: Run `node website/build.cjs` and check the output in `www/`.
5. **Commit and Push**: `git commit -m "Add my feature"` and `git push origin my-new-feature`
6. **Open a Pull Request (PR)**: Go to the original KoalaSync repository on GitHub and click "New Pull Request".
### PR Code Requirements
- **Lint**: Ensure `npm run lint` passes with zero errors and warnings.
- **Syntax**: Run `node -c` on every modified `.js` file.
- **Protocol changes**: Update relevant documentation in `docs/`.
---
## Bug Report Template
When filing a bug, the easiest way is to use the **Copy Logs** button in the extension's **Status** tab. It copies a fully formatted Markdown report to your clipboard containing:
- System info (version, protocol, peer ID, browser)
- Connection status (server, room, peers, reconnect state)
- Video debug info (playback state, readyState, network state, dimensions, error codes, shadow DOM detection, platform)
- Action history (last 20 events)
- Log entries (last 50)
Simply paste the clipboard contents into your GitHub issue and add:
| Field | Example |
|-------|---------|
| **Steps to Reproduce** | 1. Create room → 2. Join from second browser → 3. Play video |
| **Expected Behavior** | Both peers play simultaneously |
| **Actual Behavior** | Peer B remains paused |
If you cannot access the Status tab, include as much of the following manually:
| Field | Example |
|-------|---------|
| **Browser** | Chrome 125, Firefox 128 |
| **Extension Version** | v1.9.3 (visible at bottom of Settings tab) |
| **Website/Platform** | Netflix, YouTube, Twitch, Jellyfin, etc.
---
## Translation Contributions (Translators Welcome!)
We welcome native speakers to help translate KoalaSync! You **do not** need deep programming knowledge to contribute translations.
KoalaSync supports multiple languages. To add or improve translations:
1. Read the **[Translation Guide](docs/TRANSLATION.md)** first. It explains how our localization system works.
2. Edit the `.json` files in `website/locales/` (for the website) and `extension/locales/` (for the extension).
3. Test your translations locally by running:
- `node scripts/test-locales.cjs` (for extension)
- `node scripts/test-website-locales.mjs` (for website)
- `node website/build.cjs` (to build the site)
4. Follow the **Open Source Workflow** above (Fork -> Branch -> Edit -> PR) to submit your translations.
---
## Security
If you find a security vulnerability, please do not open a public issue. Instead, refer to our [SECURITY.md](SECURITY.md) for responsible disclosure instructions.
If you discover a security vulnerability, **do not open a public issue**. Report it privately as described in [SECURITY.md](SECURITY.md).
-46
View File
@@ -1,46 +0,0 @@
# KoalaSync - Production Caddy Configuration Example
# Replace domains and paths with your actual setup.
# 1. Marketing Website & Invitation Bridge
sync.koalastuff.net {
root * /var/www/koalasync/website
file_server
encode zstd gzip
# Static Caching for high-performance PageSpeed (1 year with validation)
@static {
file
path *.ico *.css *.js *.png *.svg *.webp
}
header @static Cache-Control "public, max-age=31536000, must-revalidate"
# Security Headers & Content Security Policy (CSP)
header {
# Strict Content Security Policy (restricts scripts and connections to self, forbids frames)
Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; object-src 'none'; frame-ancestors 'none';"
# Prevent FLoC tracking
Permissions-Policy interest-cohort=()
# Security best practices
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy no-referrer-when-downgrade
}
}
# 2. Relay Server (Socket.IO / WebSocket)
syncserver.koalastuff.net {
reverse_proxy localhost:3000 {
# Ensure WebSocket support is explicitly handled if needed
# (Caddy usually handles this automatically)
header_up Host {host}
header_up X-Real-IP {remote_host}
}
# Security Headers for the relay
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy no-referrer
}
}
+53 -10
View File
@@ -1,18 +1,24 @@
<h1 align="center"><img src="extension/icons/icon128.png" width="32" valign="middle"> KoalaSync</h1>
<p align="center">
<img src="website/assets/PlatformJuggler_New.webp" width="280" alt="KoalaSync Mascot">
</p>
<h1 align="center">KoalaSync</h1>
<p align="center">
<a href="https://github.com/Shik3i/KoalaSync/actions/workflows/release.yml"><img src="https://github.com/Shik3i/KoalaSync/actions/workflows/release.yml/badge.svg" alt="Release Status"></a>
<a href="https://github.com/Shik3i/KoalaSync/releases"><img src="https://img.shields.io/github/v/release/Shik3i/KoalaSync" alt="GitHub release"></a>
<a href="LICENSE"><img src="https://img.shields.io/github/license/Shik3i/KoalaSync?color=blue" alt="License"></a>
<a href="https://github.com/Shik3i/KoalaSync/releases"><img src="https://img.shields.io/badge/Release-v2.4.6-blue?logo=github" alt="GitHub release"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue" alt="License"></a>
<a href="https://addons.mozilla.org/de/firefox/addon/koalasync/"><img src="https://img.shields.io/badge/Firefox-Download-orange?logo=firefoxbrowser&logoColor=white" alt="Firefox Add-on"></a>
<a href="https://chromewebstore.google.com/detail/koalasync/obbnmkmlaaddodakcbdljknjpagklifc"><img src="https://img.shields.io/badge/Chrome-Download-blue?logo=googlechrome&logoColor=white" alt="Chrome Extension"></a>
</p>
<p align="center"><i>KoalaSync is a lightweight Browser Extension and Relay Server for synchronized video playback on almost any website with a video element—YouTube, Twitch, Netflix, Emby, Jellyfin, and beyond. Built with a focus on <b>Data Sovereignty</b> and <b>Performance</b>.</i></p>
<p align="center"><a href="docs/CHANGELOG.md"><b>New v2.4.6 Release!</b> — See what's changed</a></p>
### 🌟 Why KoalaSync?
* **🛡️ Security-First**: Volatile RAM-based relay with built-in brute-force protection and zero-persistence architecture.
* **🛡️ Security-First**: Volatile RAM-based relay with built-in brute-force protection and zero-persistence architecture. We keep no logs of your sessions or synchronizations. *We don't track you. We only track our server* (relying on the [aggregated, anonymous, non-personal metrics](https://syncserver.koalastuff.net/health) provided under `/health`).
* **📡 Direct Logic**: Manual Socket.IO wire implementation for reliable synchronization.
* **🛠️ Clean Build**: Dependency-free extension runtime with no library overhead.
* **🌐 Universal**: Works on any website with a `<video>` tag.
@@ -27,6 +33,7 @@
- **Dual Heartbeat Architecture**: Robust session tracking that prevents ghost rooms and stale connections.
- **Efficient Relay**: Minimal overhead WebSocket message forwarding.
- **Seamless Invitations**: Smart links that automatically configure server and room credentials for your friends.
- **Smart Audio Compressor**: Tired of constantly riding the volume? Automatically balance out whispering dialogue and deafening explosions with simple presets, or fully customize the audio to your liking.
---
@@ -47,6 +54,16 @@ The easiest and safest way to install KoalaSync is directly through the official
2. **Invite Friends:** Share the auto-copied invite link. Once they click it, they automatically join.
3. **Pick a Video:** Navigate to the Sync Tab, select the tab playing your video, and grab some popcorn! 🍿
---
### 🌐 Localization & Translations
Both the official KoalaSync website and the **v2.0 Browser Extension** feature full dynamic localization:
- **Available Languages**: Support is included for 15 languages: English (`en`), German (`de`), French (`fr`), Spanish (`es`), Portuguese (Brazil) (`pt-BR`), Russian (`ru`), Italian (`it`), Polish (`pl`), Turkish (`tr`), Dutch (`nl`), Japanese (`ja`), Korean (`ko`), Chinese (Simplified) (`zh`), Ukrainian (`uk`), and European Portuguese (`pt`).
- **Real-Time Extension Localization**: Inside the extension Settings panel, users can swap languages instantly. The entire interface, notifications, Empty States, and onboarding guides re-translate dynamically in real-time.
- **Contributing**: We welcome community translations for both the website and the extension! Please refer directly to the [TRANSLATION.md](docs/TRANSLATION.md) guide for step-by-step instructions on how to audit, refine, or add new languages.
---
### 🛠️ For Developers & Self-Hosters
@@ -64,7 +81,7 @@ The easiest and safest way to install KoalaSync is directly through the official
To build the extension from source and synchronize protocol constants:
```bash
npm install
node scripts/build-extension.js
npm run build:extension
```
The compiled artifacts will be available in the `dist/` directory.
@@ -75,28 +92,54 @@ Deploy your own private relay server using our official image:
docker pull ghcr.io/shik3i/koalasync:latest
# Or use our example compose file
cp docker-compose.example.yml docker-compose.yml
cp examples/docker-compose.caddy.example.yml docker-compose.yml
docker-compose up -d
```
The server will be available at `ws://localhost:3000`. See [docker-compose.example.yml](docker-compose.example.yml) for advanced configuration.
The server will be available at `ws://localhost:3000`. See [Docker network compose](examples/docker-compose.caddy.example.yml) or [Static IP compose](examples/docker-compose.ip.example.yml) for ready-to-use Docker Compose files.
To connect your extension to a self-hosted server, open the popup → **Room** tab → select **Custom Server** → enter your server's WebSocket URL (e.g., `ws://localhost:3000`).
> **⚠️ Note**: `ws://` only works for `localhost`. If you deploy to a real domain, you **must** use `wss://` (e.g., `wss://sync.yourdomain.com`). This requires a TLS-terminating reverse proxy (e.g., Caddy, Nginx, or Traefik) in front of the relay server. See [Caddyfile.example](Caddyfile.example) for a production-ready template.
> **⚠️ Note**: `ws://` only works for `localhost`. If you deploy to a real domain, you **must** use `wss://` (e.g., `wss://sync.yourdomain.com`). This requires a TLS-terminating reverse proxy (e.g., Caddy, Nginx, or Traefik) in front of the relay server. See [Caddyfile.example](examples/Caddyfile.example) for a production-ready template.
To verify your relay is reachable from outside, visit `https://your-domain.com` in a browser — it should return `{"status":"online","service":"KoalaSync Relay"}`.
#### Supply Chain Security (v2.2.2+)
All official release artifacts (Docker images and extension binaries) are published with signed [artifact attestations](https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations) to prove they were built from this repository's source code.
**Verify a Docker image:**
```bash
gh attestation verify oci://ghcr.io/shik3i/koalasync:latest \
-R Shik3i/KoalaSync
```
**Verify an extension binary:**
```bash
gh attestation verify dist/koalasync-chrome.zip \
-R Shik3i/KoalaSync
```
---
### 📖 Documentation & Links
- **[PRIVACY.md](PRIVACY.md)**: Data Handling and Privacy Policy.
- **[CHANGELOG.md](docs/CHANGELOG.md)**: Full version history for the extension and relay server.
- **[TESTED_SERVICES.md](docs/TESTED_SERVICES.md)**: Detailed compatibility matrix of tested streaming platforms and known limitations.
- **[TRANSLATION.md](docs/TRANSLATION.md)**: Translation and localization guide for contributors.
- **[PRIVACY.md](docs/PRIVACY.md)**: Data Handling and Privacy Policy.
- **[CONTRIBUTING.md](CONTRIBUTING.md)**: How to help make KoalaSync better.
- **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)**: Our community standards and expectations.
- **[HOW_IT_WORKS.md](docs/HOW_IT_WORKS.md)**: Step-by-step walkthrough of the complete user flow.
- **[ARCHITECTURE.md](docs/ARCHITECTURE.md)**: Deep-dive into the two-phase sync and heartbeat logic.
- **[ROADMAP.md](docs/ROADMAP.md)**: Planned features, backlog, and rejected ideas.
- **[SECURITY.md](SECURITY.md)**: Disclosure policy and security practices.
- **[Caddyfile.example](Caddyfile.example)**: Production Caddy configuration for website and relay.
- **[Caddyfile.example](examples/Caddyfile.example)**: Production Caddy configuration for website and relay.
---
<div align="center">
<sub><a href="https://support.koalastuff.net"><img src="https://img.shields.io/badge/Support-KoalaSync-FF5E5B" alt="Support KoalaSync"></a></sub>
<sub><a href="https://gitgem.org/github/Shik3i/KoalaSync"><img src="https://gitgem.org/api/badge/github/Shik3i/KoalaSync.svg" alt="GitGem Badge" /></a></sub>
<sub>Built with ❤️ by <a href="https://github.com/Shik3i">Shik3i</a>. KoalaSync is Open Source under the <a href="LICENSE">MIT License</a>.</sub>
</div>
+92 -13
View File
@@ -1,23 +1,102 @@
# Security Policy
KoalaSync is built on a **zero-persistence, privacy-first** architecture. We take security seriously and appreciate responsible disclosure of vulnerabilities.
---
## Supported Versions
We take the security of our users and their data very seriously. We actively support and patch the latest stable releases of KoalaSync.
Only the latest stable release receives security patches.
| Version | Supported |
| -------------- | ------------------ |
| Latest Release | :white_check_mark: |
| Older Versions | :x: |
| Version | Supported |
|---------|-----------|
| Latest release | :white_check_mark: Active |
| Older versions | :x: Unsupported |
Users on older versions are encouraged to update. The server enforces a minimum client version via `MIN_VERSION`.
---
## Scope
The following components are within scope for security reports:
| Component | Examples |
|-----------|----------|
| **Relay Server** (`server/`) | Authentication bypass, rate-limit evasion, room hijacking, DoS vectors |
| **Browser Extension** (`extension/`) | XSS via content scripts, privilege escalation, data exfiltration, tab snooping |
| **WebSocket Protocol** | Message injection, replay attacks, man-in-the-middle (WSS bypass) |
| **Website** (`website/`) | XSS, CSP bypass, invitation-hash leaks |
### Out of Scope
- Theoretical attacks requiring physical device access
- Social engineering or phishing
- Denial of service via resource exhaustion on self-hosted instances
- Vulnerabilities in third-party browser extensions or websites
---
## Reporting a Vulnerability
If you discover a security vulnerability within KoalaSync (e.g., related to the Node.js relay server, WebSocket wire protocol, or the Chrome/Firefox browser extension), please **DO NOT** report it by creating a public GitHub issue.
> [!CAUTION]
> **Do NOT open a public GitHub issue for security vulnerabilities.** Public disclosure before a patch is available puts users at risk.
Publicly disclosing a vulnerability before a patch is available puts our users at risk. Instead, please send an email privately to the project administrator at:
**koalasync_admin@koalamail.rocks**
Instead, email the project maintainer privately:
### What to expect
1. **Acknowledgment**: You should receive an acknowledgment of your report within 48 hours.
2. **Investigation**: We will investigate the issue, confirm its severity, and work on a patch.
3. **Resolution**: We will notify you when the patch is deployed to the Chrome Web Store, Mozilla Add-on Store, and our GitHub Docker releases.
4. **Disclosure**: Once the fix is confirmed and users have had time to update, we will publicly acknowledge your contribution in our release notes (unless you prefer to remain anonymous).
**`koalasync_admin@koalamail.rocks`**
Encrypt sensitive findings with our PGP key (available on request).
### What to Include
- **Affected component**: Server / Extension / Website / Protocol
- **Steps to reproduce**: Clear, minimal steps to trigger the vulnerability
- **Impact**: What an attacker could achieve (data access, privilege escalation, etc.)
- **Environment**: Browser version, extension version, server configuration
- **Suggested fix** (optional): If you have ideas for a patch
### What to Expect
| Timeline | Action |
|----------|--------|
| **Within 48 hours** | Acknowledgment of your report |
| **Within 7 days** | Initial assessment and severity confirmation |
| **As needed** | Collaborative discussion for clarification |
| **After patch** | Notification that the fix is deployed |
| **After rollout** | Public acknowledgment in release notes (or anonymity if preferred) |
---
## Architecture & Threat Model
KoalaSync's security is grounded in its architecture:
- **RAM-only relay**: No database, no persistent logs. All session data evaporates on disconnect.
- **Keyed SHA-256 room password hashes**: Plaintext passwords are never stored. Room passwords are held only as in-memory HMAC-SHA256 hashes for the short room lifetime, with brute-force protection: 5 attempts → 15-minute IP lockout.
- **Rate limiting**: Connection rate (IP-based, 60s window), health endpoint rate (10 requests/minute/IP), wrong admin-metrics bearer attempts (5 requests/minute/IP), and event rate (per-socket, 10s window). Health-style JSON responses are cached server-side for 60 seconds and refreshed lazily on request.
- **Reverse proxy boundary**: The relay trusts one proxy hop for client IP detection. In production, keep the Node server reachable only through Caddy or another trusted reverse proxy.
- **URL-hash credential isolation**: Invitation credentials live in the URL fragment (`#join:...`) — never sent to the web server.
- **Strict CSP**: `default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'`.
- **No third-party requests**: Zero CDNs, fonts, analytics, or external scripts.
If you find a way to bypass any of these protections, we want to know about it.
> [!NOTE]
> Some frequently-reported "issues" are **intentional and out of scope** for our
> threat model (ephemeral, account-less rooms of invited participants) — e.g. an
> unauthenticated `peerId` or non-constant-time room-password compare. Before
> reporting, please read **[`docs/KNOWN_LIMITATIONS.md`](docs/KNOWN_LIMITATIONS.md)**.
---
## Responsible Disclosure
We follow the principle of **coordinated vulnerability disclosure**:
1. You report privately.
2. We investigate and develop a patch.
3. We deploy to the Chrome Web Store, Firefox Add-ons, and Docker registry.
4. We credit you publicly (unless you prefer to remain anonymous).
We do not pursue legal action against researchers who act in good faith and follow this disclosure process.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 KiB

+66
View File
@@ -0,0 +1,66 @@
# KoalaSync — Marketing Copy Kit
Ready-to-paste copy for product listings, launch pages, directory submissions, and anywhere else you keep re-typing the same pitch. Three lengths, one consistent message. Pick the one that fits the field limit.
---
## 1. One-Sentence Pitch
> KoalaSync is a privacy-first browser extension that synchronizes video playback across almost any website so you can watch with friends in real time — no accounts, no tracking, and your video never passes through anyone's server but the original site's.
**Shorter alternative** (for tight tagline fields):
> Private, universal watch parties on any website — no accounts, no tracking, no media proxying.
---
## 2. Three-Sentence Overview
> KoalaSync is a lightweight browser extension that keeps you and your friends perfectly in sync on YouTube, Netflix, Twitch, Prime Video, Jellyfin, Emby, and almost any other site with an HTML5 video player — press play once and everyone stays together. It's built privacy-first: no accounts, no telemetry, and the official relay server runs entirely in volatile RAM with zero persistence, so nothing about your sessions is ever stored. Open source under the MIT license and fully self-hostable with a single Docker command, KoalaSync is a transparent watch-party tool that works everywhere and respects your data sovereignty.
---
## 3. Full Description
### Watch together — on any site, on your terms.
Counting down "3, 2, 1, play" over voice chat doesn't scale past two people. KoalaSync fixes that with a tiny browser extension that synchronizes play, pause, and seeking across everyone in the room, on almost any website with a `<video>` element. Create a room, share a link, press play — that's it.
### What makes KoalaSync different
Most watch-party tools fall into one of two traps: they only work on a short allowlist of sites (site-specific extensions that need a separate build for every platform), or they route your video through their own player and servers. KoalaSync was built around three principles that break that mold.
**Universal by design.** If the site has an HTML5 `<video>` element, KoalaSync can usually sync it. YouTube, Netflix, Twitch, Prime Video, Disney+, Jellyfin, Emby, and countless niche sites work out of the box — no per-site integration to wait for, no extension swap when your friends want to switch services.
**Your video never touches our servers.** KoalaSync only relays tiny timing messages — play, pause, seek position, readiness — over a hand-rolled WebSocket protocol. The actual video keeps streaming directly from the original site to each viewer's browser. KoalaSync never proxies, transcodes, uploads, or redistributes a single frame, which also means there is no legal gray zone around redistribution.
**Privacy is the default, not an upgrade.** No accounts, no emails, no telemetry, no analytics, no behavior profiling. The official relay server runs entirely in volatile RAM and keeps zero persistent state — when the room closes, the data is gone. Pick a nickname or let KoalaSync generate one for you and you're in.
### Built for people who actually want to read the code
KoalaSync is MIT-licensed open source, built by a solo developer. Audit it, fork it, change it. The extension is dependency-free with a direct Socket.IO wire implementation — no opaque libraries, no framework bloat, no surprise third-party SDKs. Want full sovereignty? Self-host your own relay with a single Docker command and keep all watch-party coordination traffic inside your own infrastructure. The official public relay is there when you don't care, self-hosting is there when you do.
### Little touches you'll notice
- **Episode Auto-Sync** pauses the room when someone loads the next episode and resumes only when everyone is ready — no spoilers, no one left behind on the previous cliffhanger.
- **Smart Audio Compressor** tames the modern "whisper dialogue, deafening explosion" mix with one click. Three presets or full manual control over threshold, ratio, attack, and release.
- **One-click invite links** auto-configure the server and room for your friends — they just click the link and they're in. No fumbling with server URLs or room IDs.
- **Dual-heartbeat architecture** kills ghost rooms and stale connections before they desync your session.
- **15 languages** fully translated and switchable in real time from the settings panel — English, German, French, Spanish, Portuguese (Brazil + European), Russian, Italian, Polish, Turkish, Dutch, Japanese, Korean, Chinese, Ukrainian.
### Install and start in under a minute
Install KoalaSync from the Chrome Web Store or Firefox Add-ons, click "Create Room," share the invite link, and pick a video. The official relay is ready out of the box — no setup required unless you want to self-host.
- Website: https://sync.koalastuff.net
- GitHub: https://github.com/Shik3i/KoalaSync
---
## Bonus: Taglines (for hero headlines, social bios, meta descriptions)
- Watch together. Anywhere. Privately.
- The watch-party tool that works on every site and tracks none of them.
- Sync play, pause, and seek on any video — no accounts, no logs, no lock-in.
- Self-hostable, open-source watch parties for the post-"3, 2, 1, play" era.
- Press play once. Stay together anywhere.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

+57
View File
@@ -0,0 +1,57 @@
KoalaSync: Private Watch Parties for Emby, Jellyfin, Plex, Netflix & YouTube
Tired of counting down "3, 2, 1, Play" over voice chat? KoalaSync keeps you and your friends perfectly in sync. Whether you are streaming from your own self-hosted media server like Emby, Jellyfin or Plex, or watching on a major platform like Netflix, Prime Video or YouTube — KoalaSync is designed for smooth, browser-based watch parties.
✨ CORE FEATURES
No account required. No tracking. Just create a room, invite your friends, and start watching together.
• Real-Time Video Sync: Play, pause, seek, and watch together with fast synchronized playback across everyone in your room.
• No Account Needed: Create a room and share the invite link. No emails, no passwords, no sign-ups. Pick a nickname or let KoalaSync generate one for you.
• Works Almost Everywhere: If the website uses a standard HTML5 video player, KoalaSync can usually sync it. Perfect for streaming sites, self-hosted media servers, and other websites.
• Smart Binge-Watching: When a new episode loads, KoalaSync automatically pauses the lobby until everyone is ready. No spoilers, no one left behind.
• Smart Audio Compressor: Tired of quiet dialogue and suddenly loud action scenes? Balance whispering, explosions, and music with a single click while you watch.
• One-Click Invites: Send a smart invite link to your friends. When they open it, KoalaSync automatically configures the room so they can join instantly.
• 13 Languages: Enjoy a native experience with a fully translated user interface.
🛡️ PRIVACY & SECURITY
KoalaSync is built for private watch parties without unnecessary data collection.
• No Tracking: Zero analytics, zero telemetry, and absolutely no behavior profiling.
• Anonymous by Design: No accounts needed. Rooms can be joined with a simple nickname.
• Ready Out of the Box: Install KoalaSync and start watching immediately using the official public relay server. No technical setup required.
• RAM-Only Public Server: The official relay server operates entirely in volatile RAM. No databases, no stored watch history, no persistent room data. Room data exists only temporarily and disappears when the room closes.
• Self-Hostable: Want full control? You can run your own private KoalaSync relay server via Docker in seconds. Self-hosting is optional and never required.
🚀 HOW IT WORKS
1. Install KoalaSync.
2. Click "Create Room" to start a private watch party.
3. Share the invite link with your friends.
4. Open your favorite streaming site or media server.
5. Select the active video tab.
6. Press play — everyone stays perfectly in sync.
⚙️ UNDER THE HOOD
KoalaSync is lightweight, transparent, and built with privacy in mind.
• On-Demand Relay: Playback state is synchronized through a custom WebSocket-based relay server. No persistent connection — the relay is only active while you're in a room. No background traffic, no idle connections.
• No Media Streaming: KoalaSync does not stream, proxy, upload, download, or redistribute any video content. Everyone watches from their own browser on the original website.
• Temporary Room State Only: The relay server only coordinates room state such as play, pause, seek position, active target, nickname, and readiness status.
• Docker Self-Hosting: The relay server can be self-hosted with Docker if you prefer to run your own private instance.
• Open Architecture: The project is designed to be inspectable, forkable, and easy to review.
💻 OPEN SOURCE
KoalaSync was built by a solo developer who needed a fast, secure way to watch movies with friends. The code is fully transparent under the MIT license: audit it, fork it, improve it, or self-host your own relay server.
Found a bug or have a feature idea? Open an issue on GitHub. Contributions and code reviews are always welcome.
• Website: https://sync.koalastuff.net
• GitHub: https://github.com/Shik3i/KoalaSync
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

-25
View File
@@ -1,25 +0,0 @@
services:
koala-sync:
image: ghcr.io/shik3i/koalasync:latest
container_name: KoalaSync
restart: always
ports:
- "3000:3000"
environment:
- TZ=Europe/Berlin
- PORT=3000
- MIN_VERSION=1.0.0
- MAX_ROOMS=100
- MAX_PEERS_PER_ROOM=50
# KoalaSync uses in-memory storage for the relay,
# so no persistent database volume is required.
pids_limit: 2048
# Example for custom network (e.g., Unraid/Macvlan)
# networks:
# custom_network:
# ipv4_address: 192.168.1.XXX
# networks:
# custom_network:
# external: true
# name: br0
+32 -9
View File
@@ -5,6 +5,9 @@ Welcome to the KoalaSync project. This file is the primary entry point for any d
> [!IMPORTANT]
> **Privacy & Data Sovereignty**: KoalaSync follows a strict **Zero-External-Requests Policy**: The extension and website must not make requests to any third-party domains (Google Fonts, CDNs, etc.). All assets (fonts, icons, scripts) must be self-hosted or use system defaults.
> - **Font Stack**: Use a modern system font stack (e.g., -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif) to maintain a premium look without external dependencies. Prohibit the use of `@import` or `<link>` for external font services.
>
> [!IMPORTANT]
> **Caveman Communication Protocol**: To conserve context window and tokens, AI agents MUST communicate with the user in **caveman language** (broken, short German/English hybrid caveman style, e.g., "Ich Antigravity. Ich machen Git pull. Code fertig.") at all times. This rule is highly strict and must NOT be broken under any circumstances unless the user explicitly requests to drop it.
---
@@ -18,25 +21,27 @@ KoalaSync is a specialized tool for **synchronized video playback** across multi
- `extension/`: Browser Extension (Chrome & Firefox, Manifest V3). Contains background service worker, content scripts, and popup UI.
- `server/`: Node.js Relay Server using Socket.IO (WebSocket-only).
- `website/`: **Landing Page** & Invitation Bridge (Marketing, Tutorials, and Downloads).
- **`build.cjs`**: Zero-dependency static site compiler. Translates `template.html` + `locales/*.json``www/`. Also minifies CSS/JS automatically.
- **`www/` is auto-generated**: Never edit files in `www/` directly. Always edit source files (`template.html`, `style.css`, `app.js`, `lang-init.js`, `locales/*.json`) and run `node website/build.cjs` to regenerate. CSS/JS are output as `.min.*` files — a built-in cleanup step removes stale artifacts on each build.
- `shared/`: **Single Source of Truth** for protocol constants and event names.
- `scripts/`: Development utilities (e.g., `build-extension.js`).
- `scripts/`: Development utilities (e.g., `build-extension.cjs`).
- `docker-compose.yml`: Root-level orchestration for the relay server.
> [!IMPORTANT]
> **Single Source of Truth**: `shared/constants.js` and `shared/blacklist.js` are the master files. They must be synchronized to the `extension/shared/` directory using `node scripts/build-extension.js`.
> **Single Source of Truth**: `shared/constants.js` and `shared/blacklist.js` are the master files. They must be synchronized to the `extension/shared/` directory using `node scripts/build-extension.cjs`.
> - **Extension Modules** (`background.js`, `popup.js`) import directly from `./shared/constants.js`.
> - **Content Scripts** (`content.js`) use a **marker-injected synchronous copy** of the constants. The build script automatically replaces the marked blocks — no manual mirroring needed.
## 3. Mandatory Reading
Before touching any code, you MUST read the following documents in order:
1. [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) Detailed communication flows, Dual Heartbeat, and two-phase sync protocol.
2. [extension/README.md](extension/README.md) Extension components, tab structure, and loading process.
3. [docs/SYNC_GUIDE.md](docs/SYNC_GUIDE.md) Protocol constants and synchronization requirements.
1. [ARCHITECTURE.md](ARCHITECTURE.md) Detailed communication flows, Dual Heartbeat, and two-phase sync protocol.
2. [extension/README.md](../extension/README.md) Extension components, tab structure, and loading process.
3. [SYNC_GUIDE.md](SYNC_GUIDE.md) Protocol constants and synchronization requirements.
## 4. The "Vanilla JS Mirror" Pattern
To avoid boot-time race conditions in Manifest V3 without a bundler, the following architectural trade-off is enforced:
- **Synchronous Execution**: `content.js` MUST execute synchronously to catch early media events.
- **Automated Injection**: The build script (`node scripts/build-extension.js`) automatically injects `EVENTS` and `HEARTBEAT_INTERVAL` into `content.js` using marker-based replacement (see `scripts/README.md` for marker details).
- **Automated Injection**: The build script (`node scripts/build-extension.cjs`) automatically injects `EVENTS` and `HEARTBEAT_INTERVAL` into `content.js` using marker-based replacement (see `../scripts/README.md` for marker details).
- **Maintenance**: After modifying `shared/constants.js`, simply run the build script. No manual mirroring is required.
## 5. File Responsibility Map
@@ -85,6 +90,7 @@ The following features are critical and must not be removed or fundamentally alt
- **Server Transport**: Restricted to `websocket` only. Polling is disabled.
- **Docker Context**: The Docker build must run from the **Repo Root**.
- **Manifest Settings**: `run_at` must remain `document_idle`, and `all_frames` must remain `false`.
- **Strict Backward & Forward Compatibility (Store Delay Rule)**: Browser extensions are distributed through stores (e.g., Chrome Web Store, Firefox Add-ons) which can take up to 2 weeks to approve updates. Therefore, the server MUST NOT reject older extension clients unless a critical protocol version bump is explicitly authorized, and new extension versions MUST remain fully operational when connected to older servers (e.g., by silently falling back if a new feature is not supported). This is a core architectural requirement.
## 9. Security & Deployment
- **Tokens**: Security tokens are intentionally managed via `shared/constants.js` and server `.env`.
@@ -94,13 +100,23 @@ The following features are critical and must not be removed or fundamentally alt
## 10. Common Workflows
## CRITICAL: Git & Release Rules
- **NEVER** push, commit, tag, or release without explicit user instruction.
- **NEVER** retag or force-push without explicit instruction.
- **NEVER** create tags for documentation-only or README changes.
- **NEVER** run `git push`, `git tag`, `git commit` unless the user says "push", "commit", or "tag".
- Only the user decides when to commit, push, tag, or release.
- Ask before any git write operation.
### ⚠️ Pre-Session Git Sync (MANDATORY)
Before starting any task, committing, or pushing, you **MUST** run `git pull --rebase` to ensure your local branch is up-to-date with `origin/main`. CI pipelines and other agents may push commits concurrently. Skipping this step will cause merge conflicts and rejected pushes.
### Releasing a New Version (CRITICAL WORKFLOW FOR AI AGENTS)
> [!CAUTION]
> **AI AGENTS MUST FOLLOW THIS EXACT SEQUENCE WHEN RELEASING A NEW VERSION OR TAGGING.**
> The CI pipeline automatically injects the version from the git tag into `manifest.base.json`, `shared/constants.js`, and `package.json`. You do NOT need to manually bump version numbers.
>
> **🚫 NO MANUAL VERSION BUMPING**: You MUST **NEVER** manually modify the version strings in `package.json`, `extension/manifest.base.json`, or `website/version.json`. The GitHub Actions CI pipeline automatically extracts the version from the git tag (e.g. `v2.0.5` -> `2.0.5`), injects it into all target files, and commits the updates back to `main` with `[skip ci]`. Manual bumps will cause merge conflicts and build failures.
> - **Website Versioning**: **NEVER** manually modify the version fallback strings in `website/index.html`. The website dynamically fetches the latest version and release date from `website/version.json` at runtime using `website/app.js`. Manual bumps in the HTML file are completely redundant and should be avoided.
1. **MANDATORY SYNTAX & LINT CHECKS**: Before staging, committing, or pushing any changes, you **MUST** run both checks on every modified JavaScript file:
- **Syntax Validation**: Run `node -c` on every single modified JavaScript file (e.g., `node -c extension/background.js` and `node -c extension/content.js`). **NEVER** commit or push code that fails this check.
@@ -121,11 +137,18 @@ Before starting any task, committing, or pushing, you **MUST** run `git pull --r
### Adding a Protocol Event
1. Add the event name to `shared/constants.js`.
2. Run the build script (`node scripts/build-extension.js`).
2. Run the build script (`node scripts/build-extension.cjs`).
3. Implement the handler in `server/index.js` and `background.js`.
### Making Website Changes
1. Edit source files in `website/` (`template.html`, `style.css`, `app.js`, `lang-init.js`, or `locales/*.json`).
2. Run the compiler: `node website/build.cjs`. This generates the multilingual pages in `www/` and minifies CSS/JS.
3. Verify the output: `node --check website/www/app.js && node --check website/www/lang-init.js`.
4. Test locally: `npx serve website/www` or `python3 -m http.server 8080 -d website/www`.
5. Commit both source changes and the updated `www/` output.
### Testing Locally
1. Run the build script: `node scripts/build-extension.js`.
1. Run the build script: `node scripts/build-extension.cjs`.
2. Load `dist/chrome/` as an "Unpacked Extension" in Chrome (or `dist/firefox/` in Firefox).
3. Start the server from the root: `docker-compose up --build`.
4. Use **different browser profiles** or vendors to test multi-peer logic.
+7 -6
View File
@@ -2,9 +2,10 @@
This document describes the communication flows and internal logic of the KoalaSync system.
## 1. Extension Startup & Connection
- **Initialization**: On startup, `background.js` reads settings (Server URL, Username, Last Room) from `chrome.storage.sync`.
- **WebSocket Handshake**:
## 1. Extension Connection (Lazy Connect)
- **Initialization**: On startup, `background.js` reads settings (Server URL, Username, Last Room) from `chrome.storage.sync`. No WebSocket connection is established at this point.
- **On-Demand Connection**: The extension only connects when needed — either the user opens the popup with saved room credentials, or when actively in a room. When not in a room, no connection exists. This improves privacy (IP not exposed while idle) and reduces battery/network usage.
- **WebSocket Handshake (when connecting)**:
1. Background creates a `new WebSocket` to `/socket.io/?EIO=4&transport=websocket&version=1.0.0`.
2. Server performs security checks:
- **IP Rate Limit**: Checks if the IP has exceeded connection limits.
@@ -45,7 +46,7 @@ To maintain a clean room state and eliminate "Ghost Peers":
- **Video Heartbeat (Content)**: Every 15 seconds, `content.js` sends current playback metadata (time, title, state) if a video is found.
- **Server Pruning**: The server runs a "Reaper" every 2 minutes. If a peer has sent **zero** activity (no events and no heartbeats) for 5 minutes, they are forcefully disconnected.
- **Immediate Cleanup**: Rooms are deleted instantly when the last peer leaves or disconnects.
- **Reconnect Strategy**: Aggressive backoff — 500ms base, 1.5x multiplier, capped at 5s. Max 20 attempts before marking as failed. Events are queued during disconnect and flushed after namespace rejoin.
- **Reconnect Strategy (while in room)**: Aggressive backoff — 500ms base, 1.5x multiplier, capped at 5s. Max 20 attempts before marking as failed. Events are queued during disconnect and flushed after namespace rejoin. When not in a room, no reconnection occurs.
> [!CAUTION]
> **Identity Rule**: Differentiate between `peerId` and `socket.id`. Use `socket.id` exclusively for ephemeral transport routing on the server. Use `peerId` exclusively for identity, state management, and room tracking across the stack.
@@ -58,7 +59,7 @@ KoalaSync uses a megaphone routing approach to minimize server logic:
## 7. Security & Stability
- **Service Worker Lifecycle**: Uses `chrome.alarms` (30s interval) to prevent the Manifest V3 service worker from suspending while in an active room. On wake, runtime state is restored from `chrome.storage.session` via `ensureState()`.
- **Reconnect Visualization**: Badge shows "..." (orange) during reconnect. Popup displays "Reconnecting..." with attempt counter.
- **Rate Limiting**: Server-side per-socket and per-IP rate limits to prevent sync-spamming or DoS. Real client IP extracted via `x-forwarded-for` header behind proxies/CDNs.
- **Rate Limiting**: Server-side per-socket and per-IP rate limits to prevent sync-spamming or simple DoS. Public health endpoints are limited to 10 requests/minute/IP and cached server-side for 60 seconds, wrong admin-metrics bearer attempts to 5 requests/minute/IP, and room discovery to one request every 10 seconds per socket. Real client IP is taken from the trusted reverse proxy hop, so the Node port must stay private behind Caddy or another trusted proxy.
- **Room Creation Lock**: Per-room mutex prevents race conditions when multiple peers join a new room simultaneously.
- **CORS**: Allows `chrome-extension://` origins for WebSocket fallback compatibility.
- **Message Buffer**: `maxHttpBufferSize` set to 4KB to accommodate large `JOIN_ROOM` payloads.
@@ -70,5 +71,5 @@ KoalaSync uses a megaphone routing approach to minimize server logic:
To maintain a "Single Source of Truth" across the server and extension without using a bundler:
- **Relay Server & Extension Modules**: `background.js` and `popup.js` import constants directly from `shared/constants.js`.
- **Content Scripts**: To ensure zero-latency execution, `content.js` uses a synchronized copy of `EVENTS` and constants.
- **Automation**: The `node scripts/build-extension.js` script automatically injects these constants into `content.js` during the build process, eliminating the risk of manual mirror mismatch.
- **Automation**: The `npm run build:extension` script automatically injects `EVENTS`, `HEARTBEAT_INTERVAL`, and `episode-utils.js` functions into `content.js` during the build process, eliminating the risk of manual mirror mismatch.
- **Verification**: Any protocol change is automatically propagated across the stack by running the build script.
+348
View File
@@ -0,0 +1,348 @@
# KoalaSync Changelog
All notable changes to the KoalaSync browser extension and relay server.
---
## [v2.5.0] — 2026-06-29
### Added
- **Extension + Relay: Host Control Mode** — Room owners can now switch a room between open playback control and host-controlled playback. In host-only mode, guests stay synchronized but their local play, pause, and seek actions are not rebroadcast to the room.
- **Backward-compatible Host Control rollout** — The extension only shows Host Control when the connected relay supports it, so users on older self-hosted servers do not see controls that cannot work yet.
- **Extension: Clear host and guest states** — The popup shows the current control mode, host status, peer roles, and localized guest guidance so participants understand when playback is controlled by the host.
- **Website: FAQ clarification for streaming access** — The landing page and FAQ structured data now state clearly that KoalaSync does not stream, host, share, or bypass access to video content. Every participant watches locally and needs their own access to services such as Netflix.
### Changed
- **Playback sync now follows the room's control setting** — When Host Control is enabled, only the host can drive room-wide playback changes; guests can still watch in sync without accidentally changing playback for everyone.
---
## [v2.4.6] — 2026-06-23
### Fixed
- **Room and settings are no longer stored in `chrome.storage.sync`** — Room ID, password, and username were being resurrected from synced storage on a fresh install (sync survives an uninstall in the user's Google account), which made the extension silently auto-connect to a dead room and appear permanently connected. `getSettings()` and all settings reads are now local-only, and legacy keys are actively purged from sync on install/update/startup. Only `onboardingComplete` and `dismissedHints` remain in sync.
- **No server traffic while alone in a room** — When you are the only peer, heartbeats, force-sync, and episode auto-sync are now fully suppressed (previously the keepAlive heartbeat, force-sync, and episode lobby were still broadcast to an empty room). The solo state is re-evaluated live on every event — never cached — so the instant another peer joins, syncing resumes immediately, including an instant state push so the newcomer sees your current position without waiting for the next heartbeat.
## [v2.4.4] — 2026-06-23
### Changed
- **Server: Event rate limit raised 30 → 50 per 10s**, and all connection/event/health rate-limit thresholds and windows extracted into named constants.
- **Extension: Reconnect backoff tuned and jittered** — capped at ~8 attempts/60s (under the per-IP connection limit) with ±20% jitter to de-synchronize reconnect herds after a server blip.
- **CI: Added a verification workflow** running lint, tests, audits, and builds on every push/PR; the release build now uses `npm ci`.
### Fixed
- **Extension: Offline event-queue flush is now paced** (small batches instead of one synchronous burst) so a reconnect after a long outage no longer trips the server event limit and gets disconnected on rejoin.
- **Extension: Ping liveness tolerates one missed PONG** — a reconnect is forced only after 2 consecutive misses (~20s) instead of a single 5s timeout, avoiding spurious drops under transient load.
- **Extension: `socket.send()` failures are caught and re-queued** instead of losing the event on a disconnect race.
## [v2.4.3] — 2026-06-19
### Added
- **Two new languages: Ukrainian (`uk`) and Chinese (`zh`, Simplified)** — added across the extension (UI strings + Chrome `_locales`) and the website (localized pages, hreflang/Open Graph/schema tags, language selector), bringing the total to 15 languages.
### Changed
- **Play/pause sync coalescing** — The content script now collapses rapid bursts of native play/pause events (source swaps, ABR/quality switches, ad transitions, page teardown) into a single relayed command: the first event is sent instantly and a short 150ms window absorbs the rest. This cuts redundant relay traffic and stops bursts from tripping the server's per-socket event rate limit.
### Fixed
- **zh/uk translation quality** — Corrected systematic machine-translation word-sense errors in the two new locales (e.g. "Play", "Status", "Leave Room", "Clear", "Open", "peers", and audio compressor terms) and translated the remaining English leftovers.
- **Relay logging** — An `EVENT_ACK` aimed at a peer that already left is now logged quietly instead of as a `[SECURITY]` cross-room event, so genuine cross-room attempts stand out in the logs.
## [v2.4.2] — 2026-06-19
### Changed
- **Extension: Optimized uninstall URL registration** — Extracted registration into a reusable, race-condition-protected `initUninstallURL()` helper. It registers the uninstall feedback URL with browser context on both extension installation/update and browser startup to prevent state loss, without storing or sending an installation token.
## [v2.4.1] — 2026-06-19
### Added
- **Extension: Onboarding tour now has a closing step** — The first-run tour ends on a dedicated "You're all set!" card (the `ONBOARDING_5` copy that already existed in all 13 locales but was never shown). The tour no longer stops abruptly on the username step.
- **Extension: One-click invite from the empty peer list** — The "No peers yet" state now shows a **📋 Invite Link** button that copies the invite link to the clipboard, so users can share it without hunting for the field.
### Changed
- **Extension: Cleaner onboarding welcome** — Step 1 is now a centered welcome card instead of spotlighting the logo title. Added a guard so target-less tour steps center cleanly.
- **Website: Mobile comparison table** — The KoalaSync vs Teleparty table stacks into per-feature cards on phones instead of forcing horizontal scrolling; feature descriptions are shown again on mobile.
### Fixed
- **Extension: Onboarding step counter/progress placeholders** — Static `Step 1 of 3` / 33% fallbacks in `popup.html` corrected to match the actual 5-step tour (`Step 1 of 5` / 20%).
- **Website: Mobile navigation restored** — The header hamburger menu was hidden by a `display:none !important` rule, leaving the nav links unreachable on phones. Re-enabled, with spacing kept comfortable down to ~320px.
- **Website: Hero alignment on mobile** — A fixed-width extension mockup forced the hero grid column wider than the container, shifting all hero content off-center (larger left margin than right). The mockup is now responsive (`width:100%/max-width` + `minmax(0,1fr)` grid track).
- **Website: Reveal-animation fallback** — Added a `<noscript>` style fallback and `IntersectionObserver` feature guards so scroll-revealed content can never stay invisible if JavaScript is disabled or unsupported.
## [v2.4.0] — 2026-06-16
### Added
- **Extension: Lazy WebSocket connection** — The extension no longer maintains a permanent WebSocket connection to the relay server. Instead, the connection is established only when actively in a room or when the popup is opened with a saved room configuration. This improves privacy (IP is not exposed while idle), reduces battery/network usage, and prevents the server from tracking online status of inactive users. Automatic reconnect is guaranteed while in a room — zero behavior change during active sync sessions. See `connectIntent` flag in `background.js`.
- **Extension: Episode title regex unification** — `extractEpisodeId()` had inconsistent regex patterns between `background.js` and `content.js`. The content script correctly matched Crunchyroll-style separators (`S01/E01`) while the service worker's stricter pattern (`[\s\-\.]*`) silently rejected them, causing episode lobby sync failures. Now unified to `[^a-zA-Z0-9]*` via shared `episode-utils.js`.
- **Unit tests: `rate-limiter` and `episode-utils`** — 12 test groups for rate-limit functions and 30+ assertions for episode title parsing, covering all 6 separator types (dash, dot, slash, colon, comma, space). Run automatically via `npm run verify`.
### Changed
- **Server: Rate limiter extracted to `rate-limiter.js`** — 6 rate-limit functions, all rate-limit Maps, and cleanup intervals moved from `index.js` (149 lines). `index.js` now imports via facade pattern with re-exports for backward compatibility.
- **Extension: Episode utilities extracted to `episode-utils.js`** — `extractEpisodeId()` and `sameEpisode()` deduplicated from `background.js` and `content.js`. The shared module is imported as an ES module by the service worker and injected into the content script IIFE by the build script.
- **Build: `"type": "module"` in root `package.json`** — All scripts standardized to ESM (`.mjs`) or explicitly CommonJS (`.cjs`). Eliminated Node.js `MODULE_TYPELESS_PACKAGE_JSON` warnings.
- **Build: 4 CJS scripts renamed to `.cjs`** — `build-extension.js`, `test-content-video-finder.js`, `test-locales.js`, `website/build.js`.
### Fixed
- **Server: npm audit resolved** — `ws` package vulnerability (CVE-2024-37890) fixed. Zero vulnerabilities in production dependencies.
- **Pop-up: Connection status flicker fixed** — Removed hardcoded `disconnected` state on every pop-up open. Status now reflects actual background state from the first frame.
- **Pop-up: Join button timeout improved** — No longer blindly re-enables after 15s. Polls connection status and extends window if still connecting.
- **Pop-up: Validation failure state cleanup** — Custom server URL validation errors now properly reset `isProcessingConnection` and `joinBtnTimeout`.
- **Extension: `WEB_JOIN_REQUEST` channel leak fixed** — Missing `sendResponse()` call when already in the target room.
- **Extension: `LEAVE_ROOM` now clears `roomId` from storage** — Prevents phantom auto-reconnect on browser restart after explicit leave.
- **Extension: Reconnect attempt counters reset on leave** — Prevents stale `reconnecting` status display after intentional disconnect.
## [v2.3.2] — 2026-06-16
### Changed
- **Extension: Refined Spanish, Italian, and Portuguese translations**: Complete manual review and improvement of all Spanish (`es`), Italian (`it`), Portuguese — Brazil (`pt-BR`), and Portuguese — Portugal (`pt`) locale files for both the extension UI and the landing website. Thanks to [@Kaia-Alenia](https://github.com/Kaia-Alenia) for the native-quality translations.
### Fixed
- **Extension: Locale typos and corrupted characters fixed**: Repaired a Korean refresh button label (`Refreschi``새로고침`), a corrupted Korean connection status string (`연kel``연결`), a Korean character contaminating a Japanese string (`의``の`), and a Dutch typo (`cmmuniceren``communiceren`).
- **Server: Admin token length leak fixed (timing side-channel)**: `isAdminMetricsAuthorized()` returned early when the provided buffer had a different length than the expected token, leaking the token length via response timing. Now `crypto.timingSafeEqual` runs in constant time on every attempt regardless of length match. Reported by [@Kaia-Alenia](https://github.com/Kaia-Alenia).
---
## [v2.3.1] — 2026-06-15
### Fixed
- **Server: Concurrent peer join race condition and teardown error handling**
### Changed
- **Server: Smart unhandled rejection handling (exits after 5/min instead of 1)**
- **Server: Optimized admin health metrics allocation**
---
## [v2.3.0] — 2026-06-14
### Added
- **Extension: New Interactive Onboarding Tour**: A fully redesigned, interactive step-by-step onboarding experience.
- **Extension: Auto-Switch to Sync Tab**: The UI now intelligently switches to the Sync tab when you join a room to guide video selection.
- **Extension: Uninstall URL Integration**: Prepared an uninstall URL setup that works natively across Chrome and Firefox, cleanly attaching browser context for analytics.
### Fixed
- **Extension: Infinite Seek Loop Prevention**: Replaced the fragile time-based seek suppression with an exact target-time verification mechanism, entirely eliminating infinite seek loops on slow buffers.
- **Extension: Zombie Connections Resolved**: Implemented a forced disconnect upon ping timeouts, ensuring the extension reliably auto-reconnects when the WebSocket hangs in a half-open state.
- **Extension: Room Switching Architecture**: Joining a new room while already connected now explicitly severs the old connection first, preventing state cross-contamination.
- **Extension: Join/Leave Race Conditions**: Added UI locks to prevent users from accidentally sending conflicting connection commands via rapid double-clicking.
- **Extension: Same-Room Invite Bypass**: Clicking an invite link for the room you are currently in no longer triggers a redundant reconnect, instead instantly confirming the join.
- **Extension: Audio settings now propagate immediately to video tabs**: Changes made in the audio options page are now instantly applied to the active video tab. Previously, settings saved to `chrome.storage.local` were not picked up by the background listener, which only watched `chrome.storage.sync`.
- **Extension: Audio compressor now logs enable/disable state and resume failures**: The compressor reports when it is activated or bypassed, and warns if the `AudioContext` cannot be resumed (e.g. browser autoplay policy requires a user gesture on the page first).
- **Extension: Video heartbeat no longer sent when alone in a room**: The full media metadata `PEER_STATUS` is now only emitted when other peers are present. The session keepalive (background heartbeat) continues to run unaffected, preventing the server reaper from disconnecting idle peers.
- **Server: Increased `failedAuthAttempts` eviction threshold from 50k to 200k**: Reduces frequency of expensive batch evictions under high auth-failure volumes, smoothing heap usage.
---
## [v2.2.4] — 2026-06-10
### Fixed
- **Extension: Error notifications now respect `browserNotifications` setting**: Server error events (e.g. "Server is restarting") no longer trigger a browser notification when the user has disabled notifications in the extension settings.
- **Server: Misleading reconnect message corrected**: The graceful shutdown message no longer tells users to manually reconnect — the extension handles this automatically.
---
## [v2.2.3] — 2026-06-10
### Added
- **Artifact Attestations (Supply Chain Security)**: All release artifacts (Docker images, extension ZIPs) are now published with signed [SLSA provenance attestations](https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations) via `actions/attest@v4`. Anyone can verify that an artifact was built from this repository using `gh attestation verify`.
- **Admin health: `rateLimits.denied` counters**: New rolling counters track actual rate-limit denials (429 responses), separate from `rateLimits.trackedClients` which reports unique IPs in the tracking window.
- **Docker HEALTHCHECK**: Container health is now checked every 30s via `GET /health`.
- **`npm start` script**: Server can now be started with `npm start`.
### Fixed
- **Server: `activeLobby` no longer silently overwritten**: If a second peer sends `EPISODE_LOBBY` while a lobby is already active, the request is now ignored instead of destroying the first peer's lobby.
- **CORS log sanitization**: Rejected origin headers are sanitized (`\r\n` stripped) to prevent log injection.
- **Extension: pagehide resource leak**: `keepAlivePort`, `lobbyPollTimer`, heartbeats, and `MutationObserver` are now properly cleaned up when a tab is hidden or enters bfcache.
- **Extension: unhandled storage rejections**: `chrome.storage.session.set()` calls in the disconnect handler now have `.catch(() => {})`.
- **`'Pixel'` duplicate in name generator**: Second occurrence replaced with `'Nitro'` for better name diversity.
- **`'opposum'` typo**: Corrected to `'opossum'` in the emoji map and added `'Opossum'` to `USERNAME_NOUNS`.
- **Test reliability**: `test-server-routes.mjs` now sets `ADMIN_METRICS_TOKEN` before importing the server module, fixing standalone test execution.
- **`MAX_PEERS_PER_ROOM` konsistent**: `.env` auf 25 gesetzt (wie `.env.example`).
- **pt-BR.json duplicate removed**: Duplicate `FOOTER_DISCLAIMER` key removed from website locale.
### Changed
- **Admin health: `rateLimitEntries` renamed to `rateLimits.trackedClients`**: The field now accurately describes that it tracks unique clients in the rate-limit window, not denial counts. Update your json_exporter/Grafana config accordingly.
- **README restructured**: Sections reordered by progressive technical depth. New "Supply Chain Security" subsection under "For Developers & Self-Hosters" with verification commands.
---
## [v2.2.2] — 2026-06-09
### Added
- **Chrome Web Store i18n Support**: Added `default_locale: "en"` to manifest and created `_locales/*/messages.json` for all 13 supported languages. This unlocks the language selection dropdown in the Chrome Web Store dashboard, allowing translated store listings (title, description) per locale. The extension's own UI translations (`locales/*.json` + `i18n.js`) remain unchanged.
- **Locale test coverage**: Extended `scripts/test-locales.js` to validate all `_locales/*/messages.json` files (correct format, required keys, no duplicates) and verify `default_locale` is set in the manifest.
### Fixed
- **Copy Logs button alignment**: Removed stray `margin-top: 8px` inherited from `.secondary` class that pushed the button 8px down in the connection status row.
---
## [v2.2.1] — 2026-06-09
### Added
- **Server Ping Display**: Measures round-trip latency to the relay server via application-level ping/pong events. The extension sends `PING { t }` every 15 seconds; the server responds with `PONG { t }`. Round-trip time is calculated client-side and displayed in the Status tab, color-coded (<50ms green, 50150ms yellow, >150ms red). No ping value is shown when disconnected or if the server does not respond within 5 seconds.
- **Peer Ping Response (Future-Proof)**: The extension can now respond to incoming `PING { t, sender }` events from other peers by sending back `PONG { t, target: sender }`. The relay server forwards `PING` to the target peer and routes `PONG` back to the original sender. Both client and server validate that peers are in the same room before forwarding/routing. Peer-to-peer ping initiation will be activated in a future extension update without requiring a server restart.
---
## [v2.2.0] — 2026-06-08
### Added
- **Web Audio API Compressor**: Built-in audio dynamic range compression with four presets (Recommended, Dynamic Range, Vocal Enhancement, Smooth) and fully customizable sliders (threshold, ratio, knee, attack, release). Uses dry/wet crossfade (40ms linear ramp) to avoid clicks. Configured via the new Audio Options page accessible from the Settings tab.
- **Audio Options Page** (`audio-options.html`): Dedicated settings page with master toggle, compressor preset selector, real-time custom sliders, and equalizer placeholder. Dark theme matching the popup design.
- **Feature Hint System**: Generic `dismissedHints` array in sync storage for announcing new features. First hint highlights the Audio Options entry in Settings. Extensible for future features.
### Changed
- **Support Links**: Static footer badges on the Settings and Status tabs linking to the developer's support page. README and website footer updated with a Support KoalaSync badge.
### Fixed
- **Portuguese (PT) locale**: Removed Italian contamination — "sincronizzazione" → "sincronização", "tempo reale" → "tempo real", "Link di Invito" → "Link de Convite", "Sair della Sala" → "Sair da Sala".
- **Korean locale**: Fixed broken character in `HOWTO_STEP_2_TEXT` (`클rip보드``클립보드`).
- **Website COMP_FEAT_6_KOALA**: Normalized from inconsistent "6 Languages" to "13 Languages" across all locale files (en, de, es, fr, pt-BR, ru).
- **Debug report showing wrong logs**: Fixed `logs.slice(-50)` and `history.slice(-20)` in the "Copy Debug Report" feature. Since `addLog()` and `addToHistory()` use `unshift` (inserting entries at index 0), the arrays are ordered newest-first. `slice(-N)` took the N **oldest** entries instead of the N **newest**. Changed to `slice(0, N).reverse()` to correctly include the most recent logs and display them chronologically.
---
## [v2.1.2] — 2026-06-06
### Fixed
- **Episode guard regex**: Fixed `isDifferentEpisode()` not detecting episode changes when the MediaSession title uses `Sxx:Exx` format (colon separator, as used by Jellyfin/Emby). The regex character class `[\s\-\.]` was replaced with `[^a-zA-Z0-9]` to match **any** non-alphanumeric separator between season and episode numbers, preventing play/pause/seek commands from a different episode leaking through and incorrectly manipulating a peer's playback.
- **Per-device storage isolation**: Migrated `username`, `roomId`, `password`, `serverUrl`, and `useCustomServer` from `chrome.storage.sync` (synced across Google account) to `chrome.storage.local` (per-device). This prevents the extension from automatically joining the same room with the same name on multiple devices. Existing user data is migrated silently on first run; all preferences (`filterNoise`, `autoSyncNextEpisode`, etc.) remain synced.
### Changed
- Added one-time migration fallback in `getSettings()` and popup `init()` to copy existing user settings from `storage.sync` to `storage.local` on first launch after the update.
---
## [v2.1.0] — 2026-06-04
### Added
- Added full translation support for 7 new languages to both the browser extension popup settings and landing website: Italian (`it`), Polish (`pl`), Turkish (`tr`), Dutch (`nl`), Japanese (`ja`), Korean (`ko`), and European Portuguese (`pt`).
- Implemented robust, centralized browser system language detection mapping `pt-BR` to Brazilian Portuguese and other `pt` locales (like `pt-PT`) automatically to European Portuguese.
- Added flag emojis to language selector dropdowns in both the extension popup and landing/utility web pages for quicker visual identification.
- Added 181 translation keys parity validation suite checks for the new languages.
### Fixed & Hardened (Extension Audit)
- Guarded all website `localStorage` interactions to prevent initialization/join flow script failures on privacy-hardened or cookie-blocked browser configurations.
- Added robust validation null-guards to `chrome.runtime.onMessage` listeners across all extension scripts (`bridge.js`, `content.js`, `background.js`, `popup.js`) to reject unexpected runtime messages.
- Guarded CustomEvent payload destructuring in `bridge.js` to ensure stability when receiving third-party page events.
- Wrapped `video.currentTime` seeking adjustments during forced sync in content scripts with exception handling to absorb uninitialized video state DOMExceptions.
- Added payload validation guards on incoming Socket.IO events within the background script's event handlers to secure against malformed server updates.
- Prevented noisy browser console exceptions from context invalidation in target tabs by catching promise rejections on extension message dispatches.
### Performance
- Implemented in-memory language dictionary caching in the background script to completely avoid redundant extension package filesystem reads during translations.
---
## [v2.0.8] — 2026-06-03
### Fixed
- Fixed a bug where switching language inside the extension popup overwrote dynamic fields (such as active room ID, connection status, active server details, and video debug info) with default localized placeholder texts.
- Fixed a version reporting mismatch where the copied logs (debug reports) and connection handshake parameters incorrectly reported the hardcoded `1.9.0` version instead of the actual installed manifest version.
---
## [v2.0.7] — 2026-06-03
### Added
- Added a `DEBUG_LOGGING` environment variable to the relay server (defaulting to `"0"` / disabled) to prevent console spam from verbose connection (`CONN`), room activity (`ROOM`, `DEDUPE`), and `CORS` events under load. Critical logs like `SERVER`, `SECURITY`, `AUTH`, and `ERROR` remain enabled at all times.
---
## [v2.0.6] — 2026-06-03
### Performance & Security Hardening
- Optimized failed authentication attempts cache eviction algorithm to $O(1)$ by exploiting Javascript `Map` insertion-order properties. This completely removes the previous array copying and sorting bottleneck, neutralizing a potential main-thread blocking DoS vector under heavy brute-force password traffic.
---
## [v2.0.5] — 2026-06-03
### Security & Hardening
- Hardened extension room idle auto-leave detection to correctly recognize when the target tab's video heartbeat goes stale (e.g., after tab navigation or media closure).
- Exported cleaner graceful shutdown and lifecycle methods (`stopServerForTests`) from the relay server to prevent socket leaks and port-binding conflicts during verify checks.
### Added
- Added a validation step in `test-locales.js` to ensure the supported language list in `extension/i18n.js` is perfectly synchronized with the actual JSON translation files in the locales directory.
- Added a robust route verification test suite (`scripts/test-server-routes.mjs`) covering rate limit throttling, caching headers, and admin metrics access control.
---
## [v2.0.4] — 2026-06-03
### Security & Hardening
- Hardened relay health endpoints against simple flood traffic: `GET /` and `GET /health` are now limited to 10 requests per minute per client IP.
- Added lazy 60-second server-side caching for `GET /`, basic `/health`, and admin `/health` JSON responses to reduce repeated health-check work under noisy polling.
- Added stricter brute-force throttling for invalid admin metrics bearer attempts.
- Added startup warning for short `ADMIN_METRICS_TOKEN` values and documented that production Node ports must stay private behind Caddy or another trusted reverse proxy.
- Lowered the default maximum peers per room to 25.
### Added
- Optional privacy-preserving admin metrics on `/health` when `ADMIN_METRICS_TOKEN` is configured and a valid bearer token is supplied. Metrics are aggregate-only and exclude room IDs, peer IDs, usernames, IP addresses, media titles, passwords, and other user-level data.
### Changed
- Removed `bcryptjs`; temporary room passwords continue to use keyed SHA-256/HMAC hashing as documented.
- Public room discovery is now rate-limited server-side to one refresh every 10 seconds per socket, with the extension refresh button locked for 11 seconds.
### Fixed
- Improved Shadow DOM video detection so real embedded players are not hidden by smaller light-DOM preview or placeholder videos.
- Fixed join-button timeout cleanup after join status responses.
---
## [v2.0.2] — 2026-06-02
### Fixed
- Peer identity spoofing in relay server: client-supplied `peerId` could be used to impersonate other peers in PEER_STATUS events. Server now always stamps `peerId` with the authenticated sender's identity.
- Amazon domain detection: replaced broad `includes('amazon.')` substring check with boundary-safe regex that correctly matches all Amazon storefronts (`amazon.com`, `amazon.de`, `amazon.co.uk`, etc.) while rejecting lookalike domains.
---
## [v2.0.1] — 2026-06-01
### Fixed
- Video detection on Prime Video: `findVideo()` now scores all video elements by size, duration, and mute state instead of picking the first one. Fixes 0×0 placeholder being selected over the actual player.
- History entries in debug report showing `?` instead of action names.
- Prime Video status in compatibility matrix updated to reflect partial support.
### Added
- Multi-video overview table in Copy Debug Report when a page has more than one `<video>` element. Shows resolution, mute state, playback state, readyState, duration, and marks the currently targeted video.
---
## [v2.0.0] — 2026-06-01
### 🌍 Multi-Language Extension (Biggest Feature!)
- **6-Language UI**: The browser extension is now fully translated into **English, German, French, Spanish, Portuguese (Brazilian), and Russian**. Switch languages instantly in Settings without reload.
- **Real-Time i18n**: Every label, button, tooltip, toast notification, empty state, and onboarding guide updates dynamically when the language changes.
### New Features
- **Copy Debug Report (Markdown)**: The *Copy Logs* button in the Status tab now copies a fully formatted Markdown debug report — system info, connection status, video diagnostics, action history, and logs. One click, paste into a GitHub issue, all debugging data ready.
- **Platform Auto-Detection**: The Dev tab now identifies streaming platforms (YouTube, Netflix, Twitch, Prime Video, Disney+, HBO Max, Vimeo, Dailymotion) and displays the detected platform.
- **Enhanced Video Debug Info**: 20+ new fields in the Status tab including network state, buffered ranges, dimensions (with 0×0 warning), media error codes, shadow DOM status, seeking/ended/loop flags, volume, playback speed, and data attributes.
- **No-Video Diagnostic Mode**: When no video is found, the Status tab shows platform, page title, video count, shadow DOM presence, and MediaSession data to help troubleshoot.
### Changed
- **New TwoPointZero Branding**: Updated extension icons (16/32/48/96/128px).
- **Larger Popup Logo**: Extension popup icon increased to 48px.
- **Prime Video Unblocked**: Removed `amazon.` from the tab blacklist so Amazon/Prime Video tabs appear in the video selector.
- **Improved Debug Report**: Full User-Agent string for accurate browser identification, UTC timestamp, connection details including server URL and room info.
- **Smart Disconnect**: Improved disconnect handling when leaving rooms.
- **Human-Readable Room IDs**: Expanded word lists for friendlier room names.
- **Custom Server Support**: WEB_JOIN_REQUEST and join button for custom server invite flows.
- **Reconnection Strategy**: Custom server reconnection improvements.
- **Episode-Aware Sync**: Command sequencing with smarter episode transition detection and echo suppression for smoother series binges.
- **Sync Status Refinements**: YouTube and Twitch sync behavior improved.
- **No External Dependencies**: Extension remains dependency-free with no library overhead.
### Fixed
- Hardcoded strings, missing translation keys, and Service Worker notification race conditions.
---
## Versioning Policy
- **MAJOR** (x.0.0): Breaking protocol changes, architecture rewrites, or major feature milestones.
- **MINOR** (0.x.0): New features, significant enhancements, new translations, or UI redesigns.
- **PATCH** (0.0.x): Bug fixes, minor improvements, and documentation updates. PATCH releases may not receive individual changelog entries if bundled with a MINOR release.
+5 -5
View File
@@ -16,9 +16,9 @@ This guide walks through the complete user flow of KoalaSync, from creating a ro
## Step 2: Connecting to the Relay Server
When you open the extension popup, the background service worker connects to the relay server:
When you open the extension popup (with saved room credentials) or when a saved room configuration exists from a previous session, the background service worker connects to the relay server:
1. **WebSocket Handshake**: `background.js` opens a WebSocket to `wss://syncserver.koalastuff.net/socket.io/?EIO=4&transport=websocket`.
1. **WebSocket Handshake** (on demand): `background.js` opens a WebSocket to `wss://syncserver.koalastuff.net/socket.io/?EIO=4&transport=websocket` only when needed (popup opened or active room).
2. **Security Checks** (server-side):
- The server checks the client's **IP rate limit** (max 10 connections per 60 seconds).
- The server validates the **authentication token** (hardcoded in `shared/constants.js`) to verify this is a legitimate KoalaSync client.
@@ -51,7 +51,7 @@ Click **"Create Room"** in the popup's Room tab:
4. **Server-side processing**:
- All fields are **sanitized**: `roomId` is stripped of invalid characters and clamped to 64 chars; `peerId` clamped to 16 chars; `password` clamped to 128 chars; `username` clamped to 30 chars.
- The server **hashes the password** with bcrypt and stores the hash in RAM (the plaintext is never stored).
- The server **hashes the password** with a keyed SHA-256 HMAC and stores only that hash in RAM (the plaintext is never stored).
- A new room object is created in memory with the peer's data.
- The server responds with `ROOM_DATA` containing the list of peers in the room.
@@ -89,7 +89,7 @@ When your friend opens the link in their browser:
- After 500ms, the page dispatches a `KOALASYNC_JOIN_REQUEST` custom DOM event with `{ roomId, password, useCustomServer, serverUrl }`.
- `bridge.js` catches this event and forwards it to `background.js` via `chrome.runtime.sendMessage`.
- `background.js` stores the credentials in `chrome.storage.sync` and emits `JOIN_ROOM` to the server.
- The server validates the password against the stored bcrypt hash.
- The server validates the password against the stored keyed SHA-256 HMAC hash.
- On success, the server responds with `ROOM_DATA` and broadcasts `PEER_STATUS { status: 'joined' }` to all existing peers.
- The join page updates to show "✅ Successfully joined!".
@@ -155,7 +155,7 @@ While in a room, two heartbeats keep the session alive:
| Heartbeat | Interval | Source | Purpose |
|:----------|:---------|:-------|:--------|
| **Background** | 30 seconds | `background.js` | Signals "I'm still connected" and triggers aggressive reconnect (500ms base, max 5s) |
| **Background** | 30 seconds | `background.js` | While connected, signals "I'm still connected" and triggers automatic reconnect (500ms base, max 5s). No heartbeats fire when idle (lazy connect). |
| **Content** | 15 seconds | `content.js` | Sends video metadata: `currentTime`, `mediaTitle`, `playbackState`, `volume`, `muted` |
- **Server Reaper**: Every 2 minutes, the server checks for peers with no activity for 5+ minutes and disconnects them ("dead peer pruning").
+103
View File
@@ -0,0 +1,103 @@
# Known Limitations & Accepted Non-Issues (NOFIX)
This file records design decisions that are **intentional** and **out of scope** for
KoalaSync's threat model. They are frequently re-discovered by reviewers, linters, and
AI agents and re-reported as "bugs" or "vulnerabilities." They are not. Please read the
threat model below before flagging any of them.
> **For reviewers & agents:** Do **not** re-report the NOFIX items below. They are
> deliberate. If you think the *threat model itself* should change (e.g. KoalaSync
> starts handling accounts, payments, or sensitive data), raise **that** discussion
> first — these items only stand because the threat model holds.
---
## Threat model
KoalaSync is a **privacy-preserving watch-party sync tool**, not a security product.
- **Rooms are ephemeral.** They exist for a few hours and are auto-reaped. There are no
accounts, no persistent storage, no money, and no sensitive content on the relay.
- **The relay is a dumb, stateless message bus.** It forwards play/pause/seek between
peers who *chose* to watch together and joined via an invite link shared out-of-band.
- **Participants are invited.** Anyone in a room was let in. The social contract is
"we're watching a video together," not "mutually distrusting parties."
### What we DO defend against
- **Accidental disruption** — the entire point of Host Control Mode.
- **Spam / DoS** that degrades the relay for everyone — rate limits, 4 KB payload cap,
server-side gating, lazy-cached health responses.
- **Resource exhaustion / memory leaks** — bounded maps, periodic cleanup, room/peer reaping.
- **Crashes from malformed input** — strict sanitization and clamping of every field.
- **Genuine boundary breaches** — admin-metrics auth (constant-time), CORS, WSS upgrade,
invite-hash isolation, strict CSP. Reports here are very welcome (see `SECURITY.md`).
### What we explicitly DO NOT defend against
A **determined participant who modifies their own client to misbehave inside a room they
were invited to.** The worst they achieve is sending playback commands or seizing the
"host" role in a temporary room they could already disrupt by other means. That is a
**social** problem, solved socially: kick them, or start a new room. Engineering real
identity/auth to prevent it would destroy the account-less, frictionless, privacy-first
design — a bad trade for an ad-hoc movie night.
---
## NOFIX entries
### NOFIX-1 — `peerId` is unauthenticated; a crafted client can impersonate or seize the host
**Flag:** `peerId` is client-asserted and broadcast to every peer (in `ROOM_DATA` /
`PEER_STATUS`). A modified client can join with the host's `peerId`, dedupe-kick the real
host, and become host — controlling or locking `host-only` mode.
**Why NOFIX:** Requires a *modified client* + an *invited* participant + a `peerId` that is
only meaningful inside that one *temporary* room. The payoff is sending play/pause or
locking a room the attacker is already in — pure trolling, instantly reversible (kick /
new room). Cryptographic per-user identity is wildly disproportionate for an ad-hoc,
account-less, ephemeral watch party. **Out of threat model.**
Do **not** "fix" with accounts, signed peerIds, or per-user tokens — that breaks the
core design.
### NOFIX-2 — Room-password comparison is not constant-time
**Flag:** room password hashes are compared with `!==` (`server/index.js`), so the compare
is theoretically timing-attackable.
**Why NOFIX:** The compared value is an **HMAC-SHA256 hash that never leaves the server**
an attacker cannot observe it to mount a timing attack. Even a hypothetical success only
lets someone join a *temporary* room to send playback commands. Not worth defending.
(The admin-metrics bearer token — a real boundary — **does** use `crypto.timingSafeEqual`.
That is the line we actually guard.)
### NOFIX-3 — `OFFICIAL_SERVER_TOKEN` is public in the repo
**Flag:** the connection token in `shared/constants.js` is committed, so anyone can connect.
**Why NOFIX:** It is a **coarse filter** to keep random scanners off the relay, **not
authentication**. The relay is a public message bus by design; rate limits and per-room
behavior are the real protections.
### NOFIX-4 — Room IDs are enumerable via `GET_ROOMS`
**Flag:** any connected client can list all room IDs (and whether each has a password).
**Why NOFIX:** This is the intended **"Public Rooms"** feature. Rooms wanting privacy set a
password; listing the IDs of password-less rooms only lets someone join a watch party —
the same as being handed the invite link.
### NOFIX-5 — A pause/seek can only be reverted, not prevented
**Flag:** in `host-only` mode a guest's pause still fires locally before the extension can
react, so there is a brief flicker before snap-back.
**Why NOFIX:** A content script cannot intercept a `<video>` event before the element
acts. Reacting (snap-back) is the only option and is by design; the ~½s flicker is
acceptable. Not a bug.
---
## Not NOFIX — just deferred (may be revisited)
These are *not* accepted-forever; they are scoped out of v1 and tracked separately
(see the host-control-mode design docs in `docs/`):
- **Host grace on a long disconnect (EC-10).** A brief reconnect/second-tab keeps the host
(handled), but a long real disconnect still falls back to `everyone`. A ~30s host-reserve
grace could be added later.
- **Intent-classifier / snap-back tuning.** Thresholds are first-pass; real-device testing
may adjust them.
+4 -2
View File
@@ -2,12 +2,14 @@
**KoalaSync does not collect, store, or sell any personal data.**
*We don't track you. We only track our server* (relying exclusively on aggregated, anonymous, and non-personal system metrics to monitor performance and stability).
KoalaSync is designed with a **Security-First & Volatile** architecture. This means we prioritize keeping your data out of persistent storage, though certain technical data must be processed temporarily to ensure service stability and security.
## 1. Data Processing (In-Memory Only)
KoalaSync does not use a database. All active session data exists only in the server's RAM and is purged immediately when no longer needed.
- **Session Data**: To synchronize playback, the server must temporarily hold your `peerId`, `username`, and the `title` of the video you are watching. Additionally, playback metadata (`mediaTitle`, `playbackState`, `currentTime`, `volume`, `muted`) is held per peer for the duration of the session. All of this is deleted as soon as you leave the room.
- **Room Passwords**: If you set a room password, it is stored only as a secure **bcrypt hash** in RAM. The server never sees or stores your plaintext password.
- **Room Passwords**: If you set a room password, it is stored only as an in-memory **keyed SHA-256 HMAC hash**. The server receives the plaintext password only during join validation, never stores it, and keeps only the hash for the short room lifetime.
- **Routing Maps**: The server maintains ephemeral lookup tables (`socketToRoom`, `peerToSocket`) to route messages between peers. These contain only transport identifiers and are purged on disconnect.
### Data Retention
@@ -31,7 +33,7 @@ The browser extension requires the following permissions:
- `storage`: To remember your local preferences (username, server URL, room settings).
- `tabs` & `scripting`: To detect and control video elements on the pages you choose to sync.
- `<all_urls>` (host permission): Required to detect `<video>` elements on any website the user chooses to synchronize. The extension only activates on the specific tab the user has actively selected — it does not scan, monitor, or interact with any other tabs or pages.
- `alarms`: To keep the background service worker alive during active sync sessions.
- `alarms`: To keep the background service worker alive during active sync sessions (the extension only stays connected while you are in a room).
- `notifications`: To display sync status updates (e.g., "Peer joined", "Force Sync initiated").
- **No History Access**: We do not read, store, or transmit your browsing history. We only interact with the specific tab you have actively selected for synchronization.
+1
View File
@@ -5,3 +5,4 @@ This directory contains deep-dives into the KoalaSync protocol and architecture.
- [HOW_IT_WORKS.md](HOW_IT_WORKS.md): Step-by-step walkthrough of every user flow, from room creation to synchronized playback. Ideal for store reviewers and manual testers.
- [ARCHITECTURE.md](ARCHITECTURE.md): Communication flows, Dual Heartbeat, and Sync logic.
- [SYNC_GUIDE.md](SYNC_GUIDE.md): Protocol constants and sync requirements.
- [TRANSLATION.md](TRANSLATION.md): Translation and localization guide for the extension and website.
+72 -16
View File
@@ -1,26 +1,82 @@
# KoalaSync Roadmap
This document tracks planned features, improvements, and their implementation details.
> Feature priorities, planned work, backlog, and rejected ideas for KoalaSync.
---
## Offene technische Fragen
## Status Legend
### 1. Service Worker Fallback bei Room-State Verlust
Manifest V3 suspendiert den Service Worker nach ~30s Inaktivität. `chrome.alarms` weckt ihn auf, aber:
- **Problem:** Wenn der SW neu startet, sind alle Variablen (`currentRoom`, `socket`, `isNamespaceJoined`) weg
- **Aktueller Stand:** `chrome.storage.session` persistiert `currentRoom`, `peerId`, `eventQueue` — der SW stellt diese beim Start wieder her (`ensureState()`)
- **Gelöst:** WebSocket wird automatisch via `connect()` neu aufgebaut. Events werden während Reconnect gequeued und nach Namespace-Join geflushed. "Reconnecting..." Status wird im Popup + Badge angezeigt. KeepAlive-Alarm auf 30s reduziert. Reconnect-Backoff: 500ms Basis, max 5s (statt vorher 1s→30s).
### 7. Tests für Extensions
Stimmt, sind aufwändig. Praktische Ansätze:
- **Unit Tests:** `jest` + `jest-chrome` (mockt `chrome.*` APIs) — testet `popup.js` Logik, Server-Logik
- **Integration Tests:** `puppeteer` mit `--load-extension` Flag — testet Extension im echten Browser
- **Server Tests:** `supertest` + `socket.io-client` — testet WebSocket-Flows
- **Aufwand:** ~400-600 LOC für sinnvolle Testabdeckung der Kernlogik
| Badge | Meaning |
|---|---|
| 🚧 In Progress | Currently being developed |
| 📋 Planned | Prioritized for an upcoming phase |
| 💡 Backlog | Under evaluation, not yet prioritized |
| ❌ Rejected | Declined (with rationale) |
| ✅ Completed | Shipped |
---
## Zukünftige Features
## 🚧 In Progress
*Currently being worked on.*
| Feature | Priority | Area |
|---|---|---|
| *(none yet)* | | |
---
## 📋 Planned
*Prioritized for upcoming phases.*
### 1. Split large JavaScript files into smaller modules
- **Priority:** P1
- **Category:** Maintainability / AI Context Optimization
- **Background:** Core files like `background.js` and `popup.js` have grown large and exceed 800 lines. This makes manual debugging harder and wastes context window space for AI models.
- **Planned solution:**
- Structurally split logic into separate focused modules (e.g., UI Renderer, Message Router, Storage Manager, Socket Client).
- Use ES modules for clean separation and better reusability.
### 2. Invite link with target URL for auto-redirect
- **Priority:** P2
- **Category:** UX / Ease of Sharing
- **Background:** The invite link currently only contains the room ID. The invited person has to manually open the page. Ideally, the link would include the shared tab's URL so the invitee gets redirected to the right page and the tab is auto-selected (auto-matching via tab title already exists).
- **Known challenges:**
- Many streaming sites (e.g., Emby, Jellyfin) don't have unique URLs per content — once inside the player, the URL stays the same.
- Dozens of such edge cases exist; a generic solution is difficult.
- Would likely need site-specific extractor logic (similar to the existing sync service adapters).
- **Possible approaches:**
- Fallback: if no unique URL can be determined, only pass the tab title.
- Site-specific URL extraction for known services.
---
## 💡 Backlog
*Ideas and feature requests under evaluation.*
### In-room chat overlay (like TeleParty)
- **Priority:** P3
- **Category:** Social / Communication
- **Background:** A collapsible chat panel to the right of the video (or as an overlay) allowing text-based communication with everyone in the room.
- **Why backlog (still uncertain):**
- **Use case:** No strong personal need — with chat, latency matters less than with voice; async communication tolerates a few seconds of delay.
- **Complexity:** Relatively large feature (UI + message persistence + possibly history).
- **Legal/moderation:** Unclear what moderation requirements would apply if users can exchange chat messages. Could be relevant depending on jurisdiction.
- **Status:** Under evaluation, may come later.
---
## ❌ Rejected
*Declined features with rationale — keeps decisions documented so they don't get re-debated.*
| Feature | Reason |
|---|---|
| *(none yet)* | |
Neue Features werden nur nach expliziter Freigabe hinzugefügt.
+5 -3
View File
@@ -17,13 +17,15 @@ You MUST run the build script in any of the following scenarios:
Run the Node.js build script from the repository root:
```bash
node scripts/build-extension.js
node scripts/build-extension.cjs
# or simply:
npm run build:extension
```
## What does it do?
The build script performs the following actions:
1. Synchronizes protocol constants by copying `shared/constants.js`, `shared/blacklist.js`, and `shared/README.md` into `extension/shared/`.
2. Injects `EVENTS` and `HEARTBEAT_INTERVAL` into `content.js` via marker-based replacement.
2. Injects `EVENTS`, `HEARTBEAT_INTERVAL`, and `episode-utils.js` functions (`extractEpisodeId`, `sameEpisode`) into `content.js` via marker-based replacement.
3. Compiles browser-specific manifest files.
4. Packages the final ready-to-publish extension artifacts for Chrome and Firefox into the `dist/` directory.
@@ -31,7 +33,7 @@ The build script performs the following actions:
The system enforces a strict `protocolVersion` check during the `JOIN_ROOM` handshake.
- The version is defined in `shared/constants.js`.
- If the extension and server versions mismatch, the server will reject the connection with an `Incompatible protocol version` error.
- **Always run the build script** after bumping the version number to ensure both components are updated.
- **Never manually bump version numbers**. The CI pipeline automatically injects the version from the git tag into `manifest.base.json`, `shared/constants.js`, and `package.json` during release builds. Run the build script to synchronize other constant updates.
> [!CAUTION]
> **NEVER** edit the files inside `extension/shared/` directly. They will be overwritten the next time the build script is run. Always edit the files in the root `shared/` directory and then run the build script.
+46
View File
@@ -0,0 +1,46 @@
# Tested Streaming Services
This document tracks which streaming platforms and media servers have been tested with KoalaSync.
| Service | Sync Works | Media Title | Episode Auto-Sync | Notes |
|---------|:----------:|:-----------:|:-----------------:|-------|
| **YouTube** | ✅ Full | ✅ Full | ❌ | Individual videos, not episodes — no episode auto-sync. |
| **Twitch** | ✅ Full | ✅ Full | ❌ | Individual streams/VODs, not episodes — no episode auto-sync. |
| **Netflix** | ✅ Full | ❌ | ❌ | No media title exposed. |
| **Emby** | ✅ Full | ✅ Full | ✅ Full | Best-in-class support. |
| **Jellyfin** | ✅ Full | ✅ Full | ✅ Full | — |
| **Plex** | Not tested | Not tested | Not tested | — |
| **Disney+** | ✅ Full | ⚠️ Partial | ❌ | Series title only (e.g. "The Simpsons"), no episode info. |
| **Prime Video** | ✅ Full | ✅ Full | ❌ | — |
| **HBO Max / Max** | Not tested | Not tested | Not tested | — |
| **Crunchyroll** | Not tested | Not tested | Not tested | — |
| **Vimeo** | Not tested | Not tested | Not tested | — |
| **Dailymotion** | Not tested | Not tested | Not tested | — |
| **ARD / ZDF Mediathek** | Not tested | Not tested | Not tested | — |
## Legend
| Symbol | Meaning |
|--------|---------|
| ✅ Full | Works without limitations. |
| ⚠️ Partial | Works with caveats (see Notes). |
| ❌ N/A | Not applicable or not supported. |
## How to Contribute
Tested a service that's not listed? Found different behavior than documented?
1. Test KoalaSync on the service with two browser profiles
2. Use the extension's **Dev tab** to check `readyState`, `currentTime`, and media title
3. Open a GitHub issue or PR updating this table
## Technical Background
KoalaSync works on any website with a **standard HTML5 `<video>` element** that allows script injection.
Limited functionality on certain platforms is typically caused by:
- **DRM/Copy Protection** (e.g., Widevine on Netflix) which restricts access to media metadata like title and playback state
- **Shadow DOM encapsulation** that hides video elements from content scripts
- **Strict Content Security Policies** (CSP) that block script injection
Websites with heavily obfuscated custom players (e.g., complex Shadow DOM, iframe isolation) may require platform-specific workarounds in `content.js`.
+121
View File
@@ -0,0 +1,121 @@
# KoalaSync Translation & Localization Guide
Welcome to the **KoalaSync** translation guide. We rely on the open-source community to make KoalaSync accessible to users worldwide.
KoalaSync is split into two independent translation areas. You can translate either one, or both:
1. **The Browser Extension** (`extension/locales/`): The core product that users interact with daily.
2. **The Website** (`website/locales/`): The landing page and invitation bridge.
---
## Supported Languages Dashboard
We divide supported languages into two tiers: **Core Languages** (fully hand-crafted and audited by native speakers) and **Extended Languages** (auto-generated using translation models to expand initial coverage).
> [!TIP]
> **Help Us Improve!**
> We welcome community contributions to audit `Auto-Generated` translations and elevate them to `100% Manually Verified` status.
| Language Code | Language Name | Verification Status | Rationale / Context |
| :--- | :--- | :--- | :--- |
| `en` | **English** | `100% Manually Verified` | Global default language (verified by developer) |
| `de` | **German** | `100% Manually Verified` | Developer's native language |
| `fr` | **French** | `Auto-Generated` | Needs manual native review and polishing |
| `es` | **Spanish** | `100% Manually Verified` | Manual native review by Alenia Studios |
| `pt-BR` | **Portuguese (Brazil)** | `100% Manually Verified` | Manual native review by Alenia Studios |
| `ru` | **Russian** | `Auto-Generated` | Needs manual native review and polishing |
| `it` | **Italian** | `100% Manually Verified` | Manual native review by Alenia Studios |
| `pl` | **Polish** | `Auto-Generated` | Needs manual native review and polishing |
| `tr` | **Turkish** | `Auto-Generated` | Needs manual native review and polishing |
| `nl` | **Dutch** | `Auto-Generated` | Needs manual native review and polishing |
| `ja` | **Japanese** | `Auto-Generated` | Needs manual native review and polishing |
| `ko` | **Korean** | `Auto-Generated` | Needs manual native review and polishing |
| `pt` | **European Portuguese** | `100% Manually Verified` | Manual native review by Alenia Studios |
| `zh` | **Chinese (Simplified)** | `Auto-Generated` | Needs manual native review and polishing |
| `uk` | **Ukrainian** | `Auto-Generated` | Needs manual native review and polishing |
> [!WARNING]
> **Autogeneration Quality Rule**
> Any newly contributed languages must be marked as `Auto-Generated` in this table until fully reviewed and signed off by a native speaker in a pull request.
---
## How to Translate KoalaSync
Here is the exact step-by-step process for contributing translations.
### Step 1: Fork and Clone the Repository
If you are an external contributor, start with the standard open-source workflow:
1. Click the "Fork" button on GitHub to create your own copy of the repository.
2. Clone your fork locally: `git clone https://github.com/YOUR-USERNAME/KoalaSync.git`
3. Create a branch: `git checkout -b translation/my-language`
### Step 2: Translate the Extension
The browser extension handles real-time syncing, settings, and popups.
1. Navigate to `extension/locales/`.
2. Edit an existing `[lang].json` or copy `en.json` to create a new one (for example, `it.json`).
3. Translate all string values. **Do not change the JSON keys.**
### Step 3: Translate the Website
The website hosts the landing page and invitation bridge.
1. Navigate to `website/locales/`.
2. Edit an existing `[lang].json` or copy `en.json` to create a new one.
3. Translate all string values. **Do not change the JSON keys.**
4. If creating a brand new language, configure the metadata at the top of your JSON file:
```json
{
"LANG_CODE": "it",
"HTML_CLASS": "lang-it",
"CANONICAL_PATH": "it/",
"LANG_TOGGLE_URL": "../",
"LANG_TOGGLE_TEXT": "EN"
}
```
5. If creating a brand new language, register it in `website/build.cjs` by adding it to the `languages` array.
### Step 4: Verify Locally
Ensure your JSON files are valid and all keys match the English baseline. Open your terminal in the KoalaSync root folder and run:
```bash
# Tests the extension locales for missing keys or syntax errors
node scripts/test-locales.cjs
# Tests the website locales for missing keys or syntax errors
node scripts/test-website-locales.mjs
# Builds the website with your new translations
node website/build.cjs
```
If you receive any errors about missing keys or placeholder strings, fix them before submitting.
### Step 5: Commit and Pull Request
1. Open this `TRANSLATION.md` file and add or update your language in the **Supported Languages Dashboard** above. Mark it as `100% Manually Verified` only if it has been reviewed by a native speaker.
2. Commit your changes: `git commit -m "Update Italian translations"`
3. Push to your fork: `git push origin translation/my-language`
4. Open a pull request on the main KoalaSync repository on GitHub.
---
## Strict Legal Exclusion Rule
Our legal pages have strict constraints to protect user privacy and avoid regulatory liabilities.
> [!IMPORTANT]
> **Do Not Translate Legal Documents**
> The legal notice (`website/impressum.html`) and privacy policy (`website/datenschutz.html`) **MUST remain exclusively in English and German**.
>
> **Rationale:** Legal compliance under the European Union General Data Protection Regulation (GDPR) and the German Digital Services Act (DDG). Offering automated translations of legally binding notices introduces compliance risks due to potential mistranslations of liability limits.
>
> **Technical fallback:** Our system automatically falls back to English for legal pages if a user visits them in an unsupported language, so you do not need to translate them.
+118
View File
@@ -0,0 +1,118 @@
# Co-Host (Multi-Controller) — Implementation Plan
Branch base: `feature/host-control-mode` (builds directly on it).
Goal: let the room owner grant **playback control to several peers** (co-hosts), not
just one — e.g. 4 of N people in a room may drive play/pause/seek, the rest are guests.
This is the second server-gated feature the `capabilities` hook was designed for
(`CAPABILITIES.CO_HOST = 'co-host'`, already stubbed in `shared/constants.js`).
---
## 1. Roles
| Role | Can drive (play/pause/seek/force-sync/episode-lobby) | Can promote/demote + toggle mode |
|------|------|------|
| **Owner** (room creator, = today's "host") | yes (always a controller) | **yes** |
| **Controller** (co-host) | yes (in `host-only` mode) | no |
| **Guest** | only in `everyone` mode | no |
The single-host feature is just the special case `controllers = { owner }`.
## 2. Data model
### Server (`room` object)
- `ownerPeerId` — the creator / manager. Keep `hostPeerId` as an **alias** (= ownerPeerId)
so older clients keep working.
- `controllers: Set<peerId>` — peers allowed to drive. **Always contains ownerPeerId.**
- `controlMode: 'everyone' | 'host-only'` — unchanged wire values (`'host-only'` now means
"restricted to controllers", not "single host").
- `MAX_CONTROLLERS` cap (e.g. 10) to bound the set + payload.
### Shared constants
- `CAPABILITIES.CO_HOST = 'co-host'` (un-stub it) → add to `SERVER_CAPABILITIES`.
- New events:
- `SET_PEER_ROLE` (client→server): `{ peerId, controller: boolean }` — owner promotes/demotes.
- Extend `CONTROL_MODE` (server→client) payload: `{ controlMode, ownerPeerId, hostPeerId, controllers: [peerId...] }`.
- `ROOM_DATA` gains `ownerPeerId` + `controllers`.
## 3. Gate generalization (the core change)
Today the gate compares against a single `hostPeerId`. Generalize to set membership:
- **Server relay gate** (`server/index.js`): `controlMode === 'host-only' && !room.controllers.has(mapping.peerId)` → drop. (Was `mapping.peerId !== room.hostPeerId`.)
- **Background gates** (sender + receiver): replace `amHost()` / `senderId !== hostPeerId`
with controller-set membership: `controllers.includes(myPeerId)` / `senderId ∈ controllers`.
- **Helpers:** split `amHost()` into `amOwner()` (manage rights) and `amController()`
(drive rights). The desync/snap-back path keys on `!amController()` instead of `!amHost()`.
`SET_PEER_ROLE` handler (server): validate sender is owner, target is a current peer in the
room, enforce `MAX_CONTROLLERS`, always keep owner in the set, then broadcast `CONTROL_MODE`
with the new `controllers`.
## 4. Client + UI
- **Owner** sees the peer list with a per-peer **"Controller" toggle** (promote/demote) plus
the existing mode toggle.
- **Controllers** see a "Controller" badge and are NOT locked out of the remote-control buttons.
- **Guests** see "Guest" + the host-only notice (unchanged).
- The promote UI + co-host badges render only when the relay advertises the `co-host`
capability (feature detection, same pattern as `hostControlSupported`).
- i18n: new keys (`ROLE_CONTROLLER`, `BTN_PROMOTE`, `BTN_DEMOTE`, …) across all locales.
## 5. Backwards compatibility
- **New client + old server** (host-control only, no `co-host` capability): no co-host UI;
behaves as today's single-host. ✓
- **Old client + new server**: ignores `controllers` / `SET_PEER_ROLE`. An old client that
the owner promotes still **gates itself** (its sender-gate only knows `!amHost`), so it
can't drive — it degrades to a guest. Co-host requires a client that understands
`controllers`. Document this; not a crash. ✓
- No `PROTOCOL_VERSION` bump needed — purely additive, same as host-control.
## 6. Edge cases
- **Controller leaves** → `removePeerFromRoom` also does `room.controllers.delete(peerId)`.
- **Owner leaves** → fallback: promote the earliest remaining **controller** to owner (prefer
a controller over a random peer); if none, earliest peer; keep the rest of the set. Reuse
the `peerJoinLocks` guard so a reconnect/second-tab doesn't demote (same fix as host).
- **Promote a peer not in the room** → server rejects (target must be a live peer).
- **Promote beyond `MAX_CONTROLLERS`** → server rejects, re-syncs the owner's UI.
- **`everyone` mode** → the `controllers` set is still maintained (so flipping to `host-only`
keeps the chosen co-hosts), it just isn't enforced while in `everyone`.
- **peerId spoofing** → unchanged accepted limitation (see `docs/KNOWN_LIMITATIONS.md`);
co-host doesn't widen it materially (still bounded to a temporary room).
## 7. Scale: the "4 of 510 people" part — read this
The role change above is moderate. **Putting 510 people in one room is a separate, larger
problem** and should be its own track:
- `MAX_PEERS_PER_ROOM` is **25** today. 510 needs a large raise + load testing.
- **The real bottleneck at scale is heartbeat fan-out, not control events.** Every peer
heartbeats and the relay broadcasts each to all peers → O(N²) per interval. At 510 that's
~510×509 / 15s ≈ **17k msg/s just for heartbeats** — the scaling wall.
- **Co-host actually *helps* the control-event side:** in `host-only` mode only the few
controllers emit play/pause/seek, so event *sources* drop from N to K (e.g. 4). Restricting
who can drive is synergistic with big rooms.
- Large rooms therefore need (independent of co-host):
- **Heartbeat fan-out reduction** — e.g. only relay controller/owner heartbeats to everyone,
relay guest heartbeats only to the owner/controllers (for the UI), or server-side
aggregation into periodic snapshots instead of per-peer relay.
- **`ROOM_DATA` payload trimming** — a 510-entry peer list is large; send counts + controller
details, lazy-load the full roster.
- Possibly the **socket.io Redis adapter** for horizontal scaling, and broadcast tuning.
## 8. Effort estimate
- **Co-host roles** (server gate generalization + `SET_PEER_ROLE` + owner-leave fallback +
client gates + promote UI + i18n), at the current ≤25-peer scale: **~34 dev days** (same
shape as host-control itself — mostly generalizing host→controller-set).
- **Large-room scaling (510)**: a **separate ~12 week** track (heartbeat redesign + payload
trimming + cap raise + load testing), independent of co-host. Recommend shipping co-host at
the current cap first, then scaling rooms as its own project.
## 9. Suggested sequencing
1. `CAPABILITIES.CO_HOST` + `controllers`/`ownerPeerId` in room state + `ROOM_DATA` (additive).
2. Server `SET_PEER_ROLE` + gate generalization + owner-leave fallback + WS tests.
3. Background: controller-set membership in both gates + `amOwner`/`amController`.
4. Popup: promote/demote toggles (owner) + Controller badge + i18n.
5. (Separate track) large-room scaling.
+283
View File
@@ -0,0 +1,283 @@
# Host Control Mode — Branch Overview, Goals & Edge Cases
> **This is the canonical entry-point doc for branch `feature/host-control-mode`.**
> If you're an agent or contributor picking this up: read this file first, then the
> implementation plan in [`host-control-mode-plan.md`](./host-control-mode-plan.md).
> Temporary working doc — delete or fold into permanent docs before merging to `main`.
>
> Status legend: 🔴 open / unresolved · 🟡 idea, needs testing · 🟢 decided
---
## 0. Implementation status
All five layers are implemented & pushed (server + background + content + popup + i18n).
Automated checks green: ESLint, WS integration tests (incl. host-only gate, toggle
reject, host-leave fallback), content video-finder, locale consistency (15 langs),
full release verification.
**Still needs real-device testing** — the EC test matrix in §7 (YouTube/Netflix/
Twitch/Disney+/Jellyfin): involuntary-pause classification (EC-1/EC-5/EC-8), snap-back
reliability and fight-loops (EC-4), and the desync/resync flow across players. The
intent classifier (EC-9) and snap-back cooldown are first-pass heuristics tuned by
reading the code, not yet by watching them behave on each site.
Deferred by decision (see §8): host grace period on disconnect (EC-10).
### Capability detection (forward-compat hook)
The relay advertises `capabilities: ['host-control']` in `ROOM_DATA`
(`SERVER_CAPABILITIES` in server, `CAPABILITIES` in shared/constants). The client
enables host-control UI/behavior only when the flag is present, so the feature
degrades cleanly on an older relay (absent → off) and old clients ignore the
field. This is the extensible hook for the planned **co-host** feature (owner
promotes guests to additional controllers): it will add a `'co-host'` capability
+ events without a protocol bump or breaking older relays/clients. Add new flags
to `CAPABILITIES` / `SERVER_CAPABILITIES` as features land.
### Pre-test self-audit (fixed)
- **Popup remote buttons froze for guests** — in host-only a guest's Play/Pause/SYNC
click was gated server-side but the button stuck on "Playing"/disabled with no
feedback. Now the remote controls are locked (disabled + tooltip) for guests, with
backstop guards in the handlers. (popup.js)
- **Desync dialog could break under strict CSP** — it used `innerHTML` with inline
`style=""` attributes, which Netflix/YouTube/Disney+ strip via `style-src`. Rebuilt
with the DOM API (CSSOM `.style` is CSP-safe) inside a **Shadow DOM** so page CSS
can't restyle/hide it. (content.js)
- **Live-DVR not detected (EC-15)** — `duration === Infinity` misses Twitch/YouTube
live-DVR (finite, sliding duration). Added a `seekable.start(0) > 1` sliding-window
heuristic in `hcmIsLive()`. (content.js)
### Pre-test self-audit
- ~~**EC-4/EC-1 snap-back thrash:**~~ FIXED — implemented the **buffer-aware deferred
snap-back** (`hcmDeferredSnapBack`): on an involuntary event, if the player isn't
ready (`readyState<3` or seeking) we wait (poll, 8s cap) until it can play, then snap
ONCE to the host's re-queried position instead of repeatedly fighting the buffer.
Done defensively/player-agnostically — we can't enumerate every site, so this is safe
whether a player fires `pause()` or only `waiting`. Aborts if the user goes solo or is
no longer a gated guest; single pending poll (no stacking).
- ~~**Control-mode race at join:**~~ FIXED — `hcmHandleBlocked` now treats `HOST_BLOCKED`
as authoritative (adopts host-only/guest role) instead of re-checking local mode,
since background only sends it to gated guests.
- ~~**Dialog/badge text is English-only**~~ FIXED — background resolves the strings
via GET_HCM_STRINGS (it has the i18n loader); content fetches them on init with
English fallback. 6 new keys (HCM_DIALOG_*/HCM_BADGE_*) across all 15 locales.
---
## 1. What this branch is for
Origin: a GitHub feature request. When watching with larger groups, anyone can pause
or seek and disrupt everyone else. The requester wants the room creator to optionally
restrict control to a single **host**, the way Teleparty works — guests who try to
pause get asked whether they want to pause *only their own* player (and desync), and
otherwise get snapped back to the room's position.
**Goal:** Add an optional per-room **Host Control Mode**. A room can be switched
between:
- **`everyone`** (default, current behavior): anyone can play/pause/seek for the room.
- **`host-only`**: only the host drives the room. A guest's deliberate play/pause/seek
is not broadcast; instead they're snapped back to the room position — unless they
explicitly choose to desync (go solo) with a "Resync" escape hatch.
## 2. Trust model (read this before over-engineering)
This is **client-side trust, by design**. It's a watch party, not a security boundary.
The point is preventing *accidental* and *casual* disruption, not stopping someone
determined to patch their own extension. We do **not** add auth, tokens, or
cryptographic host identity. `peerId` is unauthenticated and that's fine here.
(We still gate server-side as the robust chokepoint — see plan — but that's about
killing spam reliably, not about defeating a hostile client.)
## 3. Scope / non-goals
In scope:
- Host designation (first joiner = host), mode toggle, host-only gating of all
room-moving events, guest snap-back, deliberate-desync flow + resync, host UI.
Explicit non-goals (for this branch):
- Authenticated / spoof-proof host identity.
- Persistent host across server restarts (room state is in-memory).
- Syncing around personalized ad breaks.
- Host transfer UI (auto-fallback to `everyone` when host leaves; manual transfer
is a possible later add).
## 4. Architecture summary
Three-layer gate for room-moving events from a non-host in host-only mode
(`PLAY`, `PAUSE`, `SEEK`, `FORCE_SYNC_PREPARE`, `FORCE_SYNC_EXECUTE`, `EPISODE_LOBBY`):
1. **Server** — doesn't relay them (robust chokepoint, kills spam regardless of client).
2. **Sender (guest)** — doesn't emit; shows confirm dialog / disables host-only buttons.
3. **Receiver** — drops any that slip through (covers old/buggy/modified clients).
Snap-back reuses the existing `_setSuppress` mechanism (content.js:442) so applying
the room state programmatically doesn't echo back as a new event. Target position is
extrapolated from the host's last known state (±1s). Full detail + code hooks in the
plan doc.
---
## 5. The central challenge
Everything hard about this feature reduces to **one question** (see EC-9):
> How do we reliably tell a **deliberate** guest pause/seek from an **involuntary**
> player/browser event (buffering, ads, tab throttling, source swaps, DRM hiccups)?
If we get this wrong, guests get spammed with desync dialogs and snap-back loops for
things they never did. The host/role plumbing is the easy part; this classifier is the
real work. **Design the intent-classifier before writing the gate.**
---
## 6. Edge cases
### EC-1 🔴 Buffering / loading fires a `pause` event
content.js listens to `play`/`pause`/`seeked`/`loadeddata` only (content.js:1000-1003),
not `waiting`/`stalled`. Pure HTML5 buffering fires `waiting` → harmless. But custom
players (Netflix/YouTube/Twitch/JW) often call `video.pause()` during buffering/ads →
real `pause` → guest gate would mis-classify as deliberate. Sub-cases: (a) initial load
sits paused, no event, fine; (b) mid-stream stall, player-dependent; (c) seek-induced
re-buffering may outlast the suppress window and leak. Mitigation: `isBuffering` flag
from `waiting`/`playing`, or grace window; in host-only the guest's own state is
irrelevant so just ignore involuntary pauses and let catch-up logic (content.js:489)
re-sync them.
### EC-2 🟢 Force-Sync / Episode-Lobby abuse by guests
Guest could seek + spam Force-Sync to drag everyone, or spam Episode-Lobby to pause
everyone. Decision: host-only blocks guest *initiation* of `FORCE_SYNC_*` and
`EPISODE_LOBBY`; guests may only respond (`FORCE_SYNC_ACK`, `EPISODE_READY`). Guests'
legitimate path is the personal "Resync" button.
### EC-3 🟢 Host leaves the room
Fall back to `controlMode = 'everyone'`, broadcast `CONTROL_MODE`. Never a stuck locked
room. (Auto-promote next peer deferred.)
### EC-4 🔴 Snap-back fight loop (pause/play/skip back/pause/play)
Mashing controls or a janky player → each snap-back may emit events → ping-pong.
Mitigation: cooldown (~600ms) after snap-back; ensure snap-back runs fully under
suppress. Also: if target is unreachable (seek past buffered range), retry K times then
give up — no infinite loop.
### EC-5 🔴 Ad breaks (YouTube/Twitch/…)
Mid-roll ads pause/swap the media element, differ per peer → desync is unavoidable and
must NOT spam the dialog. Probably covered by EC-1 buffering grace; flag for explicit
testing.
### EC-6 🟡 Snap-back target accuracy
No continuous room clock; extrapolate from host's `currentTime` + `lastHeartbeat`
(±1s, worse if stale). The follow-up host correction must also be suppressed so it
doesn't read as guest input.
### EC-7 🟢 Old / buggy / modified guest client
Covered by receiver-side + server-side gates.
### EC-8 🔴 Tab throttling / background tab
Backgrounding throttles timers and may pause media. There's existing
`visibilityGraceUntil` handling for seeks (content.js:892). Confirm a
background-induced pause isn't treated as deliberate in host-only; reuse the grace flag.
### EC-9 🔴 What counts as "deliberate" — the central unresolved question
Collapses EC-1/EC-5/EC-8. Candidate signals: `readyState`/`networkState`/`video.seeking`
at event time; recent `waiting`; recent user gesture (`navigator.userActivation`,
keydown/click); visibility/focus. Build one shared **intent-classifier** helper in
content.js that all host-only gating flows through.
### EC-10 🔴 Host brief disconnect / reconnect (network blip)
Host's wifi drops for 3s and reconnects. With "host leaves → fallback to everyone",
a blip would silently unlock the room and demote the host (peerId persists in
chrome.storage so they rejoin with the same id, but the server already cleared
`hostPeerId`). Mitigation idea: short **host grace period** (e.g. keep `hostPeerId`
reserved for ~30s after disconnect; if the same peerId rejoins, restore host + mode).
Needs the server reaper (server:644) and `removePeerFromRoom` (server:168) to cooperate.
### EC-11 🔴 New guest joins mid-session in host-only mode
On join they must (a) immediately sync to the host's current position without the host
doing anything, and (b) see they're a guest in the UI. ROOM_DATA already carries peers;
add `hostPeerId`/`controlMode` so a fresh client knows its role instantly. Verify the
existing "newcomer syncs without waiting" path (content.js:542) still fires.
### EC-12 🔴 Desync semantics — what does "solo" actually mean?
When a guest chooses "pause only me", do they (a) fully ignore all subsequent host
events until they Resync, or (b) keep receiving but not auto-applying? Define clearly.
Proposed: full solo — ignore host play/pause/seek while desynced; Resync re-attaches and
snaps to current host position. Also: what state does Resync land them in if the host is
currently paused vs playing?
### EC-13 🔴 Race: host flips to host-only exactly as a guest pauses
Event ordering between `SET_CONTROL_MODE`/`CONTROL_MODE` and an in-flight guest `PAUSE`.
The `seq` ordering helps, but define the tie-break. Likely: server is authoritative —
once it has `host-only`, it drops the guest event regardless of client-side timing.
### EC-14 🟡 Volume / mute / audio-options must NOT be gated
Those are per-peer, not room control. The gate must target only play/pause/seek +
forcesync/episode — not `PEER_STATUS` volume/mute fields. Easy to over-block; add a test.
### EC-15 🔴 Live streams (Twitch live, live DVR)
"Room timestamp" is fuzzy on live edge; seeking semantics differ. Decide whether
host-only even makes sense for live, or degrade gracefully. Low priority but log it.
### EC-16 🟡 Host's own involuntary events still drive the room
If the host buffers and the player auto-pauses, that pause is "allowed" and pauses
everyone. That's existing behavior, but in host-only it means the host's buffering
stalls the whole room. Acceptable? Probably yes (host is authoritative), but note it.
### EC-17 🟡 Server restart drops room state
`hostPeerId`/`controlMode` are in-memory. After a server restart, whoever rejoins first
becomes the new host and mode resets to `everyone`. Acceptable for now (non-goal), but
document so it's not a surprise.
### EC-18 🟡 Dialog dismissed without choosing
Guest clicks away / presses Esc on the desync prompt. Default = treat as "No" → snap
back. Make sure an un-answered dialog can't leave them in limbo (paused + not desynced +
no dialog).
### EC-19 🟡 Multiple video elements / element swap (SPA, ad → content)
Players that swap the `<video>` element mid-session: re-attach handlers (content.js
already re-binds on `loadeddata`) and make sure host-only gating follows the new element.
### EC-20 🟡 Peer list shape (object vs legacy string)
Throughout background.js peers may be objects or bare peerId strings
(`typeof p === 'object' ? p.peerId : p`). All new `hostPeerId` comparisons must handle
both forms, or we get a host that's never recognized.
### EC-21 🟡 Mode toggle spam / rate limiting
Host hammering the toggle → many `SET_CONTROL_MODE`. Covered by existing
`checkEventRate` (server), but debounce in the UI and ignore no-op transitions.
---
## 7. Test matrix (fill in during dev)
| Player | Buffer→`pause`? | Ad behavior | Snap-back works? | Element swap? | Notes |
|-------------------|-----------------|-------------|------------------|---------------|-------|
| Generic HTML5 | | | | | |
| YouTube | | | | | |
| Netflix | | | | | |
| Twitch (VOD) | | | | | |
| Twitch (live) | | | | | |
| Disney+ / DRM | | | | | |
| Jellyfin / Emby | | | | | |
## 8. Decisions (audited)
- [x] **Intent-classifier (EC-9):** A `pause`/`seek` is **involuntary** if ANY of:
`readyState < 3`, `video.seeking`, a `waiting` fired < ~1500ms ago (`isBuffering`
flag), inside `visibilityGraceUntil`, OR no own-tracked user gesture
(`Date.now() - lastUserGestureAt < 1000`, via capturing keydown/pointerdown — do
NOT use sticky `navigator.userActivation.hasBeenActive`). Bias: only *clearly*
involuntary is ignored; everything else = deliberate. **Note:** in host-only the
guest never broadcasts anyway, so this only decides dialog-vs-silent — a UX call,
not a room-integrity call. Start simple, tune later.
- [x] **Host grace on disconnect (EC-10): NOT in v1.** Immediate fallback to
`everyone` (EC-3). A grace window risks a multi-second hard-lock if the host never
returns. Revisit as polish once the core flow works.
- [x] **Desync semantics (EC-12): full solo.** Ignore host play/pause/seek while
desynced; Resync snaps to host position + adopts host play/pause state. Desync
auto-clears on new media/episode. Requires a persistent, obvious "You are desynced"
UI.
- [x] **Snap-back cooldown (EC-4): until-settled, not fixed.** Suppress re-trigger
until `readyState>=3 && playing && |Δt|<tol`, hard-cap ~1500ms. Retry target up to
3×, then give up (no infinite loop).
- [x] **Live streams (EC-15): degrade.** Disable the gate when
`video.duration === Infinity`. Caveat: live-DVR may report finite duration and slip
through — acceptable for v1.
+83
View File
@@ -0,0 +1,83 @@
# Host Control Mode — Beta Testing Guide
> Temporary doc for branch `feature/host-control-mode`. Remove before merge.
> The feature needs the **server** half too — the official relay
> (`wss://syncserver.koalastuff.net`) doesn't run it yet, so test against a beta
> server first.
## 1. Run the beta relay (Docker)
The branch publishes the server image to GHCR under non-production tags
(`:beta` = newest branch build, `:sha-<commit>` = immutable pin). `:latest` is
never touched.
```bash
# Log in (the package is private → PAT with read:packages)
echo "$GHCR_PAT" | docker login ghcr.io -u <your-gh-user> --password-stdin
# Pull + (re)create — NOTE: `docker restart` does NOT pick up a new image,
# you must remove and re-run (or use compose / Watchtower).
docker pull ghcr.io/shik3i/koalasync:beta
docker rm -f koala-beta 2>/dev/null || true
docker run -d --name koala-beta -p 3000:3000 \
-e SERVER_SALT='choose-your-own-salt' \
ghcr.io/shik3i/koalasync:beta
```
To always run the newest beta automatically, point **Watchtower** at the
container — it does the pull → recreate whenever `:beta` moves.
The `OFFICIAL_SERVER_TOKEN` is baked into `shared/constants.js`, so no token env
is needed. Set `SERVER_SALT` (used for room-password hashing).
## 2. Connect the extension to it
⚠️ The client **force-upgrades `ws://` to `wss://` for any non-localhost host**
(see background.js "Upgraded to wss:// for remote host"). So a bare
`ws://your-server:3000` will fail without TLS. Two options:
- **Quick (no TLS):** SSH-tunnel the port so it counts as local:
```bash
ssh -L 3000:localhost:3000 your-beta-server
```
then in the popup → **Manual Connect / Advanced → Custom →** `ws://localhost:3000`.
- **Proper:** put the container behind a TLS reverse proxy (Caddy does automatic
HTTPS) → `wss://beta.yourdomain`.
Then create/join a room.
## 3. ⚠️ Use two *new* clients
Test with **two browser profiles both running this branch build** (load unpacked
from `extension/`, or install the built zip). A stock release client as a guest
will be correctly gated by the server but has none of the content-side code, so
it silently desyncs with no dialog/snap-back — that's expected degradation, not a
bug, but it looks like one during testing.
## 4. Verification checklist
| # | Step | Expect |
|---|------|--------|
| 1 | Create a room (host) | Host Control card shows **Host** + the toggle |
| 2 | Second profile joins | Guest sees no card while mode is "everyone" |
| 3 | Host enables "Only I can control" | Guest's Play/Pause/SYNC buttons lock; card shows **Guest** |
| 4 | Guest presses pause/space on the video | Brief flicker, snaps back to host position |
| 5 | Guest pauses again | Dialog: "Stay in sync" / "Watch on my own" |
| 6 | Guest → "Watch on my own" | Persistent "Solo" badge; host's peer list shows **Solo** |
| 7 | Guest → "Resync" | Snaps back to host; Solo badge clears on both sides |
| 8 | Guest tries Force-Sync / seek spam | Nothing propagates to the room |
| 9 | Host disables host-only | Card hides for guest; controls unlock |
| 10 | Host leaves the room | Room falls back to "everyone"; a remaining peer becomes host |
| 11 | Reload the guest's page while desynced | Still shows Solo (state survives reload) |
| 12 | Switch the popup language | Dialog/badge text is localized |
## 5. Capability detection
The relay advertises `capabilities: ['host-control']` in `ROOM_DATA`. The client
only enables the feature when that flag is present, so:
- against this beta server → feature on;
- against the old official server → feature cleanly hidden (no errors).
This is the extensible hook for the planned **co-host** feature (owner promotes
guests to additional controllers) — it'll add a `'co-host'` capability + events
without breaking older relays/clients.
+122
View File
@@ -0,0 +1,122 @@
# Host Control Mode — Implementierungsplan
Branch: `feature/host-control-mode`
Issue: GitHub feature request (wasserrutschentester) — nur Host darf den Raum steuern; Gäste werden zurückgesnappt oder gehen bewusst in Desync.
## Ziel
Ein Raum kann zwischen zwei Modi umgeschaltet werden:
- **`everyone`** (Default, heutiges Verhalten): jeder kann play/pause/seek für alle auslösen.
- **`host-only`**: nur der Host steuert den Raum. Pause/Seek eines Gasts wird **nicht** gebroadcastet; stattdessen snappt die eigene Extension den Gast zurück auf den Raum-Zustand — es sei denn, der Gast entscheidet sich bewusst für Desync.
Trust-Modell: client-seitig durchgesetzt. Kein Token, keine Auth. Es geht um versehentliches Stören, nicht um Angriffsschutz.
---
## Datenmodell
### Server (`server/index.js`, Room-Objekt ~Z.331)
Room bekommt zwei neue Felder:
```js
room = {
...,
hostPeerId: peerId, // gesetzt beim Anlegen = erster Joiner
controlMode: 'everyone', // 'everyone' | 'host-only'
}
```
- In `ROOM_DATA` (~Z.418) mitschicken: `hostPeerId`, `controlMode`.
- Neues Event `SET_CONTROL_MODE` (siehe unten): nur akzeptieren, wenn `senderPeerId === room.hostPeerId`. Server setzt `room.controlMode`, broadcastet die Änderung an alle.
- **Host-Migration:** in `removePeerFromRoom` (~Z.168) — wenn der gehende Peer `hostPeerId` war: entweder neuen Host bestimmen (nächster Peer) **oder** `controlMode` auf `everyone` zurückfallen lassen. → Entscheidung: **Fallback auf `everyone`** (simpel, nie verwaister gesperrter Raum). Optional später: Host-Transfer-Button.
### Shared Constants (`shared/constants.js`)
Neue Events im `EVENTS`-Objekt:
```js
SET_CONTROL_MODE: "set_control_mode", // Client->Server: Host ändert Modus
CONTROL_MODE: "control_mode", // Server->Client: Modus geändert { controlMode, hostPeerId }
```
⚠️ Danach `node scripts/build-extension.cjs` laufen lassen (Single Source of Truth propagieren). Ggf. `PROTOCOL_VERSION` bumpen — **nein, nur wenn alte Clients brechen würden**. Da alles additiv ist und alte Clients die neuen Felder/Events einfach ignorieren, ist KEIN Protokoll-Bump nötig. (Alter Client in host-only-Raum kennt den Modus nicht und sendet weiter → Host-Extensions ignorieren fremde Events nicht... → siehe Edge Case 7. Evtl. doch Bump erwägen.)
### Extension State (`background.js`)
```js
let controlMode = 'everyone';
let hostPeerId = null;
// abgeleitet: const amHost = () => hostPeerId === peerId;
```
Aus `ROOM_DATA` / `CONTROL_MODE` befüllen, in `chrome.storage.session` persistieren (wie `currentRoom`).
---
## Implementierung nach Schichten
### 1. Server (`server/index.js`)
- [ ] Room-Objekt um `hostPeerId` + `controlMode` erweitern (~Z.331).
- [ ] `ROOM_DATA`-Payload erweitern (~Z.418).
- [ ] Handler `SET_CONTROL_MODE`: validieren (Host-Check + Wert in {everyone, host-only}), setzen, `CONTROL_MODE` an Raum broadcasten.
- [ ] Host-Migration in `removePeerFromRoom`: Fallback auf `everyone` + neues `CONTROL_MODE` broadcasten, wenn Host geht.
- [ ] `SET_CONTROL_MODE` in die `relayEvents`-Liste? **Nein** — eigener Handler, da Sonderlogik + Host-Check. (relayEvents broadcastet blind.)
### 2. Shared / Build
- [ ] `EVENTS.SET_CONTROL_MODE`, `EVENTS.CONTROL_MODE` ergänzen.
- [ ] `node scripts/build-extension.cjs`.
### 3. background.js (Gast-Logik = Kern)
- [ ] `controlMode` / `hostPeerId` aus `ROOM_DATA` (~Z.875) und neuem `CONTROL_MODE`-Case übernehmen + persistieren + an Popup/Content pushen.
- [ ] **Emit-Gate** im SEND-Pfad (~Z.1786): bei `host-only && !amHost()` und action ∈ {play, pause, seek}:
- NICHT `emit`en.
- Stattdessen Content-Script anweisen: "snap back" ODER Desync-Confirm anzeigen.
- [ ] **Snap-Back-Zielzeit berechnen:** aus Host-Peer-State (`playbackState`, `currentTime`, `lastHeartbeat`) extrapolieren:
`targetTime = host.currentTime + (host.playbackState==='playing' ? (now - host.lastHeartbeat)/1000 : 0)`.
Genauigkeit ~±1s, für Watchparty ok. (Force-Sync-Maschinerie als Referenz für Ziel-Zeit-Koordination.)
- [ ] Nachricht an content.js: `{ type: 'HOST_BLOCK', action, targetTime, hostPlaybackState }`.
### 4. content.js (Player-Reaktion + Dialog)
- [ ] Handler für `HOST_BLOCK`: Confirm-Dialog im Player-Overlay rendern:
"Pause only your own player and desync from the group? [Yes] [No]".
- **No** (Default): Player via bestehende `_setSuppress`-Mechanik ([content.js:442](../extension/content.js:442)) wieder in Raum-Zustand zwingen (play + seek auf targetTime). Suppress verhindert Re-Broadcast.
- **Yes:** lokal pausiert lassen, `isDesynced = true` setzen, dezenten "Desynced — Resync"-Button zeigen.
- [ ] "Resync"-Button → snappt zurück auf aktuelle Raum-Zeit, `isDesynced = false`.
- [ ] **Loop-Schutz:** nach einer Snap-Back-Aktion kurzes Cooldown-Fenster (z.B. 600ms), in dem weitere lokale pause/seek-Events nicht erneut den Dialog triggern (verhindert pause→play→pause-Pingpong).
### 5. popup (Host-UI)
- [ ] Host-Toggle "Only I can control" (nur sichtbar wenn `amHost()`), sendet `SET_CONTROL_MODE`.
- [ ] Rollen-Badge: "Host" / "Guest" + aktueller Modus.
- [ ] Gast-Hinweis wenn host-only aktiv: "The host controls playback".
- [ ] i18n-Keys in `extension/_locales` / `locales` für ~15 Sprachen.
---
## Edge Cases (Test-Checkliste)
1. **Pause nicht verhinderbar, nur revidierbar** → kurzer Flicker (~½s) beim Gast ist erwartet/akzeptabel.
2. **Snap-Back-Zielzeit** aus Heartbeat extrapoliert, ±1s. Bei stark veraltetem Host-State (kein Heartbeat) → letzten bekannten Wert nehmen.
3. **Kampf-Loop pause/play/skip back/pause/play** → Cooldown-Fenster nach Snap-Back. Testen mit aggressivem Mashing.
4. **Desync-Escape:** Gast kann bewusst pausieren (Klo/Telefon) → "Yes" → solo, dann Resync.
5. **Host verlässt Raum** → Fallback auf `everyone`, alle bekommen `CONTROL_MODE`-Update. Testen: Host schließt Tab / Disconnect / Netzabbruch.
6. **host-only + Episode-Auto-Sync / Force-Sync** (server/index.js:503-516): **Gast darf NICHT initiieren.** Force-Sync trägt eine `targetTime` und zwingt ALLE darauf (background.js:1261) — ein Gast könnte seeken → Force-Sync spammen und damit host-only komplett aushebeln. Episode-Lobby pausiert ebenfalls alle. → Im host-only-Modus dürfen `FORCE_SYNC_PREPARE`/`FORCE_SYNC_EXECUTE` und `EPISODE_LOBBY` nur vom Host **initiiert** werden. Gäste dürfen weiterhin nur **reagieren**: `FORCE_SYNC_ACK`, `EPISODE_READY`. Gäste brauchen Force-Sync nicht — ihr legitimer Fall ist der "Resync"-Button (snappt nur sie selbst, nicht alle).
7. **Alter Client (ohne Feature) in host-only-Raum** → kennt Modus nicht, sendet weiter pause/seek → andere Extensions wenden es an. Mitigation: Empfänger-seitiges Gate (host-only-Clients ignorieren play/pause/seek von Nicht-Host) ODER `MIN_VERSION`/Protokoll-Bump. → **Empfehlung: zusätzlich Empfänger-seitig filtern** (robuster als nur Sender-Gate).
8. **Seek getrennt von Pause** → host-only blockt auch Gast-Seeks, nicht nur Pausen.
9. **Mehrere Tabs / Multi-Peer mit gleicher peerId** (Dedup, server:381) → Host-Identität bleibt an peerId hängen, ok.
10. **DAU-Verwirrung** "warum kann ich nicht mehr pausieren?" → klare UI-Botschaft + der Desync-Dialog erklärt sich selbst.
## Architektur-Entscheidung zu Edge Case 7 (wichtig)
Wir setzen das Gate **doppelt** und über **alle raum-verschiebenden Events**, nicht nur play/pause/seek:
Geblockte Initiierungen für Nicht-Host im host-only-Modus:
`PLAY`, `PAUSE`, `SEEK`, `FORCE_SYNC_PREPARE`, `FORCE_SYNC_EXECUTE`, `EPISODE_LOBBY`.
Weiterhin erlaubt für Gäste (reine Reaktion, verschiebt niemanden): `FORCE_SYNC_ACK`, `EPISODE_READY`, `PEER_STATUS`, `PING`/`PONG`.
- **Sender-seitig** (Gast sendet erst gar nicht) → saubere UX, Confirm-Dialog bei play/pause/seek; Force-Sync-/Episode-Lobby-Buttons im Gast-UI deaktiviert/ausgeblendet.
- **Empfänger-seitig** (in `handleServerEvent`, background.js:969 + Force-Sync-/Episode-Cases): wenn `host-only` und `data.senderId !== hostPeerId` → Event verwerfen (nicht an Content routen, keine State-Mutation).
So sind auch alte/buggy/manipulierte Clients abgedeckt, ohne harten Protokoll-Bump.
Optional zusätzlich **server-seitig** in den `relayEvents` (server/index.js:445): im host-only-Modus Initiierungs-Events von Nicht-Host gar nicht erst relayen. Spart Traffic + deckt alles zentral ab. Empfehlenswert, da der Server `hostPeerId`/`controlMode` ohnehin kennt.
---
## Reihenfolge der Umsetzung (kleine, testbare Schritte)
1. Constants + Build (Events da, nichts kaputt).
2. Server: hostPeerId/controlMode + ROOM_DATA + SET_CONTROL_MODE + Migration.
3. background.js: State übernehmen + Empfänger-seitiges Gate (Edge 7) — testbar ohne UI.
4. background.js: Sender-seitiges Gate + Snap-Back-Zielzeit.
5. content.js: Snap-Back-Apply + Confirm-Dialog + Loop-Cooldown.
6. popup: Host-Toggle + Badge + i18n.
7. Durchtesten der Edge-Case-Liste auf YT / Netflix / generischem HTML5-Player.
+8 -2
View File
@@ -8,6 +8,7 @@ export default [
sourceType: "module",
globals: {
chrome: "readonly",
browser: "readonly",
window: "readonly",
document: "readonly",
navigator: "readonly",
@@ -20,6 +21,7 @@ export default [
fetch: "readonly",
CustomEvent: "readonly",
MutationObserver: "readonly",
IntersectionObserver: "readonly",
Uint32Array: "readonly",
Set: "readonly",
Map: "readonly",
@@ -33,8 +35,12 @@ export default [
Date: "readonly",
Error: "readonly",
URL: "readonly",
URLSearchParams: "readonly",
WebSocket: "readonly",
self: "readonly"
history: "readonly",
location: "readonly",
self: "readonly",
process: "readonly",
}
},
rules: {
@@ -52,7 +58,7 @@ export default [
}
},
{
files: ["server/**/*.js", "scripts/**/*.js"],
files: ["server/**/*.js", "scripts/**/*.js", "scripts/**/*.cjs", "website/build.cjs"],
languageOptions: {
globals: {
require: "readonly",
+83
View File
@@ -0,0 +1,83 @@
# ==============================================================================
# KoalaSync - Production Caddy Configuration Example
# ==============================================================================
# This file provides examples of both a lightweight "Simple" configuration
# and a production-hardened "Advanced" configuration.
# Replace domains, reverse proxy locations, and directories with your actual setup.
# ------------------------------------------------------------------------------
# OPTION A: Simple Configuration
# ------------------------------------------------------------------------------
# Minimal configuration that serves the static website, enables gzip compression,
# supports extension-less Clean URLs, and reverse proxies the relay server.
# sync.koalastuff.net {
# root * /var/www/koalasync/website/www
# encode zstd gzip
#
# # Clean URLs support (resolves /join to join.html, etc.)
# try_files {path} {path}.html {path}/
# file_server
# }
#
# syncserver.koalastuff.net {
# reverse_proxy localhost:3000
# }
# ------------------------------------------------------------------------------
# OPTION B: Advanced Configuration (Production-Hardened)
# ------------------------------------------------------------------------------
# Highly secure, optimized configuration using advanced HTTP security headers,
# aggressive static assets caching, server signature concealment, and strict
# hardware permission access policies.
(security_headers) {
header {
# Enable HTTP Strict Transport Security (HSTS)
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Prevent clickjacking attacks (Sameorigin)
X-Frame-Options "SAMEORIGIN"
# Prevent MIME-sniffing
X-Content-Type-Options "nosniff"
# Enable browser XSS protection
X-XSS-Protection "1; mode=block"
# Control referrer information
Referrer-Policy "strict-origin-when-cross-origin"
# Hide Caddy server stamp signature
-Server
}
}
sync.koalastuff.net {
encode zstd gzip
root * /var/www/koalasync/website/www
# Clean URLs: Resolves paths without .html in the URL
try_files {path} {path}.html {path}/
file_server
# Static Caching for high-performance PageSpeed (1 year with validation)
@static {
file
path *.ico *.css *.js *.png *.svg *.webp *.avif
}
header @static Cache-Control "public, max-age=31536000, must-revalidate"
# Security Headers & Content Security Policy (CSP)
import security_headers
header {
# CSP hardened with base-uri and form-action limits
Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
# Modern Permissions Policy (blocks browser hardware access for enhanced privacy)
Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()"
}
}
syncserver.koalastuff.net {
import security_headers
encode zstd gzip
reverse_proxy KoalaSync:3000
}
+18
View File
@@ -0,0 +1,18 @@
services: # Top-level key defining all containers in this Compose file
koala-sync: # Name of the KoalaSync service
image: ghcr.io/shik3i/koalasync:latest # Pulls the latest KoalaSync image from GitHub Container Registry
container_name: KoalaSync # Sets a fixed container name instead of an auto-generated one
restart: always # Always restart the container if it stops or if Docker starts
environment: # Environment variables passed into the container
- TZ=Europe/Berlin # Sets the timezone inside the container
- PORT=3000 # Port KoalaSync listens on inside the container
- MIN_VERSION=1.0.0 # Minimum client version allowed to connect
- MAX_ROOMS=100 # Maximum number of rooms that can exist
- MAX_PEERS_PER_ROOM=25 # Maximum number of peers allowed per room
- ADMIN_METRICS_TOKEN= # Optional: 32+ char random token for aggregate-only /health metrics
pids_limit: 2048 # Limits the container to 2048 process IDs for safety
networks: # Attaches the service to the networks listed below
- caddy_net # Joins the pre-existing Caddy network for reverse proxying
networks: # Top-level networks definition
caddy_net: # Network name as referenced by the service
external: true # Marks the network as managed outside of Compose (created by Caddy)
+22
View File
@@ -0,0 +1,22 @@
services: # Top-level key defining all containers in this Compose file
koala-sync: # Name of the KoalaSync service
image: ghcr.io/shik3i/koalasync:latest # Pulls the latest KoalaSync image from GitHub Container Registry
container_name: KoalaSync # Sets a fixed container name instead of an auto-generated one
restart: always # Always restart the container if it stops or if Docker starts
ports: # Exposes the container port to the host
- "3000:3000" # Maps host port 3000 to container port 3000
environment: # Environment variables passed into the container
- TZ=Europe/Berlin # Sets the timezone inside the container
- PORT=3000 # Port KoalaSync listens on inside the container
- MIN_VERSION=1.0.0 # Minimum client version allowed to connect
- MAX_ROOMS=100 # Maximum number of rooms that can exist
- MAX_PEERS_PER_ROOM=25 # Maximum number of peers allowed per room
- ADMIN_METRICS_TOKEN= # Optional: 32+ char random token for aggregate-only /health metrics
pids_limit: 2048 # Limits the container to 2048 process IDs for safety
networks: # Attaches the service to the networks listed below
bond0_network: # Network name as referenced by the service
ipv4_address: 192.168.1.XXX # Static IPv4 address for the KoalaSync container
networks: # Top-level networks definition
bond0_network: # Network name inside Compose
external: true # Marks the network as managed outside of Compose
name: bond0 # Name of the pre-existing network on the Docker host
+104
View File
@@ -0,0 +1,104 @@
# Prometheus Community JSON Exporter Configuration Example
# File: examples/json_exporter.example.yml
#
# Use this configuration to map KoalaSync admin health metrics (JSON)
# to native Prometheus metrics.
#
# Usage:
# 1. Rename this file to json_exporter.yml
# 2. Replace "YOUR_ADMIN_METRICS_TOKEN" with your actual ADMIN_METRICS_TOKEN env value
# 3. Mount it to the json-exporter docker container: /config.yml
modules:
koalasync:
http_client_config:
bearer_token: "YOUR_ADMIN_METRICS_TOKEN"
metrics:
- name: koalasync_uptime_seconds
path: '{.uptime}'
help: "Uptime of the KoalaSync relay server in seconds"
- name: koalasync_rooms
path: '{.rooms}'
help: "Total active rooms"
- name: koalasync_connections
path: '{.connections}'
help: "Total active socket connections (sockets)"
- name: koalasync_peers
path: '{.peers}'
help: "Total connected peers across all rooms"
- name: koalasync_rooms_with_lobby
path: '{.roomsWithLobby}'
help: "Number of rooms waiting in an episode lobby"
- name: koalasync_avg_peers_per_room
path: '{.avgPeersPerRoom}'
help: "Average number of peers per room"
- name: koalasync_max_peers_in_room
path: '{.maxPeersInRoom}'
help: "Maximum number of peers in a single room"
- name: koalasync_memory_rss_bytes
path: '{.memory.rss}'
help: "Resident Set Size (RSS) memory usage in bytes"
- name: koalasync_memory_heap_used_bytes
path: '{.memory.heapUsed}'
help: "V8 engine heap used in bytes"
- name: koalasync_memory_heap_total_bytes
path: '{.memory.heapTotal}'
help: "V8 engine heap total in bytes"
# Rate limiter tracking — unique clients currently in each tracking window
# (not rate-limit denials; these include legitimate traffic too)
- name: koalasync_rate_limit_connections
path: '{.rateLimits.trackedClients.connections}'
help: "Unique clients tracked in the connection rate limiter window"
- name: koalasync_rate_limit_events
path: '{.rateLimits.trackedClients.events}'
help: "Unique sockets tracked in the event rate limiter window"
- name: koalasync_rate_limit_health
path: '{.rateLimits.trackedClients.health}'
help: "Unique IPs tracked in the health endpoint rate limiter window"
- name: koalasync_rate_limit_admin_metrics_auth
path: '{.rateLimits.trackedClients.adminMetricsAuth}'
help: "Unique IPs tracked in the admin metrics auth rate limiter window"
- name: koalasync_rate_limit_auth_failures
path: '{.rateLimits.trackedClients.authFailures}'
help: "Unique IPs tracked in the authentication failures cache"
- name: koalasync_rate_limit_room_list
path: '{.rateLimits.trackedClients.roomList}'
help: "Unique sockets in the room list cooldown cache"
# Actual rate-limit denials — incremented only when a 429 is served
- name: koalasync_rate_limit_denied_connections
path: '{.rateLimits.denied.connections}'
help: "Total connection attempts denied by rate limiter"
- name: koalasync_rate_limit_denied_events
path: '{.rateLimits.denied.events}'
help: "Total socket events denied by rate limiter"
- name: koalasync_rate_limit_denied_health
path: '{.rateLimits.denied.health}'
help: "Total health endpoint requests denied by rate limiter"
- name: koalasync_rate_limit_denied_admin_metrics_auth
path: '{.rateLimits.denied.adminMetricsAuth}'
help: "Total admin metrics auth attempts denied by rate limiter"
- name: koalasync_rate_limit_denied_room_list
path: '{.rateLimits.denied.roomList}'
help: "Total room list refresh requests denied by rate limiter"
+17 -4
View File
@@ -6,13 +6,14 @@ A Manifest V3 Browser Extension (Chrome & Firefox) for synchronized video playba
- **Manifest V3**: Optimized Service Worker architecture with session persistence.
- **Pure Vanilla JS**: No external dependencies or heavy libraries.
- **Smart Peer IDs**: Hexadecimal IDs combined with customizable Usernames for easy identification.
- **Dual Heartbeat**: Advanced session tracking (Background) and video synchronization (Content) to prevent ghost sessions.
- **On-Demand Connection**: The service worker only maintains a WebSocket connection while you're in a room. No persistent background connections — privacy-first architecture. Based on `connectIntent` flag that gates all reconnect attempts.
- **Live Diagnostics**: Built-in "Dev" tab for real-time video state debugging (ReadyState, CurrentTime, etc.).
- **Dynamic i18n (Multi-Language)**: Fully localized in 13 languages (`en`, `de`, `fr`, `es`, `it`, `pl`, `tr`, `nl`, `ja`, `ko`, `pt-BR`, `pt`, `ru`) with auto-detected fallback and dynamic on-the-fly language selectors.
## Tab Overview
1. **Room**: Manage connections, view active peers, and share invitation links.
2. **Sync**: Control video playback (Play/Pause/Force Sync) and view recent activity.
3. **Settings**: Customize your Username and toggle domain-based Noise Filtering.
3. **Settings**: Customize your Username, toggle domain-based Noise Filtering, and switch the App Language.
4. **Dev**: Monitor connection status and view real-time video element metadata for debugging.
## Privacy & Permissions
@@ -25,7 +26,7 @@ KoalaSync requires `<all_urls>` permission to detect and interact with video ele
## Installation
1. **Prepare Extension**: From the repository root, run:
```bash
node scripts/build-extension.js
npm run build:extension
```
2. Open Chrome and go to `chrome://extensions/`.
3. Enable **Developer mode** (top right).
@@ -34,6 +35,18 @@ KoalaSync requires `<all_urls>` permission to detect and interact with video ele
## Development
If you modify `shared/constants.js`, you must synchronize the changes by running the build script from the root:
```bash
node scripts/build-extension.js
npm run build:extension
```
This ensures that the `extension/shared` folder is updated with the latest protocol constants.
## Module Structure
| File | Purpose |
|---|---|
| `background.js` | Service worker: message routing, tab listeners, startup |
| `content.js` | Video detection, audio processing, episode transition (IIFE) |
| `popup.js` | Popup UI: join/create, tabs, status, settings |
| `bridge.js` | Landing page bridge (injected into sync.koalastuff.net) |
| `episode-utils.js` | Shared `extractEpisodeId()` / `sameEpisode()` — used by background.js, injected into content.js at build time |
| `i18n.js` | Translation loader |
| `shared/` | Constants, blacklist, name generator |
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "Synchronisiere die Videowiedergabe auf YouTube, Netflix, Emby, Jellyfin und jeder HTML5-Seite in Echtzeit mit Freunden."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "Synchronize video playback on YouTube, Netflix, Emby, Jellyfin, and any HTML5 site in real-time with friends."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "Mira videos junto a tus amigos en perfecta sincronización. Compatible con Netflix, YouTube, Twitch, Prime Video, Disney+ y cualquier reproductor HTML5."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "Synchronisez la lecture vidéo sur YouTube, Netflix, Emby, Jellyfin et tout site HTML5 en temps réel avec vos amis."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "Sincronizza la riproduzione video su YouTube, Netflix, Emby, Jellyfin e qualsiasi sito HTML5 in tempo reale con gli amici."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "YouTube、Netflix、Emby、Jellyfin、その他HTML5サイトでの動画再生を友達とリアルタイムで同期します。"
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "YouTube, Netflix, Emby, Jellyfin 및 모든 HTML5 사이트에서 친구들과 실시간으로 비디오 재생을 동기화하세요."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "Synchroniseer video-afspelen op YouTube, Netflix, Emby, Jellyfin en elke HTML5-site in realtime met vrienden."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "Synchronizuj odtwarzanie wideo na YouTube, Netflix, Emby, Jellyfin i dowolnej stronie HTML5 w czasie rzeczywistym ze znajomymi."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "Assista a vídeos junto com seus amigos em sincronia perfeita. Compatível com Netflix, YouTube, Twitch, Prime Video, Disney+ e qualquer player HTML5."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "Veja vídeos em conjunto com os seus amigos em sincronia perfeita. Compatível com Netflix, YouTube, Twitch, Prime Video, Disney+ e qualquer leitor HTML5."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "Синхронизируйте воспроизведение видео на YouTube, Netflix, Emby, Jellyfin и любых HTML5-сайтах в реальном времени с друзьями."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "YouTube, Netflix, Emby, Jellyfin ve herhangi bir HTML5 sitesinde video oynatmayı arkadaşlarınızla gerçek zamanlı olarak senkronize edin."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "Синхронізуйте відтворення відео на YouTube, Netflix, Emby, Jellyfin і будь-якому сайті HTML5 у режимі реального часу з друзями."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "与朋友实时同步YouTube、Netflix、Emby、Jellyfin和任何HTML5网站上的视频播放。"
}
}
Binary file not shown.
+247
View File
@@ -0,0 +1,247 @@
:root {
--bg: #0f172a;
--card: #1e293b;
--panel: #172033;
--accent: #6366f1;
--accent-hover: #818cf8;
--text: #f8fafc;
--text-muted: #94a3b8;
--border: #334155;
--radius: 8px;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
background: var(--bg);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 14px;
}
.page-shell {
width: min(760px, calc(100vw - 32px));
margin: 0 auto;
padding: 32px 0;
}
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
margin-bottom: 18px;
}
.back-link {
display: inline-block;
color: var(--text-muted);
text-decoration: none;
font-size: 12px;
margin-bottom: 6px;
transition: color 0.2s;
}
.back-link:hover {
color: var(--accent);
}
.eyebrow {
margin: 0 0 4px;
color: var(--text-muted);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
h1,
h2,
p {
margin: 0;
}
h1 {
color: var(--accent-hover);
font-size: 28px;
font-weight: 800;
}
h2 {
font-size: 16px;
}
.panel {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.muted-panel {
color: var(--text-muted);
}
.section-heading,
.master-toggle,
.inline-toggle {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.master-toggle,
.inline-toggle {
color: var(--text-muted);
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
}
.preset-group {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
gap: 10px;
margin: 18px 0;
}
.preset-card {
min-height: 44px;
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text);
cursor: pointer;
transition: border-color 0.2s, box-shadow 0.2s;
}
.preset-card:hover {
border-color: rgba(99, 102, 241, 0.4);
}
.preset-card:has(input:checked) {
border-color: var(--accent);
box-shadow: 0 0 0 1px rgba(99, 102, 241, 0.35);
}
.preset-card input {
accent-color: var(--accent);
}
.custom-grid {
display: grid;
gap: 12px;
padding: 16px;
background: rgba(15, 23, 42, 0.58);
border: 1px solid rgba(148, 163, 184, 0.16);
border-radius: var(--radius);
transition: opacity 0.3s;
}
.control-row {
display: grid;
grid-template-columns: 110px minmax(160px, 1fr) 78px 30px;
align-items: center;
gap: 10px;
}
.control-row label {
color: var(--text-muted);
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
}
input[type="range"] {
width: 100%;
accent-color: var(--accent);
}
input[type="number"] {
width: 100%;
padding: 8px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text);
font: inherit;
transition: border-color 0.2s;
}
input[type="number"]:focus {
border-color: var(--accent);
outline: none;
}
.unit {
color: var(--text-muted);
font-size: 12px;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 38px;
height: 22px;
flex: 0 0 auto;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
inset: 0;
background-color: #334155;
transition: .3s;
border-radius: 22px;
}
.slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 3px;
bottom: 3px;
background-color: #94a3b8;
transition: .3s;
border-radius: 50%;
}
input:checked + .slider {
background-color: var(--accent);
}
input:checked + .slider:before {
transform: translateX(16px);
background-color: white;
}
@media (max-width: 620px) {
.page-header,
.section-heading {
align-items: flex-start;
flex-direction: column;
}
.control-row {
grid-template-columns: 1fr 74px 28px;
}
.control-row label {
grid-column: 1 / -1;
}
}
+104
View File
@@ -0,0 +1,104 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>KoalaSync Audio Settings</title>
<link rel="stylesheet" href="audio-options.css">
</head>
<body>
<main class="page-shell">
<header class="page-header">
<div>
<a id="backLink" href="#" class="back-link" data-i18n="AUDIO_BACK">← Back</a>
<p class="eyebrow">KoalaSync</p>
<h1 data-i18n="AUDIO_PAGE_TITLE">Audio Settings</h1>
</div>
<label class="master-toggle">
<span data-i18n="AUDIO_MASTER_TOGGLE">Audio Processing</span>
<span class="toggle-switch">
<input type="checkbox" id="audioEnabled">
<span class="slider"></span>
</span>
</label>
</header>
<section class="panel">
<div class="section-heading">
<h2 data-i18n="AUDIO_COMPRESSOR">Compressor</h2>
<label class="inline-toggle">
<span data-i18n="AUDIO_COMPRESSOR_ENABLE">Enabled</span>
<span class="toggle-switch">
<input type="checkbox" id="compressorEnabled">
<span class="slider"></span>
</span>
</label>
</div>
<div class="preset-group" role="radiogroup" aria-label="Compressor preset">
<label class="preset-card">
<input type="radio" name="preset" value="recommended">
<span data-i18n="AUDIO_PRESET_RECOMMENDED">Recommended</span>
</label>
<label class="preset-card">
<input type="radio" name="preset" value="dynamicRange">
<span data-i18n="AUDIO_PRESET_DYNAMIC_RANGE">Dynamic Range</span>
</label>
<label class="preset-card">
<input type="radio" name="preset" value="vocalEnhancement">
<span data-i18n="AUDIO_PRESET_VOCAL_ENHANCEMENT">Vocal Enhancement</span>
</label>
<label class="preset-card">
<input type="radio" name="preset" value="smooth">
<span data-i18n="AUDIO_PRESET_SMOOTH">Smooth</span>
</label>
<label class="preset-card">
<input type="radio" name="preset" value="custom">
<span data-i18n="AUDIO_PRESET_CUSTOM">Custom</span>
</label>
</div>
<div class="custom-grid" id="customControls">
<div class="control-row" data-param="threshold">
<label data-i18n="AUDIO_PARAM_THRESHOLD">Threshold</label>
<input type="range" min="-60" max="0" step="1">
<input type="number" min="-60" max="0" step="1">
<span class="unit">dB</span>
</div>
<div class="control-row" data-param="knee">
<label data-i18n="AUDIO_PARAM_KNEE">Knee</label>
<input type="range" min="0" max="40" step="1">
<input type="number" min="0" max="40" step="1">
<span class="unit">dB</span>
</div>
<div class="control-row" data-param="ratio">
<label data-i18n="AUDIO_PARAM_RATIO">Ratio</label>
<input type="range" min="1" max="20" step="0.5">
<input type="number" min="1" max="20" step="0.5">
<span class="unit">:1</span>
</div>
<div class="control-row" data-param="attack">
<label data-i18n="AUDIO_PARAM_ATTACK">Attack</label>
<input type="range" min="0" max="1" step="0.001">
<input type="number" min="0" max="1000" step="1" data-ms-input="true">
<span class="unit">ms</span>
</div>
<div class="control-row" data-param="release">
<label data-i18n="AUDIO_PARAM_RELEASE">Release</label>
<input type="range" min="0" max="1" step="0.005">
<input type="number" min="0" max="1000" step="5" data-ms-input="true">
<span class="unit">ms</span>
</div>
</div>
</section>
<section class="panel muted-panel">
<div class="section-heading">
<h2 data-i18n="AUDIO_EQUALIZER">Equalizer</h2>
</div>
<p data-i18n="AUDIO_COMING_SOON">Coming soon</p>
</section>
</main>
<script src="audio-options.js" type="module"></script>
</body>
</html>
+192
View File
@@ -0,0 +1,192 @@
import { loadLocale, translateDOM, getSystemLanguage } from './i18n.js';
const PRESETS = {
recommended: { threshold: -24, ratio: 8, attack: 0.010, release: 0.300, knee: 15 },
dynamicRange: { threshold: -18, ratio: 4, attack: 0.020, release: 0.200, knee: 10 },
vocalEnhancement: { threshold: -12, ratio: 3, attack: 0.015, release: 0.150, knee: 5 },
smooth: { threshold: -30, ratio: 1.5, attack: 0.030, release: 0.250, knee: 20 },
custom: { threshold: -24, ratio: 12, attack: 0.003, release: 0.250, knee: 30 }
};
const DEFAULT_AUDIO_SETTINGS = {
enabled: false,
compressor: {
enabled: false,
preset: 'recommended',
customParams: { ...PRESETS.custom }
}
};
const PARAM_LIMITS = {
threshold: { min: -60, max: 0 },
knee: { min: 0, max: 40 },
ratio: { min: 1, max: 20 },
attack: { min: 0, max: 1 },
release: { min: 0, max: 1 }
};
const elements = {
audioEnabled: document.getElementById('audioEnabled'),
compressorEnabled: document.getElementById('compressorEnabled'),
presetInputs: Array.from(document.querySelectorAll('input[name="preset"]')),
controlRows: Array.from(document.querySelectorAll('.control-row')),
backLink: document.getElementById('backLink')
};
let saveTimer = null;
let isRendering = false;
function cloneDefaultSettings() {
return JSON.parse(JSON.stringify(DEFAULT_AUDIO_SETTINGS));
}
let currentSettings = cloneDefaultSettings();
function mergeAudioSettings(settings = {}) {
const safeSettings = settings && typeof settings === 'object' ? settings : {};
const defaults = cloneDefaultSettings();
return {
...defaults,
...safeSettings,
compressor: {
...defaults.compressor,
...(safeSettings.compressor || {}),
customParams: {
...defaults.compressor.customParams,
...(safeSettings.compressor?.customParams || {})
}
}
};
}
function debounceSave() {
if (saveTimer) clearTimeout(saveTimer);
saveTimer = setTimeout(() => {
chrome.storage.local.set({ audioSettings: currentSettings });
}, 40);
}
function getParamValue(param, value, isMsInput = false) {
const parsed = Number(value);
const candidate = Number.isFinite(parsed)
? (isMsInput ? parsed / 1000 : parsed)
: currentSettings.compressor.customParams[param];
const limits = PARAM_LIMITS[param];
if (!limits) return candidate;
return Math.min(limits.max, Math.max(limits.min, candidate));
}
function formatNumber(value, param, isMsInput = false) {
if (isMsInput) return Math.round(value * 1000);
if (param === 'ratio') return Number(value).toFixed(1).replace(/\.0$/, '');
return value;
}
function render() {
isRendering = true;
elements.audioEnabled.checked = currentSettings.enabled === true;
elements.compressorEnabled.checked = currentSettings.compressor.enabled === true;
const selectedPreset = currentSettings.compressor.preset || 'recommended';
elements.presetInputs.forEach(input => {
input.checked = input.value === selectedPreset;
});
const params = selectedPreset === 'custom'
? currentSettings.compressor.customParams
: PRESETS[selectedPreset] || PRESETS.recommended;
elements.controlRows.forEach(row => {
const param = row.dataset.param;
const range = row.querySelector('input[type="range"]');
const number = row.querySelector('input[type="number"]');
const value = params[param];
range.value = value;
number.value = formatNumber(value, param, number.dataset.msInput === 'true');
});
isRendering = false;
}
function setPreset(preset) {
currentSettings.compressor.preset = preset;
if (preset === 'custom') {
currentSettings.compressor.customParams = {
...PRESETS.custom,
...currentSettings.compressor.customParams
};
}
render();
debounceSave();
}
function setCustomParam(param, value) {
currentSettings.compressor.preset = 'custom';
currentSettings.compressor.customParams[param] = getParamValue(param, value);
render();
debounceSave();
}
async function init() {
// Local-only: audioSettings/locale are never read from storage.sync.
const { audioSettings, locale } = await chrome.storage.local.get(['audioSettings', 'locale']);
const lang = locale || getSystemLanguage();
await loadLocale(lang);
translateDOM();
currentSettings = mergeAudioSettings(audioSettings);
render();
}
elements.audioEnabled.addEventListener('change', () => {
currentSettings.enabled = elements.audioEnabled.checked;
if (currentSettings.enabled && !currentSettings.compressor.enabled) {
currentSettings.compressor.enabled = true;
}
render();
debounceSave();
});
elements.compressorEnabled.addEventListener('change', () => {
currentSettings.compressor.enabled = elements.compressorEnabled.checked;
if (currentSettings.compressor.enabled) currentSettings.enabled = true;
render();
debounceSave();
});
elements.presetInputs.forEach(input => {
input.addEventListener('change', () => {
if (input.checked) setPreset(input.value);
});
});
elements.controlRows.forEach(row => {
const param = row.dataset.param;
const range = row.querySelector('input[type="range"]');
const number = row.querySelector('input[type="number"]');
range.addEventListener('input', () => {
if (isRendering) return;
setCustomParam(param, getParamValue(param, range.value));
});
number.addEventListener('input', () => {
if (isRendering) return;
setCustomParam(param, getParamValue(param, number.value, number.dataset.msInput === 'true'));
});
});
chrome.storage.onChanged.addListener((changes, area) => {
if (area !== 'local' || !changes.audioSettings) return;
currentSettings = mergeAudioSettings(changes.audioSettings.newValue);
render();
});
if (elements.backLink) {
elements.backLink.addEventListener('click', (e) => {
e.preventDefault();
window.close();
});
}
init().catch(err => {
console.error('[AudioOptions] Failed to initialize:', err);
});
+1039 -273
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -10,6 +10,7 @@ document.documentElement.dataset.koalasyncInstalled = 'true';
// 2. Listen for Join Requests from the Website
window.addEventListener('KOALASYNC_JOIN_REQUEST', (e) => {
if (!e || !e.detail) return;
const { roomId, password, useCustomServer, serverUrl } = e.detail;
chrome.runtime.sendMessage({
type: 'WEB_JOIN_REQUEST',
@@ -17,11 +18,12 @@ window.addEventListener('KOALASYNC_JOIN_REQUEST', (e) => {
password,
useCustomServer,
serverUrl
});
}).catch(() => {});
});
// 3. Listen for Status Updates from the Extension and relay to Website
chrome.runtime.onMessage.addListener((msg) => {
if (!msg) return;
if (msg.type === 'JOIN_STATUS') {
const detail = { success: msg.success, message: msg.message };
// Firefox MV3 content scripts run in an isolated world. When dispatching
+829 -55
View File
File diff suppressed because it is too large Load Diff
+24
View File
@@ -0,0 +1,24 @@
/**
* KoalaSync Episode Title Utilities
* Single source of truth — synced to content.js by build-extension.js.
* Keep in sync with the injection block in content.js!
*/
export function extractEpisodeId(title) {
if (!title || typeof title !== 'string') return null;
const se = title.match(/S(?:eason\s*)?(\d+)[^a-zA-Z0-9]*E(?:pisode\s*)?(\d+)/i);
if (se) return `S${String(se[1]).padStart(2, '0')}E${String(se[2]).padStart(2, '0')}`;
const ep = title.match(/(?:Episode|Folge|Ep\.?|#)\s*(\d+)/i);
if (ep) return `EP${String(ep[1]).padStart(3, '0')}`;
return null;
}
export function sameEpisode(titleA, titleB) {
if (!titleA && !titleB) return true;
if (!titleA || !titleB) return false;
const idA = extractEpisodeId(titleA);
const idB = extractEpisodeId(titleB);
if (idA && idB) return idA === idB;
if (idA || idB) return false;
return titleA === titleB;
}
+136
View File
@@ -0,0 +1,136 @@
// extension/i18n.js
export const SUPPORTED_LANGUAGES = ['en', 'de', 'fr', 'es', 'it', 'nl', 'pl', 'pt', 'pt-BR', 'tr', 'ru', 'ja', 'ko', 'zh', 'uk'];
export const DEFAULT_LANGUAGE = 'en';
let activeDictionary = {};
const dictionaryCache = {};
let currentLanguage = null;
/**
* Resolves, loads, and merges the target language with the English baseline fallback.
* @param {string} langCode - Target language (e.g. 'de')
*/
export async function loadLocale(langCode) {
const resolvedLang = SUPPORTED_LANGUAGES.includes(langCode) ? langCode : DEFAULT_LANGUAGE;
if (currentLanguage === resolvedLang && Object.keys(activeDictionary).length > 0) {
return;
}
if (dictionaryCache[resolvedLang]) {
activeDictionary = dictionaryCache[resolvedLang];
currentLanguage = resolvedLang;
return;
}
try {
// Load Baseline English
let enDict;
if (dictionaryCache[DEFAULT_LANGUAGE]) {
enDict = dictionaryCache[DEFAULT_LANGUAGE];
} else {
const enResponse = await fetch(chrome.runtime.getURL(`locales/${DEFAULT_LANGUAGE}.json`));
enDict = await enResponse.json();
dictionaryCache[DEFAULT_LANGUAGE] = enDict;
}
if (resolvedLang === DEFAULT_LANGUAGE) {
activeDictionary = enDict;
currentLanguage = resolvedLang;
return;
}
// Load Target Locale
const targetResponse = await fetch(chrome.runtime.getURL(`locales/${resolvedLang}.json`));
const targetDict = await targetResponse.json();
// Airtight Fallback Merge: target overrides en, missing elements fallback to en
const mergedDict = Object.assign({}, enDict, targetDict);
dictionaryCache[resolvedLang] = mergedDict;
activeDictionary = mergedDict;
currentLanguage = resolvedLang;
} catch (err) {
console.error('[i18n] Failed to load locale. Defaulting to English:', err);
// Fallback directly to static English if fetching fails
try {
let enDict;
if (dictionaryCache[DEFAULT_LANGUAGE]) {
enDict = dictionaryCache[DEFAULT_LANGUAGE];
} else {
const enResponse = await fetch(chrome.runtime.getURL(`locales/${DEFAULT_LANGUAGE}.json`));
enDict = await enResponse.json();
dictionaryCache[DEFAULT_LANGUAGE] = enDict;
}
activeDictionary = enDict;
currentLanguage = DEFAULT_LANGUAGE;
} catch (_) {
activeDictionary = {};
currentLanguage = null;
}
}
}
/**
* Returns the translated string for a given key. Supports optional value interpolation.
* @param {string} key - Dictionary key
* @param {object} [placeholders] - Key-value map for replacements (e.g., { name: 'Alice' })
* @returns {string} Translated string or the key itself
*/
export function getMessage(key, placeholders = null) {
let msg = activeDictionary[key] !== undefined ? String(activeDictionary[key]) : key;
if (placeholders && typeof placeholders === 'object') {
for (const [k, v] of Object.entries(placeholders)) {
msg = msg.replace(new RegExp(`{${k}}`, 'g'), v);
}
}
return msg;
}
/**
* Performs dynamic DOM replacements for elements carrying data-i18n attributes.
*/
export function translateDOM() {
// 1. Text Content
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
const translated = getMessage(key);
// Special case: Preserve logo image elements inside headers (like h1 logo)
const img = el.querySelector('img');
if (img) {
el.innerHTML = '';
el.appendChild(img);
el.appendChild(document.createTextNode(' ' + translated));
} else {
el.textContent = translated;
}
});
// 2. Tooltips (titles)
document.querySelectorAll('[data-i18n-title]').forEach(el => {
const key = el.getAttribute('data-i18n-title');
el.setAttribute('title', getMessage(key));
});
// 3. Placeholders
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
const key = el.getAttribute('data-i18n-placeholder');
el.setAttribute('placeholder', getMessage(key));
});
}
/**
* Detects and maps the user's system language to the best supported locale.
* @returns {string} Supported language code
*/
export function getSystemLanguage() {
const uiLang = (typeof chrome !== 'undefined' && chrome.i18n && chrome.i18n.getUILanguage)
? chrome.i18n.getUILanguage()
: '';
const fullLang = (navigator.language || uiLang || '').toLowerCase();
if (fullLang.startsWith('pt-br')) {
return 'pt-BR';
}
const baseLang = fullLang.split('-')[0];
return SUPPORTED_LANGUAGES.includes(baseLang) ? baseLang : DEFAULT_LANGUAGE;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 889 B

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+233
View File
@@ -0,0 +1,233 @@
{
"LANG_CODE": "de",
"HTML_CLASS": "lang-de",
"APP_TITLE": "KoalaSync",
"TAB_ROOM": "Raum",
"TAB_ROOM_TOOLTIP": "Raum-Einstellungen und Verbindung",
"TAB_SYNC": "Sync",
"TAB_SYNC_TOOLTIP": "Video-Synchronisations-Steuerung und Aktionen",
"TAB_SETTINGS": "Optionen",
"TAB_SETTINGS_TOOLTIP": "Erweiterungseinstellungen",
"TAB_STATUS": "Status",
"TAB_STATUS_TOOLTIP": "Erweiterte Diagnose & Protokolle",
"BTN_CREATE_ROOM": "+ Neuer Raum",
"MANUAL_CONNECT_HEADER": "Manuell verbinden / Erweitert",
"LABEL_SERVER": "Server",
"BTN_SERVER_OFFICIAL": "Offiziell",
"BTN_SERVER_OFFICIAL_TOOLTIP": "Verwende den offiziellen, zuverlässigen Server",
"BTN_SERVER_CUSTOM": "Eigener",
"BTN_SERVER_CUSTOM_TOOLTIP": "Verbinde dich mit deinem eigenen, selbstgehosteten Server",
"PLACEHOLDER_SERVER_URL": "wss://dein-server:3000",
"LABEL_ROOM_ID": "Raum-ID",
"LABEL_ROOM_ID_TOOLTIP": "Der eindeutige Identifikator für deinen Sync-Raum",
"PLACEHOLDER_ROOM_ID": "Raum-ID eingeben",
"PLACEHOLDER_ROOM_ID_TOOLTIP": "Die eindeutige ID des Raums, dem du beitreten möchtest",
"LABEL_PASSWORD": "Passwort (Optional)",
"LABEL_PASSWORD_TOOLTIP": "Optionales Passwort, um den Raumzugang zu beschränken",
"PLACEHOLDER_PASSWORD": "Raum-Passwort (optional)",
"PLACEHOLDER_PASSWORD_TOOLTIP": "Passwort für den Raum (leer lassen, wenn keines vorhanden)",
"BTN_JOIN_ROOM": "Raum beitreten / erstellen",
"BTN_JOIN_ROOM_TOOLTIP": "Mit dem Raum verbinden",
"LABEL_PUBLIC_ROOMS": "Öffentliche Räume",
"LABEL_PUBLIC_ROOMS_TOOLTIP": "Liste der öffentlich verfügbaren Räume auf diesem Server",
"BTN_REFRESH": "AKTUALISIEREN",
"BTN_REFRESH_TOOLTIP": "Die Liste der öffentlichen Räume aktualisieren",
"PUBLIC_ROOMS_REFRESHING": "Aktualisiere...",
"BTN_REFRESH_COOLDOWN": "WARTE {seconds}s",
"BTN_REFRESH_COOLDOWN_TOOLTIP": "Die Raumliste kühlt ab. Versuche es in {seconds}s erneut.",
"PUBLIC_ROOMS_REFRESHING_COOLDOWN": "Aktualisiere öffentliche Räume. Nächste Aktualisierung in {seconds}s verfügbar.",
"LABEL_ACTIVE_ROOM": "Aktiver Raum",
"LABEL_ACTIVE_ROOM_TOOLTIP": "Der Raum, mit dem du gerade verbunden bist",
"ACTIVE_ROOM_NONE": "KEINER",
"ACTIVE_SERVER_OFFICIAL": "Offizieller Server",
"LABEL_INVITE_LINK": "Einladungslink",
"LABEL_INVITE_LINK_TOOLTIP": "Teile diesen Link mit Freunden, damit sie beitreten können",
"LABEL_PEERS_IN_ROOM": "Teilnehmer im Raum",
"LABEL_PEERS_IN_ROOM_TOOLTIP": "Andere Benutzer, die derzeit mit diesem Raum verbunden sind",
"LABEL_HOST_CONTROL": "Host-Steuerung",
"LABEL_HOST_CONTROL_TOOLTIP": "Festlegen, wer die Wiedergabe in diesem Raum steuern darf",
"LABEL_HOST_ONLY_TOGGLE": "Nur ich darf die Wiedergabe steuern",
"NOTICE_HOST_CONTROLS": "Der Host steuert die Wiedergabe für alle.",
"NOTICE_COHOST_HINT": "Tipp: Gib einzelnen Teilnehmern unten in der Teilnehmerliste die Steuerung.",
"BADGE_HOST": "Host",
"BADGE_GUEST": "Gast",
"BADGE_CONTROLLER": "Controller",
"BTN_GIVE_CONTROL": "Steuerung geben",
"BTN_REVOKE_CONTROL": "Entziehen",
"BADGE_DESYNCED": "Solo",
"TOOLTIP_PEER_DESYNCED": "Schaut alleine — ignoriert die Befehle des Hosts",
"HCM_DIALOG_TITLE": "KoalaSync · Der Host steuert diesen Raum",
"HCM_DIALOG_BODY": "Nur der Host kann die Wiedergabe in diesem Raum steuern. Weiter gemeinsam schauen oder allein weiterschauen?",
"HCM_DIALOG_STAY": "Synchron bleiben",
"HCM_DIALOG_SOLO": "Allein weiterschauen",
"HCM_BADGE_SOLO": "Du schaust allein",
"HCM_BADGE_RESYNC": "Neu sync",
"NO_PEERS_CONNECTED": "Keine Teilnehmer verbunden",
"BTN_LEAVE_ROOM": "Raum verlassen",
"LABEL_SELECT_VIDEO": "Video auswählen",
"LABEL_SELECT_VIDEO_TOOLTIP": "Wähle den Browser-Tab aus, der das zu synchronisierende Video enthält",
"OPTION_SELECT_TAB": "-- Wähle einen Tab --",
"LABEL_REMOTE_CONTROL": "Fernsteuerung",
"BTN_COPY_INVITE": "📋 Einladungslink",
"BTN_COPY_INVITE_TOOLTIP": "Einladungslink kopieren",
"BTN_PLAY": "▶ Abspielen",
"BTN_PLAY_TOOLTIP": "Sende einen Play-Befehl an alle",
"BTN_PAUSE": "⏸ Pause",
"BTN_PAUSE_TOOLTIP": "Sende einen Pause-Befehl an alle",
"BTN_SYNC": "⚡ SYNC",
"BTN_SYNC_TOOLTIP": "Erzwinge die Synchronisation aller Teilnehmer",
"OPTION_JUMP_TO_OTHERS": "Zu anderen springen",
"OPTION_JUMP_TO_ME": "Zu mir springen",
"OPTION_JUMP_MODE_TOOLTIP": "Synchronisationsziel auswählen",
"LABEL_LAST_ACTIVITY": "Letzter Aktivitätsstatus",
"LABEL_LAST_ACTIVITY_TOOLTIP": "Zeigt den neuesten Play-, Pause- oder Seek-Befehl",
"NO_RECENT_COMMANDS": "Keine aktuellen Befehle",
"LOBBY_HEADER": "EPISODEN-LOBBY",
"LOBBY_WAITING_FOR": "🎬 Warte auf: \"{title}\"",
"LOBBY_WAITING_PEERS": "Warte auf Teilnehmer...",
"BTN_SKIP_PLAY": "Überspringen & Abspielen",
"BTN_SKIP_PLAY_TOOLTIP": "Lobby abbrechen und trotzdem abspielen",
"LOBBY_CONNECT_FIRST": "Zuerst Raum beitreten",
"LOBBY_CONNECT_FIRST_DESC": "Du musst über einen Einladungslink beitreten oder einen neuen Raum erstellen, um Videos zu synchronisieren.",
"BTN_CREATE_ROOM_ALT": "Neuen Raum erstellen",
"BTN_CREATE_ROOM_ALT_TOOLTIP": "Einen neuen zufälligen Raum erstellen und beitreten",
"LABEL_USERNAME": "Dein Benutzername",
"LABEL_USERNAME_TOOLTIP": "Der Benutzername hilft anderen, dich zu identifizieren.",
"PLACEHOLDER_USERNAME": "Anonymer Koala",
"LABEL_HIDE_CLUTTER": "Aufgeräumte Tab-Liste",
"LABEL_HIDE_CLUTTER_TOOLTIP": "Filtert Nicht-Video-Tabs und irrelevante Domains heraus, um die Liste sauber zu halten",
"LABEL_AUTO_SYNC_NEXT": "Nächste Episode auto-syncen",
"LABEL_AUTO_SYNC_NEXT_TOOLTIP": "Pausiert automatisch und wartet auf alle Teilnehmer bei Episodenwechsel, startet dann synchron.",
"LABEL_AUTO_COPY_INVITE": "Einladungslink auto-kopieren",
"LABEL_AUTO_COPY_INVITE_TOOLTIP": "Kopiert den Einladungslink automatisch beim Erstellen eines Raums.",
"LABEL_NOTIFICATIONS": "Browser-Benachrichtigungen",
"LABEL_NOTIFICATIONS_TOOLTIP": "Zeigt Systembenachrichtigungen, wenn jemand beitritt/verlässt oder abspielt/pausiert.",
"LABEL_LANGUAGE": "Sprache",
"LABEL_LANGUAGE_TOOLTIP": "Wähle deine bevorzugte Sprache für die Erweiterung aus",
"LABEL_TROUBLESHOOTING": "Fehlerbehebung",
"LABEL_TROUBLESHOOTING_TOOLTIP": "Werkzeuge zur Behebung von Verbindungsproblemen",
"BTN_REGEN_ID": "Peer-ID neu generieren",
"BTN_REGEN_ID_TOOLTIP": "Generiere deine interne ID neu und verbinde dich erneut",
"REGEN_ID_DESC": "Verwende dies, wenn du Fehler wegen doppelter Identität siehst.",
"REGEN_ID_OTHER_ISSUE": "Anderes Problem? Öffne ein GitHub Issue",
"TOAST_ID_REGENERATED": "Identität neu generiert — Verbindung wird wiederhergestellt…",
"LABEL_CONN_STATUS": "Verbindungsstatus",
"LABEL_CONN_STATUS_TOOLTIP": "Aktueller WebSocket-Verbindungsstatus",
"CONN_STATUS_DISCONNECTED": "Getrennt",
"BTN_RETRY": "WIEDERHOLEN",
"BTN_RETRY_TOOLTIP": "Versuchen, die Verbindung zum Server wiederherzustellen",
"BTN_COPY_LOGS": "Logs kopieren",
"BTN_COPY_LOGS_TOOLTIP": "Logs zum Teilen in die Zwischenablage kopieren",
"LABEL_VIDEO_DEBUG": "Video-Debug-Info",
"LABEL_VIDEO_DEBUG_TOOLTIP": "Technische Details zum aktuell ausgewählten Video-Element",
"VIDEO_DEBUG_EMPTY": "Kein Tab ausgewählt oder kein Video erkannt.",
"LABEL_HISTORY": "Aktionsverlauf",
"LABEL_HISTORY_TOOLTIP": "Chronologisches Protokoll aller Sync-Befehle im Raum",
"HISTORY_EMPTY": "Noch keine Aktivität",
"LABEL_LOGS": "Logs (Letzte 50)",
"LABEL_LOGS_TOOLTIP": "Technische Verbindungsprotokolle zur Fehlersuche",
"BTN_CLEAR": "LEEREN",
"BTN_CLEAR_TOOLTIP": "Protokollausgabe leeren",
"LABEL_GITHUB": "GitHub-Repository",
"BTN_ONBOARDING_SKIP": "Überspringen",
"BTN_ONBOARDING_SKIP_TOOLTIP": "Einführung überspringen",
"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_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",
"ONBOARDING_3_TEXT": "Wähle hier das Video aus, das du synchronisieren willst. Abspielen, Pause, Spulen — alle bleiben synchron.",
"ONBOARDING_4_TITLE": "3. Personalisieren",
"ONBOARDING_4_TEXT": "Wähle einen lustigen Benutzernamen, damit deine Freunde wissen, wer du bist.",
"ONBOARDING_5_TITLE": "Alles startklar!",
"ONBOARDING_5_TEXT": "Zeit, das Popcorn zu holen. Viel Spaß beim gemeinsamen Schauen!",
"ERR_CONN_TIMEOUT": "Verbindung abgelaufen. Bitte versuche es erneut.",
"ERR_INVALID_SERVER_URL": "Ungültiges Server-URL-Format.",
"ERR_IDENTITY_NOT_LOADED": "Identität noch nicht geladen. Warte kurz und versuche es erneut.",
"ERR_NO_PEERS_TIME": "Keine anderen Teilnehmer mit bekannter Zeit. Wechsle zu 'Zu mir springen'.",
"ERR_NO_VIDEO_TAB": "Verbindung zum Video-Tab fehlgeschlagen.",
"ERR_SELECT_VIDEO": "Bitte wähle zuerst ein Video aus!",
"TOAST_INVITE_COPIED": "Einladungslink kopiert!",
"TOAST_COPY_FAILED": "Kopieren in Zwischenablage fehlgeschlagen",
"TOAST_LOBBY_SKIPPED": "Episoden-Lobby übersprungen.",
"TOAST_LOBBY_SKIP_FAILED": "Überspringen der Lobby fehlgeschlagen.",
"TOAST_LOGS_COPIED": "Kopiert!",
"TOAST_PEER_JOINED": "{name} ist dem Raum beigetreten",
"TOAST_PEER_LEFT": "{name} hat den Raum verlassen",
"TOAST_PEER_ACTION": "{name} hat {action}",
"STATUS_CONNECTED": "Verbunden",
"STATUS_RECONNECTING": "Verbinde erneut...",
"STATUS_CONNECTING": "Verbinde...",
"STATUS_FAILED": "Fehlgeschlagen",
"STATUS_DISCONNECTED": "Getrennt",
"STATUS_IDLE": "Bereit zum Verbinden",
"STATUS_IDLE_TOOLTIP": "KoalaSync ist bereit. Tritt einem Raum bei oder erstelle einen, um die Verbindung herzustellen und die Synchronisierung zu starten.",
"BTN_STATE_JOINING": "🚀 Trete bei...",
"BTN_STATE_RECONNECTING": "🔄 Verbinde erneut...",
"BTN_STATE_PLAYING": "▶ Spiele ab...",
"BTN_STATE_PAUSING": "⏸ Pausiere...",
"BTN_STATE_SYNCING_GROUP": "Synce zur Gruppe ({time})...",
"BTN_STATE_SYNCING": "Synchronisiere...",
"BTN_STATE_SYNCED": "✅ Synchronisiert!",
"NOTIF_PLAY": "die Wiedergabe gestartet",
"NOTIF_PAUSE": "die Wiedergabe pausiert",
"NOTIF_SEEK": "im Video gespult",
"NOTIF_FORCE_PREPARE": "eine erzwungene Synchronisation gestartet",
"NOTIF_FORCE_EXECUTE": "alle Teilnehmer synchronisiert",
"DEBUG_NO_TAB": "Kein Ziel-Tab ausgewählt.",
"DEBUG_COMM_FAIL": "Kommunikation mit dem Tab-Video fehlgeschlagen.",
"EMPTY_PEERS_TITLE": "Noch keine Teilnehmer",
"EMPTY_PEERS_HINT": "Teile deinen Einladungslink, um loszulegen",
"EMPTY_HISTORY_TITLE": "Noch keine Aktivität",
"EMPTY_HISTORY_HINT": "Abspielen, Pausieren oder Spulen, um Verlauf zu sehen",
"EMPTY_LOGS_TITLE": "Keine Logs",
"EMPTY_LOGS_HINT": "Verbindungsereignisse werden hier angezeigt",
"EMPTY_ROOMS_TITLE": "Keine aktiven Räume",
"EMPTY_ROOMS_HINT": "Raum erstellen oder aktualisieren, um öffentliche zu finden",
"LABEL_YOU": "Du",
"ONBOARDING_DONE": "Fertig!",
"LABEL_LOBBY_PEER_READY": "Bereit",
"LABEL_LOBBY_PEER_LOADING": "Lädt...",
"LABEL_PASSWORD_PROTECTED": "Passwortgeschützt",
"LABEL_PEERS_COUNT": "{count} Teilnehmer",
"LABEL_CUSTOM_SERVER": "Eigener Server",
"BTN_STATE_CREATING": "🚀 Erstelle Raum...",
"NOTIF_LOBBY_CANCEL_TITLE": "KoalaSync — Episoden-Synchronisation fehlgeschlagen",
"NOTIF_LOBBY_CANCEL_MSG": "Auto-Sync abgebrochen: {reason}. Du musst eventuell manuell synchronisieren.",
"LOBBY_CANCEL_TIMEOUT": "Zeitüberschreitung",
"LOBBY_CANCEL_TIMEOUT_RECOVERED": "Zeitüberschreitung (wiederhergestellt)",
"LOBBY_CANCEL_PEERS_LEFT": "Alle anderen Teilnehmer haben den Raum verlassen",
"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_REVIEW": "★ Bewerten",
"FOOTER_SUPPORT_PROMPT": "Gefällt dir KoalaSync? Hinterlasse eine Bewertung!",
"LABEL_AUDIO_PROCESSING": "Audio-Verarbeitung",
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Wendet Audioeffekte wie Kompression auf die Videowiedergabe an",
"AUDIO_OPEN_SETTINGS": "Öffnen",
"NEW_FEATURE_AUDIO": "Neu: Audio-Verarbeitung — probiere den Kompressor aus!",
"AUDIO_BACK": "← Zurück",
"AUDIO_PAGE_TITLE": "Audio-Einstellungen",
"AUDIO_MASTER_TOGGLE": "Audio-Verarbeitung",
"AUDIO_COMPRESSOR": "Kompressor",
"AUDIO_COMPRESSOR_ENABLE": "Aktiviert",
"AUDIO_PRESET": "Preset",
"AUDIO_PRESET_RECOMMENDED": "Empfohlen",
"AUDIO_PRESET_DYNAMIC_RANGE": "Dynamikumfang",
"AUDIO_PRESET_VOCAL_ENHANCEMENT": "Sprache verbessern",
"AUDIO_PRESET_SMOOTH": "Sanft",
"AUDIO_PRESET_CUSTOM": "Benutzerdefiniert",
"AUDIO_PARAM_THRESHOLD": "Schwellwert",
"AUDIO_PARAM_KNEE": "Knee",
"AUDIO_PARAM_RATIO": "Ratio",
"AUDIO_PARAM_ATTACK": "Attack",
"AUDIO_PARAM_RELEASE": "Release",
"AUDIO_EQUALIZER": "Equalizer",
"AUDIO_COMING_SOON": "Demnächst",
"BTN_RESTART_TOUR": "Einführung neu starten",
"BTN_RESTART_TOUR_TOOLTIP": "Startet die Einführung für neue Benutzer erneut",
"HINT_SELECT_VIDEO": "Wähle hier dein Video aus!"
}
+233
View File
@@ -0,0 +1,233 @@
{
"LANG_CODE": "en",
"HTML_CLASS": "lang-en",
"APP_TITLE": "KoalaSync",
"TAB_ROOM": "Room",
"TAB_ROOM_TOOLTIP": "Room settings and connection",
"TAB_SYNC": "Sync",
"TAB_SYNC_TOOLTIP": "Video sync controls and remote actions",
"TAB_SETTINGS": "Settings",
"TAB_SETTINGS_TOOLTIP": "Extension preferences",
"TAB_STATUS": "Status",
"TAB_STATUS_TOOLTIP": "Advanced Diagnostics & Logs",
"BTN_CREATE_ROOM": "+ Create New Room",
"MANUAL_CONNECT_HEADER": "Manual Connect / Advanced",
"LABEL_SERVER": "Server",
"BTN_SERVER_OFFICIAL": "Official",
"BTN_SERVER_OFFICIAL_TOOLTIP": "Use the official reliable server",
"BTN_SERVER_CUSTOM": "Custom",
"BTN_SERVER_CUSTOM_TOOLTIP": "Connect to your own self-hosted server",
"PLACEHOLDER_SERVER_URL": "wss://your-server:3000",
"LABEL_ROOM_ID": "Room ID",
"LABEL_ROOM_ID_TOOLTIP": "The unique identifier for your sync room",
"PLACEHOLDER_ROOM_ID": "Enter Room ID",
"PLACEHOLDER_ROOM_ID_TOOLTIP": "The unique ID of the room you want to join",
"LABEL_PASSWORD": "Password (Optional)",
"LABEL_PASSWORD_TOOLTIP": "Optional password to restrict room access",
"PLACEHOLDER_PASSWORD": "Room Password (optional)",
"PLACEHOLDER_PASSWORD_TOOLTIP": "Password for the room (leave empty if none)",
"BTN_JOIN_ROOM": "Join / Create Room",
"BTN_JOIN_ROOM_TOOLTIP": "Connect to the room",
"LABEL_PUBLIC_ROOMS": "Public Rooms",
"LABEL_PUBLIC_ROOMS_TOOLTIP": "List of publicly available rooms on this server",
"BTN_REFRESH": "REFRESH",
"BTN_REFRESH_TOOLTIP": "Refresh the list of public rooms",
"PUBLIC_ROOMS_REFRESHING": "Refreshing...",
"BTN_REFRESH_COOLDOWN": "WAIT {seconds}s",
"BTN_REFRESH_COOLDOWN_TOOLTIP": "Room list refresh is cooling down. Try again in {seconds}s.",
"PUBLIC_ROOMS_REFRESHING_COOLDOWN": "Refreshing public rooms. Next refresh available in {seconds}s.",
"LABEL_ACTIVE_ROOM": "Active Room",
"LABEL_ACTIVE_ROOM_TOOLTIP": "The room you are currently connected to",
"ACTIVE_ROOM_NONE": "NONE",
"ACTIVE_SERVER_OFFICIAL": "Official Server",
"LABEL_INVITE_LINK": "Invite Link",
"LABEL_INVITE_LINK_TOOLTIP": "Share this link with friends so they can join",
"LABEL_PEERS_IN_ROOM": "Peers in Room",
"LABEL_PEERS_IN_ROOM_TOOLTIP": "Other users currently connected to this room",
"LABEL_HOST_CONTROL": "Host Control",
"LABEL_HOST_CONTROL_TOOLTIP": "Restrict who can control playback in this room",
"LABEL_HOST_ONLY_TOGGLE": "Only I can control playback",
"NOTICE_HOST_CONTROLS": "The host controls playback for everyone.",
"NOTICE_COHOST_HINT": "Tip: grant control to individual viewers in the participant list below.",
"BADGE_HOST": "Host",
"BADGE_GUEST": "Guest",
"BADGE_CONTROLLER": "Controller",
"BTN_GIVE_CONTROL": "Give control",
"BTN_REVOKE_CONTROL": "Revoke",
"BADGE_DESYNCED": "Solo",
"TOOLTIP_PEER_DESYNCED": "Watching on their own — host's commands are ignored",
"HCM_DIALOG_TITLE": "KoalaSync · Host controls this room",
"HCM_DIALOG_BODY": "Only the host can control playback in this room. Keep watching together, or watch on your own?",
"HCM_DIALOG_STAY": "Stay in sync",
"HCM_DIALOG_SOLO": "Watch on my own",
"HCM_BADGE_SOLO": "Watching on your own",
"HCM_BADGE_RESYNC": "Resync",
"NO_PEERS_CONNECTED": "No peers connected",
"BTN_LEAVE_ROOM": "Leave Room",
"LABEL_SELECT_VIDEO": "Select Video",
"LABEL_SELECT_VIDEO_TOOLTIP": "Choose the browser tab containing the video to sync",
"OPTION_SELECT_TAB": "-- Select a Tab --",
"LABEL_REMOTE_CONTROL": "Remote Control",
"BTN_COPY_INVITE": "📋 Invite Link",
"BTN_COPY_INVITE_TOOLTIP": "Copy Invite Link",
"BTN_PLAY": "▶ Play",
"BTN_PLAY_TOOLTIP": "Send a Play command to everyone",
"BTN_PAUSE": "⏸ Pause",
"BTN_PAUSE_TOOLTIP": "Send a Pause command to everyone",
"BTN_SYNC": "⚡ SYNC",
"BTN_SYNC_TOOLTIP": "Force all users to sync up",
"OPTION_JUMP_TO_OTHERS": "Jump to Others",
"OPTION_JUMP_TO_ME": "Jump to Me",
"OPTION_JUMP_MODE_TOOLTIP": "Choose sync target",
"LABEL_LAST_ACTIVITY": "Last Activity Status",
"LABEL_LAST_ACTIVITY_TOOLTIP": "Shows the most recent play, pause, or seek command",
"NO_RECENT_COMMANDS": "No recent commands",
"LOBBY_HEADER": "EPISODE LOBBY",
"LOBBY_WAITING_FOR": "🎬 Waiting for: \"{title}\"",
"LOBBY_WAITING_PEERS": "Waiting for peers...",
"BTN_SKIP_PLAY": "Skip & Play anyway",
"BTN_SKIP_PLAY_TOOLTIP": "Cancel lobby and play anyway",
"LOBBY_CONNECT_FIRST": "Connect to a room first",
"LOBBY_CONNECT_FIRST_DESC": "You need to join a room via an invite link or create a new one to sync videos.",
"BTN_CREATE_ROOM_ALT": "Create New Room",
"BTN_CREATE_ROOM_ALT_TOOLTIP": "Create a new random room and join it",
"LABEL_USERNAME": "Your Username",
"LABEL_USERNAME_TOOLTIP": "Username helps others identify you.",
"PLACEHOLDER_USERNAME": "Anonymous Koala",
"LABEL_HIDE_CLUTTER": "Hide Clutter Tabs",
"LABEL_HIDE_CLUTTER_TOOLTIP": "Filters out non-video tabs and unrelated domains to keep the list clean",
"LABEL_AUTO_SYNC_NEXT": "Auto-Sync Next Episode",
"LABEL_AUTO_SYNC_NEXT_TOOLTIP": "Pauses automatically and waits for all peers when an episode changes, then sync-starts together.",
"LABEL_AUTO_COPY_INVITE": "Auto-Copy Invite Link",
"LABEL_AUTO_COPY_INVITE_TOOLTIP": "Automatically copies the invite link to your clipboard when creating a new room.",
"LABEL_NOTIFICATIONS": "Browser Notifications",
"LABEL_NOTIFICATIONS_TOOLTIP": "Shows native system notifications when someone joins/leaves or plays/pauses.",
"LABEL_LANGUAGE": "App Language",
"LABEL_LANGUAGE_TOOLTIP": "Choose your preferred extension language",
"LABEL_TROUBLESHOOTING": "Troubleshooting",
"LABEL_TROUBLESHOOTING_TOOLTIP": "Tools for fixing connection issues",
"BTN_REGEN_ID": "Regenerate Peer ID",
"BTN_REGEN_ID_TOOLTIP": "Regenerate your internal ID and reconnect",
"REGEN_ID_DESC": "Use this if you see \"Duplicate Identity\" errors.",
"REGEN_ID_OTHER_ISSUE": "Other issue? Open a GitHub Issue",
"TOAST_ID_REGENERATED": "Identity regenerated — reconnecting…",
"LABEL_CONN_STATUS": "Connection Status",
"LABEL_CONN_STATUS_TOOLTIP": "Current WebSocket connection state",
"CONN_STATUS_DISCONNECTED": "Disconnected",
"BTN_RETRY": "RETRY",
"BTN_RETRY_TOOLTIP": "Attempt to reconnect to the server",
"BTN_COPY_LOGS": "Copy Logs",
"BTN_COPY_LOGS_TOOLTIP": "Copy logs to clipboard for sharing",
"LABEL_VIDEO_DEBUG": "Video Debug Info",
"LABEL_VIDEO_DEBUG_TOOLTIP": "Technical details about the currently selected video element",
"VIDEO_DEBUG_EMPTY": "No tab selected or video detected.",
"LABEL_HISTORY": "Full Action History",
"LABEL_HISTORY_TOOLTIP": "Chronological log of all sync commands in the room",
"HISTORY_EMPTY": "No activity yet",
"LABEL_LOGS": "Logs (Last 50)",
"LABEL_LOGS_TOOLTIP": "Technical connection logs for debugging",
"BTN_CLEAR": "CLEAR",
"BTN_CLEAR_TOOLTIP": "Clear log output",
"LABEL_GITHUB": "GitHub Repository",
"BTN_ONBOARDING_SKIP": "Skip",
"BTN_ONBOARDING_SKIP_TOOLTIP": "Skip the tutorial",
"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_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",
"ONBOARDING_3_TEXT": "Navigate here to select the video you want to sync. Play, pause, and seek — everyone stays in sync.",
"ONBOARDING_4_TITLE": "3. Personalize",
"ONBOARDING_4_TEXT": "Pick a fun username so your friends know who you are.",
"ONBOARDING_5_TITLE": "You're all set!",
"ONBOARDING_5_TEXT": "Time to grab some popcorn. Enjoy watching together!",
"ERR_CONN_TIMEOUT": "Connection timed out. Please try again.",
"ERR_INVALID_SERVER_URL": "Invalid Server URL format.",
"ERR_IDENTITY_NOT_LOADED": "Identity not yet loaded. Wait a moment and try again.",
"ERR_NO_PEERS_TIME": "No other peers with a known time. Switch to 'Jump to Me'.",
"ERR_NO_VIDEO_TAB": "Could not connect to video tab.",
"ERR_SELECT_VIDEO": "Please select a video first!",
"TOAST_INVITE_COPIED": "Invite link copied!",
"TOAST_COPY_FAILED": "Failed to copy to clipboard",
"TOAST_LOBBY_SKIPPED": "Episode Lobby skipped.",
"TOAST_LOBBY_SKIP_FAILED": "Failed to skip lobby.",
"TOAST_LOGS_COPIED": "Copied!",
"TOAST_PEER_JOINED": "{name} joined the room",
"TOAST_PEER_LEFT": "{name} left the room",
"TOAST_PEER_ACTION": "{name} {action}",
"STATUS_CONNECTED": "Connected",
"STATUS_RECONNECTING": "Reconnecting...",
"STATUS_CONNECTING": "Connecting...",
"STATUS_FAILED": "Failed",
"STATUS_DISCONNECTED": "Disconnected",
"STATUS_IDLE": "Ready to connect",
"STATUS_IDLE_TOOLTIP": "KoalaSync is ready. Join or create a room to connect and start syncing.",
"BTN_STATE_JOINING": "🚀 Joining...",
"BTN_STATE_RECONNECTING": "🔄 Reconnecting...",
"BTN_STATE_PLAYING": "▶ Playing...",
"BTN_STATE_PAUSING": "⏸ Pausing...",
"BTN_STATE_SYNCING_GROUP": "Syncing to group ({time})...",
"BTN_STATE_SYNCING": "Syncing...",
"BTN_STATE_SYNCED": "✅ Synced!",
"NOTIF_PLAY": "started playback",
"NOTIF_PAUSE": "paused playback",
"NOTIF_SEEK": "seeked the video",
"NOTIF_FORCE_PREPARE": "started force sync",
"NOTIF_FORCE_EXECUTE": "synchronized everyone",
"DEBUG_NO_TAB": "No target tab selected.",
"DEBUG_COMM_FAIL": "Could not communicate with tab video.",
"EMPTY_PEERS_TITLE": "No peers yet",
"EMPTY_PEERS_HINT": "Share your invite link to get started",
"EMPTY_HISTORY_TITLE": "No activity yet",
"EMPTY_HISTORY_HINT": "Play, pause, or seek to see history",
"EMPTY_LOGS_TITLE": "No logs",
"EMPTY_LOGS_HINT": "Connection events will appear here",
"EMPTY_ROOMS_TITLE": "No active rooms",
"EMPTY_ROOMS_HINT": "Create a room or refresh to find public ones",
"LABEL_YOU": "You",
"ONBOARDING_DONE": "Done!",
"LABEL_LOBBY_PEER_READY": "Ready",
"LABEL_LOBBY_PEER_LOADING": "Loading...",
"LABEL_PASSWORD_PROTECTED": "Password Protected",
"LABEL_PEERS_COUNT": "{count} peers",
"LABEL_CUSTOM_SERVER": "Custom Server",
"BTN_STATE_CREATING": "🚀 Creating Room...",
"NOTIF_LOBBY_CANCEL_TITLE": "KoalaSync — Episode Sync Failed",
"NOTIF_LOBBY_CANCEL_MSG": "Auto-sync cancelled: {reason}. You may need to manually sync.",
"LOBBY_CANCEL_TIMEOUT": "Timeout",
"LOBBY_CANCEL_TIMEOUT_RECOVERED": "Timeout (recovered)",
"LOBBY_CANCEL_PEERS_LEFT": "All other peers left",
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Timeout — not all peers loaded the episode",
"LOBBY_CANCEL_USER": "Cancelled by user",
"NOTIF_ERROR_TITLE": "KoalaSync Error",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_REVIEW": "★ Rate us",
"FOOTER_SUPPORT_PROMPT": "Enjoying KoalaSync? Leave a review!",
"LABEL_AUDIO_PROCESSING": "Audio Processing",
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Apply audio effects like compression to video playback",
"AUDIO_OPEN_SETTINGS": "Open",
"NEW_FEATURE_AUDIO": "New: Audio Processing — try the compressor!",
"AUDIO_BACK": "← Back",
"AUDIO_PAGE_TITLE": "Audio Settings",
"AUDIO_MASTER_TOGGLE": "Audio Processing",
"AUDIO_COMPRESSOR": "Compressor",
"AUDIO_COMPRESSOR_ENABLE": "Enabled",
"AUDIO_PRESET": "Preset",
"AUDIO_PRESET_RECOMMENDED": "Recommended",
"AUDIO_PRESET_DYNAMIC_RANGE": "Dynamic Range",
"AUDIO_PRESET_VOCAL_ENHANCEMENT": "Vocal Enhancement",
"AUDIO_PRESET_SMOOTH": "Smooth",
"AUDIO_PRESET_CUSTOM": "Custom",
"AUDIO_PARAM_THRESHOLD": "Threshold",
"AUDIO_PARAM_KNEE": "Knee",
"AUDIO_PARAM_RATIO": "Ratio",
"AUDIO_PARAM_ATTACK": "Attack",
"AUDIO_PARAM_RELEASE": "Release",
"AUDIO_EQUALIZER": "Equalizer",
"AUDIO_COMING_SOON": "Coming soon",
"BTN_RESTART_TOUR": "Restart Tutorial",
"BTN_RESTART_TOUR_TOOLTIP": "Restart the onboarding tutorial",
"HINT_SELECT_VIDEO": "Select your video here!"
}
+233
View File
@@ -0,0 +1,233 @@
{
"LANG_CODE": "es",
"HTML_CLASS": "lang-es",
"APP_TITLE": "KoalaSync",
"TAB_ROOM": "Sala",
"TAB_ROOM_TOOLTIP": "Configuración de sala y conexión",
"TAB_SYNC": "Sincro",
"TAB_SYNC_TOOLTIP": "Controles de sincronización de video y acciones",
"TAB_SETTINGS": "Configuración",
"TAB_SETTINGS_TOOLTIP": "Preferencias de la extensión",
"TAB_STATUS": "Estado",
"TAB_STATUS_TOOLTIP": "Diagnósticos avanzados y registros",
"BTN_CREATE_ROOM": "+ Crear nueva sala",
"MANUAL_CONNECT_HEADER": "Conexión manual / Avanzado",
"LABEL_SERVER": "Servidor",
"BTN_SERVER_OFFICIAL": "Oficial",
"BTN_SERVER_OFFICIAL_TOOLTIP": "Usar el servidor oficial confiable",
"BTN_SERVER_CUSTOM": "Personalizado",
"BTN_SERVER_CUSTOM_TOOLTIP": "Conectarse a su propio servidor auto-alojado",
"PLACEHOLDER_SERVER_URL": "wss://tu-servidor:3000",
"LABEL_ROOM_ID": "ID de sala",
"LABEL_ROOM_ID_TOOLTIP": "El identificador único para tu sala de sincronización",
"PLACEHOLDER_ROOM_ID": "Ingresa el ID de la sala",
"PLACEHOLDER_ROOM_ID_TOOLTIP": "El ID único de la sala a la que deseas unirte",
"LABEL_PASSWORD": "Contraseña (Opcional)",
"LABEL_PASSWORD_TOOLTIP": "Contraseña opcional para restringir el acceso a la sala",
"PLACEHOLDER_PASSWORD": "Contraseña de la sala (opcional)",
"PLACEHOLDER_PASSWORD_TOOLTIP": "Contraseña para la sala (dejar vacío si no hay)",
"BTN_JOIN_ROOM": "Unirse / Crear sala",
"BTN_JOIN_ROOM_TOOLTIP": "Conectarse a la sala",
"LABEL_PUBLIC_ROOMS": "Salas públicas",
"LABEL_PUBLIC_ROOMS_TOOLTIP": "Lista de salas públicas disponibles en este servidor",
"BTN_REFRESH": "ACTUALIZAR",
"BTN_REFRESH_TOOLTIP": "Actualizar la lista de salas públicas",
"PUBLIC_ROOMS_REFRESHING": "Actualizando...",
"BTN_REFRESH_COOLDOWN": "ESPERA {seconds}s",
"BTN_REFRESH_COOLDOWN_TOOLTIP": "La lista de salas está en espera. Inténtalo de nuevo en {seconds}s.",
"PUBLIC_ROOMS_REFRESHING_COOLDOWN": "Actualizando salas públicas. Próxima actualización en {seconds}s.",
"LABEL_ACTIVE_ROOM": "Sala activa",
"LABEL_ACTIVE_ROOM_TOOLTIP": "La sala a la que estás conectado actualmente",
"ACTIVE_ROOM_NONE": "NINGUNA",
"ACTIVE_SERVER_OFFICIAL": "Servidor oficial",
"LABEL_INVITE_LINK": "Enlace de invitación",
"LABEL_INVITE_LINK_TOOLTIP": "Comparte este enlace con amigos para que se unan",
"LABEL_PEERS_IN_ROOM": "Participantes en la sala",
"LABEL_PEERS_IN_ROOM_TOOLTIP": "Otros usuarios conectados actualmente a esta sala",
"LABEL_HOST_CONTROL": "Control del anfitrión",
"LABEL_HOST_CONTROL_TOOLTIP": "Restringir quién puede controlar la reproducción en esta sala",
"LABEL_HOST_ONLY_TOGGLE": "Solo yo puedo controlar la reproducción",
"NOTICE_HOST_CONTROLS": "El anfitrión controla la reproducción para todos.",
"NOTICE_COHOST_HINT": "Consejo: concede el control a usuarios concretos en la lista de participantes de abajo.",
"BADGE_HOST": "Anfitrión",
"BADGE_GUEST": "Invitado",
"BADGE_CONTROLLER": "Controlador",
"BTN_GIVE_CONTROL": "Dar control",
"BTN_REVOKE_CONTROL": "Quitar",
"BADGE_DESYNCED": "Solo",
"TOOLTIP_PEER_DESYNCED": "Viendo por su cuenta — ignora los comandos del anfitrión",
"HCM_DIALOG_TITLE": "KoalaSync · El anfitrión controla esta sala",
"HCM_DIALOG_BODY": "Solo el anfitrión puede controlar la reproducción en esta sala. ¿Seguir viendo juntos o ver por tu cuenta?",
"HCM_DIALOG_STAY": "Seguir sincronizado",
"HCM_DIALOG_SOLO": "Ver por mi cuenta",
"HCM_BADGE_SOLO": "Viendo por tu cuenta",
"HCM_BADGE_RESYNC": "Resincronizar",
"NO_PEERS_CONNECTED": "Sin participantes conectados",
"BTN_LEAVE_ROOM": "Salir de la sala",
"LABEL_SELECT_VIDEO": "Seleccionar video",
"LABEL_SELECT_VIDEO_TOOLTIP": "Elige la pestaña del navegador que contiene el video a sincronizar",
"OPTION_SELECT_TAB": "-- Selecciona una pestaña --",
"LABEL_REMOTE_CONTROL": "Control remoto",
"BTN_COPY_INVITE": "📋 Enlace de invitación",
"BTN_COPY_INVITE_TOOLTIP": "Copiar enlace de invitación",
"BTN_PLAY": "▶ Reproducir",
"BTN_PLAY_TOOLTIP": "Enviar un comando de reproducción a todos",
"BTN_PAUSE": "⏸ Pausar",
"BTN_PAUSE_TOOLTIP": "Enviar un comando de pausa a todos",
"BTN_SYNC": "⚡ SYNC",
"BTN_SYNC_TOOLTIP": "Forzar la sincronización de todos los usuarios",
"OPTION_JUMP_TO_OTHERS": "Ir a la posición de otros",
"OPTION_JUMP_TO_ME": "Traerlos a mi posición",
"OPTION_JUMP_MODE_TOOLTIP": "Elegir el objetivo de sincronización",
"LABEL_LAST_ACTIVITY": "Última actividad",
"LABEL_LAST_ACTIVITY_TOOLTIP": "Muestra el comando más reciente de reproducción, pausa o salto en el tiempo",
"NO_RECENT_COMMANDS": "Sin comandos recientes",
"LOBBY_HEADER": "SALA DE ESPERA DE EPISODIO",
"LOBBY_WAITING_FOR": "🎬 Esperando a: \"{title}\"",
"LOBBY_WAITING_PEERS": "Esperando participantes...",
"BTN_SKIP_PLAY": "Omitir y reproducir ya",
"BTN_SKIP_PLAY_TOOLTIP": "Cancelar la sala de espera y reproducir de todos modos",
"LOBBY_CONNECT_FIRST": "Únete a una sala primero",
"LOBBY_CONNECT_FIRST_DESC": "Necesitas unirte a una sala a través de un enlace de invitación o crear una nueva para sincronizar videos.",
"BTN_CREATE_ROOM_ALT": "Crear nueva sala",
"BTN_CREATE_ROOM_ALT_TOOLTIP": "Crear una nueva sala aleatoria y unirse",
"LABEL_USERNAME": "Tu nombre de usuario",
"LABEL_USERNAME_TOOLTIP": "El nombre de usuario ayuda a otros a identificarte.",
"PLACEHOLDER_USERNAME": "Koala anónimo",
"LABEL_HIDE_CLUTTER": "Ocultar pestañas sin video",
"LABEL_HIDE_CLUTTER_TOOLTIP": "Filtra pestañas que no son de video y dominios no relacionados para mantener limpia la lista",
"LABEL_AUTO_SYNC_NEXT": "Sincro auto del siguiente episodio",
"LABEL_AUTO_SYNC_NEXT_TOOLTIP": "Pausa automáticamente y espera a todos al cambiar de episodio, luego inicia de forma sincrónica.",
"LABEL_AUTO_COPY_INVITE": "Auto-copiar enlace",
"LABEL_AUTO_COPY_INVITE_TOOLTIP": "Copia automáticamente el enlace de invitación a tu portapapeles al crear una nueva sala.",
"LABEL_NOTIFICATIONS": "Notificaciones del navegador",
"LABEL_NOTIFICATIONS_TOOLTIP": "Muestra notificaciones del sistema cuando alguien se une/sale o reproduce/pausa.",
"LABEL_LANGUAGE": "Idioma de la aplicación",
"LABEL_LANGUAGE_TOOLTIP": "Elige tu idioma preferido para la extensión",
"LABEL_TROUBLESHOOTING": "Resolución de problemas",
"LABEL_TROUBLESHOOTING_TOOLTIP": "Herramientas para solucionar problemas de conexión",
"BTN_REGEN_ID": "Regenerar ID de usuario",
"BTN_REGEN_ID_TOOLTIP": "Regenerar tu ID interno y volver a conectarte",
"REGEN_ID_DESC": "Usa esto si ves errores de 'Identidad duplicada'.",
"REGEN_ID_OTHER_ISSUE": "¿Tienes otro problema? Abre un reporte en GitHub",
"TOAST_ID_REGENERATED": "Identidad regenerada — reconectando…",
"LABEL_CONN_STATUS": "Estado de la conexión",
"LABEL_CONN_STATUS_TOOLTIP": "Estado actual de la conexión WebSocket",
"CONN_STATUS_DISCONNECTED": "Desconectado",
"BTN_RETRY": "REINTENTAR",
"BTN_RETRY_TOOLTIP": "Intentar volver a conectarse al servidor",
"BTN_COPY_LOGS": "Copiar registros",
"BTN_COPY_LOGS_TOOLTIP": "Copiar registros al portapapeles para compartir",
"LABEL_VIDEO_DEBUG": "Información de depuración de video",
"LABEL_VIDEO_DEBUG_TOOLTIP": "Detalles técnicos sobre el elemento de video seleccionado actualmente",
"VIDEO_DEBUG_EMPTY": "No hay pestaña seleccionada o no se detectó ningún video.",
"LABEL_HISTORY": "Historial completo",
"LABEL_HISTORY_TOOLTIP": "Registro cronológico de todos los comandos de sincronización en la sala",
"HISTORY_EMPTY": "Sin actividad aún",
"LABEL_LOGS": "Logs (Últimos 50)",
"LABEL_LOGS_TOOLTIP": "Registros técnicos de conexión para depuración",
"BTN_CLEAR": "LIMPIAR",
"BTN_CLEAR_TOOLTIP": "Limpiar salida de registros",
"LABEL_GITHUB": "Repositorio de GitHub",
"BTN_ONBOARDING_SKIP": "Omitir",
"BTN_ONBOARDING_SKIP_TOOLTIP": "Omitir el tutorial",
"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_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",
"ONBOARDING_3_TEXT": "Navega aquí para seleccionar el video que deseas sincronizar. Reproduce, pausa y busca: todos permanecen sincronizados.",
"ONBOARDING_4_TITLE": "3. Personalizar",
"ONBOARDING_4_TEXT": "Elige un nombre de usuario divertido para que tus amigos sepan quién eres.",
"ONBOARDING_5_TITLE": "¡Todo listo!",
"ONBOARDING_5_TEXT": "Hora de preparar las palomitas. ¡Disfruta viendo con tus amigos!",
"ERR_CONN_TIMEOUT": "Tiempo de conexión agotado. Inténtalo de nuevo.",
"ERR_INVALID_SERVER_URL": "Formato de URL de servidor no válido.",
"ERR_IDENTITY_NOT_LOADED": "Identidad no cargada aún. Espera un momento e inténtalo de nuevo.",
"ERR_NO_PEERS_TIME": "No hay otros participantes con posición conocida. Cambia a 'Traerlos a mi posición'.",
"ERR_NO_VIDEO_TAB": "No se pudo conectar a la pestaña de video.",
"ERR_SELECT_VIDEO": "¡Selecciona un video primero!",
"TOAST_INVITE_COPIED": "¡Enlace de invitación copiado!",
"TOAST_COPY_FAILED": "Error al copiar al portapapeles",
"TOAST_LOBBY_SKIPPED": "Sala de espera de episodio omitida.",
"TOAST_LOBBY_SKIP_FAILED": "Error al omitir la sala de espera.",
"TOAST_LOGS_COPIED": "¡Copiado!",
"TOAST_PEER_JOINED": "{name} se ha unido a la sala",
"TOAST_PEER_LEFT": "{name} ha salido de la sala",
"TOAST_PEER_ACTION": "{name} ha {action}",
"STATUS_CONNECTED": "Conectado",
"STATUS_RECONNECTING": "Reconectando...",
"STATUS_CONNECTING": "Conectando...",
"STATUS_FAILED": "Error",
"STATUS_DISCONNECTED": "Desconectado",
"STATUS_IDLE": "Listo para conectar",
"STATUS_IDLE_TOOLTIP": "KoalaSync está listo. Únete o crea una sala para conectarte y empezar a sincronizar.",
"BTN_STATE_JOINING": "🚀 Uniéndose...",
"BTN_STATE_RECONNECTING": "🔄 Reconectando...",
"BTN_STATE_PLAYING": "▶ Reproduciendo...",
"BTN_STATE_PAUSING": "⏸ Pausando...",
"BTN_STATE_SYNCING_GROUP": "Sincronizando al grupo ({time})...",
"BTN_STATE_SYNCING": "Sincronizando...",
"BTN_STATE_SYNCED": "✅ ¡Sincronizado!",
"NOTIF_PLAY": "ha iniciado la reproducción",
"NOTIF_PAUSE": "ha pausado la reproducción",
"NOTIF_SEEK": "ha cambiado la posición del video",
"NOTIF_FORCE_PREPARE": "ha iniciado una sincronización forzada",
"NOTIF_FORCE_EXECUTE": "ha sincronizado a todos",
"DEBUG_NO_TAB": "No hay pestaña objetivo seleccionada.",
"DEBUG_COMM_FAIL": "No se pudo comunicar con el video de la pestaña.",
"EMPTY_PEERS_TITLE": "Sin participantes aún",
"EMPTY_PEERS_HINT": "Comparte tu enlace de invitación para comenzar",
"EMPTY_HISTORY_TITLE": "Sin actividad aún",
"EMPTY_HISTORY_HINT": "Reproduce, pausa o salta en el tiempo para ver el historial",
"EMPTY_LOGS_TITLE": "Sin registros",
"EMPTY_LOGS_HINT": "Los eventos de conexión aparecerán aquí",
"EMPTY_ROOMS_TITLE": "Sin salas activas",
"EMPTY_ROOMS_HINT": "Crea una sala o actualiza para buscar salas públicas",
"LABEL_YOU": "Tú",
"ONBOARDING_DONE": "¡Hecho!",
"LABEL_LOBBY_PEER_READY": "Listo",
"LABEL_LOBBY_PEER_LOADING": "Cargando...",
"LABEL_PASSWORD_PROTECTED": "Protegido con contraseña",
"LABEL_PEERS_COUNT": "{count} participantes",
"LABEL_CUSTOM_SERVER": "Servidor personalizado",
"BTN_STATE_CREATING": "🚀 Creando sala...",
"NOTIF_LOBBY_CANCEL_TITLE": "KoalaSync — Error al sincronizar episodio",
"NOTIF_LOBBY_CANCEL_MSG": "Sincronización automática cancelada: {reason}. Es posible que debas sincronizar manualmente.",
"LOBBY_CANCEL_TIMEOUT": "Tiempo de espera agotado",
"LOBBY_CANCEL_TIMEOUT_RECOVERED": "Tiempo de espera agotado (recuperado)",
"LOBBY_CANCEL_PEERS_LEFT": "Todos los demás participantes se han ido",
"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_REVIEW": "★ Valorar",
"FOOTER_SUPPORT_PROMPT": "¿Te gusta KoalaSync? ¡Deja una reseña!",
"LABEL_AUDIO_PROCESSING": "Procesamiento de audio",
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Aplica efectos de audio como compresión a la reproducción de video",
"AUDIO_OPEN_SETTINGS": "Abrir",
"NEW_FEATURE_AUDIO": "Nuevo: Procesamiento de audio — ¡prueba el compresor!",
"AUDIO_BACK": "← Volver",
"AUDIO_PAGE_TITLE": "Configuración de audio",
"AUDIO_MASTER_TOGGLE": "Procesamiento de audio",
"AUDIO_COMPRESSOR": "Compresor",
"AUDIO_COMPRESSOR_ENABLE": "Activado",
"AUDIO_PRESET": "Preajuste",
"AUDIO_PRESET_RECOMMENDED": "Recomendado",
"AUDIO_PRESET_DYNAMIC_RANGE": "Rango dinámico",
"AUDIO_PRESET_VOCAL_ENHANCEMENT": "Mejora de voz",
"AUDIO_PRESET_SMOOTH": "Suave",
"AUDIO_PRESET_CUSTOM": "Personalizado",
"AUDIO_PARAM_THRESHOLD": "Umbral (Threshold)",
"AUDIO_PARAM_KNEE": "Codo (Knee)",
"AUDIO_PARAM_RATIO": "Relación (Ratio)",
"AUDIO_PARAM_ATTACK": "Ataque (Attack)",
"AUDIO_PARAM_RELEASE": "Liberación (Release)",
"AUDIO_EQUALIZER": "Ecualizador",
"AUDIO_COMING_SOON": "Próximamente",
"BTN_RESTART_TOUR": "Reiniciar tutorial",
"BTN_RESTART_TOUR_TOOLTIP": "Reiniciar el tutorial de inicio",
"HINT_SELECT_VIDEO": "¡Selecciona tu video aquí!"
}
+233
View File
@@ -0,0 +1,233 @@
{
"LANG_CODE": "fr",
"HTML_CLASS": "lang-fr",
"APP_TITLE": "KoalaSync",
"TAB_ROOM": "Salon",
"TAB_ROOM_TOOLTIP": "Paramètres de salon et connexion",
"TAB_SYNC": "Synchro",
"TAB_SYNC_TOOLTIP": "Contrôles de synchronisation vidéo et actions",
"TAB_SETTINGS": "Options",
"TAB_SETTINGS_TOOLTIP": "Préférences de l'extension",
"TAB_STATUS": "Statut",
"TAB_STATUS_TOOLTIP": "Diagnostics avancés & Journaux",
"BTN_CREATE_ROOM": "+ Créer un nouveau salon",
"MANUAL_CONNECT_HEADER": "Connexion manuelle / Avancé",
"LABEL_SERVER": "Serveur",
"BTN_SERVER_OFFICIAL": "Officiel",
"BTN_SERVER_OFFICIAL_TOOLTIP": "Utiliser le serveur officiel fiable",
"BTN_SERVER_CUSTOM": "Perso",
"BTN_SERVER_CUSTOM_TOOLTIP": "Se connecter à votre propre serveur auto-hébergé",
"PLACEHOLDER_SERVER_URL": "wss://votre-serveur:3000",
"LABEL_ROOM_ID": "ID du salon",
"LABEL_ROOM_ID_TOOLTIP": "L'identifiant unique de votre salon de synchronisation",
"PLACEHOLDER_ROOM_ID": "Entrer l'ID du salon",
"PLACEHOLDER_ROOM_ID_TOOLTIP": "L'ID unique du salon que vous souhaitez rejoindre",
"LABEL_PASSWORD": "Mot de passe (Optionnel)",
"LABEL_PASSWORD_TOOLTIP": "Mot de passe optionnel pour restreindre l'accès au salon",
"PLACEHOLDER_PASSWORD": "Mot de passe du salon (optionnel)",
"PLACEHOLDER_PASSWORD_TOOLTIP": "Mot de passe du salon (laisser vide si aucun)",
"BTN_JOIN_ROOM": "Rejoindre / Créer le salon",
"BTN_JOIN_ROOM_TOOLTIP": "Se connecter au salon",
"LABEL_PUBLIC_ROOMS": "Salons publics",
"LABEL_PUBLIC_ROOMS_TOOLTIP": "Liste des salons publics disponibles sur ce serveur",
"BTN_REFRESH": "ACTUALISER",
"BTN_REFRESH_TOOLTIP": "Actualiser la liste des salons publics",
"PUBLIC_ROOMS_REFRESHING": "Actualisation...",
"BTN_REFRESH_COOLDOWN": "ATTENDRE {seconds}s",
"BTN_REFRESH_COOLDOWN_TOOLTIP": "La liste des salons est en pause. Réessayez dans {seconds}s.",
"PUBLIC_ROOMS_REFRESHING_COOLDOWN": "Actualisation des salons publics. Prochaine actualisation dans {seconds}s.",
"LABEL_ACTIVE_ROOM": "Salon actif",
"LABEL_ACTIVE_ROOM_TOOLTIP": "Le salon auquel vous êtes actuellement connecté",
"ACTIVE_ROOM_NONE": "AUCUN",
"ACTIVE_SERVER_OFFICIAL": "Serveur officiel",
"LABEL_INVITE_LINK": "Lien d'invitation",
"LABEL_INVITE_LINK_TOOLTIP": "Partagez ce lien avec vos amis pour qu'ils vous rejoignent",
"LABEL_PEERS_IN_ROOM": "Membres dans le salon",
"LABEL_PEERS_IN_ROOM_TOOLTIP": "Autres utilisateurs connectés à ce salon",
"LABEL_HOST_CONTROL": "Contrôle de l'hôte",
"LABEL_HOST_CONTROL_TOOLTIP": "Définir qui peut contrôler la lecture dans ce salon",
"LABEL_HOST_ONLY_TOGGLE": "Moi seul peux contrôler la lecture",
"NOTICE_HOST_CONTROLS": "L'hôte contrôle la lecture pour tout le monde.",
"NOTICE_COHOST_HINT": "Astuce : donnez le contrôle à des participants précis dans la liste ci-dessous.",
"BADGE_HOST": "Hôte",
"BADGE_GUEST": "Invité",
"BADGE_CONTROLLER": "Contrôleur",
"BTN_GIVE_CONTROL": "Donner le contrôle",
"BTN_REVOKE_CONTROL": "Retirer",
"BADGE_DESYNCED": "Solo",
"TOOLTIP_PEER_DESYNCED": "Regarde de son côté — ignore les commandes de l'hôte",
"HCM_DIALOG_TITLE": "KoalaSync · L'hôte contrôle ce salon",
"HCM_DIALOG_BODY": "Seul l'hôte peut contrôler la lecture dans ce salon. Continuer à regarder ensemble, ou regarder de votre côté ?",
"HCM_DIALOG_STAY": "Rester synchronisé",
"HCM_DIALOG_SOLO": "Regarder de mon côté",
"HCM_BADGE_SOLO": "Vous regardez seul",
"HCM_BADGE_RESYNC": "Resynchroniser",
"NO_PEERS_CONNECTED": "Aucun membre connecté",
"BTN_LEAVE_ROOM": "Quitter le salon",
"LABEL_SELECT_VIDEO": "Choisir une vidéo",
"LABEL_SELECT_VIDEO_TOOLTIP": "Choisissez l'onglet du navigateur contenant la vidéo à synchroniser",
"OPTION_SELECT_TAB": "-- Choisir un onglet --",
"LABEL_REMOTE_CONTROL": "Contrôle à distance",
"BTN_COPY_INVITE": "📋 Lien d'invitation",
"BTN_COPY_INVITE_TOOLTIP": "Copier le lien d'invitation",
"BTN_PLAY": "▶ Lecture",
"BTN_PLAY_TOOLTIP": "Envoyer une commande Lecture à tout le monde",
"BTN_PAUSE": "⏸ Pause",
"BTN_PAUSE_TOOLTIP": "Envoyer une commande Pause à tout le monde",
"BTN_SYNC": "⚡ SYNC",
"BTN_SYNC_TOOLTIP": "Forcer tous les utilisateurs à se synchroniser",
"OPTION_JUMP_TO_OTHERS": "Rejoindre les autres",
"OPTION_JUMP_TO_ME": "Les amener à moi",
"OPTION_JUMP_MODE_TOOLTIP": "Choisir la cible de synchronisation",
"LABEL_LAST_ACTIVITY": "Dernière activité",
"LABEL_LAST_ACTIVITY_TOOLTIP": "Affiche la dernière commande de lecture, pause ou recherche",
"NO_RECENT_COMMANDS": "Aucune commande récente",
"LOBBY_HEADER": "LOBBY D'ÉPISODE",
"LOBBY_WAITING_FOR": "🎬 En attente de : \"{title}\"",
"LOBBY_WAITING_PEERS": "En attente des membres...",
"BTN_SKIP_PLAY": "Ignorer & Lancer",
"BTN_SKIP_PLAY_TOOLTIP": "Annuler le lobby et lancer la lecture quand même",
"LOBBY_CONNECT_FIRST": "Rejoignez d'abord un salon",
"LOBBY_CONNECT_FIRST_DESC": "Vous devez rejoindre un salon via un lien d'invitation ou en créer un nouveau pour synchroniser des vidéos.",
"BTN_CREATE_ROOM_ALT": "Créer un nouveau salon",
"BTN_CREATE_ROOM_ALT_TOOLTIP": "Créer un salon aléatoire et le rejoindre",
"LABEL_USERNAME": "Votre pseudo",
"LABEL_USERNAME_TOOLTIP": "Votre pseudo permet aux autres de vous identifier.",
"PLACEHOLDER_USERNAME": "Koala anonyme",
"LABEL_HIDE_CLUTTER": "Liste d'onglets épurée",
"LABEL_HIDE_CLUTTER_TOOLTIP": "Filtre les onglets non-vidéo et les domaines non pertinents pour garder la liste propre",
"LABEL_AUTO_SYNC_NEXT": "Synchro auto l'épisode suivant",
"LABEL_AUTO_SYNC_NEXT_TOOLTIP": "Met en pause et attend tous les membres lors d'un changement d'épisode, puis démarre de manière synchrone.",
"LABEL_AUTO_COPY_INVITE": "Copie auto du lien",
"LABEL_AUTO_COPY_INVITE_TOOLTIP": "Copie automatiquement le lien d'invitation dans votre presse-papiers lors de la création d'un salon.",
"LABEL_NOTIFICATIONS": "Notifications de navigateur",
"LABEL_NOTIFICATIONS_TOOLTIP": "Affiche des notifications système lorsqu'un membre arrive/part ou lance/met en pause la lecture.",
"LABEL_LANGUAGE": "Langue de l'application",
"LABEL_LANGUAGE_TOOLTIP": "Choisissez la langue de l'application",
"LABEL_TROUBLESHOOTING": "Dépannage",
"LABEL_TROUBLESHOOTING_TOOLTIP": "Outils pour résoudre les problèmes de connexion",
"BTN_REGEN_ID": "Régénérer l'identifiant",
"BTN_REGEN_ID_TOOLTIP": "Régénérer votre identifiant interne et vous reconnecter",
"REGEN_ID_DESC": "Utilisez cette option si vous rencontrez des erreurs de 'Double identité'.",
"REGEN_ID_OTHER_ISSUE": "Autre problème? Ouvrez un Issue GitHub",
"TOAST_ID_REGENERATED": "Identité régénérée — reconnexion…",
"LABEL_CONN_STATUS": "Statut de la connexion",
"LABEL_CONN_STATUS_TOOLTIP": "Statut actuel de la connexion WebSocket",
"CONN_STATUS_DISCONNECTED": "Déconnecté",
"BTN_RETRY": "RÉESSAYER",
"BTN_RETRY_TOOLTIP": "Tenter de se reconnecter au serveur",
"BTN_COPY_LOGS": "Copier les journaux",
"BTN_COPY_LOGS_TOOLTIP": "Copier les journaux dans le presse-papiers",
"LABEL_VIDEO_DEBUG": "Infos de débogage vidéo",
"LABEL_VIDEO_DEBUG_TOOLTIP": "Détails techniques de l'élément vidéo actuellement sélectionné",
"VIDEO_DEBUG_EMPTY": "Aucun onglet sélectionné ou aucune vidéo détectée.",
"LABEL_HISTORY": "Historique complet",
"LABEL_HISTORY_TOOLTIP": "Historique chronologique de toutes les commandes de synchronisation dans le salon",
"HISTORY_EMPTY": "Aucune activité pour le moment",
"LABEL_LOGS": "Journaux (50 derniers)",
"LABEL_LOGS_TOOLTIP": "Journaux techniques pour le débogage de la connexion",
"BTN_CLEAR": "EFFACER",
"BTN_CLEAR_TOOLTIP": "Effacer la sortie des journaux",
"LABEL_GITHUB": "Dépôt GitHub",
"BTN_ONBOARDING_SKIP": "Passer",
"BTN_ONBOARDING_SKIP_TOOLTIP": "Passer le didacticiel",
"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_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",
"ONBOARDING_3_TEXT": "Naviguez ici pour sélectionner la vidéo à synchroniser. Lecture, pause, recherche — tout le monde reste synchrone.",
"ONBOARDING_4_TITLE": "3. Personnaliser",
"ONBOARDING_4_TEXT": "Choisissez un pseudo sympa pour que vos amis sachent qui vous êtes.",
"ONBOARDING_5_TITLE": "Vous êtes prêt !",
"ONBOARDING_5_TEXT": "Il est temps de préparer le pop-corn. Bon visionnage !",
"ERR_CONN_TIMEOUT": "Délai de connexion dépassé. Veuillez réessayer.",
"ERR_INVALID_SERVER_URL": "Format d'adresse de serveur non valide.",
"ERR_IDENTITY_NOT_LOADED": "Identifiant non encore chargé. Veuillez patienter et réessayer.",
"ERR_NO_PEERS_TIME": "Aucun autre membre avec une position connue. Basculez sur 'Les amener à moi'.",
"ERR_NO_VIDEO_TAB": "Impossible de se connecter à l'onglet vidéo.",
"ERR_SELECT_VIDEO": "Veuillez d'abord sélectionner une vidéo !",
"TOAST_INVITE_COPIED": "Lien d'invitation copié !",
"TOAST_COPY_FAILED": "Échec de la copie dans le presse-papiers",
"TOAST_LOBBY_SKIPPED": "Lobby d'épisode ignoré.",
"TOAST_LOBBY_SKIP_FAILED": "Échec de l'annulation du lobby.",
"TOAST_LOGS_COPIED": "Copié !",
"TOAST_PEER_JOINED": "{name} a rejoint le salon",
"TOAST_PEER_LEFT": "{name} a quitté le salon",
"TOAST_PEER_ACTION": "{name} a {action}",
"STATUS_CONNECTED": "Connecté",
"STATUS_RECONNECTING": "Reconnexion...",
"STATUS_CONNECTING": "Connexion...",
"STATUS_FAILED": "Échec",
"STATUS_DISCONNECTED": "Déconnecté",
"STATUS_IDLE": "Prêt à se connecter",
"STATUS_IDLE_TOOLTIP": "KoalaSync est prêt. Rejoignez ou créez un salon pour vous connecter et commencer la synchronisation.",
"BTN_STATE_JOINING": "🚀 Connexion...",
"BTN_STATE_RECONNECTING": "🔄 Reconnexion...",
"BTN_STATE_PLAYING": "▶ Lecture...",
"BTN_STATE_PAUSING": "⏸ Pause...",
"BTN_STATE_SYNCING_GROUP": "Synchro au groupe ({time})...",
"BTN_STATE_SYNCING": "Synchronisation...",
"BTN_STATE_SYNCED": "✅ Synchronisé !",
"NOTIF_PLAY": "lancé la lecture",
"NOTIF_PAUSE": "mis la lecture en pause",
"NOTIF_SEEK": "déplacé la position dans la vidéo",
"NOTIF_FORCE_PREPARE": "lancé une synchronisation forcée",
"NOTIF_FORCE_EXECUTE": "synchronisé tout le monde",
"DEBUG_NO_TAB": "Aucun onglet cible sélectionné.",
"DEBUG_COMM_FAIL": "Impossible de communiquer avec l'onglet vidéo.",
"EMPTY_PEERS_TITLE": "Aucun membre pour l'instant",
"EMPTY_PEERS_HINT": "Partagez votre lien d'invitation pour commencer",
"EMPTY_HISTORY_TITLE": "Aucune activité pour l'instant",
"EMPTY_HISTORY_HINT": "Lancez, mettez en pause ou déplacez la position pour voir l'historique",
"EMPTY_LOGS_TITLE": "Aucun journal",
"EMPTY_LOGS_HINT": "Les événements de connexion s'afficheront ici",
"EMPTY_ROOMS_TITLE": "Aucun salon actif",
"EMPTY_ROOMS_HINT": "Créez un salon ou actualisez pour trouver des salons publics",
"LABEL_YOU": "Vous",
"ONBOARDING_DONE": "Terminé !",
"LABEL_LOBBY_PEER_READY": "Prêt",
"LABEL_LOBBY_PEER_LOADING": "Chargement...",
"LABEL_PASSWORD_PROTECTED": "Protégé par mot de passe",
"LABEL_PEERS_COUNT": "{count} membres",
"LABEL_CUSTOM_SERVER": "Serveur personnalisé",
"BTN_STATE_CREATING": "🚀 Création de la salle...",
"NOTIF_LOBBY_CANCEL_TITLE": "KoalaSync — Échec de la synchronisation de l'épisode",
"NOTIF_LOBBY_CANCEL_MSG": "Synchronisation automatique annulée : {reason}. Vous devrez peut-être synchroniser manuellement.",
"LOBBY_CANCEL_TIMEOUT": "Délai d'attente dépassé",
"LOBBY_CANCEL_TIMEOUT_RECOVERED": "Délai dépassé (récupéré)",
"LOBBY_CANCEL_PEERS_LEFT": "Tous les autres membres sont partis",
"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_REVIEW": "★ Évaluer",
"FOOTER_SUPPORT_PROMPT": "Tu aimes KoalaSync? Laisse un avis!",
"LABEL_AUDIO_PROCESSING": "Traitement audio",
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Applique des effets audio comme la compression à la lecture vidéo",
"AUDIO_OPEN_SETTINGS": "Ouvrir",
"NEW_FEATURE_AUDIO": "Nouveau : Traitement audio — essayez le compresseur !",
"AUDIO_BACK": "← Retour",
"AUDIO_PAGE_TITLE": "Paramètres audio",
"AUDIO_MASTER_TOGGLE": "Traitement audio",
"AUDIO_COMPRESSOR": "Compresseur",
"AUDIO_COMPRESSOR_ENABLE": "Activé",
"AUDIO_PRESET": "Préréglage",
"AUDIO_PRESET_RECOMMENDED": "Recommandé",
"AUDIO_PRESET_DYNAMIC_RANGE": "Plage dynamique",
"AUDIO_PRESET_VOCAL_ENHANCEMENT": "Amélioration vocale",
"AUDIO_PRESET_SMOOTH": "Douce",
"AUDIO_PRESET_CUSTOM": "Personnalisé",
"AUDIO_PARAM_THRESHOLD": "Seuil",
"AUDIO_PARAM_KNEE": "Knee",
"AUDIO_PARAM_RATIO": "Ratio",
"AUDIO_PARAM_ATTACK": "Attack",
"AUDIO_PARAM_RELEASE": "Release",
"AUDIO_EQUALIZER": "Égaliseur",
"AUDIO_COMING_SOON": "Bientôt disponible",
"BTN_RESTART_TOUR": "Redémarrer le tutoriel",
"BTN_RESTART_TOUR_TOOLTIP": "Recommencer le tutoriel d'intégration",
"HINT_SELECT_VIDEO": "Sélectionnez votre vidéo ici !"
}
+233
View File
@@ -0,0 +1,233 @@
{
"LANG_CODE": "it",
"HTML_CLASS": "lang-it",
"APP_TITLE": "KoalaSync",
"TAB_ROOM": "Stanza",
"TAB_ROOM_TOOLTIP": "Impostazioni della stanza y connessione",
"TAB_SYNC": "Sincro",
"TAB_SYNC_TOOLTIP": "Controlli di sincronizzazione video e azioni remote",
"TAB_SETTINGS": "Impostazioni",
"TAB_SETTINGS_TOOLTIP": "Preferenze dell'estensione",
"TAB_STATUS": "Stato",
"TAB_STATUS_TOOLTIP": "Diagnostica avanzata e log",
"BTN_CREATE_ROOM": "+ Crea Nuova Stanza",
"MANUAL_CONNECT_HEADER": "Connessione Manuale / Avanzata",
"LABEL_SERVER": "Server",
"BTN_SERVER_OFFICIAL": "Ufficiale",
"BTN_SERVER_OFFICIAL_TOOLTIP": "Usa il server ufficiale affidabile",
"BTN_SERVER_CUSTOM": "Personalizzato",
"BTN_SERVER_CUSTOM_TOOLTIP": "Connettiti al tuo server privato",
"PLACEHOLDER_SERVER_URL": "wss://tuo-server:3000",
"LABEL_ROOM_ID": "ID Stanza",
"LABEL_ROOM_ID_TOOLTIP": "L'identificatore univoco per la tua stanza",
"PLACEHOLDER_ROOM_ID": "Inserisci ID Stanza",
"PLACEHOLDER_ROOM_ID_TOOLTIP": "L'ID della stanza a cui vuoi unirti",
"LABEL_PASSWORD": "Password (Opzionale)",
"LABEL_PASSWORD_TOOLTIP": "Password per limitare l'accesso alla stanza",
"PLACEHOLDER_PASSWORD": "Password della stanza (opzionale)",
"PLACEHOLDER_PASSWORD_TOOLTIP": "Lascia vuoto se non c'è una password",
"BTN_JOIN_ROOM": "Entra / Crea Stanza",
"BTN_JOIN_ROOM_TOOLTIP": "Entra nella stanza",
"LABEL_PUBLIC_ROOMS": "Stanze Pubbliche",
"LABEL_PUBLIC_ROOMS_TOOLTIP": "Elenco delle stanze pubbliche su questo server",
"BTN_REFRESH": "AGGIORNA",
"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.",
"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",
"ACTIVE_ROOM_NONE": "NESSUNA",
"ACTIVE_SERVER_OFFICIAL": "Server Ufficiale",
"LABEL_INVITE_LINK": "Link di Invito",
"LABEL_INVITE_LINK_TOOLTIP": "Condividi questo link per invitare i tuoi amici",
"LABEL_PEERS_IN_ROOM": "Partecipanti",
"LABEL_PEERS_IN_ROOM_TOOLTIP": "Utenti connessi a questa stanza",
"LABEL_HOST_CONTROL": "Controllo host",
"LABEL_HOST_CONTROL_TOOLTIP": "Limita chi può controllare la riproduzione in questa stanza",
"LABEL_HOST_ONLY_TOGGLE": "Solo io posso controllare la riproduzione",
"NOTICE_HOST_CONTROLS": "L'host controlla la riproduzione per tutti.",
"NOTICE_COHOST_HINT": "Suggerimento: concedi il controllo ai singoli partecipanti nell'elenco qui sotto.",
"BADGE_HOST": "Host",
"BADGE_GUEST": "Ospite",
"BADGE_CONTROLLER": "Controller",
"BTN_GIVE_CONTROL": "Dai il controllo",
"BTN_REVOKE_CONTROL": "Revoca",
"BADGE_DESYNCED": "Solo",
"TOOLTIP_PEER_DESYNCED": "Guarda per conto proprio — ignora i comandi dell'host",
"HCM_DIALOG_TITLE": "KoalaSync · L'host controlla questa stanza",
"HCM_DIALOG_BODY": "Solo l'host può controllare la riproduzione in questa stanza. Continuare a guardare insieme o guardare per conto tuo?",
"HCM_DIALOG_STAY": "Resta sincronizzato",
"HCM_DIALOG_SOLO": "Guarda per conto mio",
"HCM_BADGE_SOLO": "Stai guardando da solo",
"HCM_BADGE_RESYNC": "Risincronizza",
"NO_PEERS_CONNECTED": "Nessun partecipante connesso",
"BTN_LEAVE_ROOM": "Lascia Stanza",
"LABEL_SELECT_VIDEO": "Seleziona Video",
"LABEL_SELECT_VIDEO_TOOLTIP": "Scegli la scheda con il video da sincronizzare",
"OPTION_SELECT_TAB": "-- Seleziona una Scheda --",
"LABEL_REMOTE_CONTROL": "Telecomando",
"BTN_COPY_INVITE": "📋 Link di Invito",
"BTN_COPY_INVITE_TOOLTIP": "Copia il link di invito",
"BTN_PLAY": "▶ Riproduci",
"BTN_PLAY_TOOLTIP": "Avvia la riproduzione per tutti",
"BTN_PAUSE": "⏸ Pausa",
"BTN_PAUSE_TOOLTIP": "Metti in pausa per tutti",
"BTN_SYNC": "⚡ SINCRO",
"BTN_SYNC_TOOLTIP": "Forza la sincronizzazione per tutti",
"OPTION_JUMP_TO_OTHERS": "Vai alla posizione degli altri",
"OPTION_JUMP_TO_ME": "Portali alla mia posizione",
"OPTION_JUMP_MODE_TOOLTIP": "Scegli l'obiettivo di sincronizzazione",
"LABEL_LAST_ACTIVITY": "Ultima Attività",
"LABEL_LAST_ACTIVITY_TOOLTIP": "Mostra l'ultimo comando inviato",
"NO_RECENT_COMMANDS": "Nessun comando recente",
"LOBBY_HEADER": "SALA D'ATTESA EPISODIO",
"LOBBY_WAITING_FOR": "🎬 In attesa di: \"{title}\"",
"LOBBY_WAITING_PEERS": "In attesa dei partecipanti...",
"BTN_SKIP_PLAY": "Salta e riproduci ora",
"BTN_SKIP_PLAY_TOOLTIP": "Annulla l'attesa e riproduci comunque",
"LOBBY_CONNECT_FIRST": "Entra prima in una stanza",
"LOBBY_CONNECT_FIRST_DESC": "Devi unirti a una stanza o crearne una nuova per sincronizzare i video.",
"BTN_CREATE_ROOM_ALT": "Crea Nuova Stanza",
"BTN_CREATE_ROOM_ALT_TOOLTIP": "Crea una stanza casuale ed entra",
"LABEL_USERNAME": "Tuo Nome Utente",
"LABEL_USERNAME_TOOLTIP": "Il tuo nome visibile agli altri.",
"PLACEHOLDER_USERNAME": "Koala Anonimo",
"LABEL_HIDE_CLUTTER": "Nascondi schede senza video",
"LABEL_HIDE_CLUTTER_TOOLTIP": "Mantiene pulito l'elenco filtrando le schede non pertinenti",
"LABEL_AUTO_SYNC_NEXT": "Sincro auto prossimo episodio",
"LABEL_AUTO_SYNC_NEXT_TOOLTIP": "Attende tutti i partecipanti prima di avviare il prossimo episodio.",
"LABEL_AUTO_COPY_INVITE": "Copia automatica invito",
"LABEL_AUTO_COPY_INVITE_TOOLTIP": "Copia il link negli appunti quando crei una nuova stanza.",
"LABEL_NOTIFICATIONS": "Notifiche del Browser",
"LABEL_NOTIFICATIONS_TOOLTIP": "Mostra notifiche quando qualcuno entra, esce o cambia stato.",
"LABEL_LANGUAGE": "Lingua Applicazione",
"LABEL_LANGUAGE_TOOLTIP": "Scegli la lingua dell'estensione",
"LABEL_TROUBLESHOOTING": "Risoluzione Problemi",
"LABEL_TROUBLESHOOTING_TOOLTIP": "Strumenti per risolvere problemi di connessione",
"BTN_REGEN_ID": "Rigenera ID Utente",
"BTN_REGEN_ID_TOOLTIP": "Genera un nuovo ID interno e riconnettiti",
"REGEN_ID_DESC": "Usa questa opzione se riscontri errori di 'Identità Duplicata'.",
"REGEN_ID_OTHER_ISSUE": "Hai altri problemi? Apri una segnalazione su GitHub",
"TOAST_ID_REGENERATED": "Identità rigenerata — riconnessione…",
"LABEL_CONN_STATUS": "Stato Connessione",
"LABEL_CONN_STATUS_TOOLTIP": "Stato attuale della connessione",
"CONN_STATUS_DISCONNECTED": "Disconnesso",
"BTN_RETRY": "RIPROVA",
"BTN_RETRY_TOOLTIP": "Tenta di riconnettersi al server",
"BTN_COPY_LOGS": "Copia Log",
"BTN_COPY_LOGS_TOOLTIP": "Copia i log negli appunti",
"LABEL_VIDEO_DEBUG": "Debug Video",
"LABEL_VIDEO_DEBUG_TOOLTIP": "Dettagli tecnici sul video selezionato",
"VIDEO_DEBUG_EMPTY": "Nessuna scheda selezionata o video rilevato.",
"LABEL_HISTORY": "Cronologia Completa",
"LABEL_HISTORY_TOOLTIP": "Registro di tutti i comandi inviati nella stanza",
"HISTORY_EMPTY": "Ancora nessuna attività",
"LABEL_LOGS": "Log (Ultimi 50)",
"LABEL_LOGS_TOOLTIP": "Log tecnici per il debug",
"BTN_CLEAR": "PULISCI",
"BTN_CLEAR_TOOLTIP": "Cancella i log",
"LABEL_GITHUB": "Repository GitHub",
"BTN_ONBOARDING_SKIP": "Salta",
"BTN_ONBOARDING_SKIP_TOOLTIP": "Salta il tutorial",
"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_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",
"ONBOARDING_3_TEXT": "Scegli il video che vuoi sincronizzare. Play, pausa o salta: tutti vedranno lo stesso.",
"ONBOARDING_4_TITLE": "3. Personalizza",
"ONBOARDING_4_TEXT": "Scegli un nome utente per farti riconoscere dai tuoi amici.",
"ONBOARDING_5_TITLE": "Tutto pronto!",
"ONBOARDING_5_TEXT": "È ora di prendere i popcorn. Buona visione!",
"ERR_CONN_TIMEOUT": "Connessione scaduta. Riprova.",
"ERR_INVALID_SERVER_URL": "Formato URL del server non valido.",
"ERR_IDENTITY_NOT_LOADED": "Identità non ancora caricata. Attendi un momento.",
"ERR_NO_PEERS_TIME": "Nessun altro partecipante con posizione nota. Passa a 'Portali alla mia posizione'.",
"ERR_NO_VIDEO_TAB": "Impossibile connettersi alla scheda del video.",
"ERR_SELECT_VIDEO": "Seleziona prima un video!",
"TOAST_INVITE_COPIED": "Link di invito copiato!",
"TOAST_COPY_FAILED": "Impossibile copiare negli appunti",
"TOAST_LOBBY_SKIPPED": "Sala d'attesa saltata.",
"TOAST_LOBBY_SKIP_FAILED": "Impossibile saltare la sala d'attesa.",
"TOAST_LOGS_COPIED": "Copiato!",
"TOAST_PEER_JOINED": "{name} è entrato nella stanza",
"TOAST_PEER_LEFT": "{name} ha lasciato la stanza",
"TOAST_PEER_ACTION": "{name} {action}",
"STATUS_CONNECTED": "Connesso",
"STATUS_RECONNECTING": "Riconnessione...",
"STATUS_CONNECTING": "Connessione in corso...",
"STATUS_FAILED": "Errore",
"STATUS_DISCONNECTED": "Disconnesso",
"STATUS_IDLE": "Pronto a connettersi",
"STATUS_IDLE_TOOLTIP": "KoalaSync è pronto. Entra o crea una stanza per connetterti e avviare la sincronizzazione.",
"BTN_STATE_JOINING": "🚀 Entrando...",
"BTN_STATE_RECONNECTING": "🔄 Riconnessione...",
"BTN_STATE_PLAYING": "▶ In riproduzione...",
"BTN_STATE_PAUSING": "⏸ In pausa...",
"BTN_STATE_SYNCING_GROUP": "Sincronizzazione di gruppo ({time})...",
"BTN_STATE_SYNCING": "Sincronizzazione...",
"BTN_STATE_SYNCED": "✅ Sincronizzato!",
"NOTIF_PLAY": "ha avviato la riproduzione",
"NOTIF_PAUSE": "ha messo in pausa",
"NOTIF_SEEK": "ha cambiato posizione",
"NOTIF_FORCE_PREPARE": "ha avviato una sincronizzazione forzata",
"NOTIF_FORCE_EXECUTE": "ha sincronizzato tutti",
"DEBUG_NO_TAB": "Nessuna scheda selezionata.",
"DEBUG_COMM_FAIL": "Errore di comunicazione con il video.",
"EMPTY_PEERS_TITLE": "Nessun partecipante",
"EMPTY_PEERS_HINT": "Condividi il tuo link per iniziare",
"EMPTY_HISTORY_TITLE": "Nessuna attività",
"EMPTY_HISTORY_HINT": "Usa i comandi per vedere la cronologia",
"EMPTY_LOGS_TITLE": "Nessun log",
"EMPTY_LOGS_HINT": "Gli eventi appariranno qui",
"EMPTY_ROOMS_TITLE": "Nessuna stanza attiva",
"EMPTY_ROOMS_HINT": "Crea una stanza o aggiorna l'elenco",
"LABEL_YOU": "Tu",
"ONBOARDING_DONE": "Fatto!",
"LABEL_LOBBY_PEER_READY": "Pronto",
"LABEL_LOBBY_PEER_LOADING": "Caricamento...",
"LABEL_PASSWORD_PROTECTED": "Protetto da Password",
"LABEL_PEERS_COUNT": "{count} partecipanti",
"LABEL_CUSTOM_SERVER": "Server Personalizzato",
"BTN_STATE_CREATING": "🚀 Creazione Stanza...",
"NOTIF_LOBBY_CANCEL_TITLE": "KoalaSync — Sincro Episodio Fallita",
"NOTIF_LOBBY_CANCEL_MSG": "Sincro auto annullata: {reason}. Usa la sincronizzazione manuale.",
"LOBBY_CANCEL_TIMEOUT": "Tempo scaduto",
"LOBBY_CANCEL_TIMEOUT_RECOVERED": "Tempo scaduto (ripristinato)",
"LOBBY_CANCEL_PEERS_LEFT": "Tutti gli altri partecipanti sono usciti",
"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_REVIEW": "★ Valutaci",
"FOOTER_SUPPORT_PROMPT": "Ti piace KoalaSync? Lascia una recensione!",
"LABEL_AUDIO_PROCESSING": "Elaborazione Audio",
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Applica effetti sonori (come il compressore) ai video",
"AUDIO_OPEN_SETTINGS": "Apri",
"NEW_FEATURE_AUDIO": "Novità: Elaborazione Audio — prova il compressore!",
"AUDIO_BACK": "← Indietro",
"AUDIO_PAGE_TITLE": "Impostazioni Audio",
"AUDIO_MASTER_TOGGLE": "Elaborazione Audio",
"AUDIO_COMPRESSOR": "Compressore",
"AUDIO_COMPRESSOR_ENABLE": "Attivo",
"AUDIO_PRESET": "Preimpostazione",
"AUDIO_PRESET_RECOMMENDED": "Consigliato",
"AUDIO_PRESET_DYNAMIC_RANGE": "Gamma Dinamica",
"AUDIO_PRESET_VOCAL_ENHANCEMENT": "Miglioramento Vocale",
"AUDIO_PRESET_SMOOTH": "Morbido",
"AUDIO_PRESET_CUSTOM": "Personalizzato",
"AUDIO_PARAM_THRESHOLD": "Soglia (Threshold)",
"AUDIO_PARAM_KNEE": "Ginocchio (Knee)",
"AUDIO_PARAM_RATIO": "Rapporto (Ratio)",
"AUDIO_PARAM_ATTACK": "Attacco (Attack)",
"AUDIO_PARAM_RELEASE": "Rilascio (Release)",
"AUDIO_EQUALIZER": "Equalizzatore",
"AUDIO_COMING_SOON": "Prossimamente",
"BTN_RESTART_TOUR": "Riavvia Tutorial",
"BTN_RESTART_TOUR_TOOLTIP": "Riavvia il tutorial introduttivo",
"HINT_SELECT_VIDEO": "Scegli il tuo video qui!"
}
+233
View File
@@ -0,0 +1,233 @@
{
"LANG_CODE": "ja",
"HTML_CLASS": "lang-ja",
"APP_TITLE": "KoalaSync",
"TAB_ROOM": "ルーム",
"TAB_ROOM_TOOLTIP": "ルーム設定と接続",
"TAB_SYNC": "同期",
"TAB_SYNC_TOOLTIP": "ビデオ同期コントロールとリモートアクション",
"TAB_SETTINGS": "設定",
"TAB_SETTINGS_TOOLTIP": "拡張機能の設定",
"TAB_STATUS": "ステータス",
"TAB_STATUS_TOOLTIP": "高度な診断とログ",
"BTN_CREATE_ROOM": "+ 新規ルーム作成",
"MANUAL_CONNECT_HEADER": "手動接続 / 詳細設定",
"LABEL_SERVER": "サーバー",
"BTN_SERVER_OFFICIAL": "公式",
"BTN_SERVER_OFFICIAL_TOOLTIP": "公式の信頼できるサーバーを使用",
"BTN_SERVER_CUSTOM": "カスタム",
"BTN_SERVER_CUSTOM_TOOLTIP": "独自のセルフホストサーバーに接続",
"PLACEHOLDER_SERVER_URL": "wss://your-server:3000",
"LABEL_ROOM_ID": "ルームID",
"LABEL_ROOM_ID_TOOLTIP": "同期ルームの固有の識別子",
"PLACEHOLDER_ROOM_ID": "ルームIDを入力",
"PLACEHOLDER_ROOM_ID_TOOLTIP": "参加したいルームの固有のID",
"LABEL_PASSWORD": "パスワード(任意)",
"LABEL_PASSWORD_TOOLTIP": "ルームへのアクセスを制限するための任意のパスワード",
"PLACEHOLDER_PASSWORD": "ルームのパスワード(任意)",
"PLACEHOLDER_PASSWORD_TOOLTIP": "ルームのパスワード(ない場合は空欄)",
"BTN_JOIN_ROOM": "ルームに参加 / 作成",
"BTN_JOIN_ROOM_TOOLTIP": "ルームに接続",
"LABEL_PUBLIC_ROOMS": "公開ルーム",
"LABEL_PUBLIC_ROOMS_TOOLTIP": "このサーバー上の公開ルームのリスト",
"BTN_REFRESH": "更新",
"BTN_REFRESH_TOOLTIP": "公開ルームのリストを更新",
"PUBLIC_ROOMS_REFRESHING": "更新中...",
"BTN_REFRESH_COOLDOWN": "{seconds}秒待機",
"BTN_REFRESH_COOLDOWN_TOOLTIP": "ルームリスト更新のクールダウン中。{seconds}秒後に再試行してください。",
"PUBLIC_ROOMS_REFRESHING_COOLDOWN": "公開ルームを更新中。次の更新まであと{seconds}秒。",
"LABEL_ACTIVE_ROOM": "アクティブなルーム",
"LABEL_ACTIVE_ROOM_TOOLTIP": "現在接続しているルーム",
"ACTIVE_ROOM_NONE": "なし",
"ACTIVE_SERVER_OFFICIAL": "公式サーバー",
"LABEL_INVITE_LINK": "招待リンク",
"LABEL_INVITE_LINK_TOOLTIP": "友達とこのリンクを共有して参加してもらいましょう",
"LABEL_PEERS_IN_ROOM": "ルーム内のメンバー",
"LABEL_PEERS_IN_ROOM_TOOLTIP": "現在このルームに接続している他のユーザー",
"LABEL_HOST_CONTROL": "ホスト操作",
"LABEL_HOST_CONTROL_TOOLTIP": "このルームで再生を操作できる人を制限します",
"LABEL_HOST_ONLY_TOGGLE": "再生を操作できるのは自分だけ",
"NOTICE_HOST_CONTROLS": "ホストが全員の再生を操作します。",
"NOTICE_COHOST_HINT": "ヒント: 下の参加者リストで個別の参加者に操作権限を付与できます。",
"BADGE_HOST": "ホスト",
"BADGE_GUEST": "ゲスト",
"BADGE_CONTROLLER": "操作権あり",
"BTN_GIVE_CONTROL": "操作権を付与",
"BTN_REVOKE_CONTROL": "取り消す",
"BADGE_DESYNCED": "ソロ",
"TOOLTIP_PEER_DESYNCED": "単独で視聴中 — ホストのコマンドを無視しています",
"HCM_DIALOG_TITLE": "KoalaSync · ホストがこのルームを操作中",
"HCM_DIALOG_BODY": "このルームではホストのみが再生を操作できます。一緒に視聴を続けますか、それとも自分だけで視聴しますか?",
"HCM_DIALOG_STAY": "同期を維持",
"HCM_DIALOG_SOLO": "自分だけで視聴",
"HCM_BADGE_SOLO": "自分だけで視聴中",
"HCM_BADGE_RESYNC": "再同期",
"NO_PEERS_CONNECTED": "接続しているメンバーはいません",
"BTN_LEAVE_ROOM": "ルームを退室",
"LABEL_SELECT_VIDEO": "ビデオを選択",
"LABEL_SELECT_VIDEO_TOOLTIP": "同期するビデオが含まれるブラウザのタブを選択してください",
"OPTION_SELECT_TAB": "-- タブを選択してください --",
"LABEL_REMOTE_CONTROL": "リモートコントロール",
"BTN_COPY_INVITE": "📋 招待リンク",
"BTN_COPY_INVITE_TOOLTIP": "招待リンクをコピー",
"BTN_PLAY": "▶ 再生",
"BTN_PLAY_TOOLTIP": "全員に再生コマンドを送信",
"BTN_PAUSE": "⏸ 一時停止",
"BTN_PAUSE_TOOLTIP": "全員に一時停止コマンドを送信",
"BTN_SYNC": "⚡ 同期",
"BTN_SYNC_TOOLTIP": "すべてのユーザーを強制的に同期",
"OPTION_JUMP_TO_OTHERS": "他の人に合わせる",
"OPTION_JUMP_TO_ME": "自分に合わせる",
"OPTION_JUMP_MODE_TOOLTIP": "同期対象を選択",
"LABEL_LAST_ACTIVITY": "最新のアクティビティ状況",
"LABEL_LAST_ACTIVITY_TOOLTIP": "最新の再生、一時停止、またはシークコマンドを表示します",
"NO_RECENT_COMMANDS": "最近のコマンドはありません",
"LOBBY_HEADER": "エピソードロビー",
"LOBBY_WAITING_FOR": "🎬 待機中: \"{title}\"",
"LOBBY_WAITING_PEERS": "メンバーを待機中...",
"BTN_SKIP_PLAY": "スキップして再生",
"BTN_SKIP_PLAY_TOOLTIP": "ロビーをキャンセルして再生します",
"LOBBY_CONNECT_FIRST": "最初にルームに接続してください",
"LOBBY_CONNECT_FIRST_DESC": "ビデオを同期するには、招待リンクからルームに参加するか、新規に作成する必要があります。",
"BTN_CREATE_ROOM_ALT": "新規ルーム作成",
"BTN_CREATE_ROOM_ALT_TOOLTIP": "ランダムな新規ルームを作成して参加",
"LABEL_USERNAME": "ユーザー名",
"LABEL_USERNAME_TOOLTIP": "ユーザー名は他のメンバーがあなたを識別するのに役立ちます。",
"PLACEHOLDER_USERNAME": "匿名コアラ",
"LABEL_HIDE_CLUTTER": "不要なタブを非表示",
"LABEL_HIDE_CLUTTER_TOOLTIP": "リストをすっきりさせるため、ビデオのないタブや無関係なドメインをフィルタリングします",
"LABEL_AUTO_SYNC_NEXT": "次のエピソードを自動同期",
"LABEL_AUTO_SYNC_NEXT_TOOLTIP": "エピソード変更時に自動的に一時停止して全員を待ち、準備ができたら同時に再生を開始します。",
"LABEL_AUTO_COPY_INVITE": "招待リンク自動コピー",
"LABEL_AUTO_COPY_INVITE_TOOLTIP": "新規ルーム作成時に招待リンクをクリップボードに自動コピーします。",
"LABEL_NOTIFICATIONS": "ブラウザ通知",
"LABEL_NOTIFICATIONS_TOOLTIP": "誰かが参加/退室したとき、または再生/一時停止したときにシステムの通知を表示します。",
"LABEL_LANGUAGE": "アプリの言語",
"LABEL_LANGUAGE_TOOLTIP": "拡張機能の優先言語を選択してください",
"LABEL_TROUBLESHOOTING": "トラブルシューティング",
"LABEL_TROUBLESHOOTING_TOOLTIP": "接続の問題を修正するためのツール",
"BTN_REGEN_ID": "ピアIDの再生成",
"BTN_REGEN_ID_TOOLTIP": "内部IDを再生成して再接続します",
"REGEN_ID_DESC": "「Duplicate Identity」(ID重複)エラーが表示される場合に使用します。",
"REGEN_ID_OTHER_ISSUE": "他の問題?GitHub Issueを開く",
"TOAST_ID_REGENERATED": "IDを再生成しました — 再接続中…",
"LABEL_CONN_STATUS": "接続状態",
"LABEL_CONN_STATUS_TOOLTIP": "現在のWebSocket接続状態",
"CONN_STATUS_DISCONNECTED": "切断されました",
"BTN_RETRY": "再試行",
"BTN_RETRY_TOOLTIP": "サーバーへの再接続を試行",
"BTN_COPY_LOGS": "ログをコピー",
"BTN_COPY_LOGS_TOOLTIP": "共有用にログをクリップボードにコピー",
"LABEL_VIDEO_DEBUG": "ビデオデバッグ情報",
"LABEL_VIDEO_DEBUG_TOOLTIP": "現在選択されているビデオ要素に関する技術的詳細",
"VIDEO_DEBUG_EMPTY": "タブが選択されていないか、ビデオが検出されませんでした。",
"LABEL_HISTORY": "全アクション履歴",
"LABEL_HISTORY_TOOLTIP": "ルーム内のすべての同期コマンドの時系列ログ",
"HISTORY_EMPTY": "アクティビティはまだありません",
"LABEL_LOGS": "ログ(直近50件)",
"LABEL_LOGS_TOOLTIP": "デバッグ用の技術接続ログ",
"BTN_CLEAR": "消去",
"BTN_CLEAR_TOOLTIP": "ログ出力をクリア",
"LABEL_GITHUB": "GitHubリポジトリ",
"BTN_ONBOARDING_SKIP": "スキップ",
"BTN_ONBOARDING_SKIP_TOOLTIP": "チュートリアルをスキップ",
"BTN_ONBOARDING_NEXT": "次へ",
"BTN_ONBOARDING_NEXT_TOOLTIP": "次のステップに進む",
"ONBOARDING_1_TITLE": "KoalaSyncへようこそ!",
"ONBOARDING_1_TEXT": "どこにいても、完璧に同期してビデオを一緒に楽しめます。簡単なツアーを始めましょう!",
"ONBOARDING_2_TITLE": "1. ルームの作成",
"ONBOARDING_2_TEXT": "ここからスタート。ルームを作成し、招待リンクを友達に共有します。",
"ONBOARDING_3_TITLE": "2. ビデオの選択",
"ONBOARDING_3_TEXT": "ここで同期したいビデオを選択します。再生、一時停止、シーク操作は全員に同期されます。",
"ONBOARDING_4_TITLE": "3. カスタマイズ",
"ONBOARDING_4_TEXT": "友達にわかりやすいように、楽しいユーザー名を設定しましょう。",
"ONBOARDING_5_TITLE": "準備が整いました!",
"ONBOARDING_5_TEXT": "ポップコーンを用意しましょう。一緒に鑑賞を楽しんでください!",
"ERR_CONN_TIMEOUT": "接続がタイムアウトしました。再試行してください。",
"ERR_INVALID_SERVER_URL": "サーバーURLの形式が無効です。",
"ERR_IDENTITY_NOT_LOADED": "IDがまだロードされていません。しばらく待ってから再試行してください。",
"ERR_NO_PEERS_TIME": "既知の時間を持つ他のメンバーがいません。「自分に合わせる」に切り替えてください。",
"ERR_NO_VIDEO_TAB": "ビデオタブに接続できませんでした。",
"ERR_SELECT_VIDEO": "最初にビデオを選択してください!",
"TOAST_INVITE_COPIED": "招待リンクをコピーしました!",
"TOAST_COPY_FAILED": "クリップボードへのコピーに失敗しました",
"TOAST_LOBBY_SKIPPED": "エピソードロビーをスキップしました。",
"TOAST_LOBBY_SKIP_FAILED": "ロビーのスキップに失敗しました。",
"TOAST_LOGS_COPIED": "コピーしました!",
"TOAST_PEER_JOINED": "{name}が参加しました",
"TOAST_PEER_LEFT": "{name}が退室しました",
"TOAST_PEER_ACTION": "{name}が{action}",
"STATUS_CONNECTED": "接続完了",
"STATUS_RECONNECTING": "再接続中...",
"STATUS_CONNECTING": "接続中...",
"STATUS_FAILED": "失敗",
"STATUS_DISCONNECTED": "切断されました",
"STATUS_IDLE": "接続準備完了",
"STATUS_IDLE_TOOLTIP": "KoalaSyncの準備ができました。ルームに参加するか作成して接続し、同期を開始してください。",
"BTN_STATE_JOINING": "🚀 参加中...",
"BTN_STATE_RECONNECTING": "🔄 再接続中...",
"BTN_STATE_PLAYING": "▶ 再生中...",
"BTN_STATE_PAUSING": "⏸ 一時停止中...",
"BTN_STATE_SYNCING_GROUP": "グループに同期中 ({time})...",
"BTN_STATE_SYNCING": "同期中...",
"BTN_STATE_SYNCED": "✅ 同期完了!",
"NOTIF_PLAY": "再生を開始しました",
"NOTIF_PAUSE": "再生を一時停止しました",
"NOTIF_SEEK": "動画をシークしました",
"NOTIF_FORCE_PREPARE": "強制同期を開始しました",
"NOTIF_FORCE_EXECUTE": "全員を同期しました",
"DEBUG_NO_TAB": "対象のタブが選択されていません。",
"DEBUG_COMM_FAIL": "タブのビデオと通信できませんでした。",
"EMPTY_PEERS_TITLE": "メンバーはまだいません",
"EMPTY_PEERS_HINT": "招待リンクを共有して始めましょう",
"EMPTY_HISTORY_TITLE": "アクティビティはまだありません",
"EMPTY_HISTORY_HINT": "履歴を表示するには再生、一時停止、またはシークを行います",
"EMPTY_LOGS_TITLE": "ログはありません",
"EMPTY_LOGS_HINT": "接続イベントがここに表示されます",
"EMPTY_ROOMS_TITLE": "アクティブなルームはありません",
"EMPTY_ROOMS_HINT": "ルームを作成するか、更新して公開ルームを探します",
"LABEL_YOU": "あなた",
"ONBOARDING_DONE": "完了!",
"LABEL_LOBBY_PEER_READY": "準備完了",
"LABEL_LOBBY_PEER_LOADING": "読み込み中...",
"LABEL_PASSWORD_PROTECTED": "パスワード保護",
"LABEL_PEERS_COUNT": "{count}人",
"LABEL_CUSTOM_SERVER": "カスタムサーバー",
"BTN_STATE_CREATING": "🚀 ルーム作成中...",
"NOTIF_LOBBY_CANCEL_TITLE": "KoalaSync — エピソード同期に失敗しました",
"NOTIF_LOBBY_CANCEL_MSG": "自動同期がキャンセルされました: {reason}。手動で同期する必要がある場合があります。",
"LOBBY_CANCEL_TIMEOUT": "タイムアウト",
"LOBBY_CANCEL_TIMEOUT_RECOVERED": "タイムアウト(回復済)",
"LOBBY_CANCEL_PEERS_LEFT": "他のすべてのメンバーが退室しました",
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "タイムアウト — 一部のメンバーがエピソードを読み込めませんでした",
"LOBBY_CANCEL_USER": "ユーザーによってキャンセルされました",
"NOTIF_ERROR_TITLE": "KoalaSyncエラー",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_REVIEW": "★ 評価する",
"FOOTER_SUPPORT_PROMPT": "KoalaSyncはいかが?レビューを書いてください!",
"LABEL_AUDIO_PROCESSING": "オーディオ処理",
"LABEL_AUDIO_PROCESSING_TOOLTIP": "動画再生に圧縮などのオーディオエフェクトを適用します",
"AUDIO_OPEN_SETTINGS": "開く",
"NEW_FEATURE_AUDIO": "新機能: オーディオ処理 — コンプレッサーを試してみよう!",
"AUDIO_BACK": "← 戻る",
"AUDIO_PAGE_TITLE": "オーディオ設定",
"AUDIO_MASTER_TOGGLE": "オーディオ処理",
"AUDIO_COMPRESSOR": "コンプレッサー",
"AUDIO_COMPRESSOR_ENABLE": "有効",
"AUDIO_PRESET": "プリセット",
"AUDIO_PRESET_RECOMMENDED": "推奨",
"AUDIO_PRESET_DYNAMIC_RANGE": "ダイナミックレンジ",
"AUDIO_PRESET_VOCAL_ENHANCEMENT": "ボーカル強調",
"AUDIO_PRESET_SMOOTH": "スムース",
"AUDIO_PRESET_CUSTOM": "カスタム",
"AUDIO_PARAM_THRESHOLD": "スレッショルド",
"AUDIO_PARAM_KNEE": "Knee",
"AUDIO_PARAM_RATIO": "Ratio",
"AUDIO_PARAM_ATTACK": "Attack",
"AUDIO_PARAM_RELEASE": "Release",
"AUDIO_EQUALIZER": "イコライザー",
"AUDIO_COMING_SOON": "近日公開",
"BTN_RESTART_TOUR": "チュートリアルを再起動",
"BTN_RESTART_TOUR_TOOLTIP": "オンボーディングチュートリアルを再起動する",
"HINT_SELECT_VIDEO": "ここで動画を選択してください!"
}
+233
View File
@@ -0,0 +1,233 @@
{
"LANG_CODE": "ko",
"HTML_CLASS": "lang-ko",
"APP_TITLE": "KoalaSync",
"TAB_ROOM": "방",
"TAB_ROOM_TOOLTIP": "방 설정 및 연결",
"TAB_SYNC": "동기화",
"TAB_SYNC_TOOLTIP": "비디오 동기화 제어 및 원격 작업",
"TAB_SETTINGS": "설정",
"TAB_SETTINGS_TOOLTIP": "확장 프로그램 기본 설정",
"TAB_STATUS": "상태",
"TAB_STATUS_TOOLTIP": "고급 진단 및 로그",
"BTN_CREATE_ROOM": "+ 새 방 만들기",
"MANUAL_CONNECT_HEADER": "수동 연결 / 고급",
"LABEL_SERVER": "서버",
"BTN_SERVER_OFFICIAL": "공식",
"BTN_SERVER_OFFICIAL_TOOLTIP": "공식적이고 신뢰할 수 있는 서버 사용",
"BTN_SERVER_CUSTOM": "사용자 정의",
"BTN_SERVER_CUSTOM_TOOLTIP": "자체 호스팅 서버에 연결",
"PLACEHOLDER_SERVER_URL": "wss://your-server:3000",
"LABEL_ROOM_ID": "방 ID",
"LABEL_ROOM_ID_TOOLTIP": "동기화 방의 고유 식별자",
"PLACEHOLDER_ROOM_ID": "방 ID 입력",
"PLACEHOLDER_ROOM_ID_TOOLTIP": "참여하려는 방의 고유 ID",
"LABEL_PASSWORD": "비밀번호 (선택사항)",
"LABEL_PASSWORD_TOOLTIP": "방 액세스를 제한하기 위한 선택적 비밀번호",
"PLACEHOLDER_PASSWORD": "방 비밀번호 (선택사항)",
"PLACEHOLDER_PASSWORD_TOOLTIP": "방 비밀번호 (없으면 비워둠)",
"BTN_JOIN_ROOM": "방 참여 / 만들기",
"BTN_JOIN_ROOM_TOOLTIP": "방에 연결",
"LABEL_PUBLIC_ROOMS": "공개 방",
"LABEL_PUBLIC_ROOMS_TOOLTIP": "이 서버의 공개 방 목록",
"BTN_REFRESH": "새로고침",
"BTN_REFRESH_TOOLTIP": "공개 방 목록 새로고침",
"PUBLIC_ROOMS_REFRESHING": "새로고침 중...",
"BTN_REFRESH_COOLDOWN": "{seconds}초 대기",
"BTN_REFRESH_COOLDOWN_TOOLTIP": "방 목록 새로고침 대기 중입니다. {seconds}초 후에 다시 시도하세요.",
"PUBLIC_ROOMS_REFRESHING_COOLDOWN": "공개 방 새로고침 중. {seconds}초 후 새로고침 가능.",
"LABEL_ACTIVE_ROOM": "활성 방",
"LABEL_ACTIVE_ROOM_TOOLTIP": "현재 연결된 방",
"ACTIVE_ROOM_NONE": "없음",
"ACTIVE_SERVER_OFFICIAL": "공식 서버",
"LABEL_INVITE_LINK": "초대 링크",
"LABEL_INVITE_LINK_TOOLTIP": "친구들이 참여할 수 있도록 이 링크를 공유하세요",
"LABEL_PEERS_IN_ROOM": "방의 참여자",
"LABEL_PEERS_IN_ROOM_TOOLTIP": "현재 이 방에 연결되어 있는 다른 사용자들",
"LABEL_HOST_CONTROL": "호스트 제어",
"LABEL_HOST_CONTROL_TOOLTIP": "이 방에서 재생을 제어할 수 있는 사람 제한",
"LABEL_HOST_ONLY_TOGGLE": "나만 재생을 제어할 수 있음",
"NOTICE_HOST_CONTROLS": "호스트가 모두의 재생을 제어합니다.",
"NOTICE_COHOST_HINT": "팁: 아래 참가자 목록에서 개별 참가자에게 제어 권한을 부여하세요.",
"BADGE_HOST": "호스트",
"BADGE_GUEST": "게스트",
"BADGE_CONTROLLER": "제어자",
"BTN_GIVE_CONTROL": "제어 권한 부여",
"BTN_REVOKE_CONTROL": "취소",
"BADGE_DESYNCED": "단독",
"TOOLTIP_PEER_DESYNCED": "혼자 시청 중 — 호스트 명령을 무시합니다",
"HCM_DIALOG_TITLE": "KoalaSync · 호스트가 이 방을 제어합니다",
"HCM_DIALOG_BODY": "이 방에서는 호스트만 재생을 제어할 수 있습니다. 계속 함께 보시겠어요, 아니면 혼자 보시겠어요?",
"HCM_DIALOG_STAY": "동기화 유지",
"HCM_DIALOG_SOLO": "혼자 보기",
"HCM_BADGE_SOLO": "혼자 시청 중",
"HCM_BADGE_RESYNC": "다시 동기화",
"NO_PEERS_CONNECTED": "연결된 참여자가 없습니다",
"BTN_LEAVE_ROOM": "방 나가기",
"LABEL_SELECT_VIDEO": "비디오 선택",
"LABEL_SELECT_VIDEO_TOOLTIP": "동기화할 비디오가 포함된 브라우저 탭을 선택하세요",
"OPTION_SELECT_TAB": "-- 탭 선택 --",
"LABEL_REMOTE_CONTROL": "원격 제어",
"BTN_COPY_INVITE": "📋 초대 링크",
"BTN_COPY_INVITE_TOOLTIP": "초대 링크 복사",
"BTN_PLAY": "▶ 재생",
"BTN_PLAY_TOOLTIP": "모든 사람에게 재생 명령 전송",
"BTN_PAUSE": "⏸ 일시정지",
"BTN_PAUSE_TOOLTIP": "모든 사람에게 일시정지 명령 전송",
"BTN_SYNC": "⚡ 동기화",
"BTN_SYNC_TOOLTIP": "모든 사용자의 강제 동기화",
"OPTION_JUMP_TO_OTHERS": "다른 사람에게 이동",
"OPTION_JUMP_TO_ME": "나에게 이동",
"OPTION_JUMP_MODE_TOOLTIP": "동기화 대상 선택",
"LABEL_LAST_ACTIVITY": "최근 활동 상태",
"LABEL_LAST_ACTIVITY_TOOLTIP": "가장 최근의 재생, 일시정지, 탐색 명령을 표시합니다",
"NO_RECENT_COMMANDS": "최근 명령 없음",
"LOBBY_HEADER": "에피소드 로비",
"LOBBY_WAITING_FOR": "🎬 대기 중: \"{title}\"",
"LOBBY_WAITING_PEERS": "참여자 대기 중...",
"BTN_SKIP_PLAY": "건너뛰고 재생",
"BTN_SKIP_PLAY_TOOLTIP": "로비를 취소하고 재생합니다",
"LOBBY_CONNECT_FIRST": "먼저 방에 연결하세요",
"LOBBY_CONNECT_FIRST_DESC": "비디오를 동기화하려면 초대 링크를 통해 방에 참여하거나 새 방을 만들어야 합니다.",
"BTN_CREATE_ROOM_ALT": "새 방 만들기",
"BTN_CREATE_ROOM_ALT_TOOLTIP": "새로운 무작위 방을 만들고 참여",
"LABEL_USERNAME": "사용자 이름",
"LABEL_USERNAME_TOOLTIP": "사용자 이름은 다른 사람들이 귀하를 식별하는 데 도움이 됩니다.",
"PLACEHOLDER_USERNAME": "익명의 코알라",
"LABEL_HIDE_CLUTTER": "복잡한 탭 숨기기",
"LABEL_HIDE_CLUTTER_TOOLTIP": "목록을 깔끔하게 유지하기 위해 비디오가 없는 탭과 관련 없는 도메인을 필터링합니다",
"LABEL_AUTO_SYNC_NEXT": "다음 에피소드 자동 동기화",
"LABEL_AUTO_SYNC_NEXT_TOOLTIP": "에피소드가 변경되면 자동으로 일시정지하고 모든 참여자를 기다린 후 함께 동기화하여 시작합니다.",
"LABEL_AUTO_COPY_INVITE": "초대 링크 자동 복사",
"LABEL_AUTO_COPY_INVITE_TOOLTIP": "새 방을 만들 때 초대 링크를 클립보드에 자동으로 복사합니다.",
"LABEL_NOTIFICATIONS": "브라우저 알림",
"LABEL_NOTIFICATIONS_TOOLTIP": "누군가 참여/퇴장하거나 재생/일시정지할 때 시스템 알림을 표시합니다.",
"LABEL_LANGUAGE": "앱 언어",
"LABEL_LANGUAGE_TOOLTIP": "선호하는 확장 프로그램 언어를 선택하세요",
"LABEL_TROUBLESHOOTING": "문제 해결",
"LABEL_TROUBLESHOOTING_TOOLTIP": "연결 문제 해결을 위한 도구",
"BTN_REGEN_ID": "피어 ID 재생성",
"BTN_REGEN_ID_TOOLTIP": "내부 ID를 재생성하고 다시 연결합니다",
"REGEN_ID_DESC": "\"중복된 ID\" 오류가 발생할 경우에 사용하세요.",
"REGEN_ID_OTHER_ISSUE": "다른 문제? GitHub Issue 열기",
"TOAST_ID_REGENERATED": "ID가 재생성되었습니다 — 다시 연결 중…",
"LABEL_CONN_STATUS": "연결 상태",
"LABEL_CONN_STATUS_TOOLTIP": "현재 WebSocket 연결 상태",
"CONN_STATUS_DISCONNECTED": "연결 끊김",
"BTN_RETRY": "재시도",
"BTN_RETRY_TOOLTIP": "서버에 재연결 시도",
"BTN_COPY_LOGS": "로그 복사",
"BTN_COPY_LOGS_TOOLTIP": "공유를 위해 로그를 클립보드에 복사",
"LABEL_VIDEO_DEBUG": "비디오 디버그 정보",
"LABEL_VIDEO_DEBUG_TOOLTIP": "현재 선택된 비디오 요소에 대한 기술적 세부 정보",
"VIDEO_DEBUG_EMPTY": "선택된 탭이 없거나 비디오가 감지되지 않았습니다.",
"LABEL_HISTORY": "전체 작업 내역",
"LABEL_HISTORY_TOOLTIP": "방 내 모든 동기화 명령의 시간순 로그",
"HISTORY_EMPTY": "활동 없음",
"LABEL_LOGS": "로그 (최근 50개)",
"LABEL_LOGS_TOOLTIP": "디버깅용 기술 연결 로그",
"BTN_CLEAR": "지우기",
"BTN_CLEAR_TOOLTIP": "로그 출력 지우기",
"LABEL_GITHUB": "GitHub 저장소",
"BTN_ONBOARDING_SKIP": "건너뛰기",
"BTN_ONBOARDING_SKIP_TOOLTIP": "튜토리얼 건너뛰기",
"BTN_ONBOARDING_NEXT": "다음",
"BTN_ONBOARDING_NEXT_TOOLTIP": "다음 단계로 이동",
"ONBOARDING_1_TITLE": "KoalaSync에 오신 것을 환영합니다!",
"ONBOARDING_1_TEXT": "어디에 있든 완벽하게 동기화된 비디오를 함께 시청하세요. 간단히 둘러볼까요!",
"ONBOARDING_2_TITLE": "1. 방 만들기",
"ONBOARDING_2_TEXT": "여기서 시작하세요. 방을 만들고 친구들에게 초대 링크를 공유하세요.",
"ONBOARDING_3_TITLE": "2. 비디오 선택",
"ONBOARDING_3_TEXT": "동기화할 비디오를 선택하려면 여기로 이동하세요. 재생, 일시정지, 탐색이 모든 참여자에게 동기화됩니다.",
"ONBOARDING_4_TITLE": "3. 개인화",
"ONBOARDING_4_TEXT": "친구들이 나를 알아볼 수 있도록 재미있는 사용자 이름을 골라보세요.",
"ONBOARDING_5_TITLE": "모든 준비가 끝났습니다!",
"ONBOARDING_5_TEXT": "팝콘을 준비할 시간입니다. 함께 즐겁게 시청하세요!",
"ERR_CONN_TIMEOUT": "연결 시간이 초과되었습니다. 다시 시도해 주세요.",
"ERR_INVALID_SERVER_URL": "올바르지 않은 서버 URL 형식입니다.",
"ERR_IDENTITY_NOT_LOADED": "ID가 아직 로드되지 않았습니다. 잠시 후 다시 시도해 주세요.",
"ERR_NO_PEERS_TIME": "시간을 알고 있는 다른 피어가 없습니다. '나에게 이동'으로 전환하세요.",
"ERR_NO_VIDEO_TAB": "비디오 탭에 연결할 수 없습니다.",
"ERR_SELECT_VIDEO": "먼저 비디오를 선택하세요!",
"TOAST_INVITE_COPIED": "초대 링크가 복사되었습니다!",
"TOAST_COPY_FAILED": "클립보드 복사에 실패했습니다",
"TOAST_LOBBY_SKIPPED": "에피소드 로비를 건너뛰었습니다.",
"TOAST_LOBBY_SKIP_FAILED": "로비 건너뛰기에 실패했습니다.",
"TOAST_LOGS_COPIED": "복사됨!",
"TOAST_PEER_JOINED": "{name} 님이 방에 참여했습니다",
"TOAST_PEER_LEFT": "{name} 님이 방에서 나갔습니다",
"TOAST_PEER_ACTION": "{name} 님이 {action}",
"STATUS_CONNECTED": "연결됨",
"STATUS_RECONNECTING": "재연결 중...",
"STATUS_CONNECTING": "연결 중...",
"STATUS_FAILED": "실패",
"STATUS_DISCONNECTED": "연결 끊김",
"STATUS_IDLE": "연결 준비 완료",
"STATUS_IDLE_TOOLTIP": "KoalaSync가 준비되었습니다. 방에 참여하거나 생성하여 연결하고 동기화를 시작하세요.",
"BTN_STATE_JOINING": "🚀 참여 중...",
"BTN_STATE_RECONNECTING": "🔄 재연결 중...",
"BTN_STATE_PLAYING": "▶ 재생 중...",
"BTN_STATE_PAUSING": "⏸ 일시정지 중...",
"BTN_STATE_SYNCING_GROUP": "그룹에 동기화 중 ({time})...",
"BTN_STATE_SYNCING": "동기화 중...",
"BTN_STATE_SYNCED": "✅ 동기화됨!",
"NOTIF_PLAY": "재생을 시작했습니다",
"NOTIF_PAUSE": "재생을 일시정지했습니다",
"NOTIF_SEEK": "비디오를 탐색했습니다",
"NOTIF_FORCE_PREPARE": "강제 동기화를 시작했습니다",
"NOTIF_FORCE_EXECUTE": "모든 사용자를 동기화했습니다",
"DEBUG_NO_TAB": "대상 탭이 선택되지 않았습니다.",
"DEBUG_COMM_FAIL": "탭 비디오와 통신할 수 없습니다.",
"EMPTY_PEERS_TITLE": "참여자 없음",
"EMPTY_PEERS_HINT": "시작하려면 초대 링크를 공유하세요",
"EMPTY_HISTORY_TITLE": "활동 없음",
"EMPTY_HISTORY_HINT": "기록을 보려면 재생, 일시정지 또는 탐색을 해보세요",
"EMPTY_LOGS_TITLE": "로그 없음",
"EMPTY_LOGS_HINT": "연결 이벤트가 여기에 표시됩니다",
"EMPTY_ROOMS_TITLE": "활성 방 없음",
"EMPTY_ROOMS_HINT": "방을 만들거나 새로고침하여 공개 방을 찾으세요",
"LABEL_YOU": "나",
"ONBOARDING_DONE": "완료!",
"LABEL_LOBBY_PEER_READY": "준비",
"LABEL_LOBBY_PEER_LOADING": "로딩 중...",
"LABEL_PASSWORD_PROTECTED": "비밀번호 보호됨",
"LABEL_PEERS_COUNT": "{count}명",
"LABEL_CUSTOM_SERVER": "사용자 정의 서버",
"BTN_STATE_CREATING": "🚀 방 만드는 중...",
"NOTIF_LOBBY_CANCEL_TITLE": "KoalaSync — 에피소드 동기화 실패",
"NOTIF_LOBBY_CANCEL_MSG": "자동 동기화 취소됨: {reason}. 수동 동기화가 필요할 수 있습니다.",
"LOBBY_CANCEL_TIMEOUT": "시간 초과",
"LOBBY_CANCEL_TIMEOUT_RECOVERED": "시간 초과 (복구됨)",
"LOBBY_CANCEL_PEERS_LEFT": "다른 모든 피어가 나갔습니다",
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "시간 초과 — 모든 피어가 에피소드를 로드하지 못했습니다",
"LOBBY_CANCEL_USER": "사용자에 의해 취소됨",
"NOTIF_ERROR_TITLE": "KoalaSync 오류",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_REVIEW": "★ 평가하기",
"FOOTER_SUPPORT_PROMPT": "KoalaSync가 마음에 드세요? 리뷰를 남겨주세요!",
"LABEL_AUDIO_PROCESSING": "오디오 처리",
"LABEL_AUDIO_PROCESSING_TOOLTIP": "비디오 재생에 압축과 같은 오디오 효과를 적용합니다",
"AUDIO_OPEN_SETTINGS": "열기",
"NEW_FEATURE_AUDIO": "신규: 오디오 처리 — 컴프레서를 사용해보세요!",
"AUDIO_BACK": "← 뒤로",
"AUDIO_PAGE_TITLE": "오디오 설정",
"AUDIO_MASTER_TOGGLE": "오디오 처리",
"AUDIO_COMPRESSOR": "컴프레서",
"AUDIO_COMPRESSOR_ENABLE": "활성화",
"AUDIO_PRESET": "프리셋",
"AUDIO_PRESET_RECOMMENDED": "추천",
"AUDIO_PRESET_DYNAMIC_RANGE": "다이내믹 레인지",
"AUDIO_PRESET_VOCAL_ENHANCEMENT": "음성 강화",
"AUDIO_PRESET_SMOOTH": "부드럽게",
"AUDIO_PRESET_CUSTOM": "사용자 정의",
"AUDIO_PARAM_THRESHOLD": "임계값",
"AUDIO_PARAM_KNEE": "Knee",
"AUDIO_PARAM_RATIO": "Ratio",
"AUDIO_PARAM_ATTACK": "Attack",
"AUDIO_PARAM_RELEASE": "Release",
"AUDIO_EQUALIZER": "이퀄라이저",
"AUDIO_COMING_SOON": "출시 예정",
"BTN_RESTART_TOUR": "튜토리얼 다시 시작",
"BTN_RESTART_TOUR_TOOLTIP": "온보딩 튜토리얼을 다시 시작합니다",
"HINT_SELECT_VIDEO": "여기에서 비디오를 선택하세요!"
}
+233
View File
@@ -0,0 +1,233 @@
{
"LANG_CODE": "nl",
"HTML_CLASS": "lang-nl",
"APP_TITLE": "KoalaSync",
"TAB_ROOM": "Kamer",
"TAB_ROOM_TOOLTIP": "Kamerinstellingen en verbinding",
"TAB_SYNC": "Sync",
"TAB_SYNC_TOOLTIP": "Videobeheer en externe acties",
"TAB_SETTINGS": "Instellingen",
"TAB_SETTINGS_TOOLTIP": "Extensievoorkeuren",
"TAB_STATUS": "Status",
"TAB_STATUS_TOOLTIP": "Geavanceerde diagnostische gegevens & logs",
"BTN_CREATE_ROOM": "+ Nieuwe kamer maken",
"MANUAL_CONNECT_HEADER": "Handmatig verbinden / Geavanceerd",
"LABEL_SERVER": "Server",
"BTN_SERVER_OFFICIAL": "Officieel",
"BTN_SERVER_OFFICIAL_TOOLTIP": "Gebruik de officiële betrouwbare server",
"BTN_SERVER_CUSTOM": "Aangepast",
"BTN_SERVER_CUSTOM_TOOLTIP": "Verbind met uw eigen gehoste server",
"PLACEHOLDER_SERVER_URL": "wss://uw-server:3000",
"LABEL_ROOM_ID": "Kamer-ID",
"LABEL_ROOM_ID_TOOLTIP": "De unieke identificatie voor uw synchronisatiekamer",
"PLACEHOLDER_ROOM_ID": "Voer kamer-ID in",
"PLACEHOLDER_ROOM_ID_TOOLTIP": "Het unieke ID van de kamer waar u aan wilt deelnemen",
"LABEL_PASSWORD": "Wachtwoord (optioneel)",
"LABEL_PASSWORD_TOOLTIP": "Optioneel wachtwoord om toegang tot de kamer te beperken",
"PLACEHOLDER_PASSWORD": "Kamerwachtwoord (optioneel)",
"PLACEHOLDER_PASSWORD_TOOLTIP": "Wachtwoord voor de kamer (leeg laten indien geen)",
"BTN_JOIN_ROOM": "Deelnemen / Kamer maken",
"BTN_JOIN_ROOM_TOOLTIP": "Verbinden met de kamer",
"LABEL_PUBLIC_ROOMS": "Openbare kamers",
"LABEL_PUBLIC_ROOMS_TOOLTIP": "Lijst met openbaar beschikbare kamers op deze server",
"BTN_REFRESH": "VERNIEUWEN",
"BTN_REFRESH_TOOLTIP": "Vernieuw de lijst met openbare kamers",
"PUBLIC_ROOMS_REFRESHING": "Vernieuwen...",
"BTN_REFRESH_COOLDOWN": "WACHT {seconds}s",
"BTN_REFRESH_COOLDOWN_TOOLTIP": "Vernieuwen van kamerlijst is aan het afkoelen. Probeer opnieuw in {seconds}s.",
"PUBLIC_ROOMS_REFRESHING_COOLDOWN": "Openbare kamers vernieuwen. Volgende verversing beschikbaar in {seconds}s.",
"LABEL_ACTIVE_ROOM": "Actieve kamer",
"LABEL_ACTIVE_ROOM_TOOLTIP": "De kamer waar u momenteel mee bent verbonden",
"ACTIVE_ROOM_NONE": "GEEN",
"ACTIVE_SERVER_OFFICIAL": "Officiële server",
"LABEL_INVITE_LINK": "Uitnodigingslink",
"LABEL_INVITE_LINK_TOOLTIP": "Deel deze link met vrienden zodat ze kunnen deelnemen",
"LABEL_PEERS_IN_ROOM": "Deelnemers in kamer",
"LABEL_PEERS_IN_ROOM_TOOLTIP": "Andere gebruikers die momenteel zijn verbonden met deze kamer",
"LABEL_HOST_CONTROL": "Hostbeheer",
"LABEL_HOST_CONTROL_TOOLTIP": "Bepaal wie de weergave in deze kamer mag bedienen",
"LABEL_HOST_ONLY_TOGGLE": "Alleen ik mag de weergave bedienen",
"NOTICE_HOST_CONTROLS": "De host bedient de weergave voor iedereen.",
"NOTICE_COHOST_HINT": "Tip: geef afzonderlijke deelnemers de besturing in de lijst hieronder.",
"BADGE_HOST": "Host",
"BADGE_GUEST": "Gast",
"BADGE_CONTROLLER": "Bediener",
"BTN_GIVE_CONTROL": "Bediening geven",
"BTN_REVOKE_CONTROL": "Intrekken",
"BADGE_DESYNCED": "Solo",
"TOOLTIP_PEER_DESYNCED": "Zelfstandig aan het kijken — negeert de commando's van de host",
"HCM_DIALOG_TITLE": "KoalaSync · De host bedient deze kamer",
"HCM_DIALOG_BODY": "Alleen de host kan de weergave in deze kamer bedienen. Samen blijven kijken of zelf verder kijken?",
"HCM_DIALOG_STAY": "Gesynchroniseerd blijven",
"HCM_DIALOG_SOLO": "Zelf verder kijken",
"HCM_BADGE_SOLO": "Je kijkt zelfstandig",
"HCM_BADGE_RESYNC": "Opnieuw sync",
"NO_PEERS_CONNECTED": "Geen deelnemers verbonden",
"BTN_LEAVE_ROOM": "Kamer verlaten",
"LABEL_SELECT_VIDEO": "Selecteer video",
"LABEL_SELECT_VIDEO_TOOLTIP": "Kies het browsertabblad met de video die u wilt synchroniseren",
"OPTION_SELECT_TAB": "-- Selecteer een tabblad --",
"LABEL_REMOTE_CONTROL": "Afstandsbediening",
"BTN_COPY_INVITE": "📋 Uitnodigingslink",
"BTN_COPY_INVITE_TOOLTIP": "Uitnodigingslink kopiëren",
"BTN_PLAY": "▶ Afspelen",
"BTN_PLAY_TOOLTIP": "Stuur een afspeelopdracht naar iedereen",
"BTN_PAUSE": "⏸ Pauzeren",
"BTN_PAUSE_TOOLTIP": "Stuur een pauzeeropdracht naar iedereen",
"BTN_SYNC": "⚡ SYNC",
"BTN_SYNC_TOOLTIP": "Dwing alle gebruikers om te synchroniseren",
"OPTION_JUMP_TO_OTHERS": "Spring naar anderen",
"OPTION_JUMP_TO_ME": "Spring naar mij",
"OPTION_JUMP_MODE_TOOLTIP": "Kies synchronisatiedoel",
"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_WAITING_FOR": "🎬 Wachten op: \"{title}\"",
"LOBBY_WAITING_PEERS": "Wachten op deelnemers...",
"BTN_SKIP_PLAY": "Overslaan & toch afspelen",
"BTN_SKIP_PLAY_TOOLTIP": "Lobby annuleren en toch afspelen",
"LOBBY_CONNECT_FIRST": "Verbind eerst met een kamer",
"LOBBY_CONNECT_FIRST_DESC": "U moet deelnemen aan een kamer via een uitnodigingslink of een nieuwe maken om video's te synchroniseren.",
"BTN_CREATE_ROOM_ALT": "Nieuwe kamer maken",
"BTN_CREATE_ROOM_ALT_TOOLTIP": "Maak een nieuwe willekeurige kamer en neem deel",
"LABEL_USERNAME": "Uw gebruikersnaam",
"LABEL_USERNAME_TOOLTIP": "Gebruikersnaam helpt anderen u te identificeren.",
"PLACEHOLDER_USERNAME": "Anonieme Koala",
"LABEL_HIDE_CLUTTER": "Overbodige tabbladen verbergen",
"LABEL_HIDE_CLUTTER_TOOLTIP": "Filtert tabbladen zonder video's en niet-gerelateerde domeinen om de lijst schoon te houden",
"LABEL_AUTO_SYNC_NEXT": "Volgende aflevering automatisch syncen",
"LABEL_AUTO_SYNC_NEXT_TOOLTIP": "Pauzeert automatisch en wacht op alle deelnemers wanneer een aflevering verandert, en start dan gezamenlijk gesynchroniseerd.",
"LABEL_AUTO_COPY_INVITE": "Uitnodigingslink automatisch kopiëren",
"LABEL_AUTO_COPY_INVITE_TOOLTIP": "Kopieert de uitnodigingslink automatisch naar uw klembord bij het maken van een nieuwe kamer.",
"LABEL_NOTIFICATIONS": "Browsernotificaties",
"LABEL_NOTIFICATIONS_TOOLTIP": "Toont systeemnotificaties wanneer iemand deelneemt/verlaat of afspeelt/pauzeert.",
"LABEL_LANGUAGE": "Taal app",
"LABEL_LANGUAGE_TOOLTIP": "Kies uw favoriete taal voor de extensie",
"LABEL_TROUBLESHOOTING": "Problemen oplossen",
"LABEL_TROUBLESHOOTING_TOOLTIP": "Hulpmiddelen voor het oplossen van verbindingsproblemen",
"BTN_REGEN_ID": "Peer-ID opnieuw genereren",
"BTN_REGEN_ID_TOOLTIP": "Genereer uw interne ID opnieuw en maak opnieuw verbinding",
"REGEN_ID_DESC": "Gebruik dit als u fouten ziet over 'Duplicate Identity'.",
"REGEN_ID_OTHER_ISSUE": "Ander probleem? Open een GitHub Issue",
"TOAST_ID_REGENERATED": "Identiteit opnieuw gegenereerd — opnieuw verbinden…",
"LABEL_CONN_STATUS": "Verbindingsstatus",
"LABEL_CONN_STATUS_TOOLTIP": "Huidige WebSocket-verbindingsstatus",
"CONN_STATUS_DISCONNECTED": "Verbinding verbroken",
"BTN_RETRY": "OPNIEUW PROBEREN",
"BTN_RETRY_TOOLTIP": "Proberen opnieuw verbinding te maken met de server",
"BTN_COPY_LOGS": "Logs kopiëren",
"BTN_COPY_LOGS_TOOLTIP": "Kopieer logboeken naar het klembord om te delen",
"LABEL_VIDEO_DEBUG": "Video-debuginfo",
"LABEL_VIDEO_DEBUG_TOOLTIP": "Technische details over het momenteel geselecteerde video-element",
"VIDEO_DEBUG_EMPTY": "Geen tabblad geselecteerd of video gedetecteerd.",
"LABEL_HISTORY": "Volledige actiegeschiedenis",
"LABEL_HISTORY_TOOLTIP": "Chronologisch logboek van alle synchronisatieopdrachten in de kamer",
"HISTORY_EMPTY": "Nog geen activiteit",
"LABEL_LOGS": "Logs (Laatste 50)",
"LABEL_LOGS_TOOLTIP": "Technische verbindingslogboeken voor debuggen",
"BTN_CLEAR": "WISSEN",
"BTN_CLEAR_TOOLTIP": "Logboekuitvoer wissen",
"LABEL_GITHUB": "GitHub-repository",
"BTN_ONBOARDING_SKIP": "Overslaan",
"BTN_ONBOARDING_SKIP_TOOLTIP": "Sla de tutorial over",
"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_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",
"ONBOARDING_3_TEXT": "Navigeer hiernaartoe om de video te selecteren die u wilt synchroniseren. Spelen, pauzeren en zoeken — iedereen blijft synchroon.",
"ONBOARDING_4_TITLE": "3. Personaliseren",
"ONBOARDING_4_TEXT": "Kies een leuke gebruikersnaam zodat uw vrienden weten wie u bent.",
"ONBOARDING_5_TITLE": "U bent er helemaal klaar voor!",
"ONBOARDING_5_TEXT": "Tijd voor popcorn. Veel plezier met samen kijken!",
"ERR_CONN_TIMEOUT": "Verbinding time-out. Probeer het opnieuw.",
"ERR_INVALID_SERVER_URL": "Ongeldig server URL-formaat.",
"ERR_IDENTITY_NOT_LOADED": "Identiteit nog niet geladen. Wacht een moment en probeer het opnieuw.",
"ERR_NO_PEERS_TIME": "Geen andere deelnemers met een bekende tijd. Schakel over naar 'Spring naar mij'.",
"ERR_NO_VIDEO_TAB": "Kon geen verbinding maken met videotabblad.",
"ERR_SELECT_VIDEO": "Selecteer eerst een video!",
"TOAST_INVITE_COPIED": "Uitnodigingslink gekopieerd!",
"TOAST_COPY_FAILED": "Kopiëren naar klembord mislukt",
"TOAST_LOBBY_SKIPPED": "Episode Lobby overgeslagen.",
"TOAST_LOBBY_SKIP_FAILED": "Lobby overslaan mislukt.",
"TOAST_LOGS_COPIED": "Gekopieerd!",
"TOAST_PEER_JOINED": "{name} neemt deel aan de kamer",
"TOAST_PEER_LEFT": "{name} heeft de kamer verlaten",
"TOAST_PEER_ACTION": "{name} {action}",
"STATUS_CONNECTED": "Verbonden",
"STATUS_RECONNECTING": "Opnieuw verbinden...",
"STATUS_CONNECTING": "Verbinden...",
"STATUS_FAILED": "Mislukt",
"STATUS_DISCONNECTED": "Verbinding verbroken",
"STATUS_IDLE": "Gereed om te verbinden",
"STATUS_IDLE_TOOLTIP": "KoalaSync is gereed. Neem deel aan een kamer of maak er een aan om te verbinden en te synchroniseren.",
"BTN_STATE_JOINING": "🚀 Deelnemen...",
"BTN_STATE_RECONNECTING": "🔄 Opnieuw verbinden...",
"BTN_STATE_PLAYING": "▶ Afspelen...",
"BTN_STATE_PAUSING": "⏸ Pauzeren...",
"BTN_STATE_SYNCING_GROUP": "Synchroniseren met groep ({time})...",
"BTN_STATE_SYNCING": "Synchroniseren...",
"BTN_STATE_SYNCED": "✅ Gesynchroniseerd!",
"NOTIF_PLAY": "is begonnen met afspelen",
"NOTIF_PAUSE": "heeft afspelen gepauzeerd",
"NOTIF_SEEK": "heeft de video gespoeld",
"NOTIF_FORCE_PREPARE": "is een geforceerde sync gestart",
"NOTIF_FORCE_EXECUTE": "heeft iedereen gesynchroniseerd",
"DEBUG_NO_TAB": "Geen doeltabblad geselecteerd.",
"DEBUG_COMM_FAIL": "Kon niet communiceren met de videotab.",
"EMPTY_PEERS_TITLE": "Nog geen deelnemers",
"EMPTY_PEERS_HINT": "Deel uw uitnodigingslink om te beginnen",
"EMPTY_HISTORY_TITLE": "Nog geen activiteit",
"EMPTY_HISTORY_HINT": "Speel af, pauzeer of spoel om geschiedenis te zien",
"EMPTY_LOGS_TITLE": "Geen logs",
"EMPTY_LOGS_HINT": "Verbindingsgebeurtenissen verschijnen hier",
"EMPTY_ROOMS_TITLE": "Geen actieve kamers",
"EMPTY_ROOMS_HINT": "Maak een kamer of vernieuw om openbare te vinden",
"LABEL_YOU": "Jij",
"ONBOARDING_DONE": "Klaar!",
"LABEL_LOBBY_PEER_READY": "Gereed",
"LABEL_LOBBY_PEER_LOADING": "Laden...",
"LABEL_PASSWORD_PROTECTED": "Wachtwoord beveiligd",
"LABEL_PEERS_COUNT": "{count} deelnemers",
"LABEL_CUSTOM_SERVER": "Aangepaste server",
"BTN_STATE_CREATING": "🚀 Kamer maken...",
"NOTIF_LOBBY_CANCEL_TITLE": "KoalaSync — Episode Sync mislukt",
"NOTIF_LOBBY_CANCEL_MSG": "Auto-sync geannuleerd: {reason}. Mogelijk moet u handmatig synchroniseren.",
"LOBBY_CANCEL_TIMEOUT": "Time-out",
"LOBBY_CANCEL_TIMEOUT_RECOVERED": "Time-out (hersteld)",
"LOBBY_CANCEL_PEERS_LEFT": "Alle andere deelnemers zijn vertrokken",
"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_REVIEW": "★ Beoordelen",
"FOOTER_SUPPORT_PROMPT": "Vind je KoalaSync leuk? Laat een review achter!",
"LABEL_AUDIO_PROCESSING": "Audioverwerking",
"LABEL_AUDIO_PROCESSING_TOOLTIP": "Past audio-effecten zoals compressie toe op video-afspelen",
"AUDIO_OPEN_SETTINGS": "Openen",
"NEW_FEATURE_AUDIO": "Nieuw: Audioverwerking — probeer de compressor!",
"AUDIO_BACK": "← Terug",
"AUDIO_PAGE_TITLE": "Audio-instellingen",
"AUDIO_MASTER_TOGGLE": "Audioverwerking",
"AUDIO_COMPRESSOR": "Compressor",
"AUDIO_COMPRESSOR_ENABLE": "Ingeschakeld",
"AUDIO_PRESET": "Voorinstelling",
"AUDIO_PRESET_RECOMMENDED": "Aanbevolen",
"AUDIO_PRESET_DYNAMIC_RANGE": "Dynamisch bereik",
"AUDIO_PRESET_VOCAL_ENHANCEMENT": "Stemverbetering",
"AUDIO_PRESET_SMOOTH": "Zacht",
"AUDIO_PRESET_CUSTOM": "Aangepast",
"AUDIO_PARAM_THRESHOLD": "Drempel",
"AUDIO_PARAM_KNEE": "Knee",
"AUDIO_PARAM_RATIO": "Ratio",
"AUDIO_PARAM_ATTACK": "Attack",
"AUDIO_PARAM_RELEASE": "Release",
"AUDIO_EQUALIZER": "Equalizer",
"AUDIO_COMING_SOON": "Binnenkort beschikbaar",
"BTN_RESTART_TOUR": "Tutorial opnieuw starten",
"BTN_RESTART_TOUR_TOOLTIP": "Start de introductie-tutorial opnieuw",
"HINT_SELECT_VIDEO": "Selecteer hier je video!"
}

Some files were not shown because too many files have changed in this diff Show More