From fd47eb82b9b728ac86c110d9f55941241613ed68 Mon Sep 17 00:00:00 2001 From: Koala <6156589+Shik3i@users.noreply.github.com> Date: Mon, 1 Jun 2026 01:05:46 +0200 Subject: [PATCH] feat(extension): implement full dynamic i18n support and automated consistency checks --- extension/background.js | 30 +++-- extension/i18n.js | 89 ++++++++++++++ extension/locales/de.json | 177 ++++++++++++++++++++++++++++ extension/locales/en.json | 177 ++++++++++++++++++++++++++++ extension/locales/es.json | 177 ++++++++++++++++++++++++++++ extension/locales/fr.json | 177 ++++++++++++++++++++++++++++ extension/locales/i18n_plan.md | 204 +++++++++++++++++++++++++++++++++ extension/locales/pt-BR.json | 177 ++++++++++++++++++++++++++++ extension/locales/ru.json | 177 ++++++++++++++++++++++++++++ extension/popup.html | 144 ++++++++++++----------- extension/popup.js | 113 +++++++++++------- scripts/test-locales.js | 55 +++++++++ 12 files changed, 1579 insertions(+), 118 deletions(-) create mode 100644 extension/i18n.js create mode 100644 extension/locales/de.json create mode 100644 extension/locales/en.json create mode 100644 extension/locales/es.json create mode 100644 extension/locales/fr.json create mode 100644 extension/locales/i18n_plan.md create mode 100644 extension/locales/pt-BR.json create mode 100644 extension/locales/ru.json create mode 100644 scripts/test-locales.js diff --git a/extension/background.js b/extension/background.js index c8e952b..7810752 100644 --- a/extension/background.js +++ b/extension/background.js @@ -1,5 +1,7 @@ import { EVENTS, PROTOCOL_VERSION, OFFICIAL_SERVER_URL, OFFICIAL_SERVER_TOKEN, APP_VERSION, EPISODE_LOBBY_TIMEOUT, FORCE_SYNC_TIMEOUT } from './shared/constants.js'; import { generateUsername } from './shared/names.js'; +import { loadLocale, getMessage } from './i18n.js'; + // --- State Management --- let socket = null; @@ -471,26 +473,38 @@ function updateBadgeStatus() { } function showNotification(senderName, action) { - chrome.storage.sync.get(['browserNotifications'], (settings) => { + chrome.storage.sync.get(['browserNotifications', 'locale'], async (settings) => { if (!settings.browserNotifications) return; - const label = action === 'play' ? 'started playback' : - action === 'pause' ? 'paused playback' : - action === 'seek' ? 'seeked the video' : - action === 'force_sync_prepare' ? 'started force sync' : - action === 'force_sync_execute' ? 'synchronized everyone' : action; - + const lang = settings.locale || 'en'; + await loadLocale(lang); + + let labelKey = ''; + if (action === 'play') labelKey = 'NOTIF_PLAY'; + else if (action === 'pause') labelKey = 'NOTIF_PAUSE'; + else if (action === 'seek') labelKey = 'NOTIF_SEEK'; + else if (action === 'force_sync_prepare') labelKey = 'NOTIF_FORCE_PREPARE'; + else if (action === 'force_sync_execute') labelKey = 'NOTIF_FORCE_EXECUTE'; + + const label = labelKey ? getMessage(labelKey) : action; + let displayName = senderName || 'A peer'; if (currentRoom && Array.isArray(currentRoom.peers)) { const peer = currentRoom.peers.find(p => (p.peerId || p) === senderName); if (peer && peer.username) displayName = peer.username; } + if (displayName === 'You' || displayName === 'YOU') { + displayName = getMessage('LABEL_YOU') || 'YOU'; + } + + const message = getMessage('TOAST_PEER_ACTION', { name: displayName, action: label }) + '.'; + chrome.notifications.create(`sync_${Date.now()}`, { type: 'basic', iconUrl: 'icons/icon128.png', title: 'KoalaSync', - message: `${displayName} ${label}.`, + message: message, priority: 1 }); }); diff --git a/extension/i18n.js b/extension/i18n.js new file mode 100644 index 0000000..94a0c15 --- /dev/null +++ b/extension/i18n.js @@ -0,0 +1,89 @@ +// extension/i18n.js +export const SUPPORTED_LANGUAGES = ['en', 'de', 'fr', 'es', 'pt-BR', 'ru']; +export const DEFAULT_LANGUAGE = 'en'; + +let activeDictionary = {}; + +/** + * Resolves, loads, and merges the target language with the English baseline fallback. + * @param {string} langCode - Target language (e.g. 'de') + */ +export async function loadLocale(langCode) { + const resolvedLang = SUPPORTED_LANGUAGES.includes(langCode) ? langCode : DEFAULT_LANGUAGE; + + try { + // Load Baseline English + const enResponse = await fetch(chrome.runtime.getURL(`locales/${DEFAULT_LANGUAGE}.json`)); + const enDict = await enResponse.json(); + + if (resolvedLang === DEFAULT_LANGUAGE) { + activeDictionary = enDict; + return; + } + + // Load Target Locale + const targetResponse = await fetch(chrome.runtime.getURL(`locales/${resolvedLang}.json`)); + const targetDict = await targetResponse.json(); + + // Airtight Fallback Merge: target overrides en, missing elements fallback to en + activeDictionary = Object.assign({}, enDict, targetDict); + } catch (err) { + console.error('[i18n] Failed to load locale. Defaulting to English:', err); + // Fallback directly to static English if fetching fails + try { + const enResponse = await fetch(chrome.runtime.getURL(`locales/${DEFAULT_LANGUAGE}.json`)); + activeDictionary = await enResponse.json(); + } catch (_) { + activeDictionary = {}; + } + } +} + +/** + * Returns the translated string for a given key. Supports optional value interpolation. + * @param {string} key - Dictionary key + * @param {object} [placeholders] - Key-value map for replacements (e.g., { name: 'Alice' }) + * @returns {string} Translated string or the key itself + */ +export function getMessage(key, placeholders = null) { + let msg = activeDictionary[key] || key; + if (placeholders && typeof placeholders === 'object') { + for (const [k, v] of Object.entries(placeholders)) { + msg = msg.replace(new RegExp(`{${k}}`, 'g'), v); + } + } + return msg; +} + +/** + * Performs dynamic DOM replacements for elements carrying data-i18n attributes. + */ +export function translateDOM() { + // 1. Text Content + document.querySelectorAll('[data-i18n]').forEach(el => { + const key = el.getAttribute('data-i18n'); + const translated = getMessage(key); + + // Special case: Preserve logo image elements inside headers (like h1 logo) + const img = el.querySelector('img'); + if (img) { + el.innerHTML = ''; + el.appendChild(img); + el.appendChild(document.createTextNode(' ' + translated)); + } else { + el.textContent = translated; + } + }); + + // 2. Tooltips (titles) + document.querySelectorAll('[data-i18n-title]').forEach(el => { + const key = el.getAttribute('data-i18n-title'); + el.setAttribute('title', getMessage(key)); + }); + + // 3. Placeholders + document.querySelectorAll('[data-i18n-placeholder]').forEach(el => { + const key = el.getAttribute('data-i18n-placeholder'); + el.setAttribute('placeholder', getMessage(key)); + }); +} diff --git a/extension/locales/de.json b/extension/locales/de.json new file mode 100644 index 0000000..0c03d28 --- /dev/null +++ b/extension/locales/de.json @@ -0,0 +1,177 @@ +{ + "LANG_CODE": "de", + "HTML_CLASS": "lang-de", + + "APP_TITLE": "KoalaSync", + "TAB_ROOM": "Raum", + "TAB_ROOM_TOOLTIP": "Raum-Einstellungen und Verbindung", + "TAB_SYNC": "Sync", + "TAB_SYNC_TOOLTIP": "Video-Synchronisations-Steuerung und Aktionen", + "TAB_SETTINGS": "Optionen", + "TAB_SETTINGS_TOOLTIP": "Erweiterungseinstellungen", + "TAB_STATUS": "Status", + "TAB_STATUS_TOOLTIP": "Erweiterte Diagnose & Protokolle", + + "BTN_CREATE_ROOM": "+ Neuer Raum", + "MANUAL_CONNECT_HEADER": "Manuell verbinden / Erweitert", + "LABEL_SERVER": "Server", + "BTN_SERVER_OFFICIAL": "Offiziell", + "BTN_SERVER_OFFICIAL_TOOLTIP": "Verwende den offiziellen, zuverlässigen Server", + "BTN_SERVER_CUSTOM": "Eigener", + "BTN_SERVER_CUSTOM_TOOLTIP": "Verbinde dich mit deinem eigenen, selbstgehosteten Server", + "PLACEHOLDER_SERVER_URL": "wss://dein-server:3000", + "LABEL_ROOM_ID": "Raum-ID", + "LABEL_ROOM_ID_TOOLTIP": "Der eindeutige Identifikator für deinen Sync-Raum", + "PLACEHOLDER_ROOM_ID": "Raum-ID eingeben", + "PLACEHOLDER_ROOM_ID_TOOLTIP": "Die eindeutige ID des Raums, dem du beitreten möchtest", + "LABEL_PASSWORD": "Passwort (Optional)", + "LABEL_PASSWORD_TOOLTIP": "Optionales Passwort, um den Raumzugang zu beschränken", + "PLACEHOLDER_PASSWORD": "Raum-Passwort (optional)", + "PLACEHOLDER_PASSWORD_TOOLTIP": "Passwort für den Raum (leer lassen, wenn keines vorhanden)", + "BTN_JOIN_ROOM": "Raum beitreten", + "BTN_JOIN_ROOM_TOOLTIP": "Mit dem Raum verbinden", + "LABEL_PUBLIC_ROOMS": "Öffentliche Räume", + "LABEL_PUBLIC_ROOMS_TOOLTIP": "Liste der öffentlich verfügbaren Räume auf diesem Server", + "BTN_REFRESH": "AKTUALISIEREN", + "BTN_REFRESH_TOOLTIP": "Die Liste der öffentlichen Räume aktualisieren", + "PUBLIC_ROOMS_REFRESHING": "Aktualisiere...", + + "LABEL_ACTIVE_ROOM": "Aktiver Raum", + "LABEL_ACTIVE_ROOM_TOOLTIP": "Der Raum, mit dem du gerade verbunden bist", + "ACTIVE_ROOM_NONE": "KEINER", + "ACTIVE_SERVER_OFFICIAL": "Offizieller Server", + "LABEL_INVITE_LINK": "Einladungslink", + "LABEL_INVITE_LINK_TOOLTIP": "Teile diesen Link mit Freunden, damit sie beitreten können", + "LABEL_PEERS_IN_ROOM": "Teilnehmer im Raum", + "LABEL_PEERS_IN_ROOM_TOOLTIP": "Andere Benutzer, die derzeit mit diesem Raum verbunden sind", + "NO_PEERS_CONNECTED": "Keine Teilnehmer verbunden", + "BTN_LEAVE_ROOM": "Raum verlassen", + + "LABEL_SELECT_VIDEO": "Video auswählen", + "LABEL_SELECT_VIDEO_TOOLTIP": "Wähle den Browser-Tab aus, der das zu synchronisierende Video enthält", + "OPTION_SELECT_TAB": "-- Wähle einen Tab --", + "LABEL_REMOTE_CONTROL": "Fernsteuerung", + "BTN_COPY_INVITE": "📋 Einladungslink", + "BTN_COPY_INVITE_TOOLTIP": "Einladungslink kopieren", + "BTN_PLAY": "▶ Abspielen", + "BTN_PLAY_TOOLTIP": "Sende einen Play-Befehl an alle", + "BTN_PAUSE": "⏸ Pause", + "BTN_PAUSE_TOOLTIP": "Sende einen Pause-Befehl an alle", + "BTN_SYNC": "⚡ SYNC", + "BTN_SYNC_TOOLTIP": "Erzwinge die Synchronisation aller Teilnehmer", + "OPTION_JUMP_TO_OTHERS": "Zu anderen springen", + "OPTION_JUMP_TO_ME": "Zu mir springen", + "OPTION_JUMP_MODE_TOOLTIP": "Synchronisationsziel auswählen", + "LABEL_LAST_ACTIVITY": "Letzter Aktivitätsstatus", + "LABEL_LAST_ACTIVITY_TOOLTIP": "Zeigt den neuesten Play-, Pause- oder Seek-Befehl", + "NO_RECENT_COMMANDS": "Keine aktuellen Befehle", + "LOBBY_HEADER": "EPISODEN-LOBBY", + "LOBBY_WAITING_FOR": "🎬 Warte auf: \"{title}\"", + "LOBBY_WAITING_PEERS": "Warte auf Teilnehmer...", + "BTN_SKIP_PLAY": "Überspringen & Abspielen", + "BTN_SKIP_PLAY_TOOLTIP": "Lobby abbrechen und trotzdem abspielen", + "LOBBY_CONNECT_FIRST": "Zuerst Raum beitreten", + "LOBBY_CONNECT_FIRST_DESC": "Du musst über einen Einladungslink beitreten oder einen neuen Raum erstellen, um Videos zu synchronisieren.", + "BTN_CREATE_ROOM_ALT": "Neuen Raum erstellen", + "BTN_CREATE_ROOM_ALT_TOOLTIP": "Einen neuen zufälligen Raum erstellen und beitreten", + + "LABEL_USERNAME": "Dein Benutzername", + "LABEL_USERNAME_TOOLTIP": "Der Benutzername hilft anderen, dich zu identifizieren.", + "PLACEHOLDER_USERNAME": "Anonymer Koala", + "LABEL_HIDE_CLUTTER": "Aufgeräumte Tab-Liste", + "LABEL_HIDE_CLUTTER_TOOLTIP": "Filtert Nicht-Video-Tabs und irrelevante Domains heraus, um die Liste sauber zu halten", + "LABEL_AUTO_SYNC_NEXT": "Nächste Episode auto-syncen", + "LABEL_AUTO_SYNC_NEXT_TOOLTIP": "Pausiert automatisch und wartet auf alle Teilnehmer bei Episodenwechsel, startet dann synchron.", + "LABEL_AUTO_COPY_INVITE": "Einladungslink auto-kopieren", + "LABEL_AUTO_COPY_INVITE_TOOLTIP": "Kopiert den Einladungslink automatisch beim Erstellen eines Raums.", + "LABEL_NOTIFICATIONS": "Browser-Benachrichtigungen", + "LABEL_NOTIFICATIONS_TOOLTIP": "Zeigt Systembenachrichtigungen, wenn jemand beitritt/verlässt oder abspielt/pausiert.", + "LABEL_LANGUAGE": "Sprache", + "LABEL_LANGUAGE_TOOLTIP": "Wähle deine bevorzugte Sprache für die Erweiterung aus", + "LABEL_TROUBLESHOOTING": "Fehlerbehebung", + "LABEL_TROUBLESHOOTING_TOOLTIP": "Werkzeuge zur Behebung von Verbindungsproblemen", + "BTN_REGEN_ID": "Peer-ID neu generieren", + "BTN_REGEN_ID_TOOLTIP": "Generiere deine interne ID neu und verbinde dich erneut", + "REGEN_ID_DESC": "Verwende dies, wenn du Fehler wegen doppelter Identität siehst.", + + "LABEL_CONN_STATUS": "Verbindungsstatus", + "LABEL_CONN_STATUS_TOOLTIP": "Aktueller WebSocket-Verbindungsstatus", + "CONN_STATUS_DISCONNECTED": "Getrennt", + "BTN_RETRY": "WIEDERHOLEN", + "BTN_RETRY_TOOLTIP": "Versuchen, die Verbindung zum Server wiederherzustellen", + "BTN_COPY_LOGS": "Logs kopieren", + "BTN_COPY_LOGS_TOOLTIP": "Logs zum Teilen in die Zwischenablage kopieren", + "LABEL_VIDEO_DEBUG": "Video-Debug-Info", + "LABEL_VIDEO_DEBUG_TOOLTIP": "Technische Details zum aktuell ausgewählten Video-Element", + "VIDEO_DEBUG_EMPTY": "Kein Tab ausgewählt oder kein Video erkannt.", + "LABEL_HISTORY": "Aktionsverlauf", + "LABEL_HISTORY_TOOLTIP": "Chronologisches Protokoll aller Sync-Befehle im Raum", + "HISTORY_EMPTY": "Noch keine Aktivität", + "LABEL_LOGS": "Logs (Letzte 50)", + "LABEL_LOGS_TOOLTIP": "Technische Verbindungsprotokolle zur Fehlersuche", + "BTN_CLEAR": "LEEREN", + "BTN_CLEAR_TOOLTIP": "Protokollausgabe leeren", + "LABEL_GITHUB": "GitHub-Repository", + + "BTN_ONBOARDING_SKIP": "Überspringen", + "BTN_ONBOARDING_SKIP_TOOLTIP": "Einführung überspringen", + "BTN_ONBOARDING_NEXT": "Weiter", + "BTN_ONBOARDING_NEXT_TOOLTIP": "Zum nächsten Schritt gehen", + "ONBOARDING_1_TITLE": "Willkommen bei KoalaSync!", + "ONBOARDING_1_TEXT": "Schau Videos in perfekter Synchronität zusammen — egal wo ihr seid. Machen wir eine kurze Tour!", + "ONBOARDING_2_TITLE": "1. Raum erstellen", + "ONBOARDING_2_TEXT": "Hier geht's los. Erstelle einen Raum und teile den Einladungslink mit deinen Freunden.", + "ONBOARDING_3_TITLE": "2. Video auswählen", + "ONBOARDING_3_TEXT": "Wähle hier das Video aus, das du synchronisieren willst. Abspielen, Pause, Spulen — alle bleiben synchron.", + "ONBOARDING_4_TITLE": "3. Personalisieren", + "ONBOARDING_4_TEXT": "Wähle einen lustigen Benutzernamen, damit deine Freunde wissen, wer du bist.", + "ONBOARDING_5_TITLE": "Alles startklar!", + "ONBOARDING_5_TEXT": "Zeit, das Popcorn zu holen. Viel Spaß beim gemeinsamen Schauen!", + + "ERR_CONN_TIMEOUT": "Verbindung abgelaufen. Bitte versuche es erneut.", + "ERR_INVALID_SERVER_URL": "Ungültiges Server-URL-Format.", + "ERR_IDENTITY_NOT_LOADED": "Identität noch nicht geladen. Warte kurz und versuche es erneut.", + "ERR_NO_PEERS_TIME": "Keine anderen Teilnehmer mit bekannter Zeit. Wechsle zu 'Zu mir springen'.", + "ERR_NO_VIDEO_TAB": "Verbindung zum Video-Tab fehlgeschlagen.", + "ERR_SELECT_VIDEO": "Bitte wähle zuerst ein Video aus!", + "TOAST_INVITE_COPIED": "Einladungslink kopiert!", + "TOAST_COPY_FAILED": "Kopieren in Zwischenablage fehlgeschlagen", + "TOAST_LOBBY_SKIPPED": "Episoden-Lobby übersprungen.", + "TOAST_LOBBY_SKIP_FAILED": "Überspringen der Lobby fehlgeschlagen.", + "TOAST_LOGS_COPIED": "Kopiert!", + "TOAST_PEER_JOINED": "{name} ist dem Raum beigetreten", + "TOAST_PEER_LEFT": "{name} hat den Raum verlassen", + "TOAST_PEER_ACTION": "{name} hat {action}", + + "STATUS_CONNECTED": "Verbunden", + "STATUS_RECONNECTING": "Verbinde erneut...", + "STATUS_CONNECTING": "Verbinde...", + "STATUS_FAILED": "Fehlgeschlagen", + "STATUS_DISCONNECTED": "Getrennt", + + "BTN_STATE_JOINING": "🚀 Trete bei...", + "BTN_STATE_RECONNECTING": "🔄 Verbinde erneut...", + "BTN_STATE_PLAYING": "▶ Spiele ab...", + "BTN_STATE_PAUSING": "⏸ Pausiere...", + "BTN_STATE_SYNCING_GROUP": "Synce zur Gruppe ({time})...", + "BTN_STATE_SYNCING": "Synchronisiere...", + "BTN_STATE_SYNCED": "✅ Synchronisiert!", + + "NOTIF_PLAY": "die Wiedergabe gestartet", + "NOTIF_PAUSE": "die Wiedergabe pausiert", + "NOTIF_SEEK": "im Video gespult", + "NOTIF_FORCE_PREPARE": "eine erzwungene Synchronisation gestartet", + "NOTIF_FORCE_EXECUTE": "alle Teilnehmer synchronisiert", + + "DEBUG_NO_TAB": "Kein Ziel-Tab ausgewählt.", + "DEBUG_COMM_FAIL": "Kommunikation mit dem Tab-Video fehlgeschlagen.", + + "EMPTY_PEERS_TITLE": "Noch keine Teilnehmer", + "EMPTY_PEERS_HINT": "Teile deinen Einladungslink, um loszulegen", + "EMPTY_HISTORY_TITLE": "Noch keine Aktivität", + "EMPTY_HISTORY_HINT": "Abspielen, Pausieren oder Spulen, um Verlauf zu sehen", + "EMPTY_LOGS_TITLE": "Keine Logs", + "EMPTY_LOGS_HINT": "Verbindungsereignisse werden hier angezeigt", + "EMPTY_ROOMS_TITLE": "Keine aktiven Räume", + "EMPTY_ROOMS_HINT": "Raum erstellen oder aktualisieren, um öffentliche zu finden" +} diff --git a/extension/locales/en.json b/extension/locales/en.json new file mode 100644 index 0000000..3611ef1 --- /dev/null +++ b/extension/locales/en.json @@ -0,0 +1,177 @@ +{ + "LANG_CODE": "en", + "HTML_CLASS": "lang-en", + + "APP_TITLE": "KoalaSync", + "TAB_ROOM": "Room", + "TAB_ROOM_TOOLTIP": "Room settings and connection", + "TAB_SYNC": "Sync", + "TAB_SYNC_TOOLTIP": "Video sync controls and remote actions", + "TAB_SETTINGS": "Settings", + "TAB_SETTINGS_TOOLTIP": "Extension preferences", + "TAB_STATUS": "Status", + "TAB_STATUS_TOOLTIP": "Advanced Diagnostics & Logs", + + "BTN_CREATE_ROOM": "+ Create New Room", + "MANUAL_CONNECT_HEADER": "Manual Connect / Advanced", + "LABEL_SERVER": "Server", + "BTN_SERVER_OFFICIAL": "Official", + "BTN_SERVER_OFFICIAL_TOOLTIP": "Use the official reliable server", + "BTN_SERVER_CUSTOM": "Custom", + "BTN_SERVER_CUSTOM_TOOLTIP": "Connect to your own self-hosted server", + "PLACEHOLDER_SERVER_URL": "wss://your-server:3000", + "LABEL_ROOM_ID": "Room ID", + "LABEL_ROOM_ID_TOOLTIP": "The unique identifier for your sync room", + "PLACEHOLDER_ROOM_ID": "Enter Room ID", + "PLACEHOLDER_ROOM_ID_TOOLTIP": "The unique ID of the room you want to join", + "LABEL_PASSWORD": "Password (Optional)", + "LABEL_PASSWORD_TOOLTIP": "Optional password to restrict room access", + "PLACEHOLDER_PASSWORD": "Room Password (optional)", + "PLACEHOLDER_PASSWORD_TOOLTIP": "Password for the room (leave empty if none)", + "BTN_JOIN_ROOM": "Join Room", + "BTN_JOIN_ROOM_TOOLTIP": "Connect to the room", + "LABEL_PUBLIC_ROOMS": "Public Rooms", + "LABEL_PUBLIC_ROOMS_TOOLTIP": "List of publicly available rooms on this server", + "BTN_REFRESH": "REFRESH", + "BTN_REFRESH_TOOLTIP": "Refresh the list of public rooms", + "PUBLIC_ROOMS_REFRESHING": "Refreshing...", + + "LABEL_ACTIVE_ROOM": "Active Room", + "LABEL_ACTIVE_ROOM_TOOLTIP": "The room you are currently connected to", + "ACTIVE_ROOM_NONE": "NONE", + "ACTIVE_SERVER_OFFICIAL": "Official Server", + "LABEL_INVITE_LINK": "Invite Link", + "LABEL_INVITE_LINK_TOOLTIP": "Share this link with friends so they can join", + "LABEL_PEERS_IN_ROOM": "Peers in Room", + "LABEL_PEERS_IN_ROOM_TOOLTIP": "Other users currently connected to this room", + "NO_PEERS_CONNECTED": "No peers connected", + "BTN_LEAVE_ROOM": "Leave Room", + + "LABEL_SELECT_VIDEO": "Select Video", + "LABEL_SELECT_VIDEO_TOOLTIP": "Choose the browser tab containing the video to sync", + "OPTION_SELECT_TAB": "-- Select a Tab --", + "LABEL_REMOTE_CONTROL": "Remote Control", + "BTN_COPY_INVITE": "📋 Invite Link", + "BTN_COPY_INVITE_TOOLTIP": "Copy Invite Link", + "BTN_PLAY": "▶ Play", + "BTN_PLAY_TOOLTIP": "Send a Play command to everyone", + "BTN_PAUSE": "⏸ Pause", + "BTN_PAUSE_TOOLTIP": "Send a Pause command to everyone", + "BTN_SYNC": "⚡ SYNC", + "BTN_SYNC_TOOLTIP": "Force all users to sync up", + "OPTION_JUMP_TO_OTHERS": "Jump to Others", + "OPTION_JUMP_TO_ME": "Jump to Me", + "OPTION_JUMP_MODE_TOOLTIP": "Choose sync target", + "LABEL_LAST_ACTIVITY": "Last Activity Status", + "LABEL_LAST_ACTIVITY_TOOLTIP": "Shows the most recent play, pause, or seek command", + "NO_RECENT_COMMANDS": "No recent commands", + "LOBBY_HEADER": "EPISODE LOBBY", + "LOBBY_WAITING_FOR": "🎬 Waiting for: \"{title}\"", + "LOBBY_WAITING_PEERS": "Waiting for peers...", + "BTN_SKIP_PLAY": "Skip & Play anyway", + "BTN_SKIP_PLAY_TOOLTIP": "Cancel lobby and play anyway", + "LOBBY_CONNECT_FIRST": "Connect to a room first", + "LOBBY_CONNECT_FIRST_DESC": "You need to join a room via an invite link or create a new one to sync videos.", + "BTN_CREATE_ROOM_ALT": "Create New Room", + "BTN_CREATE_ROOM_ALT_TOOLTIP": "Create a new random room and join it", + + "LABEL_USERNAME": "Your Username", + "LABEL_USERNAME_TOOLTIP": "Username helps others identify you.", + "PLACEHOLDER_USERNAME": "Anonymous Koala", + "LABEL_HIDE_CLUTTER": "Hide Clutter Tabs", + "LABEL_HIDE_CLUTTER_TOOLTIP": "Filters out non-video tabs and unrelated domains to keep the list clean", + "LABEL_AUTO_SYNC_NEXT": "Auto-Sync Next Episode", + "LABEL_AUTO_SYNC_NEXT_TOOLTIP": "Pauses automatically and waits for all peers when an episode changes, then sync-starts together.", + "LABEL_AUTO_COPY_INVITE": "Auto-Copy Invite Link", + "LABEL_AUTO_COPY_INVITE_TOOLTIP": "Automatically copies the invite link to your clipboard when creating a new room.", + "LABEL_NOTIFICATIONS": "Browser Notifications", + "LABEL_NOTIFICATIONS_TOOLTIP": "Shows native system notifications when someone joins/leaves or plays/pauses.", + "LABEL_LANGUAGE": "App Language", + "LABEL_LANGUAGE_TOOLTIP": "Choose your preferred extension language", + "LABEL_TROUBLESHOOTING": "Troubleshooting", + "LABEL_TROUBLESHOOTING_TOOLTIP": "Tools for fixing connection issues", + "BTN_REGEN_ID": "Regenerate Peer ID", + "BTN_REGEN_ID_TOOLTIP": "Regenerate your internal ID and reconnect", + "REGEN_ID_DESC": "Use this if you see \"Duplicate Identity\" errors.", + + "LABEL_CONN_STATUS": "Connection Status", + "LABEL_CONN_STATUS_TOOLTIP": "Current WebSocket connection state", + "CONN_STATUS_DISCONNECTED": "Disconnected", + "BTN_RETRY": "RETRY", + "BTN_RETRY_TOOLTIP": "Attempt to reconnect to the server", + "BTN_COPY_LOGS": "Copy Logs", + "BTN_COPY_LOGS_TOOLTIP": "Copy logs to clipboard for sharing", + "LABEL_VIDEO_DEBUG": "Video Debug Info", + "LABEL_VIDEO_DEBUG_TOOLTIP": "Technical details about the currently selected video element", + "VIDEO_DEBUG_EMPTY": "No tab selected or video detected.", + "LABEL_HISTORY": "Full Action History", + "LABEL_HISTORY_TOOLTIP": "Chronological log of all sync commands in the room", + "HISTORY_EMPTY": "No activity yet", + "LABEL_LOGS": "Logs (Last 50)", + "LABEL_LOGS_TOOLTIP": "Technical connection logs for debugging", + "BTN_CLEAR": "CLEAR", + "BTN_CLEAR_TOOLTIP": "Clear log output", + "LABEL_GITHUB": "GitHub Repository", + + "BTN_ONBOARDING_SKIP": "Skip", + "BTN_ONBOARDING_SKIP_TOOLTIP": "Skip the tutorial", + "BTN_ONBOARDING_NEXT": "Next", + "BTN_ONBOARDING_NEXT_TOOLTIP": "Go to next step", + "ONBOARDING_1_TITLE": "Welcome to KoalaSync!", + "ONBOARDING_1_TEXT": "Watch videos together in perfect sync — no matter where you are. Let's take a quick tour!", + "ONBOARDING_2_TITLE": "1. Create a Room", + "ONBOARDING_2_TEXT": "Start here. Create a room and share the invite link with your friends.", + "ONBOARDING_3_TITLE": "2. Select Video", + "ONBOARDING_3_TEXT": "Navigate here to select the video you want to sync. Play, pause, and seek — everyone stays in sync.", + "ONBOARDING_4_TITLE": "3. Personalize", + "ONBOARDING_4_TEXT": "Pick a fun username so your friends know who you are.", + "ONBOARDING_5_TITLE": "You're all set!", + "ONBOARDING_5_TEXT": "Time to grab some popcorn. Enjoy watching together!", + + "ERR_CONN_TIMEOUT": "Connection timed out. Please try again.", + "ERR_INVALID_SERVER_URL": "Invalid Server URL format.", + "ERR_IDENTITY_NOT_LOADED": "Identity not yet loaded. Wait a moment and try again.", + "ERR_NO_PEERS_TIME": "No other peers with a known time. Switch to 'Jump to Me'.", + "ERR_NO_VIDEO_TAB": "Could not connect to video tab.", + "ERR_SELECT_VIDEO": "Please select a video first!", + "TOAST_INVITE_COPIED": "Invite link copied!", + "TOAST_COPY_FAILED": "Failed to copy to clipboard", + "TOAST_LOBBY_SKIPPED": "Episode Lobby skipped.", + "TOAST_LOBBY_SKIP_FAILED": "Failed to skip lobby.", + "TOAST_LOGS_COPIED": "Copied!", + "TOAST_PEER_JOINED": "{name} joined the room", + "TOAST_PEER_LEFT": "{name} left the room", + "TOAST_PEER_ACTION": "{name} {action}", + + "STATUS_CONNECTED": "Connected", + "STATUS_RECONNECTING": "Reconnecting...", + "STATUS_CONNECTING": "Connecting...", + "STATUS_FAILED": "Failed", + "STATUS_DISCONNECTED": "Disconnected", + + "BTN_STATE_JOINING": "🚀 Joining...", + "BTN_STATE_RECONNECTING": "🔄 Reconnecting...", + "BTN_STATE_PLAYING": "▶ Playing...", + "BTN_STATE_PAUSING": "⏸ Pausing...", + "BTN_STATE_SYNCING_GROUP": "Syncing to group ({time})...", + "BTN_STATE_SYNCING": "Syncing...", + "BTN_STATE_SYNCED": "✅ Synced!", + + "NOTIF_PLAY": "started playback", + "NOTIF_PAUSE": "paused playback", + "NOTIF_SEEK": "seeked the video", + "NOTIF_FORCE_PREPARE": "started force sync", + "NOTIF_FORCE_EXECUTE": "synchronized everyone", + + "DEBUG_NO_TAB": "No target tab selected.", + "DEBUG_COMM_FAIL": "Could not communicate with tab video.", + + "EMPTY_PEERS_TITLE": "No peers yet", + "EMPTY_PEERS_HINT": "Share your invite link to get started", + "EMPTY_HISTORY_TITLE": "No activity yet", + "EMPTY_HISTORY_HINT": "Play, pause, or seek to see history", + "EMPTY_LOGS_TITLE": "No logs", + "EMPTY_LOGS_HINT": "Connection events will appear here", + "EMPTY_ROOMS_TITLE": "No active rooms", + "EMPTY_ROOMS_HINT": "Create a room or refresh to find public ones" +} diff --git a/extension/locales/es.json b/extension/locales/es.json new file mode 100644 index 0000000..42fd884 --- /dev/null +++ b/extension/locales/es.json @@ -0,0 +1,177 @@ +{ + "LANG_CODE": "es", + "HTML_CLASS": "lang-es", + + "APP_TITLE": "KoalaSync", + "TAB_ROOM": "Sala", + "TAB_ROOM_TOOLTIP": "Configuración de sala y conexión", + "TAB_SYNC": "Sincro", + "TAB_SYNC_TOOLTIP": "Controles de sincronización de video y acciones", + "TAB_SETTINGS": "Opciones", + "TAB_SETTINGS_TOOLTIP": "Preferencias de la extensión", + "TAB_STATUS": "Estado", + "TAB_STATUS_TOOLTIP": "Diagnósticos avanzados y registros", + + "BTN_CREATE_ROOM": "+ Crear nueva sala", + "MANUAL_CONNECT_HEADER": "Conexión manual / Avanzado", + "LABEL_SERVER": "Servidor", + "BTN_SERVER_OFFICIAL": "Oficial", + "BTN_SERVER_OFFICIAL_TOOLTIP": "Usar el servidor oficial confiable", + "BTN_SERVER_CUSTOM": "Perso", + "BTN_SERVER_CUSTOM_TOOLTIP": "Conectarse a su propio servidor auto-alojado", + "PLACEHOLDER_SERVER_URL": "wss://tu-servidor:3000", + "LABEL_ROOM_ID": "ID de sala", + "LABEL_ROOM_ID_TOOLTIP": "El identificador único para tu sala de sincronización", + "PLACEHOLDER_ROOM_ID": "Ingresa el ID de la sala", + "PLACEHOLDER_ROOM_ID_TOOLTIP": "El ID único de la sala a la que deseas unirte", + "LABEL_PASSWORD": "Contraseña (Opcional)", + "LABEL_PASSWORD_TOOLTIP": "Contraseña opcional para restringir el acceso a la sala", + "PLACEHOLDER_PASSWORD": "Contraseña de la sala (opcional)", + "PLACEHOLDER_PASSWORD_TOOLTIP": "Contraseña para la sala (dejar vacío si no hay)", + "BTN_JOIN_ROOM": "Unirse a la sala", + "BTN_JOIN_ROOM_TOOLTIP": "Conectarse a la sala", + "LABEL_PUBLIC_ROOMS": "Salas públicas", + "LABEL_PUBLIC_ROOMS_TOOLTIP": "Lista de salas públicas disponibles en este servidor", + "BTN_REFRESH": "ACTUALIZAR", + "BTN_REFRESH_TOOLTIP": "Actualizar la lista de salas públicas", + "PUBLIC_ROOMS_REFRESHING": "Actualizando...", + + "LABEL_ACTIVE_ROOM": "Sala activa", + "LABEL_ACTIVE_ROOM_TOOLTIP": "La sala a la que estás conectado actualmente", + "ACTIVE_ROOM_NONE": "NINGUNA", + "ACTIVE_SERVER_OFFICIAL": "Servidor oficial", + "LABEL_INVITE_LINK": "Enlace de invitación", + "LABEL_INVITE_LINK_TOOLTIP": "Comparte este enlace con amigos para que se unan", + "LABEL_PEERS_IN_ROOM": "Participantes en la sala", + "LABEL_PEERS_IN_ROOM_TOOLTIP": "Otros usuarios conectados actualmente a esta sala", + "NO_PEERS_CONNECTED": "Sin participantes conectados", + "BTN_LEAVE_ROOM": "Salir de la sala", + + "LABEL_SELECT_VIDEO": "Seleccionar video", + "LABEL_SELECT_VIDEO_TOOLTIP": "Elige la pestaña del navegador que contiene el video a sincronizar", + "OPTION_SELECT_TAB": "-- Selecciona una pestaña --", + "LABEL_REMOTE_CONTROL": "Control remoto", + "BTN_COPY_INVITE": "📋 Enlace de invitación", + "BTN_COPY_INVITE_TOOLTIP": "Copiar enlace de invitación", + "BTN_PLAY": "▶ Reproducir", + "BTN_PLAY_TOOLTIP": "Enviar un comando de reproducción a todos", + "BTN_PAUSE": "⏸ Pausar", + "BTN_PAUSE_TOOLTIP": "Enviar un comando de pausa a todos", + "BTN_SYNC": "⚡ SYNC", + "BTN_SYNC_TOOLTIP": "Forzar la sincronización de todos los usuarios", + "OPTION_JUMP_TO_OTHERS": "Unirse a otros", + "OPTION_JUMP_TO_ME": "Traerlos a mí", + "OPTION_JUMP_MODE_TOOLTIP": "Elegir el objetivo de sincronización", + "LABEL_LAST_ACTIVITY": "Última actividad", + "LABEL_LAST_ACTIVITY_TOOLTIP": "Muestra el comando más reciente de reproducción, pausa o búsqueda", + "NO_RECENT_COMMANDS": "Sin comandos recientes", + "LOBBY_HEADER": "SALA DE ESPERA DE EPISODIO", + "LOBBY_WAITING_FOR": "🎬 Esperando a: \"{title}\"", + "LOBBY_WAITING_PEERS": "Esperando participantes...", + "BTN_SKIP_PLAY": "Omitir y reproducir", + "BTN_SKIP_PLAY_TOOLTIP": "Cancelar la sala de espera y reproducir de todos modos", + "LOBBY_CONNECT_FIRST": "Únete a una sala primero", + "LOBBY_CONNECT_FIRST_DESC": "Necesitas unirte a una sala a través de un enlace de invitación o crear una nueva para sincronizar videos.", + "BTN_CREATE_ROOM_ALT": "Crear nueva sala", + "BTN_CREATE_ROOM_ALT_TOOLTIP": "Crear una nueva sala aleatoria y unirse", + + "LABEL_USERNAME": "Tu nombre de usuario", + "LABEL_USERNAME_TOOLTIP": "El nombre de usuario ayuda a otros a identificarte.", + "PLACEHOLDER_USERNAME": "Koala anónimo", + "LABEL_HIDE_CLUTTER": "Lista de pestañas limpia", + "LABEL_HIDE_CLUTTER_TOOLTIP": "Filtra pestañas que no son de video y dominios no relacionados para mantener limpia la lista", + "LABEL_AUTO_SYNC_NEXT": "Sincro auto del siguiente episodio", + "LABEL_AUTO_SYNC_NEXT_TOOLTIP": "Pausa automáticamente y espera a todos al cambiar de episodio, luego inicia de forma sincrónica.", + "LABEL_AUTO_COPY_INVITE": "Auto-copiar enlace", + "LABEL_AUTO_COPY_INVITE_TOOLTIP": "Copia automáticamente el enlace de invitación a tu portapapeles al crear una nueva sala.", + "LABEL_NOTIFICATIONS": "Notificaciones del navegador", + "LABEL_NOTIFICATIONS_TOOLTIP": "Muestra notificaciones del sistema cuando alguien se une/sale o reproduce/pausa.", + "LABEL_LANGUAGE": "Idioma de la aplicación", + "LABEL_LANGUAGE_TOOLTIP": "Elige tu idioma preferido para la extensión", + "LABEL_TROUBLESHOOTING": "Resolución de problemas", + "LABEL_TROUBLESHOOTING_TOOLTIP": "Herramientas para solucionar problemas de conexión", + "BTN_REGEN_ID": "Regenerar ID de participante", + "BTN_REGEN_ID_TOOLTIP": "Regenerar tu ID interno y volver a conectarte", + "REGEN_ID_DESC": "Usa esto si ves errores de 'Identidad duplicada'.", + + "LABEL_CONN_STATUS": "Estado de la conexión", + "LABEL_CONN_STATUS_TOOLTIP": "Estado actual de la conexión WebSocket", + "CONN_STATUS_DISCONNECTED": "Desconectado", + "BTN_RETRY": "REINTENTAR", + "BTN_RETRY_TOOLTIP": "Intentar volver a conectarse al servidor", + "BTN_COPY_LOGS": "Copiar registros", + "BTN_COPY_LOGS_TOOLTIP": "Copiar registros al portapapeles para compartir", + "LABEL_VIDEO_DEBUG": "Información de depuración de video", + "LABEL_VIDEO_DEBUG_TOOLTIP": "Detalles técnicos sobre el elemento de video seleccionado actualmente", + "VIDEO_DEBUG_EMPTY": "No hay pestaña seleccionada o no se detectó ningún video.", + "LABEL_HISTORY": "Historial completo", + "LABEL_HISTORY_TOOLTIP": "Registro cronológico de todos los comandos de sincronización en la sala", + "HISTORY_EMPTY": "Sin actividad aún", + "LABEL_LOGS": "Logs (Últimos 50)", + "LABEL_LOGS_TOOLTIP": "Registros técnicos de conexión para depuración", + "BTN_CLEAR": "LIMPIAR", + "BTN_CLEAR_TOOLTIP": "Limpiar salida de registros", + "LABEL_GITHUB": "Repositorio de GitHub", + + "BTN_ONBOARDING_SKIP": "Omitir", + "BTN_ONBOARDING_SKIP_TOOLTIP": "Omitir el tutorial", + "BTN_ONBOARDING_NEXT": "Siguiente", + "BTN_ONBOARDING_NEXT_TOOLTIP": "Ir al siguiente paso", + "ONBOARDING_1_TITLE": "¡Bienvenido a KoalaSync!", + "ONBOARDING_1_TEXT": "Mira videos juntos en perfecta sincronización, sin importar dónde estén. ¡Hagamos un recorrido rápido!", + "ONBOARDING_2_TITLE": "1. Crear una sala", + "ONBOARDING_2_TEXT": "Comienza aquí. Crea una sala y comparte el enlace de invitación con tus amigos.", + "ONBOARDING_3_TITLE": "2. Seleccionar video", + "ONBOARDING_3_TEXT": "Navega aquí para seleccionar el video que deseas sincronizar. Reproduce, pausa y busca: todos permanecen sincronizados.", + "ONBOARDING_4_TITLE": "3. Personalizar", + "ONBOARDING_4_TEXT": "Elige un nombre de usuario divertido para que tus amigos sepan quién eres.", + "ONBOARDING_5_TITLE": "¡Todo listo!", + "ONBOARDING_5_TEXT": "Hora de preparar las palomitas. ¡Disfruta viendo con tus amigos!", + + "ERR_CONN_TIMEOUT": "Tiempo de conexión agotado. Inténtalo de nuevo.", + "ERR_INVALID_SERVER_URL": "Formato de URL de servidor no válido.", + "ERR_IDENTITY_NOT_LOADED": "Identidad no cargada aún. Espera un momento e inténtalo de nuevo.", + "ERR_NO_PEERS_TIME": "No hay otros participantes con posición conocida. Cambia a 'Traerlos a mí'.", + "ERR_NO_VIDEO_TAB": "No se pudo conectar a la pestaña de video.", + "ERR_SELECT_VIDEO": "¡Selecciona un video primero!", + "TOAST_INVITE_COPIED": "¡Enlace de invitación copiado!", + "TOAST_COPY_FAILED": "Error al copiar al portapapeles", + "TOAST_LOBBY_SKIPPED": "Sala de espera de episodio omitida.", + "TOAST_LOBBY_SKIP_FAILED": "Error al omitir la sala de espera.", + "TOAST_LOGS_COPIED": "¡Copiado!", + "TOAST_PEER_JOINED": "{name} se unió a la sala", + "TOAST_PEER_LEFT": "{name} salió de la sala", + "TOAST_PEER_ACTION": "{name} ha {action}", + + "STATUS_CONNECTED": "Conectado", + "STATUS_RECONNECTING": "Reconectando...", + "STATUS_CONNECTING": "Conectando...", + "STATUS_FAILED": "Error", + "STATUS_DISCONNECTED": "Desconectado", + + "BTN_STATE_JOINING": "🚀 Uniéndose...", + "BTN_STATE_RECONNECTING": "🔄 Reconectando...", + "BTN_STATE_PLAYING": "▶ Reproduciendo...", + "BTN_STATE_PAUSING": "⏸ Pausando...", + "BTN_STATE_SYNCING_GROUP": "Sincronizando al grupo ({time})...", + "BTN_STATE_SYNCING": "Sincronizando...", + "BTN_STATE_SYNCED": "✅ ¡Sincronizado!", + + "NOTIF_PLAY": "inició la reproducción", + "NOTIF_PAUSE": "pausó la reproducción", + "NOTIF_SEEK": "cambió la posición del video", + "NOTIF_FORCE_PREPARE": "inició una sincronización forzada", + "NOTIF_FORCE_EXECUTE": "sincronizó a todos", + + "DEBUG_NO_TAB": "No hay pestaña objetivo seleccionada.", + "DEBUG_COMM_FAIL": "No se pudo comunicar con el video de la pestaña.", + + "EMPTY_PEERS_TITLE": "Sin participantes aún", + "EMPTY_PEERS_HINT": "Comparte tu enlace de invitación para comenzar", + "EMPTY_HISTORY_TITLE": "Sin actividad aún", + "EMPTY_HISTORY_HINT": "Reproduce, pausa o busca para ver el historial", + "EMPTY_LOGS_TITLE": "Sin registros", + "EMPTY_LOGS_HINT": "Los eventos de conexión aparecerán aquí", + "EMPTY_ROOMS_TITLE": "Sin salas activas", + "EMPTY_ROOMS_HINT": "Crea una sala o actualiza para buscar salas públicas" +} diff --git a/extension/locales/fr.json b/extension/locales/fr.json new file mode 100644 index 0000000..2a9d82a --- /dev/null +++ b/extension/locales/fr.json @@ -0,0 +1,177 @@ +{ + "LANG_CODE": "fr", + "HTML_CLASS": "lang-fr", + + "APP_TITLE": "KoalaSync", + "TAB_ROOM": "Salon", + "TAB_ROOM_TOOLTIP": "Paramètres de salon et connexion", + "TAB_SYNC": "Synchro", + "TAB_SYNC_TOOLTIP": "Contrôles de synchronisation vidéo et actions", + "TAB_SETTINGS": "Options", + "TAB_SETTINGS_TOOLTIP": "Préférences de l'extension", + "TAB_STATUS": "Statut", + "TAB_STATUS_TOOLTIP": "Diagnostics avancés & Journaux", + + "BTN_CREATE_ROOM": "+ Créer un nouveau salon", + "MANUAL_CONNECT_HEADER": "Connexion manuelle / Avancé", + "LABEL_SERVER": "Serveur", + "BTN_SERVER_OFFICIAL": "Officiel", + "BTN_SERVER_OFFICIAL_TOOLTIP": "Utiliser le serveur officiel fiable", + "BTN_SERVER_CUSTOM": "Perso", + "BTN_SERVER_CUSTOM_TOOLTIP": "Se connecter à votre propre serveur auto-hébergé", + "PLACEHOLDER_SERVER_URL": "wss://votre-serveur:3000", + "LABEL_ROOM_ID": "ID du salon", + "LABEL_ROOM_ID_TOOLTIP": "L'identifiant unique de votre salon de synchronisation", + "PLACEHOLDER_ROOM_ID": "Entrer l'ID du salon", + "PLACEHOLDER_ROOM_ID_TOOLTIP": "L'ID unique du salon que vous souhaitez rejoindre", + "LABEL_PASSWORD": "Mot de passe (Optionnel)", + "LABEL_PASSWORD_TOOLTIP": "Mot de passe optionnel pour restreindre l'accès au salon", + "PLACEHOLDER_PASSWORD": "Mot de passe du salon (optionnel)", + "PLACEHOLDER_PASSWORD_TOOLTIP": "Mot de passe du salon (laisser vide si aucun)", + "BTN_JOIN_ROOM": "Rejoindre le salon", + "BTN_JOIN_ROOM_TOOLTIP": "Se connecter au salon", + "LABEL_PUBLIC_ROOMS": "Salons publics", + "LABEL_PUBLIC_ROOMS_TOOLTIP": "Liste des salons publics disponibles sur ce serveur", + "BTN_REFRESH": "ACTUALISER", + "BTN_REFRESH_TOOLTIP": "Actualiser la liste des salons publics", + "PUBLIC_ROOMS_REFRESHING": "Actualisation...", + + "LABEL_ACTIVE_ROOM": "Salon actif", + "LABEL_ACTIVE_ROOM_TOOLTIP": "Le salon auquel vous êtes actuellement connecté", + "ACTIVE_ROOM_NONE": "AUCUN", + "ACTIVE_SERVER_OFFICIAL": "Serveur officiel", + "LABEL_INVITE_LINK": "Lien d'invitation", + "LABEL_INVITE_LINK_TOOLTIP": "Partagez ce lien avec vos amis pour qu'ils vous rejoignent", + "LABEL_PEERS_IN_ROOM": "Membres dans le salon", + "LABEL_PEERS_IN_ROOM_TOOLTIP": "Autres utilisateurs connectés à ce salon", + "NO_PEERS_CONNECTED": "Aucun membre connecté", + "BTN_LEAVE_ROOM": "Quitter le salon", + + "LABEL_SELECT_VIDEO": "Choisir une vidéo", + "LABEL_SELECT_VIDEO_TOOLTIP": "Choisissez l'onglet du navigateur contenant la vidéo à synchroniser", + "OPTION_SELECT_TAB": "-- Choisir un onglet --", + "LABEL_REMOTE_CONTROL": "Contrôle à distance", + "BTN_COPY_INVITE": "📋 Lien d'invitation", + "BTN_COPY_INVITE_TOOLTIP": "Copier le lien d'invitation", + "BTN_PLAY": "▶ Lecture", + "BTN_PLAY_TOOLTIP": "Envoyer une commande Lecture à tout le monde", + "BTN_PAUSE": "⏸ Pause", + "BTN_PAUSE_TOOLTIP": "Envoyer une commande Pause à tout le monde", + "BTN_SYNC": "⚡ SYNC", + "BTN_SYNC_TOOLTIP": "Forcer tous les utilisateurs à se synchroniser", + "OPTION_JUMP_TO_OTHERS": "Rejoindre les autres", + "OPTION_JUMP_TO_ME": "Les amener à moi", + "OPTION_JUMP_MODE_TOOLTIP": "Choisir la cible de synchronisation", + "LABEL_LAST_ACTIVITY": "Dernière activité", + "LABEL_LAST_ACTIVITY_TOOLTIP": "Affiche la dernière commande de lecture, pause ou recherche", + "NO_RECENT_COMMANDS": "Aucune commande récente", + "LOBBY_HEADER": "LOBBY D'ÉPISODE", + "LOBBY_WAITING_FOR": "🎬 En attente de : \"{title}\"", + "LOBBY_WAITING_PEERS": "En attente des membres...", + "BTN_SKIP_PLAY": "Ignorer & Lancer", + "BTN_SKIP_PLAY_TOOLTIP": "Annuler le lobby et lancer la lecture quand même", + "LOBBY_CONNECT_FIRST": "Rejoignez d'abord un salon", + "LOBBY_CONNECT_FIRST_DESC": "Vous devez rejoindre un salon via un lien d'invitation ou en créer un nouveau pour synchroniser des vidéos.", + "BTN_CREATE_ROOM_ALT": "Créer un nouveau salon", + "BTN_CREATE_ROOM_ALT_TOOLTIP": "Créer un salon aléatoire et le rejoindre", + + "LABEL_USERNAME": "Votre pseudo", + "LABEL_USERNAME_TOOLTIP": "Votre pseudo permet aux autres de vous identifier.", + "PLACEHOLDER_USERNAME": "Koala anonyme", + "LABEL_HIDE_CLUTTER": "Liste d'onglets épurée", + "LABEL_HIDE_CLUTTER_TOOLTIP": "Filtre les onglets non-vidéo et les domaines non pertinents pour garder la liste propre", + "LABEL_AUTO_SYNC_NEXT": "Synchro auto l'épisode suivant", + "LABEL_AUTO_SYNC_NEXT_TOOLTIP": "Met en pause et attend tous les membres lors d'un changement d'épisode, puis démarre de manière synchrone.", + "LABEL_AUTO_COPY_INVITE": "Copie auto du lien", + "LABEL_AUTO_COPY_INVITE_TOOLTIP": "Copie automatiquement le lien d'invitation dans votre presse-papiers lors de la création d'un salon.", + "LABEL_NOTIFICATIONS": "Notifications de navigateur", + "LABEL_NOTIFICATIONS_TOOLTIP": "Affiche des notifications système lorsqu'un membre arrive/part ou lance/met en pause la lecture.", + "LABEL_LANGUAGE": "Langue de l'application", + "LABEL_LANGUAGE_TOOLTIP": "Choisissez la langue de l'application", + "LABEL_TROUBLESHOOTING": "Dépannage", + "LABEL_TROUBLESHOOTING_TOOLTIP": "Outils pour résoudre les problèmes de connexion", + "BTN_REGEN_ID": "Régénérer l'identifiant", + "BTN_REGEN_ID_TOOLTIP": "Régénérer votre identifiant interne et vous reconnecter", + "REGEN_ID_DESC": "Utilisez cette option si vous rencontrez des erreurs de 'Double identité'.", + + "LABEL_CONN_STATUS": "Statut de la connexion", + "LABEL_CONN_STATUS_TOOLTIP": "Statut actuel de la connexion WebSocket", + "CONN_STATUS_DISCONNECTED": "Déconnecté", + "BTN_RETRY": "RÉESSAYER", + "BTN_RETRY_TOOLTIP": "Tenter de se reconnecter au serveur", + "BTN_COPY_LOGS": "Copier les journaux", + "BTN_COPY_LOGS_TOOLTIP": "Copier les journaux dans le presse-papiers", + "LABEL_VIDEO_DEBUG": "Infos de débogage vidéo", + "LABEL_VIDEO_DEBUG_TOOLTIP": "Détails techniques de l'élément vidéo actuellement sélectionné", + "VIDEO_DEBUG_EMPTY": "Aucun onglet sélectionné ou aucune vidéo détectée.", + "LABEL_HISTORY": "Historique complet", + "LABEL_HISTORY_TOOLTIP": "Historique chronologique de toutes les commandes de synchronisation dans le salon", + "HISTORY_EMPTY": "Aucune activité pour le moment", + "LABEL_LOGS": "Journaux (50 derniers)", + "LABEL_LOGS_TOOLTIP": "Journaux techniques pour le débogage de la connexion", + "BTN_CLEAR": "EFFACER", + "BTN_CLEAR_TOOLTIP": "Effacer la sortie des journaux", + "LABEL_GITHUB": "Dépôt GitHub", + + "BTN_ONBOARDING_SKIP": "Passer", + "BTN_ONBOARDING_SKIP_TOOLTIP": "Passer le didacticiel", + "BTN_ONBOARDING_NEXT": "Suivant", + "BTN_ONBOARDING_NEXT_TOOLTIP": "Étape suivante", + "ONBOARDING_1_TITLE": "Bienvenue sur KoalaSync !", + "ONBOARDING_1_TEXT": "Regardez des vidéos en parfaite synchronisation — où que vous soyez. Faisons un petit tour !", + "ONBOARDING_2_TITLE": "1. Créer un salon", + "ONBOARDING_2_TEXT": "Commencez ici. Créez un salon et partagez le lien d'invitation avec vos amis.", + "ONBOARDING_3_TITLE": "2. Choisir une vidéo", + "ONBOARDING_3_TEXT": "Naviguez ici pour sélectionner la vidéo à synchroniser. Lecture, pause, recherche — tout le monde reste synchrone.", + "ONBOARDING_4_TITLE": "3. Personnaliser", + "ONBOARDING_4_TEXT": "Choisissez un pseudo sympa pour que vos amis sachent qui vous êtes.", + "ONBOARDING_5_TITLE": "Vous êtes prêt !", + "ONBOARDING_5_TEXT": "Il est temps de préparer le pop-corn. Bon visionnage !", + + "ERR_CONN_TIMEOUT": "Délai de connexion dépassé. Veuillez réessayer.", + "ERR_INVALID_SERVER_URL": "Format d'adresse de serveur non valide.", + "ERR_IDENTITY_NOT_LOADED": "Identifiant non encore chargé. Veuillez patienter et réessayer.", + "ERR_NO_PEERS_TIME": "Aucun autre membre avec une position connue. Basculez sur 'Les amener à moi'.", + "ERR_NO_VIDEO_TAB": "Impossible de se connecter à l'onglet vidéo.", + "ERR_SELECT_VIDEO": "Veuillez d'abord sélectionner une vidéo !", + "TOAST_INVITE_COPIED": "Lien d'invitation copié !", + "TOAST_COPY_FAILED": "Échec de la copie dans le presse-papiers", + "TOAST_LOBBY_SKIPPED": "Lobby d'épisode ignoré.", + "TOAST_LOBBY_SKIP_FAILED": "Échec de l'annulation du lobby.", + "TOAST_LOGS_COPIED": "Copié !", + "TOAST_PEER_JOINED": "{name} a rejoint le salon", + "TOAST_PEER_LEFT": "{name} a quitté le salon", + "TOAST_PEER_ACTION": "{name} a {action}", + + "STATUS_CONNECTED": "Connecté", + "STATUS_RECONNECTING": "Reconnexion...", + "STATUS_CONNECTING": "Connexion...", + "STATUS_FAILED": "Échec", + "STATUS_DISCONNECTED": "Déconnecté", + + "BTN_STATE_JOINING": "🚀 Connexion...", + "BTN_STATE_RECONNECTING": "🔄 Reconnexion...", + "BTN_STATE_PLAYING": "▶ Lecture...", + "BTN_STATE_PAUSING": "⏸ Pause...", + "BTN_STATE_SYNCING_GROUP": "Synchro au groupe ({time})...", + "BTN_STATE_SYNCING": "Synchronisation...", + "BTN_STATE_SYNCED": "✅ Synchronisé !", + + "NOTIF_PLAY": "lancé la lecture", + "NOTIF_PAUSE": "mis la lecture en pause", + "NOTIF_SEEK": "déplacé la position dans la vidéo", + "NOTIF_FORCE_PREPARE": "lancé une synchronisation forcée", + "NOTIF_FORCE_EXECUTE": "synchronisé tout le monde", + + "DEBUG_NO_TAB": "Aucun onglet cible sélectionné.", + "DEBUG_COMM_FAIL": "Impossible de communiquer avec l'onglet vidéo.", + + "EMPTY_PEERS_TITLE": "Aucun membre pour l'instant", + "EMPTY_PEERS_HINT": "Partagez votre lien d'invitation pour commencer", + "EMPTY_HISTORY_TITLE": "Aucune activité pour l'instant", + "EMPTY_HISTORY_HINT": "Lancez, mettez en pause ou déplacez la position pour voir l'historique", + "EMPTY_LOGS_TITLE": "Aucun journal", + "EMPTY_LOGS_HINT": "Les événements de connexion s'afficheront ici", + "EMPTY_ROOMS_TITLE": "Aucun salon actif", + "EMPTY_ROOMS_HINT": "Créez un salon ou actualisez pour trouver des salons publics" +} diff --git a/extension/locales/i18n_plan.md b/extension/locales/i18n_plan.md new file mode 100644 index 0000000..a27ed52 --- /dev/null +++ b/extension/locales/i18n_plan.md @@ -0,0 +1,204 @@ +# KoalaSync Browser Extension v2.0 - i18n Technical Implementation Plan + +Welcome, future Antigravity AI agent! This document is placed directly in the codebase at `/extension/locales/i18n_plan.md` to serve as a comprehensive architectural handbook for the next steps in adding **full internationalization (i18n) support to the browser extension itself** while maintaining 100% video-sync and background communication safety. + +--- + +## 🔍 Context & Audited Scope + +KoalaSync is a lightweight, premium browser extension (Chrome & Firefox Manifest V3) for synchronized video playback. The landing pages are already compiled dynamically in 6 languages: +* English (`en`) +* German (`de`) +* French (`fr`) +* Spanish (`es`) +* Portuguese (Brazil) (`pt-BR`) +* Russian (`ru`) + +Our goal is to build an identical, premium translation engine for the extension itself. + +--- + +## 🏛️ Architectural Choice: Custom JSON Dictionary Engine (Approach B) + +We evaluated two paths: +1. **Approach A (Native `chrome.i18n` API):** Uses `_locales/` directory. Too rigid—cannot support real-time dynamic switching inside the extension Settings dropdown without closing/re-opening the popup. +2. **Approach B (Custom Unified JSON Engine):** Uses flat JSON files matching our website files (`"KEY": "Value"`). Dynamically merges the target dictionary with baseline English `en.json` at runtime, programmatically providing an **airtight English fallback** and **real-time DOM translations** without popup reload. + +We chose **Approach B** for maximum compatibility, premium real-time toggling, and clean fallback safety. + +--- + +## 🔄 Resolve, Load, & State Flow + +1. **On launch:** Look for saved language in `chrome.storage.sync.get('locale')`. +2. **Fallback Autodetect:** If no saved language, read `navigator.language` or `chrome.i18n.getUILanguage()`. + * If the detected locale is supported, set as active. + * If not supported, default to English (`en`). +3. **Dictionary Resolution:** + * Asynchronously load the English baseline dictionary (`/extension/locales/en.json`). + * If the target language is different, load target JSON (e.g. `/extension/locales/de.json`) and execute `Object.assign({}, enDict, targetDict)`. This guarantees dynamic translation while cleanly falling back to English for any missing keys. +4. **DOM Replacements:** Scan for `data-i18n`, `data-i18n-title`, and `data-i18n-placeholder` attributes, and translate them on the fly. +5. **Persistence:** Save dynamic selection modifications from the dropdown into `chrome.storage.sync`. Trigger instant DOM re-translation on change. + +--- + +## 📂 Proposed File Structure + +``` +KoalaPlay/ +└── extension/ + ├── locales/ # [NEW] Contains flat translation maps + │ ├── i18n_plan.md # This roadmap file + │ ├── en.json # Flat English baseline keys + │ ├── de.json # German keys + │ ├── fr.json # French keys + │ ├── es.json # Spanish keys + │ ├── pt-BR.json # Portuguese (Brasil) keys + │ └── ru.json # Russian keys + ├── i18n.js # [NEW] ESM translation engine module + ├── popup.html # Modified with data-i18n attributes + ├── popup.js # Modified to initialize locales and update variables + └── background.js # Modified to push localized notification alerts +``` + +--- + +## 🛠️ Draft Code Snippets + +### 1. `i18n.js` (Zero-Dependency Engine Module) +```javascript +// extension/i18n.js +export const SUPPORTED_LANGUAGES = ['en', 'de', 'fr', 'es', 'pt-BR', 'ru']; +export const DEFAULT_LANGUAGE = 'en'; + +let activeDictionary = {}; + +export async function loadLocale(langCode) { + const resolvedLang = SUPPORTED_LANGUAGES.includes(langCode) ? langCode : DEFAULT_LANGUAGE; + try { + const enResponse = await fetch(chrome.runtime.getURL(`locales/${DEFAULT_LANGUAGE}.json`)); + const enDict = await enResponse.json(); + + if (resolvedLang === DEFAULT_LANGUAGE) { + activeDictionary = enDict; + return; + } + + const targetResponse = await fetch(chrome.runtime.getURL(`locales/${resolvedLang}.json`)); + const targetDict = await targetResponse.json(); + + activeDictionary = Object.assign({}, enDict, targetDict); + } catch (err) { + console.error('[i18n] Failed to load dictionary. Falling back to English:', err); + const rescue = await fetch(chrome.runtime.getURL(`locales/${DEFAULT_LANGUAGE}.json`)); + activeDictionary = await rescue.json(); + } +} + +export function getMessage(key) { + return activeDictionary[key] || key; +} + +export function translateDOM() { + // Translate text nodes + document.querySelectorAll('[data-i18n]').forEach(el => { + const key = el.getAttribute('data-i18n'); + const translated = getMessage(key); + const img = el.querySelector('img'); + if (img) { + el.innerHTML = ''; + el.appendChild(img); + el.appendChild(document.createTextNode(' ' + translated)); + } else { + el.textContent = translated; + } + }); + + // Translate tooltips + document.querySelectorAll('[data-i18n-title]').forEach(el => { + const key = el.getAttribute('data-i18n-title'); + el.setAttribute('title', getMessage(key)); + }); + + // Translate placeholders + document.querySelectorAll('[data-i18n-placeholder]').forEach(el => { + const key = el.getAttribute('data-i18n-placeholder'); + el.setAttribute('placeholder', getMessage(key)); + }); +} +``` + +### 2. Markup Changes (`popup.html`) +* Annotate text elements: `` +* Add Language Dropdown in the Settings Panel: + ```html +
+ + +
+ ``` + +### 3. Dynamic Script Wiring (`popup.js`) +* Import our engine: `import { loadLocale, translateDOM, getMessage } from './i18n.js';` +* Initialize during startup: + ```javascript + async function init() { + const data = await chrome.storage.sync.get(['locale', ...]); + let activeLang = data.locale; + if (!activeLang) { + const systemLang = (navigator.language || chrome.i18n.getUILanguage()).split('-')[0]; + activeLang = ['en', 'de', 'fr', 'es', 'pt', 'ru'].includes(systemLang) ? (systemLang === 'pt' ? 'pt-BR' : systemLang) : 'en'; + chrome.storage.sync.set({ locale: activeLang }); + } + await loadLocale(activeLang); + translateDOM(); + + // Select matching option in selector + const langSelector = document.getElementById('langSelector'); + if (langSelector) langSelector.value = activeLang; + + // rest of standard init... + } + ``` +* Listen to Settings change event: + ```javascript + const langSelector = document.getElementById('langSelector'); + if (langSelector) { + langSelector.addEventListener('change', async () => { + const selectedLang = langSelector.value; + await chrome.storage.sync.set({ locale: selectedLang }); + await loadLocale(selectedLang); + translateDOM(); + // Re-render empty elements and tab list using new dynamic strings + refreshLogs(); + refreshHistory(); + populateTabs(); + }); + } + ``` + +### 4. Background Notifications (`background.js`) +* Notifications inside `showNotification` should fetch dynamic keys based on `chrome.storage.sync` language settings and build clean alerts dynamically using `chrome.storage.sync.get('locale')`. +* Ensure that WebSocket event transmissions and video player scripts in `content.js` remain **completely untouched and unaware** of the localization engine. + +--- + +## 🔒 Safety Guardrail + +* **WebSocket Engine Integrity:** Under no circumstances should `background.js` Socket.IO handshakes, rate limits, or binary protocol headers be adjusted. i18n is strictly an interface rendering skin layer. +* **Player Control Pipeline Integrity:** `content.js` expectedEvent suppression tables, HLS buffer calculations, and Media Session API listeners must remain untouched. Do not bind any i18n logic into the content script's active tracking loop. + +--- + +## 🧪 Verification Tasks + +1. **Autodetection Audit:** Force-load the extension in Chrome/Firefox in a non-English language container and ensure it starts in that language or English gracefully. +2. **Integrity Validation Script:** Write a sanity node checker script `/scripts/test-locales.js` to ensure all key structures are uniform across all JSON files. +3. **Real-Time Refresh Verification:** Change dynamic drop-down preferences and verify that every element and alert swaps languages cleanly with zero redraw glitches or page crashes. diff --git a/extension/locales/pt-BR.json b/extension/locales/pt-BR.json new file mode 100644 index 0000000..17c3b6e --- /dev/null +++ b/extension/locales/pt-BR.json @@ -0,0 +1,177 @@ +{ + "LANG_CODE": "pt-BR", + "HTML_CLASS": "lang-pt-br", + + "APP_TITLE": "KoalaSync", + "TAB_ROOM": "Sala", + "TAB_ROOM_TOOLTIP": "Configurações da sala e conexão", + "TAB_SYNC": "Sincro", + "TAB_SYNC_TOOLTIP": "Controles de sincronia de vídeo e ações", + "TAB_SETTINGS": "Opções", + "TAB_SETTINGS_TOOLTIP": "Preferências da extensão", + "TAB_STATUS": "Status", + "TAB_STATUS_TOOLTIP": "Diagnósticos avançados e logs", + + "BTN_CREATE_ROOM": "+ Criar nova sala", + "MANUAL_CONNECT_HEADER": "Conexão manual / Avançado", + "LABEL_SERVER": "Servidor", + "BTN_SERVER_OFFICIAL": "Oficial", + "BTN_SERVER_OFFICIAL_TOOLTIP": "Usar o servidor oficial confiável", + "BTN_SERVER_CUSTOM": "Perso", + "BTN_SERVER_CUSTOM_TOOLTIP": "Conectar ao seu próprio servidor auto-hospedado", + "PLACEHOLDER_SERVER_URL": "wss://seu-servidor:3000", + "LABEL_ROOM_ID": "ID da sala", + "LABEL_ROOM_ID_TOOLTIP": "O identificador exclusivo para sua sala de sincronização", + "PLACEHOLDER_ROOM_ID": "Digite o ID da sala", + "PLACEHOLDER_ROOM_ID_TOOLTIP": "O ID exclusivo da sala na qual você deseja entrar", + "LABEL_PASSWORD": "Senha (Opcional)", + "LABEL_PASSWORD_TOOLTIP": "Senha opcional para restringir o acesso à sala", + "PLACEHOLDER_PASSWORD": "Senha da sala (opcional)", + "PLACEHOLDER_PASSWORD_TOOLTIP": "Senha para a sala (deixe em branco se não houver)", + "BTN_JOIN_ROOM": "Entrar na sala", + "BTN_JOIN_ROOM_TOOLTIP": "Conectar à sala", + "LABEL_PUBLIC_ROOMS": "Salas públicas", + "LABEL_PUBLIC_ROOMS_TOOLTIP": "Lista de salas públicas disponíveis neste servidor", + "BTN_REFRESH": "ATUALIZAR", + "BTN_REFRESH_TOOLTIP": "Atualizar a lista de salas públicas", + "PUBLIC_ROOMS_REFRESHING": "Atualizando...", + + "LABEL_ACTIVE_ROOM": "Sala ativa", + "LABEL_ACTIVE_ROOM_TOOLTIP": "A sala à qual você está conectado no momento", + "ACTIVE_ROOM_NONE": "NENHUMA", + "ACTIVE_SERVER_OFFICIAL": "Servidor oficial", + "LABEL_INVITE_LINK": "Link de convite", + "LABEL_INVITE_LINK_TOOLTIP": "Compartilhe este link com amigos para que eles entrem", + "LABEL_PEERS_IN_ROOM": "Participantes na sala", + "LABEL_PEERS_IN_ROOM_TOOLTIP": "Outros usuários conectados atualmente a esta sala", + "NO_PEERS_CONNECTED": "Sem participantes conectados", + "BTN_LEAVE_ROOM": "Sair da sala", + + "LABEL_SELECT_VIDEO": "Selecionar vídeo", + "LABEL_SELECT_VIDEO_TOOLTIP": "Escolha a aba do navegador contendo o vídeo a ser sincronizado", + "OPTION_SELECT_TAB": "-- Selecione uma aba --", + "LABEL_REMOTE_CONTROL": "Controle remoto", + "BTN_COPY_INVITE": "📋 Link de convite", + "BTN_COPY_INVITE_TOOLTIP": "Copiar link de convite", + "BTN_PLAY": "▶ Reproduzir", + "BTN_PLAY_TOOLTIP": "Enviar um comando de reproduzir para todos", + "BTN_PAUSE": "⏸ Pausar", + "BTN_PAUSE_TOOLTIP": "Enviar um comando de pausar para todos", + "BTN_SYNC": "⚡ SYNC", + "BTN_SYNC_TOOLTIP": "Forçar todos os usuários a sincronizar", + "OPTION_JUMP_TO_OTHERS": "Ir para os outros", + "OPTION_JUMP_TO_ME": "Trazer para mim", + "OPTION_JUMP_MODE_TOOLTIP": "Escolher o alvo de sincronização", + "LABEL_LAST_ACTIVITY": "Última atividade", + "LABEL_LAST_ACTIVITY_TOOLTIP": "Mostra o comando mais recente de reproduzir, pausar ou buscar", + "NO_RECENT_COMMANDS": "Sem comandos recentes", + "LOBBY_HEADER": "LOBBY DE EPISÓDIO", + "LOBBY_WAITING_FOR": "🎬 Aguardando: \"{title}\"", + "LOBBY_WAITING_PEERS": "Aguardando participantes...", + "BTN_SKIP_PLAY": "Pular e reproduzir", + "BTN_SKIP_PLAY_TOOLTIP": "Cancelar o lobby e reproduzir assim mesmo", + "LOBBY_CONNECT_FIRST": "Entre em uma sala primeiro", + "LOBBY_CONNECT_FIRST_DESC": "Você precisa entrar em uma sala através de um link de convite ou criar uma nova para sincronizar vídeos.", + "BTN_CREATE_ROOM_ALT": "Criar nova sala", + "BTN_CREATE_ROOM_ALT_TOOLTIP": "Criar uma nova sala aleatória e entrar", + + "LABEL_USERNAME": "Seu nome de usuário", + "LABEL_USERNAME_TOOLTIP": "O nome de usuário ajuda os outros a identificá-lo.", + "PLACEHOLDER_USERNAME": "Coala anônimo", + "LABEL_HIDE_CLUTTER": "Lista de abas limpa", + "LABEL_HIDE_CLUTTER_TOOLTIP": "Filtra abas que não são de vídeo e domínios não relacionados para manter a lista organizada", + "LABEL_AUTO_SYNC_NEXT": "Sincro auto do próximo episódio", + "LABEL_AUTO_SYNC_NEXT_TOOLTIP": "Pausa automaticamente e aguarda todos ao mudar de episódio, depois inicia em sincronia.", + "LABEL_AUTO_COPY_INVITE": "Auto-copiar link", + "LABEL_AUTO_COPY_INVITE_TOOLTIP": "Copia automaticamente o link de convite para a área de transferência ao criar uma nova sala.", + "LABEL_NOTIFICATIONS": "Notificações do navegador", + "LABEL_NOTIFICATIONS_TOOLTIP": "Mostra notificações do sistema quando alguém entra/sai ou reproduz/pausa.", + "LABEL_LANGUAGE": "Idioma do aplicativo", + "LABEL_LANGUAGE_TOOLTIP": "Escolha o idioma de sua preferência para a extensão", + "LABEL_TROUBLESHOOTING": "Resolução de problemas", + "LABEL_TROUBLESHOOTING_TOOLTIP": "Ferramentas para solucionar problemas de conexão", + "BTN_REGEN_ID": "Regenerar ID de participante", + "BTN_REGEN_ID_TOOLTIP": "Regenerar seu ID interno e conectar novamente", + "REGEN_ID_DESC": "Use esta opção se notar erros de 'Identidade duplicada'.", + + "LABEL_CONN_STATUS": "Status da conexão", + "LABEL_CONN_STATUS_TOOLTIP": "Status atual da conexão WebSocket", + "CONN_STATUS_DISCONNECTED": "Desconectado", + "BTN_RETRY": "REENTRAR", + "BTN_RETRY_TOOLTIP": "Tentar reconectar ao servidor", + "BTN_COPY_LOGS": "Copiar logs", + "BTN_COPY_LOGS_TOOLTIP": "Copiar logs para a área de transferência para compartilhar", + "LABEL_VIDEO_DEBUG": "Informações de depuração de vídeo", + "LABEL_VIDEO_DEBUG_TOOLTIP": "Detalhes técnicos sobre o elemento de vídeo selecionado atualmente", + "VIDEO_DEBUG_EMPTY": "Nenhuma aba selecionada ou vídeo detectado.", + "LABEL_HISTORY": "Histórico completo", + "LABEL_HISTORY_TOOLTIP": "Registro cronológico de todos os comandos de sincronização na sala", + "HISTORY_EMPTY": "Sem atividade ainda", + "LABEL_LOGS": "Logs (Últimos 50)", + "LABEL_LOGS_TOOLTIP": "Logs técnicos de conexão para depuração", + "BTN_CLEAR": "LIMPAR", + "BTN_CLEAR_TOOLTIP": "Limpar a saída dos logs", + "LABEL_GITHUB": "Repositório GitHub", + + "BTN_ONBOARDING_SKIP": "Pular", + "BTN_ONBOARDING_SKIP_TOOLTIP": "Pular o tutorial", + "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 juntos em perfeita sincronia, não importa onde estejam. Vamos fazer um tour rápido!", + "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", + "ONBOARDING_3_TEXT": "Navegue aqui para selecionar o vídeo a ser sincronizado. Reproduza, pause e busque: todos permanecem sincronizados.", + "ONBOARDING_4_TITLE": "3. Personalizar", + "ONBOARDING_4_TEXT": "Escolha um nome de usuário divertido para que seus amigos saibam quem você é.", + "ONBOARDING_5_TITLE": "Tudo pronto!", + "ONBOARDING_5_TEXT": "Hora de pegar a pipoca. Divirta-se assistindo com seus amigos!", + + "ERR_CONN_TIMEOUT": "Tempo limite de conexão esgotado. Tente novamente.", + "ERR_INVALID_SERVER_URL": "Formato de URL do servidor inválido.", + "ERR_IDENTITY_NOT_LOADED": "Identidade não carregada ainda. Aguarde um momento e tente novamente.", + "ERR_NO_PEERS_TIME": "Nenhum outro participante com posição conhecida. Mude para 'Traerlos a mí' / 'Trazer para mim'.", + "ERR_NO_VIDEO_TAB": "Não foi possível conectar à aba do vídeo.", + "ERR_SELECT_VIDEO": "Selecione um vídeo primeiro!", + "TOAST_INVITE_COPIED": "Link de convite copiado!", + "TOAST_COPY_FAILED": "Falha ao copiar para a área de transferência", + "TOAST_LOBBY_SKIPPED": "Lobby de episódio ignorado.", + "TOAST_LOBBY_SKIP_FAILED": "Falha ao ignorar o lobby.", + "TOAST_LOGS_COPIED": "Copiado!", + "TOAST_PEER_JOINED": "{name} entrou na sala", + "TOAST_PEER_LEFT": "{name} saiu da sala", + "TOAST_PEER_ACTION": "{name} {action}", + + "STATUS_CONNECTED": "Conectado", + "STATUS_RECONNECTING": "Reconectando...", + "STATUS_CONNECTING": "Conectando...", + "STATUS_FAILED": "Falhou", + "STATUS_DISCONNECTED": "Desconectado", + + "BTN_STATE_JOINING": "🚀 Entrando...", + "BTN_STATE_RECONNECTING": "🔄 Reconectando...", + "BTN_STATE_PLAYING": "▶ Reproduzindo...", + "BTN_STATE_PAUSING": "⏸ Pausando...", + "BTN_STATE_SYNCING_GROUP": "Sincronizando com o grupo ({time})...", + "BTN_STATE_SYNCING": "Sincronizando...", + "BTN_STATE_SYNCED": "✅ Sincronizado!", + + "NOTIF_PLAY": "iniciou a reprodução", + "NOTIF_PAUSE": "pausou a reprodução", + "NOTIF_SEEK": "mudou a posição do vídeo", + "NOTIF_FORCE_PREPARE": "iniciou uma sincronização forçada", + "NOTIF_FORCE_EXECUTE": "sincronizou todos", + + "DEBUG_NO_TAB": "Nenhuma aba alvo selecionada.", + "DEBUG_COMM_FAIL": "Não foi possível comunicar com o vídeo da aba.", + + "EMPTY_PEERS_TITLE": "Nenhum participante ainda", + "EMPTY_PEERS_HINT": "Compartilhe seu link de convite para começar", + "EMPTY_HISTORY_TITLE": "Sem atividade ainda", + "EMPTY_HISTORY_HINT": "Reproduza, pause ou busque para ver o histórico", + "EMPTY_LOGS_TITLE": "Sem logs", + "EMPTY_LOGS_HINT": "Os eventos de conexão aparecerão aqui", + "EMPTY_ROOMS_TITLE": "Sem salas ativas", + "EMPTY_ROOMS_HINT": "Crie uma sala ou atualize para buscar salas públicas" +} diff --git a/extension/locales/ru.json b/extension/locales/ru.json new file mode 100644 index 0000000..514f6f7 --- /dev/null +++ b/extension/locales/ru.json @@ -0,0 +1,177 @@ +{ + "LANG_CODE": "ru", + "HTML_CLASS": "lang-ru", + + "APP_TITLE": "KoalaSync", + "TAB_ROOM": "Комната", + "TAB_ROOM_TOOLTIP": "Настройки комнаты и подключение", + "TAB_SYNC": "Синхро", + "TAB_SYNC_TOOLTIP": "Управление синхронизацией видео и действия", + "TAB_SETTINGS": "Опции", + "TAB_SETTINGS_TOOLTIP": "Настройки расширения", + "TAB_STATUS": "Статус", + "TAB_STATUS_TOOLTIP": "Дополнительная диагностика и журналы", + + "BTN_CREATE_ROOM": "+ Создать комнату", + "MANUAL_CONNECT_HEADER": "Ручное подключение / Дополнительно", + "LABEL_SERVER": "Сервер", + "BTN_SERVER_OFFICIAL": "Официальный", + "BTN_SERVER_OFFICIAL_TOOLTIP": "Использовать официальный надежный сервер", + "BTN_SERVER_CUSTOM": "Свой", + "BTN_SERVER_CUSTOM_TOOLTIP": "Подключиться к своему выделенному серверу", + "PLACEHOLDER_SERVER_URL": "wss://ваш-сервер:3000", + "LABEL_ROOM_ID": "ID комнаты", + "LABEL_ROOM_ID_TOOLTIP": "Уникальный идентификатор вашей синхронной комнаты", + "PLACEHOLDER_ROOM_ID": "Введите ID комнаты", + "PLACEHOLDER_ROOM_ID_TOOLTIP": "Уникальный ID комнаты, к которой хотите присоединиться", + "LABEL_PASSWORD": "Пароль (Опционально)", + "LABEL_PASSWORD_TOOLTIP": "Дополнительный пароль для ограничения доступа к комнате", + "PLACEHOLDER_PASSWORD": "Пароль комнаты (опционально)", + "PLACEHOLDER_PASSWORD_TOOLTIP": "Пароль от комнаты (оставьте пустым, если его нет)", + "BTN_JOIN_ROOM": "Войти в комнату", + "BTN_JOIN_ROOM_TOOLTIP": "Подключиться к комнате", + "LABEL_PUBLIC_ROOMS": "Публичные комнаты", + "LABEL_PUBLIC_ROOMS_TOOLTIP": "Список публично доступных комнат на этом сервере", + "BTN_REFRESH": "ОБНОВИТЬ", + "BTN_REFRESH_TOOLTIP": "Обновить список публичных комнат", + "PUBLIC_ROOMS_REFRESHING": "Обновление...", + + "LABEL_ACTIVE_ROOM": "Активная комната", + "LABEL_ACTIVE_ROOM_TOOLTIP": "Комната, к которой вы сейчас подключены", + "ACTIVE_ROOM_NONE": "НЕТ", + "ACTIVE_SERVER_OFFICIAL": "Официальный сервер", + "LABEL_INVITE_LINK": "Ссылка-приглашение", + "LABEL_INVITE_LINK_TOOLTIP": "Поделитесь этой ссылкой с друзьями, чтобы они вошли", + "LABEL_PEERS_IN_ROOM": "Участники в комнате", + "LABEL_PEERS_IN_ROOM_TOOLTIP": "Другие пользователи, подключенные к этой комнате", + "NO_PEERS_CONNECTED": "Нет подключенных участников", + "BTN_LEAVE_ROOM": "Выйти из комнаты", + + "LABEL_SELECT_VIDEO": "Выбрать видео", + "LABEL_SELECT_VIDEO_TOOLTIP": "Выберите вкладку браузера, содержащую видео для синхронизации", + "OPTION_SELECT_TAB": "-- Выберите вкладку --", + "LABEL_REMOTE_CONTROL": "Дистанционное управление", + "BTN_COPY_INVITE": "📋 Ссылка", + "BTN_COPY_INVITE_TOOLTIP": "Копировать ссылку-приглашение", + "BTN_PLAY": "▶ Запуск", + "BTN_PLAY_TOOLTIP": "Отправить команду воспроизведения всем", + "BTN_PAUSE": "⏸ Пауза", + "BTN_PAUSE_TOOLTIP": "Отправить команду паузы всем", + "BTN_SYNC": "⚡ СИНХРО", + "BTN_SYNC_TOOLTIP": "Принудительно синхронизировать всех участников", + "OPTION_JUMP_TO_OTHERS": "Перейти к другим", + "OPTION_JUMP_TO_ME": "Синхронизировать по мне", + "OPTION_JUMP_MODE_TOOLTIP": "Выбрать цель синхронизации", + "LABEL_LAST_ACTIVITY": "Последняя активность", + "LABEL_LAST_ACTIVITY_TOOLTIP": "Показывает самую последнюю команду воспроизведения, паузы или перемотки", + "NO_RECENT_COMMANDS": "Нет последних команд", + "LOBBY_HEADER": "ЛОББИ СЕРИИ", + "LOBBY_WAITING_FOR": "🎬 Ожидание: \"{title}\"", + "LOBBY_WAITING_PEERS": "Ожидание участников...", + "BTN_SKIP_PLAY": "Пропустить и запустить", + "BTN_SKIP_PLAY_TOOLTIP": "Закрыть лобби и воспроизвести в любом случае", + "LOBBY_CONNECT_FIRST": "Сначала войдите в комнату", + "LOBBY_CONNECT_FIRST_DESC": "Вам необходимо войти в комнату по ссылке-приглашению или создать новую, чтобы синхронизировать видео.", + "BTN_CREATE_ROOM_ALT": "Создать новую комнату", + "BTN_CREATE_ROOM_ALT_TOOLTIP": "Создать новую случайную комнату и войти", + + "LABEL_USERNAME": "Ваше имя пользователя", + "LABEL_USERNAME_TOOLTIP": "Имя пользователя помогает другим узнать вас.", + "PLACEHOLDER_USERNAME": "Анонимный Коала", + "LABEL_HIDE_CLUTTER": "Чистый список вкладок", + "LABEL_HIDE_CLUTTER_TOOLTIP": "Скрывает вкладки без видео и несвязанные домены для порядка", + "LABEL_AUTO_SYNC_NEXT": "Автосинхрон следующей серии", + "LABEL_AUTO_SYNC_NEXT_TOOLTIP": "Автоматически ставит на паузу и ждет всех при смене серии, затем запускает видео синхронно.", + "LABEL_AUTO_COPY_INVITE": "Автокопирование ссылки", + "LABEL_AUTO_COPY_INVITE_TOOLTIP": "Автоматически копирует ссылку-приглашение в буфер обмена при создании новой комнаты.", + "LABEL_NOTIFICATIONS": "Уведомления браузера", + "LABEL_NOTIFICATIONS_TOOLTIP": "Показывает системные уведомления, когда кто-то входит/выходит или запускает/приостанавливает воспроизведение.", + "LABEL_LANGUAGE": "Язык приложения", + "LABEL_LANGUAGE_TOOLTIP": "Выберите предпочтительный язык интерфейса расширения", + "LABEL_TROUBLESHOOTING": "Устранение неполадок", + "LABEL_TROUBLESHOOTING_TOOLTIP": "Инструменты для исправления проблем с подключением", + "BTN_REGEN_ID": "Сбросить Peer ID", + "BTN_REGEN_ID_TOOLTIP": "Сбросить ваш внутренний идентификатор и переподключиться", + "REGEN_ID_DESC": "Используйте при появлении ошибок дублирования идентификации.", + + "LABEL_CONN_STATUS": "Статус подключения", + "LABEL_CONN_STATUS_TOOLTIP": "Текущий статус WebSocket-соединения с сервером", + "CONN_STATUS_DISCONNECTED": "Отключено", + "BTN_RETRY": "ПОВТОРИТЬ", + "BTN_RETRY_TOOLTIP": "Попробовать переподключиться к серверу", + "BTN_COPY_LOGS": "Копировать логи", + "BTN_COPY_LOGS_TOOLTIP": "Скопировать логи в буфер обмена для отправки", + "LABEL_VIDEO_DEBUG": "Отладочные данные видео", + "LABEL_VIDEO_DEBUG_TOOLTIP": "Технические сведения о выбранном видеоэлементе", + "VIDEO_DEBUG_EMPTY": "Вкладка не выбрана или видео не обнаружено.", + "LABEL_HISTORY": "История действий", + "LABEL_HISTORY_TOOLTIP": "Хронологический список всех команд синхронизации в комнате", + "HISTORY_EMPTY": "Нет недавней активности", + "LABEL_LOGS": "Логи (Последние 50)", + "LABEL_LOGS_TOOLTIP": "Технические системные сообщения подключения", + "BTN_CLEAR": "ОЧИСТИТЬ", + "BTN_CLEAR_TOOLTIP": "Очистить вывод логов", + "LABEL_GITHUB": "Репозиторий GitHub", + + "BTN_ONBOARDING_SKIP": "Пропустить", + "BTN_ONBOARDING_SKIP_TOOLTIP": "Пропустить руководство", + "BTN_ONBOARDING_NEXT": "Далее", + "BTN_ONBOARDING_NEXT_TOOLTIP": "Перейти к следующему шагу", + "ONBOARDING_1_TITLE": "Добро пожаловать в KoalaSync!", + "ONBOARDING_1_TEXT": "Смотрите видео в идеальной синхронизации вместе — где бы вы ни находились. Давайте проведем небольшую экскурсию!", + "ONBOARDING_2_TITLE": "1. Создайте комнату", + "ONBOARDING_2_TEXT": "Начните здесь. Создайте комнату и поделитесь ссылкой-приглашением со своими друзьями.", + "ONBOARDING_3_TITLE": "2. Выберите видео", + "ONBOARDING_3_TEXT": "Выберите вкладку с видео для синхронизации. Запуск, пауза, перемотка — воспроизведение синхронизируется у всех.", + "ONBOARDING_4_TITLE": "3. Настройте профиль", + "ONBOARDING_4_TEXT": "Введите имя пользователя, чтобы друзья знали, кто совершает действия.", + "ONBOARDING_5_TITLE": "Всё готово!", + "ONBOARDING_5_TEXT": "Время готовить попкорн. Приятного совместного просмотра!", + + "ERR_CONN_TIMEOUT": "Время ожидания подключения истекло. Пожалуйста, попробуйте снова.", + "ERR_INVALID_SERVER_URL": "Неверный формат URL-адреса сервера.", + "ERR_IDENTITY_NOT_LOADED": "Идентификатор еще не загружен. Подождите немного и повторите попытку.", + "ERR_NO_PEERS_TIME": "Нет других участников с известной позицией плеера. Выберите 'Синхронизировать по мне'.", + "ERR_NO_VIDEO_TAB": "Не удалось подключиться к вкладке с видео.", + "ERR_SELECT_VIDEO": "Пожалуйста, сначала выберите вкладку с видео!", + "TOAST_INVITE_COPIED": "Ссылка скопирована в буфер обмена!", + "TOAST_COPY_FAILED": "Не удалось скопировать ссылку в буфер обмена", + "TOAST_LOBBY_SKIPPED": "Ожидание серии отменено.", + "TOAST_LOBBY_SKIP_FAILED": "Не удалось отменить ожидание серии.", + "TOAST_LOGS_COPIED": "Скопировано!", + "TOAST_PEER_JOINED": "{name} вошел в комнату", + "TOAST_PEER_LEFT": "{name} вышел из комнаты", + "TOAST_PEER_ACTION": "{name}: {action}", + + "STATUS_CONNECTED": "Подключено", + "STATUS_RECONNECTING": "Переподключение...", + "STATUS_CONNECTING": "Подключение...", + "STATUS_FAILED": "Ошибка", + "STATUS_DISCONNECTED": "Отключено", + + "BTN_STATE_JOINING": "🚀 Вход...", + "BTN_STATE_RECONNECTING": "🔄 Переподключение...", + "BTN_STATE_PLAYING": "▶ Запуск...", + "BTN_STATE_PAUSING": "⏸ Пауза...", + "BTN_STATE_SYNCING_GROUP": "Синхронизация по группе ({time})...", + "BTN_STATE_SYNCING": "Синхронизация...", + "BTN_STATE_SYNCED": "✅ Синхронно!", + + "NOTIF_PLAY": "запустил воспроизведение", + "NOTIF_PAUSE": "приостановил воспроизведение", + "NOTIF_SEEK": "перемотал видео", + "NOTIF_FORCE_PREPARE": "запустил принудительный синхрон", + "NOTIF_FORCE_EXECUTE": "синхронизировал воспроизведение у всех", + + "DEBUG_NO_TAB": "Целевая вкладка не выбрана.", + "DEBUG_COMM_FAIL": "Не удалось связаться с плеером на вкладке.", + + "EMPTY_PEERS_TITLE": "Участников пока нет", + "EMPTY_PEERS_HINT": "Поделитесь ссылкой-приглашением, чтобы начать", + "EMPTY_HISTORY_TITLE": "История действий пуста", + "EMPTY_HISTORY_HINT": "Запускайте, останавливайте или перематывайте видео, чтобы увидеть лог", + "EMPTY_LOGS_TITLE": "Журнал пуст", + "EMPTY_LOGS_HINT": "События подключения будут отображаться здесь", + "EMPTY_ROOMS_TITLE": "Нет активных комнат", + "EMPTY_ROOMS_HINT": "Создайте комнату или обновите список, чтобы найти публичные" +} diff --git a/extension/popup.html b/extension/popup.html index 50d8cf9..7e5c000 100644 --- a/extension/popup.html +++ b/extension/popup.html @@ -315,7 +315,7 @@
-

KoalaSync LogoKoalaSync

+

KoalaSync LogoKoalaSync

@@ -325,10 +325,10 @@
- - - - + + + +
@@ -336,35 +336,35 @@
- +
- Manual Connect / Advanced + Manual Connect / Advanced
- +
- - + +
- +
- - + +
- - + +
- +
- - + +
-
Refreshing...
+
Refreshing...
@@ -374,14 +374,14 @@ @@ -404,44 +404,44 @@
- +
- - + +
- - + +
- - + +
- +
-
No recent commands
+
No recent commands
@@ -450,21 +450,21 @@
- - + +
- +
- +
- +
- +
- +
+ + +
- - -

Use this if you see "Duplicate Identity" errors.

+ + +

Use this if you see "Duplicate Identity" errors.

- +
- Disconnected - - + Disconnected + +
- -
@@ -543,12 +553,12 @@