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 +
Use this if you see "Duplicate Identity" errors.
+ + +Use this if you see "Duplicate Identity" errors.