Compare commits

..

46 Commits

Author SHA1 Message Date
Timo 750b47d32a fix(i18n): correct website and extension copy 2026-07-26 05:53:57 +02:00
Timo 03710cec33 docs(comparisons): reflect encrypted text chat 2026-07-26 05:06:03 +02:00
Timo 1b97151e69 docs(privacy): document encrypted room chat 2026-07-26 04:32:06 +02:00
Timo 63db02211b fix(chat): harden overlay interactions and notifications 2026-07-26 04:14:25 +02:00
GitHub Action 90a58eb507 chore(release): update versions to v3.0.0 [skip ci] 2026-07-26 00:25:06 +00:00
KoalaDev 72113b6504 Merge pull request #32 from Shik3i/agent/update-tooling-node24
Prepare v3.0.0: encrypted text chat and runtime updates
2026-07-26 02:24:12 +02:00
Timo a0954079a1 harden chat settings and server image 2026-07-26 02:22:07 +02:00
Timo 9251ae6aff add dedicated chat settings 2026-07-26 02:12:28 +02:00
Timo 511e042b0b update tooling and server runtime 2026-07-26 01:58:23 +02:00
KoalaDev 372c384a19 Merge pull request #31 from Shik3i/agent/dependabot-security-updates
Fix Dependabot build dependency alerts
2026-07-26 01:32:41 +02:00
Timo 79715046a8 fix npm 11 lockfile portability 2026-07-26 01:23:07 +02:00
Timo 2ae79386a0 fix vulnerable build dependencies 2026-07-26 01:18:24 +02:00
Timo 883e49433e Merge branch 'feature/textchat' 2026-07-26 01:04:38 +02:00
Timo 59135b319e Merge remote-tracking branch 'origin/main' into feature/textchat
# Conflicts:
#	website/build.cjs
2026-07-26 01:03:27 +02:00
Timo ae2238e3bc docs: plan modern chat for v3.0 2026-07-26 01:02:33 +02:00
Timo 77790a279c fix(chat): preserve mixed-version compatibility 2026-07-26 01:02:20 +02:00
KoalaDev 2df048621b Centralize KoalaStuff legal notice 2026-07-23 03:04:20 +02:00
KoalaDev 17c3ea295a Centralize KoalaStuff legal notice 2026-07-23 02:59:58 +02:00
Timo 5d3cee4271 fix: update sitemap dates for dirty sources 2026-07-19 21:41:45 +02:00
Timo 1149de7ab7 fix: derive sitemap dates from page history 2026-07-19 21:33:53 +02:00
Timo 4882e058f1 fix: keep support SEO and sitemap metadata accurate 2026-07-19 21:21:49 +02:00
Timo 8d7085c97e fix: hide access banner on smaller screens 2026-07-19 21:10:50 +02:00
Timo 630869260b ci: publish text chat server beta 2026-07-19 20:43:11 +02:00
Timo e9d735cd39 Merge remote-tracking branch 'origin/main' into feature/textchat
# Conflicts:
#	extension/background.js
#	extension/popup.js
2026-07-19 20:32:24 +02:00
Timo aee3588f43 Document v2.6.4 release 2026-07-16 12:01:21 +02:00
GitHub Action 7f898e6eb4 chore(release): update versions to v2.6.4 [skip ci] 2026-07-16 09:51:25 +00:00
Timo d2d4f5f3ea Localize extension store metadata 2026-07-16 11:50:21 +02:00
GitHub Action 6e625e3a38 chore(release): update versions to v2.6.3 [skip ci] 2026-07-15 11:37:32 +00:00
Timo 6aa2f53e6b Fix access guide spacing and user help link 2026-07-15 13:34:35 +02:00
Timo 2b485fed53 chore(release): update versions to v2.6.3 [skip ci] 2026-07-15 13:34:24 +02:00
Timo fe13275b5d Harden host access recovery races 2026-07-15 13:31:56 +02:00
Timo 61d0d64c7d Remove developer link from user access guide 2026-07-15 13:28:20 +02:00
Timo abcb10a1c3 Scope access help to Chromium rollout 2026-07-15 13:23:54 +02:00
Timo 03121477a8 Add dismissible localized access banner 2026-07-15 13:20:00 +02:00
Timo 285c04e00d Align browser help cards 2026-07-15 13:09:50 +02:00
Timo 1989eb639b Clarify sudden Chrome site access rollout 2026-07-15 13:07:48 +02:00
Timo fd48ce8481 Fix site access banner overlay and immediate guidance 2026-07-15 13:05:24 +02:00
GitHub Action 649c8796a9 chore(release): update versions to v2.6.2 [skip ci] 2026-07-15 10:47:24 +00:00
KoalaDev 354500873c fix: keep llms release version in sync 2026-07-15 10:05:26 +02:00
KoalaDev 9e0d294758 fix(chat): harden client state and messaging 2026-07-15 08:40:27 +02:00
KoalaDev 36c4c492d3 fix(chat): preserve detached overlay geometry 2026-07-15 07:31:27 +02:00
KoalaDev e9dc299fc9 feat(chat): add in-page overlay 2026-07-15 07:23:02 +02:00
KoalaDev ee0d68d8d3 feat(chat): add client encryption and key lifecycle 2026-07-15 07:10:32 +02:00
KoalaDev f2eee6dc41 feat(chat): add stateless ciphertext relay 2026-07-15 07:02:08 +02:00
KoalaDev 8fee98834c feat(chat): add encrypted invite contract 2026-07-15 06:58:22 +02:00
KoalaDev 771da1cf82 docs: add E2E chat feature brief
Design brief for the in-page E2E encrypted chat: threat model, key
derivation via URL fragment, #j2 link format, backward compatibility
with old extensions, and the constraints the discarded feature/soonTMChat
branch got wrong.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 06:25:52 +02:00
114 changed files with 6789 additions and 3204 deletions
+25 -4
View File
@@ -6,9 +6,9 @@ name: Beta Server Image
#
# Tags produced (on ghcr.io/<owner>/<repo>):
# - beta moving channel pointer to the newest build
# - <branch-slug> e.g. feature-host-control-mode
# - <branch-slug> e.g. feature-textchat
# - sha-<short-commit> immutable, pin to an exact build
# - <custom> only on manual run, e.g. hostcontrol01
# - <custom> only on manual run, e.g. chatbeta01
# Never ':latest' (flavor: latest=false).
#
# Remove this workflow once the feature is merged & released the normal way.
@@ -16,11 +16,11 @@ name: Beta Server Image
on:
push:
branches:
- feature/host-control-mode
- feature/textchat
workflow_dispatch:
inputs:
tag:
description: 'Extra immutable tag for this build (e.g. hostcontrol01). Optional.'
description: 'Extra immutable tag for this build (e.g. chatbeta01). Optional.'
required: false
default: ''
@@ -37,6 +37,27 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0
- 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
- name: Install server dependencies
run: npm ci
working-directory: server
- name: Run verification suite
run: npm run verify
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
+2
View File
@@ -23,6 +23,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v6
+8 -2
View File
@@ -72,6 +72,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v6
@@ -109,7 +111,11 @@ jobs:
sed -i "s/\"softwareVersion\": \".*\"/\"softwareVersion\": \"$VERSION\"/" website/template.html
echo " ✓ website/template.html -> softwareVersion $VERSION"
# 6. README.md — version badge & banner
# 6. website/llms.txt — machine-readable release metadata
sed -i "s/Current website release: .*/Current website release: $VERSION/" website/llms.txt
echo " ✓ website/llms.txt -> $VERSION"
# 7. 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"
@@ -120,7 +126,7 @@ jobs:
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 website/template.html README.md
git add extension/manifest.base.json shared/constants.js package.json website/version.json website/template.html website/llms.txt README.md
git commit -m "chore(release): update versions to $GITHUB_REF_NAME [skip ci]" || echo "No changes to commit"
git push origin HEAD:main
env:
+1 -1
View File
@@ -22,7 +22,7 @@ Please note that by participating in this project, you agree to abide by our [Co
### Prerequisites
- **Node.js** v18+
- **Node.js** v20.9+
- **Docker** (for local relay server testing)
### Quick Start
+2 -2
View File
@@ -6,7 +6,7 @@
<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/badge/Release-v2.6.1-blue?logo=github" alt="GitHub release"></a>
<a href="https://github.com/Shik3i/KoalaSync/releases"><img src="https://img.shields.io/badge/Release-v3.0.0-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>
@@ -14,7 +14,7 @@
<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.6.1 Release!</b> — See what's changed</a></p>
<p align="center"><a href="docs/CHANGELOG.md"><b>New v3.0.0 Release!</b> — See what's changed</a></p>
### 🌟 Why KoalaSync?
+44
View File
@@ -4,6 +4,50 @@ All notable changes to the KoalaSync browser extension and relay server.
---
## [v3.0.1] — 2026-07-26
### Added
- **Extension: Google Drive workaround** — Detects and controls videos opened on `drive.google.com` through the site-specific embedded-player path.
- **Extension: Chat event messages** — Optionally shows timestamped room and playback events in chat while keeping chat-message notifications enabled by default and event notifications disabled by default.
### Fixed
- **Extension: Chat interactions** — Prevents video-player keyboard shortcuts while typing, makes the floating chat draggable, restores left/right docking, updates unread notifications, and keeps chat timestamps visible.
- **Extension: Embedded-player selection** — Avoids selecting a stale hidden media frame when multiple nested frames are temporarily ambiguous.
- **Extension: Popup layout** — Moves the video-selection hint lower and reduces its glow.
- **Website and extension copy** — Corrects malformed translations, separates chat and playback notification labels, removes stale version references and unsupported comparison claims, and accurately documents encrypted live chat and operational logging.
## [v3.0.0] — 2026-07-26
### Added
- **Extension: Optional encrypted room chat** — Rooms now support live-only, end-to-end encrypted text messages. Chat is disabled and hidden by default and can be enabled explicitly in the extension options.
- **Extension: Persistent room chat key** — Every room generates and retains a chat key even while chat is disabled, so chat can be enabled later without creating a new room.
- **Extension: Floating player chat** — When enabled, chat is available from a floating control over the selected player and opens in an overlay without replacing the synchronized video.
- **Extension: Dedicated chat settings** — Chat now has its own settings section for enablement, left/right/free-floating placement, compact/standard/large/custom sizing, and bubble-versus-open startup behavior.
### Security
- **Ciphertext-only relay** — The relay receives and forwards encrypted message payloads without storing chat history or accepting client-supplied plaintext, sender identities, timestamps, or message IDs as authoritative.
- **Backward-compatible rollout** — Versioned `chat-v1` capabilities ensure old non-chat extensions never receive unknown chat events, while current extensions continue to work with older chatless relay versions.
### Changed
- **Build dependencies** — Updated the supported build and validation toolchain and refreshed compatible transitive dependencies.
- **Relay container runtime** — Moved the production container from end-of-life Node.js 20 to Node.js 24 LTS and made production dependency installation deterministic with `npm ci --omit=dev`.
## [v2.6.4] — 2026-07-16
### Changed
- **Extension: Localized store metadata** — Renamed the public extension title to `Watch Party - KoalaSync` and added natural, store-ready descriptions for all 15 supported locales while preserving regional language variants.
- **Extension: Locale-based manifest metadata** — The extension name and description now resolve through Chrome/Firefox `_locales` messages instead of being hardcoded in the manifest.
### Fixed
- **Build: Extension metadata validation** — Extension builds now fail clearly for invalid locale JSON, missing metadata, an incorrect extension title, or store titles and descriptions that exceed their character limits.
## [v2.6.3] — 2026-07-15
### Fixed
- **Extension: Host-access recovery races** — Prevents stale permission grants, reinjection retries, closed tabs, and rapid target changes from reactivating or clearing the wrong video tab.
- **Extension: Cross-browser host patterns** — Uses Firefox-compatible match patterns for local servers while preserving exact Chromium port scopes.
- **Extension: Force-sync target integrity** — Keeps sampled playback time and acknowledgements bound to the selected tab during recovery and target changes.
## [v2.6.2] — 2026-07-15
### Added
+85
View File
@@ -0,0 +1,85 @@
# Ephemeral end-to-end encrypted chat
KoalaSync chat is an optional, live-only text channel rendered on the selected
streaming tab. It is not a messenger and does not add chat UI to the extension
popup.
## Security boundary
- The relay receives ciphertext only and never receives the chat secret.
- The relay stores no messages in RAM or on disk and sends no backlog.
- A late joiner sees only messages sent after joining.
- Room authentication is unchanged. The room password remains separate from the
chat secret and continues to be sent to the relay during `join_room`.
- Message timing and ciphertext length remain visible to the relay. Same-room
replay is accepted by the threat model.
## Invite format and key lifecycle
New invitations use a named fragment format:
```text
#j2:r=<roomId>&p=<password>&k=<base64url-secret>[&u=<relayUrl>]
```
The room creator generates 16 random bytes and encodes them as 22 unpadded
base64url characters. URL fragments are not sent in HTTP requests. The website
parses the fragment and passes structured fields through `bridge.js` to the
extension. `chatKey` must never be included in a relay event.
Legacy `#join:` links remain supported and join normally without chat. Manual
room/password entry also joins without chat. The extension clears any prior chat
secret when joining without a key, switching rooms, or leaving.
Deployment order is website first, extension second. An old extension ignores the
additional structured `chatKey` field and continues joining normally.
## Cryptography
- Secret: 16 cryptographically random bytes.
- KDF: HKDF-SHA256 with `roomId` as salt and a fixed KoalaSync chat info label.
- Encryption: AES-256-GCM using WebCrypto.
- IV: fresh random 12-byte value for every message, prepended to the ciphertext.
- AAD: `${roomId}|${senderId}`.
- The derived `CryptoKey` is cached once per active room.
The relay stamps `id`, `senderId`, and `timestamp` on the ciphertext envelope.
Changing `senderId` causes AES-GCM authentication to fail because the receiver uses
the stamped value as AAD.
## Client policy
- Maximum plaintext length: 500 Unicode code points.
- Decrypted text is untrusted. Escape HTML before applying the supported limited
Markdown formatting.
- No read receipts and no typing indicators.
- The local message DOM is bounded; this is presentation state, not server history.
## Overlay behavior
- Default dock: right.
- Live modes: right, left, and detached.
- Detached mode is draggable and moderately resizable. Size and position are stored
per origin and clamped to the current viewport.
- The overlay uses a Shadow DOM and never changes host-page layout.
- On `fullscreenchange`, its host moves into `document.fullscreenElement` and remains
visible.
- It follows all eucalyptus, cyber, and graphite light/dark theme combinations.
- Without a key, the panel stays closed and a disabled chat control explains that a
current invite link is required.
- Chat display is a local option and defaults to off. Enabling or disabling it never
deletes the room chat secret, so it can be enabled later without creating a room.
- Without the relay `chat` capability, no chat control is shown.
## Mixed-version rollout
- New extensions announce `chat-v1` in `join_room.clientCapabilities`.
- Old non-chat extensions omit the optional field and receive no `chat_message`
events. Their playback and room protocol remains unchanged.
- The first chat beta omitted the capability. When it sends one valid v1 chat
frame, the relay treats that socket as chat-capable for the rest of the
connection.
- New extensions accept the first beta's unversioned server `chat` capability as
well as `chat-v1`, so a server-first or extension-first rollout degrades safely.
Peer removal and role management are outside chat scope.
+60 -1
View File
@@ -11,6 +11,19 @@ the event names defined in `shared/constants.js`.
object payload.
- The relay caps incoming Socket.IO message size at 4 KB.
## Invite fragments
Current invitations use named URL-fragment fields:
```text
#j2:r=<roomId>&p=<password>&k=<base64url-chat-secret>[&u=<relayUrl>]
```
The fragment is parsed by the website and forwarded to the extension as structured
fields. `k` is client-only and must never appear in a relay payload. Legacy
`#join:<roomId>:<password>[:1:<relayUrl>]` fragments remain valid but provide no chat
secret.
## Connection Handshake
The Socket.IO handshake must include:
@@ -41,6 +54,7 @@ Payload:
"password": "string, max 128, optional",
"tabTitle": "string, max 100, optional",
"mediaTitle": "string, max 100, optional",
"clientCapabilities": ["chat-v1"],
"protocolVersion": "string, max 16"
}
```
@@ -69,13 +83,56 @@ Payload:
"hostPeerId": "string or null",
"controlMode": "everyone | host-only",
"controllers": ["peerId"],
"capabilities": ["host-control", "co-host"]
"capabilities": ["host-control", "co-host", "chat", "chat-v1"]
}
```
`room_data` is sent to the joining socket. It is not the general broadcast used
for every later room update.
## Ephemeral encrypted chat
Relays advertise chat support with `"chat-v1"` in `room_data.capabilities` and keep
the initial beta's `"chat"` flag during the transition. New clients announce
`"chat-v1"` in optional `join_room.clientCapabilities` (at most the first 16 entries
are inspected; unknown or malformed values are ignored). Old clients omit the field
and continue using the pre-chat protocol unchanged.
The relay sends `chat_message` only to sockets that announced `"chat-v1"`. As a
transition for the first chat beta, a socket that sends a valid v1 ciphertext is
marked capable for the rest of that connection. This prevents old non-chat
extensions from receiving unknown events while preserving the first beta's send
path.
### `chat_message`
Client to relay:
```json
{ "ciphertext": "<unpadded-base64url>" }
```
`ciphertext` contains a 12-byte AES-GCM IV followed by ciphertext and the 16-byte
authentication tag. The relay validates only canonical base64url and byte bounds.
It cannot inspect plaintext.
Relay to every chat-capable current room peer, including the sender:
```json
{
"id": "<server-generated UUID>",
"senderId": "<server-stamped peer ID>",
"timestamp": 1710000000000,
"ciphertext": "<unpadded-base64url>"
}
```
Client-provided `id`, `senderId`, `timestamp`, or plaintext fields are discarded.
The relay keeps no message collection and `room_data` contains no chat history.
Messages are limited to 10 per socket per 10 seconds in addition to the global event
budget. There are no typing, read-receipt, history, or chat-specific peer-management
events.
## Room Leave
### `leave_room` (client -> server)
@@ -365,5 +422,7 @@ If sender and target are still in the same room, the relay emits:
- `host-control`
- `co-host`
- `chat`
- `chat-v1`
Clients should treat a missing or unknown capabilities list as unsupported.
+77 -11
View File
@@ -30,6 +30,83 @@
*Prioritized for upcoming phases.*
### v3.0 — Modern Room Chat
- **Release date:** Not scheduled
- **Priority:** P1
- **Category:** Social / Communication / Privacy
- **Status:** Planned. This is a roadmap target, not a release announcement.
- **Existing foundation:** Opt-in, live-only end-to-end encrypted room chat; a floating
chat bubble over the selected player opens a dockable, detachable, resizable overlay.
The relay stores no chat history and mixed extension versions use capability-gated
delivery.
#### Core experience
- Floating bubble with an accessible unread badge (`1`, `2`, `3`, …, `99+`), cleared
only when the chat is actually viewed.
- Subtle new-message pulse with reduced-motion support; no animation or sound while
muted or during Do Not Disturb.
- Drag-and-snap bubble positioning, remembered per site, with reset-to-default.
- Dock left/right, detached overlay, compact mode, fullscreen-safe placement, responsive
narrow-player layout, and keyboard-only operation.
- Per-room mute, mention-only mode, notification sound controls, browser notifications,
and a visible mute state.
- Unread state survives overlay close/reopen and tab focus changes, but room messages
remain memory-only unless a future storage option is explicitly enabled.
#### Messaging
- Replies with quoted context, emoji reactions, emoji picker, mentions, and typing
indicators.
- Edit and delete own messages with clear local tombstones; no silent mutation.
- Delivery states (`sending`, `sent`, `failed`, `retry`) with idempotent retries and
duplicate suppression after reconnects.
- Message timestamps, date separators, jump-to-latest, unread divider, search within
the current live session, copy, and selectable text.
- Link detection with safe external opening, explicit scheme allowlist, and no automatic
previews or remote-media loading.
- Optional image/GIF/file sharing only after encrypted size limits, malware-risk UX,
relay bandwidth limits, and privacy behavior are designed and tested.
#### Presence, safety, and control
- Online/reconnecting status, join/leave events, and room-member presence without
exposing IP addresses or cross-room identity.
- Local mute/block, host moderation controls, slow mode, spam limits, and configurable
maximum message length.
- Optional read receipts and activity indicators default off; never infer or expose
viewing behavior without explicit consent.
- Clear live-only/no-history explanation, encrypted-chat indicator, room-key fingerprint,
and key-rotation/rejoin UX.
#### Reliability and compatibility
- Versioned chat capabilities for every new wire feature; unknown fields/events remain
harmless to older extensions and old non-chat clients receive no chat traffic.
- Server-first and extension-first rollout tests, including current, first-chat-beta,
old non-chat, reconnect, duplicate-frame, and malformed-capability clients.
- Bounded queues, payloads, unread counters, typing events, reactions, and attachment
metadata; backpressure and rate limits on both client and relay.
- Real Chrome and Firefox extension E2E coverage for bubble, unread count, overlay,
fullscreen, reconnect, and mixed-version rooms before v3.0 can ship.
#### Accessibility and localization
- Screen-reader announcements that do not read every busy-room message by default.
- Complete focus management, logical tab order, Escape behavior, high contrast,
zoom/reflow, reduced motion, and touch target coverage.
- All chat UI, notification text, moderation states, and errors localized across every
supported locale before release.
#### Explicit non-goals for the first v3.0 release
- No server-readable plaintext.
- No mandatory accounts, public room directory, advertising, tracking, or presence
across rooms.
- No unencrypted server-side history. Any future encrypted history requires a separate
threat model, retention controls, export/delete behavior, and explicit opt-in.
### 2. Invite link with target URL for auto-redirect
- **Priority:** P2
@@ -49,17 +126,6 @@
*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.
### Cross-frame video detection and control
- **Priority:** P3
+346
View File
@@ -0,0 +1,346 @@
# E2E-verschlüsselter In-Page-Chat für KoalaSync
## Auftrag
Baue einen privacy-first, Ende-zu-Ende-verschlüsselten Text-Chat, der direkt auf der
Streaming-Seite neben dem Video sitzt. Neuer Branch von `main`, Vorschlag
`feature/chat-e2e`.
### Harte Anforderungen
1. **Der Server darf Nachrichten nie lesen können.** Schlüssel nur clientseitig, der
Relay sieht ausschließlich Ciphertext.
2. **Der Server speichert keine Nachrichten.** Reines Live-Relay, kein RAM-Backlog,
keine History. Wer zu spät kommt, verpasst den bisherigen Chat. Das ist gewollt.
3. **Chat läuft auf der Streaming-Seite**, nicht im Extension-Popup.
4. **Themes werden respektiert** (3 Paletten x light/dark).
5. **Keine Read Receipts.** Bewusst gestrichen.
6. **Volle Abwärtskompatibilität**: alte Extensions müssen weiter normal joinen können.
### Bedrohungsmodell (bestimmt alle Krypto-Entscheidungen)
Das hier ist eine Video-Sync-Extension mit optionalem, flüchtigem Chat, **keine
sicherheitskritische Messenger-App**. Ziel ist: ein bösartiger Betreiber eines
Custom-Relays soll nicht einfach mitlesen können. Ob etwas mit sehr viel Aufwand
theoretisch knackbar wäre, ist egal.
Daraus folgt, und das ist bindend:
- **Usability und Performance haben Vorrang** vor kryptografischer Maximalhärte.
- Raum-Passwörter sind kurz, zufällig generiert (z.B. `0XUK3C`) und ständig neu. Wer
eines knackt, kann trollen (`play`/`pause` senden). Nervig, nicht kritisch. Deshalb
nutzt der Server bewusst simples SHA256/HMAC statt bcrypt. **Diese Linie beibehalten,
nicht "verbessern".**
- Der globale `SERVER_SALT` ist bekannt und kein Issue. Räume sind flüchtig, es gibt
keine Datenbank.
### Explizite Nicht-Ziele
- Kein Chat-Tab im Popup. Nicht als Fallback, nicht übergangsweise.
- Keine serverseitige `chatHistory` in irgendeiner Form.
- **Die Auth nicht anfassen.** Das Klartext-Passwort an den Relay ist bewusst so und
wird nicht umgebaut (Begründung unten).
- `website/` Theme-Redesign auf `main` nicht anfassen.
---
## Krypto-Design (entschieden, nicht neu diskutieren)
### Woher der Schlüssel kommt
Das Raum-Passwort ist **unbrauchbar** als Schlüsselmaterial: der Client sendet es im
Klartext an den Relay (`join_room` -> `payload.password`, `server/index.js:349`), der
Server HMACt es erst selbst (`hashPassword`, Zeile 52-55). Der Server könnte jeden
daraus abgeleiteten Schlüssel mitberechnen.
Es zu ändern würde auch nichts bringen: Raum-Passwörter haben ~31 Bit Entropie
(6 Zeichen A-Z0-9). Ein bösartiger Server könnte sie offline durchprobieren und den
Chat-Schlüssel ableiten.
**Lösung: ein eigenes, zufälliges Secret im URL-Fragment.** Fragmente werden nie an
einen Server gesendet, und das Secret hat volle Entropie per Konstruktion. Der
Einladungslink transportiert bereits heute das Passwort im Fragment, der Mechanismus
existiert also schon.
### Primitive
- **16 zufällige Bytes** (128 Bit), base64url, 22 Zeichen. Beispiel:
`R5Ti1nxp0crfAFHf3gVncw`
- Ableitung: **HKDF-SHA256**(secret, salt=roomId) -> AES-256-GCM-Key.
**Kein PBKDF2, kein Stretching.** Slow KDFs existieren nur, um schwache
Menschen-Passwörter zu strecken. Das Secret ist zufällig mit voller Entropie, HKDF
ist ein einziger HMAC (Mikrosekunden). Das ist dieselbe Logik wie sha256-statt-bcrypt
beim Server.
- **Key genau einmal pro Raum ableiten und den `CryptoKey` cachen.** Nicht pro
Nachricht neu ableiten oder importieren.
- Pro Nachricht: **zufälliger 12-Byte-IV**, dem Ciphertext vorangestellt.
- Alles über **WebCrypto**, keine Fremdabhängigkeit.
### AAD: senderId gegen Umetikettierung binden (Pflicht)
`senderId` wird vom Server gestempelt und liegt damit **außerhalb** des Ciphertexts.
Ohne Gegenmaßnahme kann ein bösartiger Relay Alices Ciphertext als Bob weiterreichen.
Das ist exakt der Angreifer, gegen den dieses Feature gebaut wird.
Deshalb: **AES-GCM mit AAD** verschlüsseln.
```
AAD = `${roomId}|${senderId}` // senderId = eigene peerId beim Verschlüsseln,
// envelope.senderId beim Entschlüsseln
```
Etikettiert der Server um, schlägt die Auth-Tag-Prüfung fehl und die Nachricht wird
verworfen. Kostet null Performance und bindet die Nachricht zusätzlich an den Raum
(kein Cross-Room-Replay).
Bewusst **nicht** abgedeckt und akzeptiert: Nachrichtenlängen und Timing bleiben für den
Server sichtbar (kein Padding). Ein Relay kann eine Nachricht innerhalb desselben Raums
erneut abspielen. Für das Bedrohungsmodell irrelevant.
### Sanitization wandert auf den Client (Pflicht)
Der Server sieht nur noch Ciphertext und **kann Text nicht mehr prüfen**. Die Bedrohung
verschwindet dadurch nicht, sie verschiebt sich: entschlüsselter Text stammt von einem
**Peer, der den Key besitzt**.
- Entschlüsselter Text ist **untrusted input**. Vor dem Rendern zwingend durch
`escapeChatHtml`/`formatChatText` (escapen, dann Markdown). Niemals rohes `innerHTML`.
- Die Längengrenze (500 Codepoints) muss der **Client** durchsetzen, vor dem
Verschlüsseln. Der Server kann nur noch Bytes zählen.
- **Bestehende Schranke beachten:** `maxHttpBufferSize: 4096` (`server/index.js:142`)
gilt global pro Socket-Nachricht. Nachgerechnet: 500 Codepoints als 4-Byte-Emoji
ergeben 2000 B Klartext, +16 B GCM-Tag +12 B IV = 2028 B, base64 = 2704 Zeichen,
socket.io-Frame = **2731 B**. Passt, Headroom 1365 B. Bei 700 Codepoints wären es
3799 B. Wer die Zeichengrenze anhebt, muss diese Rechnung neu machen, sonst reißt das
Limit.
---
## Link-Format und Abwärtskompatibilität (entschieden)
### Warum das alte Format nicht erweiterbar ist
Aktuell (`popup.js:559-563`):
```
offiziell: #join:<roomId>:<password>
custom: #join:<roomId>:<password>:1:<encodedUrl>
```
Der Extension-Parser (`popup.js:1294-1313`) nimmt `roomId` von vorne, das Paar
`(flag, url)` von hinten, und **alles dazwischen ist das Passwort** (`parts.join(':')`).
Es gibt keine Position, die er verwirft. Verifiziert durch Ausführen der echten Parser
gegen echte Links:
| Link | Alte Extension | Alte Website |
|---|---|---|
| `#join:SILENT-EAGLE-90:30PXPD:1:wss%3A%2F%2F…` (Kontrolle) | `password:"30PXPD"` OK | `serverFlag:"1"` OK |
| Key inline: `…:30PXPD:<KEY>:1:wss%3A%2F%2F…` | `password:"30PXPD:R5Ti1nxp…"` **korrumpiert** | `serverFlag:"R5Ti…"`, `serverUrl:"1"` -> **falscher Server** |
Zwei verschiedene, irreführende Fehler aus derselben Zeile. Deshalb: neues Präfix.
### Neues Format
```
#j2:r=<roomId>&p=<password>&k=<key>[&u=<encodedRelayUrl>]
```
- `URLSearchParams`, kein positionales Parsen. Behebt nebenbei einen bestehenden Bug:
heute zerschießt ein Doppelpunkt im Passwort den Website-Parser (`parts[2]`), während
der Extension-Parser (`parts.join(':')`) damit klarkommt.
- **`s=1` entfällt.** Es existierte nur wegen des positionalen Parsens. Mit benannten
Parametern gilt: `u` vorhanden bedeutet Custom Relay.
- Das Präfix darf `#join:` **nicht als Teilstring enthalten**, sonst greift
`includes('#join:')` in alten Extensions doch.
Beispiele:
```
offiziell: …/join.html#j2:r=SAPPHIRE-DUCK-49&p=0XUK3C&k=R5Ti1nxp0crfAFHf3gVncw
custom: …/join.html#j2:r=SILENT-EAGLE-90&p=30PXPD&k=R5Ti1nxp0crfAFHf3gVncw
&u=wss%3A%2F%2Fsync.shik3i.net
```
### Warum das alte Extensions nicht bricht
**Die Website ist der Hauptpfad, nicht die Extension.** `website/app.js:452-458` parst
das Fragment selbst, prüft via `document.documentElement.dataset.koalasyncInstalled`
(gesetzt von `bridge.js:9`), ob die Extension da ist, und dispatcht dann automatisch
(`app.js:515-526`, "AUTO-TRIGGER JOIN") ein `KOALASYNC_JOIN_REQUEST` mit fertig
geparsten, **strukturierten Feldern**. `bridge.js` reicht es als `WEB_JOIN_REQUEST` an
den background weiter. In diesem Pfad liest die Extension die URL nie an.
Altes `bridge.js` destrukturiert nur, was es kennt:
```js
const { roomId, password, useCustomServer, serverUrl } = e.detail;
```
Ein zusätzliches `chatKey` fällt dort stillschweigend auf den Boden.
**Daraus folgt die Deploy-Reihenfolge: Website zuerst.** Sie ist die einzige Stelle, die
das neue Format kennen muss, und sie ist sofort deploybar. Die Extension darf beliebig
hinterherhinken (Store-Review, Update-Zyklen der Nutzer).
| Kombination | Ergebnis |
|---|---|
| Neue Website + neue Ext | Join + Chat |
| Neue Website + alte Ext | Join normal, kein Chat, `chatKey` ignoriert |
| Neue Ext + alter `#join:`-Link | Join, kein Key, Chat aus. Legacy-Parser bleibt erhalten |
| Alte Website + neuer Link | Präfix unbekannt, Join-Seite tot. **Existiert nach dem Website-Deploy nicht mehr** |
`checkInviteLink()` (`popup.js:1289`) ist nur der Komfort-Pfad "Popup auf der Join-Seite
öffnen und Felder vorausfüllen". Bei `#j2:` greift er in alten Extensions nicht mehr,
das Feld bleibt leer statt falsch befüllt. Die neue Extension muss dort **beide**
Formate parsen (`#j2:` und Legacy `#join:`).
**`history.replaceState` auf das Legacy-Format: nicht tun.** Naheliegende Idee, um das
Autofill alter Extensions zu retten, aber ein Eigentor: es entfernt `k` aus der
Adressleiste. Nutzer kopieren die URL aus der Adressleiste, um sie weiterzuteilen. Der
weitergegebene Link joint dann zwar, hat aber stillschweigend keinen Chat mehr. Der
Fragment-Inhalt muss unangetastet bleiben.
### Lebenszyklus des Keys
- **Erzeugt wird er vom Raum-Ersteller**, einmal, beim Anlegen des Raums. Er lebt im
background neben `roomId`/`password` und geht in den Invite-Link.
- **Er darf niemals in einem Relay-Payload landen.** Nicht in `join_room`, nirgends.
Dafür einen Test schreiben, der alle ausgehenden Events gegen den Key prüft.
- **Krypto gehört in den background**, nicht in den content script. Der Key wird damit
gar nicht erst in den Kontext einer fremden Seite ausgeliefert. Das Overlay schickt
Klartext an den background und bekommt Klartext zurück, verschlüsselt wird
ausschließlich dort.
- **Kanten, die korrekt fallen müssen:**
- Raum-Ersteller hat eine **alte** Extension: es existiert kein Key, niemand chattet.
Korrektes Verhalten, kein Fehlerfall.
- Ein Peer mit **alter** Extension teilt den Invite aus seinem eigenen Popup: der Link
trägt keinen Key. Wer darüber joint, chattet nicht, während die anderen chatten.
- Jemand tippt Raum und Passwort **manuell**: kein Key, kein Chat.
- Relay **ohne** Chat-Capability: Chat-UI gar nicht erst anzeigen.
---
## Vorgeschichte
Vorgängerbranch `feature/soonTMChat` (Stand `c706513`) ist **verworfen**, bleibt als
Referenz liegen. Gründe:
- Chat lag nur im Popup. Ein Chrome-Popup schließt beim Fokusverlust, man kann nicht
gleichzeitig Video schauen und chatten. Strukturell unbrauchbar.
- Der Server speicherte bis zu 500 Klartext-Nachrichten pro Raum und schickte jedem
Joiner das Backlog.
- Verifizierter Folgebug: wer einen Raum mit >120 Nachrichten Historie betrat, wurde
**rausgeworfen**. `renderChatHistory()` feuerte eine ungebündelte `chat_read`-Quittung
pro Nachricht und riss `CHAT_READ_RATE_LIMIT` (120/10s). Reproduziert bei
`CHAT_HISTORY_LIMIT=200`, dokumentiert erlaubt sind 500.
**Lehre: kein ungebündeltes Event-pro-Nachricht-Muster. Jedes Client-Verhalten gegen die
Rate-Limits gegenrechnen, bevor es eingebaut wird.**
### Wiederverwendbar (per `git checkout feature/soonTMChat -- <pfad>`, kritisch prüfen)
| Datei | Was | Einschränkung |
|---|---|---|
| `extension/chat.js` | `escapeChatHtml`, `formatChatText`, `insertEmoji`, `createTypingTracker`, `createRemoteTypingTracker` | reine Funktionen, storage-frei, getestet. `createReceiptTracker` weglassen |
| `extension/chat.test.mjs` | Unit-Tests | Receipt-Tests weg |
| `extension/locales/*.json` | `CHAT_*`-Keys, 15 Sprachen | Receipt-Keys weg, neue Keys für Overlay nötig |
| `server/chat.js` | `sanitizeChatUsername`, `canKickPeer` | `sanitizeChatText`/`createChatMessage` greifen auf Klartext zu, bei E2E unmöglich. `parseChatHistoryLimit`, `appendChatHistory`, `canRelayReadReceipt` entfallen |
| `server/rate-limiter.js` | `checkChatMessageRate` (10/10s) | `checkChatReadRate` entfällt |
| `shared/constants.js` | `CAPABILITIES.CHAT`, Event-Namen | **muss neu hinzu.** main kennt nur `HOST_CONTROL` und `CO_HOST` (`shared/constants.js:78`, `server/index.js:174`) |
**Server-Design, das überleben soll:** Der Server vergab Message-ID, `senderId` und
`timestamp` selbst und ignorierte Client-Angaben (Spoofing-Schutz). Bleibt richtig, auch
wenn der Text jetzt Ciphertext ist.
---
## Technische Randbedingungen
### Overlay-Kontext
- `content.js` wird **programmatisch** injiziert:
`chrome.scripting.executeScript({ target: {tabId}, files: ['content.js'] })`
(`background.js:1775`). Nicht über `manifest.content_scripts`, dort steht nur
`bridge.js` für `https://sync.koalastuff.net/*`.
- `host_permissions: ["<all_urls>"]`, MV3.
- Das Overlay lebt im DOM fremder Seiten (Netflix, YouTube, Emby, Jellyfin).
**Shadow DOM ist Pflicht**, sonst bluten fremde Styles rein und umgekehrt.
- Die Theme-Variablen aus `popup.html` existieren im Page-Kontext **nicht** und müssen
in den Shadow Root injiziert werden.
- **Risiko, vorab prüfen:** Netflix-Vollbild ist ein eigener Fullscreen-Element-Kontext.
Ein Overlay im normalen DOM verschwindet dort möglicherweise. Das kann die
Positionierung grundlegend beeinflussen.
### Performance auf der Host-Seite
Das Video ist das Produkt, der Chat ist Beiwerk. Ein Overlay, das die Wiedergabe ruckeln
lässt, ist schlechter als kein Overlay.
- Nur im **Ziel-Tab** injizieren (`currentTabId` im background), nicht auf jeder Seite.
- `position: fixed`, eigener Compositing-Layer, **kein Eingriff in das Layout der
Host-Seite**. Kein Layout-Thrashing, keine erzwungenen Reflows während der Wiedergabe.
- Nachrichtenliste beim Rendern nicht unbegrenzt wachsen lassen (DOM-Knoten deckeln).
### Firefox
`scripts/build-extension.cjs:199` baut ein eigenes **Firefox-Target**, und `bridge.js`
enthält bereits Firefox-spezifisches `cloneInto`-Handling für CustomEvent-Details
(isolierte Welten in FF MV3). Overlay, Shadow DOM und WebCrypto müssen dort ebenfalls
laufen. Neue Website-zu-Extension-Felder (`chatKey`) gehen durch dieselbe
`cloneInto`-Stelle.
### Theme-System (aus `main`, verbindlich)
- `extension/theme-init.js` setzt auf `<html>`: `data-theme` (`light`/`dark`),
`data-palette` (`eucalyptus`/`cyber`/`graphite`), Klasse `theme-light`.
- Quelle: `chrome.storage.local`, Keys `themeMode` und `themePalette`, plus
`chrome.storage.onChanged` für Live-Updates. Das Overlay nutzt denselben Mechanismus
im Shadow Root.
- **Kein `prefers-color-scheme`** für Theme-Farben. Das System ist explizit, nur der
Modus `system` liest die OS-Präferenz und das erledigt `theme-init.js`.
- Tokens: `--bg`, `--card`, `--surface-alt`, `--surface-deep`, `--accent`, `--text`,
`--text-muted`, `--border-soft`, `--border-strong`, `--text-on-green`.
Text auf `--accent` immer `--text-on-green`, nie `white`.
- Getönte `oklch`-Schatten sind pro Palette hartcodiert und brauchen
`html[data-palette=...]`-Overrides. Neutrale Schwarz-Alpha-Schatten sind
palettenunabhängig und der einfachere Weg.
- Bekannte Alt-Last, nicht Aufgabe dieses Branches: `--text-on-green` auf `--accent`
erreicht in eucalyptus/light 3.76 und cyber/dark 4.24, unter WCAG AA.
### Qualitäts-Gates
- `npm run lint`, `npx vitest run`, `npm run verify` müssen grün sein.
- i18n: alle 15 Sprachen in `extension/locales/`, `en` ist Baseline-Fallback. Keine
hartcodierten UI-Strings, `getMessage(key, { placeholder })` aus `i18n.js`.
- Bei einem Release-Tag: Eintrag in `docs/CHANGELOG.md`.
- Keine Em-Dashes in nutzersichtbaren Texten.
---
## Offene Punkte (mit dem Nutzer klären, nicht selbst entscheiden)
1. **Overlay-Positionierung**: fest andockbar rechts/links, oder frei verschiebbar?
Position pro Seite in `chrome.storage.local` merken? Verhalten im Vollbild?
2. **Peers ohne Key**: Chat-UI ganz ausblenden mit Hinweis, oder anzeigen und
Nachrichten als "nicht lesbar" markieren? (Die Fälle, in denen das eintritt, stehen
oben unter Lebenszyklus.)
3. **Metadaten**: Username bleibt heute für den Server sichtbar (er sanitized ihn).
Mitverschlüsseln? Typing-Indikatoren verraten dem Server, wer wann aktiv ist.
Behalten, verschlüsseln oder streichen?
4. **Kick-Funktion**: `canKickPeer` existiert (Host/Controller-Rechte). Behalten?
5. **Nachrichtenlängen-Limit**: 500 Codepoints beibehalten, oder angesichts des
4096-B-Frames anders wählen?
---
## Vorgehen
1. Offene Punkte klären.
2. Krypto- und Linkformat-Design in `docs/CHAT.md` festhalten. Das bestehende Dokument
beschreibt das verworfene Konzept und wird neu geschrieben. Chat-Events in
`docs/PROTOCOL.md` ergänzen.
3. Website-Parser (`#j2:`, Legacy `#join:` weiter unterstützen) und Bridge-Feld
`chatKey` zuerst, weil davon die Abwärtskompatibilität hängt.
4. Dann Relay (reines Relay ohne Storage), dann Krypto im background, dann Overlay, dann
i18n, dann Tests.
5. **Verifizieren, nicht annehmen:**
- Overlay auf mindestens zwei echten Seiten in allen 6 Theme-Kombinationen, nicht nur
in einer isolierten HTML-Datei.
- Chrome **und** Firefox-Build.
- Ein Test, der beweist, dass der Key in keinem ausgehenden Relay-Payload vorkommt.
- Ein Test, der eine umetikettierte Nachricht (fremde `senderId`) als ungültig
zurückweist (AAD-Bindung).
- Alte Extension gegen neue Website: Join funktioniert weiter, Chat ist stumm.
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - KoalaSync"
},
"appDesc": {
"message": "Synchronisiere die Videowiedergabe auf YouTube, Netflix, Emby, Jellyfin und jeder HTML5-Seite in Echtzeit mit Freunden."
"message": "Erstelle private Watch Partys und synchronisiere Videos mit Freunden auf YouTube, Netflix, Jellyfin, Emby und fast jeder Website."
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - KoalaSync"
},
"appDesc": {
"message": "Synchronize video playback on YouTube, Netflix, Emby, Jellyfin, and any HTML5 site in real-time with friends."
"message": "Create private watch parties and sync videos with friends on YouTube, Netflix, Jellyfin, Emby, and almost any HTML5 website."
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - 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."
"message": "Crea fiestas privadas y sincroniza vídeos con amigos en YouTube, Netflix, Jellyfin, Emby y casi cualquier sitio HTML5."
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - KoalaSync"
},
"appDesc": {
"message": "Synchronisez la lecture vidéo sur YouTube, Netflix, Emby, Jellyfin et tout site HTML5 en temps réel avec vos amis."
"message": "Créez des watch parties privées et regardez en synchro entre amis sur YouTube, Netflix, Jellyfin, Emby et presque tout site HTML5."
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - KoalaSync"
},
"appDesc": {
"message": "Sincronizza la riproduzione video su YouTube, Netflix, Emby, Jellyfin e qualsiasi sito HTML5 in tempo reale con gli amici."
"message": "Crea watch party private e sincronizza i video con gli amici su YouTube, Netflix, Jellyfin, Emby e quasi ogni sito HTML5."
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - KoalaSync"
},
"appDesc": {
"message": "YouTube、Netflix、Emby、Jellyfin、その他HTML5サイトでの動画再生を友達とリアルタイムで同期します。"
"message": "プライベートなウォッチパーティーを作成し、YouTube、Netflix、Jellyfin、EmbyやほぼすべてのHTML5サイトで友達と動画を同期再生できます。"
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - KoalaSync"
},
"appDesc": {
"message": "YouTube, Netflix, Emby, Jellyfin 모든 HTML5 사이트에서 친구들과 실시간으로 비디오 재생을 동기화하세요."
"message": "비공개 Watch Party를 만들고 YouTube, Netflix, Jellyfin, Emby 및 거의 모든 HTML5 사이트에서 친구들과 영상을 동기화하세요."
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - KoalaSync"
},
"appDesc": {
"message": "Synchroniseer video-afspelen op YouTube, Netflix, Emby, Jellyfin en elke HTML5-site in realtime met vrienden."
"message": "Maak privéwatchparty's en kijk synchroon met vrienden op YouTube, Netflix, Jellyfin, Emby en vrijwel elke HTML5-website."
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - KoalaSync"
},
"appDesc": {
"message": "Synchronizuj odtwarzanie wideo na YouTube, Netflix, Emby, Jellyfin i dowolnej stronie HTML5 w czasie rzeczywistym ze znajomymi."
"message": "Twórz prywatne wspólne seanse i synchronizuj filmy ze znajomymi na YouTube, Netflix, Jellyfin, Emby i niemal każdej stronie HTML5."
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - 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."
"message": "Crie watch parties privadas e sincronize vídeos com amigos no YouTube, Netflix, Jellyfin, Emby e em quase qualquer site HTML5."
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - 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."
"message": "Crie sessões privadas e veja vídeos sincronizados com amigos no YouTube, Netflix, Jellyfin, Emby e em quase qualquer site HTML5."
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - KoalaSync"
},
"appDesc": {
"message": "Синхронизируйте воспроизведение видео на YouTube, Netflix, Emby, Jellyfin и любых HTML5-сайтах в реальном времени с друзьями."
"message": "Создавайте закрытые Watch Party и смотрите синхронно с друзьями на YouTube, Netflix, Jellyfin, Emby и почти любом сайте HTML5."
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - KoalaSync"
},
"appDesc": {
"message": "YouTube, Netflix, Emby, Jellyfin ve herhangi bir HTML5 sitesinde video oynatmayı arkadaşlarınızla gerçek zamanlı olarak senkronize edin."
"message": "Özel izleme partileri kurun; YouTube, Netflix, Jellyfin, Emby ve çoğu HTML5 sitesinde arkadaşlarınızla senkron izleyin."
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - KoalaSync"
},
"appDesc": {
"message": "Синхронізуйте відтворення відео на YouTube, Netflix, Emby, Jellyfin і будь-якому сайті HTML5 у режимі реального часу з друзями."
"message": "Створюйте закриті Watch Party й дивіться синхронно з друзями на YouTube, Netflix, Jellyfin, Emby та майже будь-якому сайті HTML5."
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"appName": {
"message": "KoalaSync"
"message": "Watch Party - KoalaSync"
},
"appDesc": {
"message": "与朋友实时同步YouTube、Netflix、Emby、Jellyfin任何HTML5网站上的视频播放。"
"message": "创建私人观影派对,与好友在 YouTube、Netflix、Jellyfin、Emby 及几乎任何 HTML5 视频网站同步观看。"
}
}
+814 -188
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -11,11 +11,12 @@ 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;
const { roomId, password, chatKey, useCustomServer, serverUrl } = e.detail;
chrome.runtime.sendMessage({
type: 'WEB_JOIN_REQUEST',
roomId,
password,
chatKey,
useCustomServer,
serverUrl
}).catch(() => {});
+122
View File
@@ -0,0 +1,122 @@
const CHAT_INFO = 'KoalaSync E2E Chat v1';
const SECRET_BYTES = 16;
const IV_BYTES = 12;
const MIN_ENCRYPTED_BYTES = 29;
const MAX_ENCRYPTED_BYTES = 2028;
let cachedKeyPromise = null;
let cachedRoomId = '';
let cachedSecret = '';
let cacheGeneration = 0;
function bytesToBase64Url(bytes) {
let binary = '';
for (const byte of bytes) binary += String.fromCharCode(byte);
return globalThis.btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
function base64UrlToBytes(value) {
if (typeof value !== 'string' || !/^[A-Za-z0-9_-]+$/.test(value) || value.length % 4 === 1) return null;
const padded = value.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - value.length % 4) % 4);
try {
const binary = globalThis.atob(padded);
const bytes = Uint8Array.from(binary, char => char.charCodeAt(0));
return bytesToBase64Url(bytes) === value ? bytes : null;
} catch (_) {
return null;
}
}
export function generateChatSecret(cryptoImpl = globalThis.crypto) {
const bytes = new Uint8Array(SECRET_BYTES);
cryptoImpl.getRandomValues(bytes);
return bytesToBase64Url(bytes);
}
export function validateChatSecret(value) {
const bytes = base64UrlToBytes(value);
return bytes?.length === SECRET_BYTES ? value : '';
}
export function countCodePoints(value) {
return [...String(value ?? '')].length;
}
export function normalizeOutgoingChatText(value) {
if (typeof value !== 'string') throw new TypeError('Chat message must be text');
const text = value.trim();
if (!text) throw new TypeError('Chat message must not be empty');
if (countCodePoints(text) > 500) throw new RangeError('Chat message exceeds 500 Unicode code points');
return text;
}
export function clearChatKeyCache() {
cacheGeneration++;
cachedKeyPromise = null;
cachedRoomId = '';
cachedSecret = '';
}
export async function deriveChatKey(roomId, secret, cryptoImpl = globalThis.crypto) {
const validSecret = validateChatSecret(secret);
if (!roomId || !validSecret) throw new TypeError('Valid roomId and chat secret are required');
if (cachedKeyPromise && cachedRoomId === roomId && cachedSecret === validSecret) return cachedKeyPromise;
const encoder = new globalThis.TextEncoder();
const generation = cacheGeneration;
const keyPromise = (async () => {
const material = await cryptoImpl.subtle.importKey(
'raw', base64UrlToBytes(validSecret), { name: 'HKDF' }, false, ['deriveKey']
);
return cryptoImpl.subtle.deriveKey({
name: 'HKDF',
hash: 'SHA-256',
salt: encoder.encode(roomId),
info: encoder.encode(CHAT_INFO)
}, material, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
})();
cachedKeyPromise = keyPromise;
cachedRoomId = roomId;
cachedSecret = validSecret;
try {
return await keyPromise;
} catch (error) {
if (generation === cacheGeneration && cachedKeyPromise === keyPromise) clearChatKeyCache();
throw error;
}
}
export async function encryptChatMessage({ text, roomId, senderId, secret }, cryptoImpl = globalThis.crypto) {
const plaintext = normalizeOutgoingChatText(text);
if (!senderId) throw new TypeError('senderId is required');
const key = await deriveChatKey(roomId, secret, cryptoImpl);
const encoder = new globalThis.TextEncoder();
const iv = new Uint8Array(IV_BYTES);
cryptoImpl.getRandomValues(iv);
const encrypted = new Uint8Array(await cryptoImpl.subtle.encrypt({
name: 'AES-GCM',
iv,
additionalData: encoder.encode(`${roomId}|${senderId}`)
}, key, encoder.encode(plaintext)));
const envelope = new Uint8Array(iv.length + encrypted.length);
envelope.set(iv);
envelope.set(encrypted, iv.length);
return bytesToBase64Url(envelope);
}
export async function decryptChatMessage({ ciphertext, roomId, senderId, secret }, cryptoImpl = globalThis.crypto) {
if (!senderId) throw new TypeError('senderId is required');
const bytes = base64UrlToBytes(ciphertext);
if (!bytes || bytes.length < MIN_ENCRYPTED_BYTES || bytes.length > MAX_ENCRYPTED_BYTES) {
throw new TypeError('Invalid chat ciphertext');
}
const key = await deriveChatKey(roomId, secret, cryptoImpl);
const encoder = new globalThis.TextEncoder();
const plaintext = await cryptoImpl.subtle.decrypt({
name: 'AES-GCM',
iv: bytes.slice(0, IV_BYTES),
additionalData: encoder.encode(`${roomId}|${senderId}`)
}, key, bytes.slice(IV_BYTES));
const text = new globalThis.TextDecoder('utf-8', { fatal: true }).decode(plaintext);
return normalizeOutgoingChatText(text);
}
+114
View File
@@ -0,0 +1,114 @@
import { webcrypto } from 'node:crypto';
import { Buffer } from 'node:buffer';
import { afterEach, describe, expect, it } from 'vitest';
import {
clearChatKeyCache,
countCodePoints,
decryptChatMessage,
deriveChatKey,
encryptChatMessage,
generateChatSecret,
normalizeOutgoingChatText,
validateChatSecret
} from './chat-crypto.js';
afterEach(clearChatKeyCache);
describe('chat crypto', () => {
it('generates canonical 128-bit secrets', () => {
const secret = generateChatSecret(webcrypto);
expect(secret).toMatch(/^[A-Za-z0-9_-]{22}$/);
expect(validateChatSecret(secret)).toBe(secret);
expect(validateChatSecret(`${secret}=`)).toBe('');
});
it('round-trips Unicode with a cached room key and fresh IVs', async () => {
const secret = generateChatSecret(webcrypto);
const firstKey = await deriveChatKey('ROOM-1', secret, webcrypto);
const secondKey = await deriveChatKey('ROOM-1', secret, webcrypto);
expect(secondKey).toBe(firstKey);
const input = { text: 'Hello 🐨 **world**', roomId: 'ROOM-1', senderId: 'alice', secret };
const first = await encryptChatMessage(input, webcrypto);
const second = await encryptChatMessage(input, webcrypto);
expect(second).not.toBe(first);
await expect(decryptChatMessage({ ciphertext: first, ...input }, webcrypto)).resolves.toBe(input.text);
});
it('deduplicates in-flight key derivations without restoring a cleared cache', async () => {
const secret = generateChatSecret(webcrypto);
let deriveCalls = 0;
let releaseDerive;
const delayedCrypto = {
...webcrypto,
subtle: {
importKey: (...args) => webcrypto.subtle.importKey(...args),
deriveKey: async (...args) => {
deriveCalls++;
await new Promise(resolve => { releaseDerive = resolve; });
return webcrypto.subtle.deriveKey(...args);
}
}
};
const first = deriveChatKey('ROOM-1', secret, delayedCrypto);
const second = deriveChatKey('ROOM-1', secret, delayedCrypto);
await Promise.resolve();
expect(deriveCalls).toBe(1);
clearChatKeyCache();
releaseDerive();
await Promise.all([first, second]);
const fresh = deriveChatKey('ROOM-1', secret, webcrypto);
expect(fresh).not.toBe(first);
await fresh;
});
it('rejects relabeling, cross-room replay, and the wrong secret', async () => {
const secret = generateChatSecret(webcrypto);
const ciphertext = await encryptChatMessage({ text: 'secret', roomId: 'ROOM-1', senderId: 'alice', secret }, webcrypto);
clearChatKeyCache();
await expect(decryptChatMessage({ ciphertext, roomId: 'ROOM-1', senderId: 'bob', secret }, webcrypto)).rejects.toThrow();
clearChatKeyCache();
await expect(decryptChatMessage({ ciphertext, roomId: 'ROOM-2', senderId: 'alice', secret }, webcrypto)).rejects.toThrow();
clearChatKeyCache();
await expect(decryptChatMessage({ ciphertext, roomId: 'ROOM-1', senderId: 'alice', secret: generateChatSecret(webcrypto) }, webcrypto)).rejects.toThrow();
});
it('enforces 500 Unicode code points before encryption', () => {
expect(countCodePoints('😀'.repeat(500))).toBe(500);
expect(normalizeOutgoingChatText(` ${'😀'.repeat(500)} `)).toBe('😀'.repeat(500));
expect(() => normalizeOutgoingChatText('😀'.repeat(501))).toThrow(RangeError);
expect(() => normalizeOutgoingChatText(' ')).toThrow(TypeError);
});
it('keeps the worst-case 500-codepoint payload within the relay byte bound', async () => {
const secret = generateChatSecret(webcrypto);
const ciphertext = await encryptChatMessage({
text: '😀'.repeat(500), roomId: 'ROOM-1', senderId: 'alice', secret
}, webcrypto);
const bytes = Buffer.from(ciphertext, 'base64url');
expect(bytes).toHaveLength(2028);
});
it('rejects non-canonical plaintext and malformed UTF-8 after authentication', async () => {
const secret = generateChatSecret(webcrypto);
const roomId = 'ROOM-1';
const senderId = 'alice';
const key = await deriveChatKey(roomId, secret, webcrypto);
const encoder = new globalThis.TextEncoder();
async function encryptRaw(plaintext) {
const iv = webcrypto.getRandomValues(new Uint8Array(12));
const encrypted = new Uint8Array(await webcrypto.subtle.encrypt({
name: 'AES-GCM',
iv,
additionalData: encoder.encode(`${roomId}|${senderId}`)
}, key, plaintext));
return Buffer.concat([Buffer.from(iv), Buffer.from(encrypted)]).toString('base64url');
}
const whitespace = await encryptRaw(encoder.encode(' '));
await expect(decryptChatMessage({ ciphertext: whitespace, roomId, senderId, secret }, webcrypto)).rejects.toThrow(TypeError);
const malformed = await encryptRaw(Uint8Array.of(0xff));
await expect(decryptChatMessage({ ciphertext: malformed, roomId, senderId, secret }, webcrypto)).rejects.toThrow();
});
});
+33
View File
@@ -0,0 +1,33 @@
(function exposeChatFormat(root) {
function escapeChatHtml(value) {
return String(value ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
function formatChatText(value) {
return escapeChatHtml(value)
.replace(/\*\*([^*\n]+)\*\*/g, '<strong>$1</strong>')
.replace(/\*([^*\n]+)\*/g, '<em>$1</em>');
}
function tokenizeChatText(value) {
const text = String(value ?? '');
const tokens = [];
const pattern = /\*\*([^*\n]+)\*\*|\*([^*\n]+)\*/g;
let cursor = 0;
let match;
while ((match = pattern.exec(text)) !== null) {
if (match.index > cursor) tokens.push({ type: 'text', text: text.slice(cursor, match.index) });
tokens.push({ type: match[1] !== undefined ? 'strong' : 'em', text: match[1] ?? match[2] });
cursor = pattern.lastIndex;
}
if (cursor < text.length) tokens.push({ type: 'text', text: text.slice(cursor) });
return tokens;
}
root.KoalaSyncChatFormat = Object.freeze({ escapeChatHtml, formatChatText, tokenizeChatText });
})(globalThis);
+22
View File
@@ -0,0 +1,22 @@
import { beforeAll, describe, expect, it } from 'vitest';
beforeAll(async () => {
await import('./chat-format.js');
});
describe('chat formatting', () => {
it('escapes executable HTML before applying limited Markdown', () => {
const result = globalThis.KoalaSyncChatFormat.formatChatText('<img src=x onerror=alert(1)> **bold** *italic*');
expect(result).toBe('&lt;img src=x onerror=alert(1)&gt; <strong>bold</strong> <em>italic</em>');
expect(result).not.toContain('<img');
});
it('tokenizes only text, strong, and emphasis nodes', () => {
expect(globalThis.KoalaSyncChatFormat.tokenizeChatText('<script>x</script> **bold** *italic*')).toEqual([
{ type: 'text', text: '<script>x</script> ' },
{ type: 'strong', text: 'bold' },
{ type: 'text', text: ' ' },
{ type: 'em', text: 'italic' }
]);
});
});
+152
View File
@@ -0,0 +1,152 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
const extensionDir = path.dirname(fileURLToPath(import.meta.url));
const overlaySource = fs.readFileSync(path.join(extensionDir, 'chat-overlay.js'), 'utf8');
const backgroundSource = fs.readFileSync(path.join(extensionDir, 'background.js'), 'utf8');
const popupSource = fs.readFileSync(path.join(extensionDir, 'popup.js'), 'utf8');
const localeDir = path.join(extensionDir, 'locales');
const chatKeys = [
'LABEL_CHAT_ENABLED',
'LABEL_CHAT_ENABLED_TOOLTIP',
'CHAT_TITLE',
'CHAT_LIVE_ONLY',
'CHAT_OPEN',
'CHAT_CLOSE',
'CHAT_DOCK_LEFT',
'CHAT_DOCK_RIGHT',
'CHAT_DETACHED',
'CHAT_PLACEHOLDER',
'CHAT_SEND',
'CHAT_MISSING_KEY',
'CHAT_TOO_LONG',
'CHAT_SEND_FAILED',
'CHAT_EMPTY'
];
describe('chat overlay contract', () => {
it('isolates the overlay and never renders markup as HTML', () => {
expect(overlaySource).toContain("attachShadow({ mode: 'open' })");
expect(overlaySource).not.toMatch(/\.innerHTML\s*=|insertAdjacentHTML|\.outerHTML\s*=/);
expect(overlaySource).toContain('document.createTextNode(token.text)');
});
it('injects formatting and overlay code only with the selected-tab content script', () => {
expect(backgroundSource).toContain("files: ['chat-format.js', 'chat-overlay.js', 'content.js']");
expect(overlaySource).toContain("const storageKey = `chatOverlayLayout:${location.origin}`");
expect(overlaySource).toContain('document.fullscreenElement || document.documentElement');
});
it('supports all layout and theme combinations with bounded message DOM', () => {
expect(overlaySource).toContain("['left', 'right', 'detached']");
expect(overlaySource).toContain('!layout.detachedInitialized');
expect(overlaySource).toContain('const rect = panel.getBoundingClientRect()');
expect(overlaySource).not.toContain('contentRect');
expect(overlaySource).toContain("#app[data-palette=\"cyber\"][data-theme=\"light\"]");
expect(overlaySource).toContain("#app[data-palette=\"graphite\"][data-theme=\"light\"]");
expect(overlaySource).toContain('const MAX_MESSAGES = 200');
expect(overlaySource).toContain('while (messages.querySelectorAll(\'.message\').length > MAX_MESSAGES)');
expect(overlaySource).toContain('Math.max(1, window.innerWidth)');
expect(overlaySource).toContain('min(${MIN_WIDTH}px, calc(100vw - 16px))');
});
it('blocks page playback shortcuts while the chat composer owns keyboard input', () => {
expect(overlaySource).toContain("for (const eventName of ['keydown', 'keyup', 'keypress'])");
expect(overlaySource).toContain('window.addEventListener(eventName, stopPageKeyboardShortcut, true)');
expect(overlaySource).toContain('path.includes(textarea)');
expect(overlaySource).toContain('event.stopImmediatePropagation()');
expect(overlaySource).toContain('window.removeEventListener(eventName, stopPageKeyboardShortcut, true)');
});
it('keeps the launcher draggable independently from dock mode', () => {
expect(overlaySource).toContain("launcher.addEventListener('pointerdown'");
expect(overlaySource).toContain("launcher.addEventListener('pointermove'");
expect(overlaySource).toContain('layout.launcherX = launcherDrag.x + deltaX');
expect(overlaySource).toContain('layout.launcherY = launcherDrag.y + deltaY');
expect(overlaySource).toContain('suppressLauncherClick = launcherDrag.moved');
expect(overlaySource).not.toMatch(/launcher\.style\.left = `\$\{layout\.x\}px`/);
});
it('reserves page space for real left and right dock modes', () => {
expect(overlaySource).toContain("const PAGE_DOCK_ATTRIBUTE = 'data-koalasync-chat-dock'");
expect(overlaySource).toContain('padding-left: var(${PAGE_DOCK_WIDTH}) !important');
expect(overlaySource).toContain('padding-right: var(${PAGE_DOCK_WIDTH}) !important');
expect(overlaySource).toContain('document.documentElement.setAttribute(PAGE_DOCK_ATTRIBUTE, side)');
expect(overlaySource).toContain('applyPageDock(layout.mode, dockWidth)');
expect(overlaySource).toContain('clearPageDock()');
});
it('counts unread remote messages on the bubble and clears the badge when opened', () => {
expect(overlaySource).toContain("const unreadBadge = element('span', 'unread')");
expect(overlaySource).toContain('if (!opened && !own) setUnreadCount(unreadCount + 1)');
expect(overlaySource).toMatch(/if \(opened\) \{\s*setUnreadCount\(0\)/);
expect(overlaySource).toContain("unreadBadge.classList.toggle('visible', unreadCount > 0)");
expect(backgroundSource).toMatch(/received\.senderId !== peerId[\s\S]*showChatNotification\([\s\S]*senderPeer\.username[\s\S]*received\.senderId/);
expect(backgroundSource).toContain("chrome.notifications.create(`chat_${Date.now()}`");
expect(backgroundSource).toContain("if (settings.chatNotifications === false) return");
});
it('renders timestamped room activity and keeps activity notifications opt-in', () => {
expect(overlaySource).toContain("if (message?.type === 'CHAT_EVENT') appendEvent(message.event)");
expect(overlaySource).toContain("const time = element('time', 'time')");
expect(overlaySource).toContain("date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })");
expect(overlaySource).toContain('if (!opened && context.eventNotifications) setUnreadCount(unreadCount + 1)');
expect(backgroundSource).toContain("eventNotifications: localeData.browserNotifications === true");
expect(backgroundSource).toContain("sendChatActivity(message.action, peerId, timestamp)");
expect(backgroundSource).toContain("sendChatActivity(event, data.senderId, data.actionTimestamp)");
expect(backgroundSource).toContain("sendChatActivity('joined', data.peerId, Date.now())");
expect(backgroundSource).toContain("sendChatActivity('left', data.peerId, Date.now())");
});
it('guards async refresh/send work and clears all composer state on room reset', () => {
expect(overlaySource).toContain('generation !== refreshGeneration');
expect(overlaySource).toContain('if (sending || !context?.enabled) return');
expect(overlaySource).toContain('textarea.value === submittedValue');
expect(overlaySource).toMatch(/CHAT_RESET[\s\S]*resetComposer\(\)/);
expect(overlaySource).toContain('setTimeout(() => finish(null), timeoutMs)');
expect(backgroundSource).toContain('chatReceiveQueue = chatReceiveQueue.catch(() => {}).then');
expect(backgroundSource).toContain("status: 'rate_limited'");
});
it('keeps chat hidden by default without discarding the room chat key', () => {
expect(popupSource).toContain('localData.chatEnabled === true');
expect(backgroundSource).toContain('chatEnabled: data.chatEnabled === true');
expect(backgroundSource).toContain('clientCapabilities: CLIENT_CAPABILITIES');
expect(overlaySource).toContain('all:initial;display:none;position:fixed');
expect(overlaySource).toContain("host.style.display = supported && optedIn ? '' : 'none'");
expect(overlaySource).toContain('supported && optedIn && hasKey && connected');
expect(popupSource).toContain("chrome.storage.local.set({ chatEnabled: elements.chatEnabled.checked })");
expect(popupSource).toMatch(/if \(isCreating\) \{[\s\S]*?type: 'CREATE_CHAT_KEY'[\s\S]*?chatKey = normalizeChatKey/);
expect(popupSource).not.toMatch(/chatEnabled[\s\S]{0,120}chatKey:\s*''/);
});
it('keeps unavailable chat controls discoverable to assistive technology', () => {
expect(overlaySource).toContain("launcher.setAttribute('aria-disabled'");
expect(overlaySource).not.toContain('launcher.disabled =');
expect(overlaySource).toContain("launcher.setAttribute('aria-describedby', launcherHint.id)");
expect(overlaySource).toContain("textarea.setAttribute('aria-describedby', 'chat-composer-count chat-composer-status')");
expect(overlaySource).toContain("status.setAttribute('role', 'status')");
});
it('creates a chat key for both generated-room entry points', () => {
expect(popupSource).toContain('let pendingRoomCreation = false');
expect(popupSource).toContain('const isCreating = pendingRoomCreation || !roomIdInput');
expect(popupSource).toMatch(/function handleCreateRoom\(\)[\s\S]*pendingRoomCreation = true[\s\S]*elements\.joinBtn\.click\(\)/);
expect(popupSource).toContain("type: 'CREATE_CHAT_KEY'");
});
it('contains every chat string in all 15 extension locales', () => {
const localeFiles = fs.readdirSync(localeDir).filter(file => file.endsWith('.json')).sort();
expect(localeFiles).toHaveLength(15);
for (const file of localeFiles) {
const messages = JSON.parse(fs.readFileSync(path.join(localeDir, file), 'utf8'));
for (const key of chatKeys) {
expect(messages[key], `${file}: ${key}`).toBeTypeOf('string');
expect(messages[key], `${file}: ${key}`).not.toBe('');
expect(messages[key], `${file}: ${key}`).not.toMatch(/[—–]/);
}
}
});
});
+808
View File
@@ -0,0 +1,808 @@
(function initKoalaSyncChatOverlay() {
if (window.koalaSyncChatOverlay?.refresh) {
window.koalaSyncChatOverlay.refresh();
return;
}
const MAX_MESSAGES = 200;
const MIN_WIDTH = 300;
const MIN_HEIGHT = 320;
const DEFAULT_WIDTH = 360;
const DEFAULT_HEIGHT = 520;
const LAUNCHER_SIZE = 48;
const EDGE_INSET = 8;
const DOCK_MIN_PAGE_WIDTH = 480;
const PAGE_DOCK_ATTRIBUTE = 'data-koalasync-chat-dock';
const PAGE_DOCK_WIDTH = '--koalasync-chat-dock-width';
const SIZE_PRESETS = Object.freeze({
compact: Object.freeze({ width: 320, height: 400 }),
standard: Object.freeze({ width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT }),
large: Object.freeze({ width: 440, height: 640 })
});
const storageKey = `chatOverlayLayout:${location.origin}`;
const systemTheme = window.matchMedia('(prefers-color-scheme: light)');
let context = null;
let opened = false;
let destroyed = false;
let saveTimer = null;
let applyingLayout = false;
let preferencesLoaded = false;
let startStateApplied = false;
let refreshGeneration = 0;
let sendGeneration = 0;
let sending = false;
let unreadCount = 0;
let layout = {
mode: 'right',
x: 24,
y: 72,
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
customWidth: DEFAULT_WIDTH,
customHeight: DEFAULT_HEIGHT,
detachedInitialized: false,
launcherX: null,
launcherY: null,
launcherInitialized: false
};
let chatPosition = 'right';
let chatSize = 'standard';
let chatStartMode = 'bubble';
let themeMode = 'system';
let themePalette = 'eucalyptus';
const host = document.createElement('div');
host.id = 'koalasync-chat-overlay-host';
host.style.cssText = 'all:initial;display:none;position:fixed;inset:0;z-index:2147483647;pointer-events:none;contain:layout style paint;transform:translateZ(0);';
const shadow = host.attachShadow({ mode: 'open' });
const style = document.createElement('style');
style.textContent = `
:host { all: initial; }
* { box-sizing: border-box; }
#app {
--bg: oklch(0.205 0.025 155); --card: oklch(0.30 0.035 150);
--surface-alt: oklch(0.265 0.032 150); --surface-deep: oklch(0.205 0.025 155);
--accent: oklch(0.73 0.13 155); --accent-hover: oklch(0.79 0.12 155);
--text: oklch(0.96 0.01 90); --text-muted: oklch(0.78 0.02 90);
--border-soft: oklch(0.88 0.025 145 / 0.09); --border-strong: oklch(0.39 0.038 145);
--text-on-green: oklch(0.14 0.02 90); --danger: oklch(0.63 0.18 25);
position: fixed; inset: 0; pointer-events: none; color: var(--text);
font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
#app[data-theme="light"] {
--bg: oklch(0.935 0.018 110); --card: oklch(0.995 0.006 100);
--surface-alt: oklch(0.91 0.022 115); --surface-deep: oklch(0.935 0.018 110);
--accent: oklch(0.52 0.13 155); --accent-hover: oklch(0.45 0.12 155);
--text: oklch(0.21 0.03 140); --text-muted: oklch(0.43 0.03 135);
--border-soft: oklch(0.28 0.035 140 / 0.10); --border-strong: oklch(0.73 0.035 125);
--text-on-green: oklch(0.16 0.03 140);
}
#app[data-palette="cyber"] {
--bg: oklch(0.185 0.04 265); --card: oklch(0.285 0.05 265);
--surface-alt: oklch(0.25 0.048 265); --surface-deep: oklch(0.185 0.04 265);
--accent: oklch(0.59 0.20 275); --accent-hover: oklch(0.69 0.17 275);
--text: oklch(0.97 0.012 265); --text-muted: oklch(0.74 0.03 265);
--border-soft: oklch(0.9 0.03 265 / 0.10); --border-strong: oklch(0.42 0.05 265);
--text-on-green: oklch(0.99 0.006 275);
}
#app[data-palette="cyber"][data-theme="light"] {
--bg: oklch(0.945 0.014 265); --card: oklch(0.995 0.005 265);
--surface-alt: oklch(0.915 0.02 265); --surface-deep: oklch(0.945 0.014 265);
--accent: oklch(0.53 0.21 275); --accent-hover: oklch(0.46 0.20 275);
--text: oklch(0.26 0.05 265); --text-muted: oklch(0.48 0.045 265);
--border-soft: oklch(0.30 0.05 265 / 0.12); --border-strong: oklch(0.79 0.03 265);
--text-on-green: oklch(0.99 0.005 275);
}
#app[data-palette="graphite"] {
--bg: oklch(0.165 0.004 260); --card: oklch(0.255 0.005 260);
--surface-alt: oklch(0.225 0.005 260); --surface-deep: oklch(0.165 0.004 260);
--accent: oklch(0.93 0.006 260); --accent-hover: oklch(0.99 0.003 260);
--text: oklch(0.97 0.003 260); --text-muted: oklch(0.72 0.005 260);
--border-soft: oklch(0.92 0.008 260 / 0.11); --border-strong: oklch(0.42 0.006 260);
--text-on-green: oklch(0.20 0.004 260);
}
#app[data-palette="graphite"][data-theme="light"] {
--bg: oklch(0.928 0.003 260); --card: oklch(1 0 0);
--surface-alt: oklch(0.90 0.004 260); --surface-deep: oklch(0.928 0.003 260);
--accent: oklch(0.28 0.006 260); --accent-hover: oklch(0.18 0.005 260);
--text: oklch(0.22 0.005 260); --text-muted: oklch(0.46 0.006 260);
--border-soft: oklch(0.20 0.006 260 / 0.13); --border-strong: oklch(0.77 0.004 260);
--text-on-green: oklch(0.98 0.002 260);
}
button, textarea { font: inherit; }
button { color: inherit; }
.launcher {
position: fixed; width: 48px; height: 48px; border: 1px solid var(--border-strong);
border-radius: 16px; background: var(--card); color: var(--text); cursor: pointer;
pointer-events: auto; box-shadow: 0 10px 28px rgb(0 0 0 / .32); font-size: 22px;
touch-action: none; user-select: none;
}
.launcher:hover:not([aria-disabled="true"]) { border-color: var(--accent); transform: translateY(-1px); }
.launcher[aria-disabled="true"] { cursor: not-allowed; opacity: .58; }
.launcher-icon { pointer-events: none; }
.unread {
position: absolute; top: -7px; right: -7px; display: none; min-width: 22px; height: 22px;
align-items: center; justify-content: center; padding: 0 6px; border: 2px solid var(--card);
border-radius: 999px; background: var(--danger); color: white; font-size: 11px; font-weight: 800;
line-height: 1; pointer-events: none;
}
.unread.visible { display: flex; }
.panel {
position: fixed; display: none; flex-direction: column; overflow: hidden;
min-width: min(${MIN_WIDTH}px, calc(100vw - 16px)); min-height: min(${MIN_HEIGHT}px, calc(100vh - 16px)); max-width: calc(100vw - 16px);
max-height: calc(100vh - 16px); pointer-events: auto; color: var(--text);
background: var(--card); border: 1px solid var(--border-strong); border-radius: 18px;
box-shadow: 0 18px 55px rgb(0 0 0 / .38); transform: translateZ(0);
}
.panel.open { display: flex; }
.panel.docked { border-radius: 0; max-height: 100vh; }
.header { display: flex; align-items: center; gap: 10px; padding: 12px; border-bottom: 1px solid var(--border-soft); user-select: none; }
.header.detached { cursor: move; }
.heading { min-width: 0; flex: 1; }
.title { font-size: 14px; font-weight: 760; }
.subtitle { color: var(--text-muted); font-size: 11px; }
.modes { display: flex; gap: 4px; }
.icon-button { width: 30px; height: 30px; border: 1px solid var(--border-soft); border-radius: 9px; background: var(--surface-alt); cursor: pointer; }
.icon-button:hover, .icon-button.active { border-color: var(--accent); color: var(--accent); }
.messages { flex: 1; min-height: 0; overflow-y: auto; padding: 12px; background: var(--bg); overscroll-behavior: contain; }
.empty { color: var(--text-muted); text-align: center; padding: 32px 12px; font-size: 12px; }
.message { margin-bottom: 11px; overflow-wrap: anywhere; }
.meta { display: flex; align-items: baseline; gap: 7px; margin-bottom: 2px; }
.sender { color: var(--accent); font-weight: 700; font-size: 12px; }
.time { color: var(--text-muted); font-size: 10px; }
.body { white-space: pre-wrap; color: var(--text); }
.event-message {
display: flex; align-items: center; justify-content: center; gap: 7px; margin: 8px 0;
color: var(--text-muted); font-size: 11px; text-align: center;
}
.event-message::before, .event-message::after { content: ""; flex: 1; height: 1px; background: var(--border-soft); }
.event-text { max-width: 78%; }
.composer { padding: 10px; border-top: 1px solid var(--border-soft); background: var(--card); }
textarea { width: 100%; min-height: 62px; max-height: 132px; resize: vertical; border: 1px solid var(--border-strong); border-radius: 11px; padding: 9px; background: var(--surface-deep); color: var(--text); outline: none; }
textarea:focus { border-color: var(--accent); }
textarea::placeholder { color: var(--text-muted); }
.composer-row { display: flex; align-items: center; gap: 8px; margin-top: 7px; }
.count { flex: 1; color: var(--text-muted); font-size: 10px; }
.status { color: var(--danger); font-size: 10px; min-height: 14px; }
.send { border: 0; border-radius: 10px; padding: 8px 13px; background: var(--accent); color: var(--text-on-green); font-weight: 750; cursor: pointer; }
.send:hover:not(:disabled) { background: var(--accent-hover); }
.send:disabled { opacity: .55; cursor: wait; }
.visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
@media (max-width: 520px) { .panel { max-width: calc(100vw - 12px); } }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
`;
function element(tag, className, text) {
const node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = text;
return node;
}
const app = element('div');
app.id = 'app';
const launcher = element('button', 'launcher');
launcher.type = 'button';
const launcherIcon = element('span', 'launcher-icon', '💬');
const unreadBadge = element('span', 'unread');
unreadBadge.setAttribute('aria-hidden', 'true');
launcher.append(launcherIcon, unreadBadge);
const launcherHint = element('span', 'visually-hidden');
launcherHint.id = 'chat-launcher-hint';
const panel = element('section', 'panel');
panel.setAttribute('role', 'dialog');
const header = element('header', 'header');
const heading = element('div', 'heading');
const title = element('div', 'title');
const subtitle = element('div', 'subtitle');
heading.append(title, subtitle);
const modes = element('div', 'modes');
const leftButton = element('button', 'icon-button', '←');
const detachedButton = element('button', 'icon-button', '✥');
const rightButton = element('button', 'icon-button', '→');
const closeButton = element('button', 'icon-button', '×');
for (const button of [leftButton, detachedButton, rightButton, closeButton]) button.type = 'button';
modes.append(leftButton, detachedButton, rightButton, closeButton);
header.append(heading, modes);
const messages = element('div', 'messages');
messages.setAttribute('aria-live', 'polite');
const empty = element('div', 'empty');
messages.append(empty);
const composer = element('form', 'composer');
const textareaLabel = element('label', 'visually-hidden');
textareaLabel.htmlFor = 'chat-composer-input';
const textarea = element('textarea');
textarea.id = 'chat-composer-input';
textarea.setAttribute('aria-describedby', 'chat-composer-count chat-composer-status');
const composerRow = element('div', 'composer-row');
const count = element('span', 'count', '0/500');
count.id = 'chat-composer-count';
const sendButton = element('button', 'send');
sendButton.type = 'submit';
composerRow.append(count, sendButton);
const status = element('div', 'status');
status.id = 'chat-composer-status';
status.setAttribute('role', 'status');
composer.append(textareaLabel, textarea, composerRow, status);
panel.append(header, messages, composer);
app.append(launcherHint, launcher, panel);
shadow.append(style, app);
document.documentElement.append(host);
const pageDockStyle = document.createElement('style');
pageDockStyle.id = 'koalasync-chat-page-dock-style';
pageDockStyle.textContent = `
html[${PAGE_DOCK_ATTRIBUTE}="left"] {
box-sizing: border-box !important;
padding-left: var(${PAGE_DOCK_WIDTH}) !important;
}
html[${PAGE_DOCK_ATTRIBUTE}="right"] {
box-sizing: border-box !important;
padding-right: var(${PAGE_DOCK_WIDTH}) !important;
}
`;
document.documentElement.append(pageDockStyle);
function messageRuntime(payload, timeoutMs = 5000) {
return new Promise(resolve => {
let settled = false;
const finish = response => {
if (settled) return;
settled = true;
clearTimeout(timeout);
resolve(response || null);
};
const timeout = setTimeout(() => finish(null), timeoutMs);
try {
chrome.runtime.sendMessage(payload, response => {
if (chrome.runtime.lastError) finish(null);
else finish(response);
});
} catch (_) {
finish(null);
}
});
}
function strings() {
return context?.strings || {};
}
function applyStrings() {
const text = strings();
title.textContent = text.title || '';
subtitle.textContent = text.liveOnly || '';
launcher.setAttribute('aria-label', text.open || '');
const unavailableHint = context?.supported && !context?.hasKey
? (text.missingKey || '')
: context?.supported && !context?.connected ? (text.sendFailed || '') : '';
launcher.title = unavailableHint || text.open || '';
launcherHint.textContent = unavailableHint;
if (unavailableHint) launcher.setAttribute('aria-describedby', launcherHint.id);
else launcher.removeAttribute('aria-describedby');
panel.setAttribute('aria-label', text.title || '');
textarea.placeholder = text.placeholder || '';
textareaLabel.textContent = text.placeholder || text.title || '';
sendButton.textContent = text.send || '';
empty.textContent = text.empty || '';
leftButton.title = text.dockLeft || '';
detachedButton.title = text.detached || '';
rightButton.title = text.dockRight || '';
closeButton.title = text.close || '';
leftButton.setAttribute('aria-label', text.dockLeft || '');
detachedButton.setAttribute('aria-label', text.detached || '');
rightButton.setAttribute('aria-label', text.dockRight || '');
closeButton.setAttribute('aria-label', text.close || '');
applyUnreadCount();
}
function applyTheme() {
const light = themeMode === 'light' || (themeMode === 'system' && systemTheme.matches);
app.dataset.theme = light ? 'light' : 'dark';
app.dataset.palette = ['eucalyptus', 'cyber', 'graphite'].includes(themePalette) ? themePalette : 'eucalyptus';
}
function viewportBounds() {
return { width: Math.max(1, window.innerWidth), height: Math.max(1, window.innerHeight) };
}
function normalizePosition(value) {
return ['left', 'right', 'detached'].includes(value) ? value : 'right';
}
function normalizeSize(value) {
return ['compact', 'standard', 'large', 'custom'].includes(value) ? value : 'standard';
}
function preferredSize() {
return SIZE_PRESETS[chatSize] || {
width: Number(layout.customWidth) || DEFAULT_WIDTH,
height: Number(layout.customHeight) || DEFAULT_HEIGHT
};
}
function clampLauncher() {
const viewport = viewportBounds();
const maxX = Math.max(0, viewport.width - LAUNCHER_SIZE - EDGE_INSET);
const maxY = Math.max(0, viewport.height - LAUNCHER_SIZE - EDGE_INSET);
layout.launcherX = Math.min(Math.max(Number(layout.launcherX) || EDGE_INSET, EDGE_INSET), maxX);
layout.launcherY = Math.min(Math.max(Number(layout.launcherY) || EDGE_INSET, EDGE_INSET), maxY);
}
function initializeLauncher() {
if (layout.launcherInitialized) return;
const viewport = viewportBounds();
layout.launcherX = chatPosition === 'left'
? Math.min(16, Math.max(EDGE_INSET, viewport.width - LAUNCHER_SIZE - EDGE_INSET))
: Math.max(EDGE_INSET, viewport.width - LAUNCHER_SIZE - 16);
layout.launcherY = Math.max(EDGE_INSET, Math.round(viewport.height / 2 - LAUNCHER_SIZE / 2));
layout.launcherInitialized = true;
clampLauncher();
}
function clearPageDock() {
document.documentElement.removeAttribute(PAGE_DOCK_ATTRIBUTE);
document.documentElement.style.removeProperty(PAGE_DOCK_WIDTH);
}
function applyPageDock(side, width) {
const viewport = viewportBounds();
if (!opened || document.fullscreenElement || viewport.width - width < DOCK_MIN_PAGE_WIDTH) {
clearPageDock();
return;
}
document.documentElement.setAttribute(PAGE_DOCK_ATTRIBUTE, side);
document.documentElement.style.setProperty(PAGE_DOCK_WIDTH, `${width}px`);
}
function applyUnreadCount() {
unreadBadge.textContent = unreadCount > 99 ? '99+' : String(unreadCount);
unreadBadge.classList.toggle('visible', unreadCount > 0);
const openLabel = strings().open || '';
launcher.setAttribute('aria-label', unreadCount > 0 ? `${openLabel} (${unreadCount})` : openLabel);
}
function setUnreadCount(next) {
unreadCount = Math.max(0, Number.isFinite(next) ? Math.trunc(next) : 0);
applyUnreadCount();
}
function clampDetached() {
const viewport = viewportBounds();
const maxWidth = Math.max(1, Math.min(600, viewport.width - 16));
const maxHeight = Math.max(1, viewport.height - 16);
const horizontalInset = Math.min(8, Math.max(0, Math.floor((viewport.width - 1) / 2)));
const verticalInset = Math.min(8, Math.max(0, Math.floor((viewport.height - 1) / 2)));
layout.width = Math.min(Math.max(Number(layout.width) || DEFAULT_WIDTH, Math.min(MIN_WIDTH, maxWidth)), maxWidth);
layout.height = Math.min(Math.max(Number(layout.height) || DEFAULT_HEIGHT, Math.min(MIN_HEIGHT, maxHeight)), maxHeight);
layout.x = Math.min(Math.max(Number(layout.x) || horizontalInset, horizontalInset), Math.max(horizontalInset, viewport.width - layout.width - horizontalInset));
layout.y = Math.min(Math.max(Number(layout.y) || verticalInset, verticalInset), Math.max(verticalInset, viewport.height - layout.height - verticalInset));
}
function saveLayout() {
if (saveTimer) clearTimeout(saveTimer);
saveTimer = setTimeout(() => {
saveTimer = null;
chrome.storage.local.set({ [storageKey]: layout }).catch(() => {});
}, 150);
}
function applyLayout() {
applyingLayout = true;
const size = preferredSize();
initializeLauncher();
clampLauncher();
launcher.style.left = `${layout.launcherX}px`;
launcher.style.right = 'auto';
launcher.style.top = `${layout.launcherY}px`;
panel.style.right = 'auto';
panel.style.left = 'auto';
panel.style.bottom = 'auto';
panel.style.resize = 'none';
panel.classList.toggle('docked', layout.mode !== 'detached');
header.classList.toggle('detached', layout.mode === 'detached');
leftButton.classList.toggle('active', layout.mode === 'left');
detachedButton.classList.toggle('active', layout.mode === 'detached');
rightButton.classList.toggle('active', layout.mode === 'right');
if (layout.mode === 'detached') {
clearPageDock();
clampDetached();
panel.style.left = `${layout.x}px`;
panel.style.top = `${layout.y}px`;
panel.style.width = `${layout.width}px`;
panel.style.height = `${layout.height}px`;
panel.style.resize = 'both';
} else {
const viewport = viewportBounds();
const dockWidth = Math.max(1, Math.min(size.width, viewport.width - 16));
panel.style.top = '0';
panel.style.bottom = '0';
panel.style.width = `${dockWidth}px`;
panel.style.height = `${viewport.height}px`;
panel.style[layout.mode] = '0';
applyPageDock(layout.mode, dockWidth);
}
globalThis.queueMicrotask(() => { applyingLayout = false; });
}
function setMode(mode, persistPreference = true) {
mode = normalizePosition(mode);
if (mode === 'detached' && !layout.detachedInitialized) {
const size = preferredSize();
layout.x = Math.max(8, Math.round((window.innerWidth - size.width) / 2));
layout.y = Math.max(8, Math.round((window.innerHeight - size.height) / 2));
layout.detachedInitialized = true;
}
chatPosition = mode;
layout.mode = mode;
applyLayout();
saveLayout();
if (persistPreference) chrome.storage.local.set({ chatPosition: mode }).catch(() => {});
}
function setSize(size, persistPreference = true) {
chatSize = normalizeSize(size);
const preset = SIZE_PRESETS[chatSize];
if (preset) {
layout.width = preset.width;
layout.height = preset.height;
} else {
layout.width = Number(layout.customWidth) || DEFAULT_WIDTH;
layout.height = Number(layout.customHeight) || DEFAULT_HEIGHT;
}
if (layout.mode === 'detached') clampDetached();
applyLayout();
saveLayout();
if (persistPreference) chrome.storage.local.set({ chatSize }).catch(() => {});
}
function setOpened(next) {
opened = !!next && !!context?.enabled;
panel.classList.toggle('open', opened);
launcher.style.display = opened ? 'none' : '';
if (opened) {
setUnreadCount(0);
textarea.focus();
messages.scrollTop = messages.scrollHeight;
}
applyLayout();
}
function applyContext(next) {
const previousRoomId = context?.roomId;
context = next || null;
const supported = !!context?.supported;
const optedIn = !!context?.enabled;
const hasKey = !!context?.hasKey;
const connected = !!context?.connected;
context = context ? { ...context, enabled: supported && optedIn && hasKey && connected } : null;
if (previousRoomId && previousRoomId !== context?.roomId) {
clearMessages();
startStateApplied = false;
}
host.style.display = supported && optedIn ? '' : 'none';
launcher.setAttribute('aria-disabled', String(!context?.enabled));
if (!optedIn) startStateApplied = false;
if (!context?.enabled) {
setOpened(false);
} else if (preferencesLoaded && !startStateApplied) {
startStateApplied = true;
setOpened(chatStartMode === 'open');
}
applyStrings();
applyLayout();
}
async function refresh() {
if (destroyed) return;
const generation = ++refreshGeneration;
const next = await messageRuntime({ type: 'GET_CHAT_CONTEXT' });
if (destroyed || generation !== refreshGeneration) return;
applyContext(next);
}
function appendMessage(message) {
if (!context?.enabled || !message || typeof message.text !== 'string') return;
if (empty.isConnected) empty.remove();
const wrapper = element('article', 'message');
const meta = element('div', 'meta');
const own = message.senderId === context.peerId;
if (!opened && !own) setUnreadCount(unreadCount + 1);
const sender = element('span', 'sender', own ? (strings().you || '') : (message.username || message.senderId || ''));
const time = element('time', 'time');
const date = new Date(message.timestamp);
time.textContent = Number.isFinite(date.getTime()) ? date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '';
meta.append(sender, time);
const body = element('div', 'body');
const tokens = globalThis.KoalaSyncChatFormat?.tokenizeChatText(message.text) || [{ type: 'text', text: message.text }];
for (const token of tokens) {
const node = token.type === 'strong' ? document.createElement('strong') : token.type === 'em' ? document.createElement('em') : document.createTextNode(token.text);
if (node.nodeType === 1) node.textContent = token.text;
body.append(node);
}
wrapper.append(meta, body);
messages.append(wrapper);
while (messages.querySelectorAll('.message').length > MAX_MESSAGES) messages.querySelector('.message')?.remove();
messages.scrollTop = messages.scrollHeight;
}
function appendEvent(event) {
if (!context?.enabled || !event || typeof event.action !== 'string') return;
const own = event.senderId === context.peerId;
const displayName = own ? (strings().you || '') : (event.username || event.senderId || '');
const labels = {
play: strings().eventPlay,
pause: strings().eventPause,
seek: strings().eventSeek,
force_sync_prepare: strings().eventForcePrepare,
force_sync_execute: strings().eventForceExecute
};
let eventText = '';
if (event.action === 'joined' || event.action === 'left') {
const template = event.action === 'joined' ? strings().eventJoined : strings().eventLeft;
eventText = (template || '{name}').replace('{name}', displayName);
} else if (labels[event.action]) {
eventText = (strings().eventAction || '{name} {action}')
.replace('{name}', displayName)
.replace('{action}', labels[event.action]);
}
if (!eventText) return;
if (empty.isConnected) empty.remove();
if (!opened && context.eventNotifications) setUnreadCount(unreadCount + 1);
const wrapper = element('article', 'message event-message');
const body = element('span', 'event-text', eventText);
const time = element('time', 'time');
const date = new Date(event.timestamp);
time.textContent = Number.isFinite(date.getTime()) ? date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '';
wrapper.append(body, time);
messages.append(wrapper);
while (messages.querySelectorAll('.message').length > MAX_MESSAGES) messages.querySelector('.message')?.remove();
messages.scrollTop = messages.scrollHeight;
}
function clearMessages() {
messages.replaceChildren(empty);
setUnreadCount(0);
}
function resetComposer() {
sendGeneration++;
sending = false;
textarea.value = '';
count.textContent = '0/500';
count.style.color = '';
status.textContent = '';
sendButton.disabled = false;
}
let launcherDrag = null;
let suppressLauncherClick = false;
launcher.addEventListener('click', event => {
if (suppressLauncherClick && event.detail !== 0) {
suppressLauncherClick = false;
return;
}
suppressLauncherClick = false;
setOpened(true);
});
launcher.addEventListener('pointerdown', event => {
launcherDrag = {
pointerId: event.pointerId,
startX: event.clientX,
startY: event.clientY,
x: layout.launcherX,
y: layout.launcherY,
moved: false
};
launcher.setPointerCapture(event.pointerId);
});
launcher.addEventListener('pointermove', event => {
if (!launcherDrag || event.pointerId !== launcherDrag.pointerId) return;
const deltaX = event.clientX - launcherDrag.startX;
const deltaY = event.clientY - launcherDrag.startY;
if (Math.hypot(deltaX, deltaY) >= 4) launcherDrag.moved = true;
layout.launcherX = launcherDrag.x + deltaX;
layout.launcherY = launcherDrag.y + deltaY;
clampLauncher();
launcher.style.left = `${layout.launcherX}px`;
launcher.style.top = `${layout.launcherY}px`;
});
launcher.addEventListener('pointerup', event => {
if (!launcherDrag || event.pointerId !== launcherDrag.pointerId) return;
suppressLauncherClick = launcherDrag.moved;
launcherDrag = null;
saveLayout();
});
launcher.addEventListener('pointercancel', () => {
launcherDrag = null;
suppressLauncherClick = false;
});
closeButton.addEventListener('click', () => setOpened(false));
leftButton.addEventListener('click', () => setMode('left'));
detachedButton.addEventListener('click', () => setMode('detached'));
rightButton.addEventListener('click', () => setMode('right'));
textarea.addEventListener('input', () => {
const length = [...textarea.value].length;
count.textContent = `${length}/500`;
count.style.color = length > 500 ? 'var(--danger)' : '';
status.textContent = length > 500 ? (strings().tooLong || '') : '';
});
const stopPageKeyboardShortcut = event => {
const path = typeof event.composedPath === 'function' ? event.composedPath() : [];
if (!path.includes(textarea) && !path.includes(composer)) return;
if (event.type === 'keydown' && event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
if (!sending) composer.requestSubmit();
}
event.stopImmediatePropagation();
};
for (const eventName of ['keydown', 'keyup', 'keypress']) {
window.addEventListener(eventName, stopPageKeyboardShortcut, true);
}
composer.addEventListener('submit', async event => {
event.preventDefault();
if (sending || !context?.enabled) return;
const submittedValue = textarea.value;
const text = textarea.value.trim();
if (!text) return;
if ([...text].length > 500) {
status.textContent = strings().tooLong || '';
return;
}
const generation = ++sendGeneration;
sending = true;
sendButton.disabled = true;
status.textContent = '';
const response = await messageRuntime({ type: 'CHAT_SEND', text });
if (destroyed || generation !== sendGeneration) return;
sending = false;
sendButton.disabled = false;
if (response?.status === 'ok') {
if (textarea.value === submittedValue) {
textarea.value = '';
count.textContent = '0/500';
}
} else {
status.textContent = response?.status === 'too_long' ? (strings().tooLong || '') : (strings().sendFailed || '');
}
});
let drag = null;
header.addEventListener('pointerdown', event => {
if (layout.mode !== 'detached' || event.target.closest('button')) return;
drag = { pointerId: event.pointerId, startX: event.clientX, startY: event.clientY, x: layout.x, y: layout.y };
header.setPointerCapture(event.pointerId);
});
header.addEventListener('pointermove', event => {
if (!drag || event.pointerId !== drag.pointerId) return;
layout.x = drag.x + event.clientX - drag.startX;
layout.y = drag.y + event.clientY - drag.startY;
clampDetached();
applyLayout();
});
header.addEventListener('pointerup', event => {
if (!drag || event.pointerId !== drag.pointerId) return;
drag = null;
saveLayout();
});
const resizeObserver = new globalThis.ResizeObserver(() => {
if (applyingLayout || layout.mode !== 'detached') return;
const rect = panel.getBoundingClientRect();
if (!opened || !rect?.width || !rect.height) return;
if (Math.abs(rect.width - layout.width) < 1 && Math.abs(rect.height - layout.height) < 1) return;
layout.width = rect.width;
layout.height = rect.height;
layout.customWidth = rect.width;
layout.customHeight = rect.height;
if (chatSize !== 'custom') {
chatSize = 'custom';
chrome.storage.local.set({ chatSize }).catch(() => {});
}
clampDetached();
saveLayout();
});
resizeObserver.observe(panel);
function moveIntoFullscreen() {
const parent = document.fullscreenElement || document.documentElement;
if (parent && host.parentNode !== parent) parent.append(host);
globalThis.requestAnimationFrame(applyLayout);
}
function handleResize() {
clampLauncher();
applyLayout();
saveLayout();
if (layout.mode === 'detached') {
clampDetached();
}
}
function handleSystemTheme() {
if (themeMode === 'system') applyTheme();
}
function handleStorage(changes, area) {
if (area !== 'local') return;
if (changes.themeMode) themeMode = changes.themeMode.newValue || 'system';
if (changes.themePalette) themePalette = changes.themePalette.newValue || 'eucalyptus';
if (changes.themeMode || changes.themePalette) applyTheme();
if (changes.chatPosition) setMode(changes.chatPosition.newValue, false);
if (changes.chatSize) setSize(changes.chatSize.newValue, false);
if (changes.chatStartMode) {
chatStartMode = changes.chatStartMode.newValue === 'open' ? 'open' : 'bubble';
if (context?.enabled) {
startStateApplied = true;
setOpened(chatStartMode === 'open');
}
}
if (changes.locale) refresh();
}
function handleRuntime(message) {
if (message?.type === 'CHAT_MESSAGE') appendMessage(message.message);
if (message?.type === 'CHAT_EVENT') appendEvent(message.event);
if (message?.type === 'CHAT_CONTEXT_UPDATE' || message?.type === 'CONNECTION_STATUS') refresh();
if (message?.type === 'CHAT_RESET') {
clearMessages();
resetComposer();
refresh();
}
if (message?.type === 'CHAT_DESTROY') destroy();
}
function destroy() {
if (destroyed) return;
destroyed = true;
refreshGeneration++;
sendGeneration++;
if (saveTimer) clearTimeout(saveTimer);
resizeObserver.disconnect();
clearPageDock();
pageDockStyle.remove();
for (const eventName of ['keydown', 'keyup', 'keypress']) {
window.removeEventListener(eventName, stopPageKeyboardShortcut, true);
}
document.removeEventListener('fullscreenchange', moveIntoFullscreen);
window.removeEventListener('resize', handleResize);
systemTheme.removeEventListener('change', handleSystemTheme);
chrome.storage.onChanged.removeListener(handleStorage);
chrome.runtime.onMessage.removeListener(handleRuntime);
host.remove();
window.koalaSyncChatOverlay = null;
}
document.addEventListener('fullscreenchange', moveIntoFullscreen);
window.addEventListener('resize', handleResize, { passive: true });
systemTheme.addEventListener('change', handleSystemTheme);
chrome.storage.onChanged.addListener(handleStorage);
chrome.runtime.onMessage.addListener(handleRuntime);
chrome.storage.local.get([storageKey, 'themeMode', 'themePalette', 'chatPosition', 'chatSize', 'chatStartMode'], data => {
const storedLayout = data[storageKey];
if (storedLayout && typeof storedLayout === 'object') {
layout = { ...layout, ...storedLayout };
layout.customWidth = Number(storedLayout.customWidth) || Number(storedLayout.width) || DEFAULT_WIDTH;
layout.customHeight = Number(storedLayout.customHeight) || Number(storedLayout.height) || DEFAULT_HEIGHT;
}
chatPosition = normalizePosition(data.chatPosition);
chatSize = normalizeSize(data.chatSize);
chatStartMode = data.chatStartMode === 'open' ? 'open' : 'bubble';
layout.mode = chatPosition;
if (layout.mode === 'detached') layout.detachedInitialized = true;
const preset = SIZE_PRESETS[chatSize];
if (preset) {
layout.width = preset.width;
layout.height = preset.height;
}
themeMode = data.themeMode || 'system';
themePalette = data.themePalette || 'eucalyptus';
preferencesLoaded = true;
applyTheme();
applyLayout();
refresh();
});
window.koalaSyncChatOverlay = { refresh, destroy };
})();
+58
View File
@@ -0,0 +1,58 @@
export const MAX_ROOM_ID_LENGTH = 64;
export const CHAT_SEND_LIMIT = 9;
export const CHAT_SEND_WINDOW_MS = 10000;
export function normalizeRoomId(value) {
if (typeof value !== 'string') return '';
return value.trim().replace(/[^a-zA-Z0-9\-]/g, '').slice(0, MAX_ROOM_ID_LENGTH);
}
export function createChatSendLimiter({
limit = CHAT_SEND_LIMIT,
windowMs = CHAT_SEND_WINDOW_MS,
now = () => Date.now()
} = {}) {
let timestamps = [];
return {
take() {
const current = now();
timestamps = timestamps.filter(timestamp => current - timestamp < windowMs);
if (timestamps.length >= limit) {
return {
allowed: false,
retryAfterMs: Math.max(1, windowMs - (current - timestamps[0]))
};
}
timestamps.push(current);
return { allowed: true, retryAfterMs: 0 };
},
reset() {
timestamps = [];
}
};
}
export function createLatestTaskQueue() {
let generation = 0;
let tail = Promise.resolve();
return {
invalidate() {
generation++;
},
async run(task) {
const requestGeneration = ++generation;
const previous = tail;
let release;
tail = new Promise(resolve => { release = resolve; });
await previous.catch(() => {});
const isCurrent = () => requestGeneration === generation;
try {
return await task(isCurrent);
} finally {
release();
}
}
};
}
+49
View File
@@ -0,0 +1,49 @@
import { describe, expect, it } from 'vitest';
import {
CHAT_SEND_LIMIT,
createChatSendLimiter,
createLatestTaskQueue,
MAX_ROOM_ID_LENGTH,
normalizeRoomId
} from './chat-session.js';
describe('chat session boundaries', () => {
it('matches the relay room ID sanitizer and length limit', () => {
expect(normalizeRoomId(' ROOM_!42 ')).toBe('ROOM42');
expect(normalizeRoomId('A'.repeat(MAX_ROOM_ID_LENGTH + 10))).toBe('A'.repeat(MAX_ROOM_ID_LENGTH));
expect(normalizeRoomId(null)).toBe('');
});
it('keeps client chat bursts below the relay disconnect threshold', () => {
let current = 1000;
const limiter = createChatSendLimiter({ now: () => current });
for (let index = 0; index < CHAT_SEND_LIMIT; index++) {
expect(limiter.take()).toEqual({ allowed: true, retryAfterMs: 0 });
}
expect(limiter.take()).toEqual({ allowed: false, retryAfterMs: 10000 });
current += 10000;
expect(limiter.take()).toEqual({ allowed: true, retryAfterMs: 0 });
limiter.reset();
expect(limiter.take()).toEqual({ allowed: true, retryAfterMs: 0 });
});
it('serializes join work and prevents an older request from winning storage races', async () => {
const queue = createLatestTaskQueue();
let releaseFirst;
const writes = [];
const first = queue.run(async isCurrent => {
await new Promise(resolve => { releaseFirst = resolve; });
if (isCurrent()) writes.push('first');
return { status: isCurrent() ? 'ok' : 'superseded' };
});
await new Promise(resolve => setTimeout(resolve, 0));
const second = queue.run(async isCurrent => {
if (isCurrent()) writes.push('second');
return { status: 'ok' };
});
releaseFirst();
await expect(first).resolves.toEqual({ status: 'superseded' });
await expect(second).resolves.toEqual({ status: 'ok' });
expect(writes).toEqual(['second']);
});
});
+11
View File
@@ -0,0 +1,11 @@
export function buildChatRelayPayload(ciphertext) {
return { ciphertext };
}
export function encodeSocketEvent(event, data, forbiddenSecret = '') {
const payload = JSON.stringify([event, data]);
if (forbiddenSecret && payload.includes(forbiddenSecret)) {
throw new Error('Refusing to send chat secret to relay');
}
return `42${payload}`;
}
+21
View File
@@ -0,0 +1,21 @@
import { describe, expect, it } from 'vitest';
import { buildChatRelayPayload, encodeSocketEvent } from './chat-wire.js';
describe('chat wire boundary', () => {
const secret = 'R5Ti1nxp0crfAFHf3gVncw';
it('sends only ciphertext for chat messages', () => {
expect(buildChatRelayPayload('ciphertext')).toEqual({ ciphertext: 'ciphertext' });
});
it('rejects the secret anywhere in any outgoing relay event', () => {
for (const [event, payload] of [
['join_room', { roomId: 'ROOM', password: 'PASS', chatKey: secret }],
['peer_status', { status: 'heartbeat', nested: { secret } }],
['chat_message', { ciphertext: `prefix-${secret}-suffix` }]
]) {
expect(() => encodeSocketEvent(event, payload, secret)).toThrow('Refusing to send chat secret');
}
expect(encodeSocketEvent('join_room', { roomId: 'ROOM', password: 'PASS' }, secret)).not.toContain(secret);
});
});
+33 -7
View File
@@ -12,16 +12,23 @@ export function normalizeTabId(value) {
return Number.isSafeInteger(tabId) ? tabId : null;
}
export function describeTabUrl(rawUrl) {
function browserSupportsPortMatchPatterns(chromeApi) {
// runtime.getBrowserInfo is a Firefox-only WebExtension API. Firefox does
// not accept ports in match patterns, while Chromium does.
return typeof chromeApi?.runtime?.getBrowserInfo !== 'function';
}
export function describeTabUrl(rawUrl, { includePort = true } = {}) {
if (typeof rawUrl !== 'string' || !rawUrl) return null;
try {
const url = new URL(rawUrl);
if (url.protocol === 'http:' || url.protocol === 'https:') {
const permissionHost = includePort ? url.host : url.hostname;
return {
url: rawUrl,
host: url.host,
originPattern: `${url.origin}/*`
originPattern: `${url.protocol}//${permissionHost}/*`
};
}
if (url.protocol === 'file:') {
@@ -40,7 +47,11 @@ export function describeTabUrl(rawUrl) {
export async function inspectTabHostAccess(chromeApi, tabId) {
const tab = await chromeApi.tabs.get(tabId);
const descriptor = describeTabUrl(tab?.pendingUrl || tab?.url || '');
// executeScript targets the committed document. pendingUrl may already point
// at another origin while tab.url is still the document being injected into.
const descriptor = describeTabUrl(tab?.url || tab?.pendingUrl || '', {
includePort: browserSupportsPortMatchPatterns(chromeApi)
});
if (!descriptor) {
return {
tab,
@@ -59,7 +70,8 @@ export async function inspectTabHostAccess(chromeApi, tabId) {
const granted = await callBooleanPermissionMethod(
chromeApi,
'contains',
{ origins: [descriptor.originPattern] }
{ origins: [descriptor.originPattern] },
{ timeoutMs: 1000 }
);
return {
tab,
@@ -73,20 +85,29 @@ export async function inspectTabHostAccess(chromeApi, tabId) {
}
}
function callBooleanPermissionMethod(chromeApi, methodName, request) {
function callBooleanPermissionMethod(chromeApi, methodName, request, { timeoutMs = null } = {}) {
const method = chromeApi.permissions?.[methodName];
if (typeof method !== 'function') return Promise.resolve(null);
return new Promise((resolve, reject) => {
let settled = false;
let timeout = null;
const clearSettlementTimeout = () => {
if (timeout !== null) {
clearTimeout(timeout);
timeout = null;
}
};
const finish = (value) => {
if (settled) return;
settled = true;
resolve(value === true);
clearSettlementTimeout();
resolve(value === true ? true : value === false ? false : null);
};
const fail = (error) => {
if (settled) return;
settled = true;
clearSettlementTimeout();
reject(error instanceof Error ? error : new Error(String(error || 'Permission request failed')));
};
const callback = (value) => {
@@ -98,6 +119,10 @@ function callBooleanPermissionMethod(chromeApi, methodName, request) {
finish(value);
};
if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
timeout = setTimeout(() => finish(null), timeoutMs);
}
try {
const result = method.call(chromeApi.permissions, request, callback);
if (result && typeof result.then === 'function') {
@@ -120,7 +145,8 @@ export function requestOriginPermission(chromeApi, originPattern) {
return callBooleanPermissionMethod(
chromeApi,
'request',
{ origins: [originPattern] }
{ origins: [originPattern] },
{ timeoutMs: 60000 }
).catch(() => false);
}
+55 -3
View File
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "Weiter",
"BTN_ONBOARDING_NEXT_TOOLTIP": "Zum nächsten Schritt gehen",
"ONBOARDING_1_TITLE": "Willkommen bei KoalaSync!",
"ONBOARDING_1_TEXT": "Schau Videos in perfekter Synchronität zusammen — egal wo ihr seid. Machen wir eine kurze Tour!",
"ONBOARDING_1_TEXT": "Schaut Videos gemeinsam, egal wo ihr seid. Hier ist eine kurze Einführung.",
"ONBOARDING_2_TITLE": "1. Raum erstellen",
"ONBOARDING_2_TEXT": "Hier geht's los. Erstelle einen Raum und teile den Einladungslink mit deinen Freunden.",
"ONBOARDING_3_TITLE": "2. Video auswählen",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Zeitüberschreitung — nicht alle Teilnehmer haben die Episode geladen",
"LOBBY_CANCEL_USER": "Vom Benutzer abgebrochen",
"NOTIF_ERROR_TITLE": "KoalaSync-Fehler",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "KoalaSync unterstützen",
"FOOTER_REVIEW": "★ Bewerten",
"FOOTER_SUPPORT_PROMPT": "Gefällt dir KoalaSync? Hinterlasse eine Bewertung!",
"LABEL_AUDIO_PROCESSING": "Audio-Verarbeitung",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "Profil & Aussehen",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "Ändere deinen Benutzernamen, dein Design und deine Spracheinstellungen.",
"LABEL_SETTINGS_GROUP_SYNC": "Wiedergabe & Sync",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Passe Wiedergabe-, Benachrichtigungs- und Audioeinstellungen an."
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Passe Wiedergabe-, Benachrichtigungs- und Audioeinstellungen an.",
"LABEL_CHAT_ENABLED": "Raum-Chat aktivieren",
"LABEL_CHAT_ENABLED_TOOLTIP": "Verschlüsselten Raum-Chat im ausgewählten Video-Tab anzeigen.",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Darstellung und Position des verschlüsselten Raum-Chats festlegen.",
"CHAT_SETTINGS_NOTE": "Optional und Ende-zu-Ende verschlüsselt. Nachrichten sind nur live sichtbar und werden vom Relay nicht gespeichert.",
"LABEL_CHAT_POSITION": "Position",
"LABEL_CHAT_POSITION_TOOLTIP": "Lege fest, wo der Chat am ausgewählten Player angezeigt wird.",
"LABEL_CHAT_SIZE": "Overlay-Größe",
"LABEL_CHAT_SIZE_TOOLTIP": "Wähle die Standardgröße des Chat-Overlays.",
"OPTION_CHAT_SIZE_COMPACT": "Kompakt",
"OPTION_CHAT_SIZE_STANDARD": "Standard",
"OPTION_CHAT_SIZE_LARGE": "Groß",
"OPTION_CHAT_SIZE_CUSTOM": "Letzte eigene Größe",
"LABEL_CHAT_START_MODE": "Startansicht",
"LABEL_CHAT_START_MODE_TOOLTIP": "Lege fest, ob der Chat als schwebende Blase oder geöffnetes Overlay startet.",
"OPTION_CHAT_START_BUBBLE": "Schwebende Chatblase",
"OPTION_CHAT_START_OPEN": "Geöffnetes Overlay",
"CHAT_TITLE": "Raum-Chat",
"CHAT_LIVE_ONLY": "Nur live. Kein Verlauf.",
"CHAT_OPEN": "Raum-Chat öffnen",
"CHAT_CLOSE": "Chat schließen",
"CHAT_DOCK_LEFT": "Chat links andocken",
"CHAT_DOCK_RIGHT": "Chat rechts andocken",
"CHAT_DETACHED": "Chat frei positionieren",
"CHAT_PLACEHOLDER": "Nachricht schreiben...",
"CHAT_SEND": "Senden",
"CHAT_MISSING_KEY": "Chat ist nur über einen aktuellen Einladungslink verfügbar.",
"CHAT_TOO_LONG": "Maximal 500 Zeichen.",
"CHAT_SEND_FAILED": "Nachricht konnte nicht gesendet werden.",
"CHAT_EMPTY": "Noch keine Nachrichten. Der Chat ist nur live.",
"LABEL_CHAT_NOTIFICATIONS": "Chat-Benachrichtigungen",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Zeigt neue Raum-Chatnachrichten als Systembenachrichtigung, wenn der Video-Tab nicht fokussiert ist.",
"LABEL_EVENT_NOTIFICATIONS": "Wiedergabe- & Raumbenachrichtigungen",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Zeigt Systembenachrichtigungen, wenn jemand beitritt, den Raum verlässt, abspielt oder pausiert.",
"DEBUG_SECTION_VIDEO_DETECTION": "Videoerkennung",
"DEBUG_VIDEO_NOT_FOUND": "KEIN VIDEOELEMENT GEFUNDEN",
"DEBUG_SECTION_MEDIA_SESSION": "Media Session API",
"DEBUG_VIDEO_HINT": "Läuft gerade ein Video? KoalaSync erkennt Browser-Videoelemente, nachdem das Medium geladen wurde.",
"DEBUG_SECTION_PLAYBACK": "Wiedergabe",
"DEBUG_STATE_PAUSED": "PAUSIERT",
"DEBUG_STATE_PLAYING": "LÄUFT",
"DEBUG_SECTION_PROPERTIES": "Eigenschaften",
"DEBUG_SECTION_DIMENSIONS": "Abmessungen",
"DEBUG_DIMENSIONS_HINT": "0×0 = Videoelement ist ausgeblendet oder noch nicht geladen",
"DEBUG_SECTION_MEDIA_ERROR": "Medienfehler",
"DEBUG_SECTION_DETECTION": "Erkennung",
"DEBUG_SECTION_IDENTIFICATION": "Identifikation",
"DEBUG_SECTION_MEDIA_SOURCE": "Medienquelle",
"DEBUG_SECTION_DATA_ATTRIBUTES": "Datenattribute",
"DEBUG_YES": "JA",
"DEBUG_NO": "NEIN",
"DEBUG_YES_CHECKED": "JA (geprüft)",
"DEBUG_NA": "k. A."
}
+54 -2
View File
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "Next",
"BTN_ONBOARDING_NEXT_TOOLTIP": "Go to next step",
"ONBOARDING_1_TITLE": "Welcome to KoalaSync!",
"ONBOARDING_1_TEXT": "Watch videos together in perfect sync — no matter where you are. Let's take a quick tour!",
"ONBOARDING_1_TEXT": "Watch videos together wherever you are. Here is a quick tour.",
"ONBOARDING_2_TITLE": "1. Create a Room",
"ONBOARDING_2_TEXT": "Start here. Create a room and share the invite link with your friends.",
"ONBOARDING_3_TITLE": "2. Select Video",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "Profile & Appearance",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "Change your username, theme, and language preferences.",
"LABEL_SETTINGS_GROUP_SYNC": "Playback & Sync",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Customize playback, notifications, and audio preferences."
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Customize playback, notifications, and audio preferences.",
"LABEL_CHAT_ENABLED": "Enable Room Chat",
"LABEL_CHAT_ENABLED_TOOLTIP": "Show encrypted room chat on the selected video tab.",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Configure encrypted room chat appearance and placement.",
"CHAT_SETTINGS_NOTE": "Optional and end-to-end encrypted. Messages are live only and are not stored by the relay.",
"LABEL_CHAT_POSITION": "Position",
"LABEL_CHAT_POSITION_TOOLTIP": "Choose where chat is attached to the selected player.",
"LABEL_CHAT_SIZE": "Overlay size",
"LABEL_CHAT_SIZE_TOOLTIP": "Choose the default size of the chat overlay.",
"OPTION_CHAT_SIZE_COMPACT": "Compact",
"OPTION_CHAT_SIZE_STANDARD": "Standard",
"OPTION_CHAT_SIZE_LARGE": "Large",
"OPTION_CHAT_SIZE_CUSTOM": "Last custom size",
"LABEL_CHAT_START_MODE": "Start as",
"LABEL_CHAT_START_MODE_TOOLTIP": "Choose whether chat starts as a floating bubble or an open overlay.",
"OPTION_CHAT_START_BUBBLE": "Floating bubble",
"OPTION_CHAT_START_OPEN": "Open overlay",
"CHAT_TITLE": "Room Chat",
"CHAT_LIVE_ONLY": "Live only. No history.",
"CHAT_OPEN": "Open room chat",
"CHAT_CLOSE": "Close chat",
"CHAT_DOCK_LEFT": "Dock chat left",
"CHAT_DOCK_RIGHT": "Dock chat right",
"CHAT_DETACHED": "Detach chat",
"CHAT_PLACEHOLDER": "Write a message...",
"CHAT_SEND": "Send",
"CHAT_MISSING_KEY": "Chat is available only through a current invite link.",
"CHAT_TOO_LONG": "Maximum 500 characters.",
"CHAT_SEND_FAILED": "Message could not be sent.",
"CHAT_EMPTY": "No messages yet. Chat is live only.",
"LABEL_CHAT_NOTIFICATIONS": "Chat Notifications",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Shows a system notification for new room-chat messages when the video tab is not focused.",
"LABEL_EVENT_NOTIFICATIONS": "Playback & Room Notifications",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Shows system notifications when someone joins, leaves, plays, or pauses.",
"DEBUG_SECTION_VIDEO_DETECTION": "Video Detection",
"DEBUG_VIDEO_NOT_FOUND": "NO VIDEO ELEMENT FOUND",
"DEBUG_SECTION_MEDIA_SESSION": "Media Session API",
"DEBUG_VIDEO_HINT": "Is a video currently playing? KoalaSync detects browser video elements after the media has loaded.",
"DEBUG_SECTION_PLAYBACK": "Playback",
"DEBUG_STATE_PAUSED": "PAUSED",
"DEBUG_STATE_PLAYING": "PLAYING",
"DEBUG_SECTION_PROPERTIES": "Properties",
"DEBUG_SECTION_DIMENSIONS": "Dimensions",
"DEBUG_DIMENSIONS_HINT": "0×0 = video element is hidden or not loaded yet",
"DEBUG_SECTION_MEDIA_ERROR": "Media Error",
"DEBUG_SECTION_DETECTION": "Detection",
"DEBUG_SECTION_IDENTIFICATION": "Identification",
"DEBUG_SECTION_MEDIA_SOURCE": "Media Source",
"DEBUG_SECTION_DATA_ATTRIBUTES": "Data Attributes",
"DEBUG_YES": "YES",
"DEBUG_NO": "NO",
"DEBUG_YES_CHECKED": "YES (checked)",
"DEBUG_NA": "n/a"
}
+55 -3
View File
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "Siguiente",
"BTN_ONBOARDING_NEXT_TOOLTIP": "Ir al siguiente paso",
"ONBOARDING_1_TITLE": "¡Bienvenido a KoalaSync!",
"ONBOARDING_1_TEXT": "Mira videos junto a tus amigos en perfecta sincronización, sin importar dónde estén. ¡Hagamos un recorrido rápido!",
"ONBOARDING_1_TEXT": "Ved vídeos juntos estéis donde estéis. Aquí tienes un recorrido rápido.",
"ONBOARDING_2_TITLE": "1. Crear una sala",
"ONBOARDING_2_TEXT": "Comienza aquí. Crea una sala y comparte el enlace de invitación con tus amigos.",
"ONBOARDING_3_TITLE": "2. Seleccionar video",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Tiempo de espera agotado: no todos los participantes cargaron el episodio",
"LOBBY_CANCEL_USER": "Cancelado por el usuario",
"NOTIF_ERROR_TITLE": "Error de KoalaSync",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "Apoyar KoalaSync",
"FOOTER_REVIEW": "★ Valorar",
"FOOTER_SUPPORT_PROMPT": "¿Te gusta KoalaSync? ¡Deja una reseña!",
"LABEL_AUDIO_PROCESSING": "Procesamiento de audio",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "Perfil y Apariencia",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "Cambia tu nombre de usuario, tema y preferencias de idioma.",
"LABEL_SETTINGS_GROUP_SYNC": "Reproducción y Sincronización",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personaliza la reproducción, las notificaciones y las preferencias de audio."
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personaliza la reproducción, las notificaciones y las preferencias de audio.",
"LABEL_CHAT_ENABLED": "Activar chat de la sala",
"LABEL_CHAT_ENABLED_TOOLTIP": "Mostrar el chat cifrado de la sala en la pestaña de vídeo seleccionada.",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Configura la apariencia y la posición del chat cifrado de la sala.",
"CHAT_SETTINGS_NOTE": "Opcional y cifrado de extremo a extremo. Los mensajes solo se muestran en directo y el relé no los almacena.",
"LABEL_CHAT_POSITION": "Posición",
"LABEL_CHAT_POSITION_TOOLTIP": "Elige dónde se acopla el chat al reproductor seleccionado.",
"LABEL_CHAT_SIZE": "Tamaño de la superposición",
"LABEL_CHAT_SIZE_TOOLTIP": "Elige el tamaño predeterminado de la superposición del chat.",
"OPTION_CHAT_SIZE_COMPACT": "Compacto",
"OPTION_CHAT_SIZE_STANDARD": "Estándar",
"OPTION_CHAT_SIZE_LARGE": "Grande",
"OPTION_CHAT_SIZE_CUSTOM": "Último tamaño personalizado",
"LABEL_CHAT_START_MODE": "Iniciar como",
"LABEL_CHAT_START_MODE_TOOLTIP": "Elige si el chat comienza como una burbuja flotante o una superposición abierta.",
"OPTION_CHAT_START_BUBBLE": "Burbuja flotante",
"OPTION_CHAT_START_OPEN": "Superposición abierta",
"CHAT_TITLE": "Chat de la sala",
"CHAT_LIVE_ONLY": "Solo en directo. Sin historial.",
"CHAT_OPEN": "Abrir chat de la sala",
"CHAT_CLOSE": "Cerrar chat",
"CHAT_DOCK_LEFT": "Acoplar chat a la izquierda",
"CHAT_DOCK_RIGHT": "Acoplar chat a la derecha",
"CHAT_DETACHED": "Separar chat",
"CHAT_PLACEHOLDER": "Escribe un mensaje...",
"CHAT_SEND": "Enviar",
"CHAT_MISSING_KEY": "El chat solo está disponible mediante un enlace de invitación actual.",
"CHAT_TOO_LONG": "Máximo 500 caracteres.",
"CHAT_SEND_FAILED": "No se pudo enviar el mensaje.",
"CHAT_EMPTY": "Aún no hay mensajes. El chat solo es en directo.",
"LABEL_CHAT_NOTIFICATIONS": "Notificaciones del chat",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Muestra una notificación del sistema para mensajes nuevos cuando la pestaña de vídeo no está activa.",
"LABEL_EVENT_NOTIFICATIONS": "Notificaciones de reproducción y sala",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Muestra notificaciones cuando alguien entra, sale, reproduce o pausa.",
"DEBUG_SECTION_VIDEO_DETECTION": "Detección de vídeo",
"DEBUG_VIDEO_NOT_FOUND": "NO SE ENCONTRÓ NINGÚN ELEMENTO DE VÍDEO",
"DEBUG_SECTION_MEDIA_SESSION": "API Media Session",
"DEBUG_VIDEO_HINT": "¿Hay un vídeo reproduciéndose? KoalaSync detecta elementos de vídeo cuando el contenido está cargado.",
"DEBUG_SECTION_PLAYBACK": "Reproducción",
"DEBUG_STATE_PAUSED": "EN PAUSA",
"DEBUG_STATE_PLAYING": "REPRODUCIENDO",
"DEBUG_SECTION_PROPERTIES": "Propiedades",
"DEBUG_SECTION_DIMENSIONS": "Dimensiones",
"DEBUG_DIMENSIONS_HINT": "0×0 = el elemento de vídeo está oculto o aún no se ha cargado",
"DEBUG_SECTION_MEDIA_ERROR": "Error multimedia",
"DEBUG_SECTION_DETECTION": "Detección",
"DEBUG_SECTION_IDENTIFICATION": "Identificación",
"DEBUG_SECTION_MEDIA_SOURCE": "Fuente multimedia",
"DEBUG_SECTION_DATA_ATTRIBUTES": "Atributos de datos",
"DEBUG_YES": "SÍ",
"DEBUG_NO": "NO",
"DEBUG_YES_CHECKED": "SÍ (comprobado)",
"DEBUG_NA": "n/d"
}
+55 -3
View File
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "Suivant",
"BTN_ONBOARDING_NEXT_TOOLTIP": "Étape suivante",
"ONBOARDING_1_TITLE": "Bienvenue sur KoalaSync !",
"ONBOARDING_1_TEXT": "Regardez des vidéos en parfaite synchronisation — où que vous soyez. Faisons un petit tour !",
"ONBOARDING_1_TEXT": "Regardez des vidéos ensemble où que vous soyez. Voici une présentation rapide.",
"ONBOARDING_2_TITLE": "1. Créer un salon",
"ONBOARDING_2_TEXT": "Commencez ici. Créez un salon et partagez le lien d'invitation avec vos amis.",
"ONBOARDING_3_TITLE": "2. Choisir une vidéo",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Délai dépassé — tous les membres n'ont pas chargé l'épisode",
"LOBBY_CANCEL_USER": "Annulé par l'utilisateur",
"NOTIF_ERROR_TITLE": "Erreur KoalaSync",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "Soutenir KoalaSync",
"FOOTER_REVIEW": "★ Évaluer",
"FOOTER_SUPPORT_PROMPT": "Tu aimes KoalaSync? Laisse un avis!",
"LABEL_AUDIO_PROCESSING": "Traitement audio",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "Profil & Apparence",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "Modifiez votre nom d'utilisateur, votre thème et vos préférences linguistiques.",
"LABEL_SETTINGS_GROUP_SYNC": "Lecture & Sync",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personnalisez la lecture, les notifications et les préférences audio."
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personnalisez la lecture, les notifications et les préférences audio.",
"LABEL_CHAT_ENABLED": "Activer le chat du salon",
"LABEL_CHAT_ENABLED_TOOLTIP": "Afficher le chat chiffré du salon dans l'onglet vidéo sélectionné.",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Configurez l'apparence et l'emplacement du chat chiffré du salon.",
"CHAT_SETTINGS_NOTE": "Facultatif et chiffré de bout en bout. Les messages sont uniquement en direct et ne sont pas stockés par le relais.",
"LABEL_CHAT_POSITION": "Position",
"LABEL_CHAT_POSITION_TOOLTIP": "Choisissez où le chat est ancré au lecteur sélectionné.",
"LABEL_CHAT_SIZE": "Taille de la fenêtre",
"LABEL_CHAT_SIZE_TOOLTIP": "Choisissez la taille par défaut de la fenêtre de chat.",
"OPTION_CHAT_SIZE_COMPACT": "Compacte",
"OPTION_CHAT_SIZE_STANDARD": "Standard",
"OPTION_CHAT_SIZE_LARGE": "Grande",
"OPTION_CHAT_SIZE_CUSTOM": "Dernière taille personnalisée",
"LABEL_CHAT_START_MODE": "Affichage initial",
"LABEL_CHAT_START_MODE_TOOLTIP": "Choisissez si le chat démarre sous forme de bulle flottante ou de fenêtre ouverte.",
"OPTION_CHAT_START_BUBBLE": "Bulle flottante",
"OPTION_CHAT_START_OPEN": "Fenêtre ouverte",
"CHAT_TITLE": "Chat du salon",
"CHAT_LIVE_ONLY": "En direct uniquement. Aucun historique.",
"CHAT_OPEN": "Ouvrir le chat du salon",
"CHAT_CLOSE": "Fermer le chat",
"CHAT_DOCK_LEFT": "Ancrer le chat à gauche",
"CHAT_DOCK_RIGHT": "Ancrer le chat à droite",
"CHAT_DETACHED": "Détacher le chat",
"CHAT_PLACEHOLDER": "Écrire un message...",
"CHAT_SEND": "Envoyer",
"CHAT_MISSING_KEY": "Le chat est disponible uniquement via un lien d'invitation actuel.",
"CHAT_TOO_LONG": "500 caractères maximum.",
"CHAT_SEND_FAILED": "Le message n'a pas pu être envoyé.",
"CHAT_EMPTY": "Aucun message. Le chat est uniquement en direct.",
"LABEL_CHAT_NOTIFICATIONS": "Notifications du chat",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Affiche une notification système pour les nouveaux messages lorsque longlet vidéo nest pas actif.",
"LABEL_EVENT_NOTIFICATIONS": "Notifications de lecture et de salon",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Affiche des notifications quand une personne arrive, part, lance ou met en pause.",
"DEBUG_SECTION_VIDEO_DETECTION": "Détection vidéo",
"DEBUG_VIDEO_NOT_FOUND": "AUCUN ÉLÉMENT VIDÉO DÉTECTÉ",
"DEBUG_SECTION_MEDIA_SESSION": "API Media Session",
"DEBUG_VIDEO_HINT": "Une vidéo est-elle en cours de lecture ? KoalaSync détecte les éléments vidéo une fois le média chargé.",
"DEBUG_SECTION_PLAYBACK": "Lecture",
"DEBUG_STATE_PAUSED": "EN PAUSE",
"DEBUG_STATE_PLAYING": "EN LECTURE",
"DEBUG_SECTION_PROPERTIES": "Propriétés",
"DEBUG_SECTION_DIMENSIONS": "Dimensions",
"DEBUG_DIMENSIONS_HINT": "0×0 = l’élément vidéo est masqué ou pas encore chargé",
"DEBUG_SECTION_MEDIA_ERROR": "Erreur multimédia",
"DEBUG_SECTION_DETECTION": "Détection",
"DEBUG_SECTION_IDENTIFICATION": "Identification",
"DEBUG_SECTION_MEDIA_SOURCE": "Source multimédia",
"DEBUG_SECTION_DATA_ATTRIBUTES": "Attributs de données",
"DEBUG_YES": "OUI",
"DEBUG_NO": "NON",
"DEBUG_YES_CHECKED": "OUI (vérifié)",
"DEBUG_NA": "n/d"
}
+56 -4
View File
@@ -34,7 +34,7 @@
"BTN_REFRESH_TOOLTIP": "Aggiorna l'elenco delle stanze",
"PUBLIC_ROOMS_REFRESHING": "Aggiornamento...",
"BTN_REFRESH_COOLDOWN": "ATTENDI {seconds}s",
"BTN_REFRESH_COOLDOWN_TOOLTIP": "Riprova tra {seconds}s.",
"BTN_REFRESH_COOLDOWN_TOOLTIP": "Lelenco delle stanze è in attesa. Riprova tra {seconds}s.",
"PUBLIC_ROOMS_REFRESHING_COOLDOWN": "Aggiornamento in corso. Disponibile tra {seconds}s.",
"LABEL_ACTIVE_ROOM": "Stanza Attiva",
"LABEL_ACTIVE_ROOM_TOOLTIP": "La stanza a cui sei connesso",
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "Avanti",
"BTN_ONBOARDING_NEXT_TOOLTIP": "Vai al passaggio successivo",
"ONBOARDING_1_TITLE": "Benvenuto in KoalaSync!",
"ONBOARDING_1_TEXT": "Guarda i video insieme ai tuoi amici in perfetta sincronia. Facciamo un breve tour!",
"ONBOARDING_1_TEXT": "Guarda video insieme ovunque vi troviate. Ecco una breve panoramica.",
"ONBOARDING_2_TITLE": "1. Crea una Stanza",
"ONBOARDING_2_TEXT": "Inizia da qui. Crea una stanza e condividi il link con i tuoi amici.",
"ONBOARDING_3_TITLE": "2. Seleziona Video",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Tempo scaduto: caricamento incompleto per alcuni partecipanti",
"LOBBY_CANCEL_USER": "Annullato dall'utente",
"NOTIF_ERROR_TITLE": "Errore KoalaSync",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "Sostieni KoalaSync",
"FOOTER_REVIEW": "★ Valutaci",
"FOOTER_SUPPORT_PROMPT": "Ti piace KoalaSync? Lascia una recensione!",
"LABEL_AUDIO_PROCESSING": "Elaborazione Audio",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "Profilo & Aspetto",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "Modifica il tuo nome utente, tema e preferenze di lingua.",
"LABEL_SETTINGS_GROUP_SYNC": "Riproduzione & Sync",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personalizza la riproduzione, le notifiche e le preferenze audio."
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personalizza la riproduzione, le notifiche e le preferenze audio.",
"LABEL_CHAT_ENABLED": "Attiva chat della stanza",
"LABEL_CHAT_ENABLED_TOOLTIP": "Mostra la chat crittografata della stanza nella scheda video selezionata.",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Configura l'aspetto e la posizione della chat crittografata della stanza.",
"CHAT_SETTINGS_NOTE": "Opzionale e crittografata end-to-end. I messaggi sono solo in diretta e non vengono memorizzati dal relay.",
"LABEL_CHAT_POSITION": "Posizione",
"LABEL_CHAT_POSITION_TOOLTIP": "Scegli dove agganciare la chat al lettore selezionato.",
"LABEL_CHAT_SIZE": "Dimensione overlay",
"LABEL_CHAT_SIZE_TOOLTIP": "Scegli la dimensione predefinita dell'overlay della chat.",
"OPTION_CHAT_SIZE_COMPACT": "Compatto",
"OPTION_CHAT_SIZE_STANDARD": "Standard",
"OPTION_CHAT_SIZE_LARGE": "Grande",
"OPTION_CHAT_SIZE_CUSTOM": "Ultima dimensione personalizzata",
"LABEL_CHAT_START_MODE": "Avvio come",
"LABEL_CHAT_START_MODE_TOOLTIP": "Scegli se la chat si avvia come bolla mobile o overlay aperto.",
"OPTION_CHAT_START_BUBBLE": "Bolla fluttuante",
"OPTION_CHAT_START_OPEN": "Overlay aperto",
"CHAT_TITLE": "Chat della stanza",
"CHAT_LIVE_ONLY": "Solo in diretta. Nessuna cronologia.",
"CHAT_OPEN": "Apri la chat della stanza",
"CHAT_CLOSE": "Chiudi chat",
"CHAT_DOCK_LEFT": "Aggancia chat a sinistra",
"CHAT_DOCK_RIGHT": "Aggancia chat a destra",
"CHAT_DETACHED": "Sgancia chat",
"CHAT_PLACEHOLDER": "Scrivi un messaggio...",
"CHAT_SEND": "Invia",
"CHAT_MISSING_KEY": "La chat è disponibile solo tramite un link di invito attuale.",
"CHAT_TOO_LONG": "Massimo 500 caratteri.",
"CHAT_SEND_FAILED": "Impossibile inviare il messaggio.",
"CHAT_EMPTY": "Nessun messaggio. La chat è solo in diretta.",
"LABEL_CHAT_NOTIFICATIONS": "Notifiche chat",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Mostra una notifica di sistema per i nuovi messaggi quando la scheda video non è attiva.",
"LABEL_EVENT_NOTIFICATIONS": "Notifiche di riproduzione e stanza",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Mostra notifiche quando qualcuno entra, esce, avvia o mette in pausa.",
"DEBUG_SECTION_VIDEO_DETECTION": "Rilevamento video",
"DEBUG_VIDEO_NOT_FOUND": "NESSUN ELEMENTO VIDEO TROVATO",
"DEBUG_SECTION_MEDIA_SESSION": "API Media Session",
"DEBUG_VIDEO_HINT": "È in riproduzione un video? KoalaSync rileva gli elementi video dopo il caricamento del contenuto.",
"DEBUG_SECTION_PLAYBACK": "Riproduzione",
"DEBUG_STATE_PAUSED": "IN PAUSA",
"DEBUG_STATE_PLAYING": "IN RIPRODUZIONE",
"DEBUG_SECTION_PROPERTIES": "Proprietà",
"DEBUG_SECTION_DIMENSIONS": "Dimensioni",
"DEBUG_DIMENSIONS_HINT": "0×0 = elemento video nascosto o non ancora caricato",
"DEBUG_SECTION_MEDIA_ERROR": "Errore multimediale",
"DEBUG_SECTION_DETECTION": "Rilevamento",
"DEBUG_SECTION_IDENTIFICATION": "Identificazione",
"DEBUG_SECTION_MEDIA_SOURCE": "Sorgente multimediale",
"DEBUG_SECTION_DATA_ATTRIBUTES": "Attributi dati",
"DEBUG_YES": "SÌ",
"DEBUG_NO": "NO",
"DEBUG_YES_CHECKED": "SÌ (verificato)",
"DEBUG_NA": "n/d"
}
+55 -3
View File
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "次へ",
"BTN_ONBOARDING_NEXT_TOOLTIP": "次のステップに進む",
"ONBOARDING_1_TITLE": "KoalaSyncへようこそ!",
"ONBOARDING_1_TEXT": "どこにいても、完璧に同期してビデオを一緒に楽しめます。簡単なツアーを始めましょう!",
"ONBOARDING_1_TEXT": "離れた場所から一緒に動画を視聴できます。簡単に機能を紹介します。",
"ONBOARDING_2_TITLE": "1. ルームの作成",
"ONBOARDING_2_TEXT": "ここからスタート。ルームを作成し、招待リンクを友達に共有します。",
"ONBOARDING_3_TITLE": "2. ビデオの選択",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "タイムアウト — 一部のメンバーがエピソードを読み込めませんでした",
"LOBBY_CANCEL_USER": "ユーザーによってキャンセルされました",
"NOTIF_ERROR_TITLE": "KoalaSyncエラー",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "KoalaSyncを支援",
"FOOTER_REVIEW": "★ 評価する",
"FOOTER_SUPPORT_PROMPT": "KoalaSyncはいかが?レビューを書いてください!",
"LABEL_AUDIO_PROCESSING": "オーディオ処理",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "プロフィールと外観",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "ユーザー名、テーマ、言語設定を変更します。",
"LABEL_SETTINGS_GROUP_SYNC": "再生と同期",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "再生、通知、オーディオ設定をカスタマイズします。"
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "再生、通知、オーディオ設定をカスタマイズします。",
"LABEL_CHAT_ENABLED": "ルームチャットを有効化",
"LABEL_CHAT_ENABLED_TOOLTIP": "選択した動画タブに暗号化されたルームチャットを表示します。",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "暗号化されたルームチャットの外観と位置を設定します。",
"CHAT_SETTINGS_NOTE": "任意で利用でき、エンドツーエンドで暗号化されます。メッセージはリアルタイムのみで、リレーには保存されません。",
"LABEL_CHAT_POSITION": "位置",
"LABEL_CHAT_POSITION_TOOLTIP": "選択したプレーヤーのどこにチャットを配置するか選択します。",
"LABEL_CHAT_SIZE": "オーバーレイサイズ",
"LABEL_CHAT_SIZE_TOOLTIP": "チャットオーバーレイの標準サイズを選択します。",
"OPTION_CHAT_SIZE_COMPACT": "コンパクト",
"OPTION_CHAT_SIZE_STANDARD": "標準",
"OPTION_CHAT_SIZE_LARGE": "大",
"OPTION_CHAT_SIZE_CUSTOM": "前回のカスタムサイズ",
"LABEL_CHAT_START_MODE": "開始表示",
"LABEL_CHAT_START_MODE_TOOLTIP": "フローティングバブルまたは開いたオーバーレイで開始するか選択します。",
"OPTION_CHAT_START_BUBBLE": "フローティングバブル",
"OPTION_CHAT_START_OPEN": "開いたオーバーレイ",
"CHAT_TITLE": "ルームチャット",
"CHAT_LIVE_ONLY": "ライブのみ。履歴はありません。",
"CHAT_OPEN": "ルームチャットを開く",
"CHAT_CLOSE": "チャットを閉じる",
"CHAT_DOCK_LEFT": "チャットを左に固定",
"CHAT_DOCK_RIGHT": "チャットを右に固定",
"CHAT_DETACHED": "チャットを切り離す",
"CHAT_PLACEHOLDER": "メッセージを入力...",
"CHAT_SEND": "送信",
"CHAT_MISSING_KEY": "チャットは最新の招待リンクからのみ利用できます。",
"CHAT_TOO_LONG": "最大500文字です。",
"CHAT_SEND_FAILED": "メッセージを送信できませんでした。",
"CHAT_EMPTY": "まだメッセージはありません。チャットはライブのみです。",
"LABEL_CHAT_NOTIFICATIONS": "チャット通知",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "動画タブがアクティブでないとき、新しいルームチャットをシステム通知で表示します。",
"LABEL_EVENT_NOTIFICATIONS": "再生とルームの通知",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "参加、退出、再生、一時停止をシステム通知で表示します。",
"DEBUG_SECTION_VIDEO_DETECTION": "動画検出",
"DEBUG_VIDEO_NOT_FOUND": "動画要素が見つかりません",
"DEBUG_SECTION_MEDIA_SESSION": "Media Session API",
"DEBUG_VIDEO_HINT": "動画を再生していますか?メディアの読み込み後にKoalaSyncが動画要素を検出します。",
"DEBUG_SECTION_PLAYBACK": "再生",
"DEBUG_STATE_PAUSED": "一時停止",
"DEBUG_STATE_PLAYING": "再生中",
"DEBUG_SECTION_PROPERTIES": "プロパティ",
"DEBUG_SECTION_DIMENSIONS": "サイズ",
"DEBUG_DIMENSIONS_HINT": "0×0 = 動画要素が非表示、または未読み込みです",
"DEBUG_SECTION_MEDIA_ERROR": "メディアエラー",
"DEBUG_SECTION_DETECTION": "検出",
"DEBUG_SECTION_IDENTIFICATION": "識別情報",
"DEBUG_SECTION_MEDIA_SOURCE": "メディアソース",
"DEBUG_SECTION_DATA_ATTRIBUTES": "データ属性",
"DEBUG_YES": "はい",
"DEBUG_NO": "いいえ",
"DEBUG_YES_CHECKED": "はい(確認済み)",
"DEBUG_NA": "なし"
}
+55 -3
View File
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "다음",
"BTN_ONBOARDING_NEXT_TOOLTIP": "다음 단계로 이동",
"ONBOARDING_1_TITLE": "KoalaSync에 오신 것을 환영합니다!",
"ONBOARDING_1_TEXT": "어디에 있든 완벽하게 동기화된 비디오를 함께 시청하세요. 간단히 둘러볼까요!",
"ONBOARDING_1_TEXT": "어디서든 함께 비디오를 시청하세요. 주요 기능을 간단히 살펴봅니다.",
"ONBOARDING_2_TITLE": "1. 방 만들기",
"ONBOARDING_2_TEXT": "여기서 시작하세요. 방을 만들고 친구들에게 초대 링크를 공유하세요.",
"ONBOARDING_3_TITLE": "2. 비디오 선택",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "시간 초과 — 모든 피어가 에피소드를 로드하지 못했습니다",
"LOBBY_CANCEL_USER": "사용자에 의해 취소됨",
"NOTIF_ERROR_TITLE": "KoalaSync 오류",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "KoalaSync 후원",
"FOOTER_REVIEW": "★ 평가하기",
"FOOTER_SUPPORT_PROMPT": "KoalaSync가 마음에 드세요? 리뷰를 남겨주세요!",
"LABEL_AUDIO_PROCESSING": "오디오 처리",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "프로필 및 테마",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "사용자 이름, 테마 및 언어 설정을 변경합니다.",
"LABEL_SETTINGS_GROUP_SYNC": "재생 및 동기화",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "재생, 알림 및 오디오 설정을 맞춤설정합니다."
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "재생, 알림 및 오디오 설정을 맞춤설정합니다.",
"LABEL_CHAT_ENABLED": "방 채팅 사용",
"LABEL_CHAT_ENABLED_TOOLTIP": "선택한 동영상 탭에 암호화된 방 채팅을 표시합니다.",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "암호화된 방 채팅의 모양과 위치를 설정합니다.",
"CHAT_SETTINGS_NOTE": "선택 기능이며 종단 간 암호화됩니다. 메시지는 실시간으로만 표시되고 릴레이에 저장되지 않습니다.",
"LABEL_CHAT_POSITION": "위치",
"LABEL_CHAT_POSITION_TOOLTIP": "선택한 플레이어에서 채팅을 고정할 위치를 선택합니다.",
"LABEL_CHAT_SIZE": "오버레이 크기",
"LABEL_CHAT_SIZE_TOOLTIP": "채팅 오버레이의 기본 크기를 선택합니다.",
"OPTION_CHAT_SIZE_COMPACT": "작게",
"OPTION_CHAT_SIZE_STANDARD": "표준",
"OPTION_CHAT_SIZE_LARGE": "크게",
"OPTION_CHAT_SIZE_CUSTOM": "마지막 사용자 지정 크기",
"LABEL_CHAT_START_MODE": "시작 화면",
"LABEL_CHAT_START_MODE_TOOLTIP": "플로팅 버블 또는 열린 오버레이로 시작할지 선택합니다.",
"OPTION_CHAT_START_BUBBLE": "플로팅 채팅 버블",
"OPTION_CHAT_START_OPEN": "열린 오버레이",
"CHAT_TITLE": "방 채팅",
"CHAT_LIVE_ONLY": "실시간 전용. 기록 없음.",
"CHAT_OPEN": "방 채팅 열기",
"CHAT_CLOSE": "채팅 닫기",
"CHAT_DOCK_LEFT": "채팅을 왼쪽에 고정",
"CHAT_DOCK_RIGHT": "채팅을 오른쪽에 고정",
"CHAT_DETACHED": "채팅 분리",
"CHAT_PLACEHOLDER": "메시지를 입력하세요...",
"CHAT_SEND": "보내기",
"CHAT_MISSING_KEY": "채팅은 최신 초대 링크를 통해서만 사용할 수 있습니다.",
"CHAT_TOO_LONG": "최대 500자입니다.",
"CHAT_SEND_FAILED": "메시지를 보낼 수 없습니다.",
"CHAT_EMPTY": "아직 메시지가 없습니다. 채팅은 실시간 전용입니다.",
"LABEL_CHAT_NOTIFICATIONS": "채팅 알림",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "비디오 탭이 활성 상태가 아닐 때 새 방 채팅 메시지를 시스템 알림으로 표시합니다.",
"LABEL_EVENT_NOTIFICATIONS": "재생 및 방 알림",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "참여, 퇴장, 재생 또는 일시정지를 시스템 알림으로 표시합니다.",
"DEBUG_SECTION_VIDEO_DETECTION": "비디오 감지",
"DEBUG_VIDEO_NOT_FOUND": "비디오 요소를 찾지 못했습니다",
"DEBUG_SECTION_MEDIA_SESSION": "Media Session API",
"DEBUG_VIDEO_HINT": "비디오가 재생 중인가요? 미디어가 로드된 후 KoalaSync가 비디오 요소를 감지합니다.",
"DEBUG_SECTION_PLAYBACK": "재생",
"DEBUG_STATE_PAUSED": "일시정지",
"DEBUG_STATE_PLAYING": "재생 중",
"DEBUG_SECTION_PROPERTIES": "속성",
"DEBUG_SECTION_DIMENSIONS": "크기",
"DEBUG_DIMENSIONS_HINT": "0×0 = 비디오 요소가 숨겨져 있거나 아직 로드되지 않음",
"DEBUG_SECTION_MEDIA_ERROR": "미디어 오류",
"DEBUG_SECTION_DETECTION": "감지",
"DEBUG_SECTION_IDENTIFICATION": "식별 정보",
"DEBUG_SECTION_MEDIA_SOURCE": "미디어 소스",
"DEBUG_SECTION_DATA_ATTRIBUTES": "데이터 속성",
"DEBUG_YES": "예",
"DEBUG_NO": "아니요",
"DEBUG_YES_CHECKED": "예(확인됨)",
"DEBUG_NA": "없음"
}
+56 -4
View File
@@ -82,7 +82,7 @@
"LABEL_LAST_ACTIVITY": "Laatste activiteitsstatus",
"LABEL_LAST_ACTIVITY_TOOLTIP": "Toont de meest recente afspeel-, pauzeer- of zoekopdracht",
"NO_RECENT_COMMANDS": "Geen recente commando's",
"LOBBY_HEADER": "EPISODE LOBBY",
"LOBBY_HEADER": "AFLEVERINGSLOBBY",
"LOBBY_WAITING_FOR": "🎬 Wachten op: \"{title}\"",
"LOBBY_WAITING_PEERS": "Wachten op deelnemers...",
"BTN_SKIP_PLAY": "Overslaan & toch afspelen",
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "Volgende",
"BTN_ONBOARDING_NEXT_TOOLTIP": "Ga naar de volgende stap",
"ONBOARDING_1_TITLE": "Welkom bij KoalaSync!",
"ONBOARDING_1_TEXT": "Bekijk video's samen in perfecte synchronisatie — waar u ook bent. Laten we een snelle rondleiding nemen!",
"ONBOARDING_1_TEXT": "Bekijk samen videos, waar je ook bent. Hier volgt een korte rondleiding.",
"ONBOARDING_2_TITLE": "1. Kamer maken",
"ONBOARDING_2_TEXT": "Begin hier. Maak een kamer en deel de uitnodigingslink met uw vrienden.",
"ONBOARDING_3_TITLE": "2. Selecteer video",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Time-out — niet alle deelnemers hebben de aflevering geladen",
"LOBBY_CANCEL_USER": "Geannuleerd door gebruiker",
"NOTIF_ERROR_TITLE": "KoalaSync-fout",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "Steun KoalaSync",
"FOOTER_REVIEW": "★ Beoordelen",
"FOOTER_SUPPORT_PROMPT": "Vind je KoalaSync leuk? Laat een review achter!",
"LABEL_AUDIO_PROCESSING": "Audioverwerking",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "Profiel & Weergave",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "Wijzig uw gebruikersnaam, thema en taalvoorkeuren.",
"LABEL_SETTINGS_GROUP_SYNC": "Afspelen & Sync",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Pas afspelen, meldingen en audiovoorkeuren aan."
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Pas afspelen, meldingen en audiovoorkeuren aan.",
"LABEL_CHAT_ENABLED": "Kamerchat inschakelen",
"LABEL_CHAT_ENABLED_TOOLTIP": "Versleutelde kamerchat tonen in het geselecteerde videotabblad.",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Pas het uiterlijk en de positie van de versleutelde kamerchat aan.",
"CHAT_SETTINGS_NOTE": "Optioneel en end-to-end versleuteld. Berichten zijn alleen live en worden niet door de relay opgeslagen.",
"LABEL_CHAT_POSITION": "Positie",
"LABEL_CHAT_POSITION_TOOLTIP": "Kies waar de chat aan de geselecteerde speler wordt gekoppeld.",
"LABEL_CHAT_SIZE": "Overlaygrootte",
"LABEL_CHAT_SIZE_TOOLTIP": "Kies de standaardgrootte van de chatoverlay.",
"OPTION_CHAT_SIZE_COMPACT": "Compact",
"OPTION_CHAT_SIZE_STANDARD": "Standaard",
"OPTION_CHAT_SIZE_LARGE": "Groot",
"OPTION_CHAT_SIZE_CUSTOM": "Laatste aangepaste grootte",
"LABEL_CHAT_START_MODE": "Start als",
"LABEL_CHAT_START_MODE_TOOLTIP": "Kies of de chat start als zwevende knop of als geopende overlay.",
"OPTION_CHAT_START_BUBBLE": "Zwevende chatknop",
"OPTION_CHAT_START_OPEN": "Geopende overlay",
"CHAT_TITLE": "Kamerchat",
"CHAT_LIVE_ONLY": "Alleen live. Geen geschiedenis.",
"CHAT_OPEN": "Kamerchat openen",
"CHAT_CLOSE": "Chat sluiten",
"CHAT_DOCK_LEFT": "Chat links vastzetten",
"CHAT_DOCK_RIGHT": "Chat rechts vastzetten",
"CHAT_DETACHED": "Chat losmaken",
"CHAT_PLACEHOLDER": "Schrijf een bericht...",
"CHAT_SEND": "Verzenden",
"CHAT_MISSING_KEY": "Chat is alleen beschikbaar via een actuele uitnodigingslink.",
"CHAT_TOO_LONG": "Maximaal 500 tekens.",
"CHAT_SEND_FAILED": "Bericht kon niet worden verzonden.",
"CHAT_EMPTY": "Nog geen berichten. Chat is alleen live.",
"LABEL_CHAT_NOTIFICATIONS": "Chatmeldingen",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Toont een systeemmelding voor nieuwe chatberichten wanneer het videotabblad niet actief is.",
"LABEL_EVENT_NOTIFICATIONS": "Afspeel- en kamermeldingen",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Toont meldingen wanneer iemand deelneemt, vertrekt, afspeelt of pauzeert.",
"DEBUG_SECTION_VIDEO_DETECTION": "Videodetectie",
"DEBUG_VIDEO_NOT_FOUND": "GEEN VIDEO-ELEMENT GEVONDEN",
"DEBUG_SECTION_MEDIA_SESSION": "Media Session API",
"DEBUG_VIDEO_HINT": "Wordt er een video afgespeeld? KoalaSync detecteert video-elementen nadat de media is geladen.",
"DEBUG_SECTION_PLAYBACK": "Afspelen",
"DEBUG_STATE_PAUSED": "GEPAUZEERD",
"DEBUG_STATE_PLAYING": "WORDT AFGESPEELD",
"DEBUG_SECTION_PROPERTIES": "Eigenschappen",
"DEBUG_SECTION_DIMENSIONS": "Afmetingen",
"DEBUG_DIMENSIONS_HINT": "0×0 = video-element is verborgen of nog niet geladen",
"DEBUG_SECTION_MEDIA_ERROR": "Mediafout",
"DEBUG_SECTION_DETECTION": "Detectie",
"DEBUG_SECTION_IDENTIFICATION": "Identificatie",
"DEBUG_SECTION_MEDIA_SOURCE": "Mediabron",
"DEBUG_SECTION_DATA_ATTRIBUTES": "Data-attributen",
"DEBUG_YES": "JA",
"DEBUG_NO": "NEE",
"DEBUG_YES_CHECKED": "JA (gecontroleerd)",
"DEBUG_NA": "n.v.t."
}
+55 -3
View File
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "Dalej",
"BTN_ONBOARDING_NEXT_TOOLTIP": "Przejdź do następnego kroku",
"ONBOARDING_1_TITLE": "Witaj w KoalaSync!",
"ONBOARDING_1_TEXT": "Oglądaj filmy w idealnej synchronizacji — bez względu na to, gdzie jesteś. Zróbmy szybką wycieczkę!",
"ONBOARDING_1_TEXT": "Oglądajcie filmy razem, gdziekolwiek jesteście. Oto krótkie wprowadzenie.",
"ONBOARDING_2_TITLE": "1. Utwórz pokój",
"ONBOARDING_2_TEXT": "Zacznij tutaj. Utwórz pokój i udostępnij link zaproszenia znajomym.",
"ONBOARDING_3_TITLE": "2. Wybierz wideo",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Limit czasu — nie wszyscy uczestnicy załadowali odcinek",
"LOBBY_CANCEL_USER": "Anulowane przez użytkownika",
"NOTIF_ERROR_TITLE": "Błąd KoalaSync",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "Wesprzyj KoalaSync",
"FOOTER_REVIEW": "★ Oceń",
"FOOTER_SUPPORT_PROMPT": "Podoba Ci się KoalaSync? Zostaw recenzję!",
"LABEL_AUDIO_PROCESSING": "Przetwarzanie dźwięku",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "Profil i Wygląd",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "Zmień swoją nazwę użytkownika, motyw i preferencje językowe.",
"LABEL_SETTINGS_GROUP_SYNC": "Odtwarzanie i Sync",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Dostosuj odtwarzanie, powiadomienia i preferencje audio."
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Dostosuj odtwarzanie, powiadomienia i preferencje audio.",
"LABEL_CHAT_ENABLED": "Włącz czat pokoju",
"LABEL_CHAT_ENABLED_TOOLTIP": "Pokaż zaszyfrowany czat pokoju na wybranej karcie wideo.",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Skonfiguruj wygląd i położenie zaszyfrowanego czatu pokoju.",
"CHAT_SETTINGS_NOTE": "Opcjonalny i szyfrowany od końca do końca. Wiadomości są tylko na żywo i nie są przechowywane przez serwer pośredniczący.",
"LABEL_CHAT_POSITION": "Położenie",
"LABEL_CHAT_POSITION_TOOLTIP": "Wybierz, gdzie czat ma być przypięty do wybranego odtwarzacza.",
"LABEL_CHAT_SIZE": "Rozmiar nakładki",
"LABEL_CHAT_SIZE_TOOLTIP": "Wybierz domyślny rozmiar nakładki czatu.",
"OPTION_CHAT_SIZE_COMPACT": "Kompaktowy",
"OPTION_CHAT_SIZE_STANDARD": "Standardowy",
"OPTION_CHAT_SIZE_LARGE": "Duży",
"OPTION_CHAT_SIZE_CUSTOM": "Ostatni własny rozmiar",
"LABEL_CHAT_START_MODE": "Uruchom jako",
"LABEL_CHAT_START_MODE_TOOLTIP": "Wybierz, czy czat ma startować jako pływający przycisk, czy otwarta nakładka.",
"OPTION_CHAT_START_BUBBLE": "Pływający przycisk",
"OPTION_CHAT_START_OPEN": "Otwarta nakładka",
"CHAT_TITLE": "Czat pokoju",
"CHAT_LIVE_ONLY": "Tylko na żywo. Bez historii.",
"CHAT_OPEN": "Otwórz czat pokoju",
"CHAT_CLOSE": "Zamknij czat",
"CHAT_DOCK_LEFT": "Przypnij czat po lewej",
"CHAT_DOCK_RIGHT": "Przypnij czat po prawej",
"CHAT_DETACHED": "Odłącz czat",
"CHAT_PLACEHOLDER": "Napisz wiadomość...",
"CHAT_SEND": "Wyślij",
"CHAT_MISSING_KEY": "Czat jest dostępny tylko przez aktualny link zaproszenia.",
"CHAT_TOO_LONG": "Maksymalnie 500 znaków.",
"CHAT_SEND_FAILED": "Nie udało się wysłać wiadomości.",
"CHAT_EMPTY": "Brak wiadomości. Czat działa tylko na żywo.",
"LABEL_CHAT_NOTIFICATIONS": "Powiadomienia czatu",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Pokazuje powiadomienie systemowe o nowej wiadomości, gdy karta wideo nie jest aktywna.",
"LABEL_EVENT_NOTIFICATIONS": "Powiadomienia o odtwarzaniu i pokoju",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Pokazuje powiadomienia, gdy ktoś dołącza, wychodzi, odtwarza lub wstrzymuje.",
"DEBUG_SECTION_VIDEO_DETECTION": "Wykrywanie wideo",
"DEBUG_VIDEO_NOT_FOUND": "NIE ZNALEZIONO ELEMENTU WIDEO",
"DEBUG_SECTION_MEDIA_SESSION": "API Media Session",
"DEBUG_VIDEO_HINT": "Czy wideo jest odtwarzane? KoalaSync wykrywa elementy wideo po załadowaniu multimediów.",
"DEBUG_SECTION_PLAYBACK": "Odtwarzanie",
"DEBUG_STATE_PAUSED": "WSTRZYMANE",
"DEBUG_STATE_PLAYING": "ODTWARZANIE",
"DEBUG_SECTION_PROPERTIES": "Właściwości",
"DEBUG_SECTION_DIMENSIONS": "Wymiary",
"DEBUG_DIMENSIONS_HINT": "0×0 = element wideo jest ukryty lub jeszcze niezaładowany",
"DEBUG_SECTION_MEDIA_ERROR": "Błąd multimediów",
"DEBUG_SECTION_DETECTION": "Wykrywanie",
"DEBUG_SECTION_IDENTIFICATION": "Identyfikacja",
"DEBUG_SECTION_MEDIA_SOURCE": "Źródło multimediów",
"DEBUG_SECTION_DATA_ATTRIBUTES": "Atrybuty danych",
"DEBUG_YES": "TAK",
"DEBUG_NO": "NIE",
"DEBUG_YES_CHECKED": "TAK (sprawdzono)",
"DEBUG_NA": "brak"
}
+55 -3
View File
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "Próximo",
"BTN_ONBOARDING_NEXT_TOOLTIP": "Ir para o próximo passo",
"ONBOARDING_1_TITLE": "Bem-vindo ao KoalaSync!",
"ONBOARDING_1_TEXT": "Assista a vídeos com seus amigos em perfeita sincronia, não importa onde estejam. Vamos fazer um tour rápido!",
"ONBOARDING_1_TEXT": "Assista a vídeos com outras pessoas, onde vocês estiverem. Veja uma apresentação rápida.",
"ONBOARDING_2_TITLE": "1. Criar uma sala",
"ONBOARDING_2_TEXT": "Comece aqui. Crie uma sala e compartilhe o link de convite com seus amigos.",
"ONBOARDING_3_TITLE": "2. Selecionar vídeo",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Tempo limite — nem todos carregaram o episódio",
"LOBBY_CANCEL_USER": "Cancelado pelo usuário",
"NOTIF_ERROR_TITLE": "Erro no KoalaSync",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "Apoie o KoalaSync",
"FOOTER_REVIEW": "★ Avaliar",
"FOOTER_SUPPORT_PROMPT": "Gostou do KoalaSync? Deixe uma avaliação!",
"LABEL_AUDIO_PROCESSING": "Processamento de áudio",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "Perfil e Aparência",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "Altere o seu nome de usuário, tema e preferências de idioma.",
"LABEL_SETTINGS_GROUP_SYNC": "Reprodução e Sincronização",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personalize a reprodução, as notificações e as preferências de áudio."
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personalize a reprodução, as notificações e as preferências de áudio.",
"LABEL_CHAT_ENABLED": "Ativar chat da sala",
"LABEL_CHAT_ENABLED_TOOLTIP": "Mostrar o chat criptografado da sala na guia de vídeo selecionada.",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Configure a aparência e a posição do chat criptografado da sala.",
"CHAT_SETTINGS_NOTE": "Opcional e criptografado de ponta a ponta. As mensagens são apenas ao vivo e não são armazenadas pelo relay.",
"LABEL_CHAT_POSITION": "Posição",
"LABEL_CHAT_POSITION_TOOLTIP": "Escolha onde o chat fica fixado no player selecionado.",
"LABEL_CHAT_SIZE": "Tamanho da sobreposição",
"LABEL_CHAT_SIZE_TOOLTIP": "Escolha o tamanho padrão da sobreposição do chat.",
"OPTION_CHAT_SIZE_COMPACT": "Compacto",
"OPTION_CHAT_SIZE_STANDARD": "Padrão",
"OPTION_CHAT_SIZE_LARGE": "Grande",
"OPTION_CHAT_SIZE_CUSTOM": "Último tamanho personalizado",
"LABEL_CHAT_START_MODE": "Iniciar como",
"LABEL_CHAT_START_MODE_TOOLTIP": "Escolha se o chat começa como uma bolha flutuante ou uma sobreposição aberta.",
"OPTION_CHAT_START_BUBBLE": "Bolha flutuante",
"OPTION_CHAT_START_OPEN": "Sobreposição aberta",
"CHAT_TITLE": "Chat da sala",
"CHAT_LIVE_ONLY": "Somente ao vivo. Sem histórico.",
"CHAT_OPEN": "Abrir chat da sala",
"CHAT_CLOSE": "Fechar chat",
"CHAT_DOCK_LEFT": "Fixar chat à esquerda",
"CHAT_DOCK_RIGHT": "Fixar chat à direita",
"CHAT_DETACHED": "Desanexar chat",
"CHAT_PLACEHOLDER": "Escreva uma mensagem...",
"CHAT_SEND": "Enviar",
"CHAT_MISSING_KEY": "O chat está disponível apenas por um link de convite atual.",
"CHAT_TOO_LONG": "Máximo de 500 caracteres.",
"CHAT_SEND_FAILED": "Não foi possível enviar a mensagem.",
"CHAT_EMPTY": "Ainda não há mensagens. O chat é somente ao vivo.",
"LABEL_CHAT_NOTIFICATIONS": "Notificações do chat",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Mostra uma notificação do sistema para novas mensagens quando a aba do vídeo não está ativa.",
"LABEL_EVENT_NOTIFICATIONS": "Notificações de reprodução e sala",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Mostra notificações quando alguém entra, sai, reproduz ou pausa.",
"DEBUG_SECTION_VIDEO_DETECTION": "Detecção de vídeo",
"DEBUG_VIDEO_NOT_FOUND": "NENHUM ELEMENTO DE VÍDEO ENCONTRADO",
"DEBUG_SECTION_MEDIA_SESSION": "API Media Session",
"DEBUG_VIDEO_HINT": "Há um vídeo sendo reproduzido? O KoalaSync detecta elementos de vídeo após o carregamento da mídia.",
"DEBUG_SECTION_PLAYBACK": "Reprodução",
"DEBUG_STATE_PAUSED": "PAUSADO",
"DEBUG_STATE_PLAYING": "REPRODUZINDO",
"DEBUG_SECTION_PROPERTIES": "Propriedades",
"DEBUG_SECTION_DIMENSIONS": "Dimensões",
"DEBUG_DIMENSIONS_HINT": "0×0 = elemento de vídeo oculto ou ainda não carregado",
"DEBUG_SECTION_MEDIA_ERROR": "Erro de mídia",
"DEBUG_SECTION_DETECTION": "Detecção",
"DEBUG_SECTION_IDENTIFICATION": "Identificação",
"DEBUG_SECTION_MEDIA_SOURCE": "Fonte de mídia",
"DEBUG_SECTION_DATA_ATTRIBUTES": "Atributos de dados",
"DEBUG_YES": "SIM",
"DEBUG_NO": "NÃO",
"DEBUG_YES_CHECKED": "SIM (verificado)",
"DEBUG_NA": "n/d"
}
+55 -3
View File
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "Seguinte",
"BTN_ONBOARDING_NEXT_TOOLTIP": "Ir para o passo seguinte",
"ONBOARDING_1_TITLE": "Bem-vindo ao KoalaSync!",
"ONBOARDING_1_TEXT": "Veja vídeos em conjunto com os seus amigos em sincronia perfeita. Vamos fazer uma breve visita guiada!",
"ONBOARDING_1_TEXT": "Vê vídeos com outras pessoas, onde quer que estejam. Aqui tens uma breve apresentação.",
"ONBOARDING_2_TITLE": "1. Criar uma Sala",
"ONBOARDING_2_TEXT": "Comece aqui. Crie uma sala e partilhe o link de convite com os seus amigos.",
"ONBOARDING_3_TITLE": "2. Selecionar Vídeo",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Tempo limite — alguns participantes não carregaram o episódio",
"LOBBY_CANCEL_USER": "Cancelado pelo utilizador",
"NOTIF_ERROR_TITLE": "Erro no KoalaSync",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "Apoiar o KoalaSync",
"FOOTER_REVIEW": "★ Avaliar",
"FOOTER_SUPPORT_PROMPT": "Gostas do KoalaSync? Deixa uma avaliação!",
"LABEL_AUDIO_PROCESSING": "Processamento de Áudio",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "Perfil e Aparência",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "Altere o seu nome de utilizador, tema e preferências de idioma.",
"LABEL_SETTINGS_GROUP_SYNC": "Reprodução e Sincronização",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personalize a reprodução, as notificações e as preferências de áudio."
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Personalize a reprodução, as notificações e as preferências de áudio.",
"LABEL_CHAT_ENABLED": "Ativar chat da sala",
"LABEL_CHAT_ENABLED_TOOLTIP": "Mostrar o chat encriptado da sala no separador de vídeo selecionado.",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Configure o aspeto e a posição do chat encriptado da sala.",
"CHAT_SETTINGS_NOTE": "Opcional e encriptado de ponta a ponta. As mensagens são apenas em direto e não são guardadas pelo relay.",
"LABEL_CHAT_POSITION": "Posição",
"LABEL_CHAT_POSITION_TOOLTIP": "Escolha onde o chat fica fixado no leitor selecionado.",
"LABEL_CHAT_SIZE": "Tamanho da sobreposição",
"LABEL_CHAT_SIZE_TOOLTIP": "Escolha o tamanho predefinido da sobreposição do chat.",
"OPTION_CHAT_SIZE_COMPACT": "Compacto",
"OPTION_CHAT_SIZE_STANDARD": "Padrão",
"OPTION_CHAT_SIZE_LARGE": "Grande",
"OPTION_CHAT_SIZE_CUSTOM": "Último tamanho personalizado",
"LABEL_CHAT_START_MODE": "Iniciar como",
"LABEL_CHAT_START_MODE_TOOLTIP": "Escolha se o chat começa como uma bolha flutuante ou uma sobreposição aberta.",
"OPTION_CHAT_START_BUBBLE": "Bolha flutuante",
"OPTION_CHAT_START_OPEN": "Sobreposição aberta",
"CHAT_TITLE": "Chat da sala",
"CHAT_LIVE_ONLY": "Apenas em direto. Sem histórico.",
"CHAT_OPEN": "Abrir chat da sala",
"CHAT_CLOSE": "Fechar chat",
"CHAT_DOCK_LEFT": "Fixar chat à esquerda",
"CHAT_DOCK_RIGHT": "Fixar chat à direita",
"CHAT_DETACHED": "Desanexar chat",
"CHAT_PLACEHOLDER": "Escreva uma mensagem...",
"CHAT_SEND": "Enviar",
"CHAT_MISSING_KEY": "O chat está disponível apenas através de um link de convite atual.",
"CHAT_TOO_LONG": "Máximo de 500 caracteres.",
"CHAT_SEND_FAILED": "Não foi possível enviar a mensagem.",
"CHAT_EMPTY": "Ainda não há mensagens. O chat funciona apenas em direto.",
"LABEL_CHAT_NOTIFICATIONS": "Notificações do chat",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Mostra uma notificação do sistema para novas mensagens quando o separador do vídeo não está ativo.",
"LABEL_EVENT_NOTIFICATIONS": "Notificações de reprodução e sala",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Mostra notificações quando alguém entra, sai, reproduz ou pausa.",
"DEBUG_SECTION_VIDEO_DETECTION": "Deteção de vídeo",
"DEBUG_VIDEO_NOT_FOUND": "NENHUM ELEMENTO DE VÍDEO ENCONTRADO",
"DEBUG_SECTION_MEDIA_SESSION": "API Media Session",
"DEBUG_VIDEO_HINT": "Está algum vídeo em reprodução? O KoalaSync deteta elementos de vídeo após o carregamento do conteúdo.",
"DEBUG_SECTION_PLAYBACK": "Reprodução",
"DEBUG_STATE_PAUSED": "EM PAUSA",
"DEBUG_STATE_PLAYING": "A REPRODUZIR",
"DEBUG_SECTION_PROPERTIES": "Propriedades",
"DEBUG_SECTION_DIMENSIONS": "Dimensões",
"DEBUG_DIMENSIONS_HINT": "0×0 = elemento de vídeo oculto ou ainda não carregado",
"DEBUG_SECTION_MEDIA_ERROR": "Erro multimédia",
"DEBUG_SECTION_DETECTION": "Deteção",
"DEBUG_SECTION_IDENTIFICATION": "Identificação",
"DEBUG_SECTION_MEDIA_SOURCE": "Fonte multimédia",
"DEBUG_SECTION_DATA_ATTRIBUTES": "Atributos de dados",
"DEBUG_YES": "SIM",
"DEBUG_NO": "NÃO",
"DEBUG_YES_CHECKED": "SIM (verificado)",
"DEBUG_NA": "n/d"
}
+55 -3
View File
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "Далее",
"BTN_ONBOARDING_NEXT_TOOLTIP": "Перейти к следующему шагу",
"ONBOARDING_1_TITLE": "Добро пожаловать в KoalaSync!",
"ONBOARDING_1_TEXT": "Смотрите видео в идеальной синхронизации вместе где бы вы ни находились. Давайте проведем небольшую экскурсию!",
"ONBOARDING_1_TEXT": "Смотрите видео вместе, где бы вы ни находились. Вот краткий обзор.",
"ONBOARDING_2_TITLE": "1. Создайте комнату",
"ONBOARDING_2_TEXT": "Начните здесь. Создайте комнату и поделитесь ссылкой-приглашением со своими друзьями.",
"ONBOARDING_3_TITLE": "2. Выберите видео",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Время ожидания истекло — не все участники загрузили серию",
"LOBBY_CANCEL_USER": "Отменено пользователем",
"NOTIF_ERROR_TITLE": "Ошибка KoalaSync",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "Поддержать KoalaSync",
"FOOTER_REVIEW": "★ Оценить",
"FOOTER_SUPPORT_PROMPT": "Нравится KoalaSync? Оставьте отзыв!",
"LABEL_AUDIO_PROCESSING": "Обработка звука",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "Профиль и Оформление",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "Измените имя пользователя, тему и настройки языка.",
"LABEL_SETTINGS_GROUP_SYNC": "Воспроизведение и Синхронизация",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Настройте воспроизведение, уведомления и параметры звука."
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Настройте воспроизведение, уведомления и параметры звука.",
"LABEL_CHAT_ENABLED": "Включить чат комнаты",
"LABEL_CHAT_ENABLED_TOOLTIP": "Показывать зашифрованный чат комнаты на выбранной вкладке с видео.",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Настройте внешний вид и расположение зашифрованного чата комнаты.",
"CHAT_SETTINGS_NOTE": "Необязательный чат со сквозным шифрованием. Сообщения доступны только в реальном времени и не сохраняются ретранслятором.",
"LABEL_CHAT_POSITION": "Положение",
"LABEL_CHAT_POSITION_TOOLTIP": "Выберите, где чат будет закреплён на выбранном проигрывателе.",
"LABEL_CHAT_SIZE": "Размер окна",
"LABEL_CHAT_SIZE_TOOLTIP": "Выберите стандартный размер окна чата.",
"OPTION_CHAT_SIZE_COMPACT": "Компактный",
"OPTION_CHAT_SIZE_STANDARD": "Стандартный",
"OPTION_CHAT_SIZE_LARGE": "Большой",
"OPTION_CHAT_SIZE_CUSTOM": "Последний пользовательский размер",
"LABEL_CHAT_START_MODE": "Начальный вид",
"LABEL_CHAT_START_MODE_TOOLTIP": "Выберите, запускать чат как плавающую кнопку или открытое окно.",
"OPTION_CHAT_START_BUBBLE": "Плавающая кнопка",
"OPTION_CHAT_START_OPEN": "Открытое окно",
"CHAT_TITLE": "Чат комнаты",
"CHAT_LIVE_ONLY": "Только в реальном времени. Без истории.",
"CHAT_OPEN": "Открыть чат комнаты",
"CHAT_CLOSE": "Закрыть чат",
"CHAT_DOCK_LEFT": "Закрепить чат слева",
"CHAT_DOCK_RIGHT": "Закрепить чат справа",
"CHAT_DETACHED": "Открепить чат",
"CHAT_PLACEHOLDER": "Напишите сообщение...",
"CHAT_SEND": "Отправить",
"CHAT_MISSING_KEY": "Чат доступен только по актуальной ссылке-приглашению.",
"CHAT_TOO_LONG": "Не более 500 символов.",
"CHAT_SEND_FAILED": "Не удалось отправить сообщение.",
"CHAT_EMPTY": "Сообщений пока нет. Чат работает только в реальном времени.",
"LABEL_CHAT_NOTIFICATIONS": "Уведомления чата",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Показывает системное уведомление о новых сообщениях, когда вкладка с видео не активна.",
"LABEL_EVENT_NOTIFICATIONS": "Уведомления о воспроизведении и комнате",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Показывает уведомления, когда кто-то входит, выходит, запускает или приостанавливает видео.",
"DEBUG_SECTION_VIDEO_DETECTION": "Обнаружение видео",
"DEBUG_VIDEO_NOT_FOUND": "ВИДЕОЭЛЕМЕНТ НЕ НАЙДЕН",
"DEBUG_SECTION_MEDIA_SESSION": "API Media Session",
"DEBUG_VIDEO_HINT": "Видео сейчас воспроизводится? KoalaSync обнаруживает видеоэлементы после загрузки контента.",
"DEBUG_SECTION_PLAYBACK": "Воспроизведение",
"DEBUG_STATE_PAUSED": "ПАУЗА",
"DEBUG_STATE_PLAYING": "ВОСПРОИЗВЕДЕНИЕ",
"DEBUG_SECTION_PROPERTIES": "Свойства",
"DEBUG_SECTION_DIMENSIONS": "Размеры",
"DEBUG_DIMENSIONS_HINT": "0×0 = видеоэлемент скрыт или ещё не загружен",
"DEBUG_SECTION_MEDIA_ERROR": "Ошибка медиа",
"DEBUG_SECTION_DETECTION": "Обнаружение",
"DEBUG_SECTION_IDENTIFICATION": "Идентификация",
"DEBUG_SECTION_MEDIA_SOURCE": "Источник медиа",
"DEBUG_SECTION_DATA_ATTRIBUTES": "Атрибуты данных",
"DEBUG_YES": "ДА",
"DEBUG_NO": "НЕТ",
"DEBUG_YES_CHECKED": "ДА (проверено)",
"DEBUG_NA": "н/д"
}
+55 -3
View File
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "İleri",
"BTN_ONBOARDING_NEXT_TOOLTIP": "Bir sonraki adıma git",
"ONBOARDING_1_TITLE": "KoalaSync'e Hoş Geldiniz!",
"ONBOARDING_1_TEXT": "Nerede olursanız olun videoları mükemmel bir senkronizasyonla birlikte izleyin. Hızlı bir tura çıkalım!",
"ONBOARDING_1_TEXT": "Nerede olursanız olun videoları birlikte izleyin. İşte kısa bir tanıtım.",
"ONBOARDING_2_TITLE": "1. Oda Oluştur",
"ONBOARDING_2_TEXT": "Buradan başlayın. Bir oda oluşturun ve davet bağlantısını arkadaşlarınızla paylaşın.",
"ONBOARDING_3_TITLE": "2. Video Seç",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Zaman aşımı — tüm bağlantılar bölümü yüklemedi",
"LOBBY_CANCEL_USER": "Kullanıcı tarafından iptal edildi",
"NOTIF_ERROR_TITLE": "KoalaSync Hatası",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "KoalaSynci destekle",
"FOOTER_REVIEW": "★ Değerlendir",
"FOOTER_SUPPORT_PROMPT": "KoalaSync'i beğendin mi? Bir yorum bırak!",
"LABEL_AUDIO_PROCESSING": "Ses işleme",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "Profil ve Görünüm",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "Kullanıcı adınızı, temanızı ve dil tercihlerinizi değiştirin.",
"LABEL_SETTINGS_GROUP_SYNC": "Oynatma ve Eşitleme",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Oynatma, bildirim ve ses tercihlerini özelleştirin."
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Oynatma, bildirim ve ses tercihlerini özelleştirin.",
"LABEL_CHAT_ENABLED": "Oda sohbetini etkinleştir",
"LABEL_CHAT_ENABLED_TOOLTIP": "Şifreli oda sohbetini seçili video sekmesinde göster.",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Şifreli oda sohbetinin görünümünü ve konumunu yapılandırın.",
"CHAT_SETTINGS_NOTE": "İsteğe bağlı ve uçtan uca şifrelidir. Mesajlar yalnızca canlıdır ve aktarıcı tarafından saklanmaz.",
"LABEL_CHAT_POSITION": "Konum",
"LABEL_CHAT_POSITION_TOOLTIP": "Sohbetin seçili oynatıcıda nereye sabitleneceğini seçin.",
"LABEL_CHAT_SIZE": "Kaplama boyutu",
"LABEL_CHAT_SIZE_TOOLTIP": "Sohbet kaplamasının varsayılan boyutunu seçin.",
"OPTION_CHAT_SIZE_COMPACT": "Kompakt",
"OPTION_CHAT_SIZE_STANDARD": "Standart",
"OPTION_CHAT_SIZE_LARGE": "Büyük",
"OPTION_CHAT_SIZE_CUSTOM": "Son özel boyut",
"LABEL_CHAT_START_MODE": "Başlangıç görünümü",
"LABEL_CHAT_START_MODE_TOOLTIP": "Sohbetin kayan balon veya açık kaplama olarak başlamasını seçin.",
"OPTION_CHAT_START_BUBBLE": "Kayan sohbet balonu",
"OPTION_CHAT_START_OPEN": "Açık kaplama",
"CHAT_TITLE": "Oda Sohbeti",
"CHAT_LIVE_ONLY": "Yalnızca canlı. Geçmiş yok.",
"CHAT_OPEN": "Oda sohbetini aç",
"CHAT_CLOSE": "Sohbeti kapat",
"CHAT_DOCK_LEFT": "Sohbeti sola sabitle",
"CHAT_DOCK_RIGHT": "Sohbeti sağa sabitle",
"CHAT_DETACHED": "Sohbeti ayır",
"CHAT_PLACEHOLDER": "Bir mesaj yazın...",
"CHAT_SEND": "Gönder",
"CHAT_MISSING_KEY": "Sohbet yalnızca güncel bir davet bağlantısıyla kullanılabilir.",
"CHAT_TOO_LONG": "En fazla 500 karakter.",
"CHAT_SEND_FAILED": "Mesaj gönderilemedi.",
"CHAT_EMPTY": "Henüz mesaj yok. Sohbet yalnızca canlıdır.",
"LABEL_CHAT_NOTIFICATIONS": "Sohbet bildirimleri",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Video sekmesi etkin değilken yeni oda mesajlarını sistem bildirimi olarak gösterir.",
"LABEL_EVENT_NOTIFICATIONS": "Oynatma ve oda bildirimleri",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Biri katıldığında, ayrıldığında, oynattığında veya duraklattığında bildirim gösterir.",
"DEBUG_SECTION_VIDEO_DETECTION": "Video algılama",
"DEBUG_VIDEO_NOT_FOUND": "VİDEO ÖĞESİ BULUNAMADI",
"DEBUG_SECTION_MEDIA_SESSION": "Media Session API",
"DEBUG_VIDEO_HINT": "Şu anda video oynatılıyor mu? KoalaSync medya yüklendikten sonra video öğelerini algılar.",
"DEBUG_SECTION_PLAYBACK": "Oynatma",
"DEBUG_STATE_PAUSED": "DURAKLATILDI",
"DEBUG_STATE_PLAYING": "OYNATILIYOR",
"DEBUG_SECTION_PROPERTIES": "Özellikler",
"DEBUG_SECTION_DIMENSIONS": "Boyutlar",
"DEBUG_DIMENSIONS_HINT": "0×0 = video öğesi gizli veya henüz yüklenmedi",
"DEBUG_SECTION_MEDIA_ERROR": "Medya hatası",
"DEBUG_SECTION_DETECTION": "Algılama",
"DEBUG_SECTION_IDENTIFICATION": "Tanımlama",
"DEBUG_SECTION_MEDIA_SOURCE": "Medya kaynağı",
"DEBUG_SECTION_DATA_ATTRIBUTES": "Veri öznitelikleri",
"DEBUG_YES": "EVET",
"DEBUG_NO": "HAYIR",
"DEBUG_YES_CHECKED": "EVET (kontrol edildi)",
"DEBUG_NA": "yok"
}
+55 -3
View File
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "Далі",
"BTN_ONBOARDING_NEXT_TOOLTIP": "Перейти до наступного кроку",
"ONBOARDING_1_TITLE": "Ласкаво просимо до KoalaSync!",
"ONBOARDING_1_TEXT": "Дивіться відео разом у ідеальній синхронізації — де б ви не були. Давайте зробимо короткий тур!",
"ONBOARDING_1_TEXT": "Дивіться відео разом, де б ви не були. Ось короткий огляд.",
"ONBOARDING_2_TITLE": "1. Створіть кімнату",
"ONBOARDING_2_TEXT": "Почніть тут. Створіть кімнату та поділіться посиланням запрошення з друзями.",
"ONBOARDING_3_TITLE": "2. Виберіть Відео",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "Тайм-аут — не всі вузли завантажили епізод",
"LOBBY_CANCEL_USER": "Скасовано користувачем",
"NOTIF_ERROR_TITLE": "Помилка KoalaSync",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "Підтримати KoalaSync",
"FOOTER_REVIEW": "★ Оцініть нас",
"FOOTER_SUPPORT_PROMPT": "Подобається KoalaSync? Залиште відгук!",
"LABEL_AUDIO_PROCESSING": "Обробка аудіо",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "Профіль та Вигляд",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "Змініть ім'я користувача, тему та мовні налаштування.",
"LABEL_SETTINGS_GROUP_SYNC": "Відтворення та Синхронізація",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Налаштуйте відтворення, сповіщення та параметри звуку."
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "Налаштуйте відтворення, сповіщення та параметри звуку.",
"LABEL_CHAT_ENABLED": "Увімкнути чат кімнати",
"LABEL_CHAT_ENABLED_TOOLTIP": "Показувати зашифрований чат кімнати на вибраній вкладці з відео.",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "Налаштуйте вигляд і розташування зашифрованого чату кімнати.",
"CHAT_SETTINGS_NOTE": "Необов'язковий чат із наскрізним шифруванням. Повідомлення доступні лише наживо й не зберігаються ретранслятором.",
"LABEL_CHAT_POSITION": "Розташування",
"LABEL_CHAT_POSITION_TOOLTIP": "Виберіть, де чат буде закріплено на вибраному програвачі.",
"LABEL_CHAT_SIZE": "Розмір вікна",
"LABEL_CHAT_SIZE_TOOLTIP": "Виберіть стандартний розмір вікна чату.",
"OPTION_CHAT_SIZE_COMPACT": "Компактний",
"OPTION_CHAT_SIZE_STANDARD": "Стандартний",
"OPTION_CHAT_SIZE_LARGE": "Великий",
"OPTION_CHAT_SIZE_CUSTOM": "Останній власний розмір",
"LABEL_CHAT_START_MODE": "Початковий вигляд",
"LABEL_CHAT_START_MODE_TOOLTIP": "Виберіть, запускати чат як плаваючу кнопку чи відкрите вікно.",
"OPTION_CHAT_START_BUBBLE": "Плаваюча кнопка",
"OPTION_CHAT_START_OPEN": "Відкрите вікно",
"CHAT_TITLE": "Чат кімнати",
"CHAT_LIVE_ONLY": "Лише наживо. Без історії.",
"CHAT_OPEN": "Відкрити чат кімнати",
"CHAT_CLOSE": "Закрити чат",
"CHAT_DOCK_LEFT": "Закріпити чат ліворуч",
"CHAT_DOCK_RIGHT": "Закріпити чат праворуч",
"CHAT_DETACHED": "Від'єднати чат",
"CHAT_PLACEHOLDER": "Напишіть повідомлення...",
"CHAT_SEND": "Надіслати",
"CHAT_MISSING_KEY": "Чат доступний лише за актуальним посиланням-запрошенням.",
"CHAT_TOO_LONG": "Не більше 500 символів.",
"CHAT_SEND_FAILED": "Не вдалося надіслати повідомлення.",
"CHAT_EMPTY": "Повідомлень ще немає. Чат працює лише наживо.",
"LABEL_CHAT_NOTIFICATIONS": "Сповіщення чату",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "Показує системне сповіщення про нові повідомлення, коли вкладка з відео неактивна.",
"LABEL_EVENT_NOTIFICATIONS": "Сповіщення про відтворення та кімнату",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "Показує сповіщення, коли хтось приєднується, виходить, запускає або призупиняє відео.",
"DEBUG_SECTION_VIDEO_DETECTION": "Виявлення відео",
"DEBUG_VIDEO_NOT_FOUND": "ВІДЕОЕЛЕМЕНТ НЕ ЗНАЙДЕНО",
"DEBUG_SECTION_MEDIA_SESSION": "API Media Session",
"DEBUG_VIDEO_HINT": "Відео зараз відтворюється? KoalaSync виявляє відеоелементи після завантаження медіа.",
"DEBUG_SECTION_PLAYBACK": "Відтворення",
"DEBUG_STATE_PAUSED": "ПРИЗУПИНЕНО",
"DEBUG_STATE_PLAYING": "ВІДТВОРЮЄТЬСЯ",
"DEBUG_SECTION_PROPERTIES": "Властивості",
"DEBUG_SECTION_DIMENSIONS": "Розміри",
"DEBUG_DIMENSIONS_HINT": "0×0 = відеоелемент прихований або ще не завантажений",
"DEBUG_SECTION_MEDIA_ERROR": "Помилка медіа",
"DEBUG_SECTION_DETECTION": "Виявлення",
"DEBUG_SECTION_IDENTIFICATION": "Ідентифікація",
"DEBUG_SECTION_MEDIA_SOURCE": "Джерело медіа",
"DEBUG_SECTION_DATA_ATTRIBUTES": "Атрибути даних",
"DEBUG_YES": "ТАК",
"DEBUG_NO": "НІ",
"DEBUG_YES_CHECKED": "ТАК (перевірено)",
"DEBUG_NA": "н/д"
}
+55 -3
View File
@@ -134,7 +134,7 @@
"BTN_ONBOARDING_NEXT": "下一个",
"BTN_ONBOARDING_NEXT_TOOLTIP": "进入下一步",
"ONBOARDING_1_TITLE": "欢迎来到KoalaSync",
"ONBOARDING_1_TEXT": "无论身在何处,都能完美同步地一起看视频。让我们快速浏览一下吧!",
"ONBOARDING_1_TEXT": "无论身在何处,都可以一起看视频。下面是快速介绍。",
"ONBOARDING_2_TITLE": "1. 创建房间",
"ONBOARDING_2_TEXT": "从这里开始。创建一个房间并与您的朋友分享邀请链接。",
"ONBOARDING_3_TITLE": "2. 选择视频",
@@ -204,7 +204,7 @@
"LOBBY_CANCEL_TIMEOUT_PEERS_LOAD": "超时——并非所有同行都加载了该剧集",
"LOBBY_CANCEL_USER": "已被用户取消",
"NOTIF_ERROR_TITLE": "KoalaSync 错误",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "支持 KoalaSync",
"FOOTER_REVIEW": "★ 评价我们",
"FOOTER_SUPPORT_PROMPT": "喜欢 KoalaSync 吗?留下评论!",
"LABEL_AUDIO_PROCESSING": "音频处理",
@@ -245,5 +245,57 @@
"LABEL_SETTINGS_GROUP_PROFILE": "个人资料与外观",
"LABEL_SETTINGS_GROUP_PROFILE_TOOLTIP": "更改您的用户名、主题和语言首选项。",
"LABEL_SETTINGS_GROUP_SYNC": "播放与同步",
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "自定义播放、通知和音频首选项。"
"LABEL_SETTINGS_GROUP_SYNC_TOOLTIP": "自定义播放、通知和音频首选项。",
"LABEL_CHAT_ENABLED": "启用房间聊天",
"LABEL_CHAT_ENABLED_TOOLTIP": "在选定的视频标签页中显示加密的房间聊天。",
"LABEL_SETTINGS_GROUP_CHAT_TOOLTIP": "配置加密房间聊天的外观和位置。",
"CHAT_SETTINGS_NOTE": "可选并采用端到端加密。消息仅实时显示,且不会由中继服务器存储。",
"LABEL_CHAT_POSITION": "位置",
"LABEL_CHAT_POSITION_TOOLTIP": "选择聊天在当前播放器上的固定位置。",
"LABEL_CHAT_SIZE": "浮层大小",
"LABEL_CHAT_SIZE_TOOLTIP": "选择聊天浮层的默认大小。",
"OPTION_CHAT_SIZE_COMPACT": "紧凑",
"OPTION_CHAT_SIZE_STANDARD": "标准",
"OPTION_CHAT_SIZE_LARGE": "大",
"OPTION_CHAT_SIZE_CUSTOM": "上次自定义大小",
"LABEL_CHAT_START_MODE": "启动方式",
"LABEL_CHAT_START_MODE_TOOLTIP": "选择以悬浮气泡或打开的浮层启动聊天。",
"OPTION_CHAT_START_BUBBLE": "悬浮聊天气泡",
"OPTION_CHAT_START_OPEN": "打开浮层",
"CHAT_TITLE": "房间聊天",
"CHAT_LIVE_ONLY": "仅实时显示,无历史记录。",
"CHAT_OPEN": "打开房间聊天",
"CHAT_CLOSE": "关闭聊天",
"CHAT_DOCK_LEFT": "将聊天固定在左侧",
"CHAT_DOCK_RIGHT": "将聊天固定在右侧",
"CHAT_DETACHED": "浮动聊天",
"CHAT_PLACEHOLDER": "输入消息...",
"CHAT_SEND": "发送",
"CHAT_MISSING_KEY": "聊天仅可通过当前邀请链接使用。",
"CHAT_TOO_LONG": "最多500个字符。",
"CHAT_SEND_FAILED": "无法发送消息。",
"CHAT_EMPTY": "暂无消息。聊天仅实时显示。",
"LABEL_CHAT_NOTIFICATIONS": "聊天通知",
"LABEL_CHAT_NOTIFICATIONS_TOOLTIP": "视频标签页未激活时,以系统通知显示新的房间聊天消息。",
"LABEL_EVENT_NOTIFICATIONS": "播放与房间通知",
"LABEL_EVENT_NOTIFICATIONS_TOOLTIP": "有人加入、离开、播放或暂停时显示系统通知。",
"DEBUG_SECTION_VIDEO_DETECTION": "视频检测",
"DEBUG_VIDEO_NOT_FOUND": "未找到视频元素",
"DEBUG_SECTION_MEDIA_SESSION": "Media Session API",
"DEBUG_VIDEO_HINT": "当前是否正在播放视频?媒体加载后,KoalaSync 才能检测视频元素。",
"DEBUG_SECTION_PLAYBACK": "播放",
"DEBUG_STATE_PAUSED": "已暂停",
"DEBUG_STATE_PLAYING": "播放中",
"DEBUG_SECTION_PROPERTIES": "属性",
"DEBUG_SECTION_DIMENSIONS": "尺寸",
"DEBUG_DIMENSIONS_HINT": "0×0 = 视频元素已隐藏或尚未加载",
"DEBUG_SECTION_MEDIA_ERROR": "媒体错误",
"DEBUG_SECTION_DETECTION": "检测",
"DEBUG_SECTION_IDENTIFICATION": "标识",
"DEBUG_SECTION_MEDIA_SOURCE": "媒体来源",
"DEBUG_SECTION_DATA_ATTRIBUTES": "数据属性",
"DEBUG_YES": "是",
"DEBUG_NO": "否",
"DEBUG_YES_CHECKED": "是(已检查)",
"DEBUG_NA": "无"
}
+4 -3
View File
@@ -1,9 +1,10 @@
{
"manifest_version": 3,
"default_locale": "en",
"name": "KoalaSync",
"version": "2.6.1",
"description": "Synchronize video playback on YouTube, Netflix, Emby, Jellyfin, and any HTML5 site in real-time with friends.",
"name": "__MSG_appName__",
"short_name": "KoalaSync",
"version": "3.0.0",
"description": "__MSG_appDesc__",
"permissions": [
"storage",
"tabs",
+8 -72
View File
@@ -1,20 +1,7 @@
export function initTabManager({
getCurrentTabId,
setCurrentTabId,
setCurrentTabTitle,
setLastContentHeartbeatAt,
setRoomIdleSince,
getCurrentRoom,
getPeerId,
getStorageInitialized,
updateBadgeStatus,
addLog,
getSettings,
emit,
applyAudioSettingsToTab,
injectContentScript,
ensureState,
EVENTS
reactivateCurrentTarget,
ensureState
}) {
chrome.storage.onChanged.addListener(async (changes, area) => {
if (area !== 'local' || !changes.audioSettings) return;
@@ -28,65 +15,14 @@ export function initTabManager({
}).catch(() => {});
});
chrome.tabs.onRemoved.addListener(async (tabId) => {
await ensureState();
if (tabId === getCurrentTabId()) {
const wasInRoom = !!getCurrentRoom();
setCurrentTabId(null);
setCurrentTabTitle(null);
setLastContentHeartbeatAt(null);
const now = Date.now();
setRoomIdleSince(now);
chrome.storage.session.set({
currentTabId: null,
currentTabTitle: null,
roomIdleSince: now,
lastContentHeartbeatAt: null
});
updateBadgeStatus();
addLog('Target tab closed.', 'warn');
if (wasInRoom) {
const roomAtClose = getCurrentRoom();
getSettings().then(settings => {
if (getCurrentRoom() !== roomAtClose) return;
emit(EVENTS.PEER_STATUS, {
peerId: getPeerId(),
playbackState: 'paused',
currentTime: null,
mediaTitle: null,
username: settings.username,
tabTitle: null
});
const room = getCurrentRoom();
if (room && Array.isArray(room.peers)) {
const me = room.peers.find(p => (p.peerId || p) === getPeerId());
if (me && typeof me === 'object') {
me.playbackState = 'paused';
me.currentTime = null;
me.mediaTitle = null;
me.tabTitle = null;
me.lastHeartbeat = Date.now();
if (getStorageInitialized()) {
chrome.storage.session.set({ currentRoom: room });
}
chrome.runtime.sendMessage({ type: 'PEER_UPDATE', peers: room.peers }).catch(() => {});
}
}
}).catch(() => {});
}
}
});
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, _tab) => {
await ensureState();
const curTabId = getCurrentTabId();
if (curTabId && tabId === parseInt(curTabId) && changeInfo.status === 'complete') {
injectContentScript(tabId)
.then(() => applyAudioSettingsToTab(tabId))
.catch(() => {});
const currentTabId = Number(getCurrentTabId());
if (Number.isInteger(currentTabId)
&& currentTabId > 0
&& tabId === currentTabId
&& changeInfo.status === 'complete') {
reactivateCurrentTarget(tabId).catch(() => {});
}
});
}
+60 -3
View File
@@ -1215,6 +1215,12 @@
line-height: 1.45;
text-align: left;
}
[data-chat-setting] {
transition: opacity 0.2s;
}
[data-chat-setting].is-disabled {
opacity: 0.5;
}
.settings-issue-link {
margin: 8px 0 2px;
padding-top: 9px;
@@ -1371,7 +1377,7 @@
<!-- Room credentials — distinct group below the server picker. -->
<div class="form-group">
<label for="roomId" title="The unique identifier for your sync room" data-i18n="LABEL_ROOM_ID" data-i18n-title="LABEL_ROOM_ID_TOOLTIP">Room ID</label>
<input type="text" id="roomId" data-i18n-placeholder="PLACEHOLDER_ROOM_ID" data-i18n-title="PLACEHOLDER_ROOM_ID_TOOLTIP" placeholder="Enter Room ID" title="The unique ID of the room you want to join">
<input type="text" id="roomId" maxlength="64" data-i18n-placeholder="PLACEHOLDER_ROOM_ID" data-i18n-title="PLACEHOLDER_ROOM_ID_TOOLTIP" placeholder="Enter Room ID" title="The unique ID of the room you want to join">
</div>
<div class="form-group">
<label for="password" title="Optional password to restrict room access" data-i18n="LABEL_PASSWORD" data-i18n-title="LABEL_PASSWORD_TOOLTIP">Password (Optional)</label>
@@ -1615,6 +1621,57 @@
</div>
</details>
<details class="form-group" name="settings-accordion">
<summary title="Configure encrypted room chat appearance and placement." data-i18n="CHAT_TITLE" data-i18n-title="LABEL_SETTINGS_GROUP_CHAT_TOOLTIP">Room Chat</summary>
<div class="details-content">
<p class="settings-note" data-i18n="CHAT_SETTINGS_NOTE">Optional and end-to-end encrypted. Messages are live only and are not stored by the relay.</p>
<div class="settings-row">
<label for="chatEnabled" title="Show encrypted room chat on the selected video tab." data-i18n="LABEL_CHAT_ENABLED" data-i18n-title="LABEL_CHAT_ENABLED_TOOLTIP">Enable Room Chat</label>
<label class="toggle-switch">
<input type="checkbox" id="chatEnabled">
<span class="slider"></span>
</label>
</div>
<div class="settings-row" data-chat-setting>
<label for="chatNotifications" title="Shows a system notification for new room-chat messages when the video tab is not focused." data-i18n="LABEL_CHAT_NOTIFICATIONS" data-i18n-title="LABEL_CHAT_NOTIFICATIONS_TOOLTIP">Chat Notifications</label>
<label class="toggle-switch">
<input type="checkbox" id="chatNotifications">
<span class="slider"></span>
</label>
</div>
<div class="settings-row" data-chat-setting>
<label for="chatPosition" title="Choose where chat is attached to the selected player." data-i18n="LABEL_CHAT_POSITION" data-i18n-title="LABEL_CHAT_POSITION_TOOLTIP">Position</label>
<span class="settings-select">
<select id="chatPosition" class="settings-control">
<option value="right" data-i18n="CHAT_DOCK_RIGHT">Dock chat right</option>
<option value="left" data-i18n="CHAT_DOCK_LEFT">Dock chat left</option>
<option value="detached" data-i18n="CHAT_DETACHED">Detach chat</option>
</select>
</span>
</div>
<div class="settings-row" data-chat-setting>
<label for="chatSize" title="Choose the default size of the chat overlay." data-i18n="LABEL_CHAT_SIZE" data-i18n-title="LABEL_CHAT_SIZE_TOOLTIP">Overlay size</label>
<span class="settings-select">
<select id="chatSize" class="settings-control">
<option value="compact" data-i18n="OPTION_CHAT_SIZE_COMPACT">Compact</option>
<option value="standard" data-i18n="OPTION_CHAT_SIZE_STANDARD">Standard</option>
<option value="large" data-i18n="OPTION_CHAT_SIZE_LARGE">Large</option>
<option value="custom" data-i18n="OPTION_CHAT_SIZE_CUSTOM">Last custom size</option>
</select>
</span>
</div>
<div class="settings-row" data-chat-setting>
<label for="chatStartMode" title="Choose whether chat starts as a floating bubble or an open overlay." data-i18n="LABEL_CHAT_START_MODE" data-i18n-title="LABEL_CHAT_START_MODE_TOOLTIP">Start as</label>
<span class="settings-select">
<select id="chatStartMode" class="settings-control">
<option value="bubble" data-i18n="OPTION_CHAT_START_BUBBLE">Floating bubble</option>
<option value="open" data-i18n="OPTION_CHAT_START_OPEN">Open overlay</option>
</select>
</span>
</div>
</div>
</details>
<details class="form-group" name="settings-accordion">
<summary title="Customize playback, notifications, and audio preferences." data-i18n="LABEL_SETTINGS_GROUP_SYNC" data-i18n-title="LABEL_SETTINGS_GROUP_SYNC_TOOLTIP">Playback &amp; Sync</summary>
<div class="details-content">
@@ -1640,7 +1697,7 @@
</label>
</div>
<div class="settings-row">
<label for="browserNotifications" title="Shows native system notifications when someone joins/leaves or plays/pauses." data-i18n="LABEL_NOTIFICATIONS" data-i18n-title="LABEL_NOTIFICATIONS_TOOLTIP">Browser Notifications</label>
<label for="browserNotifications" title="Shows system notifications when someone joins, leaves, plays, or pauses." data-i18n="LABEL_EVENT_NOTIFICATIONS" data-i18n-title="LABEL_EVENT_NOTIFICATIONS_TOOLTIP">Playback &amp; Room Notifications</label>
<label class="toggle-switch">
<input type="checkbox" id="browserNotifications">
<span class="slider"></span>
@@ -1682,7 +1739,7 @@
<label title="Technical details about the currently selected video element" data-i18n="LABEL_VIDEO_DEBUG" data-i18n-title="LABEL_VIDEO_DEBUG_TOOLTIP">Video Debug Info</label>
<div id="videoDebug" class="info-card" style="font-size: 10px; font-family: monospace; color: var(--text-muted); max-height: 250px; overflow-y: auto; line-height: 1.4;">
No tab selected or video detected.
<span data-i18n="VIDEO_DEBUG_EMPTY">No tab selected or video detected.</span>
</div>
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom: 8px;">
+250 -103
View File
@@ -3,8 +3,17 @@ import { BLACKLIST_DOMAINS } from './shared/blacklist.js';
import { getAvatarForName, generateUsername, USERNAME_ADJECTIVES, USERNAME_NOUNS } from './shared/names.js';
import { loadLocale, translateDOM, getMessage, getSystemLanguage } from './i18n.js';
import { TITLE_PRIVACY_MODES, normalizeSendTabTitle, normalizeTabTitle } from './title-privacy.js';
import { requestOriginPermission } from './host-access.js';
import { normalizeRoomId } from './chat-session.js';
import './shared/invite-links.js';
import { normalizeTabId, requestOriginPermission } from './host-access.js';
let pendingInviteRoomId = '';
let pendingInviteChatKey = '';
let pendingRoomCreation = false;
function normalizeChatKey(value) {
return typeof value === 'string' && /^[A-Za-z0-9_-]{21}[AQgw]$/.test(value) ? value : '';
}
const elements = {
tabs: document.querySelectorAll('.tabs .tab-btn'),
@@ -59,6 +68,11 @@ const elements = {
playBtn: document.getElementById('playBtn'),
pauseBtn: document.getElementById('pauseBtn'),
autoSyncNextEpisode: document.getElementById('autoSyncNextEpisode'),
chatEnabled: document.getElementById('chatEnabled'),
chatNotifications: document.getElementById('chatNotifications'),
chatPosition: document.getElementById('chatPosition'),
chatSize: document.getElementById('chatSize'),
chatStartMode: document.getElementById('chatStartMode'),
sendTabTitle: document.getElementById('sendTabTitle'),
mediaTitlePrivacyMode: document.getElementById('mediaTitlePrivacyMode'),
episodeLobbyCard: document.getElementById('episodeLobbyCard'),
@@ -328,7 +342,7 @@ function setRoomRefreshCooldown() {
async function init() {
// Local-only by design — settings and room credentials never come from
// storage.sync (only onboardingComplete + dismissedHints live there).
const localData = await chrome.storage.local.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'username', 'filterNoise', 'autoSyncNextEpisode', 'sendTabTitle', 'mediaTitlePrivacyMode', 'titlePrivacyMode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite', 'locale', 'audioSettings', 'activeTab', 'themeMode', 'themePalette']);
const localData = await chrome.storage.local.get(['serverUrl', 'useCustomServer', 'roomId', 'password', 'chatKey', 'chatEnabled', 'chatNotifications', 'chatPosition', 'chatSize', 'chatStartMode', 'username', 'filterNoise', 'autoSyncNextEpisode', 'sendTabTitle', 'mediaTitlePrivacyMode', 'titlePrivacyMode', 'forceSyncMode', 'browserNotifications', 'autoCopyInvite', 'locale', 'audioSettings', 'activeTab', 'themeMode', 'themePalette']);
let activeLang = localData.locale;
if (!activeLang) {
@@ -353,12 +367,18 @@ async function init() {
}
elements.serverUrl.value = localData.serverUrl || '';
elements.roomId.value = localData.roomId || '';
elements.roomId.value = normalizeRoomId(localData.roomId);
elements.password.value = localData.password || '';
elements.username.value = username;
syncDevToolsVisibility();
if (elements.filterNoise) elements.filterNoise.checked = localData.filterNoise !== false;
if (elements.autoSyncNextEpisode) elements.autoSyncNextEpisode.checked = localData.autoSyncNextEpisode !== false;
if (elements.chatEnabled) elements.chatEnabled.checked = localData.chatEnabled === true;
if (elements.chatNotifications) elements.chatNotifications.checked = localData.chatNotifications !== false;
if (elements.chatPosition) elements.chatPosition.value = ['left', 'detached'].includes(localData.chatPosition) ? localData.chatPosition : 'right';
if (elements.chatSize) elements.chatSize.value = ['compact', 'large', 'custom'].includes(localData.chatSize) ? localData.chatSize : 'standard';
if (elements.chatStartMode) elements.chatStartMode.value = localData.chatStartMode === 'open' ? 'open' : 'bubble';
syncChatSettingsState();
const legacyTitlePrivacyMode = Object.values(TITLE_PRIVACY_MODES).includes(localData.titlePrivacyMode) ? localData.titlePrivacyMode : TITLE_PRIVACY_MODES.FULL;
const mediaTitlePrivacyMode = Object.values(TITLE_PRIVACY_MODES).includes(localData.mediaTitlePrivacyMode) ? localData.mediaTitlePrivacyMode : legacyTitlePrivacyMode;
if (elements.sendTabTitle) elements.sendTabTitle.checked = normalizeSendTabTitle(localData.sendTabTitle, legacyTitlePrivacyMode);
@@ -384,7 +404,7 @@ async function init() {
}
toggleUIState(!!localData.roomId);
updateUI(localData.roomId, localData.password, localData.useCustomServer, localData.serverUrl);
updateUI(localData.roomId, localData.password, localData.useCustomServer, localData.serverUrl, localData.chatKey);
refreshLogs();
refreshHistory();
@@ -415,7 +435,11 @@ async function init() {
}
if (res.pendingTargetTabId) {
showSiteAccessNotice(res.pendingTargetHost, res.pendingTargetOriginPattern);
showSiteAccessNotice(
res.pendingTargetHost,
res.pendingTargetOriginPattern,
res.pendingTargetTabId
);
} else {
hideSiteAccessNotice();
}
@@ -477,6 +501,7 @@ let hcmGuestLocked = false;
let siteAccessBlocked = false;
let siteAccessHost = null;
let siteAccessOriginPattern = null;
let siteAccessTabId = null;
// Co-Host state mirrored for the peer-list renderer (promote/demote + role badges).
let hcmAmOwner = false;
@@ -557,15 +582,21 @@ function setRemoteControlsLocked(locked) {
if (elements.pauseBtn) elements.pauseBtn.textContent = getMessage('BTN_PAUSE') || 'Pause';
}
function showSiteAccessNotice(host, originPattern = null) {
function showSiteAccessNotice(host, originPattern = null, tabId = null) {
siteAccessBlocked = true;
siteAccessHost = host || siteAccessHost || 'website';
siteAccessOriginPattern = originPattern || siteAccessOriginPattern;
siteAccessHost = typeof host === 'string' && host.length > 0 ? host : 'website';
siteAccessOriginPattern = typeof originPattern === 'string' && originPattern.length > 0
? originPattern
: null;
siteAccessTabId = normalizeTabId(tabId);
if (elements.siteAccessMessage) {
elements.siteAccessMessage.textContent = getMessage('SITE_ACCESS_REQUIRED', {
host: siteAccessHost
});
}
if (elements.siteAccessRetry) {
elements.siteAccessRetry.disabled = !siteAccessOriginPattern || siteAccessTabId === null;
}
if (elements.siteAccessNotice) elements.siteAccessNotice.style.display = 'block';
setRemoteControlsLocked(hcmGuestLocked);
}
@@ -574,13 +605,15 @@ function hideSiteAccessNotice() {
siteAccessBlocked = false;
siteAccessHost = null;
siteAccessOriginPattern = null;
siteAccessTabId = null;
if (elements.siteAccessRetry) elements.siteAccessRetry.disabled = false;
if (elements.siteAccessNotice) elements.siteAccessNotice.style.display = 'none';
setRemoteControlsLocked(hcmGuestLocked);
}
function handleTargetTabResponse(response) {
if (response?.status === 'host_permission_required') {
showSiteAccessNotice(response.host, response.originPattern);
showSiteAccessNotice(response.host, response.originPattern, response.tabId);
return false;
}
if (response?.status === 'ok') {
@@ -605,6 +638,32 @@ function selectTargetTab(tabId, tabTitle) {
});
}
async function refreshTargetAccessState() {
const status = await new Promise(resolve => {
chrome.runtime.sendMessage({ type: 'GET_STATUS' }, response => {
if (chrome.runtime.lastError) {
resolve(null);
return;
}
resolve(response || null);
});
});
if (!status) return;
if (status.pendingTargetTabId) {
showSiteAccessNotice(
status.pendingTargetHost,
status.pendingTargetOriginPattern,
status.pendingTargetTabId
);
} else {
hideSiteAccessNotice();
}
await populateTabs(
status.peers,
status.targetTabId ?? status.pendingTargetTabId ?? null
);
}
if (elements.hostControlToggle) {
elements.hostControlToggle.addEventListener('change', () => {
const mode = elements.hostControlToggle.checked ? 'host-only' : 'everyone';
@@ -617,15 +676,20 @@ if (elements.hostControlToggle) {
});
}
function updateUI(roomId, password, useCustomServer = false, serverUrl = '') {
function updateUI(roomId, password, useCustomServer = false, serverUrl = '', chatKey = '') {
const inRoom = !!roomId;
toggleUIState(inRoom);
if (inRoom) {
let invite = `${OFFICIAL_LANDING_PAGE_URL}/join.html#join:${roomId}:${password}`;
if (useCustomServer) {
const encodedUrl = encodeURIComponent(serverUrl || '');
invite += `:1:${encodedUrl}`;
}
const validChatKey = normalizeChatKey(chatKey);
let invite = validChatKey
? `${OFFICIAL_LANDING_PAGE_URL}/join.html${globalThis.KoalaSyncInviteLinks.buildJ2Hash({
roomId,
password,
chatKey: validChatKey,
serverUrl: useCustomServer ? serverUrl : ''
})}`
: `${OFFICIAL_LANDING_PAGE_URL}/join.html#join:${roomId}:${password}`;
if (!validChatKey && useCustomServer) invite += `:1:${encodeURIComponent(serverUrl || '')}`;
elements.inviteLink.value = invite;
if (window.justCreatedRoom) {
@@ -1354,41 +1418,21 @@ function updateRoomList(rooms) {
function checkInviteLink() {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const tab = tabs[0];
if (tab && tab.url && tab.url.startsWith(OFFICIAL_LANDING_PAGE_URL + '/') && tab.url.includes('#join:')) {
if (tab && tab.url && tab.url.startsWith(OFFICIAL_LANDING_PAGE_URL + '/')) {
try {
const rawHash = tab.url.split('#join:')[1];
if (!rawHash) return;
const parts = rawHash.split(':');
if (parts.length >= 2) {
const roomId = parts.shift();
let useCustomServer = false;
let serverUrl = '';
const invite = globalThis.KoalaSyncInviteLinks.parseInviteHash(new URL(tab.url).hash);
if (!invite) return;
elements.roomId.value = normalizeRoomId(invite.roomId);
elements.password.value = invite.password;
pendingInviteRoomId = elements.roomId.value;
pendingInviteChatKey = normalizeChatKey(invite.chatKey);
const last = parts[parts.length - 1];
const secondToLast = parts[parts.length - 2];
const decodedLast = decodeURIComponent(last || '');
const isCustom = secondToLast === '1' && (decodedLast.startsWith('ws://') || decodedLast.startsWith('wss://'));
const isOfficial = secondToLast === '0' && last === '';
elements.serverUrl.value = invite.serverUrl;
setServerMode(invite.useCustomServer);
chrome.storage.local.set({ serverUrl: invite.serverUrl, useCustomServer: invite.useCustomServer });
if (parts.length >= 3 && (isCustom || isOfficial)) {
serverUrl = decodeURIComponent(parts.pop());
useCustomServer = parts.pop() === '1';
}
const password = parts.join(':');
elements.roomId.value = roomId;
elements.password.value = password;
if (serverUrl || useCustomServer) {
elements.serverUrl.value = serverUrl;
setServerMode(useCustomServer);
chrome.storage.local.set({ serverUrl, useCustomServer });
}
elements.joinBtn.style.boxShadow = '0 0 15px var(--accent)';
setTimeout(() => elements.joinBtn.style.boxShadow = '', 2000);
}
elements.joinBtn.style.boxShadow = '0 0 15px var(--accent)';
setTimeout(() => elements.joinBtn.style.boxShadow = '', 2000);
} catch (_e) {
// Malformed invite link, ignore
}
@@ -1423,6 +1467,46 @@ elements.autoSyncNextEpisode.addEventListener('change', () => {
chrome.storage.local.set({ autoSyncNextEpisode: elements.autoSyncNextEpisode.checked });
});
function syncChatSettingsState() {
const enabled = elements.chatEnabled?.checked === true;
for (const control of [elements.chatNotifications, elements.chatPosition, elements.chatSize, elements.chatStartMode]) {
if (control) control.disabled = !enabled;
}
document.querySelectorAll('[data-chat-setting]').forEach(row => {
row.classList.toggle('is-disabled', !enabled);
row.setAttribute('aria-disabled', String(!enabled));
});
}
if (elements.chatEnabled) {
elements.chatEnabled.addEventListener('change', () => {
syncChatSettingsState();
chrome.storage.local.set({ chatEnabled: elements.chatEnabled.checked });
});
}
if (elements.chatNotifications) {
elements.chatNotifications.addEventListener('change', () => {
chrome.storage.local.set({ chatNotifications: elements.chatNotifications.checked });
});
}
if (elements.chatPosition) {
elements.chatPosition.addEventListener('change', () => {
const chatPosition = ['left', 'detached'].includes(elements.chatPosition.value) ? elements.chatPosition.value : 'right';
chrome.storage.local.set({ chatPosition });
});
}
if (elements.chatSize) {
elements.chatSize.addEventListener('change', () => {
const chatSize = ['compact', 'large', 'custom'].includes(elements.chatSize.value) ? elements.chatSize.value : 'standard';
chrome.storage.local.set({ chatSize });
});
}
if (elements.chatStartMode) {
elements.chatStartMode.addEventListener('change', () => {
chrome.storage.local.set({ chatStartMode: elements.chatStartMode.value === 'open' ? 'open' : 'bubble' });
});
}
if (elements.sendTabTitle) {
elements.sendTabTitle.addEventListener('change', () => {
chrome.storage.local.set({ sendTabTitle: elements.sendTabTitle.checked }, () => {
@@ -1533,11 +1617,15 @@ if (elements.langSelector) {
lastKnownPeers = res.peers || [];
if (res.lastActionState) updateLastActionUI(res.lastActionState, res.peers);
const data = await chrome.storage.local.get(['roomId', 'password', 'useCustomServer', 'serverUrl']);
updateUI(data.roomId, data.password, data.useCustomServer, data.serverUrl);
const data = await chrome.storage.local.get(['roomId', 'password', 'chatKey', 'useCustomServer', 'serverUrl']);
updateUI(data.roomId, data.password, data.useCustomServer, data.serverUrl, data.chatKey);
if (res.pendingTargetTabId) {
showSiteAccessNotice(res.pendingTargetHost, res.pendingTargetOriginPattern);
showSiteAccessNotice(
res.pendingTargetHost,
res.pendingTargetOriginPattern,
res.pendingTargetTabId
);
} else {
hideSiteAccessNotice();
}
@@ -1545,8 +1633,8 @@ if (elements.langSelector) {
if (res.episodeLobby) updateLobbyUI(res.episodeLobby, res.peers);
} else {
applyConnectionStatus('disconnected');
const data = await chrome.storage.local.get(['roomId', 'password', 'useCustomServer', 'serverUrl']);
updateUI(data.roomId, data.password, data.useCustomServer, data.serverUrl);
const data = await chrome.storage.local.get(['roomId', 'password', 'chatKey', 'useCustomServer', 'serverUrl']);
updateUI(data.roomId, data.password, data.useCustomServer, data.serverUrl, data.chatKey);
await populateTabs();
}
});
@@ -1674,7 +1762,12 @@ function showError(msg) {
// --- Action Handlers ---
elements.roomId.addEventListener('input', () => {
elements.roomId.value = elements.roomId.value.replace(/[^a-zA-Z0-9\-]/g, '');
elements.roomId.value = normalizeRoomId(elements.roomId.value);
pendingRoomCreation = false;
if (pendingInviteRoomId && elements.roomId.value !== pendingInviteRoomId) {
pendingInviteRoomId = '';
pendingInviteChatKey = '';
}
});
elements.joinBtn.addEventListener('click', async () => {
@@ -1685,8 +1778,8 @@ elements.joinBtn.addEventListener('click', async () => {
isProcessingConnection = false;
return;
}
const roomIdInput = elements.roomId.value.trim();
const isCreating = !roomIdInput;
const roomIdInput = normalizeRoomId(elements.roomId.value);
const isCreating = pendingRoomCreation || !roomIdInput;
elements.joinBtn.disabled = true;
elements.joinBtn.textContent = isCreating ? getMessage('BTN_STATE_CREATING') : getMessage('BTN_STATE_JOINING');
@@ -1746,17 +1839,34 @@ elements.joinBtn.addEventListener('click', async () => {
self.crypto.getRandomValues(array);
password = Array.from(array, byte => chars[byte % chars.length]).join('');
elements.password.value = password;
window.justCreatedRoom = true;
}
let chatKey = roomId === pendingInviteRoomId ? pendingInviteChatKey : '';
if (isCreating) {
const created = await new Promise(resolve => chrome.runtime.sendMessage({ type: 'CREATE_CHAT_KEY' }, resolve));
chatKey = normalizeChatKey(created?.chatKey);
if (!chatKey) {
pendingRoomCreation = false;
elements.joinBtn.disabled = false;
elements.joinBtn.textContent = getMessage('BTN_JOIN_ROOM');
if (joinBtnTimeout) { clearTimeout(joinBtnTimeout); joinBtnTimeout = null; }
isProcessingConnection = false;
showError(getMessage('CHAT_SEND_FAILED'));
return;
}
}
if (isCreating) window.justCreatedRoom = true;
pendingInviteRoomId = '';
pendingInviteChatKey = '';
pendingRoomCreation = false;
await chrome.storage.local.set({ serverUrl, roomId, password, useCustomServer: useCustom });
await chrome.storage.local.set({ serverUrl, roomId, password, chatKey, useCustomServer: useCustom });
elements.roomId.value = roomId;
// Tell background to connect
chrome.runtime.sendMessage({ type: 'CONNECT' });
// UI Feedback: Immediately switch state for better responsiveness
updateUI(roomId, password, useCustom, serverUrl);
updateUI(roomId, password, useCustom, serverUrl, chatKey);
});
elements.leaveBtn.addEventListener('click', async () => {
@@ -1764,7 +1874,7 @@ elements.leaveBtn.addEventListener('click', async () => {
isProcessingConnection = true;
clearConnectionErrorTimer();
chrome.runtime.sendMessage({ type: 'LEAVE_ROOM' });
await chrome.storage.local.set({ roomId: '', password: '' });
await chrome.storage.local.set({ roomId: '', password: '', chatKey: '' });
elements.roomId.value = '';
elements.password.value = '';
lastKnownPeers = [];
@@ -1790,6 +1900,7 @@ function handleCreateRoom() {
const password = secureGenerateId();
elements.roomId.value = roomId;
elements.password.value = password;
pendingRoomCreation = true;
window.justCreatedRoom = true;
// Auto-connect
@@ -1831,26 +1942,29 @@ elements.targetTab.addEventListener('change', async () => {
if (elements.siteAccessRetry) {
elements.siteAccessRetry.addEventListener('click', async () => {
const val = elements.targetTab.value;
const tabId = val ? parseInt(val) : null;
const tabId = normalizeTabId(elements.targetTab.value);
const tabTitle = elements.targetTab.options[elements.targetTab.selectedIndex]?.dataset.originalTitle || null;
if (!tabId) {
if (tabId === null || siteAccessTabId !== tabId || !siteAccessOriginPattern) {
showToast(getMessage('ERR_SELECT_VIDEO'), 'warning');
return;
}
const requestedOriginPattern = siteAccessOriginPattern;
const requestedTabId = siteAccessTabId;
elements.siteAccessRetry.disabled = true;
elements.targetTab.disabled = true;
try {
await requestOriginPermission(chrome, requestedOriginPattern);
const selectedTabId = parseInt(elements.targetTab.value);
const granted = await requestOriginPermission(chrome, requestedOriginPattern);
if (granted !== true) return;
const selectedTabId = normalizeTabId(elements.targetTab.value);
// permissions.onAdded may already have completed activation, or a
// newer selection may have replaced this request while it was open.
if (!siteAccessBlocked || selectedTabId !== tabId
|| siteAccessTabId !== requestedTabId
|| siteAccessOriginPattern !== requestedOriginPattern) return;
await selectTargetTab(tabId, tabTitle);
} finally {
elements.siteAccessRetry.disabled = false;
elements.siteAccessRetry.disabled = siteAccessBlocked
&& (!siteAccessOriginPattern || siteAccessTabId === null);
elements.targetTab.disabled = false;
}
});
@@ -1868,6 +1982,11 @@ elements.forceSyncBtn.addEventListener('click', async () => {
elements.forceSyncBtn.disabled = false;
return;
}
const tabId = normalizeTabId(status.targetTabId);
if (tabId === null) {
elements.forceSyncBtn.disabled = false;
return;
}
const mode = elements.forceSyncMode.value;
let targetTime = null;
@@ -1909,9 +2028,9 @@ elements.forceSyncBtn.addEventListener('click', async () => {
}
};
forceSyncResetTimer = setTimeout(forceSyncReset, syncTimeoutMs);
const tabId = parseInt(status.targetTabId);
const failForceSyncTime = () => {
if (forceSyncDone) return;
if (forceSyncResetTimer) { clearTimeout(forceSyncResetTimer); forceSyncResetTimer = null; }
showError(getMessage('ERR_NO_VIDEO_TAB'));
forceSyncDone = true;
@@ -1919,7 +2038,17 @@ elements.forceSyncBtn.addEventListener('click', async () => {
elements.forceSyncBtn.textContent = originalText;
};
const sendForceSync = (time) => {
const targetIsStillCurrent = () => new Promise(resolve => {
chrome.runtime.sendMessage({ type: 'GET_STATUS' }, currentStatus => {
if (chrome.runtime.lastError) {
resolve(false);
return;
}
resolve(normalizeTabId(currentStatus?.targetTabId) === tabId);
});
});
const sendForceSync = async (time) => {
if (time === null || time === undefined) {
failForceSyncTime();
return;
@@ -1929,15 +2058,28 @@ elements.forceSyncBtn.addEventListener('click', async () => {
failForceSyncTime();
return;
}
if (!(await targetIsStillCurrent())) {
failForceSyncTime();
return;
}
chrome.runtime.sendMessage({
type: 'CONTENT_EVENT',
action: EVENTS.FORCE_SYNC_PREPARE,
expectedTabId: tabId,
payload: { targetTime: target }
}, response => {
if (chrome.runtime.lastError || response?.status === 'stale_target') {
failForceSyncTime();
}
});
};
if (mode === 'jump-to-me') {
const retryQueryTime = () => {
const retryQueryTime = async () => {
if (!(await targetIsStillCurrent())) {
failForceSyncTime();
return;
}
chrome.tabs.sendMessage(tabId, { action: 'get_current_time' }, (retryResponse) => {
if (chrome.runtime.lastError || !retryResponse || !Number.isFinite(retryResponse.currentTime)) {
failForceSyncTime();
@@ -1952,10 +2094,18 @@ elements.forceSyncBtn.addEventListener('click', async () => {
return;
}
if (chrome.runtime.lastError || !response) {
chrome.runtime.sendMessage({ type: 'INJECT_CONTENT_SCRIPT', tabId }, (injectResponse) => {
chrome.runtime.sendMessage({
type: 'INJECT_CONTENT_SCRIPT',
tabId,
expectedCurrentTabId: tabId
}, (injectResponse) => {
if (chrome.runtime.lastError || !injectResponse || injectResponse.status !== 'ok') {
if (injectResponse?.status === 'host_permission_required') {
showSiteAccessNotice(injectResponse.host, injectResponse.originPattern);
showSiteAccessNotice(
injectResponse.host,
injectResponse.originPattern,
injectResponse.tabId
);
}
failForceSyncTime();
return;
@@ -2245,20 +2395,17 @@ chrome.runtime.onMessage.addListener((msg) => {
applyConnectionStatus(msg.status);
if (res && res.reconnectAttempts !== undefined) {
if (elements.connText) {
elements.connText.textContent = `Reconnecting... (${res.reconnectAttempts})`;
elements.connText.textContent = `${getMessage('STATUS_RECONNECTING')} (${res.reconnectAttempts})`;
}
}
});
}
} else if (msg.type === 'TARGET_TAB_READY') {
hideSiteAccessNotice();
populateTabs(null, msg.tabId);
refreshTargetAccessState().catch(() => {});
} else if (msg.type === 'TARGET_TAB_ACCESS_REQUIRED') {
showSiteAccessNotice(msg.host, msg.originPattern);
populateTabs(null, msg.tabId);
refreshTargetAccessState().catch(() => {});
} else if (msg.type === 'TARGET_TAB_CLEARED') {
hideSiteAccessNotice();
populateTabs();
refreshTargetAccessState().catch(() => {});
} else if (msg.type === 'PING_UPDATE') {
updatePingDisplay(msg.ping);
} else if (msg.type === 'HISTORY_UPDATE') {
@@ -2276,15 +2423,15 @@ chrome.runtime.onMessage.addListener((msg) => {
if (msg.success) {
// Final confirmation of join from background
chrome.storage.local.get(['roomId', 'password', 'useCustomServer', 'serverUrl'], (data) => {
updateUI(data.roomId, data.password, data.useCustomServer, data.serverUrl);
chrome.storage.local.get(['roomId', 'password', 'chatKey', 'useCustomServer', 'serverUrl'], (data) => {
updateUI(data.roomId, data.password, data.useCustomServer, data.serverUrl, data.chatKey);
const syncTabBtn = document.querySelector('.tab-btn[data-tab="tab-sync"]');
if (syncTabBtn) syncTabBtn.click();
showSelectVideoHint();
});
} else {
// Join failed: reset UI state
chrome.storage.local.set({ roomId: '', password: '' });
chrome.storage.local.set({ roomId: '', password: '', chatKey: '' });
elements.roomId.value = '';
elements.password.value = '';
updateUI(null, null);
@@ -2570,35 +2717,35 @@ function refreshDebugInfo() {
if (!state.found) {
// No video found — show diagnostic info
addSection('Video Detection');
addSection(getMessage('DEBUG_SECTION_VIDEO_DETECTION'));
const notFound = document.createElement('div');
notFound.style.cssText = 'color: var(--status-error-text); font-weight: 700; margin-bottom: 8px;';
notFound.textContent = 'NO VIDEO ELEMENT FOUND';
notFound.textContent = getMessage('DEBUG_VIDEO_NOT_FOUND');
elements.videoDebug.appendChild(notFound);
addField('Platform', state.platform || '?', 'var(--accent)');
addField('Page Title', state.pageTitle || '?');
addField('URL', state.url || '?');
addField('Video Tags', String(state.videoCount || 0));
addField('Shadow DOM', state.inShadowDom ? 'YES (checked)' : 'NO');
addField('Shadow DOM', state.inShadowDom ? getMessage('DEBUG_YES_CHECKED') : getMessage('DEBUG_NO'));
if (state.metadata) {
addSection('Media Session API');
addField('Title', state.metadata.title || 'n/a');
addField('Artist', state.metadata.artist || 'n/a');
addField('Album', state.metadata.album || 'n/a');
addSection(getMessage('DEBUG_SECTION_MEDIA_SESSION'));
addField('Title', state.metadata.title || getMessage('DEBUG_NA'));
addField('Artist', state.metadata.artist || getMessage('DEBUG_NA'));
addField('Album', state.metadata.album || getMessage('DEBUG_NA'));
}
const hint = document.createElement('div');
hint.style.cssText = 'margin-top: 12px; padding: 8px; background: color-mix(in oklch, var(--warning), transparent 88%); border-left: 3px solid var(--warning); border-radius: 4px; font-size: 10px; color: var(--text);';
hint.textContent = 'Is a video currently playing? The extension only detects <video> elements. Ensure media is actively loaded on the page.';
hint.textContent = getMessage('DEBUG_VIDEO_HINT');
elements.videoDebug.appendChild(hint);
return;
}
// Video found — full debug
addSection('Playback');
addField('State', state.paused ? 'PAUSED' : 'PLAYING', state.paused ? 'var(--text-muted)' : 'var(--status-success-text)');
addSection(getMessage('DEBUG_SECTION_PLAYBACK'));
addField('State', state.paused ? getMessage('DEBUG_STATE_PAUSED') : getMessage('DEBUG_STATE_PLAYING'), state.paused ? 'var(--text-muted)' : 'var(--status-success-text)');
addField('Time', `${state.currentTime.toFixed(2)}s / ${(state.duration || 0).toFixed(2)}s`);
addField('ReadyState', `${state.readyState} (${state.readyStateLabel || '?'})`,
state.readyState >= 3 ? 'var(--status-success-text)' : 'var(--status-warning-text)');
@@ -2617,7 +2764,7 @@ function refreshDebugInfo() {
const buttons = Array.isArray(quirk.seekButtons) ? quirk.seekButtons.slice(0, 4).join(' | ') : '';
if (buttons) addField(`${label} Buttons`, buttons);
}
addSection('Properties');
addSection(getMessage('DEBUG_SECTION_PROPERTIES'));
addField('Seeking', String(state.seeking));
addField('Ended', String(state.ended));
addField('Loop', String(state.loop));
@@ -2625,45 +2772,45 @@ function refreshDebugInfo() {
addField('Volume', String(state.volume));
addField('Speed', `${state.playbackRate}x`);
addSection('Dimensions');
addSection(getMessage('DEBUG_SECTION_DIMENSIONS'));
const dimsOk = state.videoWidth > 0 && state.videoHeight > 0;
addField('Resolution', `${state.videoWidth}x${state.videoHeight}`, dimsOk ? 'var(--status-success-text)' : 'var(--status-error-text)');
if (!dimsOk) {
const dimHint = document.createElement('div');
dimHint.style.cssText = 'color: var(--status-warning-text); font-size: 10px; margin: 2px 0 6px 12px;';
dimHint.textContent = '0x0 = video element hidden or not yet loaded';
dimHint.textContent = getMessage('DEBUG_DIMENSIONS_HINT');
elements.videoDebug.appendChild(dimHint);
}
if (state.error) {
addSection('Media Error');
addSection(getMessage('DEBUG_SECTION_MEDIA_ERROR'));
addField('Code', String(state.error.code), 'var(--status-error-text)');
addField('Message', state.error.message || '?', 'var(--status-error-text)');
}
addSection('Detection');
addSection(getMessage('DEBUG_SECTION_DETECTION'));
addField('Platform', state.platform || '?', 'var(--accent)');
addField('Video Count', String(state.videoCount || 0));
addField('Shadow DOM', state.inShadowDom ? 'YES' : 'NO');
addField('Shadow DOM', state.inShadowDom ? getMessage('DEBUG_YES') : getMessage('DEBUG_NO'));
addSection('Identification');
addSection(getMessage('DEBUG_SECTION_IDENTIFICATION'));
addField('URL', state.url);
addField('ID', state.id);
addField('CLASS', state.className);
addSection('Media Source');
addSection(getMessage('DEBUG_SECTION_MEDIA_SOURCE'));
addField('CurrentSrc', state.currentSrc);
addField('Src', state.src);
if (state.metadata) {
addSection('Media Session API');
addField('Title', state.metadata.title || 'n/a');
addField('Artist', state.metadata.artist || 'n/a');
addField('Album', state.metadata.album || 'n/a');
addSection(getMessage('DEBUG_SECTION_MEDIA_SESSION'));
addField('Title', state.metadata.title || getMessage('DEBUG_NA'));
addField('Artist', state.metadata.artist || getMessage('DEBUG_NA'));
addField('Album', state.metadata.album || getMessage('DEBUG_NA'));
}
if (state.dataAttributes && Object.keys(state.dataAttributes).length > 0) {
addSection('Data Attributes');
addSection(getMessage('DEBUG_SECTION_DATA_ATTRIBUTES'));
for (const [key, val] of Object.entries(state.dataAttributes)) {
addField(key.replace('data-', '').toUpperCase(), val);
}
+783 -1254
View File
File diff suppressed because it is too large Load Diff
+17 -9
View File
@@ -1,9 +1,12 @@
{
"name": "koalasync",
"version": "2.6.1",
"version": "3.0.0",
"description": "KoalaSync Build Scripts",
"private": true,
"type": "module",
"engines": {
"node": ">=20.9.0"
},
"scripts": {
"build:extension": "node scripts/build-extension.cjs",
"indexnow": "node website/submit-indexnow.cjs",
@@ -15,17 +18,22 @@
"verify": "node scripts/verify-release.mjs"
},
"devDependencies": {
"@playwright/test": "^1.61.1",
"@vitest/coverage-v8": "^4.1.9",
"@playwright/test": "^1.62.0",
"@vitest/coverage-v8": "^4.1.10",
"addons-linter": "^10.9.0",
"archiver": "^7.0.1",
"archiver": "^8.0.0",
"esbuild": "^0.28.1",
"eslint": "^10.4.0",
"eslint": "^10.8.0",
"eslint-plugin-no-unsanitized": "^4.1.5",
"fs-extra": "^11.2.0",
"fs-extra": "^11.4.0",
"htmlnano": "^3.4.0",
"sharp": "^0.34.5",
"svgo": "^4.0.1",
"vitest": "^4.1.9"
"sharp": "^0.35.3",
"svgo": "^4.0.2",
"vitest": "^4.1.10"
},
"overrides": {
"brace-expansion": "5.0.8",
"fast-uri": "3.1.4",
"postcss": "8.5.18"
}
}
+63 -11
View File
@@ -1,11 +1,63 @@
const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
const rootDir = path.join(__dirname, '..');
const extDir = path.join(rootDir, 'extension');
const distDir = path.join(rootDir, 'dist');
const baseManifestPath = path.join(extDir, 'manifest.base.json');
const localesDir = path.join(extDir, '_locales');
const requiredAppName = 'Watch Party - KoalaSync';
const maxAppNameLength = 75;
const maxAppDescLength = 132;
function countUnicodeCharacters(value) {
return Array.from(value).length;
}
function validateExtensionMetadata() {
const localeDirectories = fs.readdirSync(localesDir, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.sort();
for (const locale of localeDirectories) {
const messagesPath = path.join(localesDir, locale, 'messages.json');
let messages;
try {
messages = JSON.parse(fs.readFileSync(messagesPath, 'utf8'));
} catch (error) {
throw new Error(`CRITICAL: Invalid locale JSON at _locales/${locale}/messages.json: ${error.message}`);
}
if (!messages || typeof messages !== 'object' || Array.isArray(messages)) {
throw new Error(`CRITICAL: _locales/${locale}/messages.json must contain a JSON object`);
}
for (const key of ['appName', 'appDesc']) {
if (!messages[key] || typeof messages[key].message !== 'string' || messages[key].message.trim().length === 0) {
throw new Error(`CRITICAL: _locales/${locale}/messages.json is missing a valid ${key}.message`);
}
}
const appNameLength = countUnicodeCharacters(messages.appName.message);
const appDescLength = countUnicodeCharacters(messages.appDesc.message);
if (appNameLength > maxAppNameLength) {
throw new Error(`CRITICAL: _locales/${locale}/messages.json appName.message is ${appNameLength} characters; maximum is ${maxAppNameLength}`);
}
if (messages.appName.message !== requiredAppName) {
throw new Error(`CRITICAL: _locales/${locale}/messages.json appName.message must exactly equal "${requiredAppName}"`);
}
if (appDescLength > maxAppDescLength) {
throw new Error(`CRITICAL: _locales/${locale}/messages.json appDesc.message is ${appDescLength} characters; maximum is ${maxAppDescLength}`);
}
}
console.log(`✓ Validated localized extension metadata for ${localeDirectories.length} locales`);
}
validateExtensionMetadata();
// Ensure dist directory exists
if (fs.existsSync(distDir)) {
@@ -22,7 +74,7 @@ if (!fs.existsSync(extSharedDir)) {
fs.mkdirSync(extSharedDir, { recursive: true });
}
const sharedFiles = ['constants.js', 'blacklist.js', 'names.js', 'README.md'];
const sharedFiles = ['constants.js', 'blacklist.js', 'names.js', 'invite-links.js', 'README.md'];
for (const file of sharedFiles) {
const src = path.join(masterSharedDir, file);
const dest = path.join(extSharedDir, file);
@@ -31,7 +83,7 @@ for (const file of sharedFiles) {
}
fs.copyFileSync(src, dest);
}
console.log('✓ constants.js, blacklist.js, names.js, and README.md synced to extension/shared/');
console.log('✓ shared runtime files synced to extension/shared/');
// Read the base manifest
const baseManifest = JSON.parse(fs.readFileSync(baseManifestPath, 'utf8'));
@@ -149,18 +201,18 @@ function copyExtensionFiles(targetDir, browserName) {
}
// Helper to zip a directory
function zipDirectory(sourceDir, outPath) {
async function zipDirectory(sourceDir, outPath) {
const { ZipArchive } = await import('archiver');
return new Promise((resolve, reject) => {
const archive = archiver('zip', { zlib: { level: 9 } });
const archive = new ZipArchive({ zlib: { level: 9 } });
const stream = fs.createWriteStream(outPath);
archive
.directory(sourceDir, false)
.on('error', err => reject(err))
.pipe(stream);
archive.on('error', reject);
stream.on('close', () => resolve());
archive.finalize();
stream.on('error', reject);
archive.pipe(stream);
archive.directory(sourceDir, false);
void archive.finalize().catch(reject);
});
}
+65
View File
@@ -0,0 +1,65 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const [popupHtml, popupJs, overlayJs, backgroundJs] = await Promise.all([
readFile(path.join(repoRoot, 'extension', 'popup.html'), 'utf8'),
readFile(path.join(repoRoot, 'extension', 'popup.js'), 'utf8'),
readFile(path.join(repoRoot, 'extension', 'chat-overlay.js'), 'utf8'),
readFile(path.join(repoRoot, 'extension', 'background.js'), 'utf8')
]);
function detailsSection(marker) {
const markerIndex = popupHtml.indexOf(marker);
assert.notEqual(markerIndex, -1, `missing settings marker: ${marker}`);
const start = popupHtml.lastIndexOf('<details', markerIndex);
const end = popupHtml.indexOf('</details>', markerIndex);
assert.ok(start >= 0 && end > markerIndex, `invalid details section: ${marker}`);
return popupHtml.slice(start, end + '</details>'.length);
}
const chatSection = detailsSection('data-i18n="CHAT_TITLE"');
const syncSection = detailsSection('data-i18n="LABEL_SETTINGS_GROUP_SYNC"');
for (const id of ['chatEnabled', 'chatNotifications', 'chatPosition', 'chatSize', 'chatStartMode']) {
assert.match(chatSection, new RegExp(`id="${id}"`), `${id} belongs in the dedicated chat settings section`);
}
assert.doesNotMatch(syncSection, /id="chatEnabled"/, 'chat enablement must not remain under Playback & Sync');
for (const value of ['right', 'left', 'detached']) {
assert.match(chatSection, new RegExp(`<option value="${value}"`), `chat position supports ${value}`);
}
for (const value of ['compact', 'standard', 'large', 'custom']) {
assert.match(chatSection, new RegExp(`<option value="${value}"`), `chat size supports ${value}`);
}
for (const value of ['bubble', 'open']) {
assert.match(chatSection, new RegExp(`<option value="${value}"`), `chat start mode supports ${value}`);
}
for (const key of ['chatEnabled', 'chatNotifications', 'chatPosition', 'chatSize', 'chatStartMode']) {
assert.match(popupJs, new RegExp(`chrome\\.storage\\.local\\.set\\(\\{ ${key}`), `popup persists ${key}`);
assert.ok(backgroundJs.includes(`'${key}'`), `legacy sync purge includes ${key}`);
}
for (const key of ['chatPosition', 'chatSize', 'chatStartMode']) {
assert.ok(overlayJs.includes(`'${key}'`), `overlay reads ${key}`);
}
assert.match(backgroundJs, /chatEnabled:\s*data\.chatEnabled === true/, 'background reads chatEnabled with an off-by-default contract');
assert.match(backgroundJs, /chatNotifications:\s*data\.chatNotifications !== false/, 'chat message notifications default on');
assert.match(popupJs, /elements\.chatNotifications\.checked = localData\.chatNotifications !== false/, 'popup shows chat notifications on by default');
assert.match(popupJs, /elements\.browserNotifications\.checked = localData\.browserNotifications === true/, 'activity notifications remain off by default');
assert.match(overlayJs, /context\?\.enabled/, 'overlay consumes chat enablement from the validated background context');
assert.match(overlayJs, /SIZE_PRESETS[\s\S]*compact[\s\S]*standard[\s\S]*large/, 'overlay defines all size presets');
assert.match(
overlayJs,
/} else {\s*layout\.width = Number\(layout\.customWidth\) \|\| DEFAULT_WIDTH;\s*layout\.height = Number\(layout\.customHeight\) \|\| DEFAULT_HEIGHT;/,
'custom size restores its dedicated dimensions'
);
assert.match(overlayJs, /layout\.customWidth = rect\.width[\s\S]*layout\.customHeight = rect\.height/, 'detached resize updates the custom-size snapshot');
assert.match(overlayJs, /changes\.chatPosition[\s\S]*setMode/, 'position changes apply live');
assert.match(overlayJs, /changes\.chatSize[\s\S]*setSize/, 'size changes apply live');
assert.match(overlayJs, /changes\.chatStartMode[\s\S]*setOpened/, 'startup-mode changes apply live');
console.log('chat settings tests passed');
+63
View File
@@ -34,6 +34,11 @@ assert.deepEqual(describeTabUrl('http://localhost:8096/web/'), {
host: 'localhost:8096',
originPattern: 'http://localhost:8096/*'
});
assert.deepEqual(describeTabUrl('http://localhost:8096/web/', { includePort: false }), {
url: 'http://localhost:8096/web/',
host: 'localhost:8096',
originPattern: 'http://localhost/*'
});
assert.equal(describeTabUrl('chrome://extensions/'), null);
assert.equal(describeTabUrl('not a url'), null);
@@ -54,6 +59,39 @@ assert.equal(access.granted, false);
assert.equal(access.host, 'video.example');
assert.deepEqual(containsRequest, { origins: ['https://video.example/*'] });
let firefoxContainsRequest = null;
const firefoxChrome = {
runtime: { getBrowserInfo: async () => ({ name: 'Firefox' }) },
tabs: {
get: async tabId => ({
id: tabId,
url: 'http://localhost:8096/web/',
pendingUrl: 'https://different.example/loading'
})
},
permissions: {
contains: async request => {
firefoxContainsRequest = request;
return false;
}
}
};
const firefoxAccess = await inspectTabHostAccess(firefoxChrome, 42);
assert.equal(firefoxAccess.host, 'localhost:8096');
assert.equal(firefoxAccess.originPattern, 'http://localhost/*');
assert.deepEqual(firefoxContainsRequest, { origins: ['http://localhost/*'] });
const unknownPermissionChrome = {
runtime: {},
tabs: {
get: async tabId => ({ id: tabId, url: 'https://video.example/watch' })
},
permissions: {
contains: (_request, callback) => { callback(undefined); }
}
};
assert.equal((await inspectTabHostAccess(unknownPermissionChrome, 42)).granted, null);
let requestedTabId = null;
const requestChrome = {
permissions: {
@@ -88,6 +126,7 @@ assert.equal(await requestOriginPermission({ permissions: {} }, 'https://video.e
const background = fs.readFileSync(path.join(cwd(), 'extension', 'background.js'), 'utf8');
const popup = fs.readFileSync(path.join(cwd(), 'extension', 'popup.js'), 'utf8');
const popupHtml = fs.readFileSync(path.join(cwd(), 'extension', 'popup.html'), 'utf8');
const tabManager = fs.readFileSync(path.join(cwd(), 'extension', 'modules', 'tab-manager.js'), 'utf8');
assert.match(background, /await activateTargetTab\(message\.tabId, message\.tabTitle\)/,
'SET_TARGET_TAB must await successful activation before acknowledging it');
@@ -97,6 +136,18 @@ assert.match(background, /retryPendingTarget\(\)/,
'pending target must resume after the user grants access');
assert.match(background, /activationGeneration !== targetActivationGeneration/,
'stale concurrent tab activations must not overwrite the newest selection');
assert.match(background, /pendingTargetRequestId/,
'pending access recovery must use an identity token');
assert.match(background, /addedOrigins\.includes\(pending\.originPattern\)/,
'unrelated permission grants must not activate a pending target');
assert.match(background, /isCurrentTargetIdentity\(tabId, targetGeneration\)/,
'stale content-routing retries must not reactivate an old target');
assert.match(background, /message\.expectedTabId/,
'popup playback events must be rejected after their target changes');
assert.match(background, /completeForceSyncBeforeTargetChange\(selectedTabId\)/,
'a target switch must finish an in-flight force sync on the old target');
assert.match(background, /FORCE_SYNC_ACK'[\s\S]*ignored_unselected_tab/,
'stale content scripts must not acknowledge force sync for a new target');
const activateTargetBody = background.slice(
background.indexOf('async function activateTargetTab'),
background.indexOf('async function retryPendingTarget')
@@ -111,6 +162,18 @@ assert.match(popup, /response\?\.status === 'host_permission_required'/,
'popup must render the structured host-access failure');
assert.match(popup, /requestOriginPermission\(chrome, requestedOriginPattern\)/,
'retry button must request withheld host access directly');
assert.match(popup, /expectedCurrentTabId: tabId/,
'manual reinjection must be tied to the selected target identity');
assert.match(popup, /expectedTabId: tabId/,
'force sync must be tied to the tab whose time was sampled');
assert.doesNotMatch(tabManager, /injectContentScript/,
'tab reload recovery must use the guarded background activation path');
assert.equal(
(background.match(/tabs\.onRemoved\.addListener/g) || []).length
+ (tabManager.match(/tabs\.onRemoved\.addListener/g) || []).length,
1,
'target-tab closure must have exactly one state owner'
);
assert.match(popupHtml, /id="siteAccessNotice"/,
'popup must contain a persistent site-access notice');
+69 -1
View File
@@ -1,4 +1,5 @@
import assert from 'node:assert/strict';
import { Buffer } from 'node:buffer';
import http from 'node:http';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
@@ -22,7 +23,10 @@ async function c() {
function s(ws, evt, d={}) { ws.send(`42${JSON.stringify([evt,d])}`); }
function a(ws) { if (ws._m.length) { const r=ws._m.shift(); return r.startsWith('42') ? JSON.parse(r.substring(2)) : r; } return new Promise((resolve, reject) => { const t=setTimeout(()=>reject(Error('timeout')),3e3); const h=(d)=>{clearTimeout(t);ws.removeListener('message',h);const r=d.toString();resolve(r.startsWith('42')?JSON.parse(r.substring(2)):r);};ws.on('message',h);}); }
async function w(ws, evt, ms=3000) { const st=Date.now(); while(Date.now()-st<ms) { for(let i=0;i<ws._m.length;i++){const r=ws._m[i];ws._m.splice(i,1);if(r.startsWith('42')){try{const[e]=JSON.parse(r.substring(2));if(e===evt)return e}catch{/* skip */}}} await new Promise(r=>setTimeout(r,50));} throw Error(`wait:${evt}`); }
async function j(ws, rid, pid, pw=null) { s(ws,'join_room',{roomId:rid,peerId:pid,password:pw,protocolVersion:'1.0.0'}); assert.equal((await a(ws))[0],'room_data'); }
async function j(ws, rid, pid, pw=null, clientCapabilities=undefined) {
s(ws,'join_room',{roomId:rid,peerId:pid,password:pw,protocolVersion:'1.0.0',clientCapabilities});
assert.equal((await a(ws))[0],'room_data');
}
function close() { clients.forEach(w=>{try{w.close()}catch{/* ignore */}}); clients.length=0; }
// Test suite opens >10 connections/min — clear the IP connection counter so the
// connection rate limiter doesn't mask test failures (test-only, never at runtime).
@@ -70,6 +74,70 @@ try {
assert.equal(capEv, 'room_data');
assert.ok(Array.isArray(capData.capabilities) && capData.capabilities.includes('host-control'),
'ROOM_DATA advertises the host-control capability');
assert.ok(capData.capabilities.includes('chat'), 'ROOM_DATA advertises the chat capability');
assert.ok(capData.capabilities.includes('chat-v1'), 'ROOM_DATA advertises the versioned chat capability');
assert.equal(capData.chatHistory, undefined, 'ROOM_DATA never contains chat history');
close();
resetConnectionRate();
// --- Encrypted chat is a live-only canonical relay ---
const chatRoom = 'chat-'+Date.now();
const chat1 = await c(), chat2 = await c();
await j(chat1, chatRoom, 'alice', null, ['chat-v1']);
await j(chat2, chatRoom, 'bob', null, ['chat-v1']);
chat1._m.length = chat2._m.length = 0;
const ciphertext = Buffer.alloc(64, 7).toString('base64url');
s(chat1, 'chat_message', {
ciphertext,
id: 'spoofed-id', senderId: 'mallory', timestamp: 1, text: 'plaintext'
});
const [chat1Event, chat1Data] = await a(chat1);
const [chat2Event, chat2Data] = await a(chat2);
assert.equal(chat1Event, 'chat_message', 'sender receives canonical chat envelope');
assert.equal(chat2Event, 'chat_message', 'peer receives canonical chat envelope');
assert.deepEqual(chat2Data, chat1Data, 'all peers receive the same canonical envelope');
assert.equal(chat1Data.senderId, 'alice', 'relay stamps senderId');
assert.equal(chat1Data.ciphertext, ciphertext, 'relay preserves ciphertext');
assert.equal(chat1Data.text, undefined, 'relay drops plaintext fields');
assert.notEqual(chat1Data.id, 'spoofed-id', 'relay replaces client IDs');
assert.ok(Number.isFinite(chat1Data.timestamp) && chat1Data.timestamp > 1, 'relay stamps timestamp');
const late = await c();
s(late, 'join_room', { roomId: chatRoom, peerId: 'late', protocolVersion: '1.0.0' });
const [lateEvent, lateRoomData] = await a(late);
assert.equal(lateEvent, 'room_data');
assert.equal(lateRoomData.chatHistory, undefined, 'late joiner gets no chat backlog');
assert.equal(late._m.some(raw => raw.includes('chat_message')), false, 'late joiner receives no old message');
close();
resetConnectionRate();
// --- Mixed rollout: old non-chat clients never receive unknown chat events ---
const mixedChatRoom = 'chat-mixed-'+Date.now();
const newChat = await c(), oldNoChat = await c();
await j(newChat, mixedChatRoom, 'new-chat', null, ['chat-v1', 'chat-v1', 'future-chat', 42]);
await j(oldNoChat, mixedChatRoom, 'old-no-chat', null, ['chat-v2']);
newChat._m.length = oldNoChat._m.length = 0;
s(newChat, 'chat_message', { ciphertext });
await w(newChat, 'chat_message');
let oldReceivedChat = false;
try { await w(oldNoChat, 'chat_message', 500); oldReceivedChat = true; } catch { /* expected */ }
assert.equal(oldReceivedChat, false, 'old client receives no unknown chat event');
// The same old client remains fully usable for the pre-chat protocol.
newChat._m.length = oldNoChat._m.length = 0;
s(oldNoChat, 'play', { currentTime: 12 });
await w(newChat, 'play');
s(newChat, 'pause', { currentTime: 13 });
await w(oldNoChat, 'pause');
// First chat beta compatibility: sending a valid chat frame dynamically
// proves receive support even when that beta omitted clientCapabilities.
const firstBeta = await c();
await j(firstBeta, mixedChatRoom, 'first-beta');
firstBeta._m.length = newChat._m.length = 0;
s(firstBeta, 'chat_message', { ciphertext });
await w(firstBeta, 'chat_message');
await w(newChat, 'chat_message');
close();
resetConnectionRate();
+33
View File
@@ -22,6 +22,39 @@ const localeFiles = fs.readdirSync(localesDir)
let hasError = false;
const encryptedChatComparisonKeys = [
'ALT_TELEPARTY_INTRO',
'ALT_TELEPARTY_WHEN_TELE_BODY',
'ALT_TELEPARTY_MAIN_DIFF_BODY',
'ALT_TELEPARTY_LIMITS_BODY',
'ALT_TELEPARTY_SUMMARY_BODY',
'ALT_SCREEN_LIMITS_BODY',
'ALT_W2G_MAIN_DIFF_BODY',
'ALT_W2G_LIMITS_BODY',
'ALT_W2G_SUMMARY_BODY',
'ALT_SCENER_INTRO',
'ALT_SCENER_MAIN_DIFF_BODY',
'ALT_SCENER_LIMITS_BODY',
'ALT_KOSMI_LIMITS_BODY',
'ALT_TWOSEVEN_LIMITS_BODY'
];
const staleChatClaims = /does not currently have a built-in chat|has no built-in chat|no built-in chat box overlay|no built-in chat or webcam|no built-in chat or voice/i;
for (const key of encryptedChatComparisonKeys) {
const value = enDict[key];
if (typeof value !== 'string' || !value.includes('end-to-end encrypted')) {
hasError = true;
console.error(`❌ English comparison key ${key} must describe KoalaSync's end-to-end encrypted chat.`);
}
if (staleChatClaims.test(value)) {
hasError = true;
console.error(`❌ English comparison key ${key} still claims that KoalaSync has no built-in text chat.`);
}
}
if (!enDict.ALT_TWOSEVEN_LIMITS_BODY.includes('floating and dockable')) {
hasError = true;
console.error('❌ TwoSeven comparison must describe KoalaSync text chat as floating and dockable.');
}
console.log(`Auditing website i18n locales using ${enKeys.length} baseline keys from en.json...\n`);
for (const file of localeFiles) {
+64 -5
View File
@@ -12,11 +12,16 @@ const demoCss = fs.readFileSync(path.join(repoRoot, 'website', 'styles', 'demo.c
const landingPrimaryCss = fs.readFileSync(path.join(repoRoot, 'website', 'styles', 'landing-primary.css'), 'utf8');
const legalCss = fs.readFileSync(path.join(repoRoot, 'website', 'styles', 'legal.css'), 'utf8');
const joinPage = fs.readFileSync(path.join(repoRoot, 'website', 'join.html'), 'utf8');
const siteAccessHelpPage = fs.readFileSync(path.join(repoRoot, 'website', 'site-access-help.html'), 'utf8');
const websiteBuild = fs.readFileSync(path.join(repoRoot, 'website', 'build.cjs'), 'utf8');
const imprintPage = fs.readFileSync(path.join(repoRoot, 'website', 'imprint.html'), 'utf8');
const germanImprintPage = fs.readFileSync(path.join(repoRoot, 'website', 'impressum-de.html'), 'utf8');
const privacyPage = fs.readFileSync(path.join(repoRoot, 'website', 'privacy.html'), 'utf8');
const germanPrivacyPage = fs.readFileSync(path.join(repoRoot, 'website', 'datenschutz-de.html'), 'utf8');
const alternativesIndex = fs.readFileSync(path.join(repoRoot, 'website', 'alternatives', 'index.html'), 'utf8');
const alternativesCss = fs.readFileSync(path.join(repoRoot, 'website', 'styles', 'alternatives.css'), 'utf8');
const llmsText = fs.readFileSync(path.join(repoRoot, 'website', 'llms.txt'), 'utf8');
const websiteVersion = JSON.parse(fs.readFileSync(path.join(repoRoot, 'website', 'version.json'), 'utf8')).version;
const mockupStart = template.indexOf('<div class="extension-mockup">');
const mockupEnd = template.indexOf('<div class="demo-invite-fly"', mockupStart);
@@ -25,6 +30,32 @@ if (mockupStart === -1 || mockupEnd === -1) {
}
const mockup = template.slice(mockupStart, mockupEnd);
const privacyChatContracts = [
[privacyPage, 'English', [
'end-to-end encrypted in the browser extension before transmission',
'256-bit AES-GCM key',
'the relay does not receive the chat secret',
'does not create a server-side chat history',
'maximum of 200 visible message or activity entries',
"browser's notification API"
]],
[germanPrivacyPage, 'German', [
'vor der Übertragung Ende-zu-Ende verschlüsselt',
'256-Bit-Schlüssel für AES-GCM',
'der Relay das Chat-Secret nicht erhält',
'keine serverseitige Chat-Historie',
'höchstens 200 Nachrichten- oder Aktivitätseinträge',
'Benachrichtigungs-API des Browsers'
]]
];
for (const [page, language, statements] of privacyChatContracts) {
for (const statement of statements) {
if (!page.includes(statement)) {
throw new Error(`${language} privacy policy is missing encrypted-chat disclosure: ${statement}`);
}
}
}
const themeSensitiveControls = [
['video selector', /<select[^>]+id="demo-video-select"[^>]+>/],
['sync target', /<div[^>]+class="mock-input"[^>]+title="Choose sync target"[^>]*>/],
@@ -63,6 +94,35 @@ for (const section of requiredLlmsSections) {
if (/\bWebRTC\b|\bport 54000\b|peer-to-peer by design/i.test(llmsText)) {
throw new Error('llms.txt must describe the current WebSocket relay architecture without obsolete WebRTC or port claims');
}
if (!llmsText.includes('- built-in voice, video or webcam chat') || llmsText.includes('- built-in text, voice or webcam chat')) {
throw new Error('llms.txt must distinguish built-in encrypted text chat from unsupported voice, video or webcam chat');
}
if (!llmsText.includes(`Current website release: ${websiteVersion}`)) {
throw new Error(`llms.txt release must match website/version.json (${websiteVersion})`);
}
const requiredSupportSocialMetadata = [
'name="twitter:card" content="summary_large_image"',
'name="twitter:title"',
'name="twitter:description"',
'name="twitter:image"',
'"datePublished"',
'"dateModified"',
'"mainEntityOfPage"'
];
for (const metadata of requiredSupportSocialMetadata) {
if (!siteAccessHelpPage.includes(metadata)) {
throw new Error(`site-access-help.html is missing SEO metadata: ${metadata}`);
}
}
if (!websiteBuild.includes("['log', '-1', '--format=%cs', '--', ...sourceFiles]")
|| !websiteBuild.includes("lastmod(['website/site-access-help.html'])")
|| !websiteBuild.includes("['rev-parse', '--is-shallow-repository']")
|| !websiteBuild.includes("['diff', '--name-only', '--']")
|| !websiteBuild.includes("['diff', '--cached', '--name-only', '--']")
|| !websiteBuild.includes("['ls-files', '--others', '--exclude-standard', '--']")
|| !websiteBuild.includes('sourceFiles.some(file => dirtySourceFiles.has(file))')) {
throw new Error('Sitemap lastmod values must come from the mapped source files in Git');
}
const documentedRelayUrls = [...llmsText.matchAll(/`(wss:\/\/[^`\s]+)`/g)].map(([, value]) => new URL(value));
const hasCanonicalPublicRelay = documentedRelayUrls.some((url) => (
url.protocol === 'wss:' &&
@@ -151,11 +211,9 @@ if (!/\.legal-inline-link\s*\{[^}]*text-decoration:\s*underline/s.test(legalCss)
throw new Error('Legal prose links must use a non-color underline cue');
}
for (const [name, page] of [['imprint.html', imprintPage], ['impressum-de.html', germanImprintPage]]) {
if ((page.match(/class="legal-inline-link"/g) || []).length !== 3) {
throw new Error(`${name} must mark all three prose links as legal-inline-link`);
}
if ((page.match(/class="legal-inline-label"/g) || []).length !== 2 || /opacity:\s*0\.6/.test(page)) {
throw new Error(`${name} contact labels must use full-opacity theme text`);
if (!/rel="canonical" href="https:\/\/koalastuff\.net\/legal"/.test(page)
|| !/href="https:\/\/koalastuff\.net\/legal"/.test(page)) {
throw new Error(`${name} must point to the central KoalaStuff legal notice`);
}
}
@@ -176,6 +234,7 @@ if (!/animation:\s*none\s*!important/.test(reducedMotionRule)) {
console.log('Extension mockup theme-sensitive text uses theme-aware colors');
console.log('Landing CSS is render-blocking, single-request, and cascade-stable');
console.log('Landing head discovers a complete and architecture-accurate llms.txt');
console.log('Support-page social metadata and Git-derived sitemap dates remain truthful');
console.log('Website self-hosting examples include production secrets and consistent networking');
console.log('Foreground film birds use complete, always-on wing and glide animations');
console.log('All Getting Started mockups define explicit light-theme surfaces');
+1
View File
@@ -20,6 +20,7 @@ const checks = [
['content video finder', 'node', ['scripts/test-content-video-finder.cjs']],
['audio settings', 'node', ['scripts/test-audio-settings.mjs']],
['popup refresh cooldown', 'node', ['scripts/test-popup-refresh-cooldown.mjs']],
['chat settings', 'node', ['scripts/test-chat-settings.mjs']],
['host access recovery', 'node', ['scripts/test-host-access.mjs']],
['server syntax index', 'node', ['-c', 'server/index.js']],
['server syntax ops', 'node', ['-c', 'server/ops.js']],
+6 -5
View File
@@ -1,15 +1,16 @@
FROM node:20-alpine
FROM node:24-alpine
WORKDIR /app
# Copy shared protocol first
COPY shared/ ./shared/
COPY --chown=node:node shared/ ./shared/
# Copy server
COPY server/package*.json ./server/
RUN cd server && npm install --production
COPY server/ ./server/
COPY --chown=node:node server/package*.json ./server/
RUN cd server && npm ci --omit=dev
COPY --chown=node:node server/ ./server/
WORKDIR /app/server
USER node
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
+24
View File
@@ -0,0 +1,24 @@
import { randomUUID } from 'node:crypto';
export const CHAT_CIPHERTEXT_MAX_BYTES = 2028;
export const CHAT_CIPHERTEXT_MIN_BYTES = 29;
export function normalizeChatCiphertext(value) {
if (typeof value !== 'string' || !/^[A-Za-z0-9_-]+$/.test(value) || value.length % 4 === 1) return null;
const bytes = Buffer.from(value, 'base64url');
if (bytes.length < CHAT_CIPHERTEXT_MIN_BYTES || bytes.length > CHAT_CIPHERTEXT_MAX_BYTES) return null;
if (bytes.toString('base64url') !== value) return null;
return value;
}
export function createChatEnvelope(data, senderId, now = Date.now, createId = randomUUID) {
if (!data || typeof data !== 'object' || typeof senderId !== 'string' || !senderId) return null;
const ciphertext = normalizeChatCiphertext(data.ciphertext);
if (!ciphertext) return null;
return {
id: createId(),
senderId,
timestamp: now(),
ciphertext
};
}
+43
View File
@@ -0,0 +1,43 @@
import { describe, expect, it } from 'vitest';
import { Buffer } from 'node:buffer';
import {
CHAT_CIPHERTEXT_MAX_BYTES,
createChatEnvelope,
normalizeChatCiphertext
} from './chat.js';
const encoded = (length) => Buffer.alloc(length, 7).toString('base64url');
describe('encrypted chat relay envelopes', () => {
it('accepts canonical base64url within the encrypted message bounds', () => {
expect(normalizeChatCiphertext(encoded(29))).toBe(encoded(29));
expect(normalizeChatCiphertext(encoded(CHAT_CIPHERTEXT_MAX_BYTES))).toBe(encoded(CHAT_CIPHERTEXT_MAX_BYTES));
});
it('rejects malformed, padded, too-small, and oversized payloads', () => {
expect(normalizeChatCiphertext('<script>')).toBeNull();
expect(normalizeChatCiphertext(`${encoded(29)}=`)).toBeNull();
expect(normalizeChatCiphertext(encoded(28))).toBeNull();
expect(normalizeChatCiphertext(encoded(CHAT_CIPHERTEXT_MAX_BYTES + 1))).toBeNull();
expect(normalizeChatCiphertext('A')).toBeNull();
});
it('stamps authoritative metadata and keeps only ciphertext from the sender', () => {
const ciphertext = encoded(64);
const envelope = createChatEnvelope({
ciphertext,
id: 'spoofed',
senderId: 'mallory',
timestamp: 1,
text: 'plaintext must not survive'
}, 'alice', () => 1234, () => 'server-id');
expect(envelope).toEqual({
id: 'server-id',
senderId: 'alice',
timestamp: 1234,
ciphertext
});
expect(JSON.stringify(envelope)).not.toContain('plaintext');
});
});
+63 -1
View File
@@ -5,6 +5,7 @@ import { Server } from 'socket.io';
import crypto from 'crypto';
import dotenv from 'dotenv';
import { EVENTS, OFFICIAL_SERVER_TOKEN, PROTOCOL_VERSION, CONTROL_MODES, CAPABILITIES } from '../shared/constants.js';
import { createChatEnvelope } from './chat.js';
import {
buildHealthPayload,
checkCooldown,
@@ -19,6 +20,7 @@ import {
connectionCounts,
failedAuthAttempts,
eventCounts,
chatMessageCounts,
healthCounts,
adminMetricsAuthCounts,
roomListCooldowns,
@@ -28,6 +30,7 @@ import {
recordAuthFailure,
checkConnectionRate,
checkEventRate,
checkChatMessageRate,
checkHealthRate,
checkAdminMetricsAuthRate,
checkLeaveRoomRate,
@@ -171,7 +174,26 @@ const HOST_ONLY_GATED_EVENTS = new Set([
// Features this relay supports, advertised to clients in ROOM_DATA so they can
// enable matching UI/behavior only when the server actually backs it. Append a
// flag here when a new server-gated feature ships (e.g. co-host promotion).
const SERVER_CAPABILITIES = [CAPABILITIES.HOST_CONTROL, CAPABILITIES.CO_HOST];
const SERVER_CAPABILITIES = [
CAPABILITIES.HOST_CONTROL,
CAPABILITIES.CO_HOST,
CAPABILITIES.CHAT,
CAPABILITIES.CHAT_V1
];
function normalizeClientCapabilities(value) {
if (!Array.isArray(value)) return [];
return [...new Set(value.slice(0, 16)
.filter(capability => typeof capability === 'string')
.map(capability => capability.substring(0, 32))
.filter(capability => capability === CAPABILITIES.CHAT_V1)
)];
}
function clientSupportsChat(socket) {
return Array.isArray(socket?.data?.clientCapabilities) &&
socket.data.clientCapabilities.includes(CAPABILITIES.CHAT_V1);
}
// M-4: minimum interval between CONTROL_MODE changes per room. Stops a rapidly
// toggling host from thrashing every guest's UI (locked/unlocked/locked...) and
@@ -353,6 +375,7 @@ io.on('connection', (socket) => {
const username = typeof payload.username === 'string' ? payload.username.substring(0, 30) : null;
const tabTitle = typeof payload.tabTitle === 'string' ? payload.tabTitle.substring(0, 100) : null;
const mediaTitle = typeof payload.mediaTitle === 'string' ? payload.mediaTitle.substring(0, 100) : null;
const clientCapabilities = normalizeClientCapabilities(payload.clientCapabilities);
if (!roomId || !peerId) return; // Guard: empty or invalid after sanitization
@@ -367,6 +390,7 @@ io.on('connection', (socket) => {
// Cleanup old room if re-joining
const oldMapping = socketToRoom.get(socket.id);
if (oldMapping && oldMapping.roomId === roomId && oldMapping.peerId === peerId) {
socket.data.clientCapabilities = clientCapabilities;
return; // Already in this room with same peerId, ignore to prevent spam
}
if (oldMapping && oldMapping.roomId !== roomId) {
@@ -490,6 +514,7 @@ io.on('connection', (socket) => {
}
socket.join(roomId);
socket.data.clientCapabilities = clientCapabilities;
room.peers.add(socket.id);
room.peerIds.set(socket.id, peerId);
room.peerData.set(socket.id, {
@@ -844,8 +869,45 @@ io.on('connection', (socket) => {
}
});
socket.on(EVENTS.CHAT_MESSAGE, (data) => {
try {
if (!checkEventRate(socket.id) || !checkChatMessageRate(socket.id)) {
log('SECURITY', `Encrypted chat rate limit exceeded for socket: ${socket.id}`);
socket.disconnect(true);
return;
}
const mapping = socketToRoom.get(socket.id);
if (!mapping) return;
const room = rooms.get(mapping.roomId);
if (!room) return;
const envelope = createChatEnvelope(data, mapping.peerId);
if (!envelope) return;
// Transitional compatibility: the first chat beta did not announce
// clientCapabilities. Successfully sending a canonical chat frame is
// sufficient proof that this socket understands the v1 receive event.
if (!clientSupportsChat(socket)) {
socket.data.clientCapabilities = [
...(socket.data.clientCapabilities || []),
CAPABILITIES.CHAT_V1
];
}
room.lastActivity = Date.now();
for (const socketId of room.peers) {
const recipient = io.sockets.sockets.get(socketId);
if (clientSupportsChat(recipient)) {
recipient.emit(EVENTS.CHAT_MESSAGE, envelope);
}
}
} catch (err) {
log('ERROR', `CHAT_MESSAGE handler error: ${err.message}`);
}
});
socket.on('disconnect', () => {
eventCounts.delete(socket.id);
chatMessageCounts.delete(socket.id);
roomListCooldowns.delete(socket.id);
leaveRoomCounts.delete(socket.id);
const mapping = socketToRoom.get(socket.id);
+90 -51
View File
@@ -30,12 +30,12 @@
}
},
"node_modules/@types/node": {
"version": "25.6.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
"version": "26.1.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
"integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.19.0"
"undici-types": "~8.3.0"
}
},
"node_modules/@types/ws": {
@@ -70,20 +70,20 @@
}
},
"node_modules/body-parser": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
"integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"content-type": "^2.0.0",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.7.0",
"http-errors": "^2.0.1",
"iconv-lite": "^0.7.2",
"on-finished": "^2.4.1",
"qs": "^6.14.1",
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
"qs": "^6.15.2",
"raw-body": "^3.0.2",
"type-is": "^2.1.0"
},
"engines": {
"node": ">=18"
@@ -93,6 +93,19 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/body-parser/node_modules/content-type": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
"integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -347,9 +360,9 @@
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -526,9 +539,9 @@
}
},
"node_modules/hasown": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
"integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -558,9 +571,9 @@
}
},
"node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
"integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -604,12 +617,16 @@
}
},
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz",
"integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/merge-descriptors": {
@@ -739,12 +756,13 @@
}
},
"node_modules/qs": {
"version": "6.15.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
"integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"version": "6.15.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
"integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
"es-define-property": "^1.0.1",
"side-channel": "^1.1.1"
},
"engines": {
"node": ">=0.6"
@@ -754,12 +772,16 @@
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
"integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/raw-body": {
@@ -851,14 +873,14 @@
"license": "ISC"
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"object-inspect": "^1.13.4",
"side-channel-list": "^1.0.1",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
@@ -951,9 +973,9 @@
}
},
"node_modules/socket.io-parser": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz",
"integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==",
"version": "4.2.7",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz",
"integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
@@ -1025,23 +1047,40 @@
}
},
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
"integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"content-type": "^2.0.0",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/type-is/node_modules/content-type": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
"integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/undici-types": {
"version": "7.19.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
"license": "MIT"
},
"node_modules/unpipe": {
@@ -1069,9 +1108,9 @@
"license": "ISC"
},
"node_modules/ws": {
"version": "8.21.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
"version": "8.21.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
+21
View File
@@ -12,6 +12,8 @@ export const CONNECTION_RATE_LIMIT = 10; // max new connections per IP
export const CONNECTION_RATE_WINDOW_MS = 60000; // 1 minute
export const EVENT_RATE_LIMIT = 50; // max relayed events per socket per window
export const EVENT_RATE_WINDOW_MS = 10000; // 10 seconds
export const CHAT_MESSAGE_RATE_LIMIT = 10; // max encrypted chat messages per socket per window
export const CHAT_MESSAGE_RATE_WINDOW_MS = 10000; // 10 seconds
export const HEALTH_RATE_WINDOW_MS = 60000; // 1 minute
export const ADMIN_METRICS_AUTH_WINDOW_MS = 60000; // 1 minute
export const LEAVE_ROOM_RATE_LIMIT = 10; // max LEAVE_ROOM events per socket per window
@@ -20,6 +22,7 @@ export const LEAVE_ROOM_RATE_WINDOW_MS = 60000; // 1 minute
export const connectionCounts = new Map(); // ip -> { count, resetTime }
export const failedAuthAttempts = new Map(); // Map<IP+RoomID, {count, lastAttempt}>
export const eventCounts = new Map(); // socketId -> { count, resetTime }
export const chatMessageCounts = new Map(); // socketId -> { count, resetTime }
export const healthCounts = new Map(); // ip -> { count, resetTime }
export const adminMetricsAuthCounts = new Map(); // ip -> { count, resetTime }
export const roomListCooldowns = new Map(); // socketId -> last allowed timestamp
@@ -28,6 +31,7 @@ export const leaveRoomCounts = new Map(); // socketId -> { count, resetTime }
export const rateLimitDenied = {
connections: 0,
events: 0,
chatMessages: 0,
health: 0,
adminMetricsAuth: 0,
roomList: 0,
@@ -104,6 +108,17 @@ export function checkEventRate(socketId) {
return false;
}
export function checkChatMessageRate(socketId) {
const now = Date.now();
const entry = chatMessageCounts.get(socketId) || { count: 0, resetTime: now + CHAT_MESSAGE_RATE_WINDOW_MS };
if (now > entry.resetTime) { entry.count = 0; entry.resetTime = now + CHAT_MESSAGE_RATE_WINDOW_MS; }
entry.count++;
chatMessageCounts.set(socketId, entry);
if (entry.count <= CHAT_MESSAGE_RATE_LIMIT) return true;
rateLimitDenied.chatMessages++;
return false;
}
export function checkHealthRate(ip) {
const now = Date.now();
const entry = healthCounts.get(ip) || { count: 0, resetTime: now + HEALTH_RATE_WINDOW_MS };
@@ -163,6 +178,11 @@ export function startRateLimitCleanup(io) {
eventCounts.delete(socketId);
}
}
for (const [socketId, entry] of chatMessageCounts.entries()) {
if (now > entry.resetTime || !io.sockets.sockets.has(socketId)) {
chatMessageCounts.delete(socketId);
}
}
for (const [socketId, entry] of leaveRoomCounts.entries()) {
if (now > entry.resetTime || !io.sockets.sockets.has(socketId)) {
leaveRoomCounts.delete(socketId);
@@ -189,6 +209,7 @@ export function clearRateLimitMaps() {
connectionCounts.clear();
failedAuthAttempts.clear();
eventCounts.clear();
chatMessageCounts.clear();
healthCounts.clear();
adminMetricsAuthCounts.clear();
+30
View File
@@ -1,6 +1,10 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import {
checkLeaveRoomRate,
checkChatMessageRate,
CHAT_MESSAGE_RATE_LIMIT,
CHAT_MESSAGE_RATE_WINDOW_MS,
chatMessageCounts,
LEAVE_ROOM_RATE_LIMIT,
LEAVE_ROOM_RATE_WINDOW_MS,
rateLimitDenied,
@@ -99,6 +103,32 @@ describe('LEAVE_ROOM Rate Limiter', () => {
});
});
describe('CHAT_MESSAGE Rate Limiter', () => {
const socketId = 'chat-socket';
beforeEach(() => {
clearRateLimitMaps();
rateLimitDenied.chatMessages = 0;
});
afterEach(() => clearRateLimitMaps());
it('allows ten messages per ten-second window and blocks the next', () => {
for (let i = 0; i < CHAT_MESSAGE_RATE_LIMIT; i++) {
expect(checkChatMessageRate(socketId)).toBe(true);
}
expect(checkChatMessageRate(socketId)).toBe(false);
expect(rateLimitDenied.chatMessages).toBe(1);
});
it('resets after the window expires', () => {
for (let i = 0; i <= CHAT_MESSAGE_RATE_LIMIT; i++) checkChatMessageRate(socketId);
const entry = chatMessageCounts.get(socketId);
entry.resetTime = Date.now() - CHAT_MESSAGE_RATE_WINDOW_MS - 1;
expect(checkChatMessageRate(socketId)).toBe(true);
});
});
describe('Rate Limit Constants', () => {
it('should have correct rate limit values', () => {
expect(LEAVE_ROOM_RATE_LIMIT).toBe(10);
+7 -2
View File
@@ -7,7 +7,7 @@
*/
export const PROTOCOL_VERSION = "1.0.0";
export const APP_VERSION = "2.6.1";
export const APP_VERSION = "3.0.0";
export const OFFICIAL_SERVER_URL = 'wss://syncserver.koalastuff.net';
export const OFFICIAL_LANDING_PAGE_URL = 'https://sync.koalastuff.net';
@@ -57,6 +57,9 @@ export const EVENTS = {
EPISODE_READY: "episode_ready", // Response: loaded the episode and paused at 0:00
EPISODE_LOBBY_CANCEL: "episode_lobby_cancel", // Broadcast: cancel active lobby and resume
// Ephemeral end-to-end encrypted chat
CHAT_MESSAGE: "chat_message", // Ciphertext relay; no server history
// Ping / Latency
PING: "ping", // { t: timestamp, target?: peerId } — empty target = server echo
PONG: "pong" // server responds with same { t } for client RTT calculation
@@ -77,7 +80,9 @@ export const CONTROL_MODES = {
// simply ignore the field. Add a flag here as each server-gated feature lands.
export const CAPABILITIES = {
HOST_CONTROL: 'host-control',
CO_HOST: 'co-host' // owner promotes guests to additional controllers
CO_HOST: 'co-host', // owner promotes guests to additional controllers
CHAT: 'chat', // legacy server capability used by the first chat beta
CHAT_V1: 'chat-v1' // versioned client/server chat wire contract
};
export const HEARTBEAT_INTERVAL = 15000; // 15s
+77
View File
@@ -0,0 +1,77 @@
(function exposeInviteLinks(root) {
const J2_PREFIX = '#j2:';
const LEGACY_PREFIX = '#join:';
function parseJ2Hash(hash) {
if (typeof hash !== 'string' || !hash.startsWith(J2_PREFIX)) return null;
const params = new URLSearchParams(hash.slice(J2_PREFIX.length));
const roomId = params.get('r') || '';
const password = params.get('p') || '';
const chatKey = params.get('k') || '';
const serverUrl = params.get('u') || '';
if (!roomId || !password || !chatKey) return null;
return {
format: 'j2',
roomId,
password,
chatKey,
useCustomServer: serverUrl.length > 0,
serverUrl
};
}
function parseLegacyHash(hash) {
if (typeof hash !== 'string' || !hash.startsWith(LEGACY_PREFIX)) return null;
const parts = hash.slice(LEGACY_PREFIX.length).split(':');
const roomId = parts.shift() || '';
let useCustomServer = false;
let serverUrl = '';
if (parts.length >= 3) {
const flag = parts.at(-2);
const rawUrl = parts.at(-1) || '';
let decodedUrl = '';
try {
decodedUrl = decodeURIComponent(rawUrl);
} catch (_) {
return null;
}
const custom = flag === '1' && /^(?:ws|wss):\/\//.test(decodedUrl);
const official = flag === '0' && rawUrl === '';
if (custom || official) {
parts.splice(-2, 2);
useCustomServer = custom;
serverUrl = custom ? decodedUrl : '';
}
}
const password = parts.join(':');
if (!roomId || !password) return null;
return {
format: 'legacy',
roomId,
password,
chatKey: '',
useCustomServer,
serverUrl
};
}
function parseInviteHash(hash) {
return parseJ2Hash(hash) || parseLegacyHash(hash);
}
function buildJ2Hash({ roomId, password, chatKey, serverUrl = '' }) {
if (!roomId || !password || !chatKey) throw new TypeError('roomId, password, and chatKey are required');
const params = new URLSearchParams({ r: roomId, p: password, k: chatKey });
if (serverUrl) params.set('u', serverUrl);
return `${J2_PREFIX}${params.toString()}`;
}
root.KoalaSyncInviteLinks = Object.freeze({
J2_PREFIX,
LEGACY_PREFIX,
parseInviteHash,
buildJ2Hash
});
})(globalThis);
+58
View File
@@ -0,0 +1,58 @@
import { beforeAll, describe, expect, it } from 'vitest';
beforeAll(async () => {
await import('./invite-links.js');
});
describe('invite links', () => {
it('round-trips official and custom j2 links', () => {
const api = globalThis.KoalaSyncInviteLinks;
const official = api.buildJ2Hash({
roomId: 'SAPPHIRE-DUCK-49',
password: '0X:UK3C',
chatKey: 'R5Ti1nxp0crfAFHf3gVncw'
});
expect(api.parseInviteHash(official)).toEqual({
format: 'j2',
roomId: 'SAPPHIRE-DUCK-49',
password: '0X:UK3C',
chatKey: 'R5Ti1nxp0crfAFHf3gVncw',
useCustomServer: false,
serverUrl: ''
});
const custom = api.buildJ2Hash({
roomId: 'SILENT-EAGLE-90',
password: '30PXPD',
chatKey: 'R5Ti1nxp0crfAFHf3gVncw',
serverUrl: 'wss://sync.example.test/socket?region=eu'
});
expect(api.parseInviteHash(custom)).toMatchObject({
useCustomServer: true,
serverUrl: 'wss://sync.example.test/socket?region=eu'
});
});
it('keeps legacy links compatible, including colons in passwords', () => {
const parse = globalThis.KoalaSyncInviteLinks.parseInviteHash;
expect(parse('#join:ROOM-1:pass:with:colons')).toMatchObject({
format: 'legacy',
password: 'pass:with:colons',
chatKey: '',
useCustomServer: false
});
expect(parse('#join:ROOM-1:pass:with:colons:1:wss%3A%2F%2Frelay.example')).toMatchObject({
password: 'pass:with:colons',
useCustomServer: true,
serverUrl: 'wss://relay.example'
});
});
it('rejects incomplete or malformed invite hashes', () => {
const parse = globalThis.KoalaSyncInviteLinks.parseInviteHash;
expect(parse('#j2:r=ROOM&p=PASS')).toBeNull();
expect(parse('#join:ROOM')).toBeNull();
expect(parse('#join:ROOM:PASS:1:%E0%A4%A')).toBeNull();
expect(parse('#other:r=ROOM&p=PASS&k=KEY')).toBeNull();
});
});
+3 -1
View File
@@ -8,7 +8,9 @@ export default defineConfig({
'server/**/*.test.js',
'server/**/*.test.mjs',
'shared/**/*.test.js',
'shared/**/*.test.mjs'
'shared/**/*.test.mjs',
'extension/**/*.test.js',
'extension/**/*.test.mjs'
],
coverage: {
provider: 'v8',
+1 -1
View File
@@ -135,7 +135,7 @@
<div class="container">
<p>&copy; 2026 KoalaSync. Open source under the MIT License.</p>
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
<a href="/imprint" style="color: var(--text-muted); text-decoration: none;">Legal Notice</a>
<a href="https://koalastuff.net/legal" style="color: var(--text-muted); text-decoration: none;">Legal Notice</a>
<a href="/privacy" style="color: var(--text-muted); text-decoration: none;">Privacy Policy</a>
</div>
</div>
+3 -3
View File
@@ -183,7 +183,7 @@
<div class="nav-links">
<a href="{{ASSET_PATH}}" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="width: 16px; height: 16px; display: block;"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
<span>Home</span>
<span>{{NAV_HOME}}</span>
</a>
<a href="https://github.com/Shik3i/KoalaSync" target="_blank" rel="noopener" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" aria-hidden="true" style="display: block;"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
@@ -192,7 +192,7 @@
</div>
<div class="nav-right">
<div class="lang-select-container">
<select class="lang-dropdown" aria-label="Select Language">
<select class="lang-dropdown" aria-label="{{ARIA_SELECT_LANGUAGE}}">
<option value="en">🇬🇧 English</option>
<option value="de">🇩🇪 Deutsch</option>
<option value="fr">🇫🇷 Français</option>
@@ -211,7 +211,7 @@
</select>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="chevron-icon"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<button class="hamburger" aria-label="Menu" aria-expanded="false"></button>
<button class="hamburger" aria-label="{{ARIA_MENU}}" aria-expanded="false"></button>
</div>
</div>
</nav>
+3 -3
View File
@@ -189,7 +189,7 @@
<div class="nav-links">
<a href="{{ASSET_PATH}}" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="width: 16px; height: 16px; display: block;"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
<span>Home</span>
<span>{{NAV_HOME}}</span>
</a>
<a href="https://github.com/Shik3i/KoalaSync" target="_blank" rel="noopener" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" aria-hidden="true" style="display: block;"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
@@ -198,7 +198,7 @@
</div>
<div class="nav-right">
<div class="lang-select-container">
<select class="lang-dropdown" aria-label="Select Language">
<select class="lang-dropdown" aria-label="{{ARIA_SELECT_LANGUAGE}}">
<option value="en">🇬🇧 English</option>
<option value="de">🇩🇪 Deutsch</option>
<option value="fr">🇫🇷 Français</option>
@@ -217,7 +217,7 @@
</select>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="chevron-icon"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<button class="hamburger" aria-label="Menu" aria-expanded="false"></button>
<button class="hamburger" aria-label="{{ARIA_MENU}}" aria-expanded="false"></button>
</div>
</div>
</nav>
+3 -3
View File
@@ -189,7 +189,7 @@
<div class="nav-links">
<a href="{{ASSET_PATH}}" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="width: 16px; height: 16px; display: block;"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
<span>Home</span>
<span>{{NAV_HOME}}</span>
</a>
<a href="https://github.com/Shik3i/KoalaSync" target="_blank" rel="noopener" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" aria-hidden="true" style="display: block;"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
@@ -198,7 +198,7 @@
</div>
<div class="nav-right">
<div class="lang-select-container">
<select class="lang-dropdown" aria-label="Select Language">
<select class="lang-dropdown" aria-label="{{ARIA_SELECT_LANGUAGE}}">
<option value="en">🇬🇧 English</option>
<option value="de">🇩🇪 Deutsch</option>
<option value="fr">🇫🇷 Français</option>
@@ -217,7 +217,7 @@
</select>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="chevron-icon"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<button class="hamburger" aria-label="Menu" aria-expanded="false"></button>
<button class="hamburger" aria-label="{{ARIA_MENU}}" aria-expanded="false"></button>
</div>
</div>
</nav>
+3 -3
View File
@@ -189,7 +189,7 @@
<div class="nav-links">
<a href="{{ASSET_PATH}}" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="width: 16px; height: 16px; display: block;"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
<span>Home</span>
<span>{{NAV_HOME}}</span>
</a>
<a href="https://github.com/Shik3i/KoalaSync" target="_blank" rel="noopener" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" aria-hidden="true" style="display: block;"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
@@ -198,7 +198,7 @@
</div>
<div class="nav-right">
<div class="lang-select-container">
<select class="lang-dropdown" aria-label="Select Language">
<select class="lang-dropdown" aria-label="{{ARIA_SELECT_LANGUAGE}}">
<option value="en">🇬🇧 English</option>
<option value="de">🇩🇪 Deutsch</option>
<option value="fr">🇫🇷 Français</option>
@@ -217,7 +217,7 @@
</select>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="chevron-icon"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<button class="hamburger" aria-label="Menu" aria-expanded="false"></button>
<button class="hamburger" aria-label="{{ARIA_MENU}}" aria-expanded="false"></button>
</div>
</div>
</nav>
+3 -3
View File
@@ -189,7 +189,7 @@
<div class="nav-links">
<a href="{{ASSET_PATH}}" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="width: 16px; height: 16px; display: block;"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
<span>Home</span>
<span>{{NAV_HOME}}</span>
</a>
<a href="https://github.com/Shik3i/KoalaSync" target="_blank" rel="noopener" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" aria-hidden="true" style="display: block;"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
@@ -198,7 +198,7 @@
</div>
<div class="nav-right">
<div class="lang-select-container">
<select class="lang-dropdown" aria-label="Select Language">
<select class="lang-dropdown" aria-label="{{ARIA_SELECT_LANGUAGE}}">
<option value="en">🇬🇧 English</option>
<option value="de">🇩🇪 Deutsch</option>
<option value="fr">🇫🇷 Français</option>
@@ -217,7 +217,7 @@
</select>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="chevron-icon"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<button class="hamburger" aria-label="Menu" aria-expanded="false"></button>
<button class="hamburger" aria-label="{{ARIA_MENU}}" aria-expanded="false"></button>
</div>
</div>
</nav>
+3 -3
View File
@@ -189,7 +189,7 @@
<div class="nav-links">
<a href="{{ASSET_PATH}}" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="width: 16px; height: 16px; display: block;"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
<span>Home</span>
<span>{{NAV_HOME}}</span>
</a>
<a href="https://github.com/Shik3i/KoalaSync" target="_blank" rel="noopener" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" aria-hidden="true" style="display: block;"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
@@ -198,7 +198,7 @@
</div>
<div class="nav-right">
<div class="lang-select-container">
<select class="lang-dropdown" aria-label="Select Language">
<select class="lang-dropdown" aria-label="{{ARIA_SELECT_LANGUAGE}}">
<option value="en">🇬🇧 English</option>
<option value="de">🇩🇪 Deutsch</option>
<option value="fr">🇫🇷 Français</option>
@@ -217,7 +217,7 @@
</select>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="chevron-icon"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<button class="hamburger" aria-label="Menu" aria-expanded="false"></button>
<button class="hamburger" aria-label="{{ARIA_MENU}}" aria-expanded="false"></button>
</div>
</div>
</nav>
+32 -27
View File
@@ -1,6 +1,14 @@
// KoalaSync Landing Page Logic
document.addEventListener('DOMContentLoaded', () => {
const siteAccessBanner = document.querySelector('.site-access-banner');
const siteAccessBannerDismiss = document.querySelector('.site-access-banner-dismiss');
if (siteAccessBanner && siteAccessBannerDismiss) {
siteAccessBannerDismiss.addEventListener('click', () => {
siteAccessBanner.hidden = true;
});
}
// Mockup Video Title Randomization on Load
const SERIES_NAMES = [
'Stranger Things',
@@ -449,15 +457,11 @@ document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
const isInstalled = document.documentElement.dataset.koalasyncInstalled === 'true';
if (window.location.hash.startsWith('#join:')) {
const parts = window.location.hash.split(':');
if (parts.length >= 3) {
const roomId = parts[1];
const password = parts[2];
const serverFlag = parts[3] || '0';
const serverUrl = parts[4] ? decodeURIComponent(parts[4]) : '';
const invite = globalThis.KoalaSyncInviteLinks?.parseInviteHash(window.location.hash);
if (invite) {
const { roomId, password, chatKey, useCustomServer, serverUrl } = invite;
if (isJoinPage) {
if (isJoinPage) {
const displayRoom = document.getElementById('display-room-id');
const actions = document.getElementById('join-actions');
if (displayRoom) displayRoom.textContent = roomId;
@@ -519,14 +523,15 @@ document.addEventListener('DOMContentLoaded', () => {
detail: {
roomId,
password,
useCustomServer: serverFlag === '1',
serverUrl: serverUrl
chatKey,
useCustomServer,
serverUrl
}
}));
}, 500);
}
}
} else {
} else {
// Fallback banner for index.html
if (!document.getElementById('koala-banner')) {
const banner = document.createElement('div');
@@ -556,22 +561,22 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
// Global listener for Join Button
document.addEventListener('click', (e) => {
if (e.target && e.target.id === 'webJoinBtn') {
e.target.textContent = 'JOINING...';
e.target.disabled = true;
window.dispatchEvent(new CustomEvent('KOALASYNC_JOIN_REQUEST', {
detail: {
roomId,
password,
useCustomServer: serverFlag === '1',
serverUrl: serverUrl
}
}));
}
});
}
// Global listener for Join Button
document.addEventListener('click', (e) => {
if (e.target && e.target.id === 'webJoinBtn') {
e.target.textContent = 'JOINING...';
e.target.disabled = true;
window.dispatchEvent(new CustomEvent('KOALASYNC_JOIN_REQUEST', {
detail: {
roomId,
password,
chatKey,
useCustomServer,
serverUrl
}
}));
}
});
}
}, 600); // 600ms delay to ensure bridge.js has set the dataset
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

+103 -36
View File
@@ -5,6 +5,7 @@
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { execFileSync } = require('child_process');
const sharp = require('sharp');
const esbuild = require('esbuild');
const htmlnano = require('htmlnano');
@@ -285,6 +286,12 @@ async function compile() {
const appName = `app.${appHash}.min.js`;
const appSRI = sha384(appMin);
const inviteLinksRaw = fs.readFileSync(path.join(websiteDir, '..', 'shared', 'invite-links.js'), 'utf8');
const inviteLinksMin = await minifyJS(inviteLinksRaw);
const inviteLinksHash = sha8(inviteLinksMin);
const inviteLinksName = `invite-links.${inviteLinksHash}.min.js`;
const inviteLinksSRI = sha384(inviteLinksMin);
const langRaw = fs.readFileSync(path.join(websiteDir, 'lang-init.js'), 'utf8');
const langMin = await minifyJS(langRaw);
const langHash = sha8(langMin);
@@ -299,6 +306,7 @@ async function compile() {
console.log(` Landing ${bundle.key}: ${bundle.name} (${(bundle.min.length/1024).toFixed(1)} KB)`);
}
console.log(` App: ${appName} (${(appMin.length/1024).toFixed(1)} KB, -${appPct}%)`);
console.log(` Invite links: ${inviteLinksName} (${(inviteLinksMin.length/1024).toFixed(1)} KB)`);
console.log(` Lang: ${langName} (${(langMin.length/1024).toFixed(1)} KB, -${langPct}%)`);
// ── 2. Clean stale minified output ──
@@ -315,6 +323,7 @@ async function compile() {
fs.writeFileSync(path.join(wwwDir, bundle.name), bundle.min);
}
fs.writeFileSync(path.join(wwwDir, appName), appMin);
fs.writeFileSync(path.join(wwwDir, inviteLinksName), inviteLinksMin);
fs.writeFileSync(path.join(wwwDir, langName), langMin);
// ── 3. Compile HTML templates ──
@@ -541,7 +550,7 @@ async function compile() {
}
// ── 5.5 Generate dynamic sitemap ──
generateSitemap(wwwDir);
generateSitemap(websiteDir, wwwDir);
// Auto-copy Google verification files and IndexNow/txt key files from website source to www root
const websiteFiles = fs.readdirSync(websiteDir);
@@ -654,6 +663,9 @@ async function compile() {
html = html.replace(/(<script\b[^>]*?\bsrc=")((?:\.\.\/)*\/?)app\.min\.js"/g, (m, before, prefix) => {
return `${before}${prefix}${appName}" integrity="${appSRI}" crossorigin="anonymous"`;
});
html = html.replace(/(<script\b[^>]*?\bsrc=")((?:\.\.\/)*\/?)invite-links\.min\.js"/g, (m, before, prefix) => {
return `${before}${prefix}${inviteLinksName}" integrity="${inviteLinksSRI}" crossorigin="anonymous"`;
});
html = html.replace(/(<script\b[^>]*?\bsrc=")((?:\.\.\/)*\/?)lang-init\.min\.js"/g, (m, before, prefix) => {
return `${before}${prefix}${langName}" integrity="${langSRI}" crossorigin="anonymous"`;
});
@@ -686,7 +698,7 @@ async function compile() {
console.log('KoalaSync build finished successfully! Output: website/www/');
}
function generateSitemap(wwwDir) {
function generateSitemap(websiteDir, wwwDir) {
const languages = [
{ code: 'en', prefix: '', hreflang: 'en' },
{ code: 'de', prefix: 'de/', hreflang: 'de' },
@@ -705,52 +717,105 @@ function generateSitemap(wwwDir) {
{ code: 'pt', prefix: 'pt/', hreflang: 'pt' }
];
const today = new Date().toISOString().split('T')[0];
const repoRoot = path.resolve(websiteDir, '..');
const lastModifiedCache = new Map();
const dirtySourceFiles = new Set();
let gitHistoryAvailable = false;
let usedDirtySourceDate = false;
try {
const isInsideWorkTree = execFileSync(
'git', ['rev-parse', '--is-inside-work-tree'],
{ cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
).trim() === 'true';
const isShallow = execFileSync(
'git', ['rev-parse', '--is-shallow-repository'],
{ cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
).trim() === 'true';
gitHistoryAvailable = isInsideWorkTree && !isShallow;
if (gitHistoryAvailable) {
const dirtyCommands = [
['diff', '--name-only', '--'],
['diff', '--cached', '--name-only', '--'],
['ls-files', '--others', '--exclude-standard', '--']
];
for (const args of dirtyCommands) {
const output = execFileSync(
'git', args,
{ cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
);
for (const file of output.split(/\r?\n/).filter(Boolean)) {
dirtySourceFiles.add(file.replaceAll('\\', '/'));
}
}
}
} catch (_) {
gitHistoryAvailable = false;
}
function getLastModified(sourceFiles) {
const cacheKey = sourceFiles.slice().sort().join('\0');
if (lastModifiedCache.has(cacheKey)) return lastModifiedCache.get(cacheKey);
if (!gitHistoryAvailable) {
lastModifiedCache.set(cacheKey, null);
return null;
}
let date = null;
try {
if (sourceFiles.some(file => dirtySourceFiles.has(file))) {
usedDirtySourceDate = true;
date = new Date().toISOString().slice(0, 10);
} else {
const output = execFileSync(
'git',
['log', '-1', '--format=%cs', '--', ...sourceFiles],
{ cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
).trim();
if (/^\d{4}-\d{2}-\d{2}$/.test(output)) date = output;
}
} catch (_) {
// Source archives and minimal deployment environments may not contain
// Git metadata. Omitting lastmod is more accurate than inventing a date.
}
lastModifiedCache.set(cacheKey, date);
return date;
}
function lastmod(sourceFiles, indent = ' ') {
const date = getLastModified(sourceFiles);
return date ? `\n${indent}<lastmod>${date}</lastmod>` : '';
}
let xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">`;
// Static legal pages
// Static project-specific policy and help pages
xml += `
<url>
<loc>https://sync.koalastuff.net/imprint</loc>
<lastmod>${today}</lastmod>
<loc>https://sync.koalastuff.net/privacy</loc>${lastmod(['website/privacy.html'])}
<changefreq>monthly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://sync.koalastuff.net/privacy</loc>
<lastmod>${today}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://sync.koalastuff.net/site-access-help.html</loc>
<lastmod>${today}</lastmod>
<loc>https://sync.koalastuff.net/site-access-help.html</loc>${lastmod(['website/site-access-help.html'])}
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://sync.koalastuff.net/de/impressum</loc>
<lastmod>${today}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://sync.koalastuff.net/de/datenschutz</loc>
<lastmod>${today}</lastmod>
<loc>https://sync.koalastuff.net/de/datenschutz</loc>${lastmod(['website/datenschutz-de.html'])}
<changefreq>monthly</changefreq>
<priority>0.3</priority>
</url>`;
function addPage(relativePath, changefreq, priority) {
function addPage(relativePath, changefreq, priority, templateSource) {
for (const lang of languages) {
const loc = `https://sync.koalastuff.net/${lang.prefix}${relativePath}`;
const sourceFiles = [templateSource, `website/locales/${lang.code}.json`];
xml += `
<url>
<loc>${loc}</loc>
<lastmod>${today}</lastmod>
<loc>${loc}</loc>${lastmod(sourceFiles)}
<changefreq>${changefreq}</changefreq>
<priority>${priority}</priority>`;
for (const alt of languages) {
@@ -765,24 +830,26 @@ function generateSitemap(wwwDir) {
}
}
addPage('', 'weekly', '1.0');
addPage('alternatives', 'weekly', '0.7');
addPage('', 'weekly', '1.0', 'website/template.html');
addPage('alternatives', 'weekly', '0.7', 'website/alternatives/index.html');
const subpages = [
'alternatives/teleparty',
'alternatives/screen-sharing',
'alternatives/watch2gether',
'alternatives/scener',
'alternatives/kosmi',
'alternatives/twoseven'
['alternatives/teleparty', 'website/alternatives/teleparty.html'],
['alternatives/screen-sharing', 'website/alternatives/screen-sharing.html'],
['alternatives/watch2gether', 'website/alternatives/watch2gether.html'],
['alternatives/scener', 'website/alternatives/scener.html'],
['alternatives/kosmi', 'website/alternatives/kosmi.html'],
['alternatives/twoseven', 'website/alternatives/twoseven.html']
];
for (const sub of subpages) {
addPage(sub, 'weekly', '0.7');
for (const [sub, templateSource] of subpages) {
addPage(sub, 'weekly', '0.7', templateSource);
}
xml += `\n</urlset>\n`;
fs.writeFileSync(path.join(wwwDir, 'sitemap.xml'), xml.trim() + '\n', 'utf8');
console.log(' ✓ Dynamically generated sitemap.xml with current date');
const datedEntries = (xml.match(/<lastmod>/g) || []).length;
const dateSource = usedDirtySourceDate ? 'Git/working-tree-derived' : 'Git-derived';
console.log(` ✓ Dynamically generated sitemap.xml with ${datedEntries} ${dateSource} modification dates`);
}
compile().catch(err => { console.error('Build failed:', err); process.exit(1); });
+23 -8
View File
@@ -11,7 +11,7 @@
<link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/privacy">
<link rel="alternate" hreflang="de" href="https://sync.koalastuff.net/de/datenschutz">
<link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/privacy">
<meta name="description" content="Datenschutzerklärung von KoalaSync - Wir nehmen Datenschutz ernst. Erfahre mehr über unseren Ansatz ohne Tracking, ohne Logs und ohne Cookies.">
<meta name="description" content="Datenschutzerklärung von KoalaSync. Erfahre, wie Live-Raumdaten, verschlüsselter Chat, Betriebslogs und Website-Access-Logs verarbeitet werden.">
<meta name="robots" content="index, follow">
<script type="application/ld+json">
@@ -31,7 +31,7 @@
"@type": "WebPage",
"name": "Datenschutzerklärung",
"url": "https://sync.koalastuff.net/de/datenschutz",
"description": "Datenschutzerklärung von KoalaSync - Wir nehmen Datenschutz ernst. Erfahre mehr über unseren Ansatz ohne Tracking, ohne Logs und ohne Cookies.",
"description": "Datenschutzerklärung von KoalaSync. Erfahre, wie Live-Raumdaten, verschlüsselter Chat, Betriebslogs und Website-Access-Logs verarbeitet werden.",
"inLanguage": "de",
"isPartOf": {
"@type": "WebSite",
@@ -202,11 +202,20 @@
<section>
<h2>4. Offizieller Relay-Server & Raumdaten</h2>
<p>
Der offizielle KoalaSync-Relay-Server führt keine persistenten Logs. Er speichert keine Raumhistorie, keinen Sync-Verlauf, keinen Wiedergabeverlauf, keine Raumnachrichten, keine Playback-Events, keine Tab-Titel und keine Medientitel auf Festplatte.
Der offizielle KoalaSync-Relay-Server erstellt keine Raum-, Chat-, Synchronisations- oder Wiedergabehistorie. Betriebslogs für Sicherheit und Fehleranalyse können IP-Adressen, Socket- oder Peer-IDs, Protokollversionen und gekürzte Room-IDs enthalten. Ihre Aufbewahrungsdauer hängt von der Server-Infrastruktur und deren Logging-Konfiguration ab.
</p>
<p style="margin-top: 0.5rem;">
Zur Bereitstellung der Raumfunktion verarbeitet der Relay Raum- und Synchronisationsdaten temporär im Arbeitsspeicher, solange ein Raum aktiv ist. Dazu können Room-ID, Peer-ID, Anzeigename, Wiedergabestatus, Sync-Events und abhängig von den Titel-Privatsphäre-Einstellungen Tab-/Medientitel gehören. Diese Daten werden nur an Teilnehmer desselben Raums weitergeleitet und aus dem Arbeitsspeicher entfernt, wenn der Raum abläuft oder geschlossen wird.
</p>
<p style="margin-top: 0.5rem;">
Der Inhalt von Raum-Chatnachrichten wird bereits in der Browser-Erweiterung vor der Übertragung Ende-zu-Ende verschlüsselt. KoalaSync leitet lokal aus der Room-ID und einem zufällig erzeugten Chat-Secret einen 256-Bit-Schlüssel für AES-GCM ab. Das Chat-Secret wird über den Einladungslink geteilt und von der Erweiterung lokal gespeichert; es ist nicht Bestandteil der an den Relay gesendeten Chat-Nutzdaten.
</p>
<p style="margin-top: 0.5rem;">
Der Relay erhält ausschließlich den verschlüsselten Nachrichteninhalt sowie die für die Live-Zustellung erforderlichen technischen Metadaten, beispielsweise Absender-ID, Nachrichten-ID und Zeitstempel. Da der Relay das Chat-Secret nicht erhält, kann er den Nachrichtentext nicht entschlüsseln oder lesen. Er leitet den verschlüsselten Inhalt an die aktuell verbundenen Teilnehmer desselben Raums weiter und erstellt keine serverseitige Chat-Historie, schreibt keine Nachrichten auf Festplatte und bewahrt sie nach der Live-Weiterleitung nicht auf.
</p>
<p style="margin-top: 0.5rem;">
Wer einen gültigen Einladungslink einschließlich Chat-Secret erhält, kann Nachrichten dieses Raums möglicherweise entschlüsseln. Einladungslinks sollten daher ausschließlich mit den vorgesehenen Teilnehmern geteilt werden.
</p>
<p style="margin-top: 0.5rem;">
Rechtsgrundlage: Art. 6 Abs. 1 lit. f DSGVO, berechtigtes Interesse an der Bereitstellung der gewünschten Synchronisationsfunktion, Sicherheit und Missbrauchsprävention.
</p>
@@ -221,7 +230,13 @@
Zur Synchronisierung der Wiedergabe zwischen Teilnehmern desselben Raums erfasst die KoalaSync Browser-Erweiterung temporär Daten des aktuell aktiven Video-Tabs (z. B. Tab-Titel, Medien-Metadaten wie den Videotitel sowie den Wiedergabestatus). Tab- oder Medientitel können je nach Inhalt potenziell personenbezogen sein. Sie werden nur für die Synchronisationsfunktion verarbeitet, können über die Privatsphäre-Einstellungen der Erweiterung eingeschränkt oder deaktiviert werden, werden nur an Teilnehmer desselben Raums weitergeleitet und vom Relay nicht protokolliert oder dauerhaft gespeichert.
</p>
<p style="margin-top: 0.5rem;">
Die Erweiterung speichert nur für den Betrieb erforderliche Einstellungen lokal im Browser, z. B. Anzeigename, Server-URL, aktuelle Raumdaten, Sprache, Titel-Privatsphäre-Einstellungen, Benachrichtigungseinstellungen und Audio-Processing-Einstellungen. KoalaSync speichert keinen allgemeinen Browserverlauf und keinen persistenten Wiedergabeverlauf.
Die Erweiterung speichert nur für den Betrieb erforderliche Einstellungen lokal im Browser, z. B. Anzeigename, Server-URL, aktuelle Raumdaten einschließlich Chat-Secret, Sprache, Titel-Privatsphäre-Einstellungen, Benachrichtigungseinstellungen und Audio-Processing-Einstellungen. KoalaSync speichert keinen allgemeinen Browserverlauf und keinen persistenten Wiedergabeverlauf.
</p>
<p style="margin-top: 0.5rem;">
Entschlüsselte Chatnachrichten werden ausschließlich im Arbeitsspeicher des Overlays im aktuellen Video-Tab gehalten; sichtbar bleiben höchstens 200 Nachrichten- oder Aktivitätseinträge. Sie werden nicht in den Browser-Speicher geschrieben und verschwinden, wenn der Chat zurückgesetzt oder entfernt, die Seite neu geladen oder die Raumsitzung beendet wird. Im Chat angezeigte Wiedergabe- und Anwesenheitseinträge werden lokal aus bereits vorhandenen Raum-Events erzeugt und erstellen keine separate serverseitige Historie.
</p>
<p style="margin-top: 0.5rem;">
Wenn Chat-Benachrichtigungen aktiviert sind, werden der entschlüsselte Anzeigename und Nachrichtentext an die Benachrichtigungs-API des Browsers übergeben und können entsprechend den Einstellungen des Browsers oder Betriebssystems angezeigt oder dort vorgehalten werden. Chat-Benachrichtigungen können in den Einstellungen der Erweiterung deaktiviert werden.
</p>
<p style="margin-top: 0.5rem;">
Die Erweiterung verbindet sich nur mit dem Relay-Server, während Sie sich aktiv in einem Raum befinden. Es wird keine dauerhafte Hintergrundverbindung aufrechterhalten, sodass Ihre IP-Adresse dem Server nicht offengelegt wird, wenn Sie die Erweiterung nicht nutzen.
@@ -254,14 +269,14 @@
<section>
<h2>8. Empfänger & Dritte</h2>
<p>
Technische Hosting- und Verbindungsdaten können durch Hetzner als Hosting-Anbieter im Auftrag des Betreibers verarbeitet werden. Raum-Synchronisationsdaten werden nur an Teilnehmer desselben Raums weitergeleitet. KoalaSync verwendet keine Analytics-Anbieter, Werbenetzwerke oder Tracking-Dienste.
Technische Hosting- und Verbindungsdaten können durch Hetzner als Hosting-Anbieter im Auftrag des Betreibers verarbeitet werden. Raum-Synchronisationsdaten werden nur an Teilnehmer desselben Raums weitergeleitet. Verschlüsselte Raum-Chat-Nutzdaten und deren Zustellungsmetadaten werden ebenfalls nur innerhalb des Raums weitergeleitet; lesbarer Nachrichteninhalt steht nur Teilnehmern zur Verfügung, die das Chat-Secret besitzen. KoalaSync verwendet keine Analytics-Anbieter, Werbenetzwerke oder Tracking-Dienste.
</p>
</section>
<section>
<h2>9. Erforderliche & optionale Daten</h2>
<p>
Die Bereitstellung technischer Verbindungsdaten ist erforderlich, um die Website aufzurufen und den Relay zu nutzen. Raumdaten und Sync-Events sind erforderlich, um die Raumsynchronisation zu verwenden. Das Teilen von Tab-/Medientitel ist optional und kann über die Privatsphäre-Einstellungen der Erweiterung eingeschränkt werden.
Die Bereitstellung technischer Verbindungsdaten ist erforderlich, um die Website aufzurufen und den Relay zu nutzen. Raumdaten und Sync-Events sind erforderlich, um die Raumsynchronisation zu verwenden. Der Text-Chat ist optional; bei seiner Nutzung sind verschlüsselte Nachrichten-Nutzdaten und Zustellungsmetadaten für die Live-Zustellung erforderlich. Das Teilen von Tab-/Medientiteln und Browser-Benachrichtigungen sind optional und können über die Privatsphäre-Einstellungen der Erweiterung gesteuert werden.
</p>
</section>
@@ -295,9 +310,9 @@
<footer>
<div class="container">
<p>&copy; 2026 KoalaSync. Open Source unter der MIT-Lizenz.</p>
<p style="font-size: 0.8rem; margin-top: 0.5rem;">Keine Relay-Logs. Kein Tracking. Website-Access-Logs werden nach 7 Tagen gelöscht.</p>
<p style="font-size: 0.8rem; margin-top: 0.5rem;">Keine Chat- oder Wiedergabehistorie. Kein Tracking. Website-Access-Logs werden nach 7 Tagen gelöscht.</p>
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
<a href="impressum" style="color: var(--text-muted); text-decoration: none;">Impressum</a>
<a href="https://koalastuff.net/legal" style="color: var(--text-muted); text-decoration: none;">Impressum</a>
<a href="datenschutz" style="color: var(--text-muted); text-decoration: none;">Datenschutz</a>
<a href="alternatives/teleparty">Vergleiche & Anleitungen</a>
<a href="https://mastodon.social/@koalastuff" rel="me noopener" target="_blank" style="color: var(--text-muted); text-decoration: none; display: inline-flex; align-items: center; gap: 4px;">
+15 -244
View File
@@ -1,250 +1,21 @@
<!DOCTYPE html>
<html lang="de" class="lang-de">
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Impressum | KoalaSync</title>
<link rel="preload" href="../style.min.css" as="style">
<link rel="stylesheet" href="../style.min.css">
<link rel="icon" type="image/webp" href="../assets/NewLogoIcon_64.webp">
<link rel="canonical" href="https://sync.koalastuff.net/de/impressum">
<link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/imprint">
<link rel="alternate" hreflang="de" href="https://sync.koalastuff.net/de/impressum">
<link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/imprint">
<meta name="description" content="Impressum (Legal Notice) von KoalaSync - Anbieterkennzeichnung und Kontaktinformationen für die Open-Source Video-Synchronisations-Erweiterung.">
<meta name="robots" content="index, follow">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Startseite", "item": "https://sync.koalastuff.net/de/" },
{ "@type": "ListItem", "position": 2, "name": "Impressum", "item": "https://sync.koalastuff.net/de/impressum" }
]
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebPage",
"name": "Impressum",
"url": "https://sync.koalastuff.net/de/impressum",
"description": "Impressum (Legal Notice) von KoalaSync - Anbieterkennzeichnung und Kontaktinformationen für die Open-Source Video-Synchronisations-Erweiterung.",
"inLanguage": "de",
"isPartOf": {
"@type": "WebSite",
"name": "KoalaSync",
"url": "https://sync.koalastuff.net/de/"
}
}
</script>
<!-- Mobile Browser Theme Styling -->
<meta name="theme-color" content="#10190e">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<script src="../lang-init.min.js"></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Impressum | KoalaSync</title>
<meta name="description" content="Das KoalaSync-Impressum ist zum zentralen KoalaStuff-Impressum umgezogen.">
<meta name="robots" content="noindex, follow">
<link rel="canonical" href="https://koalastuff.net/legal">
<link rel="stylesheet" href="../style.min.css">
</head>
<body>
<div class="bg-nature" aria-hidden="true">
<div class="bg-dusk-tint"></div>
<div class="bg-depth-day">
<div class="bg-godrays">
<span class="godray godray-1"></span>
<span class="godray godray-2"></span>
<span class="godray godray-3"></span>
</div>
<div class="bg-canopy"></div>
<span class="canopy-arc canopy-arc-1"></span>
<span class="canopy-arc canopy-arc-2"></span>
<span class="canopy-arc canopy-arc-3"></span>
<span class="canopy-arc canopy-arc-4"></span>
</div>
<div id="bamboo-far">
<span class="bamboo-stalk far-1"></span>
<span class="bamboo-stalk far-2"></span>
<span class="bamboo-stalk far-3"></span>
<span class="bamboo-stalk far-4"></span>
<span class="bamboo-stalk far-5"></span>
</div>
<div id="bamboo-near">
<div class="bamboo-stalk near-1">
<i class="bamboo-node node-1"></i>
<i class="bamboo-node node-2"></i>
<i class="bamboo-node node-3"></i>
<i class="bamboo-leaf leaf-1"></i>
<i class="bamboo-leaf leaf-2"></i>
</div>
<div class="bamboo-stalk near-2">
<i class="bamboo-node node-1"></i>
<i class="bamboo-node node-2"></i>
<i class="bamboo-node node-3"></i>
<i class="bamboo-leaf leaf-1"></i>
</div>
<div class="bamboo-stalk near-3">
<i class="bamboo-node node-1"></i>
<i class="bamboo-node node-2"></i>
<i class="bamboo-node node-3"></i>
<i class="bamboo-leaf leaf-1"></i>
</div>
</div>
<span class="fall-leaf fall-leaf-1"><i></i></span>
<span class="fall-leaf fall-leaf-2"><i></i></span>
<span class="fall-leaf fall-leaf-3"><i></i></span>
<span class="fall-leaf fall-leaf-4"><i></i></span>
<span class="fall-leaf fall-leaf-5"><i></i></span>
<span class="fall-leaf fall-leaf-6"><i></i></span>
<span class="fall-leaf fall-leaf-7"><i></i></span>
<span class="fall-leaf fall-leaf-8"><i></i></span>
<span class="bg-grass bg-grass-1"></span>
<span class="bg-grass bg-grass-2"></span>
<span class="bg-grass bg-grass-3"></span>
<span class="bg-grass bg-grass-4"></span>
<div class="bg-depth-dusk">
<div class="bg-mist bg-mist-1"></div>
<div class="bg-mist bg-mist-2"></div>
<div class="bg-horizon"></div>
<span class="firefly-wrap fw-1"><span class="firefly"></span></span>
<span class="firefly-wrap fw-2"><span class="firefly"></span></span>
<span class="firefly-wrap fw-3"><span class="firefly"></span></span>
<span class="firefly-wrap fw-4"><span class="firefly"></span></span>
<span class="firefly-wrap fw-5"><span class="firefly"></span></span>
<span class="firefly-wrap fw-6"><span class="firefly"></span></span>
</div>
<div class="bg-light-sweep"></div>
<div class="bg-grain"></div>
<main class="legal-content">
<div class="legal-card">
<h1>Impressum umgezogen</h1>
<p>Das KoalaSync-Impressum ist jetzt unter der zentralen KoalaStuff-Adresse verfügbar.</p>
<p><a class="legal-inline-link" href="https://koalastuff.net/legal">Zentrales Impressum öffnen</a></p>
</div>
<nav>
<div class="container nav-content">
<a href="./" class="logo-area" style="text-decoration: none;">
<img src="../assets/NewLogoIcon_128.webp" alt="KoalaSync Logo" width="40" height="40">
<span>KoalaSync</span>
</a>
<div class="nav-links">
<a href="./" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="width: 16px; height: 16px; display: block;"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
<span>Startseite</span>
</a>
<a href="https://github.com/Shik3i/KoalaSync" target="_blank" rel="noopener" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" aria-hidden="true" style="display: block;"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
GitHub
</a>
</div>
<div class="nav-right">
<div class="lang-select-container">
<select class="lang-dropdown" aria-label="Select Language">
<option value="en">🇬🇧 English</option>
<option value="de">🇩🇪 Deutsch</option>
<option value="fr">🇫🇷 Français</option>
<option value="es">🇪🇸 Español</option>
<option value="pt-BR">🇧🇷 Português (Brasil)</option>
<option value="ru">🇷🇺 Русский</option>
<option value="it">🇮🇹 Italiano</option>
<option value="pl">🇵🇱 Polski</option>
<option value="tr">🇹🇷 Türkçe</option>
<option value="nl">🇳🇱 Nederlands</option>
<option value="ja">🇯🇵 日本語</option>
<option value="ko">🇰🇷 한국어</option>
<option value="pt">🇵🇹 Português (Portugal)</option>
</select>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="chevron-icon"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<button class="hamburger" aria-label="Menu" aria-expanded="false"></button>
</div>
</div>
</nav>
<main class="legal-content">
<div class="legal-card" data-reveal style="padding: 2rem;">
<div style="display: flex; justify-content: center;">
<img src="../assets/KoalaImprintl-180.webp" srcset="../assets/KoalaImprintl-180.webp 180w, ../assets/KoalaImprintl-360.webp 360w" sizes="180px" alt="Niedlicher Koala, der das Impressum repräsentiert" class="legal-mascot" width="180" height="180">
</div>
<h1>Impressum / Anbieterinformationen</h1>
<p style="text-align: center; text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.8rem; border-bottom: 1px solid var(--glass-border); padding-bottom: 1.5rem; margin-bottom: 2rem;">
Kontakt & Projektinformationen
</p>
<section>
<h2>Betreiber & Kontakt</h2>
<p>Timo Schmidt</p>
<p style="margin-top: 0.5rem;">
E-Mail: <span class="email-reveal" data-user="koalasync" data-domain="koalastuff.net" style="color: var(--accent); cursor: pointer; text-decoration: underline;">[E-Mail anzeigen]</span>
</p>
<p style="margin-top: 0.75rem;">
<span class="legal-inline-label">🔒 Private Nachricht:</span>
<a href="https://mastodon.social/@koalastuff" rel="me noopener" target="_blank" class="legal-inline-link">@koalastuff auf Mastodon</a>
</p>
<p style="margin-top: 0.5rem;">
<span class="legal-inline-label">🐛 Fehlerberichte und Sicherheitsmeldungen:</span>
<a href="https://github.com/Shik3i/KoalaSync/issues" target="_blank" rel="noopener" class="legal-inline-link">GitHub Issues</a>
</p>
</section>
<section>
<h2>Projektinformationen</h2>
<p>
KoalaSync ist eine kostenlose und quelloffene Browser-Erweiterung. Der Quellcode ist auf GitHub unter der MIT-Lizenz verfügbar.
</p>
<p style="margin-top: 0.5rem;">
Informationen zum Datenschutz, einschließlich Hosting, Access Logs, Relay-Verhalten und Betroffenenrechten, finden Sie in der <a href="datenschutz" class="legal-inline-link">Datenschutzerklärung</a>.
</p>
</section>
<section>
<h2>Haftung für Inhalte</h2>
<p>
Die Inhalte dieser Website wurden mit Sorgfalt erstellt. Ich kann jedoch nicht gewährleisten, dass alle Informationen jederzeit vollständig, richtig oder aktuell sind.
</p>
<p style="margin-top: 0.5rem;">
Als Diensteanbieter bin ich nach den allgemeinen Gesetzen für eigene Inhalte auf diesen Seiten verantwortlich.
</p>
</section>
<section>
<h2>Haftung für Links</h2>
<p>
Diese Website enthält Links zu externen Websites Dritter. Auf deren Inhalte habe ich keinen Einfluss. Für die Inhalte verlinkter Seiten ist der jeweilige Anbieter oder Betreiber verantwortlich.
</p>
<p style="margin-top: 0.5rem;">
Wenn mir rechtswidrige Inhalte auf verlinkten Seiten bekannt werden, entferne ich die entsprechenden Links.
</p>
</section>
<section>
<h2>Lizenz & Urheberrecht</h2>
<p>
KoalaSync ist freie und quelloffene Software. Sofern nicht anders angegeben, steht der Quellcode von KoalaSync unter der MIT-Lizenz.
</p>
<p style="margin-top: 0.5rem;">
Die MIT-Lizenz erlaubt Nutzung, Vervielfältigung, Bearbeitung, Verbreitung, Unterlizenzierung und Verkauf der Software, sofern der Copyright-Hinweis und der Lizenztext entsprechend den Lizenzbedingungen erhalten bleiben.
</p>
<p style="margin-top: 0.5rem;">
Projektname, Logos, Grafiken, Website-Texte und andere Nicht-Code-Assets können separaten Rechten unterliegen, sofern sie nicht ausdrücklich von der Repository-Lizenz umfasst sind.
</p>
</section>
</div>
</main>
<footer>
<div class="container">
<p>&copy; 2026 KoalaSync. Open Source unter der MIT-Lizenz.</p>
<p style="font-size: 0.8rem; margin-top: 0.5rem;">Keine Relay-Logs. Kein Tracking. Website-Access-Logs werden nach 7 Tagen gelöscht.</p>
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
<a href="impressum" style="color: var(--text-muted); text-decoration: none;">Impressum</a>
<a href="datenschutz" style="color: var(--text-muted); text-decoration: none;">Datenschutz</a>
<a href="alternatives">Vergleiche & Anleitungen</a>
<a href="https://mastodon.social/@koalastuff" rel="me noopener" target="_blank" style="color: var(--text-muted); text-decoration: none; display: inline-flex; align-items: center; gap: 4px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="14" height="14" aria-hidden="true" style="display: block;"><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/></svg>
Mastodon
</a>
</div>
</div>
</footer>
<script src="../app.min.js"></script>
</main>
</body>
</html>
+8 -20
View File
@@ -1,26 +1,14 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, follow">
<title>Redirecting...</title>
<script>
(function() {
var savedLang = localStorage.getItem('koala_lang');
var browserLang = (navigator.language || navigator.userLanguage || '').toLowerCase();
var isDe = (savedLang === 'de') || (!savedLang && browserLang.indexOf('de') === 0);
var path = window.location.pathname;
var hasHtml = path.endsWith('.html');
if (isDe) {
window.location.replace(hasHtml ? 'de/impressum.html' : 'de/impressum');
} else {
window.location.replace(hasHtml ? 'imprint.html' : 'imprint');
}
})();
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="refresh" content="0; url=https://koalastuff.net/legal">
<meta name="robots" content="noindex, follow">
<link rel="canonical" href="https://koalastuff.net/legal">
<title>Legal Notice | KoalaSync</title>
</head>
<body>
<p>Redirecting / Weiterleitung...</p>
<p><a href="https://koalastuff.net/legal">Open the central KoalaStuff legal notice</a></p>
</body>
</html>
+15 -244
View File
@@ -1,250 +1,21 @@
<!DOCTYPE html>
<html lang="en" class="lang-en">
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Legal Notice | KoalaSync</title>
<link rel="preload" href="style.min.css" as="style">
<link rel="stylesheet" href="style.min.css">
<link rel="icon" type="image/webp" href="assets/NewLogoIcon_64.webp">
<link rel="canonical" href="https://sync.koalastuff.net/imprint">
<link rel="alternate" hreflang="en" href="https://sync.koalastuff.net/imprint">
<link rel="alternate" hreflang="de" href="https://sync.koalastuff.net/de/impressum">
<link rel="alternate" hreflang="x-default" href="https://sync.koalastuff.net/imprint">
<meta name="description" content="Legal Notice (Impressum) of KoalaSync - Contact information and operator details for the open-source video synchronization extension.">
<meta name="robots" content="index, follow">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "https://sync.koalastuff.net/" },
{ "@type": "ListItem", "position": 2, "name": "Legal Notice", "item": "https://sync.koalastuff.net/imprint" }
]
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebPage",
"name": "Legal Notice",
"url": "https://sync.koalastuff.net/imprint",
"description": "Legal Notice (Impressum) of KoalaSync - Contact information and operator details for the open-source video synchronization extension.",
"inLanguage": "en",
"isPartOf": {
"@type": "WebSite",
"name": "KoalaSync",
"url": "https://sync.koalastuff.net/"
}
}
</script>
<!-- Mobile Browser Theme Styling -->
<meta name="theme-color" content="#10190e">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<script src="lang-init.min.js"></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Legal Notice | KoalaSync</title>
<meta name="description" content="The KoalaSync legal notice has moved to the central KoalaStuff legal notice.">
<meta name="robots" content="noindex, follow">
<link rel="canonical" href="https://koalastuff.net/legal">
<link rel="stylesheet" href="style.min.css">
</head>
<body>
<div class="bg-nature" aria-hidden="true">
<div class="bg-dusk-tint"></div>
<div class="bg-depth-day">
<div class="bg-godrays">
<span class="godray godray-1"></span>
<span class="godray godray-2"></span>
<span class="godray godray-3"></span>
</div>
<div class="bg-canopy"></div>
<span class="canopy-arc canopy-arc-1"></span>
<span class="canopy-arc canopy-arc-2"></span>
<span class="canopy-arc canopy-arc-3"></span>
<span class="canopy-arc canopy-arc-4"></span>
</div>
<div id="bamboo-far">
<span class="bamboo-stalk far-1"></span>
<span class="bamboo-stalk far-2"></span>
<span class="bamboo-stalk far-3"></span>
<span class="bamboo-stalk far-4"></span>
<span class="bamboo-stalk far-5"></span>
</div>
<div id="bamboo-near">
<div class="bamboo-stalk near-1">
<i class="bamboo-node node-1"></i>
<i class="bamboo-node node-2"></i>
<i class="bamboo-node node-3"></i>
<i class="bamboo-leaf leaf-1"></i>
<i class="bamboo-leaf leaf-2"></i>
</div>
<div class="bamboo-stalk near-2">
<i class="bamboo-node node-1"></i>
<i class="bamboo-node node-2"></i>
<i class="bamboo-node node-3"></i>
<i class="bamboo-leaf leaf-1"></i>
</div>
<div class="bamboo-stalk near-3">
<i class="bamboo-node node-1"></i>
<i class="bamboo-node node-2"></i>
<i class="bamboo-node node-3"></i>
<i class="bamboo-leaf leaf-1"></i>
</div>
</div>
<span class="fall-leaf fall-leaf-1"><i></i></span>
<span class="fall-leaf fall-leaf-2"><i></i></span>
<span class="fall-leaf fall-leaf-3"><i></i></span>
<span class="fall-leaf fall-leaf-4"><i></i></span>
<span class="fall-leaf fall-leaf-5"><i></i></span>
<span class="fall-leaf fall-leaf-6"><i></i></span>
<span class="fall-leaf fall-leaf-7"><i></i></span>
<span class="fall-leaf fall-leaf-8"><i></i></span>
<span class="bg-grass bg-grass-1"></span>
<span class="bg-grass bg-grass-2"></span>
<span class="bg-grass bg-grass-3"></span>
<span class="bg-grass bg-grass-4"></span>
<div class="bg-depth-dusk">
<div class="bg-mist bg-mist-1"></div>
<div class="bg-mist bg-mist-2"></div>
<div class="bg-horizon"></div>
<span class="firefly-wrap fw-1"><span class="firefly"></span></span>
<span class="firefly-wrap fw-2"><span class="firefly"></span></span>
<span class="firefly-wrap fw-3"><span class="firefly"></span></span>
<span class="firefly-wrap fw-4"><span class="firefly"></span></span>
<span class="firefly-wrap fw-5"><span class="firefly"></span></span>
<span class="firefly-wrap fw-6"><span class="firefly"></span></span>
</div>
<div class="bg-light-sweep"></div>
<div class="bg-grain"></div>
<main class="legal-content">
<div class="legal-card">
<h1>Legal notice moved</h1>
<p>The KoalaSync legal notice is now available at the central KoalaStuff address.</p>
<p><a class="legal-inline-link" href="https://koalastuff.net/legal">Open the central legal notice</a></p>
</div>
<nav>
<div class="container nav-content">
<a href="./" class="logo-area" style="text-decoration: none;">
<img src="assets/NewLogoIcon_128.webp" alt="KoalaSync Logo" width="40" height="40">
<span>KoalaSync</span>
</a>
<div class="nav-links">
<a href="./" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="width: 16px; height: 16px; display: block;"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
<span>Home</span>
</a>
<a href="https://github.com/Shik3i/KoalaSync" target="_blank" rel="noopener" style="display: inline-flex; align-items: center; gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" aria-hidden="true" style="display: block;"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
GitHub
</a>
</div>
<div class="nav-right">
<div class="lang-select-container">
<select class="lang-dropdown" aria-label="Select Language">
<option value="en">🇬🇧 English</option>
<option value="de">🇩🇪 Deutsch</option>
<option value="fr">🇫🇷 Français</option>
<option value="es">🇪🇸 Español</option>
<option value="pt-BR">🇧🇷 Português (Brasil)</option>
<option value="ru">🇷🇺 Русский</option>
<option value="it">🇮🇹 Italiano</option>
<option value="pl">🇵🇱 Polski</option>
<option value="tr">🇹🇷 Türkçe</option>
<option value="nl">🇳🇱 Nederlands</option>
<option value="ja">🇯🇵 日本語</option>
<option value="ko">🇰🇷 한국어</option>
<option value="pt">🇵🇹 Português (Portugal)</option>
</select>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="chevron-icon"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<button class="hamburger" aria-label="Menu" aria-expanded="false"></button>
</div>
</div>
</nav>
<main class="legal-content">
<div class="legal-card" data-reveal style="padding: 2rem;">
<div style="display: flex; justify-content: center;">
<img src="assets/KoalaImprintl-180.webp" srcset="assets/KoalaImprintl-180.webp 180w, assets/KoalaImprintl-360.webp 360w" sizes="180px" alt="Cute Koala representing the legal notice page" class="legal-mascot" width="180" height="180">
</div>
<h1>Legal Notice</h1>
<p style="text-align: center; text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.8rem; border-bottom: 1px solid var(--glass-border); padding-bottom: 1.5rem; margin-bottom: 2rem;">
Contact & Project Information
</p>
<section>
<h2>Operator & Contact</h2>
<p>Timo Schmidt</p>
<p style="margin-top: 0.5rem;">
E-Mail: <span class="email-reveal" data-user="koalasync" data-domain="koalastuff.net" style="color: var(--accent); cursor: pointer; text-decoration: underline;">[Show Email]</span>
</p>
<p style="margin-top: 0.75rem;">
<span class="legal-inline-label">🔒 Private message:</span>
<a href="https://mastodon.social/@koalastuff" rel="me noopener" target="_blank" class="legal-inline-link">@koalastuff on Mastodon</a>
</p>
<p style="margin-top: 0.5rem;">
<span class="legal-inline-label">🐛 Bug reports and security concerns:</span>
<a href="https://github.com/Shik3i/KoalaSync/issues" target="_blank" rel="noopener" class="legal-inline-link">GitHub Issues</a>
</p>
</section>
<section>
<h2>Project Information</h2>
<p>
KoalaSync is a free and open-source browser extension. The source code is available on GitHub under the MIT License.
</p>
<p style="margin-top: 0.5rem;">
For privacy-related information, including hosting, access logs, relay behavior, and data subject rights, please see the <a href="privacy" class="legal-inline-link">Privacy Policy</a>.
</p>
</section>
<section>
<h2>Liability for Content</h2>
<p>
The contents of this website were created with care. However, I cannot guarantee that all information is always complete, accurate, or up to date.
</p>
<p style="margin-top: 0.5rem;">
As a service provider, I am responsible for my own content on these pages in accordance with the general laws.
</p>
</section>
<section>
<h2>Liability for Links</h2>
<p>
This website contains links to external third-party websites. I have no influence over the content of those websites. The respective provider or operator is responsible for the content of linked pages.
</p>
<p style="margin-top: 0.5rem;">
If I become aware of unlawful content on linked pages, I will remove the corresponding links.
</p>
</section>
<section>
<h2>License & Copyright</h2>
<p>
KoalaSync is free and open-source software. Unless otherwise stated, the KoalaSync source code is licensed under the MIT License.
</p>
<p style="margin-top: 0.5rem;">
The MIT License allows use, copying, modification, distribution, sublicensing, and sale of the software, provided that the copyright notice and license text are included where required by the license.
</p>
<p style="margin-top: 0.5rem;">
Project name, logos, graphics, website text, and other non-code assets may be subject to separate rights unless they are explicitly included under the repository license.
</p>
</section>
</div>
</main>
<footer>
<div class="container">
<p>&copy; 2026 KoalaSync. Open source under the MIT License.</p>
<p style="font-size: 0.8rem; margin-top: 0.5rem;">No relay logs. No tracking. Website access logs are deleted after 7 days.</p>
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
<a href="imprint" style="color: var(--text-muted); text-decoration: none;">Legal Notice</a>
<a href="privacy" style="color: var(--text-muted); text-decoration: none;">Privacy Policy</a>
<a href="alternatives">Guides & comparisons</a>
<a href="https://mastodon.social/@koalastuff" rel="me noopener" target="_blank" style="color: var(--text-muted); text-decoration: none; display: inline-flex; align-items: center; gap: 4px;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="14" height="14" aria-hidden="true" style="display: block;"><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/></svg>
Mastodon
</a>
</div>
</div>
</footer>
<script src="app.min.js"></script>
</main>
</body>
</html>
+4 -3
View File
@@ -183,11 +183,11 @@
<footer>
<div class="container">
<p>&copy; 2026 KoalaSync. <span lang="en">Open source under the MIT License.</span><span lang="de">Open Source unter der MIT-Lizenz.</span></p>
<p style="font-size: 0.8rem; margin-top: 0.5rem;"><span lang="en">No relay logs. No tracking. Website access logs are deleted after 7 days.</span><span lang="de">Keine Relay-Logs. Kein Tracking. Website-Access-Logs werden nach 7 Tagen gelöscht.</span></p>
<p style="font-size: 0.8rem; margin-top: 0.5rem;"><span lang="en">No chat or watch history. No tracking. Website access logs are deleted after 7 days.</span><span lang="de">Keine Chat- oder Wiedergabehistorie. Kein Tracking. Website-Access-Logs werden nach 7 Tagen gelöscht.</span></p>
<p class="footer-disclaimer" style="margin-top: 1.2rem;"><span lang="en">KoalaSync is not affiliated with, endorsed by, or associated with Netflix, Disney+, Amazon, YouTube, Twitch, or any other streaming platform. All trademarks are property of their respective owners.</span><span lang="de">KoalaSync ist nicht mit Netflix, Disney+, Amazon, YouTube, Twitch oder anderen Streaming-Plattformen verbunden, wird von diesen nicht unterstützt oder steht in keiner Beziehung zu diesen. Alle Markenrechte liegen bei ihren jeweiligen Inhabern.</span></p>
<div style="margin-top: 1.5rem; font-size: 0.8rem; display: flex; justify-content: center; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
<a href="imprint" lang="en" style="color: var(--text-muted); text-decoration: none;">Legal Notice</a>
<a href="de/impressum" lang="de" style="color: var(--text-muted); text-decoration: none;">Impressum</a>
<a href="https://koalastuff.net/legal" lang="en" style="color: var(--text-muted); text-decoration: none;">Legal Notice</a>
<a href="https://koalastuff.net/legal" lang="de" style="color: var(--text-muted); text-decoration: none;">Impressum</a>
<a href="privacy" lang="en" style="color: var(--text-muted); text-decoration: none;">Privacy Policy</a>
<a href="de/datenschutz" lang="de" style="color: var(--text-muted); text-decoration: none;">Datenschutz</a>
<a href="alternatives"><span lang="en">Guides & comparisons</span><span lang="de">Vergleiche & Anleitungen</span></a>
@@ -203,6 +203,7 @@
</div>
</footer>
<script src="invite-links.min.js"></script>
<script src="app.min.js"></script>
</body>
</html>
+3 -3
View File
@@ -19,7 +19,7 @@ KoalaSync is not a fit when users need:
- video or screen sharing to people who do not have access to the content
- a hosted video library, remote browser or cloud-streaming service
- built-in text, voice or webcam chat
- built-in voice, video or webcam chat
- a mobile app or Safari extension
- guaranteed compatibility with a player that does not expose an accessible HTML5 video element
@@ -78,7 +78,7 @@ Compatibility depends on each website's player implementation and can change whe
## Technical information
- Current website release: 2.5.4
- Current website release: 3.0.0
- License: MIT
- Extension runtime: dependency-free browser extension code
- Relay: Node.js with Socket.IO-compatible WebSocket messaging
@@ -112,6 +112,6 @@ Compatibility depends on each website's player implementation and can change whe
- [Main website](https://sync.koalastuff.net/)
- [GitHub issues](https://github.com/Shik3i/KoalaSync/issues): Bugs, compatibility reports and feature requests
- [Mastodon](https://mastodon.social/@koalastuff): Private contact and project updates
- [Legal notice](https://sync.koalastuff.net/imprint)
- [Legal notice](https://koalastuff.net/legal)
KoalaSync is an independent project and is not affiliated with, endorsed by or associated with Netflix, Disney+, Amazon, YouTube, Twitch or any other streaming platform. All trademarks belong to their respective owners.
+51 -46
View File
@@ -16,8 +16,9 @@
"NAV_HOW_IT_WORKS": "So funktioniert's",
"SUPPORT_BANNER_TEXT": "KoalaSync kann nicht auf deine Videoseite zugreifen?",
"SUPPORT_BANNER_CTA": "Websitezugriff reparieren",
"SUPPORT_BANNER_DISMISS": "Hinweis schließen",
"HERO_TITLE": "Mach jedes Video zur <span class='hero-highlight'>Watch Party</span>.",
"HERO_SUBTITLE": "Dein Kino-Abend auf Distanz, ohne Lags. Kein Account, kein Tracking, keine Werbung. Einfach Link teilen und gemeinsam Play drücken.",
"HERO_SUBTITLE": "Schaut Videos gemeinsam aus der Ferne. Kein Konto, kein Tracking, keine Werbung. Link teilen und zusammen starten.",
"HERO_MASCOT_ALT": "Ein niedlicher Koala steht da und schaut nach unten auf die Download-Buttons",
"DEMO_SYNC": "Synchron",
"DEMO_HINT": "Live-Demo: Drück Play in einem der Tabs, KoalaSync hält beide synchron.",
@@ -41,7 +42,7 @@
"USE_CASE_1_DESC": "Synchronisiere eure Filme in Echtzeit und quatscht nebenbei auf Discord, Zoom oder im Tool eurer Wahl. Es fühlt sich an, als säßet ihr im selben Raum.",
"USE_CASE_2_ALT": "Ein Koala-Professor hält einen Vortrag auf einem Bildschirm vor zwei remote zugeschalteten Schüler-Koalas",
"USE_CASE_2_TITLE": "Gemeinsam Lernen",
"USE_CASE_2_DESC": "Analysiert Online-Tutorials, Vorlesungen oder Streams gemeinsam mit Mitschülern oder Kollegen. Pausiert und besprecht komplizierte Schritte in perfektem Sync.",
"USE_CASE_2_DESC": "Analysiert Tutorials, Vorlesungen oder Schulungsvideos gemeinsam. Pausiert und besprecht komplexe Schritte in der Gruppe.",
"USE_CASE_3_ALT": "Ein niedliches Koala-Pärchen schaut remote zusammen; einer sitzt am PC und sie liegt mit dem Laptop im Bett, während Herzen zwischen ihnen fliegen",
"USE_CASE_3_TITLE": "Fernbeziehungen",
"USE_CASE_3_DESC": "Überbrückt die Distanz und genießt gemeinsame Date-Nächte. Erlebt jeden Plot-Twist, lacht über dieselben Witze und teilt emotionale Momente zur selben Millisekunde.",
@@ -114,8 +115,8 @@
"STEP_2_ILLUS_CREATE": "+ Neuer Raum",
"STEP_2_ILLUS_MANUAL": "Manuell verbinden / Erweitert",
"STEP_2_ILLUS_COPIED": "Einladungslink kopiert!",
"STEP_3_TITLE": "Synchron genießen",
"STEP_3_DESC": "Drücke auf Wiedergabe. Egal wer pausiert, spult oder startet: Alles läuft bei allen Beteiligten absolut zeitgleich und verzögerungsfrei ab.",
"STEP_3_TITLE": "Gemeinsam schauen",
"STEP_3_DESC": "Starte die Wiedergabe. Änderungen werden mit allen im Raum geteilt; Netzwerk und Videoplayer können eine kurze Verzögerung verursachen.",
"STEP_3_ILLUS_IN_SYNC": "SYNCHRON",
"SELF_TITLE": "Für Self-Hoster",
"SELF_SUBTITLE": "Du traust dem Server nicht? Behalte die volle Datenhoheit. Richte deinen eigenen privaten Relay-Server in wenigen Minuten ein.",
@@ -126,33 +127,33 @@
"BOTTOM_SUBTITLE": "KoalaSync ist zu 100 % Open Source, werbefrei und trackingsicher. Überprüfe unseren Code auf GitHub oder installiere direkt die Browser-Erweiterung.",
"BOTTOM_MASCOT_ALT": "Ein niedlicher Koala hält eine GitHub-Projektseite zusammen mit dem GitHub Maskottchen Octocat",
"FAQ_TITLE": "Häufig gestellte Fragen",
"FAQ_SUBTITLE": "Alles, was du über synchrones Videoschauen mit Freunden wissen musst.",
"FAQ_SUBTITLE": "Antworten zu unterstützten Websites, Synchronisierung, Datenschutz, Chat und Audioverarbeitung.",
"FAQ_Q1": "Kann ich Netflix, Emby oder Jellyfin synchron mit Freunden schauen?",
"FAQ_A1": "Ja! KoalaSync funktioniert mit Netflix, YouTube, Twitch und jeder Website mit einem HTML5-Videoplayer, einschließlich Emby, Jellyfin und eigenen Medienservern. Installiere die Erweiterung, erstelle einen Raum, teile den Einladungslink und alle schauen perfekt synchron.",
"FAQ_A1": "KoalaSync funktioniert mit vielen unterstützten Websites, die einen normalen Browser-Videoplayer verwenden, darunter Netflix, YouTube, Twitch, Emby, Jellyfin und selbst gehostete Medienserver. Die Kompatibilität hängt von Website, Browser und Player ab und kann sich durch Website-Updates ändern.",
"FAQ_Q_VIDEO_ACCESS": "Streamt KoalaSync das Video an meine Freunde?",
"FAQ_A_VIDEO_ACCESS": "Nein. KoalaSync streamt, hostet oder teilt keine Videoinhalte und umgeht keinen Zugang. Jede Person schaut das Video lokal im eigenen Browser und braucht selbst Zugriff auf die jeweilige Website oder den Streamingdienst, zum Beispiel ein eigenes Netflix-Konto. KoalaSync synchronisiert nur Aktionen wie Play, Pause und Spulen.",
"FAQ_Q2": "Ist KoalaSync eine kostenlose Alternative zu Teleparty?",
"FAQ_A2": "100 % kostenlos und Open Source unter der MIT-Lizenz. Keine Premium-Stufen, keine gesperrten Funktionen, keine Werbung. Anders als Teleparty läuft KoalaSyncs Relay-Server ausschließlich im RAM, ohne Datensammlung und ohne Benutzerkonten.",
"FAQ_A2": "Ja. KoalaSync ist unter der MIT-Lizenz kostenlos und Open Source ohne Premium-Stufe, gesperrte Funktionen, Werbung oder erforderliches Benutzerkonto.",
"FAQ_Q3": "Funktioniert KoalaSync mit Emby, Jellyfin und eigenen Medienservern?",
"FAQ_A3": "Ja. KoalaSync erkennt jedes HTML5-Videoelement und funktioniert perfekt mit Emby, Jellyfin, Plex und beliebigen eigenen Streaming-Setups. Du kannst den KoalaSync Relay-Server auch via Docker selbst hosten.",
"FAQ_A3": "Ja. KoalaSync unterstützt viele normale Browser-Videoplayer von Emby, Jellyfin, Plex und eigenen Streaming-Setups. Die Kompatibilität hängt vom jeweiligen Player ab. Den KoalaSync-Relay-Server kannst du außerdem mit Docker selbst hosten.",
"FAQ_Q4": "Welche Browser und Plattformen unterstützen KoalaSync?",
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave und alle Chromium-basierten Browser. Direkt aus dem Chrome Web Store oder den Firefox Add-ons installieren, keine manuelle Konfiguration nötig.",
"FAQ_Q5": "Müssen alle meine Freunde die Erweiterung installieren?",
"FAQ_A5": "Ja, jeder Teilnehmer braucht KoalaSync, aber ohne Anmeldung oder Kontoerstellung. Einfach auf den Einladungslink klicken, die Erweiterung installieren (falls nicht vorhanden) und du bist automatisch im Raum.",
"FAQ_Q6": "Sind meine Daten sicher? Was, wenn ich dem offiziellen Server nicht traue?",
"FAQ_A6": "Absolut sicher. Der KoalaSync Relay-Server läuft ausschließlich im RAM, alle Sitzungsdaten werden sofort gelöscht, sobald du den Raum verlässt. Keine Datenbank, keine Protokolle, keine Analysen, keine Benutzerkonten. Wenn du volle Kontrolle möchtest: hoste deinen eigenen privaten Relay-Server via Docker in wenigen Minuten. Der gesamte Quellcode ist Open Source und auf GitHub einsehbar.",
"FAQ_A6": "Der offizielle KoalaSync-Relay speichert Raum-, Sync-, Wiedergabe- und Chatverläufe nicht in einer Datenbank. Aktive Raumdaten liegen nur vorübergehend im Arbeitsspeicher und werden gelöscht, wenn der Raum leer ist oder wegen Inaktivität abläuft. Für Sicherheit und Missbrauchsschutz können technische Verbindungsprotokolle entstehen; deren Aufbewahrung hängt von der Serverkonfiguration ab. Chatnachrichten sind Ende-zu-Ende-verschlüsselt und werden nicht als serverseitiger Verlauf gespeichert. Den Relay kannst du über Docker auch selbst hosten.",
"FAQ_Q7": "Kann ich leise Dialoge und laute Explosionen beim Videoschauen im Browser ausgleichen?",
"FAQ_A7": "Ja. KoalaSync 2.2.0 enthält einen eingebauten Audio Compressor, der den Unterschied zwischen lauten und leisen Passagen reduziert. Er läuft lokal über die Web Audio API, keine Audiodaten verlassen deinen Computer. Öffne das Popup, erstelle einen Raum, gehe zu Einstellungen → Audio Processing, schalte den Compressor ein und wähle ein Preset. Funktioniert auf Netflix, Prime Video, Disney+, YouTube und allen HTML5-Video-Seiten.",
"FAQ_A7": "Ja. KoalaSync enthält einen Audio-Kompressor, der den Unterschied zwischen leisen und lauten Passagen reduziert. Er läuft lokal über die Web Audio API; Audiodaten werden nicht an den Relay gesendet. Öffne das Popup, erstelle einen Raum oder tritt einem bei und aktiviere ihn unter Einstellungen → Audioverarbeitung.",
"HOWTO_TITLE": "So synchronisierst du Videos mit Freunden mit KoalaSync",
"HOWTO_DESC": "Schaue Videos perfekt synchron mit Freunden auf YouTube, Netflix, Twitch und mehr. Keine Anmeldung, kein Tracking.",
"HOWTO_DESC": "Schaut unterstützte Videos gemeinsam auf YouTube, Netflix, Twitch und weiteren Seiten. Ohne Anmeldung oder Tracking.",
"HOWTO_STEP_1_NAME": "Browser-Erweiterung installieren",
"HOWTO_STEP_1_TEXT": "Füge KoalaSync aus dem Chrome Web Store oder den Firefox Add-ons zu deinem Browser hinzu. Funktioniert auf jeder Website mit einem Videoelement.",
"HOWTO_STEP_1_TEXT": "Installiere KoalaSync aus dem Chrome Web Store oder über Firefox Add-ons. Öffne eine unterstützte Seite und wähle ein von der Erweiterung erkanntes Video.",
"HOWTO_STEP_2_NAME": "Raum erstellen",
"HOWTO_STEP_2_TEXT": "Öffne das Erweiterungs-Popup und klicke auf Neuen Raum erstellen. Der Einladungslink wird automatisch in deine Zwischenablage kopiert.",
"HOWTO_STEP_3_NAME": "Teilen und synchronisieren",
"HOWTO_STEP_3_TEXT": "Sende den Einladungslink an deine Freunde. Wähle einen Video-Tab aus und genieße synchronisierte Wiedergabe in Echtzeit.",
"FOOTER_MIT": "Open Source unter der MIT-Lizenz.",
"FOOTER_RAM": "Keine Relay-Logs. Kein Tracking. Website-Access-Logs werden nach 7 Tagen gelöscht.",
"FOOTER_RAM": "Keine Chat- oder Wiedergabehistorie. Kein Tracking. Website-Access-Logs werden nach 7 Tagen gelöscht.",
"FOOTER_LEGAL": "Impressum",
"FOOTER_PRIVACY": "Datenschutz",
"MOCK_01": "Aktiver Raum",
@@ -190,7 +191,7 @@
"FOOTER_LEGAL_LINK": "de/impressum",
"FOOTER_PRIVACY_LINK": "de/datenschutz",
"FOOTER_DISCLAIMER": "KoalaSync ist nicht mit Netflix, Disney+, Amazon, YouTube, Twitch oder anderen Streaming-Plattformen verbunden, wird von diesen nicht unterstützt oder steht in keiner Beziehung zu diesen. Alle Markenrechte liegen bei ihren jeweiligen Inhabern.",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "KoalaSync unterstützen",
"ALT_INDEX_TITLE": "KoalaSync - Vergleiche & Anleitungen",
"ALT_INDEX_META_DESC": "Entdecke ehrliche Vergleiche und detaillierte Leitfäden zwischen KoalaSync und anderen Watch-Party-Erweiterungen.",
"ALT_INDEX_HEADING": "Vergleiche & Anleitungen",
@@ -200,17 +201,17 @@
"ALT_INDEX_FOOTER_LINK": "Vergleiche & Anleitungen",
"ALT_TELEPARTY_TITLE": "KoalaSync vs Teleparty: Welche Watch-Party-Lösung passt besser?",
"ALT_TELEPARTY_META_DESC": "Ein ehrlicher Vergleich von KoalaSync und Teleparty für Watch-Partys, eingebauten Chat, Mobile-Support, Browser-Video-Sync, selbst gehostete Medienserver und Open Source.",
"ALT_TELEPARTY_INTRO": "KoalaSync und Teleparty lösen beide dasselbe nervige Problem: Du willst mit anderen Leuten online etwas schauen, ohne ständig „3, 2, 1, Play“ sagen zu müssen. Beide synchronisieren Videowiedergabe, aber sie gehen ziemlich unterschiedlich an die Sache ran.<br><br>Teleparty ist sinnvoll, wenn du einen eingebauten Chat möchtest oder am Handy schauen willst. KoalaSync ist eher für Desktop-Browser-Watch-Partys gedacht, bei denen du sowieso schon mit Freunden in Discord, Teamspeak oder einem anderen Voice-Call bist und einfach nur die Wiedergabe synchron halten möchtest.<br><br>Der Hauptgrund, warum ich KoalaSync gebaut habe, war Flexibilität. Es soll mit fast jeder Website funktionieren, die einen normalen Browser-Videoplayer nutzt, nicht nur mit einer festen Liste an Plattformen. Dazu gehören auch selbst gehostete Setups wie Emby, Jellyfin und Plex, lokale oder private Domains und viele normale Videoseiten im Browser.",
"ALT_TELEPARTY_INTRO": "KoalaSync und Teleparty lösen beide dasselbe nervige Problem: Du willst mit anderen Leuten online etwas schauen, ohne ständig „3, 2, 1, Play“ sagen zu müssen. Beide bieten Text-Chat, gehen die Watch-Party aber unterschiedlich an.<br><br>Teleparty ist sinnvoll, wenn du ein stärker integriertes Watch-Party-Erlebnis bevorzugst oder am Handy schauen willst. KoalaSync ist für Desktop-Browser-Watch-Partys mit einer größeren Auswahl normaler Browser-Videoplayer gedacht. Der eingebaute Text-Chat ist Ende-zu-Ende-verschlüsselt und nur live verfügbar; Sprach- und Videoanrufe bleiben getrennt.<br><br>Der Hauptgrund, warum ich KoalaSync gebaut habe, war Flexibilität. Es soll mit fast jeder Website funktionieren, die einen normalen Browser-Videoplayer nutzt, nicht nur mit einer festen Liste an Plattformen. Dazu gehören auch selbst gehostete Setups wie Emby, Jellyfin und Plex, lokale oder private Domains und viele normale Videoseiten im Browser.",
"ALT_TELEPARTY_WHEN_TELE_TITLE": "Wann Teleparty trotzdem sinnvoller sein kann",
"ALT_TELEPARTY_WHEN_TELE_BODY": "Teleparty kann trotzdem die bessere Wahl sein, wenn du den Chat direkt im Watch-Party-Tool haben möchtest. KoalaSync hat aktuell keinen eingebauten Chat. Es geht eher davon aus, dass du sowieso schon woanders mit deinen Freunden redest, zum Beispiel in Discord, Teamspeak, WhatsApp, Signal oder einem normalen Anruf.<br><br>Mobile ist der zweite große Unterschied. Teleparty hat eine App, während KoalaSync aktuell nur auf Desktop-Browsern läuft. Wenn du Netflix oder andere Inhalte am Handy oder Tablet synchron schauen möchtest, ist KoalaSync im Moment nicht das richtige Tool dafür.<br><br>Wenn dir also vor allem eingebauter Chat und Mobile-Support wichtig sind, passt Teleparty wahrscheinlich besser zu deinem Setup.",
"ALT_TELEPARTY_WHEN_TELE_BODY": "Teleparty kann trotzdem die bessere Wahl sein, wenn du das stärker integrierte Watch-Party-Erlebnis bevorzugst oder Mobile-Support brauchst. Beide Tools bieten Text-Chat, daher ist der Chat allein nicht mehr der entscheidende Unterschied. Der KoalaSync-Chat ist Ende-zu-Ende-verschlüsselt und nur live verfügbar; Sprach- und Videoanrufe laufen weiterhin über eine separate App.<br><br>Mobile ist der zweite große Unterschied. Teleparty hat eine App, während KoalaSync aktuell nur auf Desktop-Browsern läuft. Wenn du Netflix oder andere Inhalte am Handy oder Tablet synchron schauen möchtest, ist KoalaSync im Moment nicht das richtige Tool dafür.<br><br>Wenn dir also vor allem Mobile-Support und eine stärker integrierte Plattform wichtig sind, passt Teleparty wahrscheinlich besser zu deinem Setup.",
"ALT_TELEPARTY_WHEN_KOALA_TITLE": "Wann KoalaSync besser passt",
"ALT_TELEPARTY_WHEN_KOALA_BODY": "KoalaSync passt besser, wenn du ein flexibles Sync-Tool für den Desktop-Browser suchst und nicht auf eine kurze Plattformliste beschränkt sein möchtest.<br><br>Es funktioniert mit fast jeder Website, die einen normalen Browser-Videoplayer nutzt. Das ist besonders nützlich für Emby, Jellyfin, Plex, private Medienserver, lokale Domains oder kleinere Videoseiten, die klassische Watch-Party-Tools meistens gar nicht beachten.<br><br>Außerdem ist KoalaSync kostenlos, Open Source, braucht keine Accounts und kann bei Bedarf selbst gehostet werden. Ich habe es gebaut, weil ich etwas Privacy-freundliches wollte, das mit meinem eigenen Emby-Setup funktioniert, nicht noch eine geschlossene Plattform nur für ein paar große Streaming-Seiten.",
"ALT_TELEPARTY_MAIN_DIFF_TITLE": "Der wichtigste Unterschied",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty ist eher ein Watch-Party-Produkt mit Chat und Mobile-Unterstützung. KoalaSync ist eher eine flexible Sync-Schicht für Browser-Video.<br><br>Das klingt erstmal nach einem kleinen Unterschied, ändert aber den Use Case ziemlich stark. Wenn du eine Watch-Party-App mit eingebautem Chat brauchst, ergibt Teleparty Sinn. Wenn du schon einen Voice-Call hast und nur möchtest, dass Play, Pause und Seek zwischen mehreren Browsern synchron bleiben, passt KoalaSync besser.",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty ist eher ein integriertes Watch-Party-Produkt mit Mobile-Unterstützung. KoalaSync ist eine flexible Sync-Schicht für Desktop-Browser mit Ende-zu-Ende-verschlüsseltem Live-Text-Chat.<br><br>Das klingt erstmal nach einem kleinen Unterschied, ändert aber den Use Case ziemlich stark. Wenn du eine mobile Watch-Party-App brauchst, ergibt Teleparty Sinn. Wenn du breite Browser-Player-Unterstützung einschließlich selbst gehosteter Medien möchtest, ohne dem Relay lesbare Chatnachrichten zu übermitteln, passt KoalaSync besser.",
"ALT_TELEPARTY_LIMITS_TITLE": "Ehrliche Grenzen",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync ist nicht magisch und versucht auch nicht, seine Grenzen zu verstecken. Es funktioniert aktuell nur auf Desktop-Browsern. Es gibt keinen eingebauten Chat und keine Mobile-App.<br><br>Die Kompatibilität hängt außerdem vom Browser und vom jeweiligen Videoplayer ab. KoalaSync sollte mit fast jedem normalen Browser-Videoplayer funktionieren, aber Websites können ihre Player, DRM-Logik oder Browser-Einschränkungen jederzeit ändern.<br><br>KoalaSync ist nicht mit Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex oder anderen hier genannten Marken verbunden. Die Namen werden nur verwendet, um typische Anwendungsfälle verständlich zu beschreiben.",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync ist nicht magisch und versucht auch nicht, seine Grenzen zu verstecken. Es funktioniert aktuell nur auf Desktop-Browsern. Es bietet Ende-zu-Ende-verschlüsselten Text-Chat, aber keinen Sprach-, Video- oder Webcam-Chat und keine Mobile-App.<br><br>Die Kompatibilität hängt außerdem vom Browser und vom jeweiligen Videoplayer ab. KoalaSync sollte mit fast jedem normalen Browser-Videoplayer funktionieren, aber Websites können ihre Player, DRM-Logik oder Browser-Einschränkungen jederzeit ändern.<br><br>KoalaSync ist nicht mit Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex oder anderen hier genannten Marken verbunden. Die Namen werden nur verwendet, um typische Anwendungsfälle verständlich zu beschreiben.",
"ALT_TELEPARTY_SUMMARY_TITLE": "Kurz gesagt",
"ALT_TELEPARTY_SUMMARY_BODY": "Wenn du eingebauten Chat oder Mobile-Support brauchst, passt Teleparty wahrscheinlich besser. Wenn du kostenlose, Open-Source-Desktop-Browser-Synchronisierung möchtest, die mit fast jedem normalen Videoplayer funktioniert, inklusive selbst gehosteter Medienserver wie Emby, Jellyfin und Plex, ist KoalaSync wahrscheinlich die passendere Wahl.",
"ALT_TELEPARTY_SUMMARY_BODY": "Beide Tools bieten Text-Chat. Wenn du Mobile-Support brauchst oder Telepartys stärker integriertes Watch-Party-Erlebnis bevorzugst, passt Teleparty wahrscheinlich besser. Wenn du kostenlose Open-Source-Synchronisierung für Desktop-Browser mit Ende-zu-Ende-verschlüsseltem Live-Chat möchtest, die mit fast jedem normalen Videoplayer funktioniert, einschließlich selbst gehosteter Medienserver wie Emby, Jellyfin und Plex, ist KoalaSync wahrscheinlich die passendere Wahl.",
"ALT_SCREEN_TITLE": "KoalaSync vs Bildschirmübertragung: Wann ist Video-Sync besser?",
"ALT_SCREEN_META_DESC": "Ein ehrlicher Vergleich von KoalaSync und Bildschirmübertragung für Filmabende, Wiedergabekontrolle, Videoqualität, Privatsphäre, Untertitel und gemeinsames Schauen im Browser.",
"ALT_SCREEN_INTRO": "Bildschirmübertragung funktioniert, und manchmal reicht das auch völlig. Eine Person teilt den Bildschirm, alle anderen schauen zu, fertig.<br><br>Bei längeren Filmabenden oder Serien kann das aber schnell nervig werden. Die Qualität hängt vom Upload, der CPU, dem Browser und der Komprimierung durch Discord, Teamspeak, Zoom oder andere Call-Apps ab. Außerdem kontrolliert nur eine Person wirklich die Wiedergabe.",
@@ -221,61 +222,61 @@
"ALT_SCREEN_WHY_CLEANER_TITLE": "Warum KoalaSync sauberer sein kann",
"ALT_SCREEN_WHY_CLEANER_BODY": "KoalaSync überträgt nicht deinen Bildschirm. Jeder schaut das Video lokal im eigenen Browser, und KoalaSync hält nur Aktionen wie Play, Pause und Spulen zwischen allen Teilnehmern synchron.<br><br>Dadurch kann jeder lokal Vollbild nutzen, eigene Untertitel oder Audio-Einstellungen verwenden, sofern der Player das unterstützt, und der Voice-Call bleibt separat in Discord, Teamspeak oder einer anderen App.<br><br>Für Filmabende fühlt sich das oft deutlich sauberer an, als wenn eine Person den ganzen Bildschirm streamt. Besonders praktisch ist das bei browserbasierten Setups wie Emby, Jellyfin, Plex, lokalen Dateien im Browser, privaten Domains oder anderen Websites mit normalen Browser-Videoplayern.",
"ALT_SCREEN_LIMITS_TITLE": "Ehrliche Grenzen",
"ALT_SCREEN_LIMITS_BODY": "KoalaSync ersetzt keinen Voice-Call. Wenn ihr beim Schauen reden wollt, nutzt ihr weiterhin Discord, Teamspeak, Signal, WhatsApp oder was auch immer ihr normalerweise verwendet.<br><br>Außerdem braucht jede Person selbst Zugriff auf das Video oder die jeweilige Seite. KoalaSync umgeht keine Abos, Logins, DRM, regionale Einschränkungen oder Plattformregeln.<br><br>KoalaSync ist aktuell Desktop-Browser-only, hat keinen eingebauten Chat oder Voice-Chat und die Funktion hängt immer vom jeweiligen Browser und Videoplayer ab.",
"ALT_SCREEN_LIMITS_BODY": "KoalaSync bietet Ende-zu-Ende-verschlüsselten Text-Chat, ersetzt aber keinen Sprach- oder Videoanruf. Wenn ihr beim Schauen reden wollt, nutzt ihr weiterhin Discord, Teamspeak, Signal, WhatsApp oder was auch immer ihr normalerweise verwendet.<br><br>Außerdem braucht jede Person selbst Zugriff auf das Video oder die jeweilige Seite. KoalaSync umgeht keine Abos, Logins, DRM, regionale Einschränkungen oder Plattformregeln.<br><br>KoalaSync ist aktuell Desktop-Browser-only, hat keinen eingebauten Sprach-, Video- oder Webcam-Chat und die Funktion hängt immer vom jeweiligen Browser und Videoplayer ab.",
"ALT_SCREEN_SUMMARY_TITLE": "Kurz gesagt",
"ALT_SCREEN_SUMMARY_BODY": "Bildschirmübertragung ist die einfachste Lösung, wenn es schnell gehen soll und Qualität, Privatsphäre oder individuelle Kontrolle nicht so wichtig sind. KoalaSync passt besser, wenn jeder lokal im eigenen Browser schauen soll und sich der Filmabend mehr wie wirklich gemeinsames Schauen anfühlen soll.",
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Welche Watch-Party-Lösung passt besser?",
"ALT_W2G_META_DESC": "Ein ehrlicher Vergleich von KoalaSync und Watch2Gether für Watch-Partys. Erfahre, wann KoalaSyncs Flexibilität unschlagbar ist und warum Watch2Gether für YouTube die Nase vorn hat.",
"ALT_W2G_META_DESC": "Ein praktischer Vergleich von KoalaSync und Watch2Gether zu gemeinsamen Räumen, Playlists, Browser-Player-Synchronisierung und verschlüsseltem Text-Chat.",
"ALT_W2G_INTRO": "KoalaSync und Watch2Gether lösen beide dasselbe Problem: Du willst mit Freunden online Videos schauen, ohne ständig manuell abgleichen zu müssen. Während Watch2Gether einer der ältesten und bekanntesten Dienste für gemeinsame Videoabende ist, geht KoalaSync einen ganz anderen, minimalistischen Weg. Welches Tool das richtige für dich ist, hängt stark davon ab, was und wo du schauen möchtest.",
"ALT_W2G_WHEN_W2G_TITLE": "Wann Watch2Gether die bessere Wahl ist",
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether ist unschlagbar für <strong>YouTube-Watch-Partys</strong>. Dank integrierter Playlist und Suche kann jeder in der Lobby sofort neue Videos suchen, hinzufügen und abspielen. Alle schauen nahtlos das nächste Video zusammen.<br><br>Bei KoalaSync greift auf YouTube die Autoplay-Logik der Website: Nach Videoende wird jeder auf ein anderes Empfehlungsvideo geleitet. Unsere automatische Folgen-Fortsetzung („Episode Lobby“), die auf Netflix oder Disney+ super funktioniert, klappt auf YouTube wegen dieser dynamischen Vorschläge leider nicht. Für reine YouTube-Abende ist Watch2Gether daher die bessere Wahl.",
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether kann besser passen, wenn bei YouTube-Sessions ein gemeinsamer Raum, Suche und Playlist im Mittelpunkt stehen. Unterstützung und Verhalten hängen vom aktuellen Watch2Gether-Dienst und der Inhaltsquelle ab.",
"ALT_W2G_WHEN_KOALA_TITLE": "Wann KoalaSync besser passt",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync glänzt bei <strong>echten Streaming-Diensten</strong> (Netflix, Prime Video, Disney+) und selbstgehosteten Medienservern (Jellyfin, Plex, Emby). Im Gegensatz zu Watch2Gether, das oft keine Premium-Inhalte abspielen kann, synchronisiert KoalaSync den echten Original-Player direkt auf der jeweiligen Website.<br><br>Es funktioniert auf fast jeder Seite mit einem normalen HTML5-Videoplayer. Du musst keinen virtuellen Raum auf einer Drittseite erstellen: Einfach die Extension aktivieren, mit Freunden verbinden und losschauen.",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync passt, wenn du den Original-Player auf einer unterstützten Browserseite synchronisieren chtest, einschließlich selbst gehosteter Medien, ohne die Wiedergabe auf eine separate Watch-Party-Seite zu verlagern. Die Kompatibilität hängt weiterhin von Website, Player und Browser ab.",
"ALT_W2G_MAIN_DIFF_TITLE": "Der wichtigste Unterschied",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether ist eine <strong>eigenständige Website</strong> mit integriertem Chat, Playlist und Webcam-Support. KoalaSync ist eine <strong>minimalistische Erweiterung</strong>, die sich direkt in deine normalen Browser-Tabs einklinkt.<br><br>Suchst du ein All-in-One-Paket für YouTube mit Chat? Nimm Watch2Gether. Willst du Filme auf Netflix oder deinem eigenen Server ohne Ablenkung, ohne Umwege und in voller nativer Bildqualität (ohne Server-Komprimierung) genießen? Dann ist KoalaSync die sauberere Lösung.",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether ist eine <strong>eigenständige Website</strong> mit Text-Chat, Playlists, Suche und Webcam-Support. KoalaSync ist eine <strong>minimalistische Erweiterung</strong>, die sich direkt in deine normalen Browser-Tabs einklinkt und ebenfalls Ende-zu-Ende-verschlüsselten Live-Text-Chat bietet.<br><br>Suchst du ein All-in-One-Paket für YouTube mit Playlists, Suche und Webcam-Funktionen? Nimm Watch2Gether. Willst du Filme auf Netflix oder deinem eigenen Server ohne Ablenkung, ohne Umwege und in voller nativer Bildqualität (ohne Server-Komprimierung) genießen? Dann ist KoalaSync die sauberere Lösung.",
"ALT_W2G_LIMITS_TITLE": "Ehrliche Grenzen",
"ALT_W2G_LIMITS_BODY": "KoalaSync hat <strong>keine eigene Playlist oder Suche</strong> wie Watch2Gether. Auf YouTube führt das dazu, dass das automatische Abspielen der nächsten Videos nicht synchron läuft.<br><br>Zudem gibt es <strong>keinen integrierten Chat</strong> und KoalaSync läuft aktuell nur auf Desktop-Browsern. Es ist so gedacht, dass ihr ohnehin über Discord, Teamspeak oder andere Voice-Tools miteinander sprecht.",
"ALT_W2G_LIMITS_BODY": "KoalaSync hat <strong>keine eigenen Playlists oder Videosuche</strong>. Auf YouTube führt das dazu, dass das automatische Abspielen des nächsten Videos nicht synchron läuft.<br><br>KoalaSync bietet Ende-zu-Ende-verschlüsselten Live-Text-Chat, aber keinen eingebauten Webcam-, Sprach- oder Video-Chat und läuft aktuell nur auf Desktop-Browsern. Wenn ihr miteinander reden oder euch sehen wollt, braucht ihr weiterhin Discord, Teamspeak oder ein anderes Call-Tool.",
"ALT_W2G_SUMMARY_TITLE": "Kurz gesagt",
"ALT_W2G_SUMMARY_BODY": "Für spontane YouTube-Sessions mit Playlists und integriertem Chat ist Watch2Gether die beste Wahl. Für echte Premium-Streaming-Plattformen (wie Netflix, Prime Video, Disney+), selbstgehostete Server (Jellyfin, Plex, Emby) und maximale Flexibilität im Browser ist KoalaSync das passendere und schlankere Tool.",
"ALT_W2G_SUMMARY_BODY": "Beide Tools bieten Text-Chat. Für spontane YouTube-Sessions mit Playlists, Suche und Webcam-Funktionen ist Watch2Gether die bessere Wahl. Für Premium-Streaming-Plattformen wie Netflix, Prime Video und Disney+, selbst gehostete Server wie Jellyfin, Plex und Emby, Ende-zu-Ende-verschlüsselten Live-Chat und maximale Flexibilität im Browser ist KoalaSync das passendere und schlankere Tool.",
"ALT_SCENER_TITLE": "KoalaSync vs Scener: Welche Watch-Party-Lösung passt besser?",
"ALT_SCENER_META_DESC": "Ein ehrlicher Vergleich von KoalaSync und Scener. Erfahre, wann Scener mit Webcam-Chat punktet und warum KoalaSync für werbefreie Filmabende die Nase vorn hat.",
"ALT_SCENER_INTRO": "KoalaSync und Scener gehen das Thema Watch-Partys aus zwei völlig verschiedenen Richtungen an. Scener versteht sich als virtuelles Kino mit integriertem Video-Chat und interaktiven Räumen. KoalaSync ist dagegen eine minimalistische, datenschutzfreundliche Browser-Erweiterung, die einfach nur deine Videowiedergabe synchronisiert. Welches Tool besser zu dir passt, hängt vor allem davon ab, wie wichtig dir Webcams und integrierter Chat beim Streamen sind.",
"ALT_SCENER_INTRO": "KoalaSync und Scener gehen das Thema Watch-Partys aus zwei völlig verschiedenen Richtungen an. Scener versteht sich als virtuelles Kino mit integriertem Video-Chat und interaktiven Räumen. KoalaSync ist dagegen eine minimalistische, datenschutzfreundliche Browser-Erweiterung, die die Videowiedergabe synchronisiert und Ende-zu-Ende-verschlüsselten Live-Text-Chat bietet. Welches Tool besser zu dir passt, hängt vor allem davon ab, ob du eingebaute Webcam-, Sprach-, Video- und Social-Funktionen brauchst oder ein schlankes Sync-Tool bevorzugst.",
"ALT_SCENER_WHEN_SCENER_TITLE": "Wann Scener die bessere Wahl ist",
"ALT_SCENER_WHEN_SCENER_BODY": "Scener ist eine solide Wahl, wenn du eine <strong>All-in-One-Social-Plattform</strong> mit integriertem Video- und Audio-Chat direkt im Browserfenster suchst. Scener erstellt einen virtuellen Raum, in dem der Player und die Webcams deiner Freunde nebeneinander angezeigt werden. Dies ist ideal für große, öffentliche Watch-Partys oder virtuelle Streaming-Events, bei denen der soziale Aspekt im Vordergrund steht. Beachte jedoch, dass die kostenlose Basisversion durch Werbung finanziert wird und Scener viele Funktionen (wie werbefreies Schauen und unbegrenzten Video-Chat) hinter einem kostenpflichtigen Premium-Abo ($5.79/Monat oder $39.99/Jahr) versteckt.",
"ALT_SCENER_WHEN_SCENER_BODY": "Scener passt besser, wenn du ein integriertes Social-Erlebnis mit Text-Chat und für Premium-Mitglieder Video- und Audio-Chat möchtest. Auf Computern unterstützt Scener eine festgelegte Auswahl von Streamingdiensten über seine Chrome-Erweiterung.",
"ALT_SCENER_WHEN_KOALA_TITLE": "Wann KoalaSync besser passt",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync punktet bei <strong>Bildqualität, Datenschutz und Einfachheit</strong>:<br><br>• <strong>Keine Accounts</strong>: Du musst dich nicht registrieren, einloggen oder Profile verwalten. Einfach Extension aktivieren, Raumcode teilen und losschauen.<br>• <strong>Echte native Qualität (4K/HDR)</strong>: Scener streamt ein komprimiertes Bild von einem virtuellen Cloud-Server, was zu Qualitätsverlusten und Rucklern führen kann. KoalaSync synchronisiert den echten Original-Player direkt auf deinem Computer. Du schaust Filme also in voller nativer Auflösung deines Streaming-Abos.<br>• <strong>Geringer Datenverbrauch</strong>: Da nur Steuerungsdaten (Play, Pause, Zeitstempel) übertragen werden, verbraucht KoalaSync fast keine zusätzliche Bandbreite. Scener streamt das gesamte Video und Kamerabilder über Cloud-Server, was leistungsschwächere Laptops und Internetleitungen stark beanspruchen kann.<br>• <strong>100 % kostenlos & werbefrei</strong>: Keine monatlichen Gebühren, keine Werbung.<br>• <strong>Lokale Videodateien</strong>: Da KoalaSync lokal läuft, kannst du sogar eigene Videodateien (z. B. MP4) synchron mit Freunden schauen. Ziehe die Datei einfach per Drag-and-Drop in ein Browser-Tab Scener kann das bauartbedingt (da Scener in der Cloud läuft) nicht.",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync passt, wenn du kontofreie Open-Source-Synchronisierung im Original-Player, Ende-zu-Ende-verschlüsselten Live-Text-Chat und optional einen selbst gehosteten Relay bevorzugst. Sprach-, Video- oder Webcam-Chat sind nicht enthalten.",
"ALT_SCENER_MAIN_DIFF_TITLE": "Der wichtigste Unterschied",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener ist ein <strong>kommerzielles soziales Netzwerk</strong> für Watch-Partys, das auf virtuellen Cloud-Browsern basiert und mit Werbung sowie monatlichen Abonnements finanziert wird. KoalaSync ist eine <strong>datenschutzfreundliche, kostenlose Synchronisations-Erweiterung</strong>, die sich direkt in deine Browser-Tabs einklinkt. Sie überlässt den Chat Programmen wie Discord oder Teamspeak und konzentriert sich ganz darauf, deine Wiedergabe flüssig, privat und in bester Originalqualität zu synchronisieren.",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener konzentriert sich auf integrierte Räume und Social-Funktionen. KoalaSync konzentriert sich auf schlanke Synchronisierung im Original-Player und Ende-zu-Ende-verschlüsselten Live-Text-Chat.",
"ALT_SCENER_LIMITS_TITLE": "Ehrliche Grenzen",
"ALT_SCENER_LIMITS_BODY": "KoalaSync bietet <strong>keinen eigenen Chat und keine Webcam-Unterstützung</strong>. Es ist ausschließlich als Sync-Tool gedacht und erfordert ein separates Kommunikations-Tool deiner Wahl. Zudem ist KoalaSync derzeit nur für Desktop-Browser verfügbar. Die Kompatibilität lokaler Videodateien hängt außerdem davon ab, ob dein Browser das jeweilige Videoformat und die Audio-Codecs nativ abspielen kann.",
"ALT_SCENER_LIMITS_BODY": "KoalaSync bietet <strong>Ende-zu-Ende-verschlüsselten Live-Text-Chat, aber keinen Webcam-, Sprach- oder Video-Chat</strong>. Zum Reden oder gegenseitigen Sehen braucht ihr daher ein separates Call-Tool eurer Wahl. Zudem ist KoalaSync derzeit nur für Desktop-Browser verfügbar. Die Kompatibilität lokaler Videodateien hängt außerdem davon ab, ob dein Browser das jeweilige Videoformat und die Audio-Codecs nativ abspielen kann.",
"ALT_SCENER_SUMMARY_TITLE": "Kurz gesagt",
"ALT_SCENER_SUMMARY_BODY": "Für große Watch-Partys mit Webcam-Chat und Social-Features im selben Fenster ist Scener die richtige Wahl, sofern dich Werbung und Premium-Gating nicht stören. Wenn du einfach nur komplett kostenlos, werbefrei und ungestört im echten Player (inklusive Netflix, Prime Video, Disney+ oder sogar lokalen Dateien im Browser) synchron schauen willst und ohnehin in Discord telefonierst, ist KoalaSync die sauberere und performantere Lösung.",
"ALT_SCENER_SUMMARY_BODY": "Wähle Scener für integrierte Social-, Video- und Audiofunktionen auf den unterstützten Diensten. Wähle KoalaSync für kontofreie Open-Source-Browser-Synchronisierung, verschlüsselten Live-Text-Chat und optionales Self-Hosting.",
"ALT_KOSMI_TITLE": "KoalaSync vs Kosmi: Welche Netflix-Party-Alternative passt besser?",
"ALT_KOSMI_META_DESC": "Ein ehrlicher Vergleich von KoalaSync und Kosmi. Erfahre, warum Kosmis bequeme No-Extension-Lösung punktet und wann KoalaSyncs native Bildqualität gewinnt.",
"ALT_KOSMI_INTRO": "Sowohl KoalaSync als auch Kosmi bieten hervorragende Möglichkeiten, um Netflix, YouTube und andere Plattformen synchron mit Freunden zu schauen. Doch technisch arbeiten beide Tools grundlegend verschieden. Kosmi setzt auf virtuelle Cloud-Browser, die ohne Erweiterung direkt im Web laufen. KoalaSync ist eine minimalistische Browser-Erweiterung, die deinen lokalen Player synchronisiert. Welcher Ansatz für dich der bessere ist, hängt von deinen Ansprüchen an Bildqualität, Komfort und Datenschutz ab.",
"ALT_KOSMI_INTRO": "KoalaSync und Kosmi ermöglichen gemeinsames Schauen, sind aber eigenständige Produkte mit unterschiedlichen unterstützten Diensten und Social-Funktionen. KoalaSync synchronisiert einen unterstützten Videoplayer im Desktop-Browser. Prüfe bei jedem Dienst die aktuelle Geräte- und Plattformunterstützung.",
"ALT_KOSMI_WHEN_KOSMI_TITLE": "Wann Kosmi die bessere Wahl ist",
"ALT_KOSMI_WHEN_KOSMI_BODY": "Kosmi ist unschlagbar, wenn du <strong>überhaupt nichts installieren</strong> möchtest oder auf Geräten wie Tablets oder Smartphones mitschauen willst. Da Kosmi über virtuelle Browser in der Cloud läuft, reicht ein einfacher Weblink aus, um beizutreten. Zudem bietet Kosmi eine All-in-One-Plattform: Neben Video-Streaming könnt ihr in den virtuellen Räumen auch Multiplayer-Spiele spielen (z. B. NES/SNES-Klassiker oder Pokertische) und den integrierten Webcam-, Voice- und Text-Chat nutzen. Wenn der Fokus auf einem geselligen Spiele- und Videoabend ohne jegliche Installation liegt, ist Kosmi eine fantastische Lösung.",
"ALT_KOSMI_WHEN_KOSMI_BODY": "Kosmi kann besser passen, wenn du dessen browserbasierte Social-Räume, Spiele und integrierte Kommunikationsfunktionen möchtest. Die Verfügbarkeit kann je nach Gerät, Inhaltsquelle und aktuellem Kosmi-Angebot variieren.",
"ALT_KOSMI_WHEN_KOALA_TITLE": "Wann KoalaSync besser passt",
"ALT_KOSMI_WHEN_KOALA_BODY": "KoalaSync glänzt durch seine <strong>native Wiedergabe, maximale Sicherheit und Performance</strong>:<br><br>• <strong>Keine Sicherheitsrisiken</strong>: Bei Kosmi musst du dich auf einem fremden virtuellen Computer in der Cloud in deinen Netflix- oder Prime-Account einloggen. Bei KoalaSync loggst du dich wie gewohnt auf der echten Streaming-Website in deinem eigenen Browser ein. Deine Passwörter verlassen niemals deinen Computer.<br>• <strong>Volle Bildqualität (4K/HDR)</strong>: Kosmi streamt das Bild des virtuellen Browsers als komprimierten Videofeed an dich. KoalaSync synchronisiert die Player lokal: Du genießt Filme in der vollen, gestochen scharfen Originalqualität deines Abos, ohne Pixelmatsch oder Ruckler.<br>• <strong>Schonend für Internet und Akku</strong>: Da KoalaSync nur Steuerdaten (Play/Pause/Zeitstempel) sendet, verbraucht es fast null Bandbreite. Kosmi streamt permanent ein HD-Video aus der Cloud, was schwache Internetverbindungen überlasten kann.<br>• <strong>Lokale Videodateien</strong>: Wenn du ein eigenes Video synchron schauen willst, ziehst du die MP4-Datei einfach per Drag-and-Drop in ein Browser-Tab. KoalaSync synchronisiert sie sofort. Kosmi kann auf deine lokalen Dateien auf der Festplatte nicht zugreifen.",
"ALT_KOSMI_WHEN_KOALA_BODY": "KoalaSync passt besser, wenn du einen unterstützten Player in deinem eigenen Desktop-Browser synchronisieren, Ende-zu-Ende-verschlüsselten Live-Text-Chat nutzen oder den Relay selbst hosten möchtest. Sprach-, Video- oder Webcam-Chat und eine Mobile-App sind nicht enthalten.",
"ALT_KOSMI_MAIN_DIFF_TITLE": "Der wichtigste Unterschied",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmi ist ein <strong>virtueller Treffpunkt</strong> in der Cloud, der einen Remote-Browser per Video-Feed streamt, damit niemand eine Erweiterung installieren muss. KoalaSync ist eine <strong>Schnittstelle für den eigenen Browser</strong>, die sich direkt in den nativen Player auf der echten Website einklinkt. Während Kosmi vielseitiger für Minispiele und Geräte-Kompatibilität ist, bietet KoalaSync das sicherere, flüssigere und schärfere Streaming-Erlebnis im Original-Player.",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmi konzentriert sich auf einen integrierten Social-Raum. KoalaSync ist eine Desktop-Browser-Erweiterung, die unterstützte Player synchronisiert und verschlüsselten Live-Text-Chat bietet.",
"ALT_KOSMI_LIMITS_TITLE": "Ehrliche Grenzen",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync erfordert die <strong>Installation einer Browser-Erweiterung</strong> und läuft derzeit nur auf Desktop-PCs (Windows, Mac, Linux). Es hat <strong>keine integrierten Chats oder Webcams</strong>, da es so gedacht ist, dass ihr ohnehin über Discord oder andere Voice-Tools miteinander sprecht. Zudem bietet KoalaSync keine integrierten Spiele wie Kosmi.",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync erfordert die <strong>Installation einer Browser-Erweiterung</strong> und läuft derzeit nur auf Desktop-PCs. Es bietet <strong>Ende-zu-Ende-verschlüsselten Live-Text-Chat, aber keinen Webcam-, Sprach- oder Video-Chat</strong>. Zudem bietet KoalaSync keine integrierten Spiele.",
"ALT_KOSMI_SUMMARY_TITLE": "Kurz gesagt",
"ALT_KOSMI_SUMMARY_BODY": "Für gemütliche Spieleabende inklusive SNES-Klassikern und einfachem Beitreten ohne jegliche Installation auf Smartphones oder Tablets ist Kosmi ideal. Für private Filmabende auf Netflix, Prime Video, Disney+ oder Plex in bester 4K-Originalqualität, mit maximaler Passwort-Sicherheit und minimaler Internet-Belastung ist KoalaSync die deutlich überlegene Wahl.",
"ALT_KOSMI_SUMMARY_BODY": "Wähle Kosmi für dessen integrierte Social-Raum-Funktionen. Wähle KoalaSync für kontofreie Open-Source-Synchronisierung am Desktop, verschlüsselten Live-Text-Chat und optionales Relay-Self-Hosting.",
"ALT_TWOSEVEN_TITLE": "KoalaSync vs TwoSeven: Welche Watch-Party-Lösung passt besser?",
"ALT_TWOSEVEN_META_DESC": "Ein ehrlicher Vergleich von KoalaSync und TwoSeven. Erfahre, wann TwoSeven mit integrierter Webcam punktet und warum KoalaSync für Disney+ und Netflix die Nase vorn hat.",
"ALT_TWOSEVEN_INTRO": "Sowohl KoalaSync als auch TwoSeven nutzen denselben grundlegenden Ansatz für Watch-Partys: Sie synchronisieren den originalen Videoplayer direkt in deinem Browser, anstatt ein komprimiertes Bild aus der Cloud zu streamen. Dadurch bleibt die Bildqualität bei beiden Tools auf Top-Niveau. Doch während TwoSeven als kommerzielles Portal mit integriertem Webcam-Chat und Abonnement-Optionen aufgebaut ist, bleibt KoalaSync eine minimalistische, komplett kostenfreie und datenschutzfreundliche Erweiterung. Welcher Dienst besser zu dir passt, erfährst du hier.",
"ALT_TWOSEVEN_META_DESC": "Ein praktischer Vergleich von KoalaSync und TwoSeven zu Kontopflicht, integrierter Kommunikation, verschlüsseltem Text-Chat und Self-Hosting.",
"ALT_TWOSEVEN_INTRO": "Beide Tools unterstützen synchronisiertes Schauen; unterstützte Dienste und Voraussetzungen können sich jedoch ändern. TwoSeven ist eine kontobasierte Plattform mit integrierten Kommunikationsfunktionen. KoalaSync ist eine kontofreie Erweiterung mit verschlüsseltem Live-Text-Chat und ohne eingebaute Anrufe.",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_TITLE": "Wann TwoSeven die bessere Wahl ist",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "TwoSeven ist die richtige Wahl, wenn du eine <strong>All-in-One-Lösung mit integrierter Webcam und Audio-Chat</strong> direkt im Browser suchst. Wenn du deine Freunde beim Schauen unbedingt sehen möchtest, ohne dafür nebenbei Discord oder Teamspeak laufen zu lassen, bietet TwoSeven diese Integration nativ. Zudem läuft der Dienst für YouTube und einige freie Web-Videoplayer ohne zusätzliche Accounts oder Erweiterungen. Er eignet sich gut für kleine Runden von 2 bis 3 Personen, die sich beim Schauen auch visuell austauschen möchten.",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "TwoSeven kann besser passen, wenn dir integrierte Webcam- und Audiofunktionen wichtig sind. Prüfe vor der Auswahl die aktuellen Browser-, Konto-, Tarif- und Dienstvoraussetzungen von TwoSeven.",
"ALT_TWOSEVEN_WHEN_KOALA_TITLE": "Wann KoalaSync besser passt",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync glänzt durch seine <strong>vollkommene Gebührenfreiheit, Privatsphäre und Einfachheit</strong>:<br><br>• <strong>Keine Bezahlschranken (Paywalls)</strong>: Um bei TwoSeven Premium-Dienste wie Disney+ oder Hulu synchronisiert zu schauen, muss mindestens ein Teilnehmer im Raum TwoSeven kostenpflichtig auf Patreon unterstützen (ab $3/Monat). KoalaSync synchronisiert Netflix, Prime Video, Disney+ und selbstgehostete Server (Jellyfin, Plex, Emby) zu 100 % kostenlos und ohne Einschränkungen.<br>• <strong>Absoluter Datenschutz (Keine Tracker/Werbung)</strong>: TwoSeven ist werbefinanziert und nutzt zahlreiche Tracker von Google, Facebook und Co. KoalaSync hat keine Werbung, keine Tracker und verbindet sich ausschließlich mit dem RAM-basierten Relay-Server.<br>• <strong>Keine Registrierung erforderlich</strong>: Bei TwoSeven müssen alle Teilnehmer ein Konto erstellen und eingeloggt sein. KoalaSync kommt komplett ohne Accounts aus. Einfach installieren, Raumcode eingeben und loslegen.<br>• <strong>Schlank und ressourcenschonend</strong>: Die integrierten Webcam- und Audio-Feeds von TwoSeven belasten die CPU und Internetleitung stark. Da KoalaSync die Kommunikation externen Tools wie Discord überlässt, läuft es extrem flüssig und schont den Laptop-Akku.",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync passt, wenn du kontofreie Open-Source-Synchronisierung, Ende-zu-Ende-verschlüsselten Live-Text-Chat und einen optional selbst gehosteten Relay möchtest. Sprach- und Webcam-Anrufe bleiben in einer separaten Anwendung.",
"ALT_TWOSEVEN_MAIN_DIFF_TITLE": "Der wichtigste Unterschied",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven ist eine <strong>kommerzielle Plattform</strong> mit Kontozwang, Werbung, P2P-Webcam-Chat und Patreon-Paywalls für bestimmte Streaming-Dienste. KoalaSync ist ein <strong>kostenloses Open-Source-Utility</strong>, das sich ohne Registrierung, Tracker oder zusätzliche Kosten einfach in deine Browser-Tabs einklinkt, um das ungestörte Schauen im Original-Player zu ermöglichen.",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven verbindet synchronisiertes Schauen mit einer kontobasierten Plattform und integrierten Webcam- und Audiofunktionen. KoalaSync ist eine kontofreie Browser-Erweiterung mit verschlüsseltem Live-Text-Chat; Sprach- und Videoanrufe bleiben getrennt.",
"ALT_TWOSEVEN_LIMITS_TITLE": "Ehrliche Grenzen",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync bietet <strong>keinen eigenen Webcam- oder Audio-Chat</strong> und setzt voraus, dass ihr bereits über Discord oder ein ähnliches Tool telefoniert. Zudem hat KoalaSync keine eingebaute Chatbox auf der Webseite und ist derzeit nur für Desktop-Browser verfügbar.",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync bietet <strong>keinen eingebauten Webcam- oder Audio-Chat</strong>. Wenn ihr euch sehen oder miteinander sprechen wollt, braucht ihr weiterhin Discord oder ein ähnliches Tool. KoalaSync besitzt jedoch ein frei verschiebbares und links oder rechts andockbares, Ende-zu-Ende-verschlüsseltes Live-Text-Chat-Overlay und ist derzeit nur für Desktop-Browser verfügbar.",
"ALT_TWOSEVEN_SUMMARY_TITLE": "Kurz gesagt",
"ALT_TWOSEVEN_SUMMARY_BODY": "Für gemütliche Zweier- oder Dreier-Runden, bei denen man sich unbedingt direkt im Browserfenster über Webcams sehen möchte und bereit ist, für Disney+ ein Patreon-Abo abzuschließen, ist TwoSeven eine nette Option. Für werbefreie, komplett kostenlose und private Filmabende in nativer 4K-Qualität (egal ob Netflix, Prime Video, Disney+ oder Jellyfin) ohne Kontozwang ist KoalaSync die sauberere und sicherere Wahl.",
"ALT_TWOSEVEN_SUMMARY_BODY": "Wähle TwoSeven, wenn dir integrierte Webcam- und Audiofunktionen am wichtigsten sind. Wähle KoalaSync für kontofreie Open-Source-Synchronisierung, verschlüsselten Live-Text-Chat und optionales Self-Hosting.",
"ALT_TRY_KOALASYNC": "KoalaSync ausprobieren",
"MOCK_HOST_CONTROL_LABEL": "Host-Steuerung",
"MOCK_HOST_CONTROL_TOGGLE": "Nur ich darf die Wiedergabe steuern",
@@ -287,5 +288,9 @@
"SELF_SHOW_SETUP": "Setup-Anleitung anzeigen",
"SELF_HIDE_SETUP": "Setup-Anleitung ausblenden",
"HERO_TRUST": "Kostenlos · Open Source · Kein Account nötig",
"COMP_MORE_GUIDES": "Ausführlicher Vergleich und weitere Alternativen"
"COMP_MORE_GUIDES": "Ausführlicher Vergleich und weitere Alternativen",
"NAV_HOME": "Startseite",
"ARIA_SELECT_LANGUAGE": "Sprache auswählen",
"ARIA_MENU": "Menü",
"FOOTER_GUIDES": "Anleitungen & Vergleiche"
}
+50 -45
View File
@@ -16,8 +16,9 @@
"NAV_HOW_IT_WORKS": "How it works",
"SUPPORT_BANNER_TEXT": "KoalaSync cannot access your video site?",
"SUPPORT_BANNER_CTA": "Fix website access",
"SUPPORT_BANNER_DISMISS": "Dismiss notification",
"HERO_TITLE": "Turn any video into a <span class='hero-highlight'>watch party</span>.",
"HERO_SUBTITLE": "Your remote movie night without lags. No account, no tracking, no ads. Just share a link and press play together.",
"HERO_SUBTITLE": "Watch videos together remotely. No account, tracking, or ads. Share a link and start together.",
"HERO_MASCOT_ALT": "A cute koala standing and looking down at the download buttons",
"DEMO_SYNC": "In sync",
"DEMO_HINT": "Live demo: press play in either tab and KoalaSync keeps both in sync.",
@@ -41,7 +42,7 @@
"USE_CASE_1_DESC": "Sync your movies in real time and talk via Discord, Zoom, or your favorite voice call application. It feels just like sitting in the same room.",
"USE_CASE_2_ALT": "A koala professor presenting on a screen to two remote student koalas",
"USE_CASE_2_TITLE": "Remote Learning",
"USE_CASE_2_DESC": "Analyze online tutorials, lectures, or developer training streams together with classmates or colleagues. Pause and discuss complex steps in perfect harmony.",
"USE_CASE_2_DESC": "Review tutorials, lectures, or training videos together. Pause and discuss complex steps as a group.",
"USE_CASE_3_ALT": "A cute koala couple watching together remotely; one is sitting at a PC and the other is in bed with a laptop, with flying hearts between them",
"USE_CASE_3_TITLE": "Long-Distance Relationships",
"USE_CASE_3_DESC": "Bridge the distance and enjoy date nights. Experience every plot twist, laugh at the same jokes, and share emotional moments at the exact same millisecond.",
@@ -114,8 +115,8 @@
"STEP_2_ILLUS_CREATE": "+ Create New Room",
"STEP_2_ILLUS_MANUAL": "Manual Connect / Advanced",
"STEP_2_ILLUS_COPIED": "Invite link copied!",
"STEP_3_TITLE": "Enjoy Perfect Sync",
"STEP_3_DESC": "Press play. Whether someone pauses, seeks, or plays, everyone's playback stays perfectly in sync without any lag.",
"STEP_3_TITLE": "Watch Together",
"STEP_3_DESC": "Press play. Playback changes are shared with everyone in the room; network and player conditions can cause a brief delay.",
"STEP_3_ILLUS_IN_SYNC": "IN SYNC",
"SELF_TITLE": "For Self-Hosters",
"SELF_SUBTITLE": "Don't trust our server? Maintain full data sovereignty. Deploy your own private relay server in minutes.",
@@ -126,33 +127,33 @@
"BOTTOM_SUBTITLE": "KoalaSync is 100% open source, ad-free and tracking-free. Audit our codebase on GitHub or install the browser extension directly.",
"BOTTOM_MASCOT_ALT": "A cute koala holding a GitHub repository page next to the GitHub mascot Octocat",
"FAQ_TITLE": "Frequently Asked Questions",
"FAQ_SUBTITLE": "Everything you need to know about watching videos together in perfect sync.",
"FAQ_SUBTITLE": "Answers about supported sites, synchronization, privacy, chat and audio processing.",
"FAQ_Q1": "Can I watch Netflix, Emby or Jellyfin together in sync with friends?",
"FAQ_A1": "Yes! KoalaSync works on Netflix, YouTube, Twitch and any website with an HTML5 video player, including Emby, Jellyfin and self-hosted media servers. Install the extension, create a room, share the invite link, and everyone watches in perfect sync.",
"FAQ_A1": "KoalaSync works with many supported websites that use a standard browser video player, including Netflix, YouTube, Twitch, Emby, Jellyfin and self-hosted media servers. Compatibility depends on the website, browser and player and can change when a site updates.",
"FAQ_Q_VIDEO_ACCESS": "Does KoalaSync stream the video to my friends?",
"FAQ_A_VIDEO_ACCESS": "No. KoalaSync does not stream, host, share, or bypass access to video content. Everyone watches the video locally in their own browser and needs their own access to the website or streaming service, such as their own Netflix account. KoalaSync only synchronizes playback actions like play, pause, and seeking.",
"FAQ_Q2": "Is KoalaSync a free alternative to Teleparty?",
"FAQ_A2": "100% free and open source under the MIT License. No premium tiers, no locked features, no ads, everything is included. Unlike Teleparty, KoalaSync's relay server is RAM-only with zero data collection or user accounts required.",
"FAQ_A2": "Yes. KoalaSync is free and open source under the MIT License, with no premium tier, locked features, ads or required user account.",
"FAQ_Q3": "Does KoalaSync work with Emby, Jellyfin and self-hosted media servers?",
"FAQ_A3": "Yes. KoalaSync detects any standard HTML5 video element, which means it works perfectly with Emby, Jellyfin, Plex, and any custom self-hosted streaming setup. You can also self-host the KoalaSync relay server via Docker for complete data sovereignty.",
"FAQ_A3": "Yes. KoalaSync supports many standard browser video players used by Emby, Jellyfin, Plex and custom self-hosted streaming setups. Compatibility depends on the player. You can also self-host the KoalaSync relay server with Docker.",
"FAQ_Q4": "Which browsers and platforms support KoalaSync?",
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave, and all Chromium-based browsers. Install it directly from the Chrome Web Store or Firefox Add-ons, no manual configuration needed.",
"FAQ_Q5": "Do all my friends need the extension installed?",
"FAQ_A5": "Yes, each participant needs KoalaSync installed, but there's no sign-up or account creation. Simply click the invitation link, install the extension if you haven't already, and you'll join the room automatically in seconds.",
"FAQ_Q6": "Is my data safe? What if I don't trust the official server?",
"FAQ_A6": "Completely safe. KoalaSync's relay server is RAM-only, all session data is permanently deleted the moment you leave the room. No database, no logging, no analytics, no accounts. And if you prefer full control: you can self-host your own private relay server via Docker in minutes. The entire codebase is open source and auditable on GitHub.",
"FAQ_A6": "The official KoalaSync relay does not store room, sync, watch, or chat history in a database. Active room data exists temporarily in memory and is removed when the room becomes empty or expires after inactivity. Technical connection logs may be created for security and abuse prevention; their retention depends on the server configuration. Chat messages are end-to-end encrypted and are not stored as server-side history. You can also self-host the relay with Docker.",
"FAQ_Q7": "Can I fix quiet dialogue and loud explosions when watching videos in the browser?",
"FAQ_A7": "Yes. KoalaSync 2.2.0 includes a built-in audio compressor that reduces the gap between quiet and loud parts of your video. It runs locally via the Web Audio API, no audio data ever leaves your computer. Open the popup, create a room, go to Settings → Audio Processing, toggle the compressor on, and pick a preset. Works on Netflix, Prime Video, Disney+, YouTube, and any HTML5 video site.",
"FAQ_A7": "Yes. KoalaSync includes an audio compressor that reduces the difference between quiet and loud parts of a video. It runs locally through the Web Audio API, so audio data is not sent to the relay. Open the popup, create or join a room, then enable it under Settings → Audio Processing.",
"HOWTO_TITLE": "How to synchronize videos with friends using KoalaSync",
"HOWTO_DESC": "Watch any video in perfect sync with friends across YouTube, Netflix, Twitch and more. No sign-up, no tracking.",
"HOWTO_DESC": "Watch supported videos together on YouTube, Netflix, Twitch and more. No sign-up or tracking.",
"HOWTO_STEP_1_NAME": "Install the browser extension",
"HOWTO_STEP_1_TEXT": "Add KoalaSync to your browser from Chrome Web Store or Firefox Add-ons. It works on any website with a video element.",
"HOWTO_STEP_1_TEXT": "Add KoalaSync from the Chrome Web Store or Firefox Add-ons. Open a supported page and select a video detected by the extension.",
"HOWTO_STEP_2_NAME": "Create a room",
"HOWTO_STEP_2_TEXT": "Open the extension popup and click Create New Room. The invite link is automatically copied to your clipboard.",
"HOWTO_STEP_3_NAME": "Share and sync",
"HOWTO_STEP_3_TEXT": "Send the invite link to your friends. Pick a video tab and enjoy synchronized playback in real time.",
"FOOTER_MIT": "Open source under the MIT License.",
"FOOTER_RAM": "No relay logs. No tracking. Website access logs are deleted after 7 days.",
"FOOTER_RAM": "No chat or watch history. No tracking. Website access logs are deleted after 7 days.",
"FOOTER_LEGAL": "Legal Notice",
"FOOTER_PRIVACY": "Privacy Policy",
"MOCK_01": "Active Room",
@@ -200,17 +201,17 @@
"ALT_INDEX_FOOTER_LINK": "Guides & comparisons",
"ALT_TELEPARTY_TITLE": "KoalaSync vs Teleparty: which watch party tool should you use?",
"ALT_TELEPARTY_META_DESC": "A practical comparison of KoalaSync and Teleparty for watch parties, built-in chat, mobile support, browser video sync, self-hosted media servers and open-source use.",
"ALT_TELEPARTY_INTRO": "KoalaSync and Teleparty both help with the same annoying problem: watching something together without doing the whole \"3, 2, 1, play\" routine. They just take a different approach.<br><br>Teleparty makes the most sense if you want a built-in chat or if you want to watch on mobile. KoalaSync is more for desktop browser watch parties where you are already talking on Discord, Teamspeak or another voice call and just want playback to stay in sync.<br><br>The main reason I built KoalaSync was flexibility. It is meant to work with almost any website that uses a normal browser video player, not just a fixed list of platforms. That includes self-hosted setups like Emby, Jellyfin and Plex, local/private domains, and a lot of normal video pages in the browser.",
"ALT_TELEPARTY_INTRO": "KoalaSync and Teleparty both help with the same annoying problem: watching something together without doing the whole \"3, 2, 1, play\" routine. Both include text chat, but they take a different approach.<br><br>Teleparty makes the most sense if you want its more integrated watch-party experience or want to watch on mobile. KoalaSync is designed for desktop browser watch parties across a wider range of normal browser video players. Its built-in text chat is end-to-end encrypted and live-only, while voice or video calls remain separate.<br><br>The main reason I built KoalaSync was flexibility. It is meant to work with almost any website that uses a normal browser video player, not just a fixed list of platforms. That includes self-hosted setups like Emby, Jellyfin and Plex, local/private domains, and a lot of normal video pages in the browser.",
"ALT_TELEPARTY_WHEN_TELE_TITLE": "When Teleparty may still make sense",
"ALT_TELEPARTY_WHEN_TELE_BODY": "Teleparty can still be the better choice if you want the chat to be part of the watch party tool itself. KoalaSync does not currently have a built-in chat. It assumes you are already talking somewhere else, like Discord, Teamspeak, WhatsApp, Signal or a normal call.<br><br>Mobile is the other big reason. Teleparty has an app, while KoalaSync currently only works on desktop browsers. If you want to sync Netflix on a phone or tablet, KoalaSync is not the right tool for that right now.<br><br>So if chat and mobile support are the things you care about most, Teleparty may simply fit your setup better.",
"ALT_TELEPARTY_WHEN_TELE_BODY": "Teleparty can still be the better choice if you prefer its more integrated watch-party experience or need mobile support. Both tools include text chat, so chat alone is no longer the deciding difference. KoalaSync's chat is end-to-end encrypted and live-only; voice and video calls still use a separate app.<br><br>Mobile is the other big reason. Teleparty has an app, while KoalaSync currently only works on desktop browsers. If you want to sync Netflix on a phone or tablet, KoalaSync is not the right tool for that right now.<br><br>So if mobile support and a more integrated platform are the things you care about most, Teleparty may simply fit your setup better.",
"ALT_TELEPARTY_WHEN_KOALA_TITLE": "When KoalaSync may fit better",
"ALT_TELEPARTY_WHEN_KOALA_BODY": "KoalaSync fits better if you want a flexible desktop browser sync tool instead of something tied to a short platform list.<br><br>It works with almost any website that uses a normal browser video player. That is especially useful for Emby, Jellyfin, Plex, private media servers, local domains, or smaller video sites that classic watch party tools usually ignore.<br><br>It is also free, open source, does not require accounts, and can be self-hosted if you want to run your own relay. I built it because I wanted something privacy-friendly that worked with my own Emby setup, not another closed platform just for a few big streaming sites.",
"ALT_TELEPARTY_MAIN_DIFF_TITLE": "The main difference",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty is more of a watch party product with chat and mobile support. KoalaSync is more of a flexible sync layer for browser video.<br><br>That sounds like a small difference, but it changes the use case a lot. If you need a full watch party app with chat inside it, Teleparty makes sense. If you already have voice chat and just want the video controls to stay synced across browsers, KoalaSync is the simpler fit.",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty is more of an integrated watch-party product with mobile support. KoalaSync is a flexible desktop-browser sync layer with end-to-end encrypted live text chat.<br><br>That sounds like a small difference, but it changes the use case a lot. If you need a mobile watch-party app, Teleparty makes sense. If you want broad browser-player support, including self-hosted media, without giving the relay readable chat messages, KoalaSync is the simpler fit.",
"ALT_TELEPARTY_LIMITS_TITLE": "Honest limitations",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync is not magic and it is not trying to hide its limits. It currently works only on desktop browsers. It has no built-in chat and no mobile app.<br><br>Compatibility also depends on the browser and the video player. KoalaSync should work with almost any normal browser video player, but websites can change their players, DRM behavior or browser restrictions at any time.<br><br>KoalaSync is not affiliated with Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex or any other brand mentioned here. The names are only used to explain typical use cases.",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync is not magic and it is not trying to hide its limits. It currently works only on desktop browsers. It includes end-to-end encrypted text chat, but no voice, video or webcam chat, and it has no mobile app.<br><br>Compatibility also depends on the browser and the video player. KoalaSync should work with almost any normal browser video player, but websites can change their players, DRM behavior or browser restrictions at any time.<br><br>KoalaSync is not affiliated with Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex or any other brand mentioned here. The names are only used to explain typical use cases.",
"ALT_TELEPARTY_SUMMARY_TITLE": "Summary",
"ALT_TELEPARTY_SUMMARY_BODY": "If you want built-in chat or mobile support, Teleparty may be the better fit. If you want free, open-source desktop browser sync that works with almost any normal video player, including self-hosted media servers like Emby, Jellyfin and Plex, KoalaSync is probably the better match.",
"ALT_TELEPARTY_SUMMARY_BODY": "Both tools include text chat. If you need mobile support or prefer Teleparty's more integrated watch-party experience, Teleparty may be the better fit. If you want free, open-source desktop browser sync with end-to-end encrypted live chat that works with almost any normal video player, including self-hosted media servers like Emby, Jellyfin and Plex, KoalaSync is probably the better match.",
"ALT_SCREEN_TITLE": "KoalaSync vs screen sharing: when is video sync better?",
"ALT_SCREEN_META_DESC": "A practical comparison of KoalaSync and screen sharing for remote movie nights, playback control, video quality, privacy, subtitles and browser-based watching.",
"ALT_SCREEN_INTRO": "Screen sharing works, and sometimes it is honestly enough. One person shares their screen, everyone else watches, and you are done.<br><br>But for longer movie nights or watching episodes together, it can get annoying. The video quality depends on the sharers upload, CPU, browser, and whatever compression Discord, Teamspeak, Zoom or another call app applies. Also, only one person really controls the playback.",
@@ -221,61 +222,61 @@
"ALT_SCREEN_WHY_CLEANER_TITLE": "Why KoalaSync can feel cleaner",
"ALT_SCREEN_WHY_CLEANER_BODY": "KoalaSync does not stream your screen. Everyone watches the video locally in their own browser, and KoalaSync only keeps playback actions like play, pause and seeking in sync.<br><br>That means everyone can use fullscreen on their own device, use their own subtitle or audio settings if the player supports it, and keep the voice chat separate in Discord, Teamspeak or another call.<br><br>For movie nights, that can feel a lot cleaner than one person broadcasting their screen. It is especially useful for browser-based setups like Emby, Jellyfin, Plex, local files opened in the browser, private domains, or other websites with normal browser video players.",
"ALT_SCREEN_LIMITS_TITLE": "Honest limitations",
"ALT_SCREEN_LIMITS_BODY": "KoalaSync is not a replacement for a voice call. If you want to talk while watching, you still use Discord, Teamspeak, Signal, WhatsApp or whatever you normally use.<br><br>Everyone also needs access to the video or page on their own device. KoalaSync does not bypass subscriptions, logins, DRM, regional restrictions or platform rules.<br><br>It is currently desktop-browser only, has no built-in chat or voice feature, and compatibility always depends on the browser and the video player.",
"ALT_SCREEN_LIMITS_BODY": "KoalaSync includes end-to-end encrypted text chat, but it is not a replacement for a voice or video call. If you want to talk while watching, you still use Discord, Teamspeak, Signal, WhatsApp or whatever you normally use.<br><br>Everyone also needs access to the video or page on their own device. KoalaSync does not bypass subscriptions, logins, DRM, regional restrictions or platform rules.<br><br>It is currently desktop-browser only, has no built-in voice, video or webcam chat, and compatibility always depends on the browser and the video player.",
"ALT_SCREEN_SUMMARY_TITLE": "Summary",
"ALT_SCREEN_SUMMARY_BODY": "Screen sharing is the simplest option when you just want something quick and do not care much about quality, privacy or individual control. KoalaSync is a better fit when everyone should watch locally in their own browser and the session should feel more like actually watching together.",
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: Which watch party solution fits better?",
"ALT_W2G_META_DESC": "An honest comparison between KoalaSync and Watch2Gether. Discover when KoalaSync's flexibility is unbeatable and why Watch2Gether is still the top choice for YouTube.",
"ALT_W2G_META_DESC": "A practical comparison of KoalaSync and Watch2Gether for shared rooms, playlists, browser-player synchronization and encrypted text chat.",
"ALT_W2G_INTRO": "KoalaSync and Watch2Gether both solve the same problem: watching videos in sync with friends online without manually coordinating. While Watch2Gether is one of the oldest and most popular hubs for shared movie nights, KoalaSync takes a completely different, lightweight approach. Which tool is right for you depends heavily on what and where you want to watch.",
"ALT_W2G_WHEN_W2G_TITLE": "When Watch2Gether is the better choice",
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether is unbeatable for <strong>YouTube watch parties</strong>. With its built-in playlist and search, anyone in the room can easily find, add, and play new videos. Everyone watches the next video together seamlessly.<br><br>With KoalaSync on YouTube, the site's default autoplay takes over, redirecting each user to a different recommended video. Our automatic next-episode feature (\"Episode Lobby\"), which works great on Netflix or Disney+, doesn't work on YouTube due to these dynamic recommendations. For pure YouTube nights, Watch2Gether is the better choice.",
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether may fit better for YouTube sessions where a shared room, search and playlist are central. Support and behavior depend on the current Watch2Gether service and content source.",
"ALT_W2G_WHEN_KOALA_TITLE": "When KoalaSync fits better",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync shines on <strong>real streaming services</strong> (Netflix, Prime Video, Disney+) and self-hosted media servers (Jellyfin, Plex, Emby). Unlike Watch2Gether, which often cannot play premium content at all, KoalaSync syncs the native player directly on the website.<br><br>It works on almost any site with a standard HTML5 video player. You don't need to set up a virtual room on a third-party site: just activate the extension, join a lobby, and start watching.",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync is a good fit when you want to synchronize the original player on a supported browser page, including self-hosted media, without moving playback into a separate watch-party site. Compatibility still depends on the website, player, and browser.",
"ALT_W2G_MAIN_DIFF_TITLE": "The main difference",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with built-in chat, playlists, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs.<br><br>Looking for an all-in-one package for YouTube with built-in chat? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether is a <strong>standalone website</strong> with text chat, playlists, search, and webcam support. KoalaSync is a <strong>minimalist browser extension</strong> that hooks directly into your normal browser tabs and also includes end-to-end encrypted live text chat.<br><br>Looking for an all-in-one package for YouTube with playlists, search, and webcam features? Use Watch2Gether. Want to watch movies on Netflix or your own server without distractions, in native quality (no server compression)? KoalaSync is the cleaner solution.",
"ALT_W2G_LIMITS_TITLE": "Honest limitations",
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>Additionally, there is <strong>no built-in chat</strong>, and KoalaSync currently only works on desktop browsers. It is designed under the assumption that you are already talking via Discord, Teamspeak, or other voice tools anyway.",
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>does not have built-in playlists or video search</strong>. On YouTube, this means that autoplaying the next video won't stay in sync.<br><br>KoalaSync does include end-to-end encrypted live text chat, but no built-in webcam, voice or video chat, and it currently only works on desktop browsers. If you want to talk or see each other, you still need Discord, Teamspeak, or another call tool.",
"ALT_W2G_SUMMARY_TITLE": "In short",
"ALT_W2G_SUMMARY_BODY": "For spontaneous YouTube sessions with playlists and built-in chat, Watch2Gether is the best choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
"ALT_W2G_SUMMARY_BODY": "Both tools include text chat. For spontaneous YouTube sessions with playlists, search, and webcam features, Watch2Gether is the better choice. For premium streaming platforms (like Netflix, Prime Video, Disney+), self-hosted servers (Jellyfin, Plex, Emby), end-to-end encrypted live chat, and maximum browser flexibility, KoalaSync is the more suitable and lightweight tool.",
"ALT_SCENER_TITLE": "KoalaSync vs Scener: Which watch party platform fits better?",
"ALT_SCENER_META_DESC": "An honest comparison of KoalaSync and Scener. Discover when Scener's webcam chat wins and why KoalaSync is best for distraction-free movie nights.",
"ALT_SCENER_INTRO": "KoalaSync and Scener approach watch parties from two completely different angles. Scener is a virtual movie theater with built-in video chat and interactive rooms. KoalaSync, on the other hand, is a minimalist, privacy-friendly browser extension that simply synchronizes playback controls. Which tool fits you best depends mostly on how important built-in webcam and chat features are to you.",
"ALT_SCENER_INTRO": "KoalaSync and Scener approach watch parties from two completely different angles. Scener is a virtual movie theater with built-in video chat and interactive rooms. KoalaSync, on the other hand, is a minimalist, privacy-friendly browser extension that synchronizes playback controls and includes end-to-end encrypted live text chat. Which tool fits you best depends mostly on whether you need built-in webcam, voice, video, and social features or prefer a lightweight sync tool.",
"ALT_SCENER_WHEN_SCENER_TITLE": "When Scener is the better choice",
"ALT_SCENER_WHEN_SCENER_BODY": "Scener is a solid choice if you want an <strong>all-in-one social platform</strong> with built-in video and audio chat directly in your browser. Scener creates a virtual room where the player and your friends' webcams are displayed side-by-side. This is ideal for large, public watch parties or virtual streaming events where the social aspect is front and center. However, keep in mind that the free basic version is ad-supported, and Scener locks many features (such as ad-free viewing and unlimited video chat) behind a monthly subscription ($5.79/month or $39.99/year).",
"ALT_SCENER_WHEN_SCENER_BODY": "Scener is the better fit if you want an integrated social experience with text chat and, for Premium members, video and audio chat. On computers it supports a defined set of streaming services through its Chrome extension.",
"ALT_SCENER_WHEN_KOALA_TITLE": "When KoalaSync fits better",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync shines when it comes to <strong>video quality, privacy, and simplicity</strong>:<br><br>• <strong>No accounts</strong>: You don't need to register, log in, or manage profiles. Just activate the extension, share a room code, and start watching.<br>• <strong>True native quality (4K/HDR)</strong>: Scener streams compressed video from a virtual cloud server, which can lead to quality loss and lagging. KoalaSync synchronizes the native player directly on your computer, meaning you watch movies in the full resolution of your streaming subscription.<br>• <strong>Low bandwidth usage</strong>: Because only control data (play, pause, timestamps) is transmitted, KoalaSync uses virtually no extra bandwidth. Scener streams the entire video and camera feeds over cloud servers, which can heavily strain slower internet connections and older laptops.<br>• <strong>100% free and ad-free</strong>: No monthly subscription fees, no advertisements, and no trackers.<br>• <strong>Local video files</strong>: Since KoalaSync runs locally, you can even watch your own video files (like MP4) in sync with friends. Just drag and drop the file into a browser tab Scener cannot do this because its player runs in the cloud.",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync fits if you prefer account-free, open-source synchronization in the original browser player, end-to-end encrypted live text chat, and an optional self-hosted relay. It does not include voice, video, or webcam chat.",
"ALT_SCENER_MAIN_DIFF_TITLE": "The main difference",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener is a <strong>commercial social network</strong> for watch parties that is powered by virtual cloud browsers and funded through ads and monthly subscriptions. KoalaSync is a <strong>privacy-first, free synchronization utility</strong> that hooks directly into your browser tabs. It leaves voice/video chat to applications like Discord or Teamspeak, focusing entirely on keeping your playback fluid, private, and in its native original quality.",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener focuses on integrated rooms and social features. KoalaSync focuses on lightweight synchronization in the original browser player and end-to-end encrypted live text chat.",
"ALT_SCENER_LIMITS_TITLE": "Honest limitations",
"ALT_SCENER_LIMITS_BODY": "KoalaSync offers <strong>no built-in chat or webcam support</strong>. It is purely a synchronization tool and requires a separate voice call tool of your choice. Additionally, KoalaSync is currently desktop-only, and local video file compatibility depends entirely on whether your browser natively supports playing the video format and audio codecs.",
"ALT_SCENER_LIMITS_BODY": "KoalaSync offers <strong>end-to-end encrypted live text chat, but no webcam, voice, or video chat</strong>. Talking or seeing each other therefore requires a separate call tool of your choice. Additionally, KoalaSync is currently desktop-only, and local video file compatibility depends entirely on whether your browser natively supports playing the video format and audio codecs.",
"ALT_SCENER_SUMMARY_TITLE": "In short",
"ALT_SCENER_SUMMARY_BODY": "For large watch parties with built-in webcam chat and social features, Scener is the right choice, provided you don't mind ads and premium paywalls. If you want a completely free, ad-free, and distraction-free experience in the native player (including Netflix, Prime Video, Disney+, or local browser files) and you are already in a voice call, KoalaSync is the cleaner and more performant solution.",
"ALT_SCENER_SUMMARY_BODY": "Choose Scener for integrated social, video, and audio features on its supported services. Choose KoalaSync for account-free, open-source browser synchronization, encrypted live text chat, and optional self-hosting.",
"ALT_KOSMI_TITLE": "KoalaSync vs Kosmi: Which Netflix party alternative fits better?",
"ALT_KOSMI_META_DESC": "An honest comparison of KoalaSync and Kosmi. Discover why Kosmi's no-extension setup is convenient and when KoalaSync's native video quality wins.",
"ALT_KOSMI_INTRO": "Both KoalaSync and Kosmi offer great ways to watch Netflix, YouTube, and other platforms in sync with friends. However, their underlying technologies are fundamentally different. Kosmi uses virtual cloud browsers that run directly in your web browser without installing extensions. KoalaSync is a lightweight extension that synchronizes your local video player. Which approach fits you best depends on your priorities regarding video quality, convenience, and privacy.",
"ALT_KOSMI_INTRO": "KoalaSync and Kosmi both support watching together, but they are separate products with different supported services and social features. KoalaSync synchronizes a supported video player in your desktop browser. Check each service for its current device and platform support.",
"ALT_KOSMI_WHEN_KOSMI_TITLE": "When Kosmi is the better choice",
"ALT_KOSMI_WHEN_KOSMI_BODY": "Kosmi is unbeatable if you want to <strong>avoid installing anything</strong> or want to watch on devices like tablets and smartphones. Because Kosmi runs virtual browsers in the cloud, all it takes to join is a simple web link. Furthermore, Kosmi is an all-in-one hangout platform: in addition to synchronized streaming, you can play multiplayer games (like retro NES/SNES classics or poker) and use the built-in webcam, voice, and text chat. If you want a social game night and watch party without setup, Kosmi is a fantastic solution.",
"ALT_KOSMI_WHEN_KOSMI_BODY": "Kosmi may fit better if you want its browser-based social rooms, games, and built-in communication features. Availability can vary by device, content source, and current Kosmi plan.",
"ALT_KOSMI_WHEN_KOALA_TITLE": "When KoalaSync fits better",
"ALT_KOSMI_WHEN_KOALA_BODY": "KoalaSync shines when it comes to <strong>native playback quality, security, and performance</strong>:<br><br>• <strong>No security risks</strong>: With Kosmi, you have to log into your Netflix or Prime account on a remote virtual computer in the cloud. With KoalaSync, you log in securely on the official streaming website in your own browser, just like you normally do. Your passwords never leave your machine.<br>• <strong>Full video quality (4K/HDR)</strong>: Kosmi streams the virtual browser's screen as a compressed video feed, which can result in pixelation and buffering. KoalaSync syncs the native players locally: you enjoy movies in the full, crystal-clear resolution of your subscription.<br>• <strong>Lag-free & bandwidth-friendly</strong>: Since KoalaSync only sends control commands (play/pause/timestamps), it uses almost zero bandwidth. Kosmi constantly streams HD video feeds, which can overwhelm slower connections.<br>• <strong>Local video files</strong>: If you want to watch a private video, just drag and drop the MP4 file into a browser tab and sync it. Kosmi cannot access files on your local hard drive.",
"ALT_KOSMI_WHEN_KOALA_BODY": "KoalaSync fits better if you want to synchronize a supported player in your own desktop browser, use end-to-end encrypted live text chat, or self-host the relay. It does not include voice, video, webcam chat, or a mobile app.",
"ALT_KOSMI_MAIN_DIFF_TITLE": "The main difference",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmi is a <strong>virtual cloud hangout</strong> that streams a remote browser's screen so no one has to install an extension. KoalaSync is a <strong>local browser sync layer</strong> that hooks directly into the native player on the official streaming website. While Kosmi is more versatile for mini-games and device compatibility, KoalaSync offers a more secure, fluid, and sharp streaming experience in the original player.",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmi focuses on an all-in-one social room. KoalaSync is a desktop browser extension that synchronizes supported players and provides encrypted live text chat.",
"ALT_KOSMI_LIMITS_TITLE": "Honest limitations",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync <strong>requires installing a browser extension</strong> and currently only runs on desktop PCs. It <strong>has no built-in chat or webcams</strong>, as it is designed under the assumption that you are already talking via Discord or other voice tools anyway. It also does not offer built-in games.",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync <strong>requires installing a browser extension</strong> and currently only runs on desktop PCs. It includes <strong>end-to-end encrypted live text chat, but no webcam, voice, or video chat</strong>. It also does not offer built-in games.",
"ALT_KOSMI_SUMMARY_TITLE": "In short",
"ALT_KOSMI_SUMMARY_BODY": "For casual game nights with retro classics and easy access on mobile devices without any installation, Kosmi is ideal. For premium movie nights on Netflix, Prime Video, Disney+, or Plex in full 4K quality, with maximum security and minimal bandwidth impact, KoalaSync is the clear winner.",
"ALT_KOSMI_SUMMARY_BODY": "Choose Kosmi for its integrated social-room features. Choose KoalaSync for account-free, open-source desktop synchronization, encrypted live text chat, and optional relay self-hosting.",
"ALT_TWOSEVEN_TITLE": "KoalaSync vs TwoSeven: Which watch party solution fits better?",
"ALT_TWOSEVEN_META_DESC": "An honest comparison of KoalaSync and TwoSeven. Discover when TwoSeven's built-in webcam wins and why KoalaSync is best for Disney+ and Netflix.",
"ALT_TWOSEVEN_INTRO": "Both KoalaSync and TwoSeven use the same basic approach for watch parties: they synchronize the original video player directly in your browser, rather than streaming a compressed video feed from the cloud. This ensures top-tier video quality for both tools. However, while TwoSeven is a commercial portal with built-in webcam chat and subscription tiers, KoalaSync remains a minimalist, completely free, and privacy-friendly extension. Find out which service fits you best here.",
"ALT_TWOSEVEN_META_DESC": "A practical comparison of KoalaSync and TwoSeven, including account requirements, integrated communication, encrypted text chat and self-hosting.",
"ALT_TWOSEVEN_INTRO": "Both tools support synchronized viewing, but their current service support and requirements can change. TwoSeven is an account-based platform with integrated communication features. KoalaSync is an account-free extension with encrypted live text chat and no built-in calls.",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_TITLE": "When TwoSeven is the better choice",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "TwoSeven is the right choice if you want an <strong>all-in-one solution with integrated webcam and audio chat</strong> directly in your browser. If you want to see your friends while watching without having to run Discord or Teamspeak in the background, TwoSeven offers this integration natively. Additionally, it works for YouTube and a few open web video players without extensions. It is well-suited for small groups of 2 to 3 people who want to share a visual connection while viewing.",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "TwoSeven may fit better if its integrated webcam and audio features are important to you. Check TwoSevens current browser, account, plan and supported-service requirements before choosing it.",
"ALT_TWOSEVEN_WHEN_KOALA_TITLE": "When KoalaSync fits better",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync shines with its <strong>complete lack of fees, privacy, and simplicity</strong>:<br><br>• <strong>No paywalls</strong>: To watch premium services like Disney+ or Hulu in sync on TwoSeven, at least one room member must support them on Patreon (starting at $3/month). KoalaSync synchronizes Netflix, Prime Video, Disney+, and self-hosted media servers (Jellyfin, Plex, Emby) 100% free with zero restrictions.<br>• <strong>Absolute privacy (No trackers/ads)</strong>: TwoSeven is ad-supported and uses multiple trackers from Google, Facebook, and others. KoalaSync has no ads, no trackers, and communicates exclusively with the RAM-only relay server.<br>• <strong>No registration required</strong>: TwoSeven requires all participants to create an account and log in. KoalaSync is completely account-free. Just install, enter a room code, and start watching.<br>• <strong>Lightweight and resource-friendly</strong>: TwoSeven's integrated webcam and audio feeds place a heavy load on your CPU and network. By leaving communication to tools like Discord, KoalaSync runs extremely smoothly and preserves battery life.",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync fits if you want account-free, open-source synchronization, end-to-end encrypted live text chat, and the option to self-host the relay. Voice and webcam calls remain in a separate application.",
"ALT_TWOSEVEN_MAIN_DIFF_TITLE": "The main difference",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven is a <strong>commercial platform</strong> with registration requirements, ads, peer-to-peer webcam chats, and Patreon paywalls for specific streaming services. KoalaSync is a <strong>free, open-source utility</strong> that hooks directly into your browser tabs without accounts, trackers, or extra costs, allowing for distraction-free watching in the native player.",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven combines synchronized viewing with an account-based platform and integrated webcam and audio features. KoalaSync is an account-free browser extension with encrypted live text chat; voice and video calls remain separate.",
"ALT_TWOSEVEN_LIMITS_TITLE": "Honest limitations",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync offers <strong>no built-in webcam or audio chat</strong> and assumes that you are already on a call via Discord or a similar tool. Additionally, KoalaSync has no built-in chat box overlay and is currently desktop-only.",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync offers <strong>no built-in webcam or audio chat</strong>, so seeing or talking to each other still requires Discord or a similar tool. It does include a floating and dockable end-to-end encrypted live text-chat overlay and is currently desktop-only.",
"ALT_TWOSEVEN_SUMMARY_TITLE": "In short",
"ALT_TWOSEVEN_SUMMARY_BODY": "For small groups of two or three people who want to see each other via webcams directly in the browser and don't mind supporting a Patreon for Disney+ support, TwoSeven is a nice option. For ad-free, completely free, and private movie nights in native 4K quality (whether Netflix, Prime Video, Disney+, or Jellyfin) without registrations, KoalaSync is the cleaner and safer choice.",
"ALT_TWOSEVEN_SUMMARY_BODY": "Choose TwoSeven if integrated webcam and audio features matter most. Choose KoalaSync for account-free, open-source synchronization, encrypted live text chat, and optional self-hosting.",
"ALT_TRY_KOALASYNC": "Try KoalaSync",
"MOCK_HOST_CONTROL_LABEL": "Host Control",
"MOCK_HOST_CONTROL_TOGGLE": "Only I can control playback",
@@ -287,5 +288,9 @@
"SELF_SHOW_SETUP": "Show setup guide",
"SELF_HIDE_SETUP": "Hide setup guide",
"HERO_TRUST": "Free · Open source · No account needed",
"COMP_MORE_GUIDES": "Full comparison and more alternatives"
"COMP_MORE_GUIDES": "Full comparison and more alternatives",
"NAV_HOME": "Home",
"ARIA_SELECT_LANGUAGE": "Select language",
"ARIA_MENU": "Menu",
"FOOTER_GUIDES": "Guides & comparisons"
}
+51 -46
View File
@@ -16,8 +16,9 @@
"NAV_HOW_IT_WORKS": "Cómo funciona",
"SUPPORT_BANNER_TEXT": "¿KoalaSync no puede acceder al sitio del vídeo?",
"SUPPORT_BANNER_CTA": "Reparar acceso al sitio",
"SUPPORT_BANNER_DISMISS": "Cerrar aviso",
"HERO_TITLE": "Convierte cualquier vídeo en una <span class='hero-highlight'>watch party</span>.",
"HERO_SUBTITLE": "Tu noche de cine a distancia, sin retrasos. Sin cuenta, sin rastreo, sin anuncios. Comparte un enlace y dale al play juntos.",
"HERO_SUBTITLE": "Ved vídeos juntos a distancia. Sin cuenta, seguimiento ni anuncios. Comparte un enlace y empezad juntos.",
"HERO_MASCOT_ALT": "Un lindo koala de pie mirando hacia abajo a los botones de descarga",
"DEMO_SYNC": "Sincronizado",
"DEMO_HINT": "Demo en vivo: pulsa play en cualquiera de las pestañas y KoalaSync mantiene ambas sincronizadas.",
@@ -41,7 +42,7 @@
"USE_CASE_1_DESC": "Sincroniza tus películas en tiempo real y habla por Discord, Zoom o tu aplicación de voz favorita. Es como estar en la misma habitación.",
"USE_CASE_2_ALT": "Un profesor koala presentando en una pantalla a dos estudiantes koala remotos",
"USE_CASE_2_TITLE": "Aprendizaje a distancia",
"USE_CASE_2_DESC": "Analiza tutoriales en línea, conferencias o directos de programación con compañeros o colegas. Pausa y discute pasos complejos en perfecta armonía.",
"USE_CASE_2_DESC": "Analizad juntos tutoriales, clases o vídeos de formación. Pausad y comentad los pasos complejos.",
"USE_CASE_3_ALT": "Una linda pareja de koalas viendo juntos de forma remota; uno está sentado frente a una PC y el otro en la cama con una computadora portátil, con corazones volando entre ellos",
"USE_CASE_3_TITLE": "Relaciones a larga distancia",
"USE_CASE_3_DESC": "Reduce la distancia y disfruta de noches de citas. Vive cada giro de la trama, ríete de los mismos chistes y comparte momentos emotivos en el mismo milisegundo exacto.",
@@ -114,8 +115,8 @@
"STEP_2_ILLUS_CREATE": "+ Crear nueva sala",
"STEP_2_ILLUS_MANUAL": "Conexión manual / Avanzado",
"STEP_2_ILLUS_COPIED": "¡Enlace de invitación copiado!",
"STEP_3_TITLE": "Disfrutar de la sincronización",
"STEP_3_DESC": "Pulsa reproducir. Cuando alguien pause, avance o reproduzca, el vídeo de todos se mantendrá en sincronía en tiempo real y sin retrasos.",
"STEP_3_TITLE": "Vedlo juntos",
"STEP_3_DESC": "Pulsa reproducir. Los cambios se comparten con todos los participantes; la red y el reproductor pueden causar un breve retraso.",
"STEP_3_ILLUS_IN_SYNC": "SINCRONIZADO",
"SELF_TITLE": "Para auto-alojadores",
"SELF_SUBTITLE": "¿No te fías de nuestro servidor? Mantén la soberanía total de tus datos. Despliega tu propio servidor de retransmisión en minutos.",
@@ -126,33 +127,33 @@
"BOTTOM_SUBTITLE": "KoalaSync es 100% de código abierto, sin anuncios y sin rastreadores. Audita nuestro código en GitHub o instala la extensión directamente.",
"BOTTOM_MASCOT_ALT": "Un lindo koala junto a Octocat, la mascota de GitHub",
"FAQ_TITLE": "Preguntas frecuentes",
"FAQ_SUBTITLE": "Todo lo que necesitas saber para ver vídeos en perfecta sincronía.",
"FAQ_SUBTITLE": "Respuestas sobre sitios compatibles, sincronización, privacidad, chat y procesamiento de audio.",
"FAQ_Q1": "¿Puedo ver Netflix, Emby o Jellyfin sincronizado con amigos?",
"FAQ_A1": "¡Sí! KoalaSync funciona en Netflix, YouTube, Twitch y cualquier web con reproductor HTML5, incluyendo Emby, Jellyfin y servidores multimedia propios. Instala la extensión, crea una sala, comparte el enlace y todos veréis lo mismo al mismo tiempo.",
"FAQ_A1": "KoalaSync funciona con muchos sitios compatibles que usan un reproductor de vídeo estándar del navegador, como Netflix, YouTube, Twitch, Emby, Jellyfin y servidores multimedia autohospedados. La compatibilidad depende del sitio, el navegador y el reproductor, y puede cambiar cuando se actualiza un sitio.",
"FAQ_Q_VIDEO_ACCESS": "¿KoalaSync transmite el video a mis amigos?",
"FAQ_A_VIDEO_ACCESS": "No. KoalaSync no transmite, aloja, comparte ni evita el acceso al contenido de video. Cada persona ve el video localmente en su propio navegador y necesita su propio acceso al sitio web o servicio de streaming, por ejemplo su propia cuenta de Netflix. KoalaSync solo sincroniza acciones de reproducción como reproducir, pausar y avanzar o retroceder.",
"FAQ_Q2": "¿Es KoalaSync una alternativa gratuita a Teleparty?",
"FAQ_A2": "Es 100% gratuito y de código abierto (Licencia MIT). Sin suscripciones, sin funciones bloqueadas y sin anuncios. A diferencia de Teleparty, el servidor de KoalaSync solo usa RAM: no hay bases de datos ni cuentas de usuario.",
"FAQ_A2": "Sí. KoalaSync es gratuito y de código abierto bajo la licencia MIT, sin plan prémium, funciones bloqueadas, anuncios ni cuentas obligatorias.",
"FAQ_Q3": "¿Funciona KoalaSync con Emby, Jellyfin y servidores propios?",
"FAQ_A3": "Sí. KoalaSync detecta cualquier elemento de video HTML5 estándar, por lo que es perfecto para Emby, Jellyfin, Plex y cualquier plataforma de streaming personalizada. También puedes alojar tu propio servidor con Docker.",
"FAQ_A3": "Sí. KoalaSync admite muchos reproductores de vídeo estándar usados por Emby, Jellyfin, Plex y sistemas de streaming autohospedados. La compatibilidad depende del reproductor. También puedes alojar el servidor relay de KoalaSync con Docker.",
"FAQ_Q4": "¿Qué navegadores y plataformas son compatibles?",
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave y todos los navegadores basados en Chromium. Instálalo directamente desde las tiendas oficiales sin configuraciones manuales.",
"FAQ_Q5": "¿Todos mis amigos necesitan tener la extensión instalada?",
"FAQ_A5": "Sí, cada participante necesita KoalaSync, pero no hace falta registrarse. Solo hay que hacer clic en el enlace, instalar la extensión si no se tiene y la unión a la sala es automática.",
"FAQ_Q6": "¿Están seguros mis datos? ¿Y si no confío en el servidor oficial?",
"FAQ_A6": "Totalmente seguros. El servidor de KoalaSync funciona solo en RAM: todos los datos de la sesión se borran permanentemente en cuanto sales de la sala. Sin registros ni analíticas. Si prefieres el control total, puedes auto-alojar tu propio servidor privado mediante Docker.",
"FAQ_A6": "El relay oficial de KoalaSync no guarda historiales de salas, sincronización, reproducción ni chat en una base de datos. Los datos de una sala activa solo permanecen temporalmente en memoria y se eliminan cuando la sala queda vacía o caduca por inactividad. Pueden generarse registros técnicos de conexión para seguridad y prevención de abusos; su conservación depende de la configuración del servidor. Los mensajes del chat están cifrados de extremo a extremo y no se guardan como historial en el servidor.",
"FAQ_Q7": "¿Puedo arreglar los diálogos bajos y las explosiones fuertes al ver vídeos?",
"FAQ_A7": "Sí. KoalaSync incluye un compresor de audio integrado que equilibra las partes bajas y altas del sonido. Funciona localmente mediante la Web Audio API: ningún dato de audio sale de tu equipo. Ve a Configuración → Procesamiento de audio y activa el compresor.",
"FAQ_A7": "Sí. KoalaSync incluye un compresor de audio que reduce la diferencia entre las partes suaves y fuertes del vídeo. Funciona localmente mediante Web Audio API, por lo que el audio no se envía al relay. Actívalo en Configuración → Procesamiento de audio.",
"HOWTO_TITLE": "Cómo sincronizar videos con amigos usando KoalaSync",
"HOWTO_DESC": "Mira cualquier video en sincronía con amigos en YouTube, Netflix, Twitch y más. Sin registros ni rastreo.",
"HOWTO_DESC": "Ved vídeos compatibles juntos en YouTube, Netflix, Twitch y más. Sin registro ni seguimiento.",
"HOWTO_STEP_1_NAME": "Instala la extensión",
"HOWTO_STEP_1_TEXT": "Añade KoalaSync a tu navegador desde la Chrome Web Store o Firefox Add-ons. Funciona en cualquier web con reproductor de vídeo.",
"HOWTO_STEP_1_TEXT": "Instala KoalaSync desde Chrome Web Store o Firefox Add-ons. Abre una página compatible y selecciona un vídeo detectado por la extensión.",
"HOWTO_STEP_2_NAME": "Crea una sala",
"HOWTO_STEP_2_TEXT": "Abre la extensión y haz clic en 'Crear nueva sala'. El enlace de invitación se copiará automáticamente.",
"HOWTO_STEP_3_NAME": "Comparte y sincroniza",
"HOWTO_STEP_3_TEXT": "Envía el enlace a tus amigos. Selecciona la pestaña del video y disfrutad de la reproducción en tiempo real.",
"FOOTER_MIT": "Código abierto bajo la Licencia MIT.",
"FOOTER_RAM": "No almacenamos datos. Retransmisión pura basada en RAM.",
"FOOTER_RAM": "Sin historial de chat ni reproducción. Sin seguimiento. Los registros de acceso web se eliminan después de 7 días.",
"FOOTER_LEGAL": "Aviso Legal",
"FOOTER_PRIVACY": "Política de Privacidad",
"MOCK_01": "Sala activa",
@@ -190,7 +191,7 @@
"FOOTER_LEGAL_LINK": "imprint",
"FOOTER_PRIVACY_LINK": "privacy",
"FOOTER_DISCLAIMER": "KoalaSync no está afiliado, respaldado ni asociado con Netflix, Disney+, Amazon, YouTube, Twitch ni ninguna otra plataforma de streaming. Todas las marcas comerciales pertenecen a sus respectivos dueños.",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "Apoyar KoalaSync",
"ALT_INDEX_TITLE": "KoalaSync - Alternativas y comparativas",
"ALT_INDEX_META_DESC": "Explora comparativas honestas y guías detalladas entre KoalaSync y otras extensiones de watch party y soluciones de streaming.",
"ALT_INDEX_HEADING": "Guías y comparativas",
@@ -200,17 +201,17 @@
"ALT_INDEX_FOOTER_LINK": "Guías y comparativas",
"ALT_TELEPARTY_TITLE": "KoalaSync frente a Teleparty: ¿qué herramienta de visionado conjunto deberías usar?",
"ALT_TELEPARTY_META_DESC": "Una comparativa práctica entre KoalaSync y Teleparty para ver contenido en grupo, analizando aspectos como el chat integrado, la compatibilidad móvil, la sincronización de vídeo en el navegador, los servidores multimedia autohospedados y el uso de código abierto.",
"ALT_TELEPARTY_INTRO": "Tanto KoalaSync como Teleparty ayudan a resolver el mismo problema molesto: ver algo juntos sin tener que recurrir a la típica rutina de «3, 2, 1, ¡reproducir!». Simplemente adoptan enfoques diferentes.<br><br>Teleparty es la opción más lógica si buscas un chat integrado o si quieres ver contenido desde un dispositivo móvil. KoalaSync está más orientado a sesiones de visionado en navegadores de escritorio, donde ya te comunicas a través de Discord, Teamspeak u otra aplicación de voz y solo necesitas que la reproducción se mantenga sincronizada.<br><br>La razón principal por la que creé KoalaSync fue la flexibilidad. Está diseñado para funcionar con casi cualquier sitio web que utilice un reproductor de vídeo estándar de navegador, no solo con una lista fija de plataformas. Esto incluye configuraciones autohospedadas como Emby, Jellyfin y Plex, dominios locales o privados y muchas páginas de vídeo convencionales.",
"ALT_TELEPARTY_INTRO": "Tanto KoalaSync como Teleparty ayudan a resolver el mismo problema molesto: ver algo juntos sin tener que recurrir a la típica rutina de «3, 2, 1, ¡reproducir!». Simplemente adoptan enfoques diferentes.<br><br>Teleparty tiene más sentido si deseas una experiencia de visualización en grupo más integrada o si quieres mirar en el móvil. KoalaSync está diseñado para grupos de visualización de navegadores de escritorio en una gama más amplia de reproductores de vídeo de navegador normales. Su chat de texto integrado está cifrado de extremo a extremo y es solo en vivo, mientras que las llamadas de voz o video permanecen separadas.<br><br>La razón principal por la que creé KoalaSync fue la flexibilidad. Está diseñado para funcionar con casi cualquier sitio web que utilice un reproductor de vídeo estándar de navegador, no solo con una lista fija de plataformas. Esto incluye configuraciones autohospedadas como Emby, Jellyfin y Plex, dominios locales o privados y muchas páginas de vídeo convencionales.",
"ALT_TELEPARTY_WHEN_TELE_TITLE": "Cuándo puede seguir siendo útil Teleparty",
"ALT_TELEPARTY_WHEN_TELE_BODY": "Teleparty puede seguir siendo la mejor opción si prefieres que el chat forme parte de la propia herramienta de visionado. Actualmente, KoalaSync no cuenta con chat integrado; parte de la premisa de que ya te estás comunicando por otro medio, como Discord, Teamspeak, WhatsApp, Signal o una llamada convencional.<br><br>La compatibilidad móvil es otro factor clave. Teleparty dispone de aplicación propia, mientras que KoalaSync, por ahora, solo funciona en navegadores de escritorio. Si quieres sincronizar Netflix en un teléfono o tableta, KoalaSync no es la herramienta adecuada en este momento.<br><br>Por tanto, si el chat y la compatibilidad móvil son tus prioridades, es posible que Teleparty se adapte mejor a tus necesidades.",
"ALT_TELEPARTY_WHEN_TELE_BODY": "Teleparty aún puede ser la mejor opción si prefieres una experiencia de visualización más integrada o si necesitas soporte móvil. Ambas herramientas incluyen chat de texto, por lo que el chat por sí solo ya no es la diferencia decisiva. El chat de KoalaSync está cifrado de extremo a extremo y es solo en vivo; Las llamadas de voz y vídeo siguen utilizando una aplicación independiente.<br><br>La compatibilidad móvil es otro factor clave. Teleparty dispone de aplicación propia, mientras que KoalaSync, por ahora, solo funciona en navegadores de escritorio. Si quieres sincronizar Netflix en un teléfono o tableta, KoalaSync no es la herramienta adecuada en este momento.<br><br>Entonces, si lo que más le importa es soporte móvil y una plataforma más integrada, Teleparty simplemente puede adaptarse mejor a su configuración.",
"ALT_TELEPARTY_WHEN_KOALA_TITLE": "Cuándo es mejor usar KoalaSync",
"ALT_TELEPARTY_WHEN_KOALA_BODY": "KoalaSync es una mejor opción si buscas una herramienta flexible para sincronizar la reproducción de vídeo en navegadores de escritorio, en lugar de algo limitado a una lista reducida de plataformas.<br><br>Funciona con casi cualquier sitio web que utilice el reproductor de vídeo estándar del navegador. Esto resulta especialmente útil para Emby, Jellyfin, Plex, servidores multimedia privados, dominios locales o sitios de vídeo más pequeños que las herramientas convencionales de visionado compartido suelen ignorar.<br><br>Además, es gratuito, de código abierto, no requiere crear cuentas y permite el autohospedaje si deseas gestionar tu propio servidor de retransmisión (relay). Lo creé porque buscaba una solución respetuosa con la privacidad que funcionara con mi propia configuración de Emby, y no otra plataforma cerrada limitada a unos pocos grandes servicios de streaming.",
"ALT_TELEPARTY_MAIN_DIFF_TITLE": "La diferencia principal",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty está concebido principalmente como una herramienta para ver contenido en grupo, con funciones de chat y compatibilidad móvil. KoalaSync, en cambio, funciona más bien como una capa flexible de sincronización de vídeo para navegadores.<br><br>Puede parecer una diferencia menor, pero cambia considerablemente el caso de uso. Si necesitas una aplicación completa para ver contenido en grupo que incluya chat integrado, Teleparty es la opción lógica. Si ya dispones de chat de voz y solo quieres mantener sincronizados los controles de vídeo entre distintos navegadores, KoalaSync es la solución más sencilla.",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty es una plataforma de visionado en grupo más integrada y compatible con dispositivos móviles. KoalaSync es una capa flexible de sincronización para navegadores de escritorio con chat de texto en vivo cifrado de extremo a extremo.<br><br>Puede parecer una diferencia menor, pero cambia considerablemente el caso de uso. Si necesitas una aplicación móvil para ver contenido en grupo, Teleparty tiene sentido. Si buscas compatibilidad con una amplia variedad de reproductores de navegador, incluidos servidores multimedia autohospedados, sin entregar mensajes de chat legibles al servidor relay, KoalaSync es la opción más sencilla.",
"ALT_TELEPARTY_LIMITS_TITLE": "Limitaciones reales",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync no hace milagros ni pretende ocultar sus limitaciones. Actualmente, solo funciona en navegadores de escritorio; no cuenta con chat integrado ni aplicación móvil.<br><br>La compatibilidad también depende del navegador y del reproductor de vídeo utilizado. Aunque KoalaSync debería funcionar con casi cualquier reproductor estándar de navegador, los sitios web pueden modificar sus reproductores, el comportamiento del DRM o las restricciones del navegador en cualquier momento.<br><br>KoalaSync no tiene ninguna relación con Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex ni ninguna otra marca mencionada aquí. Dichos nombres se utilizan únicamente para ilustrar casos de uso habituales.",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync no es mágico y no intenta ocultar sus límites. Actualmente sólo funciona en navegadores de escritorio. Incluye chat de texto cifrado de extremo a extremo, pero no chat de voz, video o cámara web, y no tiene aplicación móvil.<br><br>La compatibilidad también depende del navegador y del reproductor de vídeo utilizado. Aunque KoalaSync debería funcionar con casi cualquier reproductor estándar de navegador, los sitios web pueden modificar sus reproductores, el comportamiento del DRM o las restricciones del navegador en cualquier momento.<br><br>KoalaSync no tiene ninguna relación con Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex ni ninguna otra marca mencionada aquí. Dichos nombres se utilizan únicamente para ilustrar casos de uso habituales.",
"ALT_TELEPARTY_SUMMARY_TITLE": "Resumen",
"ALT_TELEPARTY_SUMMARY_BODY": "Si buscas chat integrado o compatibilidad con dispositivos móviles, Teleparty podría ser la mejor opción. Si prefieres una solución gratuita y de código abierto para sincronizar navegadores de escritorio que funcione con casi cualquier reproductor de vídeo convencional, incluidos servidores multimedia de alojamiento propio como Emby, Jellyfin y Plex, KoalaSync es probablemente la alternativa más adecuada.",
"ALT_TELEPARTY_SUMMARY_BODY": "Ambas herramientas incluyen chat de texto. Si necesita soporte móvil o prefiere la experiencia de visualización en grupo más integrada de Teleparty, Teleparty puede ser la mejor opción. Si desea una sincronización gratuita y de código abierto del navegador de escritorio con chat en vivo cifrado de extremo a extremo que funcione con casi cualquier reproductor de video normal, incluidos servidores multimedia autohospedados como Emby, Jellyfin y Plex, KoalaSync es probablemente la mejor opción.",
"ALT_SCREEN_TITLE": "KoalaSync frente a compartir pantalla: ¿cuándo es mejor la sincronización de vídeo?",
"ALT_SCREEN_META_DESC": "Una comparativa práctica entre KoalaSync y la función de compartir pantalla para noches de cine a distancia, analizando aspectos como el control de reproducción, la calidad de vídeo, la privacidad, los subtítulos y la visualización desde el navegador.",
"ALT_SCREEN_INTRO": "Compartir pantalla funciona y, sinceramente, a veces es suficiente. Una persona comparte su pantalla, las demás la ven y listo.<br><br>Sin embargo, para sesiones de cine más largas o para ver episodios en grupo, puede resultar molesto. La calidad del vídeo depende de la velocidad de subida, la CPU y el navegador de quien comparte, así como de la compresión que apliquen aplicaciones como Discord, Teamspeak, Zoom u otras plataformas de videollamada. Además, solo una persona tiene el control real de la reproducción.",
@@ -221,61 +222,61 @@
"ALT_SCREEN_WHY_CLEANER_TITLE": "Por qué KoalaSync puede resultar más limpio",
"ALT_SCREEN_WHY_CLEANER_BODY": "KoalaSync no transmite tu pantalla. Cada persona ve el video localmente en su propio navegador, y KoalaSync solo sincroniza las acciones de reproducción, como iniciar, pausar y avanzar o retroceder.<br><br>Esto significa que todos pueden usar la pantalla completa en su propio dispositivo, utilizar sus propios ajustes de subtítulos o audio (si el reproductor lo permite) y mantener el chat de voz separado en Discord, Teamspeak o cualquier otra aplicación de llamadas.<br><br>Para las noches de cine, esto puede resultar mucho más limpio que tener a una persona transmitiendo su pantalla. Es especialmente útil para configuraciones basadas en navegador como Emby, Jellyfin, Plex, archivos locales abiertos en el navegador, dominios privados u otros sitios web que utilicen reproductores de video estándar del navegador.",
"ALT_SCREEN_LIMITS_TITLE": "Limitaciones reales",
"ALT_SCREEN_LIMITS_BODY": "KoalaSync no sustituye a una llamada de voz. Si quieres hablar mientras ves el contenido, puedes seguir usando Discord, Teamspeak, Signal, WhatsApp o cualquier otra herramienta que utilices habitualmente.<br><br>Además, cada participante necesita acceder al vídeo o a la página web desde su propio dispositivo. KoalaSync no elude suscripciones, inicios de sesión, DRM, restricciones regionales ni las normas de las plataformas.<br><br>Actualmente solo funciona en navegadores de escritorio, no incluye funciones de chat o voz, y la compatibilidad depende siempre del navegador y del reproductor de vídeo.",
"ALT_SCREEN_LIMITS_BODY": "KoalaSync incluye chat de texto cifrado de extremo a extremo, pero no reemplaza una llamada de voz o video. Si quieres hablar mientras miras, aún usas Discord, Teamspeak, Signal, WhatsApp o lo que uses normalmente.<br><br>Además, cada participante necesita acceder al vídeo o a la página web desde su propio dispositivo. KoalaSync no elude suscripciones, inicios de sesión, DRM, restricciones regionales ni las normas de las plataformas.<br><br>Actualmente es solo para navegador de escritorio, no tiene chat de voz, video o cámara web incorporado, y la compatibilidad siempre depende del navegador y el reproductor de video.",
"ALT_SCREEN_SUMMARY_TITLE": "Resumen",
"ALT_SCREEN_SUMMARY_BODY": "Compartir pantalla es la opción más sencilla cuando buscas algo rápido y no te preocupa demasiado la calidad, la privacidad o el control individual. KoalaSync es una mejor alternativa cuando cada persona debe ver el contenido localmente en su propio navegador y se busca una experiencia que realmente se asemeje a verlo juntos.",
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: ¿Qué solución de fiesta de visualización se adapta mejor?",
"ALT_W2G_META_DESC": "Una comparación honesta entre KoalaSync y Watch2Gether. Descubra cuándo la flexibilidad de KoalaSync es inmejorable y por qué Watch2Gether sigue siendo la mejor opción para YouTube.",
"ALT_W2G_META_DESC": "Comparación práctica de KoalaSync y Watch2Gether: salas compartidas, listas, sincronización del reproductor y chat cifrado.",
"ALT_W2G_INTRO": "KoalaSync y Watch2Gether resuelven el mismo problema: ver vídeos sincronizados con amigos en línea sin coordinarlos manualmente. Si bien Watch2Gether es uno de los centros más antiguos y populares para noches de películas compartidas, KoalaSync adopta un enfoque liviano y completamente diferente. La herramienta adecuada para usted depende en gran medida de qué y dónde desea mirar.",
"ALT_W2G_WHEN_W2G_TITLE": "Cuando Watch2Gether es la mejor opción",
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether es inmejorable para las <strong>fiestas de visualización de YouTube</strong>. Con su lista de reproducción y búsqueda integradas, cualquier persona en la sala puede buscar, agregar y reproducir nuevos videos fácilmente. Todos ven el siguiente video juntos sin problemas.<br><br>Con KoalaSync en YouTube, la reproducción automática predeterminada del sitio toma el control y redirige a cada usuario a un video recomendado diferente. Nuestra función automática del siguiente episodio (\"Episode Lobby\"), que funciona muy bien en Netflix o Disney+, no funciona en YouTube debido a estas recomendaciones dinámicas. Para las noches puramente de YouTube, Watch2Gether es la mejor opción.",
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether puede encajar mejor para sesiones de YouTube centradas en una sala, búsqueda y lista compartidas. La compatibilidad depende del servicio y la fuente actuales.",
"ALT_W2G_WHEN_KOALA_TITLE": "Cuando KoalaSync se adapta mejor",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync brilla en <strong>servicios de streaming reales</strong> (Netflix, Prime Video, Disney+) y servidores de medios autohospedados (Jellyfin, Plex, Emby). A diferencia de Watch2Gether, que a menudo no puede reproducir ningún contenido premium, KoalaSync sincroniza el reproductor nativo directamente en el sitio web.<br><br>Funciona en casi cualquier sitio con un reproductor de vídeo HTML5 estándar. No es necesario configurar una sala virtual en un sitio de terceros: simplemente active la extensión, únase a un lobby y comience a mirar.",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync es una buena opción si quieres sincronizar el reproductor original de una página compatible, incluidos medios autohospedados, sin trasladar la reproducción a otra web de visionado compartido. La compatibilidad depende del sitio, el reproductor y el navegador.",
"ALT_W2G_MAIN_DIFF_TITLE": "La principal diferencia",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether es un <strong>sitio web independiente</strong> con chat integrado, listas de reproducción y soporte para cámara web. KoalaSync es una <strong>extensión de navegador minimalista</strong> que se conecta directamente a las pestañas normales de tu navegador.<br><br>¿Buscas un paquete todo en uno para YouTube con chat integrado? Utilice Watch2Gether. ¿Quieres ver películas en Netflix o en tu propio servidor sin distracciones, en calidad nativa (sin compresión del servidor)? KoalaSync es la solución más limpia.",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether es un <strong>sitio web independiente</strong> con chat de texto, listas de reproducción, búsqueda y soporte para cámara web. KoalaSync es una <strong>extensión de navegador minimalista</strong> que se conecta directamente a las pestañas normales de tu navegador y también incluye chat de texto en vivo cifrado de extremo a extremo.<br><br>¿Buscas un paquete todo en uno para YouTube con listas de reproducción, búsqueda y funciones de cámara web? Usa Watch2Gether. ¿Quieres ver películas en Netflix o en tu propio servidor sin distracciones, en calidad nativa (sin compresión del servidor)? KoalaSync es la solución más limpia.",
"ALT_W2G_LIMITS_TITLE": "Limitaciones honestas",
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>no tiene listas de reproducción ni búsqueda de vídeos integradas</strong>. En YouTube, esto significa que la reproducción automática del siguiente vídeo no permanecerá sincronizada.<br><br>Además, <strong>no hay chat integrado</strong> y KoalaSync actualmente solo funciona en navegadores de escritorio. Está diseñado bajo el supuesto de que ya estás hablando a través de Discord, Teamspeak u otras herramientas de voz de todos modos.",
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>no tiene listas de reproducción ni búsqueda de vídeos integradas</strong>. En YouTube, esto significa que la reproducción automática del siguiente vídeo no permanecerá sincronizada.<br><br>KoalaSync incluye chat de texto en vivo cifrado de extremo a extremo, pero no incluye cámara web ni chat de voz o vídeo integrados, y actualmente solo funciona en navegadores de escritorio. Si quieren hablar o verse por videollamada, todavía necesitan Discord, Teamspeak u otra herramienta de llamadas.",
"ALT_W2G_SUMMARY_TITLE": "En breve",
"ALT_W2G_SUMMARY_BODY": "Para sesiones espontáneas de YouTube con listas de reproducción y chat integrado, Watch2Gether es la mejor opción. Para plataformas de streaming premium (como Netflix, Prime Video, Disney+), servidores autohospedados (Jellyfin, Plex, Emby) y máxima flexibilidad del navegador, KoalaSync es la herramienta más adecuada y ligera.",
"ALT_W2G_SUMMARY_BODY": "Ambas herramientas incluyen chat de texto. Para sesiones espontáneas de YouTube con listas de reproducción, búsqueda y funciones de cámara web, Watch2Gether es la mejor opción. Para plataformas de streaming premium (como Netflix, Prime Video, Disney+), servidores autohospedados (Jellyfin, Plex, Emby), chat en vivo cifrado de extremo a extremo y máxima flexibilidad del navegador, KoalaSync es la herramienta más adecuada y liviana.",
"ALT_SCENER_TITLE": "KoalaSync vs Scener: ¿Qué plataforma de watch party se adapta mejor?",
"ALT_SCENER_META_DESC": "Una comparación honesta de KoalaSync y Scener. Descubra cuándo gana el chat con cámara web de Scener y por qué KoalaSync es mejor para noches de cine sin distracciones.",
"ALT_SCENER_INTRO": "KoalaSync y Scener abordan las fiestas de visualización desde dos ángulos completamente diferentes. Scener es una sala de cine virtual con video chat integrado y salas interactivas. KoalaSync, por otro lado, es una extensión de navegador minimalista y respetuosa con la privacidad que simplemente sincroniza los controles de reproducción. La herramienta que mejor se adapte a sus necesidades depende principalmente de la importancia que tengan para usted las funciones integradas de cámara web y chat.",
"ALT_SCENER_INTRO": "KoalaSync y Scener abordan las fiestas de visualización desde dos ángulos completamente diferentes. Scener es una sala de cine virtual con video chat integrado y salas interactivas. KoalaSync, por otro lado, es una extensión de navegador minimalista y respetuosa con la privacidad que sincroniza los controles de reproducción e incluye chat de texto en vivo cifrado de extremo a extremo. La herramienta que mejor se adapte a tus necesidades depende principalmente de si necesitas una cámara web integrada, voz, vídeo y funciones sociales o si prefieres una herramienta de sincronización ligera.",
"ALT_SCENER_WHEN_SCENER_TITLE": "Cuando Scener es la mejor opción",
"ALT_SCENER_WHEN_SCENER_BODY": "Scener es una opción sólida si desea una <strong>plataforma social todo en uno</strong> con chat de audio y video integrado directamente en su navegador. Scener crea una sala virtual donde el reproductor y las cámaras web de tus amigos se muestran una al lado de la otra. Esto es ideal para grandes fiestas públicas o eventos de transmisión virtual donde el aspecto social está al frente y al centro. Sin embargo, tenga en cuenta que la versión básica gratuita cuenta con publicidad y Scener bloquea muchas funciones (como visualización sin publicidad y video chat ilimitado) detrás de una suscripción mensual ($5,79/mes o $39,99/año).",
"ALT_SCENER_WHEN_SCENER_BODY": "Scener encaja mejor si buscas una experiencia social integrada con chat de texto y, para miembros Premium, chat de vídeo y audio. En ordenadores admite una lista definida de servicios mediante su extensión para Chrome.",
"ALT_SCENER_WHEN_KOALA_TITLE": "Cuando KoalaSync se adapta mejor",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync brilla cuando se trata de <strong>calidad de vídeo, privacidad y simplicidad</strong>:<br><br>• <strong>Sin cuentas</strong>: no es necesario registrarse, iniciar sesión ni administrar perfiles. Simplemente active la extensión, comparta un código de sala y comience a mirar.<br>• <strong>Calidad nativa verdadera (4K/HDR)</strong>: Scener transmite video comprimido desde un servidor virtual en la nube, lo que puede provocar pérdida de calidad y retrasos. KoalaSync sincroniza el reproductor nativo directamente en su computadora, lo que significa que puede ver películas en la resolución completa de su suscripción de transmisión.<br>• <strong>Bajo uso de ancho de banda</strong>: debido a que solo se transmiten datos de control (reproducción, pausa, marcas de tiempo), KoalaSync prácticamente no usa ancho de banda adicional. Scener transmite todo el video y las transmisiones de la cámara a través de servidores en la nube, lo que puede sobrecargar las conexiones a Internet más lentas y las computadoras portátiles más antiguas.<br>• <strong>100% gratis y sin publicidad</strong>: sin tarifas de suscripción mensual, sin publicidad ni rastreadores.<br>• <strong>Archivos de video locales</strong>: dado que KoalaSync se ejecuta localmente, incluso puedes ver tus propios archivos de video (como MP4) en sincronización con amigos. Simplemente arrastre y suelte el archivo en una pestaña del navegador; Scener no puede hacer esto porque su reproductor se ejecuta en la nube.",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync encaja si prefieres sincronización de código abierto sin cuenta en el reproductor original, chat de texto en vivo cifrado de extremo a extremo y un relay autohospedado opcional. No incluye chat de voz, vídeo ni cámara web.",
"ALT_SCENER_MAIN_DIFF_TITLE": "La principal diferencia",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener es una <strong>red social comercial</strong> para grupos de visualización que funciona con navegadores virtuales en la nube y se financia a través de anuncios y suscripciones mensuales. KoalaSync es una <strong>utilidad de sincronización gratuita que prioriza la privacidad</strong> y se conecta directamente a las pestañas de su navegador. Deja el chat de voz/vídeo en manos de aplicaciones como Discord o Teamspeak, centrándose por completo en mantener la reproducción fluida, privada y en su calidad original nativa.",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener se centra en salas integradas y funciones sociales. KoalaSync se centra en sincronizar el reproductor original y ofrecer chat de texto en vivo cifrado de extremo a extremo.",
"ALT_SCENER_LIMITS_TITLE": "Limitaciones honestas",
"ALT_SCENER_LIMITS_BODY": "KoalaSync no ofrece <strong>soporte integrado para chat o cámara web</strong>. Es puramente una herramienta de sincronización y requiere una herramienta de llamada de voz independiente de su elección. Además, KoalaSync actualmente es solo para escritorio y la compatibilidad de archivos de video locales depende completamente de si su navegador admite de forma nativa la reproducción del formato de video y códecs de audio.",
"ALT_SCENER_LIMITS_BODY": "KoalaSync ofrece <strong>chat de texto en vivo cifrado de extremo a extremo, pero no chat con cámara web, voz o vídeo</strong>. Por lo tanto, hablar o verse requiere una herramienta de llamada independiente de su elección. Además, KoalaSync actualmente es solo para escritorio y la compatibilidad de archivos de video locales depende completamente de si su navegador admite de forma nativa la reproducción del formato de video y códecs de audio.",
"ALT_SCENER_SUMMARY_TITLE": "En breve",
"ALT_SCENER_SUMMARY_BODY": "Para grandes grupos de visualización con chat con cámara web integrado y funciones sociales, Scener es la opción correcta, siempre que no le importen los anuncios y los muros de pago premium. Si desea una experiencia completamente gratuita, sin publicidad y sin distracciones en el reproductor nativo (incluidos Netflix, Prime Video, Disney+ o archivos del navegador local) y ya está en una llamada de voz, KoalaSync es la solución más limpia y eficaz.",
"ALT_SCENER_SUMMARY_BODY": "Elige Scener para funciones sociales, de vídeo y audio integradas en sus servicios compatibles. Elige KoalaSync para sincronización sin cuenta y de código abierto, chat cifrado en vivo y autohospedaje opcional.",
"ALT_KOSMI_TITLE": "KoalaSync vs Kosmi: ¿Qué alternativa de fiesta de Netflix encaja mejor?",
"ALT_KOSMI_META_DESC": "Una comparación honesta de KoalaSync y Kosmi. Descubra por qué la configuración sin extensiones de Kosmi es conveniente y cuándo gana la calidad de video nativa de KoalaSync.",
"ALT_KOSMI_INTRO": "Tanto KoalaSync como Kosmi ofrecen excelentes formas de ver Netflix, YouTube y otras plataformas sincronizadas con amigos. Sin embargo, sus tecnologías subyacentes son fundamentalmente diferentes. Kosmi utiliza navegadores virtuales en la nube que se ejecutan directamente en su navegador web sin instalar extensiones. KoalaSync es una extensión liviana que sincroniza su reproductor de video local. El enfoque que mejor se adapte a sus necesidades depende de sus prioridades en cuanto a calidad de vídeo, comodidad y privacidad.",
"ALT_KOSMI_INTRO": "KoalaSync y Kosmi permiten ver contenido en grupo, pero son productos distintos con servicios y funciones sociales diferentes. KoalaSync sincroniza un reproductor compatible en el navegador de escritorio. Consulta la compatibilidad actual de cada servicio.",
"ALT_KOSMI_WHEN_KOSMI_TITLE": "Cuando Kosmi es la mejor opción",
"ALT_KOSMI_WHEN_KOSMI_BODY": "Kosmi es inmejorable si quieres <strong>evitar instalar nada</strong> o quieres mirar en dispositivos como tabletas y teléfonos inteligentes. Debido a que Kosmi ejecuta navegadores virtuales en la nube, todo lo que se necesita para unirse es un simple enlace web. Además, Kosmi es una plataforma de reunión todo en uno: además de la transmisión sincronizada, puedes jugar juegos multijugador (como clásicos retro de NES/SNES o póquer) y usar la cámara web integrada, el chat de voz y de texto. Si quieres una noche de juegos sociales y una fiesta para ver sin configuración, Kosmi es una solución fantástica.",
"ALT_KOSMI_WHEN_KOSMI_BODY": "Kosmi puede encajar mejor si buscas sus salas sociales en el navegador, juegos y funciones de comunicación integradas. La disponibilidad puede variar según el dispositivo, la fuente y el plan actual.",
"ALT_KOSMI_WHEN_KOALA_TITLE": "Cuando KoalaSync se adapta mejor",
"ALT_KOSMI_WHEN_KOALA_BODY": "KoalaSync brilla cuando se trata de <strong>calidad de reproducción nativa, seguridad y rendimiento</strong>:<br><br>• <strong>Sin riesgos de seguridad</strong>: con Kosmi, debes iniciar sesión en tu cuenta de Netflix o Prime en una computadora virtual remota en la nube. Con KoalaSync, inicias sesión de forma segura en el sitio web oficial de transmisión en tu propio navegador, tal como lo haces normalmente. Sus contraseñas nunca salen de su máquina.<br>• <strong>Calidad de vídeo completa (4K/HDR)</strong>: Kosmi transmite la pantalla del navegador virtual como una señal de vídeo comprimida, lo que puede provocar pixelación y almacenamiento en búfer. KoalaSync sincroniza los reproductores nativos localmente: usted disfruta de las películas con la resolución completa y nítida de su suscripción.<br>• <strong>Sin demoras y compatible con el ancho de banda</strong>: dado que KoalaSync solo envía comandos de control (reproducir/pausar/marcas de tiempo), utiliza casi cero ancho de banda. Kosmi transmite constantemente transmisiones de video HD, lo que puede abrumar las conexiones más lentas.<br>• <strong>Archivos de video locales</strong>: si desea ver un video privado, simplemente arrastre y suelte el archivo MP4 en una pestaña del navegador y sincronícelo. Kosmi no puede acceder a los archivos de su disco duro local.",
"ALT_KOSMI_WHEN_KOALA_BODY": "KoalaSync encaja mejor si quieres sincronizar un reproductor compatible en tu navegador de escritorio, usar chat de texto en vivo cifrado de extremo a extremo o alojar el relay. No incluye llamadas ni aplicación móvil.",
"ALT_KOSMI_MAIN_DIFF_TITLE": "La principal diferencia",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmi es un <strong>lugar virtual en la nube</strong> que transmite la pantalla de un navegador remoto para que nadie tenga que instalar una extensión. KoalaSync es una <strong>capa de sincronización del navegador local</strong> que se conecta directamente al reproductor nativo en el sitio web oficial de streaming. Si bien Kosmi es más versátil en cuanto a minijuegos y compatibilidad con dispositivos, KoalaSync ofrece una experiencia de transmisión más segura, fluida y nítida en el reproductor original.",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmi se centra en una sala social integrada. KoalaSync es una extensión de escritorio que sincroniza reproductores compatibles y ofrece chat de texto cifrado en vivo.",
"ALT_KOSMI_LIMITS_TITLE": "Limitaciones honestas",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync <strong>requiere la instalación de una extensión del navegador</strong> y actualmente solo se ejecuta en PC de escritorio. <strong>No tiene chat ni cámaras web integrados</strong>, ya que está diseñado bajo el supuesto de que de todos modos ya estás hablando a través de Discord u otras herramientas de voz. Tampoco ofrece juegos integrados.",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync <strong>requiere la instalación de una extensión del navegador</strong> y actualmente solo se ejecuta en PC de escritorio. Incluye <strong>chat de texto en vivo cifrado de extremo a extremo, pero no chat con cámara web, voz o vídeo</strong>. Tampoco ofrece juegos integrados.",
"ALT_KOSMI_SUMMARY_TITLE": "En breve",
"ALT_KOSMI_SUMMARY_BODY": "Para noches de juegos casuales con clásicos retro y fácil acceso en dispositivos móviles sin instalación, Kosmi es ideal. Para noches de películas premium en Netflix, Prime Video, Disney+ o Plex en calidad total 4K, con máxima seguridad y mínimo impacto en el ancho de banda, KoalaSync es el claro ganador.",
"ALT_KOSMI_SUMMARY_BODY": "Elige Kosmi por sus funciones sociales integradas. Elige KoalaSync para sincronización de escritorio sin cuenta y de código abierto, chat cifrado y relay autohospedado opcional.",
"ALT_TWOSEVEN_TITLE": "KoalaSync vs TwoSeven: ¿Qué solución de fiesta de visualización se adapta mejor?",
"ALT_TWOSEVEN_META_DESC": "Una comparación honesta de KoalaSync y TwoSeven. Descubra cuándo gana la cámara web integrada de TwoSeven y por qué KoalaSync es mejor para Disney+ y Netflix.",
"ALT_TWOSEVEN_INTRO": "Tanto KoalaSync como TwoSeven utilizan el mismo enfoque básico para las fiestas de visualización: sincronizan el reproductor de vídeo original directamente en su navegador, en lugar de transmitir un vídeo comprimido desde la nube. Esto garantiza una calidad de vídeo de primer nivel para ambas herramientas. Sin embargo, si bien TwoSeven es un portal comercial con chat con cámara web integrado y niveles de suscripción, KoalaSync sigue siendo una extensión minimalista, completamente gratuita y respetuosa con la privacidad. Descubra aquí qué servicio se adapta mejor a sus necesidades.",
"ALT_TWOSEVEN_META_DESC": "Comparación práctica de KoalaSync y TwoSeven: cuentas, comunicación integrada, chat cifrado y autohospedaje.",
"ALT_TWOSEVEN_INTRO": "Ambas herramientas permiten el visionado sincronizado, pero sus servicios y requisitos pueden cambiar. TwoSeven usa cuentas y ofrece comunicación integrada. KoalaSync no requiere cuenta, ofrece chat de texto cifrado y no incluye llamadas.",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_TITLE": "Cuando TwoSeven es la mejor opción",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "TwoSeven es la elección correcta si desea una <strong>solución todo en uno con cámara web integrada y chat de audio</strong> directamente en su navegador. Si quieres ver a tus amigos mientras miran sin tener que ejecutar Discord o Teamspeak en segundo plano, TwoSeven ofrece esta integración de forma nativa. Además, funciona para YouTube y algunos reproductores de vídeo web abiertos sin extensiones. Es ideal para grupos pequeños de 2 a 3 personas que quieran compartir una conexión visual mientras miran.",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "TwoSeven puede encajar mejor si te importan la cámara web y el audio integrados. Comprueba sus requisitos actuales de navegador, cuenta, plan y servicios compatibles.",
"ALT_TWOSEVEN_WHEN_KOALA_TITLE": "Cuando KoalaSync se adapta mejor",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync brilla por su <strong>falta total de tarifas, privacidad y simplicidad</strong>:<br><br>• <strong>Sin muros de pago</strong>: para ver servicios premium como Disney+ o Hulu sincronizados en TwoSeven, al menos un miembro de la sala debe apoyarlos en Patreon (a partir de $3/mes). KoalaSync sincroniza Netflix, Prime Video, Disney+ y servidores multimedia autohospedados (Jellyfin, Plex, Emby) de forma 100 % gratuita y sin restricciones.<br>• <strong>Privacidad absoluta (sin rastreadores ni anuncios):</strong> TwoSeven cuenta con publicidad y utiliza múltiples rastreadores de Google, Facebook y otros. KoalaSync no tiene anuncios ni rastreadores y se comunica exclusivamente con el servidor de retransmisión de solo RAM.<br>• <strong>No es necesario registrarse</strong>: TwoSeven requiere que todos los participantes creen una cuenta e inicien sesión. KoalaSync no tiene ninguna cuenta. Simplemente instálelo, ingrese un código de sala y comience a mirar.<br>• <strong>Ligero y con pocos recursos</strong>: la cámara web integrada y las transmisiones de audio de TwoSeven suponen una gran carga para su CPU y su red. Al dejar la comunicación en manos de herramientas como Discord, KoalaSync funciona de manera extremadamente fluida y preserva la duración de la batería.",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync encaja si buscas sincronización de código abierto sin cuenta, chat de texto en vivo cifrado de extremo a extremo y la opción de alojar tu propio relay. Las llamadas de voz y cámara web se realizan en otra aplicación.",
"ALT_TWOSEVEN_MAIN_DIFF_TITLE": "La principal diferencia",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven es una <strong>plataforma comercial</strong> con requisitos de registro, anuncios, chats con cámara web de igual a igual y muros de pago de Patreon para servicios de transmisión específicos. KoalaSync es una <strong>utilidad gratuita de código abierto</strong> que se conecta directamente a las pestañas de su navegador sin cuentas, rastreadores ni costos adicionales, lo que permite mirar contenido sin distracciones en el reproductor nativo.",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven combina el visionado sincronizado con una plataforma basada en cuentas y funciones integradas de cámara web y audio. KoalaSync es una extensión sin cuenta con chat de texto en vivo cifrado; las llamadas permanecen separadas.",
"ALT_TWOSEVEN_LIMITS_TITLE": "Limitaciones honestas",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync no ofrece <strong>cámara web ni chat de audio integrados</strong> y asume que ya estás en una llamada a través de Discord o una herramienta similar. Además, KoalaSync no tiene un cuadro de chat superpuesto incorporado y actualmente es solo para escritorio.",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync no ofrece <strong>cámara web ni chat de audio integrados</strong>, por lo que verse o hablar entre sí aún requiere Discord o una herramienta similar. Incluye una superposición de chat de texto en vivo, flotante y acoplable, cifrado de extremo a extremo, y actualmente es solo para escritorio.",
"ALT_TWOSEVEN_SUMMARY_TITLE": "En breve",
"ALT_TWOSEVEN_SUMMARY_BODY": "Para grupos pequeños de dos o tres personas que quieren verse a través de cámaras web directamente en el navegador y no les importa apoyar a Patreon para Disney+, TwoSeven es una buena opción. Para noches de cine privadas, completamente gratuitas y sin publicidad en calidad nativa 4K (ya sea Netflix, Prime Video, Disney+ o Jellyfin) sin registros, KoalaSync es la opción más limpia y segura.",
"ALT_TWOSEVEN_SUMMARY_BODY": "Elige TwoSeven si lo más importante son la cámara web y el audio integrados. Elige KoalaSync para sincronización sin cuenta y de código abierto, chat cifrado en vivo y autohospedaje opcional.",
"ALT_TRY_KOALASYNC": "Prueba KoalaSync",
"MOCK_HOST_CONTROL_LABEL": "Control del anfitrión",
"MOCK_HOST_CONTROL_TOGGLE": "Solo yo puedo controlar la reproducción",
@@ -287,5 +288,9 @@
"SELF_SHOW_SETUP": "Mostrar guía de instalación",
"SELF_HIDE_SETUP": "Ocultar guía de instalación",
"HERO_TRUST": "Gratis · Código abierto · Sin cuenta",
"COMP_MORE_GUIDES": "Comparativa completa y más alternativas"
"COMP_MORE_GUIDES": "Comparativa completa y más alternativas",
"NAV_HOME": "Inicio",
"ARIA_SELECT_LANGUAGE": "Seleccionar idioma",
"ARIA_MENU": "Menú",
"FOOTER_GUIDES": "Guías y comparativas"
}
+54 -49
View File
@@ -16,8 +16,9 @@
"NAV_HOW_IT_WORKS": "Comment ça marche",
"SUPPORT_BANNER_TEXT": "KoalaSync naccède pas à votre site vidéo ?",
"SUPPORT_BANNER_CTA": "Réparer laccès au site",
"SUPPORT_BANNER_DISMISS": "Fermer lavertissement",
"HERO_TITLE": "Transformez n'importe quelle vidéo en <span class='hero-highlight'>watch party</span>.",
"HERO_SUBTITLE": "Votre soirée ciné à distance, sans décalage. Pas de compte, pas de tracking, pas de pub. Partagez un lien et lancez la lecture ensemble.",
"HERO_SUBTITLE": "Regardez des vidéos ensemble à distance. Sans compte, suivi ni publicité. Partagez un lien et démarrez ensemble.",
"HERO_MASCOT_ALT": "Un koala mignon debout et regardant vers le bas les boutons de téléchargement",
"DEMO_SYNC": "Synchronisé",
"DEMO_HINT": "Démo interactive : lancez la lecture dans un onglet, KoalaSync garde les deux synchronisés.",
@@ -41,7 +42,7 @@
"USE_CASE_1_DESC": "Synchronisez vos films en temps réel et parlez via Discord, Zoom ou votre application d'appel vocal préférée. C'est comme si vous étiez dans la même pièce.",
"USE_CASE_2_ALT": "Un professeur koala faisant une présentation sur un écran à deux élèves koalas à distance",
"USE_CASE_2_TITLE": "Apprentissage à distance",
"USE_CASE_2_DESC": "Analysez des tutoriels en ligne, des conférences ou des flux de formation de développeurs avec des camarades ou des collègues. Faites une pause et discutez des étapes complexes en parfaite harmonie.",
"USE_CASE_2_DESC": "Analysez ensemble des tutoriels, cours ou vidéos de formation. Mettez en pause et discutez des étapes complexes.",
"USE_CASE_3_ALT": "Un couple de koalas mignons regardant ensemble à distance ; l'un est assis devant un PC et l'autre est au lit avec un ordinateur portable, avec des cœurs volants entre eux",
"USE_CASE_3_TITLE": "Relations à distance",
"USE_CASE_3_DESC": "Comblez la distance et profitez de soirées en tête-à-tête. Vivez chaque rebondissement, riez des mêmes blagues et partagez des moments émotionnels à la milliseconde près.",
@@ -114,8 +115,8 @@
"STEP_2_ILLUS_CREATE": "+ Créer un nouveau salon",
"STEP_2_ILLUS_MANUAL": "Connexion manuelle / Avancé",
"STEP_2_ILLUS_COPIED": "Lien d'invitation copié !",
"STEP_3_TITLE": "Profiter de la synchronisation",
"STEP_3_DESC": "Lancez la lecture. Si quelqu'un met en pause, avance ou joue, la vidéo de tout le monde reste synchronisée en temps réel sans décalage.",
"STEP_3_TITLE": "Regardez ensemble",
"STEP_3_DESC": "Lancez la lecture. Les changements sont partagés avec tous les participants ; le réseau et le lecteur peuvent provoquer un léger délai.",
"STEP_3_ILLUS_IN_SYNC": "SYNCHRONISÉ",
"SELF_TITLE": "Pour l'auto-hébergement",
"SELF_SUBTITLE": "Vous ne faites pas confiance à notre serveur ? Conservez votre souveraineté totale sur les données. Déployez votre propre serveur relais en quelques minutes.",
@@ -126,35 +127,35 @@
"BOTTOM_SUBTITLE": "KoalaSync est 100% open source, sans publicité et sans suivi. Auditez notre base de code sur GitHub ou installez directement l'extension de navigateur.",
"BOTTOM_MASCOT_ALT": "Un koala mignon tenant une page de dépôt GitHub à côté d'Octocat, la mascotte de GitHub",
"FAQ_TITLE": "Foire aux questions",
"FAQ_SUBTITLE": "Tout ce que vous devez savoir pour regarder des vidéos en parfaite synchronisation.",
"FAQ_SUBTITLE": "Réponses sur les sites compatibles, la synchronisation, la confidentialité, le chat et le traitement audio.",
"FAQ_Q1": "Puis-je regarder Netflix, Emby ou Jellyfin en synchronisation avec des amis ?",
"FAQ_A1": "Oui ! KoalaSync fonctionne avec Netflix, YouTube, Twitch et tout site avec un lecteur vidéo HTML5, y compris Emby, Jellyfin et les serveurs multimédia auto-hébergés. Installez l'extension, créez un salon, partagez le lien et regardez en parfaite synchronisation.",
"FAQ_A1": "KoalaSync fonctionne avec de nombreux sites compatibles utilisant un lecteur vidéo standard du navigateur, dont Netflix, YouTube, Twitch, Emby, Jellyfin et des serveurs multimédias autohébergés. La compatibilité dépend du site, du navigateur et du lecteur et peut changer après une mise à jour.",
"FAQ_Q_VIDEO_ACCESS": "KoalaSync diffuse-t-il la vidéo à mes amis ?",
"FAQ_A_VIDEO_ACCESS": "Non. KoalaSync ne diffuse, nhéberge, ne partage et ne contourne aucun accès aux contenus vidéo. Chaque personne regarde la vidéo localement dans son propre navigateur et doit disposer de son propre accès au site ou au service de streaming, par exemple son propre compte Netflix. KoalaSync synchronise uniquement les actions de lecture comme lecture, pause et recherche dans la vidéo.",
"FAQ_Q2": "KoalaSync est-il une alternative gratuite à Teleparty ?",
"FAQ_A2": "100 % gratuit et open source sous licence MIT. Aucun niveau premium, aucune fonctionnalité verrouillée, aucune publicité. Contrairement à Teleparty, le serveur relay de KoalaSync fonctionne uniquement en RAM, sans collecte de données ni comptes utilisateur.",
"FAQ_A2": "Oui. KoalaSync est gratuit et open source sous licence MIT, sans offre premium, fonction verrouillée, publicité ni compte obligatoire.",
"FAQ_Q3": "KoalaSync fonctionne-t-il avec Emby, Jellyfin et les serveurs multimédia auto-hébergés ?",
"FAQ_A3": "Oui. KoalaSync détecte tout élément vidéo HTML5 standard et fonctionne parfaitement avec Emby, Jellyfin, Plex et toute configuration de streaming personnalisée. Vous pouvez également auto-héberger le serveur relay via Docker.",
"FAQ_A3": "Oui. KoalaSync prend en charge de nombreux lecteurs vidéo standards utilisés par Emby, Jellyfin, Plex et des installations autohébergées. La compatibilité dépend du lecteur. Vous pouvez aussi autohéberger le relay KoalaSync avec Docker.",
"FAQ_Q4": "Quels navigateurs et plateformes prennent en charge KoalaSync ?",
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave et tous les navigateurs basés sur Chromium. Installez-le depuis le Chrome Web Store ou les modules complémentaires Firefox.",
"FAQ_Q5": "Tous mes amis doivent-ils installer l'extension ?",
"FAQ_A5": "Oui, chaque participant a besoin de KoalaSync, mais sans inscription ni création de compte. Cliquez simplement sur le lien d'invitation, installez l'extension si nécessaire et vous rejoignez automatiquement le salon.",
"FAQ_Q6": "Mes données sont-elles en sécurité ? Et si je ne fais pas confiance au serveur officiel ?",
"FAQ_A6": "Totalement sûr. Le serveur relay de KoalaSync fonctionne uniquement en RAM, toutes les données de session sont supprimées dès que vous quittez le salon. Aucune base de données, aucun journal, aucune analyse, aucun compte. Si vous préférez un contrôle total : hébergez votre propre serveur relay privé via Docker en quelques minutes. Le code source est entièrement open source et vérifiable sur GitHub.",
"FAQ_A6": "Le relay officiel de KoalaSync ne conserve aucun historique de salon, de synchronisation, de lecture ou de chat dans une base de données. Les données d'un salon actif restent temporairement en mémoire et sont supprimées lorsque le salon est vide ou expire après une période d'inactivité. Des journaux techniques de connexion peuvent être créés à des fins de sécurité et de prévention des abus ; leur conservation dépend de la configuration du serveur. Les messages du chat sont chiffrés de bout en bout et ne sont pas conservés dans un historique côté serveur.",
"FAQ_Q7": "Puis-je atténuer les dialogues discrets et les explosions fortes en regardant des vidéos dans le navigateur ?",
"FAQ_A7": "Oui. KoalaSync 2.2.0 inclut un compresseur audio intégré qui réduit l'écart entre les parties silencieuses et bruyantes de votre vidéo. Il fonctionne localement via l'API Web Audio, aucune donnée audio ne quitte votre ordinateur. Ouvrez le popup, créez un salon, allez dans Paramètres → Audio Processing, activez le compresseur et choisissez un préréglage. Fonctionne sur Netflix, Prime Video, Disney+, YouTube et tout site vidéo HTML5.",
"FAQ_A7": "Oui. KoalaSync inclut un compresseur audio qui réduit lécart entre les passages faibles et forts. Il fonctionne localement via Web Audio API ; les données audio ne sont pas envoyées au relay. Activez-le dans Paramètres → Traitement audio.",
"HOWTO_TITLE": "Comment synchroniser des vidéos avec des amis avec KoalaSync",
"HOWTO_DESC": "Regardez des vidéos en parfaite synchronisation avec vos amis sur YouTube, Netflix, Twitch et plus. Sans inscription, sans pistage.",
"HOWTO_DESC": "Regardez ensemble des vidéos compatibles sur YouTube, Netflix, Twitch et ailleurs. Sans inscription ni suivi.",
"HOWTO_STEP_1_NAME": "Installer l'extension",
"HOWTO_STEP_1_TEXT": "Ajoutez KoalaSync à votre navigateur depuis le Chrome Web Store ou les modules complémentaires Firefox. Fonctionne sur tout site avec un élément vidéo.",
"HOWTO_STEP_1_TEXT": "Installez KoalaSync depuis le Chrome Web Store ou Firefox Add-ons. Ouvrez une page compatible et sélectionnez une vidéo détectée par lextension.",
"HOWTO_STEP_2_NAME": "Créer un salon",
"HOWTO_STEP_2_TEXT": "Ouvrez l'extension et cliquez sur Créer un nouveau salon. Le lien d'invitation est automatiquement copié dans votre presse-papiers.",
"HOWTO_STEP_3_NAME": "Partager et synchroniser",
"HOWTO_STEP_3_TEXT": "Envoyez le lien d'invitation à vos amis. Sélectionnez un onglet vidéo et profitez de la lecture synchronisée en temps réel.",
"FOOTER_MIT": "Open source sous licence MIT.",
"FOOTER_RAM": "Aucune donnée n'est stockée sur nos serveurs. Relais uniquement en RAM.",
"FOOTER_LEGAL": "Legal Notice",
"FOOTER_PRIVACY": "Privacy Policy",
"FOOTER_RAM": "Aucun historique de chat ou de visionnage. Aucun suivi. Les journaux daccès au site sont supprimés après 7 jours.",
"FOOTER_LEGAL": "Mentions légales",
"FOOTER_PRIVACY": "Politique de confidentialité",
"MOCK_01": "Salle active",
"MOCK_02": "Serveur officiel",
"MOCK_03": "Lien d'invitation",
@@ -190,7 +191,7 @@
"FOOTER_LEGAL_LINK": "imprint",
"FOOTER_PRIVACY_LINK": "privacy",
"FOOTER_DISCLAIMER": "KoalaSync n'est pas affilié à Netflix, Disney+, Amazon, YouTube, Twitch ou toute autre plateforme de streaming. Toutes les marques déposées appartiennent à leurs détenteurs respectifs.",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "Soutenir KoalaSync",
"ALT_INDEX_TITLE": "KoalaSync - Alternatives et comparatifs",
"ALT_INDEX_META_DESC": "Explorez des comparatifs honnêtes et des guides détaillés entre KoalaSync et dautres extensions de watch party et solutions de streaming.",
"ALT_INDEX_HEADING": "Guides et comparatifs",
@@ -200,17 +201,17 @@
"ALT_INDEX_FOOTER_LINK": "Guides et comparatifs",
"ALT_TELEPARTY_TITLE": "KoalaSync vs Teleparty : quel outil de visionnage en groupe choisir ?",
"ALT_TELEPARTY_META_DESC": "Une comparaison pratique entre KoalaSync et Teleparty pour le visionnage en groupe : chat intégré, compatibilité mobile, synchronisation vidéo via navigateur, serveurs multimédias auto-hébergés et nature open-source.",
"ALT_TELEPARTY_INTRO": "KoalaSync et Teleparty répondent tous deux au même problème agaçant : regarder quelque chose ensemble sans avoir à passer par le rituel du « 3, 2, 1, lecture ». Ils adoptent simplement des approches différentes.<br><br>Teleparty est la solution idéale si vous souhaitez un chat intégré ou si vous voulez regarder du contenu sur mobile. KoalaSync est davantage conçu pour le visionnage sur navigateur (PC/Mac) lorsque vous communiquez déjà via Discord, Teamspeak ou un autre service d'appel vocal, et que vous voulez simplement que la lecture reste synchronisée.<br><br>La raison principale pour laquelle j'ai créé KoalaSync est la flexibilité. L'outil est conçu pour fonctionner avec presque tous les sites web utilisant un lecteur vidéo standard, et non avec une liste fixe de plateformes. Cela inclut les installations auto-hébergées comme Emby, Jellyfin et Plex, les domaines locaux ou privés, ainsi que de nombreuses pages vidéo classiques sur navigateur.",
"ALT_TELEPARTY_INTRO": "KoalaSync et Teleparty répondent tous deux au même problème agaçant : regarder quelque chose ensemble sans avoir à passer par le rituel du « 3, 2, 1, lecture ». Ils adoptent simplement des approches différentes.<br><br>Teleparty est le plus logique si vous souhaitez une expérience de visionnage plus intégrée ou si vous souhaitez regarder sur mobile. KoalaSync est conçu pour les soirées de visionnage sur navigateur de bureau sur une gamme plus large de lecteurs vidéo de navigateur normaux. Son chat textuel intégré est chiffré de bout en bout et en direct uniquement, tandis que les appels vocaux et vidéo restent séparés.<br><br>La raison principale pour laquelle j'ai créé KoalaSync est la flexibilité. L'outil est conçu pour fonctionner avec presque tous les sites web utilisant un lecteur vidéo standard, et non avec une liste fixe de plateformes. Cela inclut les installations auto-hébergées comme Emby, Jellyfin et Plex, les domaines locaux ou privés, ainsi que de nombreuses pages vidéo classiques sur navigateur.",
"ALT_TELEPARTY_WHEN_TELE_TITLE": "Quand Teleparty reste une bonne option",
"ALT_TELEPARTY_WHEN_TELE_BODY": "Teleparty peut s'avérer être le meilleur choix si vous souhaitez que le chat soit directement intégré à l'outil de visionnage. KoalaSync ne propose pas de chat intégré pour le moment ; il part du principe que vous communiquez déjà par ailleurs, via Discord, Teamspeak, WhatsApp, Signal ou un appel classique.<br><br>L'aspect mobile constitue une autre raison majeure. Teleparty dispose d'une application, alors que KoalaSync ne fonctionne actuellement que sur les navigateurs de bureau. Si vous souhaitez synchroniser Netflix sur un téléphone ou une tablette, KoalaSync n'est pas l'outil adapté pour l'instant.<br><br>Ainsi, si le chat et la compatibilité mobile sont vos priorités, Teleparty pourrait tout simplement mieux convenir à votre configuration.",
"ALT_TELEPARTY_WHEN_TELE_BODY": "Teleparty peut rester le meilleur choix si vous préférez une expérience de watch party plus intégrée ou si vous avez besoin d'une prise en charge mobile. Les deux outils proposent un chat textuel : le chat seul n'est donc plus le critère décisif. Celui de KoalaSync est chiffré de bout en bout et disponible uniquement en direct ; les appels vocaux et vidéo passent toujours par une application distincte.<br><br>L'aspect mobile constitue une autre raison majeure. Teleparty dispose d'une application, alors que KoalaSync ne fonctionne actuellement que sur les navigateurs de bureau. Si vous souhaitez synchroniser Netflix sur un téléphone ou une tablette, KoalaSync n'est pas l'outil adapté pour l'instant.<br><br>Si la prise en charge mobile et une plateforme plus intégrée sont vos priorités, Teleparty peut donc mieux convenir à votre usage.",
"ALT_TELEPARTY_WHEN_KOALA_TITLE": "Quand choisir KoalaSync",
"ALT_TELEPARTY_WHEN_KOALA_BODY": "KoalaSync est plus adapté si vous recherchez un outil flexible de synchronisation vidéo pour navigateur de bureau, plutôt qu'une solution limitée à une courte liste de plateformes.<br><br>Il fonctionne avec presque tous les sites web utilisant un lecteur vidéo standard de navigateur. C'est particulièrement utile pour Emby, Jellyfin, Plex, les serveurs multimédias privés, les domaines locaux ou les sites vidéo plus modestes que les outils classiques de visionnage en groupe ignorent généralement.<br><br>De plus, c'est un outil gratuit et open source qui ne nécessite pas de compte et peut être auto-hébergé si vous souhaitez gérer votre propre relais. Je l'ai créé parce que je voulais une solution respectueuse de la vie privée compatible avec ma propre installation Emby, et non une plateforme fermée supplémentaire réservée à quelques grands sites de streaming.",
"ALT_TELEPARTY_MAIN_DIFF_TITLE": "La différence principale",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty est avant tout un outil de visionnage en groupe incluant une messagerie et une prise en charge mobile. KoalaSync se présente davantage comme une couche de synchronisation flexible pour la vidéo sur navigateur.<br><br>Cela peut sembler être un détail, mais cela change considérablement l'usage. Si vous avez besoin d'une application complète de visionnage en groupe avec messagerie intégrée, Teleparty est tout indiqué. Si vous disposez déjà d'un système de discussion vocale et que vous souhaitez simplement synchroniser les commandes vidéo entre différents navigateurs, KoalaSync est la solution la plus simple.",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty est une plateforme de watch party plus intégrée avec prise en charge mobile. KoalaSync est une couche de synchronisation flexible pour navigateurs de bureau avec chat textuel en direct chiffré de bout en bout.<br><br>Cette différence peut sembler minime, mais elle change fortement le cas d'utilisation. Si vous avez besoin d'une application mobile de watch party, Teleparty est un choix logique. Si vous recherchez une large compatibilité avec les lecteurs de navigateur, y compris les médias auto-hébergés, sans transmettre de messages lisibles au serveur relais, KoalaSync est la solution la plus simple.",
"ALT_TELEPARTY_LIMITS_TITLE": "Limites en toute transparence",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync n'est pas magique et ne cherche pas à dissimuler ses limites. Actuellement, il ne fonctionne que sur les navigateurs de bureau. Il ne dispose ni de messagerie intégrée ni d'application mobile.<br><br>La compatibilité dépend également du navigateur et du lecteur vidéo. KoalaSync devrait fonctionner avec presque tous les lecteurs vidéo standard, mais les sites web peuvent modifier leurs lecteurs, la gestion des DRM ou les restrictions du navigateur à tout moment.<br><br>KoalaSync n'est affilié ni à Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex, ni à aucune autre marque mentionnée ici. Ces noms sont utilisés uniquement pour illustrer des cas d'utilisation courants.",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync n'est pas magique et ne cherche pas à cacher ses limites. Il ne fonctionne actuellement que sur les navigateurs de bureau. Il inclut un chat textuel chiffré de bout en bout, mais pas de chat vocal, vidéo ou webcam, et il n'a pas d'application mobile.<br><br>La compatibilité dépend également du navigateur et du lecteur vidéo. KoalaSync devrait fonctionner avec presque tous les lecteurs vidéo standard, mais les sites web peuvent modifier leurs lecteurs, la gestion des DRM ou les restrictions du navigateur à tout moment.<br><br>KoalaSync n'est affilié ni à Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex, ni à aucune autre marque mentionnée ici. Ces noms sont utilisés uniquement pour illustrer des cas d'utilisation courants.",
"ALT_TELEPARTY_SUMMARY_TITLE": "Résumé",
"ALT_TELEPARTY_SUMMARY_BODY": "Si vous recherchez une fonction de chat intégrée ou une prise en charge mobile, Teleparty pourrait être la meilleure option. Si vous préférez une solution gratuite et open source de synchronisation via navigateur (sur ordinateur) compatible avec presque tous les lecteurs vidéo classiques, y compris les serveurs multimédias auto-hébergés comme Emby, Jellyfin et Plex, alors KoalaSync est probablement le choix idéal.",
"ALT_TELEPARTY_SUMMARY_BODY": "Les deux outils proposent un chat textuel. Si vous avez besoin d'une prise en charge mobile ou préférez l'expérience de watch party plus intégrée de Teleparty, ce dernier peut être le meilleur choix. Si vous recherchez une synchronisation gratuite et open source pour navigateurs de bureau, avec chat en direct chiffré de bout en bout et compatibilité avec presque tous les lecteurs vidéo classiques, y compris les serveurs auto-hébergés Emby, Jellyfin et Plex, KoalaSync est probablement le meilleur choix.",
"ALT_SCREEN_TITLE": "KoalaSync vs partage d'écran : quand la synchronisation vidéo est-elle préférable ?",
"ALT_SCREEN_META_DESC": "Une comparaison pratique entre KoalaSync et le partage d'écran pour les soirées cinéma à distance : contrôle de la lecture, qualité vidéo, confidentialité, sous-titres et visionnage via navigateur.",
"ALT_SCREEN_INTRO": "Le partage d'écran fonctionne et, honnêtement, cela suffit parfois. Une personne partage son écran, les autres regardent, et le tour est joué.<br><br>Cependant, pour des soirées cinéma plus longues ou pour regarder des épisodes ensemble, cela peut devenir contraignant. La qualité vidéo dépend de la vitesse d'envoi (upload), du processeur et du navigateur de la personne qui partage, ainsi que de la compression appliquée par Discord, Teamspeak, Zoom ou toute autre application d'appel. De plus, une seule personne contrôle réellement la lecture.",
@@ -221,61 +222,61 @@
"ALT_SCREEN_WHY_CLEANER_TITLE": "Pourquoi KoalaSync offre une expérience plus fluide",
"ALT_SCREEN_WHY_CLEANER_BODY": "KoalaSync ne diffuse pas votre écran. Chacun regarde la vidéo localement dans son propre navigateur, et KoalaSync se contente de synchroniser les actions de lecture telles que le lancement, la pause et la navigation dans la vidéo.<br><br>Ainsi, chacun peut passer en plein écran sur son appareil, utiliser ses propres paramètres de sous-titres ou audio (si le lecteur le permet) et gérer la discussion vocale séparément via Discord, Teamspeak ou un autre service d'appel.<br><br>Pour les soirées cinéma, cette approche est souvent plus agréable que la diffusion d'écran par une seule personne. C'est particulièrement utile pour les solutions basées sur un navigateur comme Emby, Jellyfin, Plex, les fichiers locaux ouverts dans le navigateur, les domaines privés ou tout autre site web utilisant des lecteurs vidéo standard.",
"ALT_SCREEN_LIMITS_TITLE": "Limites à connaître",
"ALT_SCREEN_LIMITS_BODY": "KoalaSync ne remplace pas un appel vocal. Si vous souhaitez discuter tout en regardant, vous continuez à utiliser Discord, Teamspeak, Signal, WhatsApp ou tout autre outil habituel.<br><br>Chacun doit également pouvoir accéder à la vidéo ou à la page depuis son propre appareil. KoalaSync ne contourne ni les abonnements, ni les identifiants, ni les DRM, ni les restrictions géographiques ou les règles des plateformes.<br><br>L'outil fonctionne actuellement uniquement sur les navigateurs de bureau, ne dispose pas de fonction de chat ou de voix intégrée, et la compatibilité dépend toujours du navigateur et du lecteur vidéo utilisé.",
"ALT_SCREEN_LIMITS_BODY": "KoalaSync inclut un chat textuel chiffré de bout en bout, mais il ne remplace pas un appel vocal ou vidéo. Si vous souhaitez parler tout en regardant, vous utilisez toujours Discord, Teamspeak, Signal, WhatsApp ou tout ce que vous utilisez habituellement.<br><br>Chacun doit également pouvoir accéder à la vidéo ou à la page depuis son propre appareil. KoalaSync ne contourne ni les abonnements, ni les identifiants, ni les DRM, ni les restrictions géographiques ou les règles des plateformes.<br><br>Il s'agit actuellement d'un navigateur de bureau uniquement, n'a pas de chat vocal, vidéo ou webcam intégré, et la compatibilité dépend toujours du navigateur et du lecteur vidéo.",
"ALT_SCREEN_SUMMARY_TITLE": "Résumé",
"ALT_SCREEN_SUMMARY_BODY": "Le partage d'écran est la solution la plus simple si vous cherchez une méthode rapide sans accorder une grande importance à la qualité, à la confidentialité ou au contrôle individuel. KoalaSync est plus adapté lorsque chaque participant doit visionner le contenu localement dans son propre navigateur, pour une expérience se rapprochant davantage d'un véritable visionnage en commun.",
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether : quelle solution de watch party vous convient le mieux ?",
"ALT_W2G_META_DESC": "Une comparaison honnête entre KoalaSync et Watch2Gether. Découvrez quand la flexibilité de KoalaSync est imbattable et pourquoi Watch2Gether reste le premier choix pour YouTube.",
"ALT_W2G_META_DESC": "Comparaison pratique de KoalaSync et Watch2Gether : salons partagés, playlists, synchronisation du lecteur et chat chiffré.",
"ALT_W2G_INTRO": "KoalaSync et Watch2Gether résolvent tous deux le même problème : regarder des vidéos synchronisées avec des amis en ligne sans coordination manuelle. Alors que Watch2Gether est l'un des hubs les plus anciens et les plus populaires pour les soirées cinéma partagées, KoalaSync adopte une approche légère et complètement différente. Le choix de l'outil qui vous convient dépend fortement de ce que vous souhaitez regarder et de l'endroit où vous le souhaitez.",
"ALT_W2G_WHEN_W2G_TITLE": "Quand Watch2Gether est le meilleur choix",
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether est imbattable pour les <strong>soirées de visionnage sur YouTube</strong>. Grâce à sa liste de lecture et à sa recherche intégrées, n'importe qui dans la pièce peut facilement trouver, ajouter et lire de nouvelles vidéos. Tout le monde regarde la vidéo suivante ensemble de manière transparente.<br><br>Avec KoalaSync sur YouTube, la lecture automatique par défaut du site prend le relais, redirigeant chaque utilisateur vers une vidéo recommandée différente. Notre fonctionnalité automatique de prochain épisode (« Episode Lobby »), qui fonctionne très bien sur Netflix ou Disney+, ne fonctionne pas sur YouTube en raison de ces recommandations dynamiques. Pour les pures soirées YouTube, Watch2Gether est le meilleur choix.",
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether peut mieux convenir aux sessions YouTube centrées sur un salon, une recherche et une playlist partagés. La compatibilité dépend du service et de la source actuels.",
"ALT_W2G_WHEN_KOALA_TITLE": "Quand KoalaSync s'adapte mieux",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync brille sur les <strong>vrais services de streaming</strong> (Netflix, Prime Video, Disney+) et les serveurs multimédias auto-hébergés (Jellyfin, Plex, Emby). Contrairement à Watch2Gether, qui ne peut souvent pas lire du contenu premium, KoalaSync synchronise le lecteur natif directement sur le site Web.<br><br>Il fonctionne sur presque tous les sites dotés d'un lecteur vidéo HTML5 standard. Vous n'avez pas besoin de créer une salle virtuelle sur un site tiers : activez simplement l'extension, rejoignez un lobby et commencez à regarder.",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync convient si vous souhaitez synchroniser le lecteur d'origine sur une page compatible, y compris des médias auto-hébergés, sans déplacer la lecture vers un autre site de visionnage partagé. La compatibilité dépend toujours du site, du lecteur et du navigateur.",
"ALT_W2G_MAIN_DIFF_TITLE": "La principale différence",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether est un <strong>site Web autonome</strong> avec chat, listes de lecture et prise en charge par webcam intégrés. KoalaSync est une <strong>extension de navigateur minimaliste</strong> qui se connecte directement aux onglets normaux de votre navigateur.<br><br>Vous recherchez un package tout-en-un pour YouTube avec chat intégré ? Utilisez Watch2Gether. Vous souhaitez regarder des films sur Netflix ou sur votre propre serveur sans distractions, en qualité native (pas de compression du serveur) ? KoalaSync est la solution la plus propre.",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether est un <strong>site Web autonome</strong> avec chat textuel, listes de lecture, recherche et prise en charge de la webcam. KoalaSync est une <strong>extension de navigateur minimaliste</strong> qui s'intègre directement dans les onglets normaux de votre navigateur et inclut également un chat textuel en direct chiffré de bout en bout.<br><br>Vous recherchez un package tout-en-un pour YouTube avec des playlists, des fonctionnalités de recherche et de webcam ? Utilisez Watch2Gether. Vous souhaitez regarder des films sur Netflix ou sur votre propre serveur sans distractions, en qualité native (pas de compression du serveur) ? KoalaSync est la solution la plus propre.",
"ALT_W2G_LIMITS_TITLE": "Limites honnêtes",
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>n'a pas de listes de lecture intégrées ni de recherche de vidéos</strong>. Sur YouTube, cela signifie que la lecture automatique de la vidéo suivante ne restera pas synchronisée.<br><br>De plus, il n'y a <strong>pas de chat intégré</strong> et KoalaSync ne fonctionne actuellement que sur les navigateurs de bureau. Il est conçu en supposant que vous parlez déjà via Discord, Teamspeak ou d'autres outils vocaux de toute façon.",
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>n'a pas de listes de lecture intégrées ni de recherche de vidéos</strong>. Sur YouTube, cela signifie que la lecture automatique de la vidéo suivante ne restera pas synchronisée.<br><br>KoalaSync inclut un chat textuel en direct chiffré de bout en bout, mais pas de webcam, de chat vocal ou vidéo intégré, et il ne fonctionne actuellement que sur les navigateurs de bureau. Si vous souhaitez vous parler ou vous voir, vous avez toujours besoin de Discord, Teamspeak ou d'un autre outil d'appel.",
"ALT_W2G_SUMMARY_TITLE": "En bref",
"ALT_W2G_SUMMARY_BODY": "Pour les sessions YouTube spontanées avec listes de lecture et chat intégré, Watch2Gether est le meilleur choix. Pour les plateformes de streaming premium (comme Netflix, Prime Video, Disney+), les serveurs auto-hébergés (Jellyfin, Plex, Emby) et une flexibilité maximale du navigateur, KoalaSync est l'outil le plus adapté et le plus léger.",
"ALT_W2G_SUMMARY_BODY": "Les deux outils incluent le chat textuel. Pour les sessions YouTube spontanées avec listes de lecture, fonctionnalités de recherche et de webcam, Watch2Gether est le meilleur choix. Pour les plateformes de streaming premium (comme Netflix, Prime Video, Disney+), les serveurs auto-hébergés (Jellyfin, Plex, Emby), le chat en direct chiffré de bout en bout et la flexibilité maximale du navigateur, KoalaSync est l'outil le plus adapté et le plus léger.",
"ALT_SCENER_TITLE": "KoalaSync vs Scener : quelle plateforme de watch party convient le mieux ?",
"ALT_SCENER_META_DESC": "Une comparaison honnête de KoalaSync et Scener. Découvrez quand le chat par webcam de Scener l'emporte et pourquoi KoalaSync est le meilleur pour des soirées cinéma sans distraction.",
"ALT_SCENER_INTRO": "KoalaSync et Scener abordent les soirées de visionnage sous deux angles complètement différents. Scener est une salle de cinéma virtuelle avec chat vidéo intégré et salles interactives. KoalaSync, quant à lui, est une extension de navigateur minimaliste et respectueuse de la vie privée qui synchronise simplement les commandes de lecture. Loutil qui vous convient le mieux dépend principalement de limportance pour vous des fonctionnalités de webcam et de chat intégrées.",
"ALT_SCENER_INTRO": "KoalaSync et Scener abordent les soirées de visionnage sous deux angles complètement différents. Scener est une salle de cinéma virtuelle avec chat vidéo intégré et salles interactives. KoalaSync, quant à lui, est une extension de navigateur minimaliste et respectueuse de la vie privée qui synchronise les commandes de lecture et inclut un chat textuel en direct chiffré de bout en bout. L'outil qui vous convient le mieux dépend principalement de si vous avez besoin de fonctionnalités intégrées de webcam, de voix, de vidéo et de réseaux sociaux ou si vous préférez un outil de synchronisation léger.",
"ALT_SCENER_WHEN_SCENER_TITLE": "Quand Scener est le meilleur choix",
"ALT_SCENER_WHEN_SCENER_BODY": "Scener est un choix judicieux si vous souhaitez une <strong>plateforme sociale tout-en-un</strong> avec chat vidéo et audio intégré directement dans votre navigateur. Scener crée une salle virtuelle où les webcams du joueur et de vos amis sont affichées côte à côte. Cest idéal pour les grandes soirées publiques ou les événements virtuels en streaming où laspect social est au premier plan. Cependant, gardez à l'esprit que la version de base gratuite est financée par la publicité et que Scener verrouille de nombreuses fonctionnalités (telles que la visualisation sans publicité et le chat vidéo illimité) derrière un abonnement mensuel (5,79 $/mois ou 39,99 $/an).",
"ALT_SCENER_WHEN_SCENER_BODY": "Scener convient mieux si vous recherchez une expérience sociale intégrée avec chat textuel et, pour les membres Premium, chat vidéo et audio. Sur ordinateur, une sélection définie de services est prise en charge via l'extension Chrome.",
"ALT_SCENER_WHEN_KOALA_TITLE": "Quand KoalaSync s'adapte mieux",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync brille en matière de <strong>qualité vidéo, confidentialité et simplicité</strong> :<br><br>• <strong>Aucun compte</strong> : vous n'avez pas besoin de vous inscrire, de vous connecter ou de gérer des profils. Activez simplement l'extension, partagez un code de salle et commencez à regarder.<br>• <strong>Véritable qualité native (4K/HDR)</strong> : Scener diffuse des vidéos compressées à partir d'un serveur cloud virtuel, ce qui peut entraîner une perte de qualité et un retard. KoalaSync synchronise le lecteur natif directement sur votre ordinateur, ce qui signifie que vous regardez des films dans la pleine résolution de votre abonnement de streaming.<br>• <strong>Faible utilisation de la bande passante</strong> : étant donné que seules les données de contrôle (lecture, pause, horodatage) sont transmises, KoalaSync n'utilise pratiquement aucune bande passante supplémentaire. Scener diffuse l'intégralité des flux vidéo et des caméras sur des serveurs cloud, ce qui peut mettre à rude épreuve les connexions Internet plus lentes et les ordinateurs portables plus anciens.<br>• <strong>100 % gratuit et sans publicité</strong> : pas de frais d'abonnement mensuels, pas de publicité et pas de trackers.<br>• <strong>Fichiers vidéo locaux</strong> : étant donné que KoalaSync fonctionne localement, vous pouvez même regarder vos propres fichiers vidéo (comme MP4) en synchronisation avec vos amis. Faites simplement glisser et déposez le fichier dans un onglet du navigateur Scener ne peut pas le faire car son lecteur fonctionne dans le cloud.",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync convient si vous préférez une synchronisation open source sans compte dans le lecteur d'origine, un chat textuel en direct chiffré de bout en bout et un relay auto-hébergé facultatif. Il n'inclut aucun chat vocal, vidéo ou par webcam.",
"ALT_SCENER_MAIN_DIFF_TITLE": "La principale différence",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener est un <strong>réseau social commercial</strong> pour les soirées de visionnage, alimenté par des navigateurs cloud virtuels et financé par des publicités et des abonnements mensuels. KoalaSync est un <strong>utilitaire de synchronisation gratuit et axé sur la confidentialité</strong> qui se connecte directement aux onglets de votre navigateur. Il laisse le chat vocal/vidéo à des applications comme Discord ou Teamspeak, se concentrant entièrement sur le maintien de votre lecture fluide, privée et dans sa qualité d'origine native.",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener privilégie les salons intégrés et les fonctions sociales. KoalaSync privilégie une synchronisation légère dans le lecteur d'origine et un chat textuel en direct chiffré de bout en bout.",
"ALT_SCENER_LIMITS_TITLE": "Limites honnêtes",
"ALT_SCENER_LIMITS_BODY": "KoalaSync n'offre <strong>pas de support intégré pour le chat ou la webcam</strong>. Il s'agit uniquement d'un outil de synchronisation et nécessite un outil d'appel vocal distinct de votre choix. De plus, KoalaSync est actuellement uniquement disponible sur ordinateur et la compatibilité des fichiers vidéo locaux dépend entièrement de la prise en charge native par votre navigateur de la lecture du format vidéo et des codecs audio.",
"ALT_SCENER_LIMITS_BODY": "KoalaSync propose un <strong>chat textuel en direct chiffré de bout en bout, mais pas de chat webcam, vocal ou vidéo</strong>. Se parler ou se voir nécessite donc un outil d'appel distinct de votre choix. De plus, KoalaSync est actuellement uniquement disponible sur ordinateur et la compatibilité des fichiers vidéo locaux dépend entièrement de la prise en charge native par votre navigateur de la lecture du format vidéo et des codecs audio.",
"ALT_SCENER_SUMMARY_TITLE": "En bref",
"ALT_SCENER_SUMMARY_BODY": "Pour les grandes soirées de visionnage avec chat par webcam intégré et fonctionnalités sociales, Scener est le bon choix, à condition que les publicités et les paywalls premium ne vous dérangent pas. Si vous souhaitez une expérience entièrement gratuite, sans publicité et sans distraction dans le lecteur natif (y compris Netflix, Prime Video, Disney+ ou les fichiers du navigateur local) et que vous êtes déjà en appel vocal, KoalaSync est la solution la plus propre et la plus performante.",
"ALT_SCENER_SUMMARY_BODY": "Choisissez Scener pour ses fonctions sociales, vidéo et audio intégrées sur les services compatibles. Choisissez KoalaSync pour la synchronisation open source sans compte, le chat chiffré en direct et l'auto-hébergement facultatif.",
"ALT_KOSMI_TITLE": "KoalaSync vs Kosmi : Quelle alternative de fête Netflix vous convient le mieux ?",
"ALT_KOSMI_META_DESC": "Une comparaison honnête de KoalaSync et Kosmi. Découvrez pourquoi la configuration sans extension de Kosmi est pratique et quand la qualité vidéo native de KoalaSync l'emporte.",
"ALT_KOSMI_INTRO": "KoalaSync et Kosmi offrent tous deux d'excellents moyens de regarder Netflix, YouTube et d'autres plateformes en synchronisation avec des amis. Cependant, leurs technologies sous-jacentes sont fondamentalement différentes. Kosmi utilise des navigateurs cloud virtuels qui s'exécutent directement dans votre navigateur Web sans installer d'extensions. KoalaSync est une extension légère qui synchronise votre lecteur vidéo local. L'approche qui vous convient le mieux dépend de vos priorités en matière de qualité vidéo, de commodité et de confidentialité.",
"ALT_KOSMI_INTRO": "KoalaSync et Kosmi permettent de regarder ensemble, mais ce sont des produits distincts avec des services et fonctions sociales différents. KoalaSync synchronise un lecteur compatible dans un navigateur de bureau. Vérifiez la prise en charge actuelle de chaque service.",
"ALT_KOSMI_WHEN_KOSMI_TITLE": "Quand Kosmi est le meilleur choix",
"ALT_KOSMI_WHEN_KOSMI_BODY": "Kosmi est imbattable si vous voulez <strong>éviter d'installer quoi que ce soit</strong> ou si vous souhaitez regarder sur des appareils comme les tablettes et les smartphones. Étant donné que Kosmi exécute des navigateurs virtuels dans le cloud, il suffit pour s'inscrire d'un simple lien Web. De plus, Kosmi est une plate-forme de rencontre tout-en-un : en plus du streaming synchronisé, vous pouvez jouer à des jeux multijoueurs (comme les classiques rétro NES/SNES ou le poker) et utiliser la webcam, le chat vocal et textuel intégrés. Si vous souhaitez organiser une soirée de jeux sociaux et regarder une soirée sans configuration, Kosmi est une solution fantastique.",
"ALT_KOSMI_WHEN_KOSMI_BODY": "Kosmi peut mieux convenir pour ses salons sociaux dans le navigateur, ses jeux et ses fonctions de communication intégrées. La disponibilité peut varier selon lappareil, la source et loffre actuelle.",
"ALT_KOSMI_WHEN_KOALA_TITLE": "Quand KoalaSync s'adapte mieux",
"ALT_KOSMI_WHEN_KOALA_BODY": "KoalaSync brille en matière de <strong>qualité de lecture native, sécurité et performances</strong> :<br><br>• <strong>Aucun risque de sécurité</strong> : avec Kosmi, vous devez vous connecter à votre compte Netflix ou Prime sur un ordinateur virtuel distant dans le cloud. Avec KoalaSync, vous vous connectez en toute sécurité sur le site officiel de streaming dans votre propre navigateur, comme vous le faites habituellement. Vos mots de passe ne quittent jamais votre ordinateur.<br>• <strong>Qualité vidéo complète (4K/HDR)</strong> : Kosmi diffuse l'écran du navigateur virtuel sous forme de flux vidéo compressé, ce qui peut entraîner une pixellisation et une mise en mémoire tampon. KoalaSync synchronise les lecteurs natifs localement : vous profitez de films dans la résolution complète et cristalline de votre abonnement.<br>• <strong>Sans décalage et respectueux de la bande passante</strong> : étant donné que KoalaSync envoie uniquement des commandes de contrôle (lecture/pause/horodatage), il n'utilise presque aucune bande passante. Kosmi diffuse constamment des flux vidéo HD, ce qui peut surcharger les connexions plus lentes.<br>• <strong>Fichiers vidéo locaux</strong> : si vous souhaitez regarder une vidéo privée, faites simplement glisser et déposez le fichier MP4 dans un onglet du navigateur et synchronisez-le. Kosmi ne peut pas accéder aux fichiers sur votre disque dur local.",
"ALT_KOSMI_WHEN_KOALA_BODY": "KoalaSync convient si vous souhaitez synchroniser un lecteur compatible dans votre navigateur de bureau, utiliser un chat textuel en direct chiffré de bout en bout ou autohéberger le relay. Il ninclut ni appels ni application mobile.",
"ALT_KOSMI_MAIN_DIFF_TITLE": "La principale différence",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmi est un <strong>hangout virtuel dans le cloud</strong> qui diffuse l'écran d'un navigateur distant afin que personne n'ait besoin d'installer une extension. KoalaSync est une <strong>couche de synchronisation du navigateur local</strong> qui se connecte directement au lecteur natif sur le site officiel de streaming. Alors que Kosmi est plus polyvalent pour les mini-jeux et la compatibilité des appareils, KoalaSync offre une expérience de streaming plus sécurisée, fluide et plus précise dans le lecteur d'origine.",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmi privilégie un salon social tout-en-un. KoalaSync est une extension de bureau qui synchronise les lecteurs compatibles et propose un chat textuel chiffré en direct.",
"ALT_KOSMI_LIMITS_TITLE": "Limites honnêtes",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync <strong>nécessite l'installation d'une extension de navigateur</strong> et ne fonctionne actuellement que sur les ordinateurs de bureau. Il <strong>n'a pas de chat ni de webcams intégrés</strong>, car il est conçu en supposant que vous parlez déjà via Discord ou d'autres outils vocaux de toute façon. Il ne propose pas non plus de jeux intégrés.",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync <strong>nécessite l'installation d'une extension de navigateur</strong> et ne fonctionne actuellement que sur les ordinateurs de bureau. Il inclut un <strong>chat textuel en direct chiffré de bout en bout, mais pas de chat webcam, vocal ou vidéo</strong>. Il ne propose pas non plus de jeux intégrés.",
"ALT_KOSMI_SUMMARY_TITLE": "En bref",
"ALT_KOSMI_SUMMARY_BODY": "Pour des soirées de jeux décontractées avec des classiques rétro et un accès facile sur les appareils mobiles sans aucune installation, Kosmi est idéal. Pour les soirées cinéma premium sur Netflix, Prime Video, Disney+ ou Plex en qualité 4K complète, avec une sécurité maximale et un impact minimal sur la bande passante, KoalaSync est clairement le gagnant.",
"ALT_TWOSEVEN_TITLE": "KoalaSync vs TwoSeven : quelle solution de soirée de surveillance convient le mieux ?",
"ALT_TWOSEVEN_META_DESC": "Une comparaison honnête de KoalaSync et TwoSeven. Découvrez quand la webcam intégrée de TwoSeven gagne et pourquoi KoalaSync est le meilleur pour Disney+ et Netflix.",
"ALT_TWOSEVEN_INTRO": "KoalaSync et TwoSeven utilisent la même approche de base pour les séances de visionnage : ils synchronisent le lecteur vidéo d'origine directement dans votre navigateur, plutôt que de diffuser un flux vidéo compressé depuis le cloud. Cela garantit une qualité vidéo de premier ordre pour les deux outils. Cependant, même si TwoSeven est un portail commercial avec chat par webcam et niveaux d'abonnement intégrés, KoalaSync reste une extension minimaliste, entièrement gratuite et respectueuse de la vie privée. Découvrez ici quel service vous convient le mieux.",
"ALT_KOSMI_SUMMARY_BODY": "Choisissez Kosmi pour ses fonctions sociales intégrées. Choisissez KoalaSync pour la synchronisation open source sans compte sur ordinateur, le chat chiffré et lauto-hébergement facultatif.",
"ALT_TWOSEVEN_TITLE": "KoalaSync vs TwoSeven : quelle solution de visionnage partagé vous convient le mieux ?",
"ALT_TWOSEVEN_META_DESC": "Comparaison pratique de KoalaSync et TwoSeven : comptes, communication intégrée, chat chiffré et auto-hébergement.",
"ALT_TWOSEVEN_INTRO": "Les deux outils permettent un visionnage synchronisé, mais leurs services et conditions peuvent changer. TwoSeven fonctionne avec un compte et propose des fonctions de communication intégrées. KoalaSync ne demande pas de compte, offre un chat textuel chiffré et nintègre pas les appels.",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_TITLE": "Quand TwoSeven est le meilleur choix",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "TwoSeven est le bon choix si vous souhaitez une <strong>solution tout-en-un avec webcam et chat audio intégrés</strong> directement dans votre navigateur. Si vous souhaitez voir vos amis tout en regardant sans avoir à exécuter Discord ou Teamspeak en arrière-plan, TwoSeven propose cette intégration de manière native. De plus, il fonctionne pour YouTube et quelques lecteurs vidéo Web ouverts sans extensions. Il convient bien aux petits groupes de 2 à 3 personnes qui souhaitent partager une connexion visuelle tout en visionnant.",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "TwoSeven peut mieux convenir si la webcam et laudio intégrés sont importants. Vérifiez ses exigences actuelles concernant le navigateur, le compte, loffre et les services pris en charge.",
"ALT_TWOSEVEN_WHEN_KOALA_TITLE": "Quand KoalaSync s'adapte mieux",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync brille par son <strong>absence totale de frais, de confidentialité et de simplicité</strong> :<br><br>• <strong>Aucun paywall</strong> : pour regarder des services premium comme Disney+ ou Hulu en synchronisation sur TwoSeven, au moins un membre de la salle doit les soutenir sur Patreon (à partir de 3 $/mois). KoalaSync synchronise Netflix, Prime Video, Disney+ et les serveurs multimédias auto-hébergés (Jellyfin, Plex, Emby) de manière 100 % gratuite et sans aucune restriction.<br>• <strong>Confidentialité absolue (pas de trackers/publicités)</strong> : TwoSeven est financé par la publicité et utilise plusieurs trackers de Google, Facebook et autres. KoalaSync n'a pas de publicité, pas de tracker et communique exclusivement avec le serveur relais RAM uniquement.<br>• <strong>Aucune inscription requise</strong> : TwoSeven demande à tous les participants de créer un compte et de se connecter. KoalaSync est totalement sans compte. Il vous suffit de l'installer, de saisir un code de pièce et de commencer à regarder.<br>• <strong>Léger et respectueux des ressources</strong> : la webcam et les flux audio intégrés de TwoSeven imposent une lourde charge sur votre processeur et votre réseau. En laissant la communication à des outils comme Discord, KoalaSync fonctionne de manière extrêmement fluide et préserve la durée de vie de la batterie.",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync convient si vous recherchez une synchronisation open source sans compte, un chat textuel en direct chiffré de bout en bout et la possibilité d'auto-héberger le relay. Les appels vocaux et webcam restent dans une application distincte.",
"ALT_TWOSEVEN_MAIN_DIFF_TITLE": "La principale différence",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven est une <strong>plate-forme commerciale</strong> avec des conditions d'inscription, des publicités, des discussions par webcam peer-to-peer et des paywalls Patreon pour des services de streaming spécifiques. KoalaSync est un <strong>utilitaire open source gratuit</strong> qui se connecte directement aux onglets de votre navigateur sans compte, tracker ou frais supplémentaires, permettant un visionnage sans distraction dans le lecteur natif.",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven associe le visionnage synchronisé à une plateforme avec comptes et fonctions webcam et audio intégrées. KoalaSync est une extension sans compte avec chat textuel en direct chiffré ; les appels restent séparés.",
"ALT_TWOSEVEN_LIMITS_TITLE": "Limites honnêtes",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync n'offre <strong>pas de webcam ni de chat audio intégré</strong> et suppose que vous êtes déjà en communication via Discord ou un outil similaire. De plus, KoalaSync n'a pas de superposition de boîte de discussion intégrée et est actuellement uniquement disponible sur ordinateur.",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync n'offre <strong>pas de webcam ni de chat audio intégré</strong>, donc pour se voir ou se parler, il faut toujours Discord ou un outil similaire. Il inclut une superposition de chat textuel en direct chiffré de bout en bout flottant et ancrable et est actuellement uniquement disponible sur ordinateur.",
"ALT_TWOSEVEN_SUMMARY_TITLE": "En bref",
"ALT_TWOSEVEN_SUMMARY_BODY": "Pour les petits groupes de deux ou trois personnes qui souhaitent se voir via des webcams directement dans le navigateur et qui ne craignent pas de prendre en charge le support Patreon pour Disney+, TwoSeven est une option intéressante. Pour des soirées cinéma sans publicité, entièrement gratuites et privées en qualité 4K native (que ce soit Netflix, Prime Video, Disney+ ou Jellyfin) sans inscription, KoalaSync est le choix le plus propre et le plus sûr.",
"ALT_TWOSEVEN_SUMMARY_BODY": "Choisissez TwoSeven si la webcam et l'audio intégrés sont prioritaires. Choisissez KoalaSync pour la synchronisation open source sans compte, le chat chiffré en direct et l'auto-hébergement facultatif.",
"ALT_TRY_KOALASYNC": "Essayer KoalaSync",
"MOCK_HOST_CONTROL_LABEL": "Contrôle de l'hôte",
"MOCK_HOST_CONTROL_TOGGLE": "Seul moi peut contrôler la lecture",
@@ -287,5 +288,9 @@
"SELF_SHOW_SETUP": "Afficher le guide d'installation",
"SELF_HIDE_SETUP": "Masquer le guide d'installation",
"HERO_TRUST": "Gratuit · Open source · Sans compte",
"COMP_MORE_GUIDES": "Comparatif complet et autres alternatives"
"COMP_MORE_GUIDES": "Comparatif complet et autres alternatives",
"NAV_HOME": "Accueil",
"ARIA_SELECT_LANGUAGE": "Choisir la langue",
"ARIA_MENU": "Menu",
"FOOTER_GUIDES": "Guides et comparatifs"
}
+51 -46
View File
@@ -16,8 +16,9 @@
"NAV_HOW_IT_WORKS": "Come Funziona",
"SUPPORT_BANNER_TEXT": "KoalaSync non riesce ad accedere al sito video?",
"SUPPORT_BANNER_CTA": "Ripristina laccesso al sito",
"SUPPORT_BANNER_DISMISS": "Chiudi avviso",
"HERO_TITLE": "Trasforma qualsiasi video in una <span class='hero-highlight'>watch party</span>.",
"HERO_SUBTITLE": "La tua serata film a distanza, senza lag. Niente account, niente tracciamento, niente pubblicità. Condividi un link e premete play insieme.",
"HERO_SUBTITLE": "Guarda video insieme a distanza. Nessun account, tracciamento o pubblicità. Condividi un link e iniziate insieme.",
"HERO_MASCOT_ALT": "Un simpatico koala che guarda i pulsanti di download",
"DEMO_SYNC": "Sincronizzato",
"DEMO_HINT": "Demo dal vivo: premi play in una delle schede e KoalaSync le mantiene sincronizzate.",
@@ -41,7 +42,7 @@
"USE_CASE_1_DESC": "Sincronizza i tuoi film in tempo reale e parla su Discord o Zoom. Sembrerà di essere nella stessa stanza.",
"USE_CASE_2_ALT": "Un professore koala che insegna a distanza",
"USE_CASE_2_TITLE": "Apprendimento a Distanza",
"USE_CASE_2_DESC": "Analizza tutorial o lezioni insieme a colleghi e compagni. Metti in pausa e discuti i passaggi più complessi in armonia.",
"USE_CASE_2_DESC": "Analizzate insieme tutorial, lezioni o video di formazione. Mettete in pausa e discutete i passaggi complessi.",
"USE_CASE_3_ALT": "Una coppia di koala che guarda video a distanza con dei cuori",
"USE_CASE_3_TITLE": "Relazioni a Distanza",
"USE_CASE_3_DESC": "Accorcia le distanze e goditi serate romantiche. Vivi ogni emozione e ridi alle stesse battute nello stesso identico istante.",
@@ -114,8 +115,8 @@
"STEP_2_ILLUS_CREATE": "+ Crea Nuova Stanza",
"STEP_2_ILLUS_MANUAL": "Manuale / Avanzato",
"STEP_2_ILLUS_COPIED": "Link copiato!",
"STEP_3_TITLE": "Godersi la sincronizzazione",
"STEP_3_DESC": "Avvia la riproduzione. Quando qualcuno mette in pausa, avanza o riproduce, il video di tutti rimarrà sincronizzato in tempo reale senza ritardi.",
"STEP_3_TITLE": "Guardate insieme",
"STEP_3_DESC": "Avvia la riproduzione. Le modifiche vengono condivise con tutti nella stanza; rete e lettore possono causare un breve ritardo.",
"STEP_3_ILLUS_IN_SYNC": "SINCRONIZZATI",
"SELF_TITLE": "Per utenti esperti",
"SELF_SUBTITLE": "Preferisci gestire i tuoi dati? Avvia il tuo server privato in pochi minuti.",
@@ -126,33 +127,33 @@
"BOTTOM_SUBTITLE": "KoalaSync è 100% open source, senza pubblicità o tracciamenti. Controlla il nostro codice su GitHub.",
"BOTTOM_MASCOT_ALT": "Un koala con Octocat, la mascotte di GitHub",
"FAQ_TITLE": "Domande Frequenti",
"FAQ_SUBTITLE": "Tutto quello che c'è da sapere per guardare video in perfetta sincronia.",
"FAQ_SUBTITLE": "Risposte su siti supportati, sincronizzazione, privacy, chat ed elaborazione audio.",
"FAQ_Q1": "Posso guardare Netflix o YouTube in sincronia?",
"FAQ_A1": "Certamente! KoalaSync funziona su Netflix, YouTube, Twitch e qualsiasi sito con video HTML5. Basta installare l'estensione, creare una stanza e condividere il link.",
"FAQ_A1": "KoalaSync funziona con molti siti supportati che usano un normale lettore video del browser, tra cui Netflix, YouTube, Twitch, Emby, Jellyfin e server multimediali self-hosted. La compatibilità dipende dal sito, dal browser e dal lettore e può cambiare dopo un aggiornamento.",
"FAQ_Q_VIDEO_ACCESS": "KoalaSync trasmette il video ai miei amici?",
"FAQ_A_VIDEO_ACCESS": "No. KoalaSync non trasmette, ospita, condivide o aggira laccesso ai contenuti video. Ogni persona guarda il video localmente nel proprio browser e deve avere il proprio accesso al sito o al servizio di streaming, ad esempio un proprio account Netflix. KoalaSync sincronizza solo le azioni di riproduzione come play, pausa e avanzamento.",
"FAQ_Q2": "KoalaSync è gratuito?",
"FAQ_A2": "Sì, al 100%. È open source con licenza MIT. Non ci sono funzioni a pagamento né pubblicità. A differenza di altre app, non raccogliamo dati utente.",
"FAQ_A2": "Sì. KoalaSync è gratuito e open source con licenza MIT, senza piano premium, funzioni bloccate, pubblicità o account obbligatorio.",
"FAQ_Q3": "Funziona con Emby o Jellyfin?",
"FAQ_A3": "Sì. KoalaSync è compatibile con qualsiasi player HTML5, inclusi server multimediali come Plex, Emby e Jellyfin.",
"FAQ_A3": "Sì. KoalaSync supporta molti lettori video standard usati da Emby, Jellyfin, Plex e configurazioni streaming self-hosted. La compatibilità dipende dal lettore. Puoi anche ospitare autonomamente il relay KoalaSync con Docker.",
"FAQ_Q4": "Quali browser sono supportati?",
"FAQ_A4": "Chrome, Firefox, Edge, Opera, Brave e tutti i browser basati su Chromium.",
"FAQ_Q5": "I miei amici devono installare l'estensione?",
"FAQ_A5": "Sì, ogni partecipante deve avere KoalaSync. Non serve registrarsi: basta cliccare sul link e si entra subito nella stanza.",
"FAQ_Q6": "I miei dati sono al sicuro?",
"FAQ_A6": "Assolutamente. Usiamo solo la RAM del server per inoltrare i segnali; nulla viene salvato in database o log. Quando chiudi la stanza, i dati spariscono per sempre.",
"FAQ_A6": "Il relay ufficiale di KoalaSync non salva in un database la cronologia delle stanze, della sincronizzazione, della visione o della chat. I dati di una stanza attiva restano temporaneamente in memoria e vengono eliminati quando la stanza è vuota o scade per inattività. Per la sicurezza e la prevenzione degli abusi possono essere generati log tecnici di connessione; la loro conservazione dipende dalla configurazione del server. I messaggi della chat sono crittografati end-to-end e non vengono salvati come cronologia lato server.",
"FAQ_Q7": "Posso migliorare l'audio dei film?",
"FAQ_A7": "Sì. KoalaSync include un compressore audio che rende le voci più chiare e attenua i rumori troppo forti. Lo trovi in Impostazioni → Elaborazione Audio.",
"FAQ_A7": "Sì. KoalaSync include un compressore audio che riduce la differenza tra le parti più basse e più forti del video. Funziona localmente tramite Web Audio API; laudio non viene inviato al relay. Attivalo in Impostazioni → Elaborazione audio.",
"HOWTO_TITLE": "Come sincronizzare i video con gli amici",
"HOWTO_DESC": "Guarda video in perfetta sincronia su YouTube, Netflix e Twitch. Senza account.",
"HOWTO_DESC": "Guarda insieme i video supportati su YouTube, Netflix, Twitch e altri siti. Senza registrazione o tracciamento.",
"HOWTO_STEP_1_NAME": "Installa l'estensione",
"HOWTO_STEP_1_TEXT": "Aggiungi KoalaSync al tuo browser dalle store ufficiali.",
"HOWTO_STEP_1_TEXT": "Installa KoalaSync dal Chrome Web Store o da Firefox Add-ons. Apri una pagina supportata e seleziona un video rilevato dallestensione.",
"HOWTO_STEP_2_NAME": "Crea una stanza",
"HOWTO_STEP_2_TEXT": "Apri l'estensione e crea una stanza. Copia il link di invito.",
"HOWTO_STEP_3_NAME": "Condividi e sincronizza",
"HOWTO_STEP_3_TEXT": "Invia il link ai tuoi amici e godetevi il film insieme.",
"FOOTER_MIT": "Codice aperto con licenza MIT.",
"FOOTER_RAM": "Nessun dato salvato. Retrasmissione basata su RAM.",
"FOOTER_RAM": "Nessuna cronologia chat o di visione. Nessun tracciamento. I log di accesso al sito vengono eliminati dopo 7 giorni.",
"FOOTER_LEGAL": "Note Legali",
"FOOTER_PRIVACY": "Privacy",
"MOCK_01": "Stanza Attiva",
@@ -190,7 +191,7 @@
"FOOTER_LEGAL_LINK": "imprint",
"FOOTER_PRIVACY_LINK": "privacy",
"FOOTER_DISCLAIMER": "KoalaSync non è affiliato a Netflix, YouTube o altre piattaforme. I marchi appartengono ai rispettivi proprietari.",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "Sostieni KoalaSync",
"ALT_INDEX_TITLE": "KoalaSync - Alternative e confronti",
"ALT_INDEX_META_DESC": "Scopri confronti onesti e guide dettagliate tra KoalaSync e altre estensioni per watch party e soluzioni di streaming.",
"ALT_INDEX_HEADING": "Guide e confronti",
@@ -200,17 +201,17 @@
"ALT_INDEX_FOOTER_LINK": "Guide e confronti",
"ALT_TELEPARTY_TITLE": "KoalaSync vs Teleparty: quale strumento scegliere per le watch party?",
"ALT_TELEPARTY_META_DESC": "Un confronto pratico tra KoalaSync e Teleparty per le watch party: chat integrata, supporto mobile, sincronizzazione video nel browser, server multimediali self-hosted e natura open-source.",
"ALT_TELEPARTY_INTRO": "Sia KoalaSync che Teleparty risolvono lo stesso fastidioso problema: guardare qualcosa insieme senza dover ricorrere alla classica procedura del '3, 2, 1, via'. Semplicemente, adottano approcci diversi.<br><br>Teleparty è la scelta ideale se cerchi una chat integrata o se vuoi guardare i contenuti da dispositivo mobile. KoalaSync è più indicato per le watch party su browser desktop, quando stai già comunicando tramite Discord, Teamspeak o un'altra chiamata vocale e vuoi semplicemente mantenere sincronizzata la riproduzione.<br><br>Il motivo principale per cui ho creato KoalaSync è la flessibilità. È progettato per funzionare con quasi tutti i siti web che utilizzano un normale lettore video del browser, non solo con una lista predefinita di piattaforme. Ciò include configurazioni self-hosted come Emby, Jellyfin e Plex, domini locali o privati e molte pagine web che ospitano video.",
"ALT_TELEPARTY_INTRO": "Sia KoalaSync che Teleparty risolvono lo stesso fastidioso problema: guardare qualcosa insieme senza dover ricorrere alla classica procedura del '3, 2, 1, via'. Semplicemente, adottano approcci diversi.<br><br>Teleparty è più sensato se desideri un'esperienza di watch-party più integrata o desideri guardare i video su dispositivi mobili. KoalaSync è progettato per partecipare a watch party su browser desktop attraverso una gamma più ampia di normali lettori video browser. La chat di testo integrata è crittografata end-to-end e funziona solo in tempo reale, mentre le chiamate vocali e video rimangono separate.<br><br>Il motivo principale per cui ho creato KoalaSync è la flessibilità. È progettato per funzionare con quasi tutti i siti web che utilizzano un normale lettore video del browser, non solo con una lista predefinita di piattaforme. Ciò include configurazioni self-hosted come Emby, Jellyfin e Plex, domini locali o privati e molte pagine web che ospitano video.",
"ALT_TELEPARTY_WHEN_TELE_TITLE": "Quando Teleparty può ancora essere la scelta giusta",
"ALT_TELEPARTY_WHEN_TELE_BODY": "Teleparty può rivelarsi la scelta migliore se desideri che la chat sia parte integrante dello strumento stesso per la watch party. Attualmente KoalaSync non dispone di una chat integrata; presuppone che tu stia già comunicando altrove, ad esempio su Discord, Teamspeak, WhatsApp, Signal o tramite una normale chiamata.<br><br>Il supporto mobile è un altro fattore chiave. Teleparty offre un'app dedicata, mentre KoalaSync funziona al momento solo su browser desktop. Se vuoi sincronizzare Netflix su smartphone o tablet, KoalaSync non è lo strumento adatto in questo momento.<br><br>Quindi, se la chat e il supporto mobile sono gli aspetti a cui tieni di più, Teleparty potrebbe adattarsi meglio alle tue esigenze.",
"ALT_TELEPARTY_WHEN_TELE_BODY": "Teleparty può comunque essere la scelta migliore se preferisci la sua esperienza di watch-party più integrata o hai bisogno di supporto mobile. Entrambi gli strumenti includono la chat di testo, quindi la sola chat non è più la differenza decisiva. La chat di KoalaSync è crittografata end-to-end e solo live; le chiamate vocali e le videochiamate utilizzano ancora un'app separata.<br><br>Il supporto mobile è un altro fattore chiave. Teleparty offre un'app dedicata, mentre KoalaSync funziona al momento solo su browser desktop. Se vuoi sincronizzare Netflix su smartphone o tablet, KoalaSync non è lo strumento adatto in questo momento.<br><br>Quindi, se il supporto mobile e una piattaforma più integrata sono le cose a cui tieni di più, Teleparty potrebbe semplicemente adattarsi meglio alla tua configurazione.",
"ALT_TELEPARTY_WHEN_KOALA_TITLE": "Quando KoalaSync è la scelta ideale",
"ALT_TELEPARTY_WHEN_KOALA_BODY": "KoalaSync è la soluzione ideale se cerchi uno strumento flessibile per sincronizzare la riproduzione video tra browser desktop, anziché una soluzione vincolata a una lista ristretta di piattaforme.<br><br>Funziona con quasi tutti i siti web che utilizzano i normali lettori video dei browser. È particolarmente utile per Emby, Jellyfin, Plex, server multimediali privati, domini locali o siti video minori che solitamente vengono ignorati dai classici strumenti per le sessioni di visione condivisa.<br><br>È inoltre gratuito, open source, non richiede la creazione di account e può essere ospitato autonomamente se desideri gestire il tuo relay. L'ho creato perché volevo una soluzione rispettosa della privacy che funzionasse con la mia configurazione Emby, non l'ennesima piattaforma chiusa limitata a pochi grandi servizi di streaming.",
"ALT_TELEPARTY_MAIN_DIFF_TITLE": "La differenza principale",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty è concepito principalmente come strumento per la visione condivisa, completo di chat e supporto mobile. KoalaSync, invece, agisce più come un livello flessibile di sincronizzazione per i video riprodotti nel browser.<br><br>Può sembrare una differenza da poco, ma cambia notevolmente le modalità d'uso. Se cerchi un'applicazione completa per la visione condivisa con chat integrata, Teleparty è la scelta giusta. Se invece disponi già di una chat vocale e vuoi semplicemente mantenere sincronizzati i controlli video tra diversi browser, KoalaSync è la soluzione più semplice ed efficace.",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty è più un prodotto watch-party integrato con supporto mobile. KoalaSync è un livello di sincronizzazione flessibile del browser desktop con chat di testo live crittografata end-to-end.<br><br>Sembra una piccola differenza, ma cambia molto il caso d'uso. Se hai bisogno di un'app mobile per watch-party, Teleparty ha senso. Se desideri un ampio supporto browser-player, compresi i media self-hosted, senza fornire messaggi di chat leggibili al relè, KoalaSync è la soluzione più semplice.",
"ALT_TELEPARTY_LIMITS_TITLE": "Limiti dichiarati apertamente",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync non fa miracoli e non cerca di nascondere i propri limiti. Attualmente funziona solo su browser desktop; non dispone di chat integrata né di un'app per dispositivi mobili.<br><br>La compatibilità dipende anche dal browser e dal lettore video utilizzato. KoalaSync dovrebbe funzionare con quasi tutti i normali lettori video dei browser, ma i siti web possono modificare in qualsiasi momento i propri lettori, la gestione dei DRM o le restrizioni del browser.<br><br>KoalaSync non è affiliato a Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex o qualsiasi altro marchio qui menzionato. I nomi sono utilizzati esclusivamente per illustrare i tipici scenari di utilizzo.",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync non è magico e non cerca di nascondere i suoi limiti. Attualmente funziona solo su browser desktop. Include chat di testo crittografata end-to-end, ma nessuna chat vocale, video o webcam e non ha un'app mobile.<br><br>La compatibilità dipende anche dal browser e dal lettore video utilizzato. KoalaSync dovrebbe funzionare con quasi tutti i normali lettori video dei browser, ma i siti web possono modificare in qualsiasi momento i propri lettori, la gestione dei DRM o le restrizioni del browser.<br><br>KoalaSync non è affiliato a Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex o qualsiasi altro marchio qui menzionato. I nomi sono utilizzati esclusivamente per illustrare i tipici scenari di utilizzo.",
"ALT_TELEPARTY_SUMMARY_TITLE": "Riepilogo",
"ALT_TELEPARTY_SUMMARY_BODY": "Se cerchi una chat integrata o il supporto per dispositivi mobili, Teleparty potrebbe essere la scelta più adatta. Se invece preferisci una soluzione gratuita e open-source per la sincronizzazione tra browser desktop, compatibile con quasi tutti i comuni lettori video, inclusi i media server self-hosted come Emby, Jellyfin e Plex. KoalaSync è probabilmente l'opzione migliore.",
"ALT_TELEPARTY_SUMMARY_BODY": "Entrambi gli strumenti includono la chat di testo. Se hai bisogno di supporto mobile o preferisci l'esperienza watch-party più integrata di Teleparty, Teleparty potrebbe essere la soluzione migliore. Se desideri una sincronizzazione gratuita e open source del browser desktop con una chat live crittografata end-to-end che funzioni con quasi tutti i normali lettori video, inclusi server multimediali self-hosted come Emby, Jellyfin e Plex, KoalaSync è probabilmente la soluzione migliore.",
"ALT_SCREEN_TITLE": "KoalaSync vs condivisione schermo: quando è meglio la sincronizzazione video?",
"ALT_SCREEN_META_DESC": "Un confronto pratico tra KoalaSync e la condivisione dello schermo per serate film a distanza, analizzando controllo della riproduzione, qualità video, privacy, sottotitoli e visione tramite browser.",
"ALT_SCREEN_INTRO": "La condivisione dello schermo funziona e, a dire il vero, a volte è sufficiente. Una persona condivide il proprio schermo, le altre guardano e il gioco è fatto.<br><br>Tuttavia, per serate film più lunghe o per guardare insieme degli episodi, può diventare scomoda. La qualità video dipende dalla velocità di upload, dalla CPU e dal browser di chi condivide, oltre che dalla compressione applicata da app come Discord, Teamspeak, Zoom o altre piattaforme di chiamata. Inoltre, solo una persona ha il controllo effettivo della riproduzione.",
@@ -221,61 +222,61 @@
"ALT_SCREEN_WHY_CLEANER_TITLE": "Perché KoalaSync offre un'esperienza più pulita",
"ALT_SCREEN_WHY_CLEANER_BODY": "KoalaSync non trasmette il tuo schermo. Ognuno guarda il video localmente sul proprio browser e KoalaSync si limita a sincronizzare le azioni di riproduzione, come avvio, pausa e scorrimento.<br><br>Questo permette a ciascuno di utilizzare la modalità a schermo intero sul proprio dispositivo, di impostare i sottotitoli o l'audio secondo le proprie preferenze (se il lettore lo consente) e di mantenere separata la chat vocale su Discord, Teamspeak o altre piattaforme.<br><br>Per le serate film, questa soluzione risulta molto più ordinata rispetto alla condivisione dello schermo da parte di una sola persona. È particolarmente utile per configurazioni basate su browser come Emby, Jellyfin, Plex, file locali aperti nel browser, domini privati o altri siti web dotati di lettori video standard.",
"ALT_SCREEN_LIMITS_TITLE": "Limiti reali",
"ALT_SCREEN_LIMITS_BODY": "KoalaSync non sostituisce una chiamata vocale. Se volete parlare mentre guardate il contenuto, potete continuare a usare Discord, Teamspeak, Signal, WhatsApp o qualsiasi altro strumento abituale.<br><br>Ciascun partecipante deve inoltre poter accedere al video o alla pagina dal proprio dispositivo. KoalaSync non aggira abbonamenti, accessi, DRM, restrizioni geografiche o regole delle piattaforme.<br><br>Al momento è utilizzabile solo tramite browser desktop, non dispone di funzioni di chat o voce integrate e la compatibilità dipende sempre dal browser e dal lettore video.",
"ALT_SCREEN_LIMITS_BODY": "KoalaSync include chat di testo crittografate end-to-end, ma non sostituisce una chiamata vocale o video. Se vuoi parlare mentre guardi, usi comunque Discord, Teamspeak, Signal, WhatsApp o qualunque cosa usi normalmente.<br><br>Ciascun partecipante deve inoltre poter accedere al video o alla pagina dal proprio dispositivo. KoalaSync non aggira abbonamenti, accessi, DRM, restrizioni geografiche o regole delle piattaforme.<br><br>Attualmente è solo per browser desktop, non ha chat vocale, video o webcam incorporata e la compatibilità dipende sempre dal browser e dal lettore video.",
"ALT_SCREEN_SUMMARY_TITLE": "Riepilogo",
"ALT_SCREEN_SUMMARY_BODY": "La condivisione dello schermo è l'opzione più semplice quando si cerca una soluzione rapida e non si dà molta importanza alla qualità, alla privacy o al controllo individuale. KoalaSync è la scelta ideale quando tutti devono guardare il contenuto localmente sul proprio browser, offrendo un'esperienza che ricorda più da vicino la visione condivisa reale.",
"ALT_W2G_TITLE": "KoalaSync vs Watch2Gether: quale soluzione per watch party è più adatta?",
"ALT_W2G_META_DESC": "Un confronto onesto tra KoalaSync e Watch2Gether. Scopri quando la flessibilità di KoalaSync è imbattibile e perché Watch2Gether è ancora la scelta migliore per YouTube.",
"ALT_W2G_META_DESC": "Confronto pratico tra KoalaSync e Watch2Gether: stanze condivise, playlist, sincronizzazione del lettore e chat crittografata.",
"ALT_W2G_INTRO": "KoalaSync e Watch2Gether risolvono entrambi lo stesso problema: guardare video sincronizzati con gli amici online senza coordinarsi manualmente. Mentre Watch2Gether è uno degli hub più antichi e popolari per le serate di cinema condivise, KoalaSync adotta un approccio completamente diverso e leggero. Lo strumento giusto per te dipende in larga misura da cosa e dove vuoi guardare.",
"ALT_W2G_WHEN_W2G_TITLE": "Quando Watch2Gether è la scelta migliore",
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether è imbattibile per i <strong>watch party di YouTube</strong>. Grazie alla playlist e alla ricerca integrate, chiunque nella stanza può trovare, aggiungere e riprodurre facilmente nuovi video. Tutti guardano insieme il video successivo senza interruzioni.<br><br>Con KoalaSync su YouTube, la riproduzione automatica predefinita del sito prende il sopravvento, reindirizzando ogni utente a un video consigliato diverso. La nostra funzione automatica per l'episodio successivo (\"Lobby episodi\"), che funziona perfettamente su Netflix o Disney+, non funziona su YouTube a causa di questi consigli dinamici. Per le serate puramente YouTube, Watch2Gether è la scelta migliore.",
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether può essere più adatto alle sessioni YouTube incentrate su stanza, ricerca e playlist condivise. Il supporto dipende dal servizio e dalla fonte attuali.",
"ALT_W2G_WHEN_KOALA_TITLE": "Quando KoalaSync si adatta meglio",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync eccelle sui <strong>servizi di streaming reali</strong> (Netflix, Prime Video, Disney+) e sui server multimediali self-hosted (Jellyfin, Plex, Emby). A differenza di Watch2Gether, che spesso non può riprodurre affatto contenuti premium, KoalaSync sincronizza il lettore nativo direttamente sul sito web.<br><br>Funziona su quasi tutti i siti con un lettore video HTML5 standard. Non è necessario configurare una stanza virtuale su un sito di terze parti: basta attivare l'estensione, unirsi a una lobby e iniziare a guardare.",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync è adatto se vuoi sincronizzare il player originale su una pagina compatibile, inclusi i media self-hosted, senza spostare la riproduzione su un altro sito per watch party. La compatibilità dipende comunque dal sito, dal player e dal browser.",
"ALT_W2G_MAIN_DIFF_TITLE": "La differenza principale",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether è un <strong>sito web autonomo</strong> con chat, playlist e supporto webcam integrati. KoalaSync è un'<strong>estensione del browser minimalista</strong> che si collega direttamente alle normali schede del browser.<br><br>Cerchi un pacchetto tutto in uno per YouTube con chat integrata? Usa Watch2Gether. Vuoi guardare film su Netflix o sul tuo server senza distrazioni, in qualità nativa (senza compressione del server)? KoalaSync è la soluzione più pulita.",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether è un <strong>sito web autonomo</strong> con chat di testo, playlist, ricerca e supporto per webcam. KoalaSync è un'<strong>estensione minimalista del browser</strong> che si collega direttamente alle normali schede del browser e include anche chat di testo live crittografata end-to-end.<br><br>Cerchi un pacchetto all-in-one per YouTube con playlist, ricerca e funzionalità webcam? Usa Watch2Gether. Vuoi guardare film su Netflix o sul tuo server senza distrazioni, in qualità nativa (senza compressione del server)? KoalaSync è la soluzione più pulita.",
"ALT_W2G_LIMITS_TITLE": "Limitazioni oneste",
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>non dispone di playlist integrate o ricerca video</strong>. Su YouTube, ciò significa che la riproduzione automatica del video successivo non rimarrà sincronizzata.<br><br>Inoltre, <strong>non è presente una chat integrata</strong> e KoalaSync attualmente funziona solo sui browser desktop. È progettato partendo dal presupposto che tu stia già parlando tramite Discord, Teamspeak o comunque altri strumenti vocali.",
"ALT_W2G_LIMITS_BODY": "KoalaSync <strong>non dispone di playlist integrate o ricerca video</strong>. Su YouTube, ciò significa che la riproduzione automatica del video successivo non rimarrà sincronizzata.<br><br>KoalaSync include chat di testo live crittografate end-to-end, ma non webcam integrata, chat vocale o video e attualmente funziona solo su browser desktop. Se vuoi parlare o vederti, hai ancora bisogno di Discord, Teamspeak o di un altro strumento di chiamata.",
"ALT_W2G_SUMMARY_TITLE": "Insomma",
"ALT_W2G_SUMMARY_BODY": "Per sessioni YouTube spontanee con playlist e chat integrata, Watch2Gether è la scelta migliore. Per piattaforme di streaming premium (come Netflix, Prime Video, Disney+), server self-hosted (Jellyfin, Plex, Emby) e la massima flessibilità del browser, KoalaSync è lo strumento più adatto e leggero.",
"ALT_W2G_SUMMARY_BODY": "Entrambi gli strumenti includono la chat di testo. Per sessioni YouTube spontanee con playlist, ricerca e funzionalità webcam, Watch2Gether è la scelta migliore. Per piattaforme di streaming premium (come Netflix, Prime Video, Disney+), server self-hosted (Jellyfin, Plex, Emby), chat live crittografata end-to-end e la massima flessibilità del browser, KoalaSync è lo strumento più adatto e leggero.",
"ALT_SCENER_TITLE": "KoalaSync vs Scener: quale piattaforma di watch party si adatta meglio?",
"ALT_SCENER_META_DESC": "Un confronto onesto tra KoalaSync e Scener. Scopri quando vince la chat webcam di Scener e perché KoalaSync è la soluzione migliore per serate di cinema senza distrazioni.",
"ALT_SCENER_INTRO": "KoalaSync e Scener si avvicinano alle feste da due angolazioni completamente diverse. Scener è un cinema virtuale con chat video integrata e stanze interattive. KoalaSync, d'altra parte, è un'estensione del browser minimalista e rispettosa della privacy che sincronizza semplicemente i controlli di riproduzione. Lo strumento più adatto a te dipende principalmente da quanto siano importanti per te le funzionalità di webcam e chat integrate.",
"ALT_SCENER_INTRO": "KoalaSync e Scener affrontano le watch party da due angolazioni completamente diverse. Scener è un cinema virtuale con chat video integrata e stanze interattive. KoalaSync, d'altra parte, è un'estensione del browser minimalista e rispettosa della privacy che sincronizza i controlli di riproduzione e include chat di testo live crittografata end-to-end. Lo strumento più adatto a te dipende principalmente dal fatto che tu abbia bisogno di webcam, voce, video e funzionalità social integrate o preferisci uno strumento di sincronizzazione leggero.",
"ALT_SCENER_WHEN_SCENER_TITLE": "Quando Scener è la scelta migliore",
"ALT_SCENER_WHEN_SCENER_BODY": "Scener è una scelta solida se desideri una <strong>piattaforma social tutto in uno</strong> con chat audio e video integrata direttamente nel tuo browser. Scener crea una stanza virtuale in cui il giocatore e le webcam dei tuoi amici vengono visualizzati fianco a fianco. Questo è l'ideale per grandi feste pubbliche o eventi di streaming virtuale in cui l'aspetto sociale è al centro dell'attenzione. Tuttavia, tieni presente che la versione base gratuita è supportata dalla pubblicità e Scener blocca molte funzionalità (come la visualizzazione senza pubblicità e la chat video illimitata) dietro un abbonamento mensile ($ 5,79 al mese o $ 39,99 all'anno).",
"ALT_SCENER_WHEN_SCENER_BODY": "Scener è più adatto se vuoi un'esperienza social integrata con chat testuale e, per gli utenti Premium, chat video e audio. Su computer supporta un elenco definito di servizi tramite l'estensione Chrome.",
"ALT_SCENER_WHEN_KOALA_TITLE": "Quando KoalaSync si adatta meglio",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync eccelle quando si tratta di <strong>qualità video, privacy e semplicità</strong>:<br><br>• <strong>Nessun account</strong>: non è necessario registrarsi, accedere o gestire i profili. Basta attivare l'estensione, condividere un codice della stanza e iniziare a guardare.<br>• <strong>Qualità nativa reale (4K/HDR)</strong>: Scener trasmette in streaming video compressi da un server cloud virtuale, il che può portare a perdita di qualità e ritardi. KoalaSync sincronizza il lettore nativo direttamente sul tuo computer, il che significa che guardi i film alla massima risoluzione del tuo abbonamento di streaming.<br>• <strong>Basso utilizzo della larghezza di banda</strong>: poiché vengono trasmessi solo i dati di controllo (riproduzione, pausa, timestamp), KoalaSync non utilizza praticamente alcuna larghezza di banda aggiuntiva. Scener trasmette in streaming l'intero video e i feed della fotocamera su server cloud, che possono mettere a dura prova le connessioni Internet più lente e i laptop più vecchi.<br>• <strong>100% gratuito e senza pubblicità</strong>: nessun costo di abbonamento mensile, nessuna pubblicità e nessun tracker.<br>• <strong>File video locali</strong>: poiché KoalaSync viene eseguito localmente, puoi persino guardare i tuoi file video (come MP4) sincronizzati con gli amici. Basta trascinare e rilasciare il file in una scheda del browser: Scener non può farlo perché il suo lettore viene eseguito nel cloud.",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync è adatto se preferisci sincronizzazione open source senza account nel player originale, chat testuale live crittografata end-to-end e un relay self-hosted facoltativo. Non include chat vocale, video o webcam.",
"ALT_SCENER_MAIN_DIFF_TITLE": "La differenza principale",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener è un <strong>social network commerciale</strong> per watch party basato su browser cloud virtuali e finanziato tramite annunci e abbonamenti mensili. KoalaSync è un'<strong>utilità di sincronizzazione gratuita, attenta alla privacy</strong> che si collega direttamente alle schede del tuo browser. Lascia la chat vocale/video ad applicazioni come Discord o Teamspeak, concentrandosi interamente sul mantenere la riproduzione fluida, privata e nella sua qualità originale nativa.",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener si concentra su stanze integrate e funzioni social. KoalaSync si concentra sulla sincronizzazione leggera nel player originale e sulla chat testuale live crittografata end-to-end.",
"ALT_SCENER_LIMITS_TITLE": "Limitazioni oneste",
"ALT_SCENER_LIMITS_BODY": "KoalaSync non offre <strong>nessun supporto integrato per chat o webcam</strong>. È puramente uno strumento di sincronizzazione e richiede uno strumento di chiamata vocale separato a tua scelta. Inoltre, KoalaSync è attualmente solo desktop e la compatibilità dei file video locali dipende interamente dal fatto che il tuo browser supporti nativamente la riproduzione del formato video e dei codec audio.",
"ALT_SCENER_LIMITS_BODY": "KoalaSync offre <strong>chat di testo live crittografata end-to-end, ma non webcam, chat vocale o video</strong>. Parlare o vedersi richiede quindi uno strumento di chiamata separato a tua scelta. Inoltre, KoalaSync è attualmente solo desktop e la compatibilità dei file video locali dipende interamente dal fatto che il tuo browser supporti nativamente la riproduzione del formato video e dei codec audio.",
"ALT_SCENER_SUMMARY_TITLE": "Insomma",
"ALT_SCENER_SUMMARY_BODY": "Per i grandi gruppi di spettatori con chat webcam integrata e funzionalità social, Scener è la scelta giusta, a condizione che non ti dispiaccia la pubblicità e i paywall premium. Se desideri un'esperienza completamente gratuita, senza pubblicità e senza distrazioni nel lettore nativo (inclusi Netflix, Prime Video, Disney+ o file del browser locale) e stai già effettuando una chiamata vocale, KoalaSync è la soluzione più pulita e performante.",
"ALT_SCENER_SUMMARY_BODY": "Scegli Scener per le funzioni social, video e audio integrate sui servizi supportati. Scegli KoalaSync per la sincronizzazione open source senza account, la chat live crittografata e l'opzione di self-hosting.",
"ALT_KOSMI_TITLE": "KoalaSync vs Kosmi: quale alternativa alla festa Netflix si adatta meglio?",
"ALT_KOSMI_META_DESC": "Un confronto onesto tra KoalaSync e Kosmi. Scopri perché la configurazione senza estensione di Kosmi è conveniente e quando vince la qualità video nativa di KoalaSync.",
"ALT_KOSMI_INTRO": "Sia KoalaSync che Kosmi offrono ottimi modi per guardare Netflix, YouTube e altre piattaforme in sincronia con gli amici. Tuttavia, le tecnologie sottostanti sono fondamentalmente diverse. Kosmi utilizza browser cloud virtuali che vengono eseguiti direttamente nel tuo browser web senza installare estensioni. KoalaSync è un'estensione leggera che sincronizza il tuo lettore video locale. L'approccio più adatto a te dipende dalle tue priorità in termini di qualità video, comodità e privacy.",
"ALT_KOSMI_INTRO": "KoalaSync e Kosmi permettono di guardare insieme, ma sono prodotti distinti con servizi e funzioni social differenti. KoalaSync sincronizza un lettore supportato nel browser desktop. Verifica il supporto attuale di ciascun servizio.",
"ALT_KOSMI_WHEN_KOSMI_TITLE": "Quando Kosmi è la scelta migliore",
"ALT_KOSMI_WHEN_KOSMI_BODY": "Kosmi è imbattibile se vuoi <strong>evitare di installare nulla</strong> o vuoi guardare su dispositivi come tablet e smartphone. Poiché Kosmi esegue browser virtuali nel cloud, per aderire è sufficiente un semplice collegamento Web. Inoltre, Kosmi è una piattaforma di ritrovo all-in-one: oltre allo streaming sincronizzato, puoi giocare a giochi multiplayer (come i classici retrò NES/SNES o il poker) e utilizzare la webcam integrata, la voce e la chat di testo. Se vuoi una serata di gioco sociale e guardare una festa senza installazione, Kosmi è una soluzione fantastica.",
"ALT_KOSMI_WHEN_KOSMI_BODY": "Kosmi può essere più adatto per le sue stanze social nel browser, i giochi e le funzioni di comunicazione integrate. La disponibilità può variare in base al dispositivo, alla fonte e allofferta attuale.",
"ALT_KOSMI_WHEN_KOALA_TITLE": "Quando KoalaSync si adatta meglio",
"ALT_KOSMI_WHEN_KOALA_BODY": "KoalaSync eccelle quando si tratta di <strong>qualità di riproduzione, sicurezza e prestazioni native</strong>:<br><br>• <strong>Nessun rischio per la sicurezza</strong>: con Kosmi, devi accedere al tuo account Netflix o Prime su un computer virtuale remoto nel cloud. Con KoalaSync, accedi in modo sicuro al sito ufficiale di streaming nel tuo browser, proprio come fai normalmente. Le tue password non lasciano mai il tuo computer.<br>• <strong>Qualità video completa (4K/HDR)</strong>: Kosmi trasmette in streaming lo schermo del browser virtuale come feed video compresso, il che può provocare pixelizzazione e buffering. KoalaSync sincronizza i lettori nativi localmente: potrai goderti i film con la risoluzione completa e cristallina del tuo abbonamento.<br>• <strong>Senza ritardi e con larghezza di banda ridotta</strong>: poiché KoalaSync invia solo comandi di controllo (riproduzione/pausa/timestamp), utilizza quasi zero larghezza di banda. Kosmi trasmette costantemente in streaming feed video HD, che possono sovraccaricare le connessioni più lente.<br>• <strong>File video locali</strong>: se desideri guardare un video privato, trascina e rilascia il file MP4 in una scheda del browser e sincronizzalo. Kosmi non può accedere ai file sul tuo disco rigido locale.",
"ALT_KOSMI_WHEN_KOALA_BODY": "KoalaSync è più adatto se vuoi sincronizzare un lettore supportato nel tuo browser desktop, usare chat testuale live crittografata end-to-end o ospitare il relay. Non include chiamate né unapp mobile.",
"ALT_KOSMI_MAIN_DIFF_TITLE": "La differenza principale",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmi è un <strong>hangout virtuale sul cloud</strong> che trasmette in streaming lo schermo di un browser remoto, quindi nessuno deve installare un'estensione. KoalaSync è un <strong>livello di sincronizzazione del browser locale</strong> che si collega direttamente al player nativo sul sito web di streaming ufficiale. Mentre Kosmi è più versatile per quanto riguarda i minigiochi e la compatibilità dei dispositivi, KoalaSync offre un'esperienza di streaming più sicura, fluida e nitida nel lettore originale.",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmi punta su una stanza social integrata. KoalaSync è unestensione desktop che sincronizza lettori supportati e offre chat testuale live crittografata.",
"ALT_KOSMI_LIMITS_TITLE": "Limitazioni oneste",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync <strong>richiede l'installazione di un'estensione del browser</strong> e attualmente funziona solo su PC desktop. <strong>Non ha chat o webcam integrate</strong>, poiché è progettato partendo dal presupposto che tu stia già parlando tramite Discord o altri strumenti vocali. Inoltre non offre giochi integrati.",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync <strong>richiede l'installazione di un'estensione del browser</strong> e attualmente funziona solo su PC desktop. Include <strong>chat di testo live crittografata end-to-end, ma non webcam, chat vocale o video</strong>. Inoltre non offre giochi integrati.",
"ALT_KOSMI_SUMMARY_TITLE": "Insomma",
"ALT_KOSMI_SUMMARY_BODY": "Per serate di gioco informali con classici retrò e un facile accesso su dispositivi mobili senza alcuna installazione, Kosmi è l'ideale. Per serate di cinema premium su Netflix, Prime Video, Disney+ o Plex in qualità 4K completa, con la massima sicurezza e un impatto minimo sulla larghezza di banda, KoalaSync è il chiaro vincitore.",
"ALT_KOSMI_SUMMARY_BODY": "Scegli Kosmi per le sue funzioni social integrate. Scegli KoalaSync per sincronizzazione desktop open source senza account, chat crittografata e relay self-hosted opzionale.",
"ALT_TWOSEVEN_TITLE": "KoalaSync vs TwoSeven: quale soluzione di watch party è più adatta?",
"ALT_TWOSEVEN_META_DESC": "Un confronto onesto tra KoalaSync e TwoSeven. Scopri quando vince la webcam integrata di TwoSeven e perché KoalaSync è la soluzione migliore per Disney+ e Netflix.",
"ALT_TWOSEVEN_INTRO": "Sia KoalaSync che TwoSeven utilizzano lo stesso approccio di base per i watch party: sincronizzano il lettore video originale direttamente nel browser, anziché eseguire lo streaming di un feed video compresso dal cloud. Ciò garantisce una qualità video di alto livello per entrambi gli strumenti. Tuttavia, mentre TwoSeven è un portale commerciale con chat webcam integrata e livelli di abbonamento, KoalaSync rimane un'estensione minimalista, completamente gratuita e rispettosa della privacy. Scopri qui il servizio più adatto a te.",
"ALT_TWOSEVEN_META_DESC": "Confronto pratico tra KoalaSync e TwoSeven: account, comunicazione integrata, chat crittografata e self-hosting.",
"ALT_TWOSEVEN_INTRO": "Entrambi supportano la visione sincronizzata, ma servizi e requisiti possono cambiare. TwoSeven usa account e offre funzioni di comunicazione integrate. KoalaSync non richiede account, offre chat testuale crittografata e non include chiamate.",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_TITLE": "Quando TwoSeven è la scelta migliore",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "TwoSeven è la scelta giusta se desideri una <strong>soluzione all-in-one con webcam e chat audio integrate</strong> direttamente nel tuo browser. Se vuoi vedere i tuoi amici mentre guardi senza dover eseguire Discord o Teamspeak in background, TwoSeven offre questa integrazione in modo nativo. Inoltre, funziona con YouTube e alcuni lettori video Web aperti senza estensioni. È adatto a piccoli gruppi di 2 o 3 persone che desiderano condividere una connessione visiva durante la visione.",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "TwoSeven può essere più adatto se webcam e audio integrati sono importanti. Verifica i requisiti attuali relativi a browser, account, piano e servizi supportati.",
"ALT_TWOSEVEN_WHEN_KOALA_TITLE": "Quando KoalaSync si adatta meglio",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync brilla per la sua <strong>totale assenza di costi, privacy e semplicità</strong>:<br><br>• <strong>Nessun paywall</strong>: per guardare servizi premium come Disney+ o Hulu in sincronia su TwoSeven, almeno un membro della stanza deve supportarli su Patreon (a partire da $ 3/mese). KoalaSync sincronizza Netflix, Prime Video, Disney+ e server multimediali self-hosted (Jellyfin, Plex, Emby) in modo gratuito al 100% e senza restrizioni.<br>• <strong>Privacy assoluta (nessun tracker/annuncio)</strong>: TwoSeven supporta la pubblicità e utilizza più tracker di Google, Facebook e altri. KoalaSync non ha pubblicità, né tracker e comunica esclusivamente con il server di inoltro solo RAM.<br>• <strong>Non è richiesta alcuna registrazione</strong>: TwoSeven richiede a tutti i partecipanti di creare un account ed effettuare l'accesso. KoalaSync è completamente privo di account. Basta installarlo, inserire il codice della stanza e iniziare a guardare.<br>• <strong>Leggero e di facile utilizzo</strong>: la webcam integrata e i feed audio di TwoSeven mettono a dura prova la CPU e la rete. Lasciando la comunicazione a strumenti come Discord, KoalaSync funziona in modo estremamente fluido e preserva la durata della batteria.",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync è adatto se vuoi sincronizzazione open source senza account, chat testuale live crittografata end-to-end e la possibilità di ospitare il relay. Le chiamate vocali e webcam restano in unapp separata.",
"ALT_TWOSEVEN_MAIN_DIFF_TITLE": "La differenza principale",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven è una <strong>piattaforma commerciale</strong> con requisiti di registrazione, annunci, chat webcam peer-to-peer e paywall Patreon per servizi di streaming specifici. KoalaSync è un'<strong>utilità gratuita e open source</strong> che si collega direttamente alle schede del tuo browser senza account, tracker o costi aggiuntivi, consentendo una visione senza distrazioni nel lettore nativo.",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven unisce la visione sincronizzata a una piattaforma con account e funzioni webcam e audio integrate. KoalaSync è unestensione senza account con chat testuale live crittografata; le chiamate restano separate.",
"ALT_TWOSEVEN_LIMITS_TITLE": "Limitazioni oneste",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync non offre <strong>nessuna webcam integrata o chat audio</strong> e presuppone che tu sia già impegnato in una chiamata tramite Discord o uno strumento simile. Inoltre, KoalaSync non ha una finestra di chat incorporata ed è attualmente solo desktop.",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync non offre <strong>webcam integrata chat audio</strong>, quindi per vedersi o parlare è comunque necessario Discord o uno strumento simile. Include però un overlay di chat testuale live, flottante e agganciabile, crittografato end-to-end ed è attualmente disponibile solo su desktop.",
"ALT_TWOSEVEN_SUMMARY_TITLE": "Insomma",
"ALT_TWOSEVEN_SUMMARY_BODY": "Per piccoli gruppi di due o tre persone che vogliono vedersi tramite webcam direttamente nel browser e non hanno problemi a supportare Patreon per Disney+, TwoSeven è una buona opzione. Per serate di cinema private, completamente gratuite e senza pubblicità in qualità 4K nativa (che si tratti di Netflix, Prime Video, Disney+ o Jellyfin) senza registrazioni, KoalaSync è la scelta più pulita e sicura.",
"ALT_TWOSEVEN_SUMMARY_BODY": "Scegli TwoSeven se webcam e audio integrati sono la priorità. Scegli KoalaSync per sincronizzazione open source senza account, chat live crittografata e self-hosting facoltativo.",
"ALT_TRY_KOALASYNC": "Prova KoalaSync",
"MOCK_HOST_CONTROL_LABEL": "Controllo host",
"MOCK_HOST_CONTROL_TOGGLE": "Solo io posso controllare la riproduzione",
@@ -287,5 +288,9 @@
"SELF_SHOW_SETUP": "Mostra la guida all'installazione",
"SELF_HIDE_SETUP": "Nascondi la guida",
"HERO_TRUST": "Gratuito · Open source · Senza account",
"COMP_MORE_GUIDES": "Confronto completo e altre alternative"
"COMP_MORE_GUIDES": "Confronto completo e altre alternative",
"NAV_HOME": "Home",
"ARIA_SELECT_LANGUAGE": "Seleziona lingua",
"ARIA_MENU": "Menu",
"FOOTER_GUIDES": "Guide e confronti"
}
+51 -46
View File
@@ -16,8 +16,9 @@
"NAV_HOW_IT_WORKS": "仕組み",
"SUPPORT_BANNER_TEXT": "KoalaSync が動画サイトにアクセスできませんか?",
"SUPPORT_BANNER_CTA": "サイトアクセスを修復",
"SUPPORT_BANNER_DISMISS": "通知を閉じる",
"HERO_TITLE": "どんな動画も<span class='hero-highlight'>ウォッチパーティー</span>に。",
"HERO_SUBTITLE": "離れていても一緒に映画ナイト。アカウント不要、トラッキングなし、広告なし。リンクを共有して一緒に再生するだけ。",
"HERO_SUBTITLE": "離れた場所から一緒に動画を視聴。アカウント、追跡、広告はありません。リンクを共有して一緒に再生できます。",
"HERO_MASCOT_ALT": "ダウンロードボタンを見下ろしているかわいいコアラ",
"DEMO_SYNC": "同期中",
"DEMO_HINT": "ライブデモ:どちらかのタブで再生すると、KoalaSyncが両方を同期させます。",
@@ -41,7 +42,7 @@
"USE_CASE_1_DESC": "映画をリアルタイムで同期し、DiscordやZoomなどでおしゃべり。まるで同じ部屋に座っているかのような感覚です。",
"USE_CASE_2_ALT": "画面を通じて2匹のリモート生徒コアラに講義をするコアラ教授",
"USE_CASE_2_TITLE": "リモート学習",
"USE_CASE_2_DESC": "オンラインチュートリアル、講義、開発者向けトレーニング配信などをクラスメイトや同僚と一緒に分析。一時停止して、複雑なステップについて話し合えます。",
"USE_CASE_2_DESC": "チュートリアル、講義、研修動画を一緒に確認できます。一時停止して難しい手順を話し合えます。",
"USE_CASE_3_ALT": "離れた場所で一緒に見ているかわいいコアラのカップル。一方はPCの前に座り、もう一方はベッドでノートPCを持っていて、二人の間にはハートが飛んでいます",
"USE_CASE_3_TITLE": "遠距離恋愛",
"USE_CASE_3_DESC": "距離を縮めてデートナイトを楽しみましょう。すべてのストーリー展開を体験し、同じジョックで笑い、感情的な瞬間をまったく同じミリ秒で共有します。",
@@ -114,8 +115,8 @@
"STEP_2_ILLUS_CREATE": "+ 新規ルーム作成",
"STEP_2_ILLUS_MANUAL": "手動接続 / 詳細設定",
"STEP_2_ILLUS_COPIED": "招待リンクをコピーしました!",
"STEP_3_TITLE": "同期を楽しむ",
"STEP_3_DESC": "再生ボタンを押します。誰かが一時停止、早送り、または再生をしても、全員の動画がタイムラグなしで完璧に同期します。",
"STEP_3_TITLE": "一緒に視聴",
"STEP_3_DESC": "再生を開始すると、操作がルーム内の参加者に共有されます。通信環境やプレーヤーによって短い遅延が生じる場合があります。",
"STEP_3_ILLUS_IN_SYNC": "同期中",
"SELF_TITLE": "セルフホストする方向け",
"SELF_SUBTITLE": "公式サーバーを信頼しませんか?データの完全な所有権を維持しましょう。数分で独自のプライベートリレーサーバーを構築できます。",
@@ -126,33 +127,33 @@
"BOTTOM_SUBTITLE": "KoalaSyncは100%オープンソースで、広告や追跡は一切ありません。GitHubでコードを監査するか、ブラウザ拡張機能を直接インストールしてください。",
"BOTTOM_MASCOT_ALT": "GitHubのマスコット「Octocat」の隣で、GitHubのリポジトリページを持っているかわいいコアラ",
"FAQ_TITLE": "よくある質問",
"FAQ_SUBTITLE": "完璧に同期して動画を一緒に楽しむために必要なすべての情報。",
"FAQ_SUBTITLE": "対応サイト、同期、プライバシー、チャット、オーディオ処理に関する回答です。",
"FAQ_Q1": "友達と一緒にNetflix、Emby、Jellyfinを同期視聴できますか?",
"FAQ_A1": "はい!KoalaSyncはNetflix, YouTube, TwitchおよびHTML5ビデオプレーヤーを備えたあらゆるウェブサイト(Emby, Jellyfin, 独自のメディアサーバーを含む)で動作します。拡張機能をインストールし、ルームを作成して招待リンクを共有するだけで、全員で完全に同期して視聴できます。",
"FAQ_A1": "KoalaSyncはNetflixYouTubeTwitchEmbyJellyfin、セルフホスト型メディアサーバーなど、標準的なブラウザ動画プレーヤーを使う多くの対応サイトで動作します。互換性はサイト、ブラウザ、プレーヤーに依存し、サイトの更新で変わることがあります。",
"FAQ_Q_VIDEO_ACCESS": "KoalaSync は友達に動画を配信しますか?",
"FAQ_A_VIDEO_ACCESS": "いいえ。KoalaSync は動画コンテンツを配信、ホスト、共有したり、アクセス制限を回避したりしません。各参加者は自分のブラウザで動画をローカルに視聴し、Netflix アカウントなど、そのウェブサイトやストリーミングサービスへの自分自身のアクセス権が必要です。KoalaSync は再生、一時停止、シークなどの再生操作だけを同期します。",
"FAQ_Q2": "KoalaSyncはTelepartyの無料の代替ツールですか?",
"FAQ_A2": "MITライセンスのもとで100%無料かつオープンソース。有料プラン、制限された機能、広告などは一切ありません。Telepartyとは異 nel、KoalaSyncのリレーサーバーはメモリ上のみで動作し、データ収集やユーザーアカウント作成は不要です。",
"FAQ_A2": "はい。KoalaSyncはMITライセンスの無料オープンソースソフトウェアで、有料プラン、機能制限、広告、必須アカウントはありません。",
"FAQ_Q3": "KoalaSyncはEmby、Jellyfin、およびセルフホストのメディアサーバーで動作しますか?",
"FAQ_A3": "はい。KoalaSyncは標準的なHTML5ビデオ要素を検出するため、Emby, Jellyfin, Plex、および独自のセルフホスト配信設定で完全に動作します。また、データの完全な所有権を維持するために、Dockerを介してリレーサーバーをセルフホストすることも可能です。",
"FAQ_A3": "はい。EmbyJellyfinPlexやセルフホスト環境で使われる多くの標準的な動画プレーヤーに対応しています。互換性はプレーヤーに依存します。DockerでKoalaSyncのリレーをセルフホストすることもできます。",
"FAQ_Q4": "どのブラウザとプラットフォームがKoalaSyncをサポートしていますか?",
"FAQ_A4": "Google Chrome、Mozilla Firefox、Microsoft Edge、Opera、Brave、およびすべてのChromiumベースのブラウザ。Chrome ウェブストアまたは Firefox Add-ons から直接インストールできます。手動の設定は不要です。",
"FAQ_Q5": "友達も全員拡張機能をインストールする必要がありますか?",
"FAQ_A5": "はい、参加者全員にKoalaSyncがインストールされている必要があります。ただし、登録やアカウント作成は不要です。招待リンクをクリックし、未インストールの場合は拡張機能をインストールすれば、数秒で自動的にルームに参加できます。",
"FAQ_Q6": "データは安全ですか? 公式サーバーを信頼できない場合は?",
"FAQ_A6": "完全に安全です。KoalaSyncのリレーサーバーはメモリ上のみで動作するため、ルームを退出した瞬間にすべてのセッションデータが永久に削除されます。データベース、ログ、分析、アカウントは一切ありません。さらに、完全に管理したい場合は、Dockerを介して独自のプライベートリレーサーバーを数分でセルフホストできます。コードベース全体がオープンソースであり、GitHubで監査可能です。",
"FAQ_A6": "KoalaSync の公式リレーは、ルーム、同期、視聴、チャットの履歴をデータベースに保存しません。アクティブなルームのデータは一時的にメモリ上に置かれ、ルームが空になるか、一定時間操作がなく期限切れになると削除されます。セキュリティと不正利用防止のために技術的な接続ログが生成される場合があり、保存期間はサーバー設定によって異なります。チャットメッセージはエンドツーエンドで暗号化され、サーバー側の履歴として保存されません。",
"FAQ_Q7": "ブラウザで動画を視聴する際、小さな会話と大きな爆発音を改善できますか?",
"FAQ_A7": "はい。KoalaSync 2.2.0には、動画の静かな部分と騒がしい部分の差を減らす内蔵オーディオコンプレッサーが搭載されています。Web Audio APIを介してローカルで動作するため、オーディオデータがコンピュータから送信されることはありません。ポップアップを開き、ルームを作成し、設定 → Audio Processingに進み、コンプレッサーを有効にしてプリセットを選択してください。Netflix、Prime Video、Disney+、YouTube、あらゆるHTML5ビデオサイトで動作します。",
"FAQ_A7": "はい。静かな部分と大きな音の差を抑えるオーディオコンプレッサーがあります。Web Audio APIを使って端末内で処理され、音声データはリレーに送信されません。設定 → オーディオ処理で有効にできます。",
"HOWTO_TITLE": "KoalaSyncを使用して友達とビデオを同期視聴する方法",
"HOWTO_DESC": "YouTube、Netflix、Twitchなどで、友達と完全に同期してあらゆる動画を視聴。登録不要、追跡なし。",
"HOWTO_DESC": "YouTube、Netflix、Twitchなどの対応動画を友人と視聴できます。登録や追跡はありません。",
"HOWTO_STEP_1_NAME": "ブラウザ拡張機能をインストールする",
"HOWTO_STEP_1_TEXT": "Chrome ウェブストアまたは Firefox Add-ons から KoalaSync をブラウザに追加します。ビデオ要素がある任意のウェブサイトで動作します。",
"HOWTO_STEP_1_TEXT": "Chrome ウェブストアまたはFirefox Add-onsからKoalaSyncをインストールします。対応ページを開き、拡張機能が検出した動画を選択してください。",
"HOWTO_STEP_2_NAME": "ルームを作成する",
"HOWTO_STEP_2_TEXT": "拡張機能のポップアップを開き、「新規ルーム作成」をクリックします。招待リンクは自動的にクリップボードにコピーされます。",
"HOWTO_STEP_3_NAME": "共有して同期する",
"HOWTO_STEP_3_TEXT": "招待リンクを友達に送信します。ビデオのタブを選択して、リアルタイムで同期された再生を楽しんでください。",
"FOOTER_MIT": "MITライセンスに基づくオープンソース。",
"FOOTER_RAM": "サーバーにはデータは一切保存されません。完全にメモリ(RAM)上でのみ中継されます。",
"FOOTER_RAM": "チャット履歴や視聴履歴は保存しません。追跡なし。Webアクセスログは7日後に削除されます。",
"FOOTER_LEGAL": "法的通知",
"FOOTER_PRIVACY": "プライバシーポリシー",
"MOCK_01": "アクティブなルーム",
@@ -190,7 +191,7 @@
"FOOTER_LEGAL_LINK": "imprint",
"FOOTER_PRIVACY_LINK": "privacy",
"FOOTER_DISCLAIMER": "KoalaSyncは、Netflix、Disney+、Amazon、YouTube、Twitch、またはその他のストリーミングプラットフォームと提携、承認、または関連付けられているものではありません。すべての商標はそれぞれの所有者に帰属します。",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "KoalaSyncを支援",
"ALT_INDEX_TITLE": "KoalaSync - 代替サービスと比較",
"ALT_INDEX_META_DESC": "KoalaSync と他のウォッチパーティー拡張機能やストリーミング向けソリューションを、公平な比較と詳しいガイドで確認できます。",
"ALT_INDEX_HEADING": "ガイドと比較",
@@ -200,17 +201,17 @@
"ALT_INDEX_FOOTER_LINK": "ガイドと比較",
"ALT_TELEPARTY_TITLE": "KoalaSync vs Teleparty:どちらのウォッチパーティツールを使うべき?",
"ALT_TELEPARTY_META_DESC": "ウォッチパーティ向けツール、KoalaSyncとTelepartyの実践的比較。チャット機能、モバイル対応、ブラウザでの動画同期、セルフホスト型メディアサーバー、オープンソース利用といった観点から解説します。",
"ALT_TELEPARTY_INTRO": "KoalaSyncとTelepartyは、どちらも「3、2、1、再生!」と声を掛け合う手間をかけずに、一緒に動画を楽しむという共通の課題を解決してくれます。ただ、そのアプローチは異なります。<br><br>チャット機能が組み込まれていることや、モバイル端末で視聴できることを重視するなら、Telepartyが最適です。一方、KoalaSyncは、DiscordやTeamSpeakなどで既に通話しながら、ブラウザ上で動画の再生タイミングだけを同期させたいという、デスクトップブラウザでの利用に適しています。<br><br>私がKoalaSyncを開発した主な理由は「柔軟性」です。特定のプラットフォームだけでなく、一般的なブラウザ用動画プレーヤーを使用するほぼすべてのウェブサイトで動作するように設計されています。これには、Emby、Jellyfin、Plexといったセルフホスト環境や、ローカル/プライベートドメイン、そしてブラウザ上の一般的な動画ページなどが含まれます。",
"ALT_TELEPARTY_INTRO": "KoalaSyncとTelepartyは、どちらも「3、2、1、再生!」と声を掛け合う手間をかけずに、一緒に動画を楽しむという共通の課題を解決してくれます。ただ、そのアプローチは異なります。<br><br>より統合されたウォッチ パーティー エクスペリエンスが必要な場合、またはモバイルで視聴したい場合は、Telepartyが最も合理的です。 KoalaSync は、幅広い通常のブラウザ ビデオ プレーヤーにわたるデスクトップ ブラウザ ウォッチ パーティー向けに設計されています。組み込みのテキスト チャットはエンドツーエンドで暗号化され、ライブのみですが、音声通話またはビデオ通話は分離されたままです。<br><br>私がKoalaSyncを開発した主な理由は「柔軟性」です。特定のプラットフォームだけでなく、一般的なブラウザ用動画プレーヤーを使用するほぼすべてのウェブサイトで動作するように設計されています。これには、Emby、Jellyfin、Plexといったセルフホスト環境や、ローカル/プライベートドメイン、そしてブラウザ上の一般的な動画ページなどが含まれます。",
"ALT_TELEPARTY_WHEN_TELE_TITLE": "Telepartyが適しているケース",
"ALT_TELEPARTY_WHEN_TELE_BODY": "ウォッチパーティツール自体にチャット機能が組み込まれていることを望むなら、Telepartyの方が良い選択肢なるでしょう。KoalaSyncには現在、チャット機能が搭載されていません。Discord、TeamSpeak、WhatsApp、Signal、あるいは通常の通話など、別の手段で既に会話していることを前提としています。<br><br>もう一つの大きな理由はモバイル対応です。Telepartyにはアプリがありますが、KoalaSyncは現時点ではデスクトップブラウザでしか動作しません。スマートフォンやタブレットでNetflixを同期再生したい場合、今のところKoalaSyncは適していません。<br><br>つまり、チャット機能やモバイル対応を何よりも重視するのであれば、Telepartyの方があなたの利用スタイルに合っているかもしれません。",
"ALT_TELEPARTY_WHEN_TELE_BODY": "より統合されたウォッチ パーティー エクスペリエンスを好む場合、またはモバイル サポートが必要な場合は、Telepartyがより良い選択肢なる可能性があります。どちらのツールにもテキスト チャットが含まれているため、チャットだけが決定的な違いではなくなりました。 KoalaSync のチャットはエンドツーエンドで暗号化され、ライブのみです。音声通話とビデオ通話では依然として別のアプリが使用されます。<br><br>もう一つの大きな理由はモバイル対応です。Telepartyにはアプリがありますが、KoalaSyncは現時点ではデスクトップブラウザでしか動作しません。スマートフォンやタブレットでNetflixを同期再生したい場合、今のところKoalaSyncは適していません。<br><br>そのため、モバイル サポートとより統合されたプラットフォームが最も重要なことである場合は、Teleparty の方がセットアップに適している可能性があります。",
"ALT_TELEPARTY_WHEN_KOALA_TITLE": "KoalaSyncが適している場合",
"ALT_TELEPARTY_WHEN_KOALA_BODY": "特定のプラットフォームに縛られるツールではなく、柔軟性の高いデスクトップブラウザ用同期ツールをお探しなら、KoalaSyncが適しています。<br><br>一般的なブラウザの動画プレーヤーを使用するほぼすべてのウェブサイトで動作します。これは特に、Emby、Jellyfin、Plex、プライベートメディアサーバー、ローカルドメイン、あるいは従来のウォッチパーティツールが見落としがちな小規模な動画サイトなどで役立ちます。<br><br>また、無料かつオープンソースであり、アカウント登録も不要です。独自のリレーサーバーを運用したい場合は、セルフホストも可能です。私がこのツールを開発したのは、一部の大手ストリーミングサイト専用の閉鎖的なプラットフォームではなく、自分のEmby環境で動作し、プライバシーに配慮したツールが欲しかったからです。",
"ALT_TELEPARTY_MAIN_DIFF_TITLE": "主な違い",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Telepartyは、チャットやモバイル対応を備えたウォッチパーティ(同時視聴)」向け製品です。一方、KoalaSyncは、ブラウザ上の動画再生を同期させるための、より柔軟な同期レイヤーと言えます。<br><br>些細な違いに聞こえるかもしれませんが、これによって利用シーンが大きく変わります。チャット機能が組み込まれた総合的なウォッチパーティアプリが必要な、Telepartyが適しています。すでにボイスチャット環境があり、単にブラウザ間で動画の再生状態を同期させたいだけであれば、よりシンプルなKoalaSyncが適しています。",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty は、モバイル サポートを備えた統合ウォッチ パーティ製品です。 KoalaSync は、エンドツーエンドで暗号化されたライブ テキスト チャットを備えた柔軟なデスクトップとブラウザの同期レイヤーす。<br><br>これは小さな違いのように聞こえますが、ユースケースは大きく変わります。モバイル ウォッチ パーティアプリが必要な場合は、Teleparty が最適です。リレーに読み取り可能なチャット メッセージを提供せずに、セルフホスト メディアを含む広範なブラウザ プレーヤーのサポートが必要な場合は、KoalaSync がより簡単に適合します。",
"ALT_TELEPARTY_LIMITS_TITLE": "制限事項について",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSyncは魔法のツールではなく、その限を隠そうとしていません。現時点ではデスクトップブラウザでのみ動作し、チャット機能やモバイルアプリは搭載されていません。<br><br>互換性はブラウザや動画プレーヤーにも依存します。KoalaSyncは一般的なブラウザの動画プレーヤーのほとんどで動作するはずですが、ウェブサイト側がプレーヤー、DRMの挙動、あるいはブラウザへの制限などをいつでも変更する可能性があります。<br><br>KoalaSyncは、Netflix、Disney+、Prime Video、YouTube、Teleparty、Emby、Jellyfin、Plex、その他ここで言及されているブランドとは一切関係ありません。これらの名称は、一般的な利用シーンを説明するために使用されているに過ぎません。",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync は魔法ではなく、その限を隠そうとしているわけでもありません。現在、デスクトップブラウザでのみ動作します。エンドツーエンドの暗号化されたテキスト チャットは含まれていますが、音声、ビデオ、ウェブカメラ チャットは含まれておらず、モバイル アプリもありません。<br><br>互換性はブラウザや動画プレーヤーにも依存します。KoalaSyncは一般的なブラウザの動画プレーヤーのほとんどで動作するはずですが、ウェブサイト側がプレーヤー、DRMの挙動、あるいはブラウザへの制限などをいつでも変更する可能性があります。<br><br>KoalaSyncは、Netflix、Disney+、Prime Video、YouTube、Teleparty、Emby、Jellyfin、Plex、その他ここで言及されているブランドとは一切関係ありません。これらの名称は、一般的な利用シーンを説明するために使用されているに過ぎません。",
"ALT_TELEPARTY_SUMMARY_TITLE": "概要",
"ALT_TELEPARTY_SUMMARY_BODY": "チャット機能やモバイル対応を重視するなら、Telepartyの方が適しているかもしれません。一方、無料かつオープンソースで、デスクトップブラウザでの同期が可能、さらにEmby、Jellyfin、Plexといったセルフホスト型メディアサーバーを含む、一般的な動画プレーヤーのほとんどで動作するツールをお探しなら、KoalaSyncの方が適しているでしょう。",
"ALT_TELEPARTY_SUMMARY_BODY": "どちらのツールにもテキスト チャットが含まれています。モバイル サポートが必要な場合、または Teleparty のより統合されたウォッチ パーティー エクスペリエンスを好む場合は、Teleparty の方が適している可能性があります。 Emby、Jellyfin、Plex などの自己ホスト型メディア サーバーを含む、ほぼすべての通常のビデオ プレーヤーで動作する、エンドツーエンドの暗号化ライブ チャットとの無料のオープンソース デスクトップ ブラウザ同期が必要な場合は、おそらく KoalaSync の方が適しています。",
"ALT_SCREEN_TITLE": "KoalaSyncと画面共有:動画同期の方が優れているのはどんな時?",
"ALT_SCREEN_META_DESC": "リモートでの映画鑑賞会におけるKoalaSyncと画面共有の実践的な比較。再生制御、画質、プライバシー、字幕、ブラウザでの視聴といった観点から解説します。",
"ALT_SCREEN_INTRO": "画面共有は有効な手段であり、正直なところ、それで十分な場合もあります。一人が画面を共有し、他の全員がそれを見るだけで済みますから。<br><br>しかし、長時間の映画鑑賞やシリーズ作品を一緒に見るとなると、煩わしさを感じることもあります。画質は、共有者のアップロード速度、CPU、ブラウザ、そしてDiscord、TeamSpeak、Zoomなどの通話アプリによる圧縮処理に左右されます。また、再生を実際に操作できるのは一人のみです。",
@@ -221,61 +222,61 @@
"ALT_SCREEN_WHY_CLEANER_TITLE": "KoalaSyncがすっきり感じられる理由",
"ALT_SCREEN_WHY_CLEANER_BODY": "KoalaSyncは画面をストリーミングしません。全員がそれぞれのブラウザでローカルに動画を視聴し、KoalaSyncは再生・一時停止・シークといった操作の同期のみを行います。<br><br>つまり、全員が自分のデバイスでフルスクリーン表示を利用したり、プレイヤーが対応していれば個別の字幕や音声設定を使ったりできるほか、DiscordやTeamSpeakなどの通話ツールで音声チャットを別に行うことも可能です。<br><br>映画鑑賞会などでは、一人が画面を配信するよりも、はるかにすっきりとした体験になります。特に、Emby、Jellyfin、Plex、ブラウザで開いたローカルファイル、プライベートドメイン、あるいは一般的なブラウザ用動画プレイヤーを備えたウェブサイトなど、ブラウザベースの環境で利用する場合に非常に便利です。",
"ALT_SCREEN_LIMITS_TITLE": "正直なところ、制限事項も",
"ALT_SCREEN_LIMITS_BODY": "KoalaSyncは音声通話の代わりになるものではありません。視聴しながら会話をする場合は、Discord、TeamSpeak、Signal、WhatsAppなど、普段お使いのツールをそのまま利用します。<br><br>また、参加者全員がそれぞれのデバイスでその動画やページにアクセスできる必要があります。KoalaSyncは、サブスクリプション、ログイン、DRM(デジタル著作権管理)、地域制限、プラットフォームの利用規約などを回避するものではありません。<br><br>現はデスクトップブラウザのみに対応しており、チャットや音声通話機能は内蔵されていません。また、動作の可否は使用するブラウザや動画プレーヤーに依存します。",
"ALT_SCREEN_LIMITS_BODY": "KoalaSync にはエンドツーエンドの暗号化されたテキスト チャットが含まれていますが、音声通話やビデオ通話に代わるものではありません。視聴しながら会話したい場合は、Discord、Teamspeak、Signal、WhatsApp など、普段使用しているものを引き続き使用します。<br><br>また、参加者全員がそれぞれのデバイスでその動画やページにアクセスできる必要があります。KoalaSyncは、サブスクリプション、ログイン、DRM(デジタル著作権管理)、地域制限、プラットフォームの利用規約などを回避するものではありません。<br><br>現時点ではデスクトップ ブラウザのみであり、音声、ビデオ、ウェブカメラ チャットは組み込まれておらず、互換性は常にブラウザとビデオ プレーヤーに依存します。",
"ALT_SCREEN_SUMMARY_TITLE": "概要",
"ALT_SCREEN_SUMMARY_BODY": "画質やプライバシー、個別の操作性をあまり気にせず、手軽に済ませたい場合は「画面共有」が最も簡単な選択肢です。一方、参加者それぞれが自身のブラウザで動画を再生し、実際に同じ空間で一緒に視聴しているような感覚を重視するなら、KoalaSyncの方が適しています。",
"ALT_W2G_TITLE": "KoalaSync と Watch2Gether: どちらのウォッチ パーティー ソリューションがより適していますか?",
"ALT_W2G_META_DESC": "KoalaSyncWatch2Gether の正直な比較。 KoalaSync の柔軟性が優れている理由と、Watch2Gether が YouTube にとって依然としてトップの選択肢である理由をご覧ください。",
"ALT_W2G_META_DESC": "共有ルーム、プレイリスト、プレーヤー同期、暗号化チャットに関するKoalaSyncWatch2Getherの比較です。",
"ALT_W2G_INTRO": "KoalaSync と Watch2Gether は両方とも同じ問題を解決します。つまり、手動で調整することなく、オンラインで友達と同期してビデオを視聴できます。 Watch2Gether は映画鑑賞を共有するための最も古く、最も人気のあるハブの 1 つですが、KoalaSync はまったく異なる軽量のアプローチを採用しています。どのツールがあなたに適しているかは、何をどこで見たいかによって大きく異なります。",
"ALT_W2G_WHEN_W2G_TITLE": "Watch2Gether がより良い選択である場合",
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether は<strong>YouTube ウォッチ パーティー</strong>にとって無敵です。内蔵のプレイリストと検索により、部屋にいる誰もが新しいビデオを簡単に検索、追加、再生できます。全員が一緒にシームレスに次の動画を視聴します。<br><br>YouTube の KoalaSync を使用すると、サイトのデフォルトの自動再生が引き継ぎ、各ユーザーを別のおすすめ動画にリダイレクトします。自動次エピソード機能 (「エピソード ロビー」) は、Netflix や Disney+ ではうまく機能しますが、こうした動的な推奨事項のため、YouTube では機能しません。純粋に YouTube の夜を楽しみたい場合はWatch2Gether が最適です。",
"ALT_W2G_WHEN_W2G_BODY": "共有ルーム、検索、プレイリストを中心にYouTubeを視聴する場合はWatch2Getherが適しています。対応状況は現在のサービスとコンテンツによって異なります。",
"ALT_W2G_WHEN_KOALA_TITLE": "KoalaSync がより適切にフィットすると",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync は<strong>実際のストリーミング サービス</strong> (Netflix、Prime Video、Disney+) および自己ホストメディア サーバー (Jellyfin、Plex、Emby) で威力を発揮します。プレミアム コンテンツをまったく再生できないことが多い Watch2Gether とは異なり、KoalaSync はウェブサイト上でネイティブ プレーヤーを直接同期します。<br><br>標準の HTML5 ビデオ プレーヤーを備えたほぼすべてのサイトで動作します。サードパーティのサイトに仮想ルームを設定する必要はありません。拡張機能をアクティブにしてロビーに参加し、視聴を開始するだけです。",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync は、セルフホストしたメディアを含む対応ページの元のプレーヤーを、別のウォッチパーティーサイトへ移さずに同期したい場合に適しています。互換性はウェブサイト、プレーヤー、ブラウザによって異なります。",
"ALT_W2G_MAIN_DIFF_TITLE": "主な違い",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether は、チャット、プレイリスト、Web カメラのサポートが組み込まれた<strong>スタンドアロン Web サイト</strong>です。 KoalaSync は、通常のブラウザ タブに直接接続る<strong>シンプルなブラウザ拡張機能</strong>です。<br><br>チャットが組み込まれた YouTube 用のオールインワン パッケージをお探しですか? Watch2Gether を使用します。 Netflix または独自のサーバーで、気が散ることなく、ネイティブ品質 (サーバー圧縮なし) で映画を視聴したいですか? KoalaSync はよりクリーンなソリューションです。",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether は、テキスト チャット、プレイリスト、検索、Web カメラのサポートを備えた<strong>スタンドアロン Web サイト</strong>です。 KoalaSync は、通常のブラウザ タブに直接接続できる<strong>シンプルなブラウザ拡張機能</strong>で、エンドツーエンドで暗号化されたライブ テキスト チャットも含まれています。<br><br>プレイリスト、検索、ウェブカメラ機能を備えた YouTube 用のオールインワン パッケージをお探しですか? Watch2Gether を使用します。 Netflix または独自のサーバーで、気が散ることなく、ネイティブ品質 (サーバー圧縮なし) で映画を視聴したいですか? KoalaSync はよりクリーンなソリューションです。",
"ALT_W2G_LIMITS_TITLE": "正直な限界",
"ALT_W2G_LIMITS_BODY": "KoalaSync には<strong>プレイリストやビデオ検索が組み込まれていません</strong>。 YouTube では、これは、次のビデオの自動再生の同期が維持されないことを意味します。<br><br>さらに、<strong>組み込みのチャットはありません</strong>。また、KoalaSync は現在デスクトップ ブラウザでのみ動作します。とにかく、Discord、Teamspeak、またはその他の音声ツールを介してすでに会話しているという前提に基づいて設計されています。",
"ALT_W2G_LIMITS_BODY": "KoalaSync には<strong>プレイリストやビデオ検索が組み込まれていません</strong>。 YouTube では、これは、次のビデオの自動再生の同期が維持されないことを意味します。<br><br>KoalaSync には、エンドツーエンドで暗号化されたライブ テキスト チャットが含まれていますが、内蔵のウェブカメラ、音声、またはビデオ チャットは含まれておらず、現時点ではデスクトップ ブラウザでのみ動作します。通話で話したり、お互いの映像を見たりしたい場合は、Discord、Teamspeak、またはその他の通話ツールが必要です。",
"ALT_W2G_SUMMARY_TITLE": "要するに",
"ALT_W2G_SUMMARY_BODY": "プレイリストと組み込みチャットを使用した自発的な YouTube セッションには、Watch2Gether が最適な選択です。プレミアム ストリーミング プラットフォーム (Netflix、Prime Video、Disney+ など)、セルフホスト サーバー (Jellyfin、Plex、Emby)、お​​よびブラウザの柔軟性を最大限に高めるには、KoalaSync がより適切で軽量なツールです。",
"ALT_W2G_SUMMARY_BODY": "どちらのツールにもテキスト チャットが含まれています。プレイリスト、検索、ウェブカメラ機能を備えた自発的な YouTube セッションには、Watch2Gether が適しています。プレミアム ストリーミング プラットフォーム (Netflix、Prime Video、Disney+ など)、自己ホストサーバー (Jellyfin、Plex、Emby)、エンドツーエンドの暗号化されたライブ チャット、ブラウザーの最大限の柔軟性には、KoalaSync がより適切で軽量なツールです。",
"ALT_SCENER_TITLE": "KoalaSync と Scener: どちらのウォッチ パーティー プラットフォームがより適していますか?",
"ALT_SCENER_META_DESC": "KoalaSync と Scener の正直な比較。 Scener の Web カメラ チャットが最適なタイミングと、気を散らすことなく映画鑑賞に KoalaSync が最適な理由をご確認ください。",
"ALT_SCENER_INTRO": "KoalaSync と Scener は、まったく異なる 2 つの角度からウォッチ パーティーにアプローチします。 Scener は、ビデオ チャットとインタラクティブ ルームが組み込まれた仮想映画館です。一方、KoalaSync は、再生コントロールを単純に同期する、最小限のプライバシーに配慮したブラウザ拡張機能です。どのツールがあなたに最適であるかは、主に、内蔵の Web カメラとチャット機能があなたにとってどれだけ重要であるかによって決まります。",
"ALT_SCENER_INTRO": "KoalaSync と Scener は、まったく異なる 2 つの角度からウォッチ パーティーにアプローチします。 Scener は、ビデオ チャットとインタラクティブ ルームが組み込まれた仮想映画館です。一方、KoalaSync は、再生コントロールを同期し、エンドツーエンドの暗号化されたライブ テキスト チャットを備えた、ミニマルでプライバシーに配慮したブラウザ拡張機能です。どのツールが最適であるかは、主に、内蔵の Web カメラ、音声、ビデオ、ソーシャル機能が必要か、それとも軽量の同期ツールを好むかによって決まります。",
"ALT_SCENER_WHEN_SCENER_TITLE": "Scener がより良い選択である場合",
"ALT_SCENER_WHEN_SCENER_BODY": "ブラウザにビデオとオーディオのチャットが直接組み込まれている<strong>オールインワンのソーシャル プラットフォーム</strong>が必要な場合は、Scener が確実な選択肢です。 Scener は、プレーヤーと友達の Web カメラが並べて表示される仮想ルームを作成します。これは、ソーシャルな側面が中心となる大規模な公開ウォッチ パーティーや仮想ストリーミング イベントに最適です。ただし、無料の基本バージョンは広告でサポートされており、Scener では月額サブスクリプション (月額 5.79 ドルまたは年間 39.99 ドル) を支払うと、多くの機能 (広告なしの視聴や無制限のビデオ チャットなど) がロックされること注意してください。",
"ALT_SCENER_WHEN_SCENER_BODY": "Scener は、テキストチャットに加え、Premium 会員向けのビデオ・音声チャットを備えた統合型の交流機能を求める場合に適しています。パソコンでは Chrome 拡張機能を通じて所定の配信サービスに対応します。",
"ALT_SCENER_WHEN_KOALA_TITLE": "KoalaSync がより適切にフィットすると",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync は、<strong>ビデオ品質、プライバシー、 シンプルさ</strong>という点で優れています。<br><br>• <strong>アカウントなし</strong>: 登録、ログイン、プロファイルの管理は必要ありません。拡張機能を有効にしてルーム コードを共有し、視聴を開始するだけです。<br>• <strong>真のネイティブ品質 (4K/HDR)</strong>: Scener は仮想クラウド サーバーから圧縮ビデオをストリーミングしますが、これにより品質の低下や遅延が発生する可能性があります。 KoalaSync は、ネイティブ プレーヤーをコンピュータ上で直接同期します。つまり、ストリーング サブスクリプションのフル解像度で映画を視聴できます。<br>• <strong>低帯域幅使用量</strong>: 制御データ (再生、一時停止、タイムスタンプ) のみが送信されるため、KoalaSync は追加の帯域幅を実質的に使用しません。 Scener はビデオ全体とカメラ フィードをクラウド サーバー経由でストリーミングします。これにより、遅いインターネット接続や古いラップトップに大きな負担がかかる可能性があります。<br>• <strong>完全に無料、広告なし</strong>: 月々の購読料、広告、トラッカーはありません。<br>• <strong>ローカル ビデオ ファイル</strong>: KoalaSync はローカルで実行されるため、自分のビデオ ファイル (MP4 など) を友達と同期して見ることもできます。ファイルをブラウザのタブにドラッグ アンド ドロップするだけです。Scener のプレーヤーはクラウドで実行されるため、これはできません。",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync は、アカウント不要のオープンソース同期、元のプレーヤーでの再生、エンドツーエンド暗号化されたライブテキストチャット、任意のセルフホスト relay を求める場合に適しています。音声、ビデオ、ウェブカメラのチャットはありません。",
"ALT_SCENER_MAIN_DIFF_TITLE": "主な違い",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener は、仮想クラウド ブラウザを利用し、広告と月々のサブスクリプションによって資金を調達しているウォッチ パーティー用の<strong>商用ソーシャル ネットワーク</strong>です。 KoalaSync はブラウザのタブに直接接続できる<strong>プライバシー最優先の無料同期ユーティリティ</strong>です。音声/ビデオ チャットを Discord や Teamspeak などのアプリケーションに任せ、再生を滑らかに、プライベートに、ネイティブのオリジナル品質で維持することに完全に重点を置きます。",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener は統合されたルームと交流機能を重視します。KoalaSync は元のプレーヤーの軽量な同期と、エンドツーエンド暗号化されたライブテキストチャットを重視します。",
"ALT_SCENER_LIMITS_TITLE": "正直な限界",
"ALT_SCENER_LIMITS_BODY": "KoalaSync は<strong>組み込みのチャットやウェブカメラのサポートを提供しません</strong>。これは純粋に同期ツールであり、選択した別の音声通話ツールが必要です。さらに、KoalaSync は現在デスクトップ専用であり、ローカル ビデオ ファイルの互換性は、ブラウザがビデオ形式とオーディオ コーデックの再生をネイティブにサポートしているかどうかによって完全に決まります。",
"ALT_SCENER_LIMITS_BODY": "KoalaSync は<strong>エンドツーエンドの暗号化されたライブ テキスト チャットを提供しますが、ウェブカメラ、音声、ビデオ チャットは提供しません</strong>。したがって、お互いに話したり会ったりするには、選択した別の通話ツールが必要です。さらに、KoalaSync は現在デスクトップ専用であり、ローカル ビデオ ファイルの互換性は、ブラウザがビデオ形式とオーディオ コーデックの再生をネイティブにサポートしているかどうかによって完全に決まります。",
"ALT_SCENER_SUMMARY_TITLE": "要するに",
"ALT_SCENER_SUMMARY_BODY": "ウェブカメラチャットやソーシャル機能が組み込まれた大規模なウォッチパーティーの場合、広告やプレミアムペイウォールを気にしない限り、Scener が正しい選択です。ネイティブ プレーヤー (Netflix、Prime Video、Disney+、またはローカル ブラウザ ファイルを含む) で完全に無料、広告なし、気を散らすことのないエクスペリエンスを希望し、すでに音声通話を行っている場合は、KoalaSync がよりクリーンでパフォーマンスの高いソリューションです。",
"ALT_SCENER_SUMMARY_BODY": "対応サービス上の統合された交流、ビデオ、音声機能を重視するなら Scener、アカウント不要のオープンソース同期、暗号化ライブチャット、セルフホストを重視するなら KoalaSync が適しています。",
"ALT_KOSMI_TITLE": "KoalaSync と Kosmi: Netflix パーティーに代わる選択肢として、どちらがより適していますか?",
"ALT_KOSMI_META_DESC": "KoalaSync と Kosmi の正直な比較。 Kosmi の拡張機能なしセットアップが便利な理由と、KoalaSync のネイティブ ビデオ品質が優れている理由をご覧ください。",
"ALT_KOSMI_INTRO": "KoalaSyncKosmi はどちらも、Netflix、YouTube、その他のプラットフォームを友達と同期して視聴するための優れた方法を提供します。ただし、それらの基盤となるテクノロジーは根本的に異なります。 Kosmi は、拡張機能をインストールせずに Web ブラウザーで直接実行される仮想クラウド ブラウザーを使用します。 KoalaSync は、ローカル ビデオ プレーヤーを同期する軽量の拡張機能です。どのアプローチが最適であるかは、ビデオの品質、利便性、プライバシーに関する優先順位によって異なります。",
"ALT_KOSMI_INTRO": "KoalaSyncKosmiはいずれも共同視聴に利用できますが、対応サービスや交流機能が異なる別製品です。KoalaSyncはデスクトップブラウザ内の対応プレーヤーを同期します。最新の対応端末とサービスは各製品で確認してください。",
"ALT_KOSMI_WHEN_KOSMI_TITLE": "Kosmi がより良い選択である場合",
"ALT_KOSMI_WHEN_KOSMI_BODY": "Kosmi は、<strong>何もインストールしたくない</strong>場合、またはタブレットやスマートフォンなどのデバイスで視聴したい場合に無敵です。 Kosmi はクラウド上で仮想ブラウザを実行するため、参加するために必要なのは単純な Web リンクだけです。さらに、Kosmi はオールインワンのハングアウト プラットフォームです。同期ストリーミングに加えて、マルチプレイヤー ゲーム (レトロな NES/SNES クラシックやポーカーなど) をプレイしたり、内蔵 Web カメラ、音声、テキスト チャットを使用したりできます。セットアップなしでソーシャル ゲーム ナイやウォッチ パーティーを開きたい場合は、Kosmi が素晴らしいソリューションです。",
"ALT_KOSMI_WHEN_KOSMI_BODY": "ブラウザ上の交流ルーム、ゲーム、統合されたコミュニケーション機能を重視する場合はKosmiが適しています。利用できる機能は端末、コンテンツ、現在のプランによって異なる場合があります。",
"ALT_KOSMI_WHEN_KOALA_TITLE": "KoalaSync がより適切にフィットすると",
"ALT_KOSMI_WHEN_KOALA_BODY": "KoalaSync は<strong>ネイティブ再生の品質、セキュリティ、 パフォーマンス</strong>という点で優れています。<br><br>• <strong>セキュリティ リスクなし</strong>: Kosmi を使用すると、クラウド内のリモート仮想コンピュータで Netflix または Prime アカウントにログインする必要があります。 KoalaSync を使用すると、通常と同じように、自分のブラウザで公式ストリーミング Web サイトに安全にログインできます。パスワードがマシンから流出することはありません。<br>• <strong>フル ビデオ品質 (4K/HDR)</strong>: Kosmi は仮想ブラウザの画面を圧縮ビデオ フィードとしてストリーミングするため、ピクセルやバッファリングが発生する可能性があります。 KoalaSync はネイティブ プレーヤーをローカルに同期します。サブスクリプションの鮮明なフル解像度で映画をお楽しみいただけます。<br>• <strong>遅延がなく、帯域幅に優しい</strong>: KoalaSync は制御コマンド (再生/一時停止/タイムスタンプ) のみを送信するため、帯域幅の使用はほぼゼロです。 Kosmi は常に HD ビデオ フィードをストリーミングするため、低速接続では圧倒される可能性があります。<br>• <strong>ローカル ビデオ ファイル</strong>: プライベート ビデオを見たい場合は、MP4 ファイルをブラウザ タブにドラッグ アンド ドロップして同期するだけです。 Kosmi はローカル ハード ドライブ上のファイルにアクセスできません。",
"ALT_KOSMI_WHEN_KOALA_BODY": "対応プレーヤーを自分のデスクトップブラウザで同期し、エンドツーエンド暗号化ライブチャットやセルフホストrelayを使いたい場合はKoalaSyncが適しています。通話やモバイルアプリはありません。",
"ALT_KOSMI_MAIN_DIFF_TITLE": "主な違い",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmi はリモートブラウザの画面をストリーミングする<strong>仮想クラウドハングアウト</strong>であるため、拡張機能をインストールする必要はありません。 KoalaSync は、 公式ストリーミング Web サイトのネイティブ プレーヤーに直接接続する<strong>ローカル ブラウザ同期レイヤー</strong>です。 Kosmi はミニゲームやデバイスの互換性に関してより多用途ですが、KoalaSync は元のプレーヤーでより安全で、滑らかで、鮮明なストリーミング エクスペリエンスを提供します。",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmiは統合された交流ルームを重視します。KoalaSyncは対応プレーヤーを同期し、暗号化ライブテキストチャットを提供するデスクトップ拡張機能です。",
"ALT_KOSMI_LIMITS_TITLE": "正直な限界",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync は<strong>ブラウザ拡張機能をインストールする必要</strong>があり、現時点ではデスクトップ PC でのみ動作します。とにかくすでにDiscordや他の音声ツールを介して話しているという前提に基づいて設計されているため、<strong>チャットやウェブカメラは組み込まれていません</strong>。また、内蔵ゲームも提供していません。",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync は<strong>ブラウザ拡張機能をインストールする必要</strong>があり、現時点ではデスクトップ PC でのみ動作します。これには<strong>エンドツーエンドで暗号化されたライブ テキスト チャットが含まれますが、ウェブカメラ、音声、ビデオ チャットは含まれません</strong>。また、内蔵ゲームも提供していません。",
"ALT_KOSMI_SUMMARY_TITLE": "要するに",
"ALT_KOSMI_SUMMARY_BODY": "レトロなクラシックを楽しみながら、インストールせずにモバイルデバイスで簡単にアクセスできるカジュアルなゲームナイトには、Kosmi が最適です。最大限のセキュリティと最小限の帯域幅への影響で、Netflix、Prime Video、Disney+、または Plex でフル 4K 品質のプレミアム映画ナイトを視聴するには、KoalaSync が明らかに勝者です。",
"ALT_KOSMI_SUMMARY_BODY": "統合された交流機能ならKosmi、アカウント不要のオープンソース同期、暗号化チャット、任意のセルフホストならKoalaSyncを選択してください。",
"ALT_TWOSEVEN_TITLE": "KoalaSync と TwoSeven: どちらのウォッチ パーティー ソリューションがより適していますか?",
"ALT_TWOSEVEN_META_DESC": "KoalaSyncTwoSeven の正直な比較。 TwoSeven の内蔵 Web カメラがどのような場合に最適なのか、そして KoalaSync が Disney+ と Netflix に最適である理由をご確認ください。",
"ALT_TWOSEVEN_INTRO": "KoalaSync と TwoSeven は両方とも、ウォッチ パーティーに対して同じ基本的なアプローチを使用します。つまり、クラウドから圧縮されたビデオ フィードをストリーミングするのではなく、ブラウザーでオリジナルのビデオ プレーヤーを直接同期します。これにより、両方のツールで最高レベルのビデオ品質が保証されます。ただし、TwoSeven は Web カメラ チャットとサブスクリプション層が組み込まれた商用ポータルですが、KoalaSync は依然としてミニマリストで完全に無料でプライバシーに配慮した拡張機能です。ここであなたに最適なサービスを見つけてください。",
"ALT_TWOSEVEN_META_DESC": "アカウント、統合コミュニケーション、暗号化テキストチャット、セルフホストに関するKoalaSyncTwoSevenの比較です。",
"ALT_TWOSEVEN_INTRO": "どちらも同期視聴に対応しますが、サービスと要件は変更される場合があります。TwoSevenはアカウント制でコミュニケーション機能を統合しています。KoalaSyncはアカウント不要で暗号化テキストチャットを提供し、通話は内蔵しません。",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_TITLE": "TwoSeven がより良い選択である場合",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "ブラウザに直接統合されたウェブカメラと音声チャット</strong>を備えた<strong>オールインワン ソリューションが必要な場合は、TwoSeven が正しい選択です。バックグラウンドで Discord や Teamspeak を実行せずに視聴中に友達を見たい場合は、TwoSeven がこの統合をネイティブに提供します。さらに、YouTube および拡張機能なしのいくつかのオープン Web ビデオ プレーヤーでも機能します。視聴中に視覚的なつながりを共有したい 2 ~ 3 人の小グループに適しています。",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "ウェブカメラと音声の統合が重要ならTwoSevenが適しています。現在のブラウザ、アカウント、プラン、対応サービスの要件を確認してください。",
"ALT_TWOSEVEN_WHEN_KOALA_TITLE": "KoalaSync がより適切にフィットすると",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync は<strong>手数料、プライバシー、 シンプルさの完全な欠如</strong>で優れています:<br><br>• <strong>ペイウォールなし</strong>: Disney+ や Hulu などのプレミアム サービスを TwoSeven で同期して視聴するには、少なくとも 1 人のルーム メンバーが Patreon でサービスをサポートする必要があります (月額 3 ドルから)。 KoalaSync は、Netflix、Prime Video、Disney+、自己ホスト型メディア サーバー (Jellyfin、Plex、Emby) を 100% 無料で制限なしで同期します。<br>• <strong>絶対プライバシー (トラッカー/広告なし)</strong>: TwoSeven は広告でサポートされており、Google、Facebook などの複数のトラッカーを使用しています。 KoalaSync には広告やトラッカーがなく、RAM のみのリレー サーバーとのみ通信します。<br>• <strong>登録は不要</strong>: TwoSeven では、すべての参加者がアカウントを作成してログインする必要があります。KoalaSync は完全にアカウント不要です。インストールしてルームコードを入力するだけで、視聴を開始できます。<br>• <strong>軽量でリソースに優しい</strong>: TwoSeven の統合されたウェブカメラとオーディオ フィードは、CPU とネットワークに大きな負荷をかけます。 Discord などのツールに通信を任せることで、KoalaSync は非常にスムーズに動作し、バッテリー寿命を節約できます。",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync は、アカウント不要のオープンソース同期、エンドツーエンド暗号化されたライブテキストチャット、relay のセルフホストを求める場合に適しています。音声通話やウェブカメラ通話には別のアプリを使用します。",
"ALT_TWOSEVEN_MAIN_DIFF_TITLE": "主な違い",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven は、登録要件、広告、ピアツーピア Web カメラ チャット、および特定のストリーミング サービス用の Patreon ペイウォールを備えた<strong>商用プラットフォーム</strong>です。 KoalaSync は<strong>無料のオープンソース ユーティリティ</strong>で、アカウントやトラッカー、追加費用なしでブラウザのタブに直接接続できるため、ネイティブ プレーヤーで気を散らすことなく視聴できます。",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven は同期視聴にアカウント制のプラットフォームとウェブカメラ・音声機能を統合しています。KoalaSync はアカウント不要で暗号化ライブテキストチャットを備えた拡張機能で、通話は別のアプリを使います。",
"ALT_TWOSEVEN_LIMITS_TITLE": "正直な限界",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync は<strong>内蔵ウェブカメラや音声チャットを提供していません</strong>ので、Discord または同様のツールを介してすでに通話中であることを前提としています。さらに、KoalaSync にはチャット ボックス オーバーレイが組み込まれておらず、現時点ではデスクトップのみです。",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync は<strong>内蔵ウェブカメラや音声チャットを提供していない</strong>ため、お互いに会いたり話したりするには依然として Discord または同様のツールが必要です。これには、フローティングおよびドッキング可能なエンドツーエンドの暗号化されたライブ テキスト チャット オーバーレイがまれており、現在はデスクトップのみです。",
"ALT_TWOSEVEN_SUMMARY_TITLE": "要するに",
"ALT_TWOSEVEN_SUMMARY_BODY": "ブラウザのウェブカメラを介して直接お互いに会いたい、Disney+ サポートのために Patreon をサポートすることを気にしない 2 人または 3 人の小グループにとって、TwoSeven は良い選択肢です。登録なしで広告なし、完全無料、ネイティブ 4K 品質 (Netflix、Prime Video、Disney+、Jellyfin のいずれでも) でプライベートな映画鑑賞をするには、KoalaSync がよりクリーンで安全な選択肢です。",
"ALT_TWOSEVEN_SUMMARY_BODY": "ウェブカメラと音声の統合を重視するなら TwoSeven、アカウント不要のオープンソース同期、暗号化ライブチャット、セルフホストを重視するなら KoalaSync が適しています。",
"ALT_TRY_KOALASYNC": "KoalaSyncを試す",
"MOCK_HOST_CONTROL_LABEL": "ホスト操作",
"MOCK_HOST_CONTROL_TOGGLE": "自分だけが再生を操作できる",
@@ -287,5 +288,9 @@
"SELF_SHOW_SETUP": "セットアップガイドを表示",
"SELF_HIDE_SETUP": "ガイドを隠す",
"HERO_TRUST": "無料 · オープンソース · アカウント不要",
"COMP_MORE_GUIDES": "詳しい比較とその他の選択肢"
"COMP_MORE_GUIDES": "詳しい比較とその他の選択肢",
"NAV_HOME": "ホーム",
"ARIA_SELECT_LANGUAGE": "言語を選択",
"ARIA_MENU": "メニュー",
"FOOTER_GUIDES": "ガイドと比較"
}
+56 -51
View File
@@ -16,8 +16,9 @@
"NAV_HOW_IT_WORKS": "작동 방식",
"SUPPORT_BANNER_TEXT": "KoalaSync가 동영상 사이트에 액세스할 수 없나요?",
"SUPPORT_BANNER_CTA": "사이트 액세스 복구",
"SUPPORT_BANNER_DISMISS": "알림 닫기",
"HERO_TITLE": "어떤 영상이든 <span class='hero-highlight'>워치 파티</span>로.",
"HERO_SUBTITLE": "지연 없는 원격 영화의 밤. 계정 없음, 추적 없음, 광고 없. 링크 공유하고 함께 재생하세요.",
"HERO_SUBTITLE": "멀리서도 함께 비디오를 시청하세요. 계정, 추적, 광고습니다. 링크 공유하고 함께 시작하세요.",
"HERO_MASCOT_ALT": "다운로드 버튼을 내려다보고 서 있는 귀여운 코알라",
"DEMO_SYNC": "동기화됨",
"DEMO_HINT": "라이브 데모: 아무 탭에서나 재생을 누르면 KoalaSync가 두 탭을 동기화합니다.",
@@ -41,7 +42,7 @@
"USE_CASE_1_DESC": "비디오를 실시간으로 동기화하고 Discord, Zoom 또는 음성 채팅 앱으로 대화하세요. 마치 같은 방에 있는 것처럼 느껴집니다.",
"USE_CASE_2_ALT": "화면을 통해 두 마리의 원격 학생 코알라에게 강의를 진행하는 코알라 교수",
"USE_CASE_2_TITLE": "원격 학습",
"USE_CASE_2_DESC": "온라인 튜토리얼, 강의 또는 개발자 교육 스트림을 클래스메이트나 동료와 함께 분석해 보세요. 어려운 단계를 멈추고 토론할 수 있습니다.",
"USE_CASE_2_DESC": "튜토리얼, 강의 또는 교육 비디오를 함께 검토하세요. 일시정지하고 어려운 단계를 논의할 수 있습니다.",
"USE_CASE_3_ALT": "원격으로 함께 시청하는 귀여운 코알라 커플. 한 명은 PC에 앉아 있고 다른 한 명은 노트북과 함께 침대에 있으며, 둘 사이에 하트가 날아다닙니다",
"USE_CASE_3_TITLE": "장거리 연애",
"USE_CASE_3_DESC": "거리의 한계를 넘어 데이트를 즐겨보세요. 모든 줄거리의 반전을 경험하고, 같은 농담에 웃으며, 감동적인 순간을 정확히 같은 밀리초에 공유하세요.",
@@ -67,9 +68,9 @@
"COMP_SUBTITLE": "오픈 소스, 광고 없음, 개인정보 보호 중심의 서비스가 왜 함께 시청하기에 더 우수한지 확인해 보세요.",
"COMP_COL_FEATURE": "기능",
"COMP_FEAT_1_NAME": "비용 / 유료 결제",
"COMP_FEAT_1_DESC": "프리미ウム 구독, 숨겨진 수수료 또는 차단된 기능.",
"COMP_FEAT_1_DESC": "프리미 구독, 숨겨진 수수료 또는 잠긴 기능이 없습니다.",
"COMP_FEAT_1_KOALA": "100% 무료",
"COMP_FEAT_1_TELE": "유료 요금제 / 프리미ウム 제한",
"COMP_FEAT_1_TELE": "유료 요금제 / 프리미 제한",
"COMP_FEAT_2_NAME": "라이선스 / 코드 감사 가능 여부",
"COMP_FEAT_2_DESC": "소스 코드가 공개되어 있으며 자유롭게 감사할 수 있는지 여부.",
"COMP_FEAT_2_KOALA": "오픈 소스 (MIT)",
@@ -114,8 +115,8 @@
"STEP_2_ILLUS_CREATE": "+ 새 방 만들기",
"STEP_2_ILLUS_MANUAL": "수동 연결 / 고급",
"STEP_2_ILLUS_COPIED": "초대 링크가 복사되었습니다!",
"STEP_3_TITLE": "동기화 즐기기",
"STEP_3_DESC": "재생을 누르세요. 누군가 일시중지, 탐색 또는 재생을 하더라도 모든 사람의 영상이 지연 없이 실시간으로 동기화됩니다.",
"STEP_3_TITLE": "함께 시청",
"STEP_3_DESC": "재생을 시작하면 변경 사항이 방의 모든 참여자에게 공유됩니다. 네트워크와 플레이어 상태에 따라 짧은 지연이 생길 수 있습니다.",
"STEP_3_ILLUS_IN_SYNC": "동기화됨",
"SELF_TITLE": "자체 호스팅 사용자를 위한 안내",
"SELF_SUBTITLE": "공식 서버를 신뢰하기 어려우신가요? 완벽한 데이터 주권을 유지하세요. 몇 분 만에 나만의 개인 릴레이 서버를 구축할 수 있습니다.",
@@ -126,33 +127,33 @@
"BOTTOM_SUBTITLE": "KoalaSync는 100% 오픈 소스이며, 광고와 추적이 없습니다. GitHub에서 코드 베이스를 감사하거나 브라우저 확장 프로그램을 직접 설치해 보세요.",
"BOTTOM_MASCOT_ALT": "GitHub 마스코트인 Octocat 옆에서 GitHub 저장소 페이지를 들고 있는 귀여운 코알라",
"FAQ_TITLE": "자주 묻는 질문",
"FAQ_SUBTITLE": "완벽한 동기화로 비디오를 함께 시청하는 방법에 대해 알아야 할 모든 것.",
"FAQ_SUBTITLE": "지원 사이트, 동기화, 개인정보 보호, 채팅 및 오디오 처리에 대한 답변입니다.",
"FAQ_Q1": "친구들과 Netflix, Emby 또는 Jellyfin을 함께 동기화하여 시청할 수 있나요?",
"FAQ_A1": "네! KoalaSync는 Netflix, YouTube, Twitch 및 HTML5 비디오 플레이어가 있는 모든 웹사이트(Emby, Jellyfin 자체 호스팅 미디어 서버 포함)에서 작동합니다. 확장 프로그램을 설치하고 방을 만든 후 초대 링크를 공유하면 모두가 완벽하게 동기화되어 시청할 수 있습니다.",
"FAQ_A1": "KoalaSync는 Netflix, YouTube, Twitch, Emby, Jellyfin, 자체 호스팅 미디어 서버 등 표준 브라우저 비디오 플레이어를 사용하는 여러 지원 사이트에서 작동합니다. 호환성은 사이트, 브라우저, 플레이어에 따라 달라지며 사이트 업데이트 후 변경될 수 있습니다.",
"FAQ_Q_VIDEO_ACCESS": "KoalaSync가 친구들에게 동영상을 스트리밍하나요?",
"FAQ_A_VIDEO_ACCESS": "아니요. KoalaSync는 동영상 콘텐츠를 스트리밍, 호스팅, 공유하거나 접근 권한을 우회하지 않습니다. 모든 참가자는 각자의 브라우저에서 동영상을 로컬로 시청하며, Netflix 계정처럼 해당 웹사이트나 스트리밍 서비스에 대한 본인만의 접근 권한이 필요합니다. KoalaSync는 재생, 일시정지, 탐색 같은 재생 동작만 동기화합니다.",
"FAQ_Q2": "KoalaSync는 Teleparty의 무료 대체 서비스인가요?",
"FAQ_A2": "MIT 라이선스에 따라 100% 무료이며 오픈 소스입니다. 프리미ウム 등급, 제한된 기능, 광고도 없습니다. 모든 것이 포함되어 있습니다. Teleparty와 달리 KoalaSync의 릴레이 서버는 가입이나 개인정보 수집 없이 RAM에서만 실행됩니다.",
"FAQ_A2": "예. KoalaSync는 MIT 라이선스의 무료 오픈 소스 소프트웨어이며 유료 등급, 잠긴 기능, 광고 또는 필수 계정이 없습니다.",
"FAQ_Q3": "KoalaSync는 Emby, Jellyfin 및 자체 호스팅 미디어 서버에서 작동하나요?",
"FAQ_A3": ". KoalaSync는 표준 HTML5 비디오 요소를 감지하므로 Emby, Jellyfin, Plex 및 모든 사용자 정의 자체 호스팅 스트리밍 설정에서 완벽하게 작동합니다. 데이터 주권을 완전히 유지하기 위해 Docker를 통해 KoalaSync 릴레이 서버를 직접 호스팅할 수도 있습니다.",
"FAQ_A3": ". Emby, Jellyfin, Plex 및 자체 호스팅 스트리밍 환경에서 사용하는 여러 표준 비디오 플레이어를 지원합니다. 호환성은 플레이어에 따라 다릅니다. Docker KoalaSync 릴레이를 직접 호스팅할 수도 있습니다.",
"FAQ_Q4": "어떤 브라우저와 플랫폼이 KoalaSync를 지원하나요?",
"FAQ_A4": "Google Chrome, Mozilla Firefox, Microsoft Edge, Opera, Brave 및 모든 Chromium 기반 브라우저를 지원합니다. Chrome 웹 스토어 또는 Firefox 부가 기능에서 직접 설치하세요. 수동 설정이 필요 없습니다.",
"FAQ_Q5": "친구들 모두 확장 프로그램을 설치해야 하나요?",
"FAQ_A5": "네, 각 참여자 모두 KoalaSync를 설치해야 합니다. 단, 가입이나 계정 생성은 필요하지 않습니다. 초대 링크를 클릭하고, 아직 설치하지 않았다면 확장 프로그램을 설치하면 몇 초 만에 자동으로 방에 참여하게 됩니다.",
"FAQ_Q6": "제 데이터는 안전한가요? 공식 서버를 믿지 못하겠다면 어떻게 하나요?",
"FAQ_A6": "완전히 안전합니다. KoalaSync 릴레이 서버는 RAM 전용으로 작동하여, 방을 나가는 즉시 모든 세션 데이터가 영구적으로 삭제됩니다. 데이터베이스도, 로그도, 분석도, 계정도 없습니다. 완전한 제어를 원하신다면 Docker몇 분 만에 나만의 개인 릴레이 서버를 직접 호스팅할 수 있습니다. 전체 소스 코드는 오픈 소스이며 GitHub에서 확인할 수 있습니다.",
"FAQ_A6": "KoalaSync 공식 릴레이는 방, 동기화, 시청 또는 채팅 기록을 데이터베이스에 저장하지 않습니다. 활성 방 데이터는 메모리에 일시적으로만 존재하며 방이 비거나 비활성 상태로 만료되면 삭제됩니다. 보안 및 악용 방지기술적인 연결 로그가 생성될 수 있으며 보관 기간은 서버 설정에 따라 달라집니다. 채팅 메시지는 종단 간 암호화되며 서버 측 기록으로 저장되지 않습니다.",
"FAQ_Q7": "브라우저에서 비디오를 시청할 때 조용한 대화와 큰 폭발음을 개선할 수 있나요?",
"FAQ_A7": "네. KoalaSync 2.2.0에는 비디오의 조용한 부분과 시끄러운 부분 간의 차이를 줄이는 내장 오디오 압축기가 포함되어 있습니다. Web Audio API를 통해 로컬에서 실행되므로 오디오 데이터가 컴퓨터를 떠나지 않습니다. 팝업을 열고, 방을 만들고, 설정 → Audio Processing으로 이동하여 압축기를 켜고 프리셋을 선택하세요. Netflix, Prime Video, Disney+, YouTube 및 모든 HTML5 비디오 사이트에서 작동합니다.",
"FAQ_A7": "예. 조용한 부분과 큰 소리의 차이를 줄이는 오디오 컴프레서가 포함되어 있습니다. Web Audio API를 통해 기기에서 처리되며 오디오 데이터는 릴레이로 전송되지 않습니다. 설정 → 오디오 처리에서 활성화하세요.",
"HOWTO_TITLE": "KoalaSync를 사용하여 친구들과 비디오를 동기화하는 방법",
"HOWTO_DESC": "YouTube, Netflix, Twitch 등에서 친구들과 완벽하게 동기화하여 비디오를 시청하세요. 가입 없음, 추적 없.",
"HOWTO_DESC": "YouTube, Netflix, Twitch 등에서 지원되는 비디오를 함께 시청하세요. 가입이나 추적습니다.",
"HOWTO_STEP_1_NAME": "브라우저 확장 프로그램 설치",
"HOWTO_STEP_1_TEXT": "Chrome 웹 스토어 또는 Firefox 부가 기능에서 KoalaSync를 브라우저에 추가하세요. 비디오 요소가 있는 모든 웹사이트에서 작동합니다.",
"HOWTO_STEP_1_TEXT": "Chrome 웹 스토어 또는 Firefox Add-ons에서 KoalaSync를 설치하세요. 지원 페이지를 열고 확장 프로그램이 감지한 비디오를 선택하세요.",
"HOWTO_STEP_2_NAME": "방 만들기",
"HOWTO_STEP_2_TEXT": "확장 프로그램 팝업을 열고 '새 방 만들기'를 클릭하세요. 초대 링크가 클립보드에 자동으로 복사됩니다.",
"HOWTO_STEP_3_NAME": "공유 및 동기화",
"HOWTO_STEP_3_TEXT": "친구들에게 초대 링크를 보내세요. 비디오 탭을 선택하고 실시간으로 동기화된 재생을 즐기세요.",
"FOOTER_MIT": "MIT 라이선스에 따른 오픈 소스.",
"FOOTER_RAM": "서버에 데이터가 저장되지 않습니다. 순수 RAM 기반 릴레이.",
"FOOTER_RAM": "채팅 또는 시청 기록 없음. 추적 없음. 웹사이트 접속 로그는 7일 후 삭제됩니다.",
"FOOTER_LEGAL": "법적 고지",
"FOOTER_PRIVACY": "개인정보 처리방침",
"MOCK_01": "활성 방",
@@ -190,7 +191,7 @@
"FOOTER_LEGAL_LINK": "imprint",
"FOOTER_PRIVACY_LINK": "privacy",
"FOOTER_DISCLAIMER": "KoalaSync는 Netflix, Disney+, Amazon, YouTube, Twitch 또는 기타 스트리밍 플랫폼과 제휴, 보증 또는 연관되어 있지 않습니다. 모든 상표는 해당 소유자의 재산입니다.",
"FOOTER_SUPPORT": "Support KoalaSync",
"FOOTER_SUPPORT": "KoalaSync 후원",
"ALT_INDEX_TITLE": "KoalaSync - 대안 및 비교",
"ALT_INDEX_META_DESC": "KoalaSync와 다른 워치 파티 확장 프로그램 및 스트리밍 솔루션을 솔직한 비교와 자세한 가이드로 살펴보세요.",
"ALT_INDEX_HEADING": "가이드 및 비교",
@@ -200,17 +201,17 @@
"ALT_INDEX_FOOTER_LINK": "가이드 및 비교",
"ALT_TELEPARTY_TITLE": "KoalaSync 대 Teleparty: 어떤 '워치 파티(watch party)' 도구를 선택해야 할까요?",
"ALT_TELEPARTY_META_DESC": "워치 파티를 위한 KoalaSync와 Teleparty의 실용적 비교: 내장 채팅, 모바일 지원, 브라우저 영상 동기화, 자체 호스팅 미디어 서버 및 오픈 소스 활용 여부 등을 다룹니다.",
"ALT_TELEPARTY_INTRO": "KoalaSync와 Teleparty는 모두 '3, 2, 1, 재생' 같은 번거로운 절차 없이 함께 영상을 시청할 수 있게 해준다는 점에서 같은 문제를 해결해 줍니다. 다만 접근 방식에는 차이가 있습니다.<br><br>내장 채팅 기능이 필요하거나 모바일에서 시청하고 싶다면 Teleparty가 가장 합리적인 선택입니다. 반면 KoalaSync는 Discord, Teamspeak 또는 기타 음성 통화 서비스이미 대화를 나누고 있으면서, 단순히 영상 재생만 동기화하고 싶은 데스크톱 브라우저 기반의 워치 파티에 더 적합합니다.<br><br>제가 KoalaSync를 개발한 주된 이유는 유연성 때문입니다. 특정 플랫폼 목록에만 국한되지 않고, 일반적인 브라우저 비디오 플레이어를 사용하는 거의 모든 웹사이트에서 작동하도록 설계되었습니다. 여기에는 Emby, Jellyfin, Plex와 같은 자체 호스팅 환경, 로컬/비공개 도메인, 그리고 브라우저상의 수많은 일반 동영상 페이지가 포함됩니다.",
"ALT_TELEPARTY_INTRO": "KoalaSync와 Teleparty는 모두 '3, 2, 1, 재생' 같은 번거로운 절차 없이 함께 영상을 시청할 수 있게 해준다는 점에서 같은 문제를 해결해 줍니다. 다만 접근 방식에는 차이가 있습니다.<br><br>Teleparty는 더욱 통합된 시청 파티 경험을 원하거나 모바일에서 시청하려는 경우 가장 적합합니다. KoalaSync는 더 넓은 범위의 일반 브라우저 비디오 플레이어에서 데스크톱 브라우저 시청 파티설계되었습니다. 내장된 문자 채팅은 종단 간 암호화되어 실시간으로만 이루어지며, 음성 통화나 영상 통화는 별도로 유지됩니다.<br><br>제가 KoalaSync를 개발한 주된 이유는 유연성 때문입니다. 특정 플랫폼 목록에만 국한되지 않고, 일반적인 브라우저 비디오 플레이어를 사용하는 거의 모든 웹사이트에서 작동하도록 설계되었습니다. 여기에는 Emby, Jellyfin, Plex와 같은 자체 호스팅 환경, 로컬/비공개 도메인, 그리고 브라우저상의 수많은 일반 동영상 페이지가 포함됩니다.",
"ALT_TELEPARTY_WHEN_TELE_TITLE": "Teleparty가 여전히 좋은 선택이 될 수 있는 경우",
"ALT_TELEPARTY_WHEN_TELE_BODY": "워치 파티 도구 자체에 채팅 기능이 포함되기를 원한다면 Teleparty가 더 나은 선택일 수 있습니다. KoalaSync에는 현재 내장 채팅 기능이 없습니다. Discord, Teamspeak, WhatsApp, Signal 또는 일반 통화 등 다른 수단을 통해 이미 대화하고 있다는 것을 전제로 하기 때문입니다.<br><br>모바일 지원 여부도 중요한 이유 중 하나입니다. Teleparty는 전용 앱을 제공하지만, KoalaSync는 현재 데스크톱 브라우저에서만 작동합니다. 스마트폰이나 태블릿에서 Netflix 영상을 동기화하고 싶다면, 현재로서는 KoalaSync가 적합한 도구가 아닙니다.<br><br>따라서 채팅과 모바일 지원이 가장 중요한 요소라면, Teleparty가 여러분의 환경에 더 잘 맞을 것입니다.",
"ALT_TELEPARTY_WHEN_TELE_BODY": "보다 통합된 워치 파티 경험을 선호하거나 모바일 지원이 필요하다면 Teleparty가 더 나은 선택일 수 있습니다. 두 도구 모두 텍스트 채팅을 제공하므로 채팅 자체는 더 이상 결정적인 차이가 아닙니다. KoalaSync의 채팅은 종단 간 암호화되며 현재 세션에서만 실시간으로 제공됩니다. 음성 및 영상 통화에는 여전히 별도의 앱이 필요합니다.<br><br>모바일 지원 여부도 중요한 이유 중 하나입니다. Teleparty는 전용 앱을 제공하지만, KoalaSync는 현재 데스크톱 브라우저에서만 작동합니다. 스마트폰이나 태블릿에서 Netflix 영상을 동기화하고 싶다면, 현재로서는 KoalaSync가 적합한 도구가 아닙니다.<br><br>모바일 지원과 보다 통합된 플랫폼이 가장 중요하다면 Teleparty가 사용 환경에 더 잘 맞을 수 있습니다.",
"ALT_TELEPARTY_WHEN_KOALA_TITLE": "KoalaSync가 더 적합한 경우",
"ALT_TELEPARTY_WHEN_KOALA_BODY": "지원 플랫폼이 제한적인 도구 대신 유연한 데스크톱 브라우저 동기화 도구를 원하신다면 KoalaSync가 더 나은 선택입니다.<br><br>일반적인 브라우저 비디오 플레이어를 사용하는 거의 모든 웹사이트에서 작동합니다. 이는 특히 Emby, Jellyfin, Plex, 개인 미디어 서버, 로컬 도메인 또는 기존 '워치 파티(watch party)' 도구들이 흔히 지원하지 않는 소규모 동영상 사이트에서 유용합니다.<br><br>또한 무료이자 오픈 소스이며, 계정 생성도 필요하지 않습니다. 직접 릴레이 서버를 운영하고 싶다면 자체 호스팅도 가능합니다. 저는 소수의 대형 스트리밍 사이트만을 위한 폐쇄적인 플랫폼이 아니라, 제 Emby 환경에서도 잘 작동하면서 개인정보 보호를 중시하는 도구를 만들고 싶어 이 프로그램을 개발했습니다.",
"ALT_TELEPARTY_MAIN_DIFF_TITLE": "주요 차이점",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty는 채팅과 모바일 지원 기능을 갖춘 '워치 파티' 서비스에 가깝습니다. 반면 KoalaSync는 브라우저 비디오를 위한 유연한 동기화 도구(레이어)에 가깝습니다.<br><br>사소한 차이처럼 들릴 수 있지만, 실제 사용 방식에는 큰 차이가 있습니다. 채팅 기능이 포함된 종합적인 워치 파티 앱이 필요하다면 Teleparty가 적합합니다. 하지만 이미 음성 채팅 수단이 있고 단순히 브라우저 간 비디오 재생 상태만 동기화하고 싶다면, 더 간편한 KoalaSync가 알맞은 선택입니다.",
"ALT_TELEPARTY_MAIN_DIFF_BODY": "Teleparty는 모바일 지원하는 통합형 워치 파티 서비스에 가깝습니다. KoalaSync는 종단 간 암호화된 실시간 텍스트 채팅을 제공하는 유연한 데스크톱 브라우저 동기화 도구입니다.<br><br>작은 차이처럼 보이지만 사용 방식은 크게 달라집니다. 모바일 워치 파티 앱이 필요하다면 Teleparty가 적합합니다. 릴레이 서버에 읽을 수 있는 채팅 메시지를 전달하지 않으면서 자체 호스팅 미디어를 포함한 다양한 브라우저 플레이어를 지원받고 싶다면 KoalaSync가 더 간단한 선택입니다.",
"ALT_TELEPARTY_LIMITS_TITLE": "솔직하게 밝히는 한계점",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync는 마법 같은 도구가 아니며, 그 한계를 숨기지 않습니다. 현재 데스크톱 브라우저에서만 작동하며, 자체 채팅 기능이나 모바일 앱 없습니다.<br><br>호환성은 브라우저와 비디오 플레이어의 영향을 받습니다. KoalaSync는 일반적인 브라우저 비디오 플레이어라면 대부분 작동하지만, 웹사이트 측에서 플레이어, DRM 방식, 또는 브라우저 관련 제한 사항을 언제든지 변경할 수 있습니다.<br><br>KoalaSync는 Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex 또는 여기에 언급된 다른 어떤 브랜드와도 제휴 관계가 아닙니다. 해당 명칭들은 단지 일반적인 사용 사례를 설명하기 위해 사용되었습니다.",
"ALT_TELEPARTY_LIMITS_BODY": "KoalaSync는 마법 아니며 한계를 숨기려고 하지 않습니다. 현재 데스크톱 브라우저에서만 작동합니다. 엔드투엔드 암호화된 텍스트 채팅이 포함되어 있지만 음성, 영상 또는 웹캠 채팅은 지원되지 않으며 모바일 앱 없습니다.<br><br>호환성은 브라우저와 비디오 플레이어의 영향을 받습니다. KoalaSync는 일반적인 브라우저 비디오 플레이어라면 대부분 작동하지만, 웹사이트 측에서 플레이어, DRM 방식, 또는 브라우저 관련 제한 사항을 언제든지 변경할 수 있습니다.<br><br>KoalaSync는 Netflix, Disney+, Prime Video, YouTube, Teleparty, Emby, Jellyfin, Plex 또는 여기에 언급된 다른 어떤 브랜드와도 제휴 관계가 아닙니다. 해당 명칭들은 단지 일반적인 사용 사례를 설명하기 위해 사용되었습니다.",
"ALT_TELEPARTY_SUMMARY_TITLE": "요약",
"ALT_TELEPARTY_SUMMARY_BODY": "내장 채팅이나 모바일 지원 기능이 필요하다면 Teleparty가 더 적합할 수 있습니다. 반면, Emby, Jellyfin, Plex 같은 자체 호스팅 미디어 서버를 포함해 거의 모든 일반적인 동영상 플레이어와 호환되는 무료 오픈 소스 데스크톱 브라우저 동기화 도구를 원하신다면 KoalaSync가 더 나은 선택일 것입니다.",
"ALT_TELEPARTY_SUMMARY_BODY": "두 도구 모두 텍스트 채팅을 제공합니다. 모바일 지원이 필요하거나 Teleparty의 통합형 워치 파티 경험을 선호한다면 Teleparty가 더 적합할 수 있습니다. Emby, Jellyfin, Plex 같은 자체 호스팅 미디어 서버를 포함해 거의 모든 일반 비디오 플레이어에서 작동하고 종단 간 암호화된 실시간 채팅을 제공하는 무료 오픈 소스 데스크톱 브라우저 동기화를 원다면 KoalaSync가 더 적합합니다.",
"ALT_SCREEN_TITLE": "KoalaSync 대 화면 공유: 동영상 동기화가 더 나은 경우는 언제일까요?",
"ALT_SCREEN_META_DESC": "원격 영화 감상, 재생 제어, 화질, 개인정보 보호, 자막 및 브라우저 기반 시청 측면에서 KoalaSync와 화면 공유를 실용적으로 비교해 봅니다.",
"ALT_SCREEN_INTRO": "화면 공유도 훌륭한 방법이며, 솔직히 때로는 그것만으로도 충분합니다. 한 사람이 화면을 공유하고 나머지는 시청하기만 하면 되니까요.<br><br>하지만 긴 영화를 보거나 여러 에피소드를 함께 시청할 때는 다소 번거로울 수 있습니다. 화질은 화면을 공유하는 사람의 업로드 속도, CPU, 브라우저, 그리고 Discord, Teamspeak, Zoom 등의 통화 앱이 적용하는 압축 방식에 따라 달라집니다. 또한, 재생을 실제로 제어할 수 있는 사람도 한 명뿐입니다.",
@@ -221,61 +222,61 @@
"ALT_SCREEN_WHY_CLEANER_TITLE": "KoalaSync가 더 깔끔하게 느껴지는 이유",
"ALT_SCREEN_WHY_CLEANER_BODY": "KoalaSync는 화면을 직접 스트리밍하지 않습니다. 각자 자신의 브라우저에서 영상을 시청하며, KoalaSync는 재생, 일시 정지, 탐색과 같은 재생 관련 동작만 동기화합니다.<br><br>따라서 각자 자신의 기기에서 전체 화면으로 시청하거나, 플레이어가 지원하는 경우 개별 자막 및 오디오 설정을 사용할 수 있으며, 음성 채팅은 Discord, Teamspeak 또는 기타 통화 서비스를 통해 별도로 진행할 수 있습니다.<br><br>영화 감상 모임의 경우, 한 사람이 화면을 송출하는 방식보다 훨씬 깔끔한 경험을 제공합니다. 특히 Emby, Jellyfin, Plex와 같은 브라우저 기반 서비스나 브라우저에서 연 로컬 파일, 비공개 도메인, 또는 일반적인 브라우저 비디오 플레이어를 사용하는 웹사이트에서 활용하기에 매우 유용합니다.",
"ALT_SCREEN_LIMITS_TITLE": "솔직한 한계점",
"ALT_SCREEN_LIMITS_BODY": "KoalaSync는 음성 통화를 대체하는 서비스가 아닙니다. 영상을 보면서 대화하고 싶다면 평소처럼 Discord, Teamspeak, Signal, WhatsApp 등을 그대로 사용하면 됩니다.<br><br>또한 모든 참가자가 각자의 기기에서 해당 영상이나 페이지에 접속할 수 있어야 합니다. KoalaSync는 구독, 로그인, DRM, 지역 제한 또는 플랫폼 규정을 우회하지 않습니다.<br><br>현재 데스크톱 브라우저에서만 지원되며, 자체 채팅이나 음성 기능은 없습니다. 호환성은 항상 브라우저 및 동영상 플레이어의 영향을 받습니다.",
"ALT_SCREEN_LIMITS_BODY": "KoalaSync종단 간 암호화된 문자 채팅이 포함되어 있지만 음성 또는 영상 통화를 대체할 수는 없습니다. 시청하면서 대화하고 싶다면 Discord, Teamspeak, Signal, WhatsApp 등 평소에 사용하는 모든 것을 사용하세요.<br><br>또한 모든 참가자가 각자의 기기에서 해당 영상이나 페이지에 접속할 수 있어야 합니다. KoalaSync는 구독, 로그인, DRM, 지역 제한 또는 플랫폼 규정을 우회하지 않습니다.<br><br>현재 데스크톱 브라우저 전용이며 음성, 영상 또는 웹캠 채팅이 내장되어 있지 않으며 호환성은 항상 브라우저와 비디오 플레이어에 따라 다릅니다.",
"ALT_SCREEN_SUMMARY_TITLE": "요약",
"ALT_SCREEN_SUMMARY_BODY": "화질, 개인정보 보호, 개별 제어 기능 등을 크게 신경 쓰지 않고 간편하게 진행하고 싶을 때는 화면 공유가 가장 간단한 방법입니다. 반면, 모든 참가자가 각자의 브라우저에서 직접 영상을 시청하며 실제로 함께 보는 듯한 느낌을 원한다면 KoalaSync가 더 적합합니다.",
"ALT_W2G_TITLE": "KoalaSync Watch2Gether: 어떤 시계 파티 솔루션이 더 적합합니까?",
"ALT_W2G_META_DESC": "KoalaSync와 Watch2Gether의 솔직한 비교입니다. KoalaSync의 유연성이 탁월한 이유와 Watch2Gether가 여전히 YouTube에서 최고의 선택인 이유를 알아보세요.",
"ALT_W2G_TITLE": "KoalaSync Watch2Gether: 어떤 함께 보기 솔루션이 더 적합할까요?",
"ALT_W2G_META_DESC": "공유 방, 재생목록, 플레이어 동기화 및 암호화 채팅을 다루는 KoalaSync와 Watch2Gether 비교입니다.",
"ALT_W2G_INTRO": "KoalaSync와 Watch2Gether는 모두 동일한 문제를 해결합니다. 수동으로 조정하지 않고도 온라인으로 친구들과 동기화된 비디오를 볼 수 있습니다. Watch2Gether는 공유 영화의 밤을 위한 가장 오래되고 가장 인기 있는 허브 중 하나이지만 KoalaSync는 완전히 다른 가벼운 접근 방식을 취합니다. 귀하에게 적합한 도구는 무엇을, 어디서 보고 싶은지에 따라 크게 달라집니다.",
"ALT_W2G_WHEN_W2G_TITLE": "Watch2Gether가 더 나은 선택인 경우",
"ALT_W2G_WHEN_W2G_BODY": "Watch2Gether는 <strong>YouTube 시청 파티</strong>에서 최고의 앱입니다. 내장된 재생 목록 및 검색 기능을 통해 회의실에 있는 누구나 쉽게 새 비디오를 찾고, 추가하고, 재생할 수 있습니다. 모두가 함께 원활하게 다음 동영상을 시청합니다.<br><br>YouTube의 KoalaSync를 사용하면 사이트의 기본 자동 재생이 대신되어 각 사용자를 다른 추천 동영상으로 리디렉션합니다. Netflix 또는 Disney+에서 잘 작동하는 자동 다음 에피소드 기능('에피소드 로비')은 이러한 동적 추천으로 인해 YouTube에서는 작동하지 않습니다. 순수한 YouTube 밤에는 Watch2Gether가 더 나은 선택입니다.",
"ALT_W2G_WHEN_W2G_BODY": "공유 방, 검색, 재생목록 중심의 YouTube 세션에는 Watch2Gether가 더 적합할 수 있습니다. 지원은 현재 서비스와 콘텐츠 소스에 따라 달라집니다.",
"ALT_W2G_WHEN_KOALA_TITLE": "KoalaSync가 더 잘 맞을 때",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync는 <strong>실제 스트리밍 서비스</strong>(Netflix, Prime Video, Disney+) 및 자체 호스팅 미디어 서버(Jellyfin, Plex, Emby)에서 빛을 발합니다. 프리미엄 콘텐츠를 전혀 재생할 수 없는 Watch2Gether와 달리 KoalaSync는 웹사이트에서 직접 기본 플레이어를 동기화합니다.<br><br>표준 HTML5 비디오 플레이어가 있는 거의 모든 사이트에서 작동합니다. 타사 사이트에 가상 방을 설정할 필요가 없습니다. 확장 프로그램을 활성화하고 로비에 참여하고 시청을 시작하기만 하면 됩니다.",
"ALT_W2G_WHEN_KOALA_BODY": "KoalaSync는 자체 호스팅 미디어를 포함한 지원 페이지의 원래 플레이어를 별도의 함께 보기 사이트로 옮기지 않고 동기화하려는 경우에 적합합니다. 호환성은 웹사이트, 플레이어, 브라우저에 따라 달라집니다.",
"ALT_W2G_MAIN_DIFF_TITLE": "주요 차이점",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether는 채팅, 재생목록, 웹캠 지원 기능이 내장된 <strong>독립형 웹사이트</strong>입니다. KoalaSync는 일반 브라우저 탭에 직접 연결되는 <strong>미니멀리스트 브라우저 확장 프로그램</strong>입니다.<br><br>채팅 기능이 내장된 YouTube용 올인원 패키지를 찾고 계십니까? Watch2Gether를 사용하세요. 방해받지 않고 기본 품질(서버 압축 없음)로 Netflix 또는 자체 서버에서 영화를 시청하고 싶으십니까? KoalaSync는 더욱 깨끗한 솔루션입니다.",
"ALT_W2G_MAIN_DIFF_BODY": "Watch2Gether는 문자 채팅, 재생목록, 검색 및 웹캠 지원 기능을 갖춘 <strong>독립형 웹사이트</strong>입니다. KoalaSync는 일반 브라우저 탭에 직접 연결되고 종단 간 암호화된 실시간 문자 채팅도 포함하는 <strong>미니멀리스트 브라우저 확장 프로그램</strong>입니다.<br><br>재생목록, 검색, 웹캠 기능을 갖춘 YouTube용 올인원 패키지를 찾고 계십니까? Watch2Gether를 사용하세요. 방해받지 않고 기본 품질(서버 압축 없음)로 Netflix 또는 자체 서버에서 영화를 시청하고 싶으십니까? KoalaSync는 더욱 깨끗한 솔루션입니다.",
"ALT_W2G_LIMITS_TITLE": "정직한 제한",
"ALT_W2G_LIMITS_BODY": "KoalaSync에는 <strong>재생목록이나 동영상 검색 기능이 내장되어 있지 않습니다</strong>. YouTube에서 이는 다음 동영상의 자동 재생이 동기화되지 않음을 의미합니다.<br><br>또한 <strong>내장된 채팅 없으며</strong> KoalaSync는 현재 데스크톱 브라우저에서만 작동합니다. 어쨌든 Discord, Teamspeak 또는 기타 음성 도구를 통해 이미 대화하고 있다는 가정하에 설계되었습니다.",
"ALT_W2G_LIMITS_BODY": "KoalaSync에는 <strong>재생목록이나 동영상 검색 기능이 내장되어 있지 않습니다</strong>. YouTube에서 이는 다음 동영상의 자동 재생이 동기화되지 않음을 의미합니다.<br><br>KoalaSync에는 종단 간 암호화된 실시간 문자 채팅이 포함되어 있지만 내장된 웹캠, 음성 또는 영상 채팅 없으며 현재 데스크톱 브라우저에서만 작동합니다. 서로 대화하거나 영상 통화로 보고 싶다면 여전히 Discord, Teamspeak 또는 다른 통화 도구가 필요합니다.",
"ALT_W2G_SUMMARY_TITLE": "요컨대",
"ALT_W2G_SUMMARY_BODY": "재생 목록과 내장된 채팅 포함 즉석 YouTube 세션의 경우 Watch2Gether가 최선의 선택입니다. 프리미엄 스트리밍 플랫폼(예: Netflix, Prime Video, Disney+), 자체 호스팅 서버(Jellyfin, Plex, Emby) 및 최대 브라우저 유연성의 경우 KoalaSync가 더 적합하고 가벼운 도구입니다.",
"ALT_SCENER_TITLE": "KoalaSync vs Scener: 어떤 시계 파티 플랫폼이 더 잘 맞나요?",
"ALT_W2G_SUMMARY_BODY": "두 도구 모두 문자 채팅 포함합니다. 재생 목록, 검색 및 웹캠 기능을 갖춘 즉석 YouTube 세션의 경우 Watch2Gether가 더 나은 선택입니다. 프리미엄 스트리밍 플랫폼(예: Netflix, Prime Video, Disney+), 자체 호스팅 서버(Jellyfin, Plex, Emby), 종단 간 암호화된 라이브 채팅 및 최대 브라우저 유연성의 경우 KoalaSync가 더 적합하고 가벼운 도구입니다.",
"ALT_SCENER_TITLE": "KoalaSync Scener: 어떤 함께 보기 플랫폼이 더 적합할까요?",
"ALT_SCENER_META_DESC": "KoalaSync와 Scener를 정직하게 비교합니다. Scener의 웹캠 채팅이 언제 승리하는지, 왜 KoalaSync가 방해 없는 영화 감상에 가장 적합한지 알아보세요.",
"ALT_SCENER_INTRO": "KoalaSync와 Scener는 완전히 다른 두 가지 각도에서 시계 파티에 접근합니다. Scener는 영상 채팅과 대화형 룸이 내장된 가상 영화관입니다. 반면에 KoalaSync는 재생 컨트롤을 간단히 동기화하는 미니멀하고 개인 정보 보호 친화적인 브라우저 확장 프로그램입니다. 귀하에게 가장 적합한 도구는 기본적으로 내장 웹캠과 채팅 기능이 귀하에게 얼마나 중요한지에 따라 달라집니다.",
"ALT_SCENER_INTRO": "KoalaSync와 Scener는 워치 파티에 완전히 다른 방식으로 접근합니다. Scener는 영상 채팅과 대화형 룸이 내장된 가상 영화관입니다. KoalaSync는 재생 제어를 동기화하고 종단 간 암호화된 실시간 텍스트 채팅을 제공하는 가볍고 개인정보 보호 중심의 브라우저 확장 프로그램입니다. 어떤 도구가 더 잘 맞는지는 내장 웹캠, 음성, 영상 및 소셜 기능이 필요한지, 아니면 가벼운 동기화 도구를 선호하는지에 따라 달라집니다.",
"ALT_SCENER_WHEN_SCENER_TITLE": "Scener가 더 나은 선택일 때",
"ALT_SCENER_WHEN_SCENER_BODY": "브라우저에 직접 영상 및 음성 채팅이 내장된 <strong>올인원 소셜 플랫폼</strong>을 원하는 경우 Scener는 확실한 선택입니다. Scener는 플레이어와 친구의 웹캠이 나란히 표시되는 가상 공간을 만듭니다. 이는 사회적 측면이 최우선인 대규모 공개 시청 파티 또는 가상 스트리밍 이벤트에 이상적입니다. 그러나 무료 기본 버전은 광고가 지원되며 Scener는 월간 구독($5.79/월 또는 $39.99/년) 뒤에 많은 기능(예: 광고 없는 시청 및 무제한 화상 채팅)을 고정한다는 점을 명심하세요.",
"ALT_SCENER_WHEN_SCENER_BODY": "Scener는 텍스트 채팅과 Premium 회원용 영상·음성 채팅이 통합된 소셜 환경을 원할 때 더 적합합니다. 컴퓨터에서는 Chrome 확장 프로그램을 통해 정해진 스트리밍 서비스를 지원합니다.",
"ALT_SCENER_WHEN_KOALA_TITLE": "KoalaSync가 더 잘 맞을 때",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync는 <strong>동영상 품질, 개인 정보 보호 및 단순성</strong> 면에서 빛을 발합니다.<br><br>• <strong>계정 없음</strong>: 등록, 로그인 또는 프로필 관리가 필요하지 않습니다. 확장 프로그램을 활성화하고 방 코드를 공유하고 시청을 시작하세요.<br>• <strong>진정한 기본 품질(4K/HDR)</strong>: Scener는 가상 클라우드 서버에서 압축된 비디오를 스트리밍하므로 품질 손실과 지연이 발생할 수 있습니다. KoalaSync는 컴퓨터에서 기본 플레이어를 직접 동기화합니다. 즉, 스트리밍 구독의 전체 해상도로 영화를 감상할 수 있습니다.<br>• <strong>은 대역폭 사용량</strong>: 제어 데이터(재생, 일시중지, 타임스탬프)만 전송되므로 KoalaSync는 사실상 추가 대역폭을 사용하지 않습니다. Scener는 클라우드 서버를 통해 전체 동영상과 카메라 피드를 스트리밍하므로 느린 인터넷 연결과 구형 노트북에 큰 부담을 줄 수 있습니다.<br>• <strong>100% 무료이고 광고가 없습니다</strong>: 월간 구독료가 없고, 광고가 없으며, 추적기도 없습니다.<br>• <strong>로컬 동영상 파일</strong>: KoalaSync는 로컬에서 실행되므로 친구와 동기화하여 자신의 동영상 파일(예: MP4)을 볼 수도 있습니다. 파일을 브라우저 탭으로 끌어다 놓기만 하면 됩니다. Scener는 플레이어가 클라우드에서 실행되기 때문에 이 작업을 수행할 수 없습니다.",
"ALT_SCENER_WHEN_KOALA_BODY": "KoalaSync는 계정 없는 오픈 소스 동기화, 원래 플레이어에서의 재생, 종단 간 암호화된 실시간 텍스트 채팅, 선택 가능한 자체 호스팅 relay를 원할 때 적합합니다. 음성, 영상 또는 웹캠 채팅은 포함하지 않습니다.",
"ALT_SCENER_MAIN_DIFF_TITLE": "주요 차이점",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener는 가상 클라우드 브라우저로 구동되고 광고와 월간 구독을 통해 자금을 조달하는 시청 파티를 위한 <strong>상업적 소셜 네트워크</strong>입니다. KoalaSync는 브라우저 탭에 직접 연결되는 <strong>개인 정보 보호를 최우선으로 하는 무료 동기화 유틸리티</strong>입니다. 음성/영상 채팅은 Discord 또는 Teamspeak와 같은 애플리케이션에 맡기고 재생을 유연하고 비공개이며 기본 원본 품질로 유지하는 데 전적으로 중점을 둡니다.",
"ALT_SCENER_MAIN_DIFF_BODY": "Scener는 통합된 방과 소셜 기능에 중점을 둡니다. KoalaSync는 원래 플레이어의 가벼운 동기화와 종단 간 암호화된 실시간 텍스트 채팅에 중점을 둡니다.",
"ALT_SCENER_LIMITS_TITLE": "정직한 제한",
"ALT_SCENER_LIMITS_BODY": "KoalaSync는 <strong>채팅이나 웹캠 지원 기능이 내장되어 있지 않습니다</strong>. 이는 순전히 동기화 도구이며 선택한 별도의 음성 통화 도구가 필요합니다. 또한 KoalaSync는 현재 데스크톱 전용이며 로컬 비디오 파일 호환성은 브라우저가 기본적으로 비디오 형식 및 오디오 코덱 재생을 지원하는지 여부에 따라 전적으로 달라집니다.",
"ALT_SCENER_LIMITS_BODY": "KoalaSync는 <strong>종단 간 암호화된 실시간 문자 채팅을 제공하지만 웹캠, 음성 또는 영상 채팅은 제공하지 않습니다</strong>. 따라서 서로 대화하거나 만나려면 원하는 별도의 통화 도구가 필요합니다. 또한 KoalaSync는 현재 데스크톱 전용이며 로컬 비디오 파일 호환성은 브라우저가 기본적으로 비디오 형식 및 오디오 코덱 재생을 지원하는지 여부에 따라 전적으로 달라집니다.",
"ALT_SCENER_SUMMARY_TITLE": "요컨대",
"ALT_SCENER_SUMMARY_BODY": "웹캠 채팅과 소셜 기능이 내장된 대규모 시청 파티의 경우 광고와 프리미엄 페이월에 신경 쓰지 않는다면 Scener가 올바른 선택입니다. 기본 플레이어(Netflix, Prime Video, Disney+ 또는 로컬 브라우저 파일 포함)에서 완전히 무료이고, 광고가 없고, 방해 없는 경험을 원하고 이미 음성 통화 중이라면 KoalaSync가 더 깔끔하고 성능이 뛰어난 솔루션입니다.",
"ALT_SCENER_SUMMARY_BODY": "지원 서비스에서 통합 소셜·영상·음성 기능이 중요하면 Scener를, 계정 없는 오픈 소스 동기화와 암호화 채팅 및 자체 호스팅이 중요하면 KoalaSync를 선택하세요.",
"ALT_KOSMI_TITLE": "KoalaSync vs Kosmi: 어떤 Netflix 파티 대안이 더 잘 맞나요?",
"ALT_KOSMI_META_DESC": "KoalaSync와 Kosmi의 솔직한 비교입니다. Kosmi의 확장 없는 설정이 편리한 이유와 KoalaSync의 기본 비디오 품질이 뛰어난 이유를 알아보세요.",
"ALT_KOSMI_INTRO": "KoalaSync와 Kosmi는 모두 친구들과 동기화하여 Netflix, YouTube 및 기타 플랫폼을 시청할 수 있는 훌륭한 방법을 제공합니다. 그러나 기본 기술은 근본적으로 다릅니다. 코스미는 확장 프로그램 설치 없이 웹 브라우저에서 직접 실행되는 가상 클라우드 브라우저를 사용합니다. KoalaSync는 로컬 비디오 플레이어를 동기화하는 경량 확장 프로그램입니다. 귀하에게 가장 적합한 접근 방식은 비디오 품질, 편의성 및 개인정보 보호에 대한 우선순위에 따라 다릅니다.",
"ALT_KOSMI_INTRO": "KoalaSync와 Kosmi는 모두 함께 보기에 사용할 수 있지만 지원 서비스와 소셜 기능이 다른 별도 제품입니다. KoalaSync는 데스크톱 브라우저의 지원 플레이어를 동기화합니다. 최신 기기 및 플랫폼 지원은 각 서비스를 확인하세요.",
"ALT_KOSMI_WHEN_KOSMI_TITLE": "코스미가 더 나은 선택일 때",
"ALT_KOSMI_WHEN_KOSMI_BODY": "<strong>아무 것도 설치하지 않고</strong> 태블릿이나 스마트폰과 같은 기기에서 시청하고 싶다면 Kosmi는 최고의 앱입니다. 코스미는 클라우드에서 가상 브라우저를 실행하기 때문에 간단한 웹 링크만 있으면 가입할 수 있습니다. 또한 Kosmi는 올인원 행아웃 플랫폼입니다. 동기화된 스트리밍 외에도 멀티플레이어 게임(예: 레트로 NES/SNES 클래식 또는 포커)을 플레이하고 내장 웹캠, 음성 및 문자 채팅을 사용할 수 있습니다. 설정 없이 소셜 게임의 밤과 파티 시청을 원한다면 Kosmi가 환상적인 솔루션입니다.",
"ALT_KOSMI_WHEN_KOSMI_BODY": "브라우저 기반 소셜 방, 게임, 통합 커뮤니케이션 기능을 원하면 Kosmi가 더 적합할 수 있습니다. 기능은 기기, 콘텐츠 소스, 현재 요금제에 따라 달라질 수 있습니다.",
"ALT_KOSMI_WHEN_KOALA_TITLE": "KoalaSync가 더 잘 맞을 때",
"ALT_KOSMI_WHEN_KOALA_BODY": "KoalaSync는 <strong>기본 재생 품질, 보안 및 성능</strong>에서 빛을 발합니다.<br><br>• <strong>보안 위험 없음</strong>: Kosmi를 사용하면 클라우드의 원격 가상 컴퓨터에서 Netflix 또는 Prime 계정에 로그인해야 합니다. KoalaSync를 사용하면 평소와 마찬가지로 자신의 브라우저에서 공식 스트리밍 웹사이트에 안전하게 로그인할 수 있습니다. 비밀번호는 컴퓨터 외부로 유출되지 않습니다.<br>• <strong>전체 동영상 품질(4K/HDR)</strong>: Kosmi는 가상 브라우저의 화면을 압축된 동영상 피드로 스트리밍하므로 픽셀화 및 버퍼링이 발생할 수 있습니다. KoalaSync는 기본 플레이어를 로컬로 동기화합니다. 구독의 완전하고 선명한 해상도로 영화를 즐길 수 있습니다.<br>• <strong>지연이 없고 대역폭 친화적</strong>: KoalaSync는 제어 명령(재생/일시 중지/타임스탬프)만 보내기 때문에 거의 0의 대역폭을 사용합니다. Kosmi는 지속적으로 HD 동영상 피드를 스트리밍하므로 연결 속도가 느려질 수 있습니다.<br>• <strong>로컬 동영상 파일</strong>: 비공개 동영상을 시청하려면 MP4 파일을 브라우저 탭으로 드래그 앤 드롭하고 동기화하면 됩니다. Kosmi는 로컬 하드 드라이브의 파일에 접근할 수 없습니다.",
"ALT_KOSMI_WHEN_KOALA_BODY": "자신의 데스크톱 브라우저에서 지원 플레이어를 동기화하고 종단 간 암호화된 실시간 채팅이나 자체 호스팅 relay를 사용하려면 KoalaSync가 적합합니다. 통화나 모바일 앱은 없습니다.",
"ALT_KOSMI_MAIN_DIFF_TITLE": "주요 차이점",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmi는 원격 브라우저의 화면을 스트리밍하는 <strong>가상 클라우드 행아웃</strong>이므로 누구도 확장 프로그램을 설치할 필요가 없습니다. KoalaSync는 공식 스트리밍 웹사이트의 기본 플레이어에 직접 연결되는 <strong>로컬 브라우저 동기화 레이어</strong>입니다. Kosmi는 미니 게임 및 장치 호환성 측면에서 더 다재다능한 반면 KoalaSync는 원래 플레이어에서 더 안전하고 유연하며 선명한 스트리밍 경험을 제공합니다.",
"ALT_KOSMI_MAIN_DIFF_BODY": "Kosmi는 통합 소셜 방에 중점을 둡니다. KoalaSync는 지원 플레이어를 동기화하고 암호화된 실시간 텍스트 채팅을 제공하는 데스크톱 확장 프로그램입니다.",
"ALT_KOSMI_LIMITS_TITLE": "정직한 제한",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync는 <strong>브라우저 확장 프로그램을 설치해야 하며</strong> 현재 데스크톱 PC에서만 실행됩니다. 어쨌든 Discord나 다른 음성 도구를 통해 이미 대화하고 있다는 가정하에 설계되었기 때문에 <strong>내장 채팅이나 웹캠이 없습니다</strong>. 또한 내장 게임도 제공하지 않습니다.",
"ALT_KOSMI_LIMITS_BODY": "KoalaSync는 <strong>브라우저 확장 프로그램을 설치해야 하며</strong> 현재 데스크톱 PC에서만 실행됩니다. <strong>종단 간 암호화된 실시간 문자 채팅이 포함되지만 웹캠, 음성 또는 영상 채팅은 포함되지 않습니다</strong>. 또한 내장 게임도 제공하지 않습니다.",
"ALT_KOSMI_SUMMARY_TITLE": "요컨대",
"ALT_KOSMI_SUMMARY_BODY": "설치 없이 모바일 장치에서 쉽게 액세스할 수 있고 레트로 클래식을 즐길 수 있는 캐주얼한 게임 밤에는 Kosmi가 이상적입니다. 최대의 보안과 최소의 대역폭 영향으로 Netflix, Prime Video, Disney+ 또는 Plex에서 풀 4K 품질로 프리미엄 영화를 감상하는 경우 KoalaSync가 확실한 승자입니다.",
"ALT_TWOSEVEN_TITLE": "KoalaSync vs TwoSeven: 어떤 시계 파티 솔루션이 더 적합할까요?",
"ALT_TWOSEVEN_META_DESC": "KoalaSync와 TwoSeven의 솔직한 비교입니다. TwoSeven의 내장 웹캠이 성공하는 시기와 KoalaSync가 Disney+ 및 Netflix에 가장 적합한 이유를 알아보세요.",
"ALT_TWOSEVEN_INTRO": "KoalaSync와 TwoSeven은 모두 함께 시청하기 위해 동일한 기본 접근 방식을 사용합니다. 즉, 클라우드에서 압축된 비디오 피드를 스트리밍하는 대신 브라우저에서 직접 원본 비디오 플레이어를 동기화합니다. 이는 두 도구 모두에 대해 최고 수준의 비디오 품질을 보장합니다. 그러나 TwoSeven은 웹캠 채팅 및 구독 계층이 내장된 상업용 포털인 반면 KoalaSync는 최소한의 완전 무료이며 개인 정보 보호 친화적인 확장 기능을 유지합니다. 여기에서 귀하에게 가장 적합한 서비스를 찾아보세요.",
"ALT_KOSMI_SUMMARY_BODY": "통합 소셜 기능은 Kosmi를, 계정 없는 오픈 소스 데스크톱 동기화와 암호화 채팅 및 자체 호스팅은 KoalaSync를 선택하세요.",
"ALT_TWOSEVEN_TITLE": "KoalaSync TwoSeven: 어떤 함께 보기 솔루션이 더 적합할까요?",
"ALT_TWOSEVEN_META_DESC": "계정, 통합 커뮤니케이션, 암호화 텍스트 채팅 및 자체 호스팅을 다루는 KoalaSync와 TwoSeven 비교입니다.",
"ALT_TWOSEVEN_INTRO": "두 도구 모두 동기화 시청을 지원하지만 서비스와 요구 사항은 바뀔 수 있습니다. TwoSeven은 계정 기반 통합 커뮤니케이션을 제공합니다. KoalaSync는 계정 없이 암호화 텍스트 채팅을 제공하며 통화는 내장하지 않습니다.",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_TITLE": "TwoSeven이 더 나은 선택일 때",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "브라우저에서 직접 <strong>웹캠과 오디오 채팅이 통합된 올인원 솔루션</strong>을 원하는 경우 TwoSeven이 올바른 선택입니다. 백그라운드에서 Discord나 Teamspeak를 실행할 필요 없이 시청하는 동안 친구들을 보고 싶다면 TwoSeven이 이 통합 기능을 기본적으로 제공합니다. 또한 확장 기능이 없는 YouTube 및 일부 개방형 웹 비디오 플레이어에서도 작동합니다. 시청하면서 시각적 연결을 공유하려는 2~3명의 소규모 그룹에 적합합니다.",
"ALT_TWOSEVEN_WHEN_TWOSEVEN_BODY": "웹캠과 오디오 통합이 중요하면 TwoSeven이 더 적합할 수 있습니다. 현재 브라우저, 계정, 요금제 및 지원 서비스 요구 사항을 확인하세요.",
"ALT_TWOSEVEN_WHEN_KOALA_TITLE": "KoalaSync가 더 잘 맞을 때",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync는 <strong>수수료, 개인 정보 보호 및 단순성이 전혀 없음</strong>으로 빛을 발합니다.<br><br>• <strong>페이월 없음</strong>: TwoSeven에서 Disney+ 또는 Hulu와 같은 프리미엄 서비스를 동기화하려면 최소 한 명의 룸 멤버가 Patreon에서 지원해야 합니다(월 3달러부터 시작). KoalaSync는 Netflix, Prime Video, Disney+ 및 자체 호스팅 미디어 서버(Jellyfin, Plex, Emby)를 제한 없이 100% 무료로 동기화합니다.<br>• <strong>추적기/광고 없음</strong>: TwoSeven은 광고가 지원되며 Google, Facebook 등의 여러 추적기를 사용합니다. KoalaSync에는 광고나 추적기가 없으며 RAM 전용 릴레이 서버와 독점적으로 통신합니다.<br>• <strong>등록이 필요하지 않습니다</strong>: TwoSeven은 모든 참가자에게 계정을 만들고 로그인하도록 요구합니다. KoalaSync는 계정이 전혀 필요하지 않습니다. 설치하고 방 코드를 입력하고 시청을 시작하세요.<br>• <strong>경량 및 리소스 친화적</strong>: TwoSeven의 통합 웹캠과 오디오 피드는 CPU와 네트워크에 큰 부하를 줍니다. Discord와 같은 도구와의 통신을 유지함으로써 KoalaSync는 매우 원활하게 실행되고 배터리 수명을 보존니다.",
"ALT_TWOSEVEN_WHEN_KOALA_BODY": "KoalaSync는 계정 없는 오픈 소스 동기화, 종단 간 암호화된 실시간 텍스트 채팅, relay 자체 호스팅을 원할 때 적합합니다. 음성 및 웹캠 통화는 별도 앱을 사용합니다.",
"ALT_TWOSEVEN_MAIN_DIFF_TITLE": "주요 차이점",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven은 등록 요구 사항, 광고, P2P 웹캠 채팅, 특정 스트리밍 서비스에 대한 Patreon 페이월을 갖춘 <strong>상업 플랫폼</strong>입니다. KoalaSync는 계정, 추적기 또는 추가 비용 없이 브라우저 탭에 직접 연결되는 <strong>무료 오픈 소스 유틸리티</strong>로, 기본 플레이어에서 방해 없이 시청할 수 있습니다.",
"ALT_TWOSEVEN_MAIN_DIFF_BODY": "TwoSeven은 동기화 시청에 계정 기반 플랫폼과 웹캠·음성 기능을 통합합니다. KoalaSync는 계정 없이 암호화된 실시간 텍스트 채팅을 제공하는 확장 프로그램이며 통화는 별도로 진행합니다.",
"ALT_TWOSEVEN_LIMITS_TITLE": "정직한 제한",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync는 <strong>내장 웹캠이나 오디오 채팅이 없으며</strong> Discord 또는 유사한 도구를 통해 이미 통화 중이라고 가정합니다. 또한 KoalaSync에는 채팅 상자 오버레이가 내장되어 있지 않으며 현재 데스크톱 전용입니다.",
"ALT_TWOSEVEN_LIMITS_BODY": "KoalaSync는 <strong>내장 웹캠이나 오디오 채팅을 제공하지 않으므로</strong> 서로 보거나 대화하려면 Discord 또는 유사한 도구가 필요합니다. 대신 자유롭게 이동하고 좌우에 도킹할 수 있는 종단 간 암호화 실시간 텍스트 채팅 오버레이를 제공하며, 현재 데스크톱에서만 사용할 수 있습니다.",
"ALT_TWOSEVEN_SUMMARY_TITLE": "요컨대",
"ALT_TWOSEVEN_SUMMARY_BODY": "브라우저에서 직접 웹캠을 통해 서로를 보고 싶어하고 Patreon에서 Disney+ 지원을 지원하는 것을 꺼리지 않는 2~3명으로 구성된 소규모 그룹의 경우 TwoSeven이 좋은 옵션입니다. 등록 없이 네이티브 4K 품질(Netflix, Prime Video, Disney+, Jellyfin 등)로 광고 없이 완전 무료로 비공개 영화를 감상하려면 KoalaSync가 더 깨끗하고 안전한 선택입니다.",
"ALT_TWOSEVEN_SUMMARY_BODY": "통합 웹캠과 음성이 중요하면 TwoSeven을, 계정 없는 오픈 소스 동기화와 암호화 채팅 및 자체 호스팅이 중요하면 KoalaSync를 선택하세요.",
"ALT_TRY_KOALASYNC": "KoalaSync 사용해 보기",
"MOCK_HOST_CONTROL_LABEL": "호스트 제어",
"MOCK_HOST_CONTROL_TOGGLE": "나만 재생을 제어할 수 있음",
@@ -287,5 +288,9 @@
"SELF_SHOW_SETUP": "설치 가이드 보기",
"SELF_HIDE_SETUP": "가이드 숨기기",
"HERO_TRUST": "무료 · 오픈소스 · 계정 불필요",
"COMP_MORE_GUIDES": "전체 비교 및 다른 대안 보기"
"COMP_MORE_GUIDES": "전체 비교 및 다른 대안 보기",
"NAV_HOME": "홈",
"ARIA_SELECT_LANGUAGE": "언어 선택",
"ARIA_MENU": "메뉴",
"FOOTER_GUIDES": "가이드 및 비교"
}

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