diff --git a/docs/host-control-mode-EDGECASES.md b/docs/host-control-mode-EDGECASES.md index 501bd42..ebb425d 100644 --- a/docs/host-control-mode-EDGECASES.md +++ b/docs/host-control-mode-EDGECASES.md @@ -49,8 +49,9 @@ Deferred by decision (see §8): host grace period on disconnect (EC-10). - ~~**Control-mode race at join:**~~ FIXED — `hcmHandleBlocked` now treats `HOST_BLOCKED` as authoritative (adopts host-only/guest role) instead of re-checking local mode, since background only sends it to gated guests. -- **Dialog/badge text is English-only** — content.js has no i18n loader; the in-page - strings aren't localized yet. Follow-up. +- ~~**Dialog/badge text is English-only**~~ FIXED — background resolves the strings + via GET_HCM_STRINGS (it has the i18n loader); content fetches them on init with + English fallback. 6 new keys (HCM_DIALOG_*/HCM_BADGE_*) across all 15 locales. --- diff --git a/extension/background.js b/extension/background.js index f372993..f280fe8 100644 --- a/extension/background.js +++ b/extension/background.js @@ -1731,6 +1731,20 @@ async function handleAsyncMessage(message, sender, sendResponse) { } else if (message.type === 'REQUEST_HOST_SYNC') { // content.js resync: hand back the host's extrapolated current position. sendResponse({ target: getHostSyncTarget() }); + } else if (message.type === 'GET_HCM_STRINGS') { + // Localized strings for the in-page host-control dialog/badge. content.js + // has no i18n loader of its own, so background resolves them here. + const settings = await chrome.storage.local.get(['locale']); + const lang = settings.locale || getSystemLanguage(); + await loadLocale(lang); + sendResponse({ + title: getMessage('HCM_DIALOG_TITLE'), + body: getMessage('HCM_DIALOG_BODY'), + stay: getMessage('HCM_DIALOG_STAY'), + solo: getMessage('HCM_DIALOG_SOLO'), + badge: getMessage('HCM_BADGE_SOLO'), + resync: getMessage('HCM_BADGE_RESYNC') + }); } else if (message.type === 'HCM_DESYNC_STATE') { // content.js tells us whether the local user chose to watch on their own. // Only accept from the currently selected tab. diff --git a/extension/content.js b/extension/content.js index daa4d80..2351333 100644 --- a/extension/content.js +++ b/extension/content.js @@ -115,6 +115,16 @@ let hcmLastUserGestureAt = 0; // for deliberate-vs-involuntary classification let hcmBufferingUntil = 0; // set on 'waiting' — buffering grace window let hcmDialogTimer = null; // 8s auto-stay timer — cleared on dialog replace (H-4) + // Localized strings for the in-page dialog/badge (content has no i18n loader; + // background resolves them via GET_HCM_STRINGS on init). English fallbacks here. + const hcmStrings = { + title: 'KoalaSync · Host controls this room', + body: 'Only the host can control playback in this room. Keep watching together, or watch on your own?', + stay: 'Stay in sync', + solo: 'Watch on my own', + badge: 'Watching on your own', + resync: 'Resync' + }; const HCM_USER_GESTURE_MS = 1000; const HCM_BUFFERING_GRACE_MS = 1500; const HCM_SNAP_BACK_COOLDOWN_MS = 1000; @@ -256,12 +266,11 @@ const wrap = hcmEl('div', 'position:fixed;z-index:2147483647;left:50%;bottom:32px;transform:translateX(-50%);background:#1f2937;color:#f9fafb;font:14px/1.4 system-ui,sans-serif;padding:16px 18px;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,.45);max-width:360px;border:1px solid #374151'); wrap.setAttribute('role', 'dialog'); - const verb = action === EVENTS.SEEK ? 'jumped' : 'paused'; - const title = hcmEl('div', 'font-weight:600;margin-bottom:6px', 'KoalaSync · Host controls this room'); - const body = hcmEl('div', 'margin-bottom:12px;color:#d1d5db', `You ${verb} your player. Only the host can control the group. Keep watching together, or watch on your own?`); + const title = hcmEl('div', 'font-weight:600;margin-bottom:6px', hcmStrings.title); + const body = hcmEl('div', 'margin-bottom:12px;color:#d1d5db', hcmStrings.body); const btnRow = hcmEl('div', 'display:flex;gap:8px;justify-content:flex-end'); - const soloBtn = hcmEl('button', 'background:#374151;color:#f9fafb;border:0;padding:8px 12px;border-radius:8px;cursor:pointer', 'Watch on my own'); - const stayBtn = hcmEl('button', 'background:#10b981;color:#062a20;border:0;padding:8px 12px;border-radius:8px;cursor:pointer;font-weight:600', 'Stay in sync'); + const soloBtn = hcmEl('button', 'background:#374151;color:#f9fafb;border:0;padding:8px 12px;border-radius:8px;cursor:pointer', hcmStrings.solo); + const stayBtn = hcmEl('button', 'background:#10b981;color:#062a20;border:0;padding:8px 12px;border-radius:8px;cursor:pointer;font-weight:600', hcmStrings.stay); btnRow.append(soloBtn, stayBtn); wrap.append(title, body, btnRow); root.appendChild(wrap); @@ -326,7 +335,7 @@ const host = hcmEl('div', 'all:initial'); const root = host.attachShadow({ mode: 'open' }); const b = hcmEl('div', 'position:fixed;z-index:2147483646;right:16px;bottom:16px;background:#b45309;color:#fff;font:13px/1.3 system-ui,sans-serif;padding:8px 12px;border-radius:10px;box-shadow:0 6px 20px rgba(0,0,0,.4);cursor:pointer;display:flex;align-items:center;gap:8px'); - b.append(hcmEl('span', null, '● Watching on your own'), hcmEl('span', 'text-decoration:underline', 'Resync')); + b.append(hcmEl('span', null, '● ' + hcmStrings.badge), hcmEl('span', 'text-decoration:underline', hcmStrings.resync)); b.addEventListener('click', hcmExitDesync); root.appendChild(b); document.body.appendChild(host); @@ -1448,4 +1457,13 @@ } }); + // Pull localized strings for the in-page dialog/badge (English fallback above). + chrome.runtime.sendMessage({ type: 'GET_HCM_STRINGS' }, (res) => { + if (chrome.runtime.lastError || !res) return; + Object.keys(hcmStrings).forEach(k => { if (res[k]) hcmStrings[k] = res[k]; }); + // If the badge is already showing (early desync), re-render it with the + // localized text now that we have it. + if (hcmBadgeHost) { hcmRemoveBadge(); hcmShowBadge(); } + }); + })(); diff --git a/extension/locales/de.json b/extension/locales/de.json index cd21b0e..1313c47 100644 --- a/extension/locales/de.json +++ b/extension/locales/de.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "Gast", "BADGE_DESYNCED": "Solo", "TOOLTIP_PEER_DESYNCED": "Schaut alleine — ignoriert die Befehle des Hosts", + "HCM_DIALOG_TITLE": "KoalaSync · Der Host steuert diesen Raum", + "HCM_DIALOG_BODY": "Nur der Host kann die Wiedergabe in diesem Raum steuern. Weiter gemeinsam schauen oder allein weiterschauen?", + "HCM_DIALOG_STAY": "Synchron bleiben", + "HCM_DIALOG_SOLO": "Allein weiterschauen", + "HCM_BADGE_SOLO": "Du schaust allein", + "HCM_BADGE_RESYNC": "Neu sync", "NO_PEERS_CONNECTED": "Keine Teilnehmer verbunden", "BTN_LEAVE_ROOM": "Raum verlassen", "LABEL_SELECT_VIDEO": "Video auswählen", diff --git a/extension/locales/en.json b/extension/locales/en.json index 358e6c8..3bc6356 100644 --- a/extension/locales/en.json +++ b/extension/locales/en.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "Guest", "BADGE_DESYNCED": "Solo", "TOOLTIP_PEER_DESYNCED": "Watching on their own — host's commands are ignored", + "HCM_DIALOG_TITLE": "KoalaSync · Host controls this room", + "HCM_DIALOG_BODY": "Only the host can control playback in this room. Keep watching together, or watch on your own?", + "HCM_DIALOG_STAY": "Stay in sync", + "HCM_DIALOG_SOLO": "Watch on my own", + "HCM_BADGE_SOLO": "Watching on your own", + "HCM_BADGE_RESYNC": "Resync", "NO_PEERS_CONNECTED": "No peers connected", "BTN_LEAVE_ROOM": "Leave Room", "LABEL_SELECT_VIDEO": "Select Video", diff --git a/extension/locales/es.json b/extension/locales/es.json index af95d76..2c1f2d6 100644 --- a/extension/locales/es.json +++ b/extension/locales/es.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "Invitado", "BADGE_DESYNCED": "Solo", "TOOLTIP_PEER_DESYNCED": "Viendo por su cuenta — ignora los comandos del anfitrión", + "HCM_DIALOG_TITLE": "KoalaSync · El anfitrión controla esta sala", + "HCM_DIALOG_BODY": "Solo el anfitrión puede controlar la reproducción en esta sala. ¿Seguir viendo juntos o ver por tu cuenta?", + "HCM_DIALOG_STAY": "Seguir sincronizado", + "HCM_DIALOG_SOLO": "Ver por mi cuenta", + "HCM_BADGE_SOLO": "Viendo por tu cuenta", + "HCM_BADGE_RESYNC": "Resincronizar", "NO_PEERS_CONNECTED": "Sin participantes conectados", "BTN_LEAVE_ROOM": "Salir de la sala", "LABEL_SELECT_VIDEO": "Seleccionar video", diff --git a/extension/locales/fr.json b/extension/locales/fr.json index 944912c..0ff7b82 100644 --- a/extension/locales/fr.json +++ b/extension/locales/fr.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "Invité", "BADGE_DESYNCED": "Solo", "TOOLTIP_PEER_DESYNCED": "Regarde de son côté — ignore les commandes de l'hôte", + "HCM_DIALOG_TITLE": "KoalaSync · L'hôte contrôle ce salon", + "HCM_DIALOG_BODY": "Seul l'hôte peut contrôler la lecture dans ce salon. Continuer à regarder ensemble, ou regarder de votre côté ?", + "HCM_DIALOG_STAY": "Rester synchronisé", + "HCM_DIALOG_SOLO": "Regarder de mon côté", + "HCM_BADGE_SOLO": "Vous regardez seul", + "HCM_BADGE_RESYNC": "Resynchroniser", "NO_PEERS_CONNECTED": "Aucun membre connecté", "BTN_LEAVE_ROOM": "Quitter le salon", "LABEL_SELECT_VIDEO": "Choisir une vidéo", diff --git a/extension/locales/it.json b/extension/locales/it.json index c911ed4..b5780b7 100644 --- a/extension/locales/it.json +++ b/extension/locales/it.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "Ospite", "BADGE_DESYNCED": "Solo", "TOOLTIP_PEER_DESYNCED": "Guarda per conto proprio — ignora i comandi dell'host", + "HCM_DIALOG_TITLE": "KoalaSync · L'host controlla questa stanza", + "HCM_DIALOG_BODY": "Solo l'host può controllare la riproduzione in questa stanza. Continuare a guardare insieme o guardare per conto tuo?", + "HCM_DIALOG_STAY": "Resta sincronizzato", + "HCM_DIALOG_SOLO": "Guarda per conto mio", + "HCM_BADGE_SOLO": "Stai guardando da solo", + "HCM_BADGE_RESYNC": "Risincronizza", "NO_PEERS_CONNECTED": "Nessun partecipante connesso", "BTN_LEAVE_ROOM": "Lascia Stanza", "LABEL_SELECT_VIDEO": "Seleziona Video", diff --git a/extension/locales/ja.json b/extension/locales/ja.json index 9b5bcda..f4f6e66 100644 --- a/extension/locales/ja.json +++ b/extension/locales/ja.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "ゲスト", "BADGE_DESYNCED": "ソロ", "TOOLTIP_PEER_DESYNCED": "単独で視聴中 — ホストのコマンドを無視しています", + "HCM_DIALOG_TITLE": "KoalaSync · ホストがこのルームを操作中", + "HCM_DIALOG_BODY": "このルームではホストのみが再生を操作できます。一緒に視聴を続けますか、それとも自分だけで視聴しますか?", + "HCM_DIALOG_STAY": "同期を維持", + "HCM_DIALOG_SOLO": "自分だけで視聴", + "HCM_BADGE_SOLO": "自分だけで視聴中", + "HCM_BADGE_RESYNC": "再同期", "NO_PEERS_CONNECTED": "接続しているメンバーはいません", "BTN_LEAVE_ROOM": "ルームを退室", "LABEL_SELECT_VIDEO": "ビデオを選択", diff --git a/extension/locales/ko.json b/extension/locales/ko.json index 595db59..f9bbe80 100644 --- a/extension/locales/ko.json +++ b/extension/locales/ko.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "게스트", "BADGE_DESYNCED": "단독", "TOOLTIP_PEER_DESYNCED": "혼자 시청 중 — 호스트 명령을 무시합니다", + "HCM_DIALOG_TITLE": "KoalaSync · 호스트가 이 방을 제어합니다", + "HCM_DIALOG_BODY": "이 방에서는 호스트만 재생을 제어할 수 있습니다. 계속 함께 보시겠어요, 아니면 혼자 보시겠어요?", + "HCM_DIALOG_STAY": "동기화 유지", + "HCM_DIALOG_SOLO": "혼자 보기", + "HCM_BADGE_SOLO": "혼자 시청 중", + "HCM_BADGE_RESYNC": "다시 동기화", "NO_PEERS_CONNECTED": "연결된 참여자가 없습니다", "BTN_LEAVE_ROOM": "방 나가기", "LABEL_SELECT_VIDEO": "비디오 선택", diff --git a/extension/locales/nl.json b/extension/locales/nl.json index 4c808ac..fe6ede8 100644 --- a/extension/locales/nl.json +++ b/extension/locales/nl.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "Gast", "BADGE_DESYNCED": "Solo", "TOOLTIP_PEER_DESYNCED": "Zelfstandig aan het kijken — negeert de commando's van de host", + "HCM_DIALOG_TITLE": "KoalaSync · De host bedient deze kamer", + "HCM_DIALOG_BODY": "Alleen de host kan de weergave in deze kamer bedienen. Samen blijven kijken of zelf verder kijken?", + "HCM_DIALOG_STAY": "Gesynchroniseerd blijven", + "HCM_DIALOG_SOLO": "Zelf verder kijken", + "HCM_BADGE_SOLO": "Je kijkt zelfstandig", + "HCM_BADGE_RESYNC": "Opnieuw sync", "NO_PEERS_CONNECTED": "Geen deelnemers verbonden", "BTN_LEAVE_ROOM": "Kamer verlaten", "LABEL_SELECT_VIDEO": "Selecteer video", diff --git a/extension/locales/pl.json b/extension/locales/pl.json index b5f3480..e28a7ce 100644 --- a/extension/locales/pl.json +++ b/extension/locales/pl.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "Gość", "BADGE_DESYNCED": "Solo", "TOOLTIP_PEER_DESYNCED": "Ogląda samodzielnie — ignoruje polecenia hosta", + "HCM_DIALOG_TITLE": "KoalaSync · Host steruje tym pokojem", + "HCM_DIALOG_BODY": "Tylko host może sterować odtwarzaniem w tym pokoju. Oglądać dalej razem czy oglądać samodzielnie?", + "HCM_DIALOG_STAY": "Pozostań zsynchronizowany", + "HCM_DIALOG_SOLO": "Oglądaj samodzielnie", + "HCM_BADGE_SOLO": "Oglądasz samodzielnie", + "HCM_BADGE_RESYNC": "Zsynchronizuj", "NO_PEERS_CONNECTED": "Brak połączonych uczestników", "BTN_LEAVE_ROOM": "Opuść pokój", "LABEL_SELECT_VIDEO": "Wybierz wideo", diff --git a/extension/locales/pt-BR.json b/extension/locales/pt-BR.json index a8b2e31..d353142 100644 --- a/extension/locales/pt-BR.json +++ b/extension/locales/pt-BR.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "Convidado", "BADGE_DESYNCED": "Solo", "TOOLTIP_PEER_DESYNCED": "Assistindo por conta própria — ignora os comandos do anfitrião", + "HCM_DIALOG_TITLE": "KoalaSync · O anfitrião controla esta sala", + "HCM_DIALOG_BODY": "Somente o anfitrião pode controlar a reprodução nesta sala. Continuar assistindo juntos ou assistir por conta própria?", + "HCM_DIALOG_STAY": "Manter sincronizado", + "HCM_DIALOG_SOLO": "Assistir por conta própria", + "HCM_BADGE_SOLO": "Assistindo por conta própria", + "HCM_BADGE_RESYNC": "Ressincronizar", "NO_PEERS_CONNECTED": "Sem participantes conectados", "BTN_LEAVE_ROOM": "Sair da sala", "LABEL_SELECT_VIDEO": "Selecionar vídeo", diff --git a/extension/locales/pt.json b/extension/locales/pt.json index 693b0ef..ab5544d 100644 --- a/extension/locales/pt.json +++ b/extension/locales/pt.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "Convidado", "BADGE_DESYNCED": "Solo", "TOOLTIP_PEER_DESYNCED": "A ver por conta própria — ignora os comandos do anfitrião", + "HCM_DIALOG_TITLE": "KoalaSync · O anfitrião controla esta sala", + "HCM_DIALOG_BODY": "Só o anfitrião pode controlar a reprodução nesta sala. Continuar a ver em conjunto ou ver por conta própria?", + "HCM_DIALOG_STAY": "Manter sincronizado", + "HCM_DIALOG_SOLO": "Ver por minha conta", + "HCM_BADGE_SOLO": "A ver por tua conta", + "HCM_BADGE_RESYNC": "Ressincronizar", "NO_PEERS_CONNECTED": "Nenhum participante ligado", "BTN_LEAVE_ROOM": "Sair da Sala", "LABEL_SELECT_VIDEO": "Selecionar Vídeo", diff --git a/extension/locales/ru.json b/extension/locales/ru.json index c17d3fb..66a222c 100644 --- a/extension/locales/ru.json +++ b/extension/locales/ru.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "Гость", "BADGE_DESYNCED": "Соло", "TOOLTIP_PEER_DESYNCED": "Смотрит отдельно — игнорирует команды ведущего", + "HCM_DIALOG_TITLE": "KoalaSync · Комнатой управляет ведущий", + "HCM_DIALOG_BODY": "Управлять воспроизведением в этой комнате может только ведущий. Продолжить смотреть вместе или смотреть самостоятельно?", + "HCM_DIALOG_STAY": "Оставаться синхронно", + "HCM_DIALOG_SOLO": "Смотреть самостоятельно", + "HCM_BADGE_SOLO": "Вы смотрите самостоятельно", + "HCM_BADGE_RESYNC": "Синхронизировать", "NO_PEERS_CONNECTED": "Нет подключенных участников", "BTN_LEAVE_ROOM": "Выйти из комнаты", "LABEL_SELECT_VIDEO": "Выбрать видео", diff --git a/extension/locales/tr.json b/extension/locales/tr.json index f075493..ff4918b 100644 --- a/extension/locales/tr.json +++ b/extension/locales/tr.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "Misafir", "BADGE_DESYNCED": "Solo", "TOOLTIP_PEER_DESYNCED": "Tek başına izliyor — sunucu komutlarını yok sayar", + "HCM_DIALOG_TITLE": "KoalaSync · Bu odayı sunucu kontrol ediyor", + "HCM_DIALOG_BODY": "Bu odada oynatmayı yalnızca sunucu kontrol edebilir. Birlikte izlemeye devam mı, yoksa kendi başına mı izlemek istiyorsun?", + "HCM_DIALOG_STAY": "Senkron kal", + "HCM_DIALOG_SOLO": "Kendi başıma izle", + "HCM_BADGE_SOLO": "Kendi başına izliyorsun", + "HCM_BADGE_RESYNC": "Yeniden senkronize et", "NO_PEERS_CONNECTED": "Bağlı kimse yok", "BTN_LEAVE_ROOM": "Odadan Ayrıl", "LABEL_SELECT_VIDEO": "Video Seç", diff --git a/extension/locales/uk.json b/extension/locales/uk.json index 40a5bc2..a76366d 100644 --- a/extension/locales/uk.json +++ b/extension/locales/uk.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "Гість", "BADGE_DESYNCED": "Соло", "TOOLTIP_PEER_DESYNCED": "Дивиться окремо — ігнорує команди ведучого", + "HCM_DIALOG_TITLE": "KoalaSync · Кімнатою керує ведучий", + "HCM_DIALOG_BODY": "Керувати відтворенням у цій кімнаті може лише ведучий. Продовжити дивитися разом чи дивитися самостійно?", + "HCM_DIALOG_STAY": "Залишатися синхронно", + "HCM_DIALOG_SOLO": "Дивитися самостійно", + "HCM_BADGE_SOLO": "Ви дивитеся самостійно", + "HCM_BADGE_RESYNC": "Синхронізувати", "NO_PEERS_CONNECTED": "Немає підключених однорангових пристроїв", "BTN_LEAVE_ROOM": "Вийти з кімнати", "LABEL_SELECT_VIDEO": "Виберіть Відео", diff --git a/extension/locales/zh.json b/extension/locales/zh.json index 5ba0b55..9169d7b 100644 --- a/extension/locales/zh.json +++ b/extension/locales/zh.json @@ -52,6 +52,12 @@ "BADGE_GUEST": "访客", "BADGE_DESYNCED": "单独", "TOOLTIP_PEER_DESYNCED": "单独观看中 — 忽略主持人的命令", + "HCM_DIALOG_TITLE": "KoalaSync · 主持人控制本房间", + "HCM_DIALOG_BODY": "本房间只有主持人可以控制播放。继续一起观看,还是自己单独观看?", + "HCM_DIALOG_STAY": "保持同步", + "HCM_DIALOG_SOLO": "自己单独观看", + "HCM_BADGE_SOLO": "正在单独观看", + "HCM_BADGE_RESYNC": "重新同步", "NO_PEERS_CONNECTED": "没有对等点连接", "BTN_LEAVE_ROOM": "离开房间", "LABEL_SELECT_VIDEO": "选择视频",